From 15eb46c1c0c0d0be601e06fbb5f569d1e5548cc2 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Tue, 17 Feb 2026 11:26:33 -0800 Subject: [PATCH 01/17] fix return type classification --- src/coreclr/jit/compiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 5b360e494ca22e..f4ec64bdfef52a 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -766,7 +766,7 @@ var_types Compiler::getReturnTypeForStruct(CORINFO_CLASS_HANDLE clsHnd, } else { - howToReturnStruct = SPK_ByValue; + howToReturnStruct = SPK_PrimitiveType; useType = WasmClassifier::ToJitType(abiType); } From 5baaff4efcebb1c74424e9244eb568374c1ed69e Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Tue, 17 Feb 2026 11:27:02 -0800 Subject: [PATCH 02/17] fix return type classification part 2 --- src/coreclr/jit/gentree.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index cf4f69d8ca2376..9875d6da56f9fc 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -31040,6 +31040,10 @@ void ReturnTypeDesc::InitializeStructReturnType(Compiler* comp, m_regType[i] = comp->getJitGCType(gcPtrs[i]); } +#elif defined(TARGET_WASM) + + // Nothing to do here. + #else // TARGET_XXX // This target needs support here! From 7da75c9ae234ddaed1d3e8beec9de6074a6e8e68 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Tue, 17 Feb 2026 16:43:46 -0800 Subject: [PATCH 03/17] null check for calls --- src/coreclr/jit/codegenwasm.cpp | 22 +++++++++++++++++++--- src/coreclr/jit/regallocwasm.cpp | 22 ++++++++++++++++++++++ src/coreclr/jit/regallocwasm.h | 1 + src/coreclr/jit/stacklevelsetter.cpp | 7 +++++++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index a23105ca5b1a1b..3820a1a9a88554 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -1629,15 +1629,23 @@ void CodeGen::genCodeForStoreInd(GenTreeStoreInd* tree) //------------------------------------------------------------------------ // genCall: Produce code for a GT_CALL node // +// Arguments: +// call - the GT_CALL node +// void CodeGen::genCall(GenTreeCall* call) { + assert(!call->IsTailCall()); + + regNumber thisReg = REG_NA; + if (call->NeedsNullCheck()) { - NYI_WASM("Insert nullchecks for calls that need it in lowering"); + CallArg* thisArg = call->gtArgs.GetThisArg(); + GenTree* thisNode = thisArg->GetNode(); + thisReg = GetMultiUseOperandReg(thisNode); + assert(thisReg != REG_NA); } - assert(!call->IsTailCall()); - for (CallArg& arg : call->gtArgs.EarlyArgs()) { genConsumeReg(arg.GetEarlyNode()); @@ -1648,6 +1656,14 @@ void CodeGen::genCall(GenTreeCall* call) genConsumeReg(arg.GetLateNode()); } + if (call->NeedsNullCheck()) + { + GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, WasmRegToIndex(thisReg)); + GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, m_compiler->compMaxUncheckedOffsetForNullObject); + GetEmitter()->emitIns(INS_I_le_u); + genJumpToThrowHlpBlk(SCK_NULL_CHECK); + } + genCallInstruction(call); WasmProduceReg(call); } diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index 237b3e09b93ac9..22f59a143985b1 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -282,6 +282,10 @@ void WasmRegAlloc::CollectReferencesForNode(GenTree* node) CollectReferencesForDivMod(node->AsOp()); break; + case GT_CALL: + CollectReferencesForCall(node->AsCall()); + break; + default: assert(!node->OperIsLocalStore()); break; @@ -304,6 +308,24 @@ void WasmRegAlloc::CollectReferencesForDivMod(GenTreeOp* divModNode) ConsumeTemporaryRegForOperand(divModNode->gtGetOp1() DEBUGARG("div-by-zero / overflow check")); } +//------------------------------------------------------------------------ +// CollectReferencesForCall: Collect virtual register references for a call. +// +// Consumes temporary registers for the div-by-zero and overflow checks. +// +// Arguments: +// callNode - The GT_CALL node +// +void WasmRegAlloc::CollectReferencesForCall(GenTreeCall* callNode) +{ + if (callNode->NeedsNullCheck()) + { + CallArg* thisArg = callNode->gtArgs.GetThisArg(); + GenTree* thisNode = thisArg->GetNode(); + ConsumeTemporaryRegForOperand(thisNode DEBUGARG("null check for call")); + } +} + //------------------------------------------------------------------------ // RewriteLocalStackStore: rewrite a store to the stack to STOREIND(LCL_ADDR, ...). // diff --git a/src/coreclr/jit/regallocwasm.h b/src/coreclr/jit/regallocwasm.h index f87464b8c31499..321767d285a2ab 100644 --- a/src/coreclr/jit/regallocwasm.h +++ b/src/coreclr/jit/regallocwasm.h @@ -119,6 +119,7 @@ class WasmRegAlloc : public RegAllocInterface void CollectReferencesForBlock(BasicBlock* block); void CollectReferencesForNode(GenTree* node); void CollectReferencesForDivMod(GenTreeOp* divModNode); + void CollectReferencesForCall(GenTreeCall* callNode); void RewriteLocalStackStore(GenTreeLclVarCommon* node); void CollectReference(GenTree* node); void RequestTemporaryRegisterForMultiplyUsedNode(GenTree* node); diff --git a/src/coreclr/jit/stacklevelsetter.cpp b/src/coreclr/jit/stacklevelsetter.cpp index 08ff7071df7be9..91254f67c098bb 100644 --- a/src/coreclr/jit/stacklevelsetter.cpp +++ b/src/coreclr/jit/stacklevelsetter.cpp @@ -289,6 +289,13 @@ void StackLevelSetter::SetThrowHelperBlocks(GenTree* node, BasicBlock* block) case GT_NULLCHECK: SetThrowHelperBlock(SCK_NULL_CHECK, block); break; + + case GT_CALL: + if (node->AsCall()->NeedsNullCheck()) + { + SetThrowHelperBlock(SCK_NULL_CHECK, block); + } + break; #endif // defined(TARGET_WASM) default: // Other opers can target throw only due to overflow. From 9d0e6ba4798798d48526e53d5ec21863f3028721 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Tue, 17 Feb 2026 17:10:18 -0800 Subject: [PATCH 04/17] must anticipate during lower --- src/coreclr/jit/lower.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 656b58ee84fbf7..1db0787028b570 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -2860,6 +2860,15 @@ GenTree* Lowering::LowerCall(GenTree* node) } #endif +#if defined(TARGET_WASM) + if (call->NeedsNullCheck()) + { + // Prepare for explicit null check + CallArg* thisArg = call->gtArgs.GetThisArg(); + thisArg->GetNode()->gtLIRFlags |= LIR::Flags::MultiplyUsed; + } +#endif + LowerArgsForCall(call); // note that everything generated from this point might run AFTER the outgoing args are placed From 99d314efd2f86417b2dc5f8fb1dc7ac94af0b46f Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Tue, 17 Feb 2026 19:27:35 -0800 Subject: [PATCH 05/17] plausible int to int cast overflow checks --- src/coreclr/jit/codegenarm.cpp | 10 ++- src/coreclr/jit/codegenwasm.cpp | 104 ++++++++++++++++++++++++++++--- src/coreclr/jit/lowerwasm.cpp | 5 ++ src/coreclr/jit/regallocwasm.cpp | 30 ++++++++- src/coreclr/jit/regallocwasm.h | 1 + 5 files changed, 141 insertions(+), 9 deletions(-) diff --git a/src/coreclr/jit/codegenarm.cpp b/src/coreclr/jit/codegenarm.cpp index 84bfe9f5866d0d..91825099b84b09 100644 --- a/src/coreclr/jit/codegenarm.cpp +++ b/src/coreclr/jit/codegenarm.cpp @@ -1506,7 +1506,15 @@ void CodeGen::genIntToFloatCast(GenTree* treeNode) { // int --> float/double conversions are always non-overflow ones assert(treeNode->OperIs(GT_CAST)); - assert(!treeNode->gtOverflow()); + + GenIntCastDesc desc(treeNode); + + if (desc.CheckKind() != GenIntCastDesc::CHECK_NONE) + { + + assert(genIsValidIntReg(srcReg)); + genIntCastOverflowCheck(treeNode, desc, srcReg); + } regNumber targetReg = treeNode->GetRegNum(); assert(genIsValidFloatReg(targetReg)); diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 3820a1a9a88554..b93d0ea2212248 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -747,16 +747,20 @@ static constexpr uint32_t PackTypes(var_types toType, var_types fromType) // void CodeGen::genIntToIntCast(GenTreeCast* cast) { - if (cast->gtOverflow()) + GenIntCastDesc desc(cast); + + if (desc.CheckKind() != GenIntCastDesc::CHECK_NONE) { - NYI_WASM("Overflow checks"); + GenTree* castValue = cast->gtGetOp1(); + regNumber castReg = GetMultiUseOperandReg(castValue); + assert(castReg != REG_NA); + genIntCastOverflowCheck(cast, desc, castReg); } - GenIntCastDesc desc(cast); - var_types toType = genActualType(cast->CastToType()); - var_types fromType = genActualType(cast->CastOp()); - int extendSize = desc.ExtendSrcSize(); - instruction ins = INS_none; + var_types toType = genActualType(cast->CastToType()); + var_types fromType = genActualType(cast->CastOp()); + int extendSize = desc.ExtendSrcSize(); + instruction ins = INS_none; assert(fromType == TYP_INT || fromType == TYP_LONG); genConsumeOperands(cast); @@ -819,6 +823,92 @@ void CodeGen::genIntToIntCast(GenTreeCast* cast) WasmProduceReg(cast); } +//------------------------------------------------------------------------ +// genIntCastOverflowCheck: Generate overflow checking code for an integer cast. +// +// Arguments: +// cast - The GT_CAST node +// desc - The cast description +// reg - The register containing the value to check +// +void CodeGen::genIntCastOverflowCheck(GenTreeCast* cast, const GenIntCastDesc& desc, regNumber reg) +{ + bool const is64BitSrc = (desc.CheckSrcSize() == 8); + emitAttr const srcSize = is64BitSrc ? EA_8BYTE : EA_4BYTE; + bool const srcUnsigned = cast->IsUnsigned(); + + GetEmitter()->emitIns_I(INS_local_get, srcSize, WasmRegToIndex(reg)); + + switch (desc.CheckKind()) + { + + case GenIntCastDesc::CHECK_POSITIVE: + { + // INT to ULONG + assert(!is64BitSrc); + GetEmitter()->emitIns_I(INS_i32_const, srcSize, 0); + GetEmitter()->emitIns(INS_i32_lt_s); + genJumpToThrowHlpBlk(SCK_OVERFLOW); + break; + } + + case GenIntCastDesc::CHECK_UINT_RANGE: + { + // (U)LONG to UINT + assert(is64BitSrc); + GetEmitter()->emitIns_I(INS_i64_const, srcSize, UINT32_MAX); + // We can re-interpret LONG as ULONG + // Then negative values will be larger than UINT32_MAX + GetEmitter()->emitIns(INS_i64_gt_u); + genJumpToThrowHlpBlk(SCK_OVERFLOW); + break; + } + + case GenIntCastDesc::CHECK_POSITIVE_INT_RANGE: + { + // ULONG to INT + GetEmitter()->emitIns_I(INS_i64_const, srcSize, INT32_MAX); + GetEmitter()->emitIns(INS_i64_gt_u); + genJumpToThrowHlpBlk(SCK_OVERFLOW); + break; + } + + case GenIntCastDesc::CHECK_INT_RANGE: + { + // LONG to INT + GetEmitter()->emitIns_I(INS_i64_const, srcSize, INT32_MAX); + GetEmitter()->emitIns(INS_i64_gt_s); + GetEmitter()->emitIns_I(INS_local_get, srcSize, WasmRegToIndex(reg)); + GetEmitter()->emitIns_I(INS_i64_const, srcSize, INT32_MIN); + GetEmitter()->emitIns(INS_i64_lt_s); + GetEmitter()->emitIns(INS_i32_or); + genJumpToThrowHlpBlk(SCK_OVERFLOW); + break; + } + + case GenIntCastDesc::CHECK_SMALL_INT_RANGE: + { + // (U)(INT|LONG) to Small INT + const int castMaxValue = desc.CheckSmallIntMax(); + const int castMinValue = desc.CheckSmallIntMin(); + + GetEmitter()->emitIns_I(is64BitSrc ? INS_i64_const : INS_i32_const, srcSize, castMaxValue); + GetEmitter()->emitIns(is64BitSrc ? (srcUnsigned ? INS_i64_gt_u : INS_i64_gt_s) + : (srcUnsigned ? INS_i32_gt_u : INS_i32_gt_s)); + GetEmitter()->emitIns_I(INS_local_get, srcSize, WasmRegToIndex(reg)); + GetEmitter()->emitIns_I(is64BitSrc ? INS_i64_const : INS_i32_const, srcSize, castMinValue); + GetEmitter()->emitIns(is64BitSrc ? (srcUnsigned ? INS_i64_lt_u : INS_i64_lt_s) + : (srcUnsigned ? INS_i32_lt_u : INS_i32_lt_s)); + GetEmitter()->emitIns(INS_i32_or); + genJumpToThrowHlpBlk(SCK_OVERFLOW); + break; + } + + default: + unreached(); + } +} + //------------------------------------------------------------------------ // genFloatToIntCast: Generate code for a floating point to integer cast // diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index 9f68ac04f3843b..b5df374eb7f218 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -221,6 +221,11 @@ void Lowering::LowerPutArgStk(GenTreePutArgStk* putArgNode) void Lowering::LowerCast(GenTree* tree) { assert(tree->OperIs(GT_CAST)); + + if (tree->gtOverflow()) + { + tree->gtGetOp1()->gtLIRFlags |= LIR::Flags::MultiplyUsed; + } ContainCheckCast(tree->AsCast()); } diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index 22f59a143985b1..95b25af809b796 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -286,6 +286,10 @@ void WasmRegAlloc::CollectReferencesForNode(GenTree* node) CollectReferencesForCall(node->AsCall()); break; + case GT_CAST: + CollectReferencesForCast(node->AsOp()); + break; + default: assert(!node->OperIsLocalStore()); break; @@ -311,7 +315,7 @@ void WasmRegAlloc::CollectReferencesForDivMod(GenTreeOp* divModNode) //------------------------------------------------------------------------ // CollectReferencesForCall: Collect virtual register references for a call. // -// Consumes temporary registers for the div-by-zero and overflow checks. +// Consumes temporary registers for a call. // // Arguments: // callNode - The GT_CALL node @@ -326,6 +330,22 @@ void WasmRegAlloc::CollectReferencesForCall(GenTreeCall* callNode) } } +//------------------------------------------------------------------------ +// CollectReferencesForCast: Collect virtual register references for a cast. +// +// Consumes temporary registers for a cast. +// +// Arguments: +// castNode - The GT_CAST node +// +void WasmRegAlloc::CollectReferencesForCast(GenTreeOp* castNode) +{ + if (castNode->gtOverflow()) + { + ConsumeTemporaryRegForOperand(castNode->gtGetOp1() DEBUGARG("cast overflow check")); + } +} + //------------------------------------------------------------------------ // RewriteLocalStackStore: rewrite a store to the stack to STOREIND(LCL_ADDR, ...). // @@ -401,6 +421,14 @@ void WasmRegAlloc::CollectReference(GenTree* node) refs->Nodes[m_lastVirtualRegRefsCount++] = node; } +//------------------------------------------------------------------------ +// RequestTemporaryRegisterForMultiplyUsedNode: request a temporary register for a node with multiple uses. +// +// To be later assigned a physical register. +// +// Arguments: +// node - A node possibly needing a temporary register +// void WasmRegAlloc::RequestTemporaryRegisterForMultiplyUsedNode(GenTree* node) { if ((node->gtLIRFlags & LIR::Flags::MultiplyUsed) == LIR::Flags::None) diff --git a/src/coreclr/jit/regallocwasm.h b/src/coreclr/jit/regallocwasm.h index 321767d285a2ab..2b9fa9e6324fec 100644 --- a/src/coreclr/jit/regallocwasm.h +++ b/src/coreclr/jit/regallocwasm.h @@ -120,6 +120,7 @@ class WasmRegAlloc : public RegAllocInterface void CollectReferencesForNode(GenTree* node); void CollectReferencesForDivMod(GenTreeOp* divModNode); void CollectReferencesForCall(GenTreeCall* callNode); + void CollectReferencesForCast(GenTreeOp* castNode); void RewriteLocalStackStore(GenTreeLclVarCommon* node); void CollectReference(GenTree* node); void RequestTemporaryRegisterForMultiplyUsedNode(GenTree* node); From 1e1a2bb661cc75d543b3ea976c728e1ab7bcef80 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Tue, 17 Feb 2026 22:13:50 -0800 Subject: [PATCH 06/17] undo inadvertent changes to arm --- src/coreclr/jit/codegenarm.cpp | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/coreclr/jit/codegenarm.cpp b/src/coreclr/jit/codegenarm.cpp index 91825099b84b09..84bfe9f5866d0d 100644 --- a/src/coreclr/jit/codegenarm.cpp +++ b/src/coreclr/jit/codegenarm.cpp @@ -1506,15 +1506,7 @@ void CodeGen::genIntToFloatCast(GenTree* treeNode) { // int --> float/double conversions are always non-overflow ones assert(treeNode->OperIs(GT_CAST)); - - GenIntCastDesc desc(treeNode); - - if (desc.CheckKind() != GenIntCastDesc::CHECK_NONE) - { - - assert(genIsValidIntReg(srcReg)); - genIntCastOverflowCheck(treeNode, desc, srcReg); - } + assert(!treeNode->gtOverflow()); regNumber targetReg = treeNode->GetRegNum(); assert(genIsValidFloatReg(targetReg)); From 2ff530584baac20fa06b834be6e4aec948dbd6ca Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 18 Feb 2026 09:57:48 -0800 Subject: [PATCH 07/17] fix stack ordering issue for delegates; add null check --- src/coreclr/jit/lower.cpp | 14 +++++++++++--- src/coreclr/jit/morph.cpp | 13 ++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 1db0787028b570..210ae17425a638 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -6468,18 +6468,26 @@ GenTree* Lowering::LowerDelegateInvoke(GenTreeCall* call) GenTree* newThis = m_compiler->gtNewIndir(TYP_REF, newThisAddr); +#if HAS_FIXED_REGISTER_SET // Insert the new 'this' arg right before the call to get the correct null // behavior (the NRE that would logically happen inside Delegate.Invoke // should happen after all args are evaluated). We must also move the // PUTARG_REG node ahead. -#if HAS_FIXED_REGISTER_SET thisArgNode->AsOp()->gtOp1 = newThis; BlockRange().Remove(thisArgNode); BlockRange().InsertBefore(call, newThisAddr, newThis, thisArgNode); #else + // CallArgs::ArgsComplete should have arranged for all args to + // be evaluated to temps, so we don't need to reorder nodes to + // get the proper side effect ordering. + BlockRange().InsertAfter(thisArgNode, newThisAddr, newThis); thisExprUse.ReplaceWith(newThis); - BlockRange().Remove(thisExpr); - BlockRange().InsertBefore(call, thisExpr, newThisAddr, newThis); + + // Make the null check on the original this explicit. + // TODO-WASM-CQ: add this explicit null check in an earlier phase and try to optimize it away. + GenTree* nullCheckBase = m_compiler->gtNewLclvNode(base->AsLclVar()->GetLclNum(), base->TypeGet()); + GenTree* nullCheck = m_compiler->gtNewNullCheck(nullCheckBase); + BlockRange().InsertBefore(thisArgNode, nullCheckBase, nullCheck); #endif ContainCheckIndir(newThis->AsIndir()); diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 5d8075ec7236d3..a240f62af39339 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -1030,7 +1030,18 @@ void CallArgs::ArgsComplete(Compiler* comp, GenTreeCall* call) // effectively null-check 'this', which should happen only after all // arguments are evaluated. Thus we must evaluate all args with side // effects to a temp. - if (comp->opts.IsCFGEnabled() && (call->IsVirtual() || call->IsDelegateInvoke())) + // + // We have a similar constraint on Wasm even for non-CFG calls. + // Arguments must be pushed on the stack in order, so we must ensure + // that any side effects happen before we start pushing arguments. + // +#if HAS_FIXED_REGISTER_SET + bool evaluateArgsToTemps = comp->opts.IsCFGEnabled() && (call->IsVirtual() || call->IsDelegateInvoke()); +#else + bool evaluateArgsToTemps = call->IsDelegateInvoke(); +#endif + + if (evaluateArgsToTemps) { // Always evaluate 'this' to temp. assert(HasThisPointer()); From 49547e68f71d1b72a7a9a57ef5452e3eb33a9ead Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 18 Feb 2026 11:11:05 -0800 Subject: [PATCH 08/17] fix stack ordering issue for NEG --- src/coreclr/jit/lowerwasm.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index b5df374eb7f218..23d0b5fbc2a4da 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -121,7 +121,14 @@ GenTree* Lowering::LowerNeg(GenTreeOp* node) // GenTree* x = node->gtGetOp1(); GenTree* zero = m_compiler->gtNewZeroConNode(node->TypeGet()); - BlockRange().InsertBefore(x, zero); + + // To preserve stack order we must insert the zero before the entire + // tree rooted at x. + // + bool isClosed; + GenTree* insertBefore = BlockRange().GetTreeRange(x, &isClosed).FirstNode(); + assert(isClosed); + BlockRange().InsertBefore(insertBefore, zero); LowerNode(zero); node->ChangeOper(GT_SUB); node->gtOp1 = zero; From df4d054f9fd027bc67ba3bdb56ece8936e84fe63 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 18 Feb 2026 16:48:47 -0800 Subject: [PATCH 09/17] review feedback (partial) --- src/coreclr/jit/codegen.h | 1 + src/coreclr/jit/codegenwasm.cpp | 38 +++++++++++++++++++------------- src/coreclr/jit/gentree.cpp | 3 ++- src/coreclr/jit/lower.cpp | 25 +++++++++++++-------- src/coreclr/jit/lower.h | 1 + src/coreclr/jit/lowerwasm.cpp | 16 ++++++++++++++ src/coreclr/jit/regallocwasm.cpp | 13 +++++------ 7 files changed, 64 insertions(+), 33 deletions(-) diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 926f2c1eb86022..96954703599c93 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -216,6 +216,7 @@ class CodeGen final : public CodeGenInterface unsigned findTargetDepth(BasicBlock* target); void WasmProduceReg(GenTree* node); regNumber GetMultiUseOperandReg(GenTree* operand); + void genEmitNullCheck(regNumber reg); #endif void genEmitStartBlock(BasicBlock* block); diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index b93d0ea2212248..6549ca82969299 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -753,7 +753,6 @@ void CodeGen::genIntToIntCast(GenTreeCast* cast) { GenTree* castValue = cast->gtGetOp1(); regNumber castReg = GetMultiUseOperandReg(castValue); - assert(castReg != REG_NA); genIntCastOverflowCheck(cast, desc, castReg); } @@ -844,10 +843,9 @@ void CodeGen::genIntCastOverflowCheck(GenTreeCast* cast, const GenIntCastDesc& d case GenIntCastDesc::CHECK_POSITIVE: { - // INT to ULONG - assert(!is64BitSrc); - GetEmitter()->emitIns_I(INS_i32_const, srcSize, 0); - GetEmitter()->emitIns(INS_i32_lt_s); + // INT or LONG to ULONG + GetEmitter()->emitIns_I(is64BitSrc ? INS_i64_const : INS_i32_const, srcSize, 0); + GetEmitter()->emitIns(is64BitSrc ? INS_i64_lt_s : INS_i32_lt_s); genJumpToThrowHlpBlk(SCK_OVERFLOW); break; } @@ -876,12 +874,10 @@ void CodeGen::genIntCastOverflowCheck(GenTreeCast* cast, const GenIntCastDesc& d case GenIntCastDesc::CHECK_INT_RANGE: { // LONG to INT - GetEmitter()->emitIns_I(INS_i64_const, srcSize, INT32_MAX); - GetEmitter()->emitIns(INS_i64_gt_s); GetEmitter()->emitIns_I(INS_local_get, srcSize, WasmRegToIndex(reg)); - GetEmitter()->emitIns_I(INS_i64_const, srcSize, INT32_MIN); - GetEmitter()->emitIns(INS_i64_lt_s); - GetEmitter()->emitIns(INS_i32_or); + GetEmitter()->emitIns(INS_i32_wrap_i64); + GetEmitter()->emitIns(INS_i64_extend32_s); + GetEmitter()->emitIns(INS_i64_ne); genJumpToThrowHlpBlk(SCK_OVERFLOW); break; } @@ -1458,6 +1454,22 @@ void CodeGen::genJumpToThrowHlpBlk(SpecialCodeKind codeKind) void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) { genConsumeAddress(tree->Addr()); + genEmitNullCheck(REG_NA); +} + +//--------------------------------------------------------------------- +// genEmitNullCheck - generate code for a null check +// +// Arguments: +// regNum - register to check, or REG_NA if value to check is on the stack +// +void CodeGen::genEmitNullCheck(regNumber reg) +{ + if (reg != REG_NA) + { + GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, WasmRegToIndex(reg)); + } + GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, m_compiler->compMaxUncheckedOffsetForNullObject); GetEmitter()->emitIns(INS_I_le_u); genJumpToThrowHlpBlk(SCK_NULL_CHECK); @@ -1733,7 +1745,6 @@ void CodeGen::genCall(GenTreeCall* call) CallArg* thisArg = call->gtArgs.GetThisArg(); GenTree* thisNode = thisArg->GetNode(); thisReg = GetMultiUseOperandReg(thisNode); - assert(thisReg != REG_NA); } for (CallArg& arg : call->gtArgs.EarlyArgs()) @@ -1748,10 +1759,7 @@ void CodeGen::genCall(GenTreeCall* call) if (call->NeedsNullCheck()) { - GetEmitter()->emitIns_I(INS_local_get, EA_PTRSIZE, WasmRegToIndex(thisReg)); - GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, m_compiler->compMaxUncheckedOffsetForNullObject); - GetEmitter()->emitIns(INS_I_le_u); - genJumpToThrowHlpBlk(SCK_NULL_CHECK); + genEmitNullCheck(thisReg); } genCallInstruction(call); diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 9875d6da56f9fc..75905efeb88c13 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -31042,7 +31042,8 @@ void ReturnTypeDesc::InitializeStructReturnType(Compiler* comp, #elif defined(TARGET_WASM) - // Nothing to do here. + // For Wasm, structs are either returned by-ref or as primitives. + unreached(); #else // TARGET_XXX diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 210ae17425a638..ed00eecf62b4b9 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -2012,8 +2012,24 @@ void Lowering::LowerArgsForCall(GenTreeCall* call) #endif // defined(TARGET_X86) && defined(FEATURE_IJW) LegalizeArgPlacement(call); + AfterLowerArgsForCall(call); } +#if !defined(TARGET_WASM) + +//------------------------------------------------------------------------ +// AfterLowerArgsForCall: post processing after call args are lowered +// +// Arguments: +// call - Call node +// +void Lowering::AfterLowerArgsForCall(GenTreeCall* call) +{ + // no-op for non-Wasm targets +} + +#endif // !defined(TARGET_WASM) + #if defined(TARGET_X86) && defined(FEATURE_IJW) //------------------------------------------------------------------------ // LowerSpecialCopyArgs: Lower special copy arguments for P/Invoke IL stubs @@ -2860,15 +2876,6 @@ GenTree* Lowering::LowerCall(GenTree* node) } #endif -#if defined(TARGET_WASM) - if (call->NeedsNullCheck()) - { - // Prepare for explicit null check - CallArg* thisArg = call->gtArgs.GetThisArg(); - thisArg->GetNode()->gtLIRFlags |= LIR::Flags::MultiplyUsed; - } -#endif - LowerArgsForCall(call); // note that everything generated from this point might run AFTER the outgoing args are placed diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index 3ae6b68fada104..579e2dd283cccf 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -211,6 +211,7 @@ class Lowering final : public Phase GenTree* LowerVirtualVtableCall(GenTreeCall* call); GenTree* LowerVirtualStubCall(GenTreeCall* call); void LowerArgsForCall(GenTreeCall* call); + void AfterLowerArgsForCall(GenTreeCall* call); #if defined(TARGET_X86) && defined(FEATURE_IJW) void LowerSpecialCopyArgs(GenTreeCall* call); void InsertSpecialCopyArg(GenTreePutArgStk* putArgStk, CORINFO_CLASS_HANDLE argType, unsigned lclNum); diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index 23d0b5fbc2a4da..10833bc3db0b14 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -475,3 +475,19 @@ void Lowering::AfterLowerBlock() Stackifier stackifier(this); stackifier.StackifyCurrentBlock(); } + +//------------------------------------------------------------------------ +// AfterLowerArgsForCall: post processing after call args are lowered +// +// Arguments: +// call - Call node +// +void Lowering::AfterLowerArgsForCall(GenTreeCall* call) +{ + if (call->NeedsNullCheck()) + { + // Prepare for explicit null check + CallArg* thisArg = call->gtArgs.GetThisArg(); + thisArg->GetNode()->gtLIRFlags |= LIR::Flags::MultiplyUsed; + } +} diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index 95b25af809b796..824c3d7627d19c 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -322,11 +322,11 @@ void WasmRegAlloc::CollectReferencesForDivMod(GenTreeOp* divModNode) // void WasmRegAlloc::CollectReferencesForCall(GenTreeCall* callNode) { - if (callNode->NeedsNullCheck()) + CallArg* thisArg = callNode->gtArgs.GetThisArg(); + + if (thisArg != nullptr) { - CallArg* thisArg = callNode->gtArgs.GetThisArg(); - GenTree* thisNode = thisArg->GetNode(); - ConsumeTemporaryRegForOperand(thisNode DEBUGARG("null check for call")); + ConsumeTemporaryRegForOperand(thisArg->GetNode() DEBUGARG("call this argument")); } } @@ -340,10 +340,7 @@ void WasmRegAlloc::CollectReferencesForCall(GenTreeCall* callNode) // void WasmRegAlloc::CollectReferencesForCast(GenTreeOp* castNode) { - if (castNode->gtOverflow()) - { - ConsumeTemporaryRegForOperand(castNode->gtGetOp1() DEBUGARG("cast overflow check")); - } + ConsumeTemporaryRegForOperand(castNode->gtGetOp1() DEBUGARG("cast overflow check")); } //------------------------------------------------------------------------ From 42c7fbe2aae89127c4cd3ffc4e4905072af225ef Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 19 Feb 2026 11:31:15 -0800 Subject: [PATCH 10/17] simplify CHECK_INT_RAGE codegen --- src/coreclr/jit/codegenwasm.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 6549ca82969299..41b3a6f10e43c2 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -874,9 +874,8 @@ void CodeGen::genIntCastOverflowCheck(GenTreeCast* cast, const GenIntCastDesc& d case GenIntCastDesc::CHECK_INT_RANGE: { // LONG to INT - GetEmitter()->emitIns_I(INS_local_get, srcSize, WasmRegToIndex(reg)); - GetEmitter()->emitIns(INS_i32_wrap_i64); GetEmitter()->emitIns(INS_i64_extend32_s); + GetEmitter()->emitIns_I(INS_local_get, srcSize, WasmRegToIndex(reg)); GetEmitter()->emitIns(INS_i64_ne); genJumpToThrowHlpBlk(SCK_OVERFLOW); break; From c69ded1e63fa0baaf62437f39c5a2d81352589ce Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 19 Feb 2026 11:32:54 -0800 Subject: [PATCH 11/17] optimize CHECK_SMALL_INT_RANGE codegen --- src/coreclr/jit/codegenwasm.cpp | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 41b3a6f10e43c2..b131c8efb32839 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -887,14 +887,25 @@ void CodeGen::genIntCastOverflowCheck(GenTreeCast* cast, const GenIntCastDesc& d const int castMaxValue = desc.CheckSmallIntMax(); const int castMinValue = desc.CheckSmallIntMin(); - GetEmitter()->emitIns_I(is64BitSrc ? INS_i64_const : INS_i32_const, srcSize, castMaxValue); - GetEmitter()->emitIns(is64BitSrc ? (srcUnsigned ? INS_i64_gt_u : INS_i64_gt_s) - : (srcUnsigned ? INS_i32_gt_u : INS_i32_gt_s)); - GetEmitter()->emitIns_I(INS_local_get, srcSize, WasmRegToIndex(reg)); - GetEmitter()->emitIns_I(is64BitSrc ? INS_i64_const : INS_i32_const, srcSize, castMinValue); - GetEmitter()->emitIns(is64BitSrc ? (srcUnsigned ? INS_i64_lt_u : INS_i64_lt_s) - : (srcUnsigned ? INS_i32_lt_u : INS_i32_lt_s)); - GetEmitter()->emitIns(INS_i32_or); + if (castMinValue == 0) + { + // When the minimum is 0, a single unsigned upper-bound check is sufficient. + // For signed sources, negative values become large unsigned values and + // thus also trigger the overflow via the same comparison. + GetEmitter()->emitIns_I(is64BitSrc ? INS_i64_const : INS_i32_const, srcSize, castMaxValue); + GetEmitter()->emitIns(is64BitSrc ? INS_i64_gt_u : INS_i32_gt_u); + } + else + { + GetEmitter()->emitIns_I(is64BitSrc ? INS_i64_const : INS_i32_const, srcSize, castMaxValue); + GetEmitter()->emitIns(is64BitSrc ? (srcUnsigned ? INS_i64_gt_u : INS_i64_gt_s) + : (srcUnsigned ? INS_i32_gt_u : INS_i32_gt_s)); + GetEmitter()->emitIns_I(INS_local_get, srcSize, WasmRegToIndex(reg)); + GetEmitter()->emitIns_I(is64BitSrc ? INS_i64_const : INS_i32_const, srcSize, castMinValue); + GetEmitter()->emitIns(is64BitSrc ? (srcUnsigned ? INS_i64_lt_u : INS_i64_lt_s) + : (srcUnsigned ? INS_i32_lt_u : INS_i32_lt_s)); + GetEmitter()->emitIns(INS_i32_or); + } genJumpToThrowHlpBlk(SCK_OVERFLOW); break; } From 923def5ff1f478fbcb0d2576219a94674582e8ca Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 19 Feb 2026 11:43:40 -0800 Subject: [PATCH 12/17] remove explicit nullcheck from delegate invoke lowering --- src/coreclr/jit/lower.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index ed00eecf62b4b9..e572946262ea51 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -6489,12 +6489,6 @@ GenTree* Lowering::LowerDelegateInvoke(GenTreeCall* call) // get the proper side effect ordering. BlockRange().InsertAfter(thisArgNode, newThisAddr, newThis); thisExprUse.ReplaceWith(newThis); - - // Make the null check on the original this explicit. - // TODO-WASM-CQ: add this explicit null check in an earlier phase and try to optimize it away. - GenTree* nullCheckBase = m_compiler->gtNewLclvNode(base->AsLclVar()->GetLclNum(), base->TypeGet()); - GenTree* nullCheck = m_compiler->gtNewNullCheck(nullCheckBase); - BlockRange().InsertBefore(thisArgNode, nullCheckBase, nullCheck); #endif ContainCheckIndir(newThis->AsIndir()); From 75e37f8b7b014868f80f154f6d0d8cb55843a9fb Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 19 Feb 2026 12:24:22 -0800 Subject: [PATCH 13/17] add gtFirstNodeInExecutionOrder --- src/coreclr/jit/gentree.cpp | 23 +++++++++++++++++++++++ src/coreclr/jit/gentree.h | 2 ++ src/coreclr/jit/lowerwasm.cpp | 4 +--- src/coreclr/jit/regallocwasm.cpp | 14 +++----------- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 75905efeb88c13..861725c7c4a3ab 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -34185,3 +34185,26 @@ ValueSize ValueSize::FromJitType(var_types type) return ValueSize(genTypeSize(type)); } } + +//------------------------------------------------------------------------ +// gtFirstNodeInExecutionOrder: return the first node of this tree +// in execution order +// +// Returns: +// If tree is a leaf, return the tree. +// If the tree has operands, recurse on the first executed operand. +// +GenTree* GenTree::gtFirstNodeInExecutionOrder() +{ + GenTree* op = this; + GenTree::VisitResult visitResult; + do + { + visitResult = op->VisitOperands([&op](GenTree* operand) { + op = operand; + return GenTree::VisitResult::Abort; + }); + } while (visitResult == GenTree::VisitResult::Abort); + + return op; +} diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index a0da22d21572df..e5710723e4e707 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -1984,6 +1984,8 @@ struct GenTree inline GenTree* gtCommaStoreVal(); + GenTree* gtFirstNodeInExecutionOrder(); + // Return the child of this node if it is a GT_RELOAD or GT_COPY; otherwise simply return the node itself inline GenTree* gtSkipReloadOrCopy(); diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index 10833bc3db0b14..6a1fdbffb948f7 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -125,9 +125,7 @@ GenTree* Lowering::LowerNeg(GenTreeOp* node) // To preserve stack order we must insert the zero before the entire // tree rooted at x. // - bool isClosed; - GenTree* insertBefore = BlockRange().GetTreeRange(x, &isClosed).FirstNode(); - assert(isClosed); + GenTree* insertBefore = x->gtFirstNodeInExecutionOrder(); BlockRange().InsertBefore(insertBefore, zero); LowerNode(zero); node->ChangeOper(GT_SUB); diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index 824c3d7627d19c..4d8121eb4a83f9 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -357,16 +357,8 @@ void WasmRegAlloc::RewriteLocalStackStore(GenTreeLclVarCommon* lclNode) { // At this point, the IR is already stackified, so we just need to find the first node in the dataflow. // TODO-WASM-TP: this is nice and simple, but can we do this more efficiently? - GenTree* value = lclNode->Data(); - GenTree* op = value; - GenTree::VisitResult visitResult; - do - { - visitResult = op->VisitOperands([&op](GenTree* operand) { - op = operand; - return GenTree::VisitResult::Abort; - }); - } while (visitResult == GenTree::VisitResult::Abort); + GenTree* value = lclNode->Data(); + GenTree* insertionPoint = value->gtFirstNodeInExecutionOrder(); // TODO-WASM-RA: figure out the address mode story here. Right now this will produce an address not folded // into the store's address mode. We can utilize a contained LEA, but that will require some liveness work. @@ -387,7 +379,7 @@ void WasmRegAlloc::RewriteLocalStackStore(GenTreeLclVarCommon* lclNode) } CurrentRange().InsertAfter(lclNode, store); CurrentRange().Remove(lclNode); - CurrentRange().InsertBefore(op, lclNode); + CurrentRange().InsertBefore(insertionPoint, lclNode); } //------------------------------------------------------------------------ From 06f811d3e0b0168350016c6a5f4383245fc1af9e Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 19 Feb 2026 14:23:39 -0800 Subject: [PATCH 14/17] simplify CHECK_SMALL_INT_RANGE checks a bit more --- src/coreclr/jit/codegenwasm.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index b131c8efb32839..378f402fb3c60c 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -832,9 +832,8 @@ void CodeGen::genIntToIntCast(GenTreeCast* cast) // void CodeGen::genIntCastOverflowCheck(GenTreeCast* cast, const GenIntCastDesc& desc, regNumber reg) { - bool const is64BitSrc = (desc.CheckSrcSize() == 8); - emitAttr const srcSize = is64BitSrc ? EA_8BYTE : EA_4BYTE; - bool const srcUnsigned = cast->IsUnsigned(); + bool const is64BitSrc = (desc.CheckSrcSize() == 8); + emitAttr const srcSize = is64BitSrc ? EA_8BYTE : EA_4BYTE; GetEmitter()->emitIns_I(INS_local_get, srcSize, WasmRegToIndex(reg)); @@ -897,13 +896,12 @@ void CodeGen::genIntCastOverflowCheck(GenTreeCast* cast, const GenIntCastDesc& d } else { + assert(!cast->IsUnsigned()); GetEmitter()->emitIns_I(is64BitSrc ? INS_i64_const : INS_i32_const, srcSize, castMaxValue); - GetEmitter()->emitIns(is64BitSrc ? (srcUnsigned ? INS_i64_gt_u : INS_i64_gt_s) - : (srcUnsigned ? INS_i32_gt_u : INS_i32_gt_s)); + GetEmitter()->emitIns(is64BitSrc ? INS_i64_gt_s : INS_i32_gt_s); GetEmitter()->emitIns_I(INS_local_get, srcSize, WasmRegToIndex(reg)); GetEmitter()->emitIns_I(is64BitSrc ? INS_i64_const : INS_i32_const, srcSize, castMinValue); - GetEmitter()->emitIns(is64BitSrc ? (srcUnsigned ? INS_i64_lt_u : INS_i64_lt_s) - : (srcUnsigned ? INS_i32_lt_u : INS_i32_lt_s)); + GetEmitter()->emitIns(is64BitSrc ? INS_i64_lt_s : INS_i32_lt_s); GetEmitter()->emitIns(INS_i32_or); } genJumpToThrowHlpBlk(SCK_OVERFLOW); From 55581f95e2f39c431f52f9bbcf45a3b492ac80a4 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 19 Feb 2026 14:37:52 -0800 Subject: [PATCH 15/17] rename new helper method to gtFirstNodeInOperandOrder --- src/coreclr/jit/gentree.cpp | 8 ++++---- src/coreclr/jit/gentree.h | 2 +- src/coreclr/jit/lowerwasm.cpp | 2 +- src/coreclr/jit/regallocwasm.cpp | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/coreclr/jit/gentree.cpp b/src/coreclr/jit/gentree.cpp index 861725c7c4a3ab..756b5007e7f4dd 100644 --- a/src/coreclr/jit/gentree.cpp +++ b/src/coreclr/jit/gentree.cpp @@ -34187,14 +34187,14 @@ ValueSize ValueSize::FromJitType(var_types type) } //------------------------------------------------------------------------ -// gtFirstNodeInExecutionOrder: return the first node of this tree -// in execution order +// gtFirstNodeInOperandOrder : return the first node of this tree +// in operand order // // Returns: // If tree is a leaf, return the tree. -// If the tree has operands, recurse on the first executed operand. +// If the tree has operands, recurse on the first operand. // -GenTree* GenTree::gtFirstNodeInExecutionOrder() +GenTree* GenTree::gtFirstNodeInOperandOrder() { GenTree* op = this; GenTree::VisitResult visitResult; diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index e5710723e4e707..0cdf4cce4a1fe8 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -1984,7 +1984,7 @@ struct GenTree inline GenTree* gtCommaStoreVal(); - GenTree* gtFirstNodeInExecutionOrder(); + GenTree* gtFirstNodeInOperandOrder(); // Return the child of this node if it is a GT_RELOAD or GT_COPY; otherwise simply return the node itself inline GenTree* gtSkipReloadOrCopy(); diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index 6a1fdbffb948f7..710b1d125c4b2f 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -125,7 +125,7 @@ GenTree* Lowering::LowerNeg(GenTreeOp* node) // To preserve stack order we must insert the zero before the entire // tree rooted at x. // - GenTree* insertBefore = x->gtFirstNodeInExecutionOrder(); + GenTree* insertBefore = x->gtFirstNodeInOperandOrder(); BlockRange().InsertBefore(insertBefore, zero); LowerNode(zero); node->ChangeOper(GT_SUB); diff --git a/src/coreclr/jit/regallocwasm.cpp b/src/coreclr/jit/regallocwasm.cpp index 4d8121eb4a83f9..af3300d3bb9baa 100644 --- a/src/coreclr/jit/regallocwasm.cpp +++ b/src/coreclr/jit/regallocwasm.cpp @@ -358,7 +358,7 @@ void WasmRegAlloc::RewriteLocalStackStore(GenTreeLclVarCommon* lclNode) // At this point, the IR is already stackified, so we just need to find the first node in the dataflow. // TODO-WASM-TP: this is nice and simple, but can we do this more efficiently? GenTree* value = lclNode->Data(); - GenTree* insertionPoint = value->gtFirstNodeInExecutionOrder(); + GenTree* insertionPoint = value->gtFirstNodeInOperandOrder(); // TODO-WASM-RA: figure out the address mode story here. Right now this will produce an address not folded // into the store's address mode. We can utilize a contained LEA, but that will require some liveness work. From 1db00ff122918ba771a6e5fcb9e50ac78f9117c2 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 19 Feb 2026 14:40:28 -0800 Subject: [PATCH 16/17] undo revisions to Wasm delegate invoke lowering --- src/coreclr/jit/lower.cpp | 8 +++----- src/coreclr/jit/morph.cpp | 13 +------------ 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index e572946262ea51..54309ffb17e14b 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -6475,20 +6475,18 @@ GenTree* Lowering::LowerDelegateInvoke(GenTreeCall* call) GenTree* newThis = m_compiler->gtNewIndir(TYP_REF, newThisAddr); -#if HAS_FIXED_REGISTER_SET // Insert the new 'this' arg right before the call to get the correct null // behavior (the NRE that would logically happen inside Delegate.Invoke // should happen after all args are evaluated). We must also move the // PUTARG_REG node ahead. +#if HAS_FIXED_REGISTER_SET thisArgNode->AsOp()->gtOp1 = newThis; BlockRange().Remove(thisArgNode); BlockRange().InsertBefore(call, newThisAddr, newThis, thisArgNode); #else - // CallArgs::ArgsComplete should have arranged for all args to - // be evaluated to temps, so we don't need to reorder nodes to - // get the proper side effect ordering. - BlockRange().InsertAfter(thisArgNode, newThisAddr, newThis); thisExprUse.ReplaceWith(newThis); + BlockRange().Remove(thisExpr); + BlockRange().InsertBefore(call, thisExpr, newThisAddr, newThis); #endif ContainCheckIndir(newThis->AsIndir()); diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index a240f62af39339..5d8075ec7236d3 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -1030,18 +1030,7 @@ void CallArgs::ArgsComplete(Compiler* comp, GenTreeCall* call) // effectively null-check 'this', which should happen only after all // arguments are evaluated. Thus we must evaluate all args with side // effects to a temp. - // - // We have a similar constraint on Wasm even for non-CFG calls. - // Arguments must be pushed on the stack in order, so we must ensure - // that any side effects happen before we start pushing arguments. - // -#if HAS_FIXED_REGISTER_SET - bool evaluateArgsToTemps = comp->opts.IsCFGEnabled() && (call->IsVirtual() || call->IsDelegateInvoke()); -#else - bool evaluateArgsToTemps = call->IsDelegateInvoke(); -#endif - - if (evaluateArgsToTemps) + if (comp->opts.IsCFGEnabled() && (call->IsVirtual() || call->IsDelegateInvoke())) { // Always evaluate 'this' to temp. assert(HasThisPointer()); From ac2ccd368cc8e6f2d94db289009b53ac1b5d6bcd Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 20 Feb 2026 14:57:07 -0800 Subject: [PATCH 17/17] review feedback --- src/coreclr/jit/codegenwasm.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 378f402fb3c60c..b0daebcd251064 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -839,7 +839,6 @@ void CodeGen::genIntCastOverflowCheck(GenTreeCast* cast, const GenIntCastDesc& d switch (desc.CheckKind()) { - case GenIntCastDesc::CHECK_POSITIVE: { // INT or LONG to ULONG @@ -896,6 +895,8 @@ void CodeGen::genIntCastOverflowCheck(GenTreeCast* cast, const GenIntCastDesc& d } else { + // We need to check a range around zero, eg [-128, 127] for 8-bit signed. + // Do two compares and combine the results: (src > max) | (src < min). assert(!cast->IsUnsigned()); GetEmitter()->emitIns_I(is64BitSrc ? INS_i64_const : INS_i32_const, srcSize, castMaxValue); GetEmitter()->emitIns(is64BitSrc ? INS_i64_gt_s : INS_i32_gt_s); @@ -1744,8 +1745,6 @@ void CodeGen::genCodeForStoreInd(GenTreeStoreInd* tree) // void CodeGen::genCall(GenTreeCall* call) { - assert(!call->IsTailCall()); - regNumber thisReg = REG_NA; if (call->NeedsNullCheck())