From d4e3f504e2e3b35c8b23c82dfe4cf88daa11d004 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 5 Jun 2026 16:06:12 -0700 Subject: [PATCH] JIT/wasm: implement CKFINITE Adds wasm codegen for the CKFINITE IL opcode, which throws ArithmeticException if the float/double operand is NaN or +/-Inf and otherwise passes the value through. The check is implemented by computing "!(|x| < +Inf)". This is true for NaN and +/-Inf and false for every finite value: for finite values |x| < +Inf is 1; for +/-Inf it is 0; for NaN any wasm float comparison other than "ne" returns 0. If the resulting bool is non-zero we jump to the SCK_ARITH_EXCPN throw helper; otherwise the original value (left on the wasm operand stack by genConsumeOperands) flows through as the result. To make the operand available twice (once for the abs+lt check and once as the produced value), GT_CKFINITE is wired into the wasm multi-use mechanism: - Lowering::LowerCkfinite calls SetMultiplyUsed on op1, which triggers the regalloc layer to allocate a temporary local and emit a local.tee. - WasmRegAlloc::CollectReferencesForNode releases the temporary local once the GT_CKFINITE consumer is processed. - CodeGen::genCkfinite emits the local.get + f{32,64}.abs + f{32,64}.const +Inf + f{32,64}.lt + i32.eqz + br_if sequence and then calls WasmProduceReg on the GT_CKFINITE node itself. Fixes the NYI assert that was blocking R2R compilation of the b25459 (JIT/Regression/CLR-x86-JIT/V1-M09.5-PDC) regression test on wasm: codegenwasm.cpp:878 Assertion failed 'CKFINITE' ... during 'Generate code' Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/coreclr/jit/codegenwasm.cpp | 68 ++++++++++++++++++++++++++++++++ src/coreclr/jit/lower.cpp | 4 ++ src/coreclr/jit/lower.h | 1 + src/coreclr/jit/lowerwasm.cpp | 15 +++++++ src/coreclr/jit/regallocwasm.cpp | 4 ++ 5 files changed, 92 insertions(+) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index e8f750d8f9e9dd..cca0f36aa403d3 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -868,6 +868,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genCatchArg(treeNode); break; + case GT_CKFINITE: + genCkfinite(treeNode); + break; + default: #ifdef DEBUG if (JitConfig.JitWasmNyiToR2RUnsupported()) @@ -1845,6 +1849,70 @@ void CodeGen::genCodeForBitCast(GenTreeOp* tree) WasmProduceReg(tree); } +//------------------------------------------------------------------------ +// genCkfinite: Generate code for ckfinite opcode. +// +// Arguments: +// treeNode - The GT_CKFINITE node +// +// Notes: +// The operand is expected to be marked MultiplyUsed so that codegen can +// re-use its value (via "local.get") after the check. +// +void CodeGen::genCkfinite(GenTree* treeNode) +{ + assert(treeNode->OperIs(GT_CKFINITE)); + + GenTree* op1 = treeNode->AsOp()->gtOp1; + var_types targetType = treeNode->TypeGet(); + assert(varTypeIsFloating(targetType)); + + // Push the operand value on the wasm stack. Because op1 was flagged as + // MultiplyUsed during lowering, "WasmProduceReg" will tee it into a + // temporary local so we can re-read it for the exponent check. + // + genConsumeOperands(treeNode->AsOp()); + + // Re-read the operand to feed the finiteness check. + // + regNumber op1Reg = GetMultiUseOperandReg(op1); + emitter* emit = GetEmitter(); + + // Compute "!(|x| < +Inf)". This is true for NaN and +/-Inf, false for + // every finite value. We rely on wasm's IEEE-754 comparison semantics + // where any comparison involving a NaN (other than "ne") returns 0. + // + // Note: IF_F32/IF_F64 both expect the +Inf constant as a double bit + // pattern (IF_F32 reinterprets and truncates to float during emission). + // + const int64_t infBits = 0x7FF0000000000000LL; + if (targetType == TYP_FLOAT) + { + emit->emitIns_I(INS_local_get, EA_4BYTE, WasmRegToIndex(op1Reg)); + emit->emitIns(INS_f32_abs); + emit->emitIns_I(INS_f32_const, EA_4BYTE, infBits); + emit->emitIns(INS_f32_lt); + } + else + { + assert(targetType == TYP_DOUBLE); + emit->emitIns_I(INS_local_get, EA_8BYTE, WasmRegToIndex(op1Reg)); + emit->emitIns(INS_f64_abs); + emit->emitIns_I(INS_f64_const, EA_8BYTE, infBits); + emit->emitIns(INS_f64_lt); + } + emit->emitIns(INS_i32_eqz); + + // If "!(|x| < +Inf)", the value is NaN or +/-Inf; throw. + // + genJumpToThrowHlpBlk(SCK_ARITH_EXCPN); + + // The operand value is still on the wasm stack from genConsumeOperands; + // produce it as the result of the GT_CKFINITE node. + // + WasmProduceReg(treeNode); +} + //------------------------------------------------------------------------ // genCodeForNegNot: Generate code for a neg/not // diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index b122b8de775259..165985601485f4 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -710,6 +710,10 @@ GenTree* Lowering::LowerNode(GenTree* node) case GT_INDEX_ADDR: LowerIndexAddr(node->AsIndexAddr()); break; + + case GT_CKFINITE: + LowerCkfinite(node->AsOp()); + break; #endif // defined(TARGET_WASM) default: diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index e7c59dca277742..a782a1b9e5aeca 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -458,6 +458,7 @@ class Lowering final : public Phase static void SetMultiplyUsed(GenTree* node DEBUGARG(const char* reason)); GenTree* LowerNeg(GenTreeOp* node); void LowerIndexAddr(GenTreeIndexAddr* indexAddr); + void LowerCkfinite(GenTreeOp* node); #endif bool TryCreateAddrMode(GenTree* addr, bool isContainable, GenTree* parent); diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index 750fd6bed58612..585c3346e8f956 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -370,6 +370,21 @@ void Lowering::LowerRotate(GenTree* tree) ContainCheckShiftRotate(tree->AsOp()); } +//------------------------------------------------------------------------ +// LowerCkfinite: Lowers a GT_CKFINITE node. +// +// Mark the operand as multiply-used since codegen needs to read it twice: +// once for the finiteness check and once for the produced value. +// +// Arguments: +// node - the GT_CKFINITE node to be lowered +// +void Lowering::LowerCkfinite(GenTreeOp* node) +{ + assert(node->OperIs(GT_CKFINITE)); + SetMultiplyUsed(node->gtGetOp1() DEBUGARG("LowerCkfinite op1 (finiteness check)")); +} + //------------------------------------------------------------------------ // LowerIndexAddr: Lowers a GT_INDEX_ADDR node // diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index e6e4a0969f3ba0..45addb5b57c544 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -498,6 +498,10 @@ void WasmRegAlloc::CollectReferencesForNode(GenTree* node) CollectReferencesForIndexAddr(node->AsIndexAddr()); break; + case GT_CKFINITE: + ConsumeTemporaryRegForOperand(node->gtGetOp1() DEBUGARG("ckfinite finiteness check")); + break; + default: assert(!node->OperIsLocalStore()); break;