From 634fe8e6b19e245625e54267031969adbfec1d8c Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 21:13:38 -0700 Subject: [PATCH 1/2] Use user-arg accessors when expanding runtime lookups fgExpandRuntimeLookupsForCall indexed the helper call's arguments with the raw CountArgs/GetArgByIndex accessors, assuming the runtime-lookup helper has exactly its two user args (genericCtx, signatureCns). On wasm, AddFinalArgsAndDetermineABIInfo prepends a non-user WasmShadowStackPointer arg to every managed call during global morph, which runs before the runtime-lookup expansion phase. That leading arg shifted the raw indices, tripping assert(CountArgs() == 2) and, with asserts off, reading the context and signature from the wrong nodes. Switch to CountUserArgs/GetUserArgByIndex, which skip non-user args (r2r cell, wasm sp) the same way the rest of the JIT already reads helper-call args. This is identical on targets that don't prepend such args. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/jit/helperexpansion.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/coreclr/jit/helperexpansion.cpp b/src/coreclr/jit/helperexpansion.cpp index f917dc1d441dc2..2160f386e41546 100644 --- a/src/coreclr/jit/helperexpansion.cpp +++ b/src/coreclr/jit/helperexpansion.cpp @@ -183,12 +183,12 @@ bool Compiler::fgExpandRuntimeLookupsForCall(BasicBlock** pBlock, Statement* stm return false; } - assert(call->gtArgs.CountArgs() == 2); + assert(call->gtArgs.CountUserArgs() == 2); // The call has the following signature: // // type = call(genericCtx, signatureCns); // - const GenTree* signatureNode = call->gtArgs.GetArgByIndex(1)->GetNode(); + const GenTree* signatureNode = call->gtArgs.GetUserArgByIndex(1)->GetNode(); if (!signatureNode->IsCnsIntOrI()) { // We expect the signature to be a constant node here (it's marked as DONT_CSE) @@ -260,7 +260,7 @@ bool Compiler::fgExpandRuntimeLookupsForCall(BasicBlock** pBlock, Statement* stm gtUpdateStmtSideEffects(stmt); } - GenTree* ctxTree = call->gtArgs.GetArgByIndex(0)->GetNode(); + GenTree* ctxTree = call->gtArgs.GetUserArgByIndex(0)->GetNode(); // Prepare slotPtr tree (TODO: consider sharing this part with impRuntimeLookup) GenTree* slotPtrTree = gtCloneExpr(ctxTree); From ad3c1aab6c47032f4a4fe543dcec36b8aab1dc19 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 21:27:05 -0700 Subject: [PATCH 2/2] Use user-arg accessors in thread-local and stack-array expansion Both fgExpandThreadLocalAccessForCall and fgExpandStackArrayAllocation indexed the IL args by raw position, which the wasm shadow-stack arg prepended during morph shifts. Switch them to the user-arg accessors used elsewhere in the file so they skip the non-user leading arg. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- src/coreclr/jit/helperexpansion.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/helperexpansion.cpp b/src/coreclr/jit/helperexpansion.cpp index 2160f386e41546..55dbb992026896 100644 --- a/src/coreclr/jit/helperexpansion.cpp +++ b/src/coreclr/jit/helperexpansion.cpp @@ -872,7 +872,7 @@ bool Compiler::fgExpandThreadLocalAccessForCall(BasicBlock** pBlock, Statement* JITDUMP("offsetOfThreadStaticBlocks= %u\n", dspOffset(threadStaticBlocksInfo.offsetOfThreadStaticBlocks)); JITDUMP("offsetOfBaseOfThreadLocalData= %u\n", dspOffset(threadStaticBlocksInfo.offsetOfBaseOfThreadLocalData)); - assert(call->gtArgs.CountArgs() == 1); + assert(call->gtArgs.CountUserArgs() == 1); // Split block right before the call tree BasicBlock* prevBb = block; @@ -1020,7 +1020,7 @@ bool Compiler::fgExpandThreadLocalAccessForCall(BasicBlock** pBlock, Statement* // Cache the tls value tlsValueDef = gtNewStoreLclVarNode(tlsLclNum, tlsValue); GenTree* tlsLclValueUse = gtNewLclVarNode(tlsLclNum); - GenTree* typeThreadStaticBlockIndexValue = call->gtArgs.GetArgByIndex(0)->GetNode(); + GenTree* typeThreadStaticBlockIndexValue = call->gtArgs.GetUserArgByIndex(0)->GetNode(); assert(genActualType(typeThreadStaticBlockIndexValue) == TYP_INT); if (helper == CORINFO_HELP_GETDYNAMIC_NONGCTHREADSTATIC_BASE_NOCTOR_OPTIMIZED2) @@ -2902,7 +2902,7 @@ bool Compiler::fgExpandStackArrayAllocation(BasicBlock* block, Statement* stmt, // Initialize the array method table pointer. // - GenTree* const mt = call->gtArgs.GetArgByIndex(typeArgIndex)->GetNode(); + GenTree* const mt = call->gtArgs.GetUserArgByIndex(typeArgIndex)->GetNode(); GenTree* const mtStore = gtNewStoreValueNode(TYP_I_IMPL, stackLocalAddress, mt); Statement* const mtStmt = fgNewStmtFromTree(mtStore); @@ -2910,7 +2910,7 @@ bool Compiler::fgExpandStackArrayAllocation(BasicBlock* block, Statement* stmt, // Initialize the array length. // - GenTree* const lengthArg = call->gtArgs.GetArgByIndex(lengthArgIndex)->GetNode(); + GenTree* const lengthArg = call->gtArgs.GetUserArgByIndex(lengthArgIndex)->GetNode(); GenTree* const lengthArgInt = fgOptimizeCast(gtNewCastNode(TYP_INT, lengthArg, false, TYP_INT)); GenTree* const lengthAddress = gtNewOperNode(GT_ADD, TYP_I_IMPL, gtCloneExpr(stackLocalAddress), gtNewIconNode(OFFSETOF__CORINFO_Array__length, TYP_I_IMPL));