From 95f7274051c6fb18a994ab4e474365e9f38c78c8 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 9 Jan 2026 15:03:01 -0800 Subject: [PATCH 1/9] [Wasm RyjJit] throw helper / null check preliminaries On Wasm null checks must be explicit and exceptions raised via helper call. Set up some of the mechanism we'll need for this: * Add null check special code kind * Track Wasm ACD entries by handler region only (instead of by try or handler). The code address of the helper cannot be used in Wasm to infer EH region containment; we will use use the virtual IP for that. So we need at most one throw helper (per kind) in each funclet and in the main method region. * Ensure throw helper blocks have Wasm labels that are always on the stack in their regions by putting the throw helpers at the end of the region RPO and pretending there is a branch from the region entry. * Add plausible codegen for GT_NULLCHECK --- src/coreclr/jit/codegenwasm.cpp | 21 +++++++++++++++++ src/coreclr/jit/fgbasic.cpp | 7 ++++++ src/coreclr/jit/fgwasm.h | 35 ++++++++++++++++++++++++++++ src/coreclr/jit/flowgraph.cpp | 22 +++++++++++++++-- src/coreclr/jit/gentree.h | 1 + src/coreclr/jit/stacklevelsetter.cpp | 8 ++++++- 6 files changed, 91 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 0fa2e31a889144..9111e14c887034 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -353,6 +353,10 @@ void CodeGen::genCodeForTreeNode(GenTree* treeNode) genCodeForNegNot(treeNode->AsOp()); break; + case GT_NULLCHECK: + genCodeForNullCheck(treeNode->AsIndir()); + break; + default: #ifdef DEBUG NYIRAW(GenTree::OpName(treeNode->OperGet())); @@ -784,6 +788,23 @@ void CodeGen::genCodeForNegNot(GenTreeOp* tree) genProduceReg(tree); } +//--------------------------------------------------------------------- +// genCodeForNullCheck - generate code for a GT_NULLCHECK node +// +// Arguments +// tree - the GT_NULLCHECK node +// +// Return value: +// None +// +void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) +{ + assert(compiler->fgUseThrowHelperBlocks()); + genConsumeOperand(tree->Addr()); + GetEmitter()->Ins(INS_i32_eqz); + genJumpToThrowHlpBlk(EJ_jmpif, SCK_NULL_REF); +} + //------------------------------------------------------------------------ // genCodeForLclAddr: Generates the code for GT_LCL_ADDR. // diff --git a/src/coreclr/jit/fgbasic.cpp b/src/coreclr/jit/fgbasic.cpp index 9af008e90f3300..b6881fd500ec30 100644 --- a/src/coreclr/jit/fgbasic.cpp +++ b/src/coreclr/jit/fgbasic.cpp @@ -6415,9 +6415,16 @@ BasicBlock* Compiler::fgNewBBatTryRegionEnd(BBKinds jumpKind, unsigned tryIndex) // // Note: // For debuggable code, codegen will generate the 'throw' code inline. +// (except for Wasm target, which always uses throw helper blocks) +// // Return Value: // true if 'throw' helper block should be created. +// bool Compiler::fgUseThrowHelperBlocks() { +#if defined(TARGET_WASM) + return true; +#else return !opts.compDbgCode; +#endif // !defined(TARGET_WASM) } diff --git a/src/coreclr/jit/fgwasm.h b/src/coreclr/jit/fgwasm.h index f9cbe5099846bf..2db30cae187914 100644 --- a/src/coreclr/jit/fgwasm.h +++ b/src/coreclr/jit/fgwasm.h @@ -318,9 +318,44 @@ class FgWasm // consider exceptional successors or successors that require runtime intervention // (eg funclet returns). // +// For method and funclet entries we add any "ACD" blocks as successors before the +// true successors. This ensures the ACD blocks end up at the end of the funclet +// region, and that we create proper Wasm blocks so we can branch to them from +// anywhere within the method region or funclet region. +// template BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc func, bool useProfile) { + // Special case throw helper blocks that are not yet connected in the flow graph. + // + AddCodeDscMap* const acdMap = comp->fgGetAddCodeDscMap(); + if (acdMap != nullptr) + { + // Behave as if these blocks have edges from their respective region entry blocks. + // + if ((block == fgFirstBB) || comp->bbIsFuncletBeg(block)) + { + AcdKeyDesignator dsg; + const unsigned blockData = comp->bbThrowIndex(block, &dsg); + + // We do not expect any ACDs to be mapped to try regions (only method/handler/filter) + // + assert(dsg != AcdKeyDesignator::KD_TRY); + + for (const AddCodeDscKey& key : AddCodeDscMap::KeyIteration(acdMap)) + { + if (key.Data() == blockData) + { + // This ACD refers to a throw helper block in the right region. + // Make it a successor. + // + AddCodeDsc* const acdBlock = acdMap->Lookup(key); + RETURN_ON_ABORT(func(acdBlock)); + } + } + } + } + switch (block->GetKind()) { // Funclet returns have no successors diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 48cb0e882fd2b2..a12a6050c80293 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -3326,6 +3326,8 @@ unsigned Compiler::acdHelper(SpecialCodeKind codeKind) return CORINFO_HELP_OVERFLOW; case SCK_FAIL_FAST: return CORINFO_HELP_FAIL_FAST; + case SCK_NULL_CHECK: + return CORINFO_HELP_THROWNULLREF; default: assert(!"Bad codeKind"); return 0; @@ -3358,6 +3360,8 @@ const char* sckName(SpecialCodeKind codeKind) return "SCK_ARITH_EXCPN"; case SCK_FAIL_FAST: return "SCK_FAIL_FAST"; + case SCK_NULL_CHECK: + return "SCK_NULL_CHECK"; default: return "SCK_UNKNOWN"; } @@ -3490,6 +3494,7 @@ PhaseStatus Compiler::fgCreateThrowHelperBlocks() BBJ_THROW, // SCK_ARG_EXCPN BBJ_THROW, // SCK_ARG_RNG_EXCPN BBJ_THROW, // SCK_FAIL_FAST + BBJ_THROW, // SCK_NULL_CHECK }; noway_assert(sizeof(jumpKinds) == SCK_COUNT); // sanity check @@ -3555,6 +3560,9 @@ PhaseStatus Compiler::fgCreateThrowHelperBlocks() case SCK_FAIL_FAST: msg = " for FAIL_FAST"; break; + case SCK_NULL_CHECK: + msg = " for NULL_CHECK"; + break; default: msg = " for ??"; break; @@ -3708,6 +3716,15 @@ unsigned Compiler::bbThrowIndex(BasicBlock* blk, AcdKeyDesignator* dsg) assert(inTry || inHnd); +#if defined(TARGET_WASM) + // For WASM we want one throw helper per funclet + // So we ignore any try region nesting. + if (!inHnd) + { + *dsg = AcdKeyDesignator::KD_NONE; + return 0; + } +#else if (inTry && (!inHnd || (tryIndex < hndIndex))) { // The most enclosing region is a try body, use it @@ -3715,6 +3732,7 @@ unsigned Compiler::bbThrowIndex(BasicBlock* blk, AcdKeyDesignator* dsg) *dsg = AcdKeyDesignator::KD_TRY; return tryIndex; } +#endif // !defined(TARGET_WASM) // The most enclosing region is a handler which will be a funclet // Now we have to figure out if blk is in the filter or handler @@ -3731,7 +3749,7 @@ unsigned Compiler::bbThrowIndex(BasicBlock* blk, AcdKeyDesignator* dsg) } //------------------------------------------------------------------------ -// AddCodedDscKey: construct from kind and block +// AddCodeDscKey: construct from kind and block // // Arguments: // kind - exception kind @@ -3755,7 +3773,7 @@ Compiler::AddCodeDscKey::AddCodeDscKey(SpecialCodeKind kind, BasicBlock* block, } //------------------------------------------------------------------------ -// AddCodedDscKey: construct from AddCodeDsc +// AddCodeDscKey: construct from AddCodeDsc // // Arguments: // add - add code dsc in querstion diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 5c60753cc7d0dd..e19efa3d0583cc 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -62,6 +62,7 @@ enum SpecialCodeKind SCK_ARG_EXCPN, // target on ArgumentException (currently used only for SIMD intrinsics) SCK_ARG_RNG_EXCPN, // target on ArgumentOutOfRangeException (currently used only for SIMD intrinsics) SCK_FAIL_FAST, // target for fail fast exception + SCK_NULL_CHECK, // target for NullReferenceException (Wasm) SCK_COUNT }; diff --git a/src/coreclr/jit/stacklevelsetter.cpp b/src/coreclr/jit/stacklevelsetter.cpp index cfff07017a2c52..e1b2fb936754f9 100644 --- a/src/coreclr/jit/stacklevelsetter.cpp +++ b/src/coreclr/jit/stacklevelsetter.cpp @@ -90,7 +90,6 @@ PhaseStatus StackLevelSetter::DoPhase() } } } - return madeChanges ? PhaseStatus::MODIFIED_EVERYTHING : PhaseStatus::MODIFIED_NOTHING; } @@ -273,6 +272,13 @@ void StackLevelSetter::SetThrowHelperBlocks(GenTree* node, BasicBlock* block) break; #endif +#if defined(TARGET_WASM) + // TODO-WASM: add other opers that imply null checks + case GT_NULLCHECK: + SetThrowHelperBlock(SCK_NULL_CHECK, block); + break; +#endif // defined(TARGET_WASM) + default: // Other opers can target throw only due to overflow. break; } From 518ee2db69b06395eab6165284e0020b353e80ea Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Fri, 9 Jan 2026 16:39:50 -0800 Subject: [PATCH 2/9] fix compilation issues --- src/coreclr/jit/codegencommon.cpp | 6 +++++- src/coreclr/jit/codegenwasm.cpp | 7 ++++--- src/coreclr/jit/fgwasm.h | 20 +++++++++++--------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 50622129a21132..1d3c6890cb339b 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1608,7 +1608,6 @@ void CodeGen::genExitCode(BasicBlock* block) genReserveEpilog(block); } -#ifndef TARGET_WASM //------------------------------------------------------------------------ // genJumpToThrowHlpBlk: Generate code for an out-of-line exception. // @@ -1670,6 +1669,9 @@ void CodeGen::genJumpToThrowHlpBlk(emitJumpKind jumpKind, SpecialCodeKind codeKi } else { +#if defined(TARGET_WASM) + NYI_WASM("genJumpToThrowHlpBlk: inline throw not yet implemented"); +#else // The code to throw the exception will be generated inline, and // we will jump around it in the normal non-exception case. @@ -1689,9 +1691,11 @@ void CodeGen::genJumpToThrowHlpBlk(emitJumpKind jumpKind, SpecialCodeKind codeKi assert(reverseJumpKind != jumpKind); genDefineTempLabel(tgtBlk); } +#endif // !defined (TARGET_WASM) } } +#ifndef TARGET_WASM /***************************************************************************** * * The last operation done was generating code for "tree" and that would diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 9111e14c887034..4d196cb9a2d829 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -800,9 +800,10 @@ void CodeGen::genCodeForNegNot(GenTreeOp* tree) void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) { assert(compiler->fgUseThrowHelperBlocks()); - genConsumeOperand(tree->Addr()); - GetEmitter()->Ins(INS_i32_eqz); - genJumpToThrowHlpBlk(EJ_jmpif, SCK_NULL_REF); + genConsumeAddress(tree->Addr()); + // TODO-WASM: compare with the appropriate small value + GetEmitter()->emitIns(INS_i32_eqz); + genJumpToThrowHlpBlk(EJ_jmpif, SCK_NULL_CHECK); } //------------------------------------------------------------------------ diff --git a/src/coreclr/jit/fgwasm.h b/src/coreclr/jit/fgwasm.h index 2db30cae187914..02e5aa1dcc3872 100644 --- a/src/coreclr/jit/fgwasm.h +++ b/src/coreclr/jit/fgwasm.h @@ -328,29 +328,31 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc { // Special case throw helper blocks that are not yet connected in the flow graph. // - AddCodeDscMap* const acdMap = comp->fgGetAddCodeDscMap(); + Compiler::AddCodeDscMap* const acdMap = comp->fgGetAddCodeDscMap(); if (acdMap != nullptr) { // Behave as if these blocks have edges from their respective region entry blocks. // - if ((block == fgFirstBB) || comp->bbIsFuncletBeg(block)) + if ((block == comp->fgFirstBB) || comp->bbIsFuncletBeg(block)) { - AcdKeyDesignator dsg; - const unsigned blockData = comp->bbThrowIndex(block, &dsg); + Compiler::AcdKeyDesignator dsg; + const unsigned blockData = comp->bbThrowIndex(block, &dsg); // We do not expect any ACDs to be mapped to try regions (only method/handler/filter) // - assert(dsg != AcdKeyDesignator::KD_TRY); + assert(dsg != Compiler::AcdKeyDesignator::KD_TRY); - for (const AddCodeDscKey& key : AddCodeDscMap::KeyIteration(acdMap)) + for (const Compiler::AddCodeDscKey& key : Compiler::AddCodeDscMap::KeyIteration(acdMap)) { if (key.Data() == blockData) { // This ACD refers to a throw helper block in the right region. - // Make it a successor. + // Make the block a successor. // - AddCodeDsc* const acdBlock = acdMap->Lookup(key); - RETURN_ON_ABORT(func(acdBlock)); + Compiler::AddCodeDsc* acd = nullptr; + ; + acdMap->Lookup(key, &acd); + RETURN_ON_ABORT(func(acd->acdDstBlk)); } } } From 0e41c68e42d763ce9b99d6589705ee2fce6c96bc Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Mon, 12 Jan 2026 17:06:24 -0800 Subject: [PATCH 3/9] handle inline null checks --- src/coreclr/jit/codegen.h | 2 ++ src/coreclr/jit/codegencommon.cpp | 6 +---- src/coreclr/jit/codegenwasm.cpp | 38 +++++++++++++++++++++++-------- src/coreclr/jit/fgbasic.cpp | 7 ------ src/coreclr/jit/fgwasm.h | 1 - 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/coreclr/jit/codegen.h b/src/coreclr/jit/codegen.h index 0dc138339952f0..758d8aefc27488 100644 --- a/src/coreclr/jit/codegen.h +++ b/src/coreclr/jit/codegen.h @@ -1356,6 +1356,8 @@ class CodeGen final : public CodeGenInterface void instGen(instruction ins); #if defined(TARGET_XARCH) void inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock, bool isRemovableJmpCandidate = false); +#elif defined(TARGET_WASM) + void inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock, bool isTempLabel = false); #else void inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock); #endif diff --git a/src/coreclr/jit/codegencommon.cpp b/src/coreclr/jit/codegencommon.cpp index 1d3c6890cb339b..50622129a21132 100644 --- a/src/coreclr/jit/codegencommon.cpp +++ b/src/coreclr/jit/codegencommon.cpp @@ -1608,6 +1608,7 @@ void CodeGen::genExitCode(BasicBlock* block) genReserveEpilog(block); } +#ifndef TARGET_WASM //------------------------------------------------------------------------ // genJumpToThrowHlpBlk: Generate code for an out-of-line exception. // @@ -1669,9 +1670,6 @@ void CodeGen::genJumpToThrowHlpBlk(emitJumpKind jumpKind, SpecialCodeKind codeKi } else { -#if defined(TARGET_WASM) - NYI_WASM("genJumpToThrowHlpBlk: inline throw not yet implemented"); -#else // The code to throw the exception will be generated inline, and // we will jump around it in the normal non-exception case. @@ -1691,11 +1689,9 @@ void CodeGen::genJumpToThrowHlpBlk(emitJumpKind jumpKind, SpecialCodeKind codeKi assert(reverseJumpKind != jumpKind); genDefineTempLabel(tgtBlk); } -#endif // !defined (TARGET_WASM) } } -#ifndef TARGET_WASM /***************************************************************************** * * The last operation done was generating code for "tree" and that would diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 4d196cb9a2d829..90870581ad7f81 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -791,19 +791,38 @@ void CodeGen::genCodeForNegNot(GenTreeOp* tree) //--------------------------------------------------------------------- // genCodeForNullCheck - generate code for a GT_NULLCHECK node // -// Arguments +// Arguments: // tree - the GT_NULLCHECK node // -// Return value: -// None +// Notes: +// If throw helper calls are being emitted inline, we need +// to wrap the resulting codegen in a block/end pair. // void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) { - assert(compiler->fgUseThrowHelperBlocks()); genConsumeAddress(tree->Addr()); - // TODO-WASM: compare with the appropriate small value - GetEmitter()->emitIns(INS_i32_eqz); - genJumpToThrowHlpBlk(EJ_jmpif, SCK_NULL_CHECK); + + // TODO-WASM: compare addr with the appropriate small value instead of zero + // + if (compiler->fgUseThrowHelperBlocks()) + { + Compiler::AddCodeDsc* const add = compiler->fgFindExcptnTarget(SCK_NULL_CHECK, compiler->compCurBB); + assert((add != nullptr) && ("ERROR: failed to find exception throw block")); + assert(add->acdUsed); + GetEmitter()->emitIns(INS_i32_eqz); + inst_JMP(EJ_jmpif, add->acdDstBlk); + } + else + { + BasicBlock* const tgtBlk = genCreateTempLabel(); + GetEmitter()->emitIns(INS_block); + // tgtBlock is not on our model control stack, so we note it is a temp label + inst_JMP(EJ_jmpif, tgtBlk, /* isTempLabel */ true); + // genEmitHelperCall(compiler->acdHelper(SCK_NULL_CHECK), 0, EA_UNKNOWN); + GetEmitter()->emitIns(INS_unreachable); + genDefineTempLabel(tgtBlk); + GetEmitter()->emitIns(INS_end); + } } //------------------------------------------------------------------------ @@ -1108,11 +1127,12 @@ void CodeGen::genSpillVar(GenTree* tree) // Arguments: // jmp - kind of jump to emit // tgtBlock - target of the jump +// isTempLabel - true if target is a temp label (implicitly at top of control flow stack) // -void CodeGen::inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock) +void CodeGen::inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock, bool isTempLabel) { instruction instr = emitter::emitJumpKindToIns(jmp); - unsigned const depth = findTargetDepth(tgtBlock); + unsigned const depth = isTempLabel ? 0 : findTargetDepth(tgtBlock); GetEmitter()->emitIns_J(instr, EA_4BYTE, depth, tgtBlock); } diff --git a/src/coreclr/jit/fgbasic.cpp b/src/coreclr/jit/fgbasic.cpp index b6881fd500ec30..9af008e90f3300 100644 --- a/src/coreclr/jit/fgbasic.cpp +++ b/src/coreclr/jit/fgbasic.cpp @@ -6415,16 +6415,9 @@ BasicBlock* Compiler::fgNewBBatTryRegionEnd(BBKinds jumpKind, unsigned tryIndex) // // Note: // For debuggable code, codegen will generate the 'throw' code inline. -// (except for Wasm target, which always uses throw helper blocks) -// // Return Value: // true if 'throw' helper block should be created. -// bool Compiler::fgUseThrowHelperBlocks() { -#if defined(TARGET_WASM) - return true; -#else return !opts.compDbgCode; -#endif // !defined(TARGET_WASM) } diff --git a/src/coreclr/jit/fgwasm.h b/src/coreclr/jit/fgwasm.h index 02e5aa1dcc3872..2d0a8b05bad02c 100644 --- a/src/coreclr/jit/fgwasm.h +++ b/src/coreclr/jit/fgwasm.h @@ -350,7 +350,6 @@ BasicBlockVisit FgWasm::VisitWasmSuccs(Compiler* comp, BasicBlock* block, TFunc // Make the block a successor. // Compiler::AddCodeDsc* acd = nullptr; - ; acdMap->Lookup(key, &acd); RETURN_ON_ABORT(func(acd->acdDstBlk)); } From 0f7bc294802ab4dd249a5a1b7c6c5084f00489b1 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Tue, 13 Jan 2026 17:11:19 -0800 Subject: [PATCH 4/9] added some comments/code for mul/div checks, but can't implement them fully yet --- src/coreclr/jit/codegenwasm.cpp | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 90870581ad7f81..db3dc9a6b17df0 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -574,6 +574,37 @@ void CodeGen::genCodeForDivMod(GenTreeOp* treeNode) { genConsumeOperands(treeNode); + // wasm stack is + // divisor (top) + // dividend (next) + // ... + // TODO-WASM: To check for exception, we will have to spill these to + // internal registers along the way, like so: + // + // ... push dividend + // tee.local $temp1 + // ... push divisor + // tee.local $temp2 + // ... exception checks (using $temp1 and $temp2; will introduce flow) + // div/mod op + + if (!varTypeIsFloating(treeNode->TypeGet())) + { + ExceptionSetFlags exSetFlags = treeNode->OperExceptions(compiler); + + // TODO-WASM:(AnyVal / 0) => DivideByZeroException + // + if ((exSetFlags & ExceptionSetFlags::DivideByZeroException) != ExceptionSetFlags::None) + { + } + + // TODO-WASM: (MinInt / -1) => ArithmeticException + // + if ((exSetFlags & ExceptionSetFlags::ArithmeticException) != ExceptionSetFlags::None) + { + } + } + instruction ins; switch (PackOperAndType(treeNode)) { @@ -803,7 +834,7 @@ void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) genConsumeAddress(tree->Addr()); // TODO-WASM: compare addr with the appropriate small value instead of zero - // + // TODO-WASM: refactor once we have implemented other cases invoking throw helpers if (compiler->fgUseThrowHelperBlocks()) { Compiler::AddCodeDsc* const add = compiler->fgFindExcptnTarget(SCK_NULL_CHECK, compiler->compCurBB); From 819ad29a9dff3c40a72646f6651ff3fffdf8e5c5 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Wed, 14 Jan 2026 18:13:31 -0800 Subject: [PATCH 5/9] update note on per-funclet throw helpers --- src/coreclr/jit/flowgraph.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index a12a6050c80293..4af3ca474fdbf0 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -3717,8 +3717,13 @@ unsigned Compiler::bbThrowIndex(BasicBlock* blk, AcdKeyDesignator* dsg) assert(inTry || inHnd); #if defined(TARGET_WASM) - // For WASM we want one throw helper per funclet - // So we ignore any try region nesting. + // The current plan for Wasm: method regions or funclets with + // trys will have a single Wasm try handle all + // resumption from catches via virtual IPs. + // + // So we do not need to consider the nesting of the throw + // in try regions, just in handlers. + // if (!inHnd) { *dsg = AcdKeyDesignator::KD_NONE; From 8c1a081bb665701c5f250b20a7790fa0cefda800 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 15 Jan 2026 11:11:11 -0800 Subject: [PATCH 6/9] use 1024 as unchecked offset for wasm --- src/coreclr/jit/codegenwasm.cpp | 18 ++++++++++++++---- src/coreclr/jit/compiler.cpp | 8 ++++++++ .../tools/Common/JitInterface/CorInfoImpl.cs | 14 ++++++++++++-- src/coreclr/vm/jitinterface.h | 11 +++++++---- 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index db3dc9a6b17df0..6b50ec79c6d586 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -12,9 +12,13 @@ #ifdef TARGET_64BIT static const instruction INS_I_const = INS_i64_const; static const instruction INS_I_add = INS_i64_add; +static const instruction INS_I_le_s = INS_i64_le_s; +static const instruction INS_I_gt_s = INS_i64_gt_s; #else // !TARGET_64BIT static const instruction INS_I_const = INS_i32_const; static const instruction INS_I_add = INS_i32_add; +static const instruction INS_I_le_s = INS_i32_le_s; +static const instruction INS_I_gt_s = INS_i32_gt_s; #endif // !TARGET_64BIT void CodeGen::genMarkLabelsForCodegen() @@ -833,26 +837,32 @@ void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) { genConsumeAddress(tree->Addr()); - // TODO-WASM: compare addr with the appropriate small value instead of zero // TODO-WASM: refactor once we have implemented other cases invoking throw helpers if (compiler->fgUseThrowHelperBlocks()) { Compiler::AddCodeDsc* const add = compiler->fgFindExcptnTarget(SCK_NULL_CHECK, compiler->compCurBB); assert((add != nullptr) && ("ERROR: failed to find exception throw block")); assert(add->acdUsed); - GetEmitter()->emitIns(INS_i32_eqz); + GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, compiler->compMaxUncheckedOffsetForNullObject); + GetEmitter()->emitIns(INS_I_le_s); inst_JMP(EJ_jmpif, add->acdDstBlk); } else { BasicBlock* const tgtBlk = genCreateTempLabel(); GetEmitter()->emitIns(INS_block); - // tgtBlock is not on our model control stack, so we note it is a temp label + GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, compiler->compMaxUncheckedOffsetForNullObject); + GetEmitter()->emitIns(INS_I_gt_s); + // tgtBlock is not on the model wasm control stack we set up earlier. + // Since we must emitted a `begin` that will end at this block, it will be at depth 0. + // Indicate this via isTempLabel. inst_JMP(EJ_jmpif, tgtBlk, /* isTempLabel */ true); + // TODO-WASM: codegen for the call // genEmitHelperCall(compiler->acdHelper(SCK_NULL_CHECK), 0, EA_UNKNOWN); + // The helper won't return, and we may have things pended on the stack, so emit unreachable GetEmitter()->emitIns(INS_unreachable); - genDefineTempLabel(tgtBlk); GetEmitter()->emitIns(INS_end); + genDefineTempLabel(tgtBlk); } } diff --git a/src/coreclr/jit/compiler.cpp b/src/coreclr/jit/compiler.cpp index 61ce486fbeb515..d56e7a0833b3aa 100644 --- a/src/coreclr/jit/compiler.cpp +++ b/src/coreclr/jit/compiler.cpp @@ -438,6 +438,14 @@ Compiler::Compiler(ArenaAllocator* arena, compMaxUncheckedOffsetForNullObject = eeInfo->maxUncheckedOffsetForNullObject; +#if defined(DEBUG) && defined(TARGET_WASM) + // if we are cross-replaying wasm, override compMaxUncheckedOffsetForNullObject + if (!info.compMatchedVM) + { + compMaxUncheckedOffsetForNullObject = 1024 - 1; + } +#endif + info.compProfilerCallback = false; // Assume false until we are told to hook this method. info.compCode = methodInfo->ILCode; diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 883af8a3cd01e0..bab5e6eb0e933a 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -3381,8 +3381,18 @@ private void getEEInfo(ref CORINFO_EE_INFO pEEInfoOut) pEEInfoOut.osPageSize = 0x1000; - pEEInfoOut.maxUncheckedOffsetForNullObject = (_compilation.NodeFactory.Target.IsWindows) ? - (32 * 1024 - 1) : (pEEInfoOut.osPageSize / 2 - 1); + if (_compilation.NodeFactory.Target.IsWasm) + { + pEEInfoOut.maxUncheckedOffsetForNullObject = 1024 - 1; + } + else if (_compilation.NodeFactory.Target.IsWindows) + { + pEEInfoOut.maxUncheckedOffsetForNullObject = 32 * 1024 - 1; + } + else + { + pEEInfoOut.maxUncheckedOffsetForNullObject = pEEInfoOut.osPageSize / 2 - 1; + } pEEInfoOut.targetAbi = TargetABI; pEEInfoOut.osType = TargetToOs(_compilation.NodeFactory.Target); diff --git a/src/coreclr/vm/jitinterface.h b/src/coreclr/vm/jitinterface.h index 89fa3099cb653b..d67118bdf04fef 100644 --- a/src/coreclr/vm/jitinterface.h +++ b/src/coreclr/vm/jitinterface.h @@ -11,11 +11,14 @@ #include "corjit.h" -#ifndef TARGET_UNIX -#define MAX_UNCHECKED_OFFSET_FOR_NULL_OBJECT ((32*1024)-1) // when generating JIT code -#else // !TARGET_UNIX +#if defined (TARGET_WASM) +#define MAX_UNCHECKED_OFFSET_FOR_NULL_OBJECT (1024 - 1) +#elif defined (TARGET_UNIX) #define MAX_UNCHECKED_OFFSET_FOR_NULL_OBJECT ((GetOsPageSize() / 2) - 1) -#endif // !TARGET_UNIX +#else +#define MAX_UNCHECKED_OFFSET_FOR_NULL_OBJECT ((32*1024)-1) // when generating JIT code +#endif + #include "pgo.h" class ILCodeStream; From 59000bf60ce2ccbb87bef3febbb362ca4b688dab Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 15 Jan 2026 13:30:42 -0800 Subject: [PATCH 7/9] unsigned compares --- src/coreclr/jit/codegenwasm.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 6b50ec79c6d586..1907fbeeb856b5 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -12,13 +12,13 @@ #ifdef TARGET_64BIT static const instruction INS_I_const = INS_i64_const; static const instruction INS_I_add = INS_i64_add; -static const instruction INS_I_le_s = INS_i64_le_s; -static const instruction INS_I_gt_s = INS_i64_gt_s; +static const instruction INS_I_le_u = INS_i64_le_u; +static const instruction INS_I_gt_u = INS_i64_gt_u; #else // !TARGET_64BIT static const instruction INS_I_const = INS_i32_const; static const instruction INS_I_add = INS_i32_add; -static const instruction INS_I_le_s = INS_i32_le_s; -static const instruction INS_I_gt_s = INS_i32_gt_s; +static const instruction INS_I_le_u = INS_i32_le_u; +static const instruction INS_I_gt_u = INS_i32_gt_u; #endif // !TARGET_64BIT void CodeGen::genMarkLabelsForCodegen() @@ -844,7 +844,7 @@ void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) assert((add != nullptr) && ("ERROR: failed to find exception throw block")); assert(add->acdUsed); GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, compiler->compMaxUncheckedOffsetForNullObject); - GetEmitter()->emitIns(INS_I_le_s); + GetEmitter()->emitIns(INS_I_le_u); inst_JMP(EJ_jmpif, add->acdDstBlk); } else @@ -852,7 +852,7 @@ void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) BasicBlock* const tgtBlk = genCreateTempLabel(); GetEmitter()->emitIns(INS_block); GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, compiler->compMaxUncheckedOffsetForNullObject); - GetEmitter()->emitIns(INS_I_gt_s); + GetEmitter()->emitIns(INS_I_gt_u); // tgtBlock is not on the model wasm control stack we set up earlier. // Since we must emitted a `begin` that will end at this block, it will be at depth 0. // Indicate this via isTempLabel. From d679d1ac3ebf113ddce6395552d06e4c58517cb2 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 15 Jan 2026 13:58:37 -0800 Subject: [PATCH 8/9] 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 1907fbeeb856b5..42565034a82f68 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -841,8 +841,7 @@ void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) if (compiler->fgUseThrowHelperBlocks()) { Compiler::AddCodeDsc* const add = compiler->fgFindExcptnTarget(SCK_NULL_CHECK, compiler->compCurBB); - assert((add != nullptr) && ("ERROR: failed to find exception throw block")); - assert(add->acdUsed); + assert(add != nullptr); GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, compiler->compMaxUncheckedOffsetForNullObject); GetEmitter()->emitIns(INS_I_le_u); inst_JMP(EJ_jmpif, add->acdDstBlk); @@ -854,7 +853,7 @@ void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, compiler->compMaxUncheckedOffsetForNullObject); GetEmitter()->emitIns(INS_I_gt_u); // tgtBlock is not on the model wasm control stack we set up earlier. - // Since we must emitted a `begin` that will end at this block, it will be at depth 0. + // Since we have just emitted a `begin` that will end at this block, it will be at depth 0. // Indicate this via isTempLabel. inst_JMP(EJ_jmpif, tgtBlk, /* isTempLabel */ true); // TODO-WASM: codegen for the call From bebc7deb538709eb7de8079160df58d1c4d97438 Mon Sep 17 00:00:00 2001 From: Andy Ayers Date: Thu, 15 Jan 2026 13:59:18 -0800 Subject: [PATCH 9/9] add back acd used check --- src/coreclr/jit/codegenwasm.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 42565034a82f68..96fdb8d5c46348 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -842,6 +842,7 @@ void CodeGen::genCodeForNullCheck(GenTreeIndir* tree) { Compiler::AddCodeDsc* const add = compiler->fgFindExcptnTarget(SCK_NULL_CHECK, compiler->compCurBB); assert(add != nullptr); + assert(add->acdUsed); GetEmitter()->emitIns_I(INS_I_const, EA_PTRSIZE, compiler->compMaxUncheckedOffsetForNullObject); GetEmitter()->emitIns(INS_I_le_u); inst_JMP(EJ_jmpif, add->acdDstBlk);