From 9e9831480ae4d0c602fbcbe4d9974b191349f2e8 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Sun, 22 Feb 2026 09:40:24 -0800 Subject: [PATCH 1/8] [Wasm RyuJit] internal registers, integer binop overflow checks Add support for internal registers. Use these to implement overflow checks for ADD/SUB/MUL, where the result of the operation is multiply used (though for long MUL, defer to a runtime helper). --- src/coreclr/jit/codegen.h | 1 + src/coreclr/jit/codegenwasm.cpp | 141 ++++++++++++++++++++++++++++--- src/coreclr/jit/lowerwasm.cpp | 7 ++ src/coreclr/jit/morph.cpp | 16 ++++ src/coreclr/jit/regallocwasm.cpp | 89 ++++++++++++++++++- src/coreclr/jit/regallocwasm.h | 4 + 6 files changed, 245 insertions(+), 13 deletions(-) diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 96954703599c93..8472dfe8c0233d 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -304,6 +304,7 @@ class CodeGen final : public CodeGenInterface #if defined(TARGET_WASM) void genJumpToThrowHlpBlk(SpecialCodeKind codeKind); + void genCodeForBinaryOverflow(GenTreeOp* node); #else void genJumpToThrowHlpBlk(emitJumpKind jumpKind, SpecialCodeKind codeKind, BasicBlock* failBlk = nullptr); #endif diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 77a17073e0f399..b2758978a8fb11 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -1026,19 +1026,21 @@ void CodeGen::genFloatToFloatCast(GenTree* tree) // void CodeGen::genCodeForBinary(GenTreeOp* treeNode) { + if (treeNode->gtOverflow()) + { + genCodeForBinaryOverflow(treeNode); + return; + } + genConsumeOperands(treeNode); instruction ins; switch (PackOperAndType(treeNode->OperGet(), treeNode->TypeGet())) { case PackOperAndType(GT_ADD, TYP_INT): - if (treeNode->gtOverflow()) - NYI_WASM("Overflow checks"); ins = INS_i32_add; break; case PackOperAndType(GT_ADD, TYP_LONG): - if (treeNode->gtOverflow()) - NYI_WASM("Overflow checks"); ins = INS_i64_add; break; case PackOperAndType(GT_ADD, TYP_FLOAT): @@ -1049,13 +1051,9 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode) break; case PackOperAndType(GT_SUB, TYP_INT): - if (treeNode->gtOverflow()) - NYI_WASM("Overflow checks"); ins = INS_i32_sub; break; case PackOperAndType(GT_SUB, TYP_LONG): - if (treeNode->gtOverflow()) - NYI_WASM("Overflow checks"); ins = INS_i64_sub; break; case PackOperAndType(GT_SUB, TYP_FLOAT): @@ -1066,13 +1064,9 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode) break; case PackOperAndType(GT_MUL, TYP_INT): - if (treeNode->gtOverflow()) - NYI_WASM("Overflow checks"); ins = INS_i32_mul; break; case PackOperAndType(GT_MUL, TYP_LONG): - if (treeNode->gtOverflow()) - NYI_WASM("Overflow checks"); ins = INS_i64_mul; break; case PackOperAndType(GT_MUL, TYP_FLOAT): @@ -1113,6 +1107,129 @@ void CodeGen::genCodeForBinary(GenTreeOp* treeNode) WasmProduceReg(treeNode); } +//------------------------------------------------------------------------ +// genCodeForBinaryOverflow: Generate code for a binary arithmetic operator +// with overflow checking +// +// Arguments: +// treeNode - The binary operation for which we are generating code. +// +void CodeGen::genCodeForBinaryOverflow(GenTreeOp* treeNode) +{ + assert(treeNode->gtOverflow()); + assert(varTypeIsIntegral(treeNode->TypeGet())); + + genConsumeOperands(treeNode); + + const bool is64BitOp = treeNode->TypeIs(TYP_LONG); + InternalRegs* regs = internalRegisters.GetAll(treeNode); + regNumber op1Reg = GetMultiUseOperandReg(treeNode->gtGetOp1()); + regNumber op2Reg = GetMultiUseOperandReg(treeNode->gtGetOp2()); + + switch (treeNode->OperGet()) + { + case GT_ADD: + { + // We require an internal register. + assert(regs->Count() == 1); + regNumber resultReg = regs->Extract(); + assert(WasmRegToType(resultReg) == TypeToWasmValueType(treeNode->TypeGet())); + + // Add and save the sum + GetEmitter()->emitIns(is64BitOp ? INS_i64_add : INS_i32_add); + GetEmitter()->emitIns_I(INS_local_tee, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + // See if addends had the same sign. XOR leaves a non-negative result if they had the same sign. + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op2Reg)); + GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); + GetEmitter()->emitIns(is64BitOp ? INS_i64_ge_s : INS_i32_ge_s); + GetEmitter()->emitIns(INS_if); + { + // Operands have the same sign. If the sum has a different sign, then the add overflowed. + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); + GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); + GetEmitter()->emitIns(is64BitOp ? INS_i64_lt_s : INS_i32_lt_s); + genJumpToThrowHlpBlk(SCK_OVERFLOW); + } + GetEmitter()->emitIns(INS_end); + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + break; + } + + case GT_SUB: + { + // We require an internal register. + assert(regs->Count() == 1); + regNumber resultReg = regs->Extract(); + assert(WasmRegToType(resultReg) == TypeToWasmValueType(treeNode->TypeGet())); + + // Subtract and save the difference + GetEmitter()->emitIns(is64BitOp ? INS_i64_sub : INS_i32_sub); + GetEmitter()->emitIns_I(INS_local_tee, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + // See if operands had a different sign. XOR leaves a negative result if they had different signs. + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op2Reg)); + GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); + GetEmitter()->emitIns(is64BitOp ? INS_i64_lt_s : INS_i32_lt_s); + GetEmitter()->emitIns(INS_if); + { + // Operands have different signs. If the difference has a different sign than op1, then the subtraction overflowed. + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); + GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); + GetEmitter()->emitIns(is64BitOp ? INS_i64_lt_s : INS_i32_lt_s); + genJumpToThrowHlpBlk(SCK_OVERFLOW); + } + GetEmitter()->emitIns(INS_end); + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + break; + } + + case GT_MUL: + { + if (is64BitOp) + { + assert(!"64 bit multply with overflow should have been transformed into a helper call by morph"); + } + + // We require an I64 internal register + assert(regs->Count() == 1); + regNumber wideReg = regs->Extract(); + assert(WasmRegToType(wideReg) == WasmValueType::I64); + + // 32 bit multiply... check by doing a 64 bit multiply and then range-checking the result + // (I suppose we could do this transformation in morph too). + const bool isUnsigned = varTypeIsUnsigned(treeNode->TypeGet()); + // Both operands are on the stack as I32. Drop the second, extend the first, then extend the second. + GetEmitter()->emitIns(INS_drop); + GetEmitter()->emitIns(isUnsigned ? INS_i64_extend_u_i32 : INS_i64_extend_s_i32); + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op2Reg)); + GetEmitter()->emitIns(isUnsigned ? INS_i64_extend_u_i32 : INS_i64_extend_s_i32); + GetEmitter()->emitIns(INS_i64_mul); + + // Save the wide result, and then overflow check it. + GetEmitter()->emitIns_I(INS_local_tee, emitActualTypeSize(treeNode), WasmRegToIndex(wideReg)); + + // Can't make a Desc right now... + // genIntCastOverflowCheck(nullptr, desc, resultReg); + + // If the check succeeds, the multiplication result is in range for a 32-bit int. + // We just need to return the low 32 bits of the result. + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(wideReg)); + GetEmitter()->emitIns(INS_i32_wrap_i64); + + break; + } + + default: + unreached(); + break; + } + + WasmProduceReg(treeNode); +} + //------------------------------------------------------------------------ // genCodeForDivMod: Generate code for a division or modulus operator // diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index 710b1d125c4b2f..4439695efa85fc 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -165,6 +165,13 @@ GenTree* Lowering::LowerJTrue(GenTreeOp* jtrue) GenTree* Lowering::LowerBinaryArithmetic(GenTreeOp* binOp) { ContainCheckBinary(binOp); + + if (binOp->gtOverflow() && (binOp->OperIs(GT_ADD, GT_SUB))) + { + binOp->gtGetOp1()->gtLIRFlags |= LIR::Flags::MultiplyUsed; + binOp->gtGetOp2()->gtLIRFlags |= LIR::Flags::MultiplyUsed; + } + return binOp->gtNext; } diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 9c5404b9c9eac6..0e2b55c33b0d33 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -7127,6 +7127,22 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA } } #endif // !defined(TARGET_64BIT) && !defined(TARGET_WASM) + +#if defined(TARGET_WASM) + if (tree->gtOverflow()) + { + // For long multiply with overflow, call the helper. + if (tree->TypeIs(TYP_LONG)) + { + helper = tree->IsUnsigned() ? CORINFO_HELP_ULMUL_OVF : CORINFO_HELP_LMUL_OVF; + goto USE_HELPER_FOR_ARITH; + } + else + { + // TODO-WASM_CQ: Transform to a long multiply and then a checked cast? + } + } +#endif break; case GT_ARR_LENGTH: diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index f8ec95b3d29964..995c36849764e5 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -218,7 +218,21 @@ regNumber WasmRegAlloc::AllocateTemporaryRegister(var_types type) regNumber WasmRegAlloc::ReleaseTemporaryRegister(var_types type) { WasmValueType wasmType = TypeToWasmValueType(type); - unsigned index = m_temporaryRegs[static_cast(wasmType)].Pop(); + return ReleaseTemporaryRegister(wasmType); +} + +//------------------------------------------------------------------------ +// ReleaseTemporaryRegister: Release the most recently allocated temporary register. +// +// Arguments: +// wasmType - The register's wasm type +// +// Return Value: +// The released register. +// +regNumber WasmRegAlloc::ReleaseTemporaryRegister(WasmValueType wasmType) +{ + unsigned index = m_temporaryRegs[static_cast(wasmType)].Pop(); return MakeWasmReg(index, wasmType); } @@ -290,6 +304,15 @@ void WasmRegAlloc::CollectReferencesForNode(GenTree* node) CollectReferencesForCast(node->AsOp()); break; + case GT_ADD: + case GT_SUB: + case GT_MUL: + case GT_OR: + case GT_XOR: + case GT_AND: + CollectReferencesForBinop(node->AsOp()); + break; + default: assert(!node->OperIsLocalStore()); break; @@ -343,6 +366,34 @@ void WasmRegAlloc::CollectReferencesForCast(GenTreeOp* castNode) ConsumeTemporaryRegForOperand(castNode->gtGetOp1() DEBUGARG("cast overflow check")); } +//------------------------------------------------------------------------ +// CollectReferencesForBinop: Collect virtual register references for a binary operation. +// +// Consumes temporary registers for a binary operation. +// +// Arguments: +// binopNode - The binary operation node +// +void WasmRegAlloc::CollectReferencesForBinop(GenTreeOp* binopNode) +{ + if (binopNode->gtOverflow()) + { + if (binopNode->OperIs(GT_ADD) || binopNode->OperIs(GT_SUB)) + { + RequestInternalRegister(binopNode, binopNode->TypeGet()); + } + else if (binopNode->OperIs(GT_MUL)) + { + assert(binopNode->TypeIs(TYP_INT)); + RequestInternalRegister(binopNode, TYP_LONG); + } + } + + ConsumeTemporaryRegForOperand(binopNode->gtGetOp2() DEBUGARG("binop overflow check")); + ConsumeTemporaryRegForOperand(binopNode->gtGetOp1() DEBUGARG("binop overflow check")); + ConsumeInternalRegisters(binopNode DEBUGARG("binop overflow check")); +} + //------------------------------------------------------------------------ // RewriteLocalStackStore: rewrite a store to the stack to STOREIND(LCL_ADDR, ...). // @@ -475,6 +526,42 @@ void WasmRegAlloc::ConsumeTemporaryRegForOperand(GenTree* operand DEBUGARG(const JITDUMP("Consumed a temporary reg for [%06u]: %s\n", Compiler::dspTreeID(operand), reason); } +//------------------------------------------------------------------------ +// RequestInternalRegisterForNode: request an internal register for a node with specific type. +// +// To be later assigned a physical register. +// +// Arguments: +// node - node whose codegen will need an internal register +// type - type of the internal register +// +void WasmRegAlloc::RequestInternalRegister(GenTree* node, var_types type) +{ + regNumber reg = AllocateTemporaryRegister(genActualType(type)); + m_codeGen->internalRegisters.Add(node, reg); +} + +//------------------------------------------------------------------------ +// ConsumeInternalRegisters: Consume the internal registers for a node +// +// Arguments: +// node - node that may have requested internal registers +// +void WasmRegAlloc::ConsumeInternalRegisters(GenTree* node DEBUGARG(const char *reason)) +{ + InternalRegs* internalRegs = m_codeGen->internalRegisters.GetAll(node); + + for (unsigned i = 0; i < internalRegs->Count(); i++) + { + regNumber reg = internalRegs->GetAt(i); + WasmValueType wasmType = WasmRegToType(reg); + regNumber reg2 = ReleaseTemporaryRegister(wasmType); + assert(reg == reg2); + + JITDUMP("Consumed an internal %s reg for [%06u]\n", WasmValueTypeName(wasmType), Compiler::dspTreeID(node)); + } +} + //------------------------------------------------------------------------ // ResolveReferences: Translate virtual registers to physical ones (WASM locals). // diff --git a/src/coreclr/jit/regallocwasm.h b/src/coreclr/jit/regallocwasm.h index 2b9fa9e6324fec..b52e206b95e848 100644 --- a/src/coreclr/jit/regallocwasm.h +++ b/src/coreclr/jit/regallocwasm.h @@ -114,6 +114,7 @@ class WasmRegAlloc : public RegAllocInterface regNumber AllocateVirtualRegister(WasmValueType type); regNumber AllocateTemporaryRegister(var_types type); regNumber ReleaseTemporaryRegister(var_types type); + regNumber ReleaseTemporaryRegister(WasmValueType wasmType); void CollectReferences(); void CollectReferencesForBlock(BasicBlock* block); @@ -121,10 +122,13 @@ class WasmRegAlloc : public RegAllocInterface void CollectReferencesForDivMod(GenTreeOp* divModNode); void CollectReferencesForCall(GenTreeCall* callNode); void CollectReferencesForCast(GenTreeOp* castNode); + void CollectReferencesForBinop(GenTreeOp* binOpNode); void RewriteLocalStackStore(GenTreeLclVarCommon* node); void CollectReference(GenTree* node); void RequestTemporaryRegisterForMultiplyUsedNode(GenTree* node); + void RequestInternalRegister(GenTree* node, var_types type); void ConsumeTemporaryRegForOperand(GenTree* operand DEBUGARG(const char* reason)); + void ConsumeInternalRegisters(GenTree* node DEBUGARG(const char* reason)); void ResolveReferences(); From a6f4995249142449c27eb211c9c9e701677f0df0 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Sun, 22 Feb 2026 13:56:55 -0800 Subject: [PATCH 2/8] format; feedback --- src/coreclr/jit/codegenwasm.cpp | 165 ++++++++++++++++--------------- src/coreclr/jit/regallocwasm.cpp | 9 +- 2 files changed, 86 insertions(+), 88 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index b2758978a8fb11..4784f8a20df54b 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -1121,110 +1121,111 @@ void CodeGen::genCodeForBinaryOverflow(GenTreeOp* treeNode) genConsumeOperands(treeNode); - const bool is64BitOp = treeNode->TypeIs(TYP_LONG); - InternalRegs* regs = internalRegisters.GetAll(treeNode); - regNumber op1Reg = GetMultiUseOperandReg(treeNode->gtGetOp1()); - regNumber op2Reg = GetMultiUseOperandReg(treeNode->gtGetOp2()); + const bool is64BitOp = treeNode->TypeIs(TYP_LONG); + InternalRegs* regs = internalRegisters.GetAll(treeNode); + regNumber op1Reg = GetMultiUseOperandReg(treeNode->gtGetOp1()); + regNumber op2Reg = GetMultiUseOperandReg(treeNode->gtGetOp2()); switch (treeNode->OperGet()) { - case GT_ADD: - { - // We require an internal register. - assert(regs->Count() == 1); - regNumber resultReg = regs->Extract(); - assert(WasmRegToType(resultReg) == TypeToWasmValueType(treeNode->TypeGet())); - - // Add and save the sum - GetEmitter()->emitIns(is64BitOp ? INS_i64_add : INS_i32_add); - GetEmitter()->emitIns_I(INS_local_tee, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); - // See if addends had the same sign. XOR leaves a non-negative result if they had the same sign. - GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); - GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op2Reg)); - GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); - GetEmitter()->emitIns(is64BitOp ? INS_i64_ge_s : INS_i32_ge_s); - GetEmitter()->emitIns(INS_if); + case GT_ADD: { - // Operands have the same sign. If the sum has a different sign, then the add overflowed. - GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + // We require an internal register. + assert(regs->Count() == 1); + regNumber resultReg = regs->Extract(); + assert(WasmRegToType(resultReg) == TypeToWasmValueType(treeNode->TypeGet())); + + // Add and save the sum + GetEmitter()->emitIns(is64BitOp ? INS_i64_add : INS_i32_add); + GetEmitter()->emitIns_I(INS_local_tee, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + // See if addends had the same sign. XOR leaves a non-negative result if they had the same sign. GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op2Reg)); GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); - GetEmitter()->emitIns(is64BitOp ? INS_i64_lt_s : INS_i32_lt_s); - genJumpToThrowHlpBlk(SCK_OVERFLOW); + GetEmitter()->emitIns(is64BitOp ? INS_i64_ge_s : INS_i32_ge_s); + GetEmitter()->emitIns(INS_if); + { + // Operands have the same sign. If the sum has a different sign, then the add overflowed. + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); + GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); + GetEmitter()->emitIns(is64BitOp ? INS_i64_lt_s : INS_i32_lt_s); + genJumpToThrowHlpBlk(SCK_OVERFLOW); + } + GetEmitter()->emitIns(INS_end); + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + break; } - GetEmitter()->emitIns(INS_end); - GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); - break; - } - - case GT_SUB: - { - // We require an internal register. - assert(regs->Count() == 1); - regNumber resultReg = regs->Extract(); - assert(WasmRegToType(resultReg) == TypeToWasmValueType(treeNode->TypeGet())); - // Subtract and save the difference - GetEmitter()->emitIns(is64BitOp ? INS_i64_sub : INS_i32_sub); - GetEmitter()->emitIns_I(INS_local_tee, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); - // See if operands had a different sign. XOR leaves a negative result if they had different signs. - GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); - GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op2Reg)); - GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); - GetEmitter()->emitIns(is64BitOp ? INS_i64_lt_s : INS_i32_lt_s); - GetEmitter()->emitIns(INS_if); + case GT_SUB: { - // Operands have different signs. If the difference has a different sign than op1, then the subtraction overflowed. - GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + // We require an internal register. + assert(regs->Count() == 1); + regNumber resultReg = regs->Extract(); + assert(WasmRegToType(resultReg) == TypeToWasmValueType(treeNode->TypeGet())); + + // Subtract and save the difference + GetEmitter()->emitIns(is64BitOp ? INS_i64_sub : INS_i32_sub); + GetEmitter()->emitIns_I(INS_local_tee, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + // See if operands had a different sign. XOR leaves a negative result if they had different signs. GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op2Reg)); GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); GetEmitter()->emitIns(is64BitOp ? INS_i64_lt_s : INS_i32_lt_s); - genJumpToThrowHlpBlk(SCK_OVERFLOW); + GetEmitter()->emitIns(INS_if); + { + // Operands have different signs. If the difference has a different sign than op1, then the subtraction + // overflowed. + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); + GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); + GetEmitter()->emitIns(is64BitOp ? INS_i64_lt_s : INS_i32_lt_s); + genJumpToThrowHlpBlk(SCK_OVERFLOW); + } + GetEmitter()->emitIns(INS_end); + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + break; } - GetEmitter()->emitIns(INS_end); - GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); - break; - } - case GT_MUL: - { - if (is64BitOp) + case GT_MUL: { - assert(!"64 bit multply with overflow should have been transformed into a helper call by morph"); - } + if (is64BitOp) + { + assert(!"64 bit multiply with overflow should have been transformed into a helper call by morph"); + } - // We require an I64 internal register - assert(regs->Count() == 1); - regNumber wideReg = regs->Extract(); - assert(WasmRegToType(wideReg) == WasmValueType::I64); + // We require an I64 internal register + assert(regs->Count() == 1); + regNumber wideReg = regs->Extract(); + assert(WasmRegToType(wideReg) == WasmValueType::I64); - // 32 bit multiply... check by doing a 64 bit multiply and then range-checking the result - // (I suppose we could do this transformation in morph too). - const bool isUnsigned = varTypeIsUnsigned(treeNode->TypeGet()); - // Both operands are on the stack as I32. Drop the second, extend the first, then extend the second. - GetEmitter()->emitIns(INS_drop); - GetEmitter()->emitIns(isUnsigned ? INS_i64_extend_u_i32 : INS_i64_extend_s_i32); - GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op2Reg)); - GetEmitter()->emitIns(isUnsigned ? INS_i64_extend_u_i32 : INS_i64_extend_s_i32); - GetEmitter()->emitIns(INS_i64_mul); + // 32 bit multiply... check by doing a 64 bit multiply and then range-checking the result + // (I suppose we could do this transformation in morph too). + const bool isUnsigned = varTypeIsUnsigned(treeNode->TypeGet()); + // Both operands are on the stack as I32. Drop the second, extend the first, then extend the second. + GetEmitter()->emitIns(INS_drop); + GetEmitter()->emitIns(isUnsigned ? INS_i64_extend_u_i32 : INS_i64_extend_s_i32); + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op2Reg)); + GetEmitter()->emitIns(isUnsigned ? INS_i64_extend_u_i32 : INS_i64_extend_s_i32); + GetEmitter()->emitIns(INS_i64_mul); - // Save the wide result, and then overflow check it. - GetEmitter()->emitIns_I(INS_local_tee, emitActualTypeSize(treeNode), WasmRegToIndex(wideReg)); + // Save the wide result, and then overflow check it. + GetEmitter()->emitIns_I(INS_local_tee, emitActualTypeSize(treeNode), WasmRegToIndex(wideReg)); - // Can't make a Desc right now... - // genIntCastOverflowCheck(nullptr, desc, resultReg); + // Can't make a Desc right now... + // genIntCastOverflowCheck(nullptr, desc, resultReg); - // If the check succeeds, the multiplication result is in range for a 32-bit int. - // We just need to return the low 32 bits of the result. - GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(wideReg)); - GetEmitter()->emitIns(INS_i32_wrap_i64); + // If the check succeeds, the multiplication result is in range for a 32-bit int. + // We just need to return the low 32 bits of the result. + GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(wideReg)); + GetEmitter()->emitIns(INS_i32_wrap_i64); - break; - } + break; + } - default: - unreached(); - break; + default: + unreached(); + break; } WasmProduceReg(treeNode); diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index 995c36849764e5..df51e2ff8878db 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -307,9 +307,6 @@ void WasmRegAlloc::CollectReferencesForNode(GenTree* node) case GT_ADD: case GT_SUB: case GT_MUL: - case GT_OR: - case GT_XOR: - case GT_AND: CollectReferencesForBinop(node->AsOp()); break; @@ -547,15 +544,15 @@ void WasmRegAlloc::RequestInternalRegister(GenTree* node, var_types type) // Arguments: // node - node that may have requested internal registers // -void WasmRegAlloc::ConsumeInternalRegisters(GenTree* node DEBUGARG(const char *reason)) +void WasmRegAlloc::ConsumeInternalRegisters(GenTree* node DEBUGARG(const char* reason)) { InternalRegs* internalRegs = m_codeGen->internalRegisters.GetAll(node); for (unsigned i = 0; i < internalRegs->Count(); i++) { - regNumber reg = internalRegs->GetAt(i); + regNumber reg = internalRegs->GetAt(i); WasmValueType wasmType = WasmRegToType(reg); - regNumber reg2 = ReleaseTemporaryRegister(wasmType); + regNumber reg2 = ReleaseTemporaryRegister(wasmType); assert(reg == reg2); JITDUMP("Consumed an internal %s reg for [%06u]\n", WasmValueTypeName(wasmType), Compiler::dspTreeID(node)); From 83b85d1620d808206ebdff6c292a8b614b4ca80d Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Sun, 22 Feb 2026 17:10:04 -0800 Subject: [PATCH 3/8] fixes --- src/coreclr/jit/codegenwasm.cpp | 4 ++-- src/coreclr/jit/lowerwasm.cpp | 2 +- src/coreclr/jit/regallocwasm.cpp | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 8d7bcb8aab6493..9fddd60ef28e25 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -1214,14 +1214,14 @@ void CodeGen::genCodeForBinaryOverflow(GenTreeOp* treeNode) GetEmitter()->emitIns(INS_i64_mul); // Save the wide result, and then overflow check it. - GetEmitter()->emitIns_I(INS_local_tee, emitActualTypeSize(treeNode), WasmRegToIndex(wideReg)); + GetEmitter()->emitIns_I(INS_local_tee, EA_8BYTE, WasmRegToIndex(wideReg)); // Can't make a Desc right now... // genIntCastOverflowCheck(nullptr, desc, resultReg); // If the check succeeds, the multiplication result is in range for a 32-bit int. // We just need to return the low 32 bits of the result. - GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(wideReg)); + GetEmitter()->emitIns_I(INS_local_get, EA_8BYTE, WasmRegToIndex(wideReg)); GetEmitter()->emitIns(INS_i32_wrap_i64); break; diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index 4439695efa85fc..b9f1f653772a58 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -166,7 +166,7 @@ GenTree* Lowering::LowerBinaryArithmetic(GenTreeOp* binOp) { ContainCheckBinary(binOp); - if (binOp->gtOverflow() && (binOp->OperIs(GT_ADD, GT_SUB))) + if (binOp->gtOverflow()) { binOp->gtGetOp1()->gtLIRFlags |= LIR::Flags::MultiplyUsed; binOp->gtGetOp2()->gtLIRFlags |= LIR::Flags::MultiplyUsed; diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index ccee2800f41676..f06686baf107a9 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -586,6 +586,11 @@ void WasmRegAlloc::ConsumeInternalRegisters(GenTree* node DEBUGARG(const char* r { InternalRegs* internalRegs = m_codeGen->internalRegisters.GetAll(node); + if (internalRegs == nullptr) + { + return; + } + for (unsigned i = 0; i < internalRegs->Count(); i++) { regNumber reg = internalRegs->GetAt(i); From 2c5447709b319bed8dfe4f41aaa10f64f3df16b2 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 23 Feb 2026 13:24:42 -0800 Subject: [PATCH 4/8] rework internal register logic --- src/coreclr/jit/regallocwasm.cpp | 44 ++++++++++---------------------- src/coreclr/jit/regallocwasm.h | 27 ++++++++++---------- 2 files changed, 27 insertions(+), 44 deletions(-) diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index f06686baf107a9..1ec59e1fbf4323 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -393,22 +393,28 @@ void WasmRegAlloc::CollectReferencesForCast(GenTreeOp* castNode) // void WasmRegAlloc::CollectReferencesForBinop(GenTreeOp* binopNode) { + regNumber internalReg = REG_NA; if (binopNode->gtOverflow()) { if (binopNode->OperIs(GT_ADD) || binopNode->OperIs(GT_SUB)) { - RequestInternalRegister(binopNode, binopNode->TypeGet()); + internalReg = RequestInternalRegister(binopNode, binopNode->TypeGet()); } else if (binopNode->OperIs(GT_MUL)) { assert(binopNode->TypeIs(TYP_INT)); - RequestInternalRegister(binopNode, TYP_LONG); + internalReg = RequestInternalRegister(binopNode, TYP_LONG); } } + if (internalReg != REG_NA) + { + regNumber releasedReg = ReleaseTemporaryRegister(WasmRegToType(internalReg)); + assert(releasedReg == internalReg); + } + ConsumeTemporaryRegForOperand(binopNode->gtGetOp2() DEBUGARG("binop overflow check")); ConsumeTemporaryRegForOperand(binopNode->gtGetOp1() DEBUGARG("binop overflow check")); - ConsumeInternalRegisters(binopNode DEBUGARG("binop overflow check")); } //------------------------------------------------------------------------ @@ -570,36 +576,14 @@ void WasmRegAlloc::ConsumeTemporaryRegForOperand(GenTree* operand DEBUGARG(const // node - node whose codegen will need an internal register // type - type of the internal register // -void WasmRegAlloc::RequestInternalRegister(GenTree* node, var_types type) +// Returns: +// reg number of internal register. +// +regNumber WasmRegAlloc::RequestInternalRegister(GenTree* node, var_types type) { regNumber reg = AllocateTemporaryRegister(genActualType(type)); m_codeGen->internalRegisters.Add(node, reg); -} - -//------------------------------------------------------------------------ -// ConsumeInternalRegisters: Consume the internal registers for a node -// -// Arguments: -// node - node that may have requested internal registers -// -void WasmRegAlloc::ConsumeInternalRegisters(GenTree* node DEBUGARG(const char* reason)) -{ - InternalRegs* internalRegs = m_codeGen->internalRegisters.GetAll(node); - - if (internalRegs == nullptr) - { - return; - } - - for (unsigned i = 0; i < internalRegs->Count(); i++) - { - regNumber reg = internalRegs->GetAt(i); - WasmValueType wasmType = WasmRegToType(reg); - regNumber reg2 = ReleaseTemporaryRegister(wasmType); - assert(reg == reg2); - - JITDUMP("Consumed an internal %s reg for [%06u]\n", WasmValueTypeName(wasmType), Compiler::dspTreeID(node)); - } + return reg; } //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/regallocwasm.h b/src/coreclr/jit/regallocwasm.h index 8f9f366f4ed44c..a24f07df1fa73d 100644 --- a/src/coreclr/jit/regallocwasm.h +++ b/src/coreclr/jit/regallocwasm.h @@ -117,20 +117,19 @@ class WasmRegAlloc : public RegAllocInterface regNumber ReleaseTemporaryRegister(var_types type); regNumber ReleaseTemporaryRegister(WasmValueType wasmType); - void CollectReferences(); - void CollectReferencesForBlock(BasicBlock* block); - void CollectReferencesForNode(GenTree* node); - void CollectReferencesForDivMod(GenTreeOp* divModNode); - void CollectReferencesForCall(GenTreeCall* callNode); - void CollectReferencesForCast(GenTreeOp* castNode); - void CollectReferencesForBinop(GenTreeOp* binOpNode); - void CollectReferencesForLclVar(GenTreeLclVar* lclVar); - void RewriteLocalStackStore(GenTreeLclVarCommon* node); - void CollectReference(GenTree* node); - void RequestTemporaryRegisterForMultiplyUsedNode(GenTree* node); - void RequestInternalRegister(GenTree* node, var_types type); - void ConsumeTemporaryRegForOperand(GenTree* operand DEBUGARG(const char* reason)); - void ConsumeInternalRegisters(GenTree* node DEBUGARG(const char* reason)); + void CollectReferences(); + void CollectReferencesForBlock(BasicBlock* block); + void CollectReferencesForNode(GenTree* node); + void CollectReferencesForDivMod(GenTreeOp* divModNode); + void CollectReferencesForCall(GenTreeCall* callNode); + void CollectReferencesForCast(GenTreeOp* castNode); + void CollectReferencesForBinop(GenTreeOp* binOpNode); + void CollectReferencesForLclVar(GenTreeLclVar* lclVar); + void RewriteLocalStackStore(GenTreeLclVarCommon* node); + void CollectReference(GenTree* node); + void RequestTemporaryRegisterForMultiplyUsedNode(GenTree* node); + regNumber RequestInternalRegister(GenTree* node, var_types type); + void ConsumeTemporaryRegForOperand(GenTree* operand DEBUGARG(const char* reason)); void ResolveReferences(); From d2023f81c563b4b8373d2413bb8483a83f0e0b5c Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 23 Feb 2026 13:25:21 -0800 Subject: [PATCH 5/8] implement mul overflow check --- src/coreclr/jit/codegenwasm.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index c5bdf286075171..70692878afe2e5 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -1222,8 +1222,20 @@ void CodeGen::genCodeForBinaryOverflow(GenTreeOp* treeNode) // Save the wide result, and then overflow check it. GetEmitter()->emitIns_I(INS_local_tee, EA_8BYTE, WasmRegToIndex(wideReg)); - // Can't make a Desc right now... - // genIntCastOverflowCheck(nullptr, desc, resultReg); + if (isUnsigned) + { + // For unsigned multiply, we just need to check if the result is greater than UINT32_MAX. + GetEmitter()->emitIns_I(INS_i64_const, EA_8BYTE, UINT32_MAX); + GetEmitter()->emitIns(INS_i64_gt_u); + genJumpToThrowHlpBlk(SCK_OVERFLOW); + } + else + { + GetEmitter()->emitIns(INS_i64_extend32_s); + GetEmitter()->emitIns_I(INS_local_get, EA_8BYTE, WasmRegToIndex(wideReg)); + GetEmitter()->emitIns(INS_i64_ne); + genJumpToThrowHlpBlk(SCK_OVERFLOW); + } // If the check succeeds, the multiplication result is in range for a 32-bit int. // We just need to return the low 32 bits of the result. From 9e3d25f2ac29687c59664005a2f8be7fd606bedb Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 23 Feb 2026 17:14:54 -0800 Subject: [PATCH 6/8] fix codegen; fix comment; add todo about maybe using helpers --- src/coreclr/jit/codegenwasm.cpp | 10 ++++++++-- src/coreclr/jit/regallocwasm.cpp | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 70692878afe2e5..1a29ba1faa5932 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -1129,6 +1129,8 @@ void CodeGen::genCodeForBinaryOverflow(GenTreeOp* treeNode) assert(treeNode->gtOverflow()); assert(varTypeIsIntegral(treeNode->TypeGet())); + // TODO-WASM-CQ: consider using helper calls for all these cases + genConsumeOperands(treeNode); const bool is64BitOp = treeNode->TypeIs(TYP_LONG); @@ -1147,11 +1149,12 @@ void CodeGen::genCodeForBinaryOverflow(GenTreeOp* treeNode) // Add and save the sum GetEmitter()->emitIns(is64BitOp ? INS_i64_add : INS_i32_add); - GetEmitter()->emitIns_I(INS_local_tee, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + GetEmitter()->emitIns_I(INS_local_set, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); // See if addends had the same sign. XOR leaves a non-negative result if they had the same sign. GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op2Reg)); GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); + GetEmitter()->emitIns_I(is64BitOp ? INS_i64_const : INS_i32_const, emitActualTypeSize(treeNode), 0); GetEmitter()->emitIns(is64BitOp ? INS_i64_ge_s : INS_i32_ge_s); GetEmitter()->emitIns(INS_if); { @@ -1159,6 +1162,7 @@ void CodeGen::genCodeForBinaryOverflow(GenTreeOp* treeNode) GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); + GetEmitter()->emitIns_I(is64BitOp ? INS_i64_const : INS_i32_const, emitActualTypeSize(treeNode), 0); GetEmitter()->emitIns(is64BitOp ? INS_i64_lt_s : INS_i32_lt_s); genJumpToThrowHlpBlk(SCK_OVERFLOW); } @@ -1176,11 +1180,12 @@ void CodeGen::genCodeForBinaryOverflow(GenTreeOp* treeNode) // Subtract and save the difference GetEmitter()->emitIns(is64BitOp ? INS_i64_sub : INS_i32_sub); - GetEmitter()->emitIns_I(INS_local_tee, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); + GetEmitter()->emitIns_I(INS_local_set, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); // See if operands had a different sign. XOR leaves a negative result if they had different signs. GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op2Reg)); GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); + GetEmitter()->emitIns_I(is64BitOp ? INS_i64_const : INS_i32_const, emitActualTypeSize(treeNode), 0); GetEmitter()->emitIns(is64BitOp ? INS_i64_lt_s : INS_i32_lt_s); GetEmitter()->emitIns(INS_if); { @@ -1189,6 +1194,7 @@ void CodeGen::genCodeForBinaryOverflow(GenTreeOp* treeNode) GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(resultReg)); GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); + GetEmitter()->emitIns_I(is64BitOp ? INS_i64_const : INS_i32_const, emitActualTypeSize(treeNode), 0); GetEmitter()->emitIns(is64BitOp ? INS_i64_lt_s : INS_i32_lt_s); genJumpToThrowHlpBlk(SCK_OVERFLOW); } diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index 1ec59e1fbf4323..dcd98577b71a5e 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -568,7 +568,7 @@ void WasmRegAlloc::ConsumeTemporaryRegForOperand(GenTree* operand DEBUGARG(const } //------------------------------------------------------------------------ -// RequestInternalRegisterForNode: request an internal register for a node with specific type. +// RequestInternalRegister: request an internal register for a node with specific type. // // To be later assigned a physical register. // From 2e2672dbafd2e79a2bc906a8797772fb9ce6eb82 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Tue, 24 Feb 2026 13:46:13 -0800 Subject: [PATCH 7/8] fix unsigned check; add notes about alternatives --- src/coreclr/jit/codegenwasm.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 1a29ba1faa5932..1743ee57c84e04 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -1154,6 +1154,8 @@ void CodeGen::genCodeForBinaryOverflow(GenTreeOp* treeNode) GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op1Reg)); GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op2Reg)); GetEmitter()->emitIns(is64BitOp ? INS_i64_xor : INS_i32_xor); + + // TODO-WASM-CQ: consider branchless alternative here (and for sub) GetEmitter()->emitIns_I(is64BitOp ? INS_i64_const : INS_i32_const, emitActualTypeSize(treeNode), 0); GetEmitter()->emitIns(is64BitOp ? INS_i64_ge_s : INS_i32_ge_s); GetEmitter()->emitIns(INS_if); @@ -1216,9 +1218,11 @@ void CodeGen::genCodeForBinaryOverflow(GenTreeOp* treeNode) assert(WasmRegToType(wideReg) == WasmValueType::I64); // 32 bit multiply... check by doing a 64 bit multiply and then range-checking the result - // (I suppose we could do this transformation in morph too). - const bool isUnsigned = varTypeIsUnsigned(treeNode->TypeGet()); + const bool isUnsigned = treeNode->IsUnsigned(); // Both operands are on the stack as I32. Drop the second, extend the first, then extend the second. + // + // TODO-WASM-CQ: consider tansforming this to a (u)long multiply plus a checked cast, either in morph or + // lower. GetEmitter()->emitIns(INS_drop); GetEmitter()->emitIns(isUnsigned ? INS_i64_extend_u_i32 : INS_i64_extend_s_i32); GetEmitter()->emitIns_I(INS_local_get, emitActualTypeSize(treeNode), WasmRegToIndex(op2Reg)); From f4267a7ca96766825a224fcd4b4eb4b9e740407e Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Tue, 24 Feb 2026 14:19:39 -0800 Subject: [PATCH 8/8] Update src/coreclr/jit/codegenwasm.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/jit/codegenwasm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 1743ee57c84e04..e60f66f3fe0f3f 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -1221,7 +1221,7 @@ void CodeGen::genCodeForBinaryOverflow(GenTreeOp* treeNode) const bool isUnsigned = treeNode->IsUnsigned(); // Both operands are on the stack as I32. Drop the second, extend the first, then extend the second. // - // TODO-WASM-CQ: consider tansforming this to a (u)long multiply plus a checked cast, either in morph or + // TODO-WASM-CQ: consider transforming this to a (u)long multiply plus a checked cast, either in morph or // lower. GetEmitter()->emitIns(INS_drop); GetEmitter()->emitIns(isUnsigned ? INS_i64_extend_u_i32 : INS_i64_extend_s_i32);