From 0b805453aedfaf25bcfa43683565222d7dc7895f Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Tue, 7 Apr 2026 15:52:53 -0700 Subject: [PATCH 01/13] Add codegen changes to make calls through PortableEntryPoints stored into locations in the R2R file work - Notably, a new level of indirection is needed --- src/coreclr/jit/codegenwasm.cpp | 2 ++ src/coreclr/jit/lower.cpp | 4 ++++ src/coreclr/jit/morph.cpp | 6 +++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 9a418ebdeb309b..9d763a9e2c0de2 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2577,6 +2577,7 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, // Push PEP onto the stack because we are calling a managed helper that expects it as the last parameter. assert(helperFunction.accessType == IAT_PVALUE); GetEmitter()->emitAddressConstant(helperFunction.addr); + GetEmitter()->emitIns_I(INS_i32_load, EA_PTRSIZE, 0); } if (params.callType == EC_INDIR_R) @@ -2585,6 +2586,7 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, assert(helperFunction.accessType == IAT_PVALUE); GetEmitter()->emitAddressConstant(helperFunction.addr); GetEmitter()->emitIns_I(INS_i32_load, EA_PTRSIZE, 0); + GetEmitter()->emitIns_I(INS_i32_load, EA_PTRSIZE, 0); } genEmitCallWithCurrentGC(params); diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 7ca6c624c65f76..495c8c00bad90a 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -6409,6 +6409,10 @@ GenTree* Lowering::LowerDirectCall(GenTreeCall* call) cellAddr->AsIntCon()->gtTargetHandle = (size_t)call->gtCallMethHnd; #endif GenTree* indir = Ind(cellAddr); +#ifdef TARGET_WASM + indir = Ind(indir); // WebAssembly "function pointers" are actually PortableEntryPoint pointers, and + // actually dispatching to them requires an additional level of indirection. +#endif result = indir; } break; diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index e2a1f11b3d0c1a..27b2f262d28f19 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -1767,7 +1767,11 @@ void CallArgs::AddFinalArgsAndDetermineABIInfo(Compiler* comp, GenTreeCall* call size_t addrValue = (size_t)call->gtEntryPoint.addr; GenTree* indirectCellAddress = comp->gtNewIconHandleNode(addrValue, GTF_ICON_FTN_ADDR); INDEBUG(indirectCellAddress->AsIntCon()->gtTargetHandle = (size_t)call->gtCallMethHnd); - +#if defined(TARGET_WASM) + // On WASM, the address in the R2R table is actually the address of something that should + // be treated as a PortableEntryPoint. To actually dispatch, we need to indirect once more. + indirectCellAddress = comp->gtNewOperNode(GT_IND, TYP_I_IMPL, indirectCellAddress); +#endif #ifdef TARGET_ARM // TODO-ARM: We currently do not properly kill this register in LSRA // (see getKillSetForCall which does so only for VSD calls). From 9c88d47a1067839bcdcdc45dd1a7c81014d3ded2 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Thu, 9 Apr 2026 15:35:48 -0700 Subject: [PATCH 02/13] Add additional comment about the need for second level of indirection in helper call codegen in Wasm --- src/coreclr/jit/codegenwasm.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 9d763a9e2c0de2..62f5c6019d3fa7 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2575,6 +2575,8 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, if (helperIsManaged) { // Push PEP onto the stack because we are calling a managed helper that expects it as the last parameter. + // The helper function address is the address of an indirection cell, so we load from the cell to get the PEP address + // to push. assert(helperFunction.accessType == IAT_PVALUE); GetEmitter()->emitAddressConstant(helperFunction.addr); GetEmitter()->emitIns_I(INS_i32_load, EA_PTRSIZE, 0); @@ -2582,7 +2584,8 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, if (params.callType == EC_INDIR_R) { - // Push the call target onto the wasm evaluation stack by dereferencing the PEP. + // Push the call target onto the wasm evaluation stack by dereferencing the indirection cell + // and then the PEP pointed to by the indirection cell. assert(helperFunction.accessType == IAT_PVALUE); GetEmitter()->emitAddressConstant(helperFunction.addr); GetEmitter()->emitIns_I(INS_i32_load, EA_PTRSIZE, 0); From 5e4a83f972b9fae9abad3fa0c5b73f094ee570ea Mon Sep 17 00:00:00 2001 From: adamperlin Date: Wed, 15 Apr 2026 18:46:49 -0700 Subject: [PATCH 03/13] WIP Lowering::LowerPEPCall for Wasm --- src/coreclr/jit/lower.cpp | 74 ++++++++++++++++++++++++++++++++++++--- src/coreclr/jit/lower.h | 1 + 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index b5fe1fcbd1ba7e..a5b5e45bdfdd07 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -3018,6 +3018,14 @@ GenTree* Lowering::LowerCall(GenTree* node) } } + // For non-helper, managed calls, if we have portable entry points enabled, we need to lower + // the call according to the portable entrypoint abi + if (!call->IsUnmanaged() && !call->IsHelperCall() && + m_compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PORTABLE_ENTRY_POINTS)) + { + LowerPEPCall(call); + } + if (varTypeIsStruct(call)) { LowerCallStruct(call); @@ -3634,6 +3642,68 @@ GenTree* Lowering::LowerTailCallViaJitHelper(GenTreeCall* call, GenTree* callTar return result; } +//--------------------------------------------------------------------------------------------- +// LowerPEPCall: Lower a call node dispatched through a PortableEntryPoint (PEP) +// +// Given a call node with gtControlExpr representing a call target which is the address of a portable entrypoint, +// this function lowers the call to appropriately dispatch through the portable entrypoint using the Portable +// entrypoint calling convention. +// To do this, it: +// 1. Introduces a new local variable to hold the PEP address +// 1. Adds a new well-known argument to the call passing this local +// 2. Rewrites the control expression to indirect through the new local, since for PEP's, the actual call target +// must be loaded from the portable entry point address. +// +// Arguments: +// call - The call node to lower. It is expected that the call node has gtControlExpr set to the original +// call target and that the call does not have a PEP arg already. +// +// Return Value: +// None. The call node is modified in place. +// +void Lowering::LowerPEPCall(GenTreeCall* call) +{ + DISPTREERANGE(BlockRange(), call); + GenTree* callTarget = call->gtControlExpr; + LIR::Use callTargetUse; + bool used = BlockRange().TryGetUse(callTarget, &callTargetUse); + assert(used); + + unsigned int callTargetLclNum = callTargetUse.ReplaceWithLclVar(m_compiler); + GenTreeLclVar* callTargetLclForArg = m_compiler->gtNewLclvNode(callTargetLclNum, TYP_I_IMPL); + + DISPTREERANGE(BlockRange(), call); + + NewCallArg pepTargetArg = NewCallArg::Primitive(callTargetLclForArg).WellKnown(WellKnownArg::WasmPortableEntryPoint); + CallArg* pepArg = call->gtArgs.PushBack(m_compiler, pepTargetArg); + + pepArg->SetEarlyNode(nullptr); + pepArg->SetLateNode(callTargetLclForArg); + call->gtArgs.PushLateBack(pepArg); + + // Set up ABI information for this arg; PEP's should be passed as the last param to a wasm function + unsigned pepIndex = call->gtArgs.CountArgs() - 1; + regNumber pepReg = MakeWasmReg(pepIndex, WasmValueType::I); + pepArg->AbiInfo = + ABIPassingInformation::FromSegmentByValue(m_compiler, + ABIPassingSegment::InRegister(pepReg, + 0, TARGET_POINTER_SIZE)); + BlockRange().InsertBefore(call, callTargetLclForArg); + + DISPTREERANGE(BlockRange(), call); + + // Lower the new PEP arg now that the call abi info is updated and lcl var is inserted + LowerArg(call, pepArg); + + // Rewrite the call's control expression to have an additional load from the PEP local + GenTree* controlExpr = call->gtControlExpr; + GenTree* target = Ind(controlExpr); + BlockRange().InsertAfter(controlExpr, target); + call->gtControlExpr = target; + + DISPTREERANGE(BlockRange(), call); +} + //------------------------------------------------------------------------ // LowerCFGCall: Potentially lower a call to use control-flow guard. This // expands indirect calls into either a validate+call sequence or to a dispatch @@ -6411,10 +6481,6 @@ GenTree* Lowering::LowerDirectCall(GenTreeCall* call) cellAddr->AsIntCon()->gtTargetHandle = (size_t)call->gtCallMethHnd; #endif GenTree* indir = Ind(cellAddr); -#ifdef TARGET_WASM - indir = Ind(indir); // WebAssembly "function pointers" are actually PortableEntryPoint pointers, and - // actually dispatching to them requires an additional level of indirection. -#endif result = indir; } break; diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index af39e59ca66a64..45fce173f22a34 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -152,6 +152,7 @@ class Lowering final : public Phase bool LowerCallMemcmp(GenTreeCall* call, GenTree** next); bool LowerCallMemset(GenTreeCall* call, GenTree** next); void LowerCFGCall(GenTreeCall* call); + void LowerPEPCall(GenTreeCall* call); void MovePutArgNodesUpToCall(GenTreeCall* call); void MovePutArgUpToCall(GenTreeCall* call, GenTree* node); #ifndef TARGET_64BIT From b42c336bca81e7f8dcac6e529565165083bf86ba Mon Sep 17 00:00:00 2001 From: adamperlin Date: Fri, 17 Apr 2026 10:28:21 -0700 Subject: [PATCH 04/13] Add more specific JITDUMP messages --- src/coreclr/jit/lower.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index a5b5e45bdfdd07..23cf737a4ba679 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -3018,6 +3018,7 @@ GenTree* Lowering::LowerCall(GenTree* node) } } +#ifdef TARGET_WASM // For non-helper, managed calls, if we have portable entry points enabled, we need to lower // the call according to the portable entrypoint abi if (!call->IsUnmanaged() && !call->IsHelperCall() && @@ -3025,6 +3026,7 @@ GenTree* Lowering::LowerCall(GenTree* node) { LowerPEPCall(call); } +#endif // TARGET_WASM if (varTypeIsStruct(call)) { @@ -3642,6 +3644,7 @@ GenTree* Lowering::LowerTailCallViaJitHelper(GenTreeCall* call, GenTree* callTar return result; } +#ifdef TARGET_WASM //--------------------------------------------------------------------------------------------- // LowerPEPCall: Lower a call node dispatched through a PortableEntryPoint (PEP) // @@ -3663,6 +3666,7 @@ GenTree* Lowering::LowerTailCallViaJitHelper(GenTreeCall* call, GenTree* callTar // void Lowering::LowerPEPCall(GenTreeCall* call) { + JITDUMP("Lowering PEP call\n"); DISPTREERANGE(BlockRange(), call); GenTree* callTarget = call->gtControlExpr; LIR::Use callTargetUse; @@ -3672,7 +3676,8 @@ void Lowering::LowerPEPCall(GenTreeCall* call) unsigned int callTargetLclNum = callTargetUse.ReplaceWithLclVar(m_compiler); GenTreeLclVar* callTargetLclForArg = m_compiler->gtNewLclvNode(callTargetLclNum, TYP_I_IMPL); - DISPTREERANGE(BlockRange(), call); + JITDUMP("Created new local variable for PEP call target: V%02u\n", callTargetLclNum); + DISPTREE(call) NewCallArg pepTargetArg = NewCallArg::Primitive(callTargetLclForArg).WellKnown(WellKnownArg::WasmPortableEntryPoint); CallArg* pepArg = call->gtArgs.PushBack(m_compiler, pepTargetArg); @@ -3690,8 +3695,8 @@ void Lowering::LowerPEPCall(GenTreeCall* call) 0, TARGET_POINTER_SIZE)); BlockRange().InsertBefore(call, callTargetLclForArg); + JITDUMP("Lowered PEP call with new arg for PEP target and local variable to hold PEP target\n"); DISPTREERANGE(BlockRange(), call); - // Lower the new PEP arg now that the call abi info is updated and lcl var is inserted LowerArg(call, pepArg); @@ -3701,8 +3706,10 @@ void Lowering::LowerPEPCall(GenTreeCall* call) BlockRange().InsertAfter(controlExpr, target); call->gtControlExpr = target; + JITDUMP("Rewrote PEP call's control expression to indirect through the new local variable\n"); DISPTREERANGE(BlockRange(), call); } +#endif // TARGET_WASM //------------------------------------------------------------------------ // LowerCFGCall: Potentially lower a call to use control-flow guard. This From f2b96b640e72df0339091e812ac9b41a1822f59a Mon Sep 17 00:00:00 2001 From: adamperlin Date: Tue, 21 Apr 2026 11:54:22 -0700 Subject: [PATCH 05/13] Remove boilerplate for getting gtControlExpr use, better JITDUMP messages --- src/coreclr/jit/lower.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 23cf737a4ba679..42992fcf7ef0f0 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -3666,19 +3666,18 @@ GenTree* Lowering::LowerTailCallViaJitHelper(GenTreeCall* call, GenTree* callTar // void Lowering::LowerPEPCall(GenTreeCall* call) { - JITDUMP("Lowering PEP call\n"); + JITDUMP("Begin lowering PEP call\n"); DISPTREERANGE(BlockRange(), call); - GenTree* callTarget = call->gtControlExpr; - LIR::Use callTargetUse; - bool used = BlockRange().TryGetUse(callTarget, &callTargetUse); - assert(used); + LIR::Use callTargetUse(BlockRange(), &call->gtControlExpr, call); + + JITDUMP("Creating new local variable for PEP"); unsigned int callTargetLclNum = callTargetUse.ReplaceWithLclVar(m_compiler); GenTreeLclVar* callTargetLclForArg = m_compiler->gtNewLclvNode(callTargetLclNum, TYP_I_IMPL); - - JITDUMP("Created new local variable for PEP call target: V%02u\n", callTargetLclNum); DISPTREE(call) + + JITDUMP("Add new arg to call arg list corresponding to PEP target"); NewCallArg pepTargetArg = NewCallArg::Primitive(callTargetLclForArg).WellKnown(WellKnownArg::WasmPortableEntryPoint); CallArg* pepArg = call->gtArgs.PushBack(m_compiler, pepTargetArg); @@ -3695,18 +3694,20 @@ void Lowering::LowerPEPCall(GenTreeCall* call) 0, TARGET_POINTER_SIZE)); BlockRange().InsertBefore(call, callTargetLclForArg); - JITDUMP("Lowered PEP call with new arg for PEP target and local variable to hold PEP target\n"); - DISPTREERANGE(BlockRange(), call); // Lower the new PEP arg now that the call abi info is updated and lcl var is inserted LowerArg(call, pepArg); + JITDUMP("Call is now:\n"); + DISPTREERANGE(BlockRange(), call); + + JITDUMP("Rewrite PEP call's control expression to indirect through the new local variable\n"); // Rewrite the call's control expression to have an additional load from the PEP local GenTree* controlExpr = call->gtControlExpr; GenTree* target = Ind(controlExpr); BlockRange().InsertAfter(controlExpr, target); call->gtControlExpr = target; - JITDUMP("Rewrote PEP call's control expression to indirect through the new local variable\n"); + JITDUMP("Finished lowering PEP call\n"); DISPTREERANGE(BlockRange(), call); } #endif // TARGET_WASM From 83015d277480a68e69aa88211965b8c20da7e06b Mon Sep 17 00:00:00 2001 From: adamperlin Date: Tue, 21 Apr 2026 16:02:55 -0700 Subject: [PATCH 06/13] Remove addition of R2R indirection cell arg in morph for Wasm; remove unused cases in call codegen and clarify comments --- src/coreclr/jit/codegenwasm.cpp | 59 +++++++++++---------------------- src/coreclr/jit/lower.cpp | 10 +++--- src/coreclr/jit/morph.cpp | 21 +++++------- 3 files changed, 31 insertions(+), 59 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 019a8e38ec65cf..05131ccbd898c2 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2428,6 +2428,9 @@ void CodeGen::genCallInstruction(GenTreeCall* call) params.wasmSignature = m_compiler->info.compCompHnd->getWasmTypeSymbol(typeStack.Data(), typeStack.Height()); + // A non-null target expression always indicates an indirect call on Wasm, + // as currently the only possible result of the target expression would be a + // table index which must be used via call_indirect if (target != nullptr) { // Codegen should have already evaluated our target node (last) and pushed it onto the stack, @@ -2439,52 +2442,28 @@ void CodeGen::genCallInstruction(GenTreeCall* call) } else { - // If we have no target and this is a call with indirection cell then - // we do an optimization where we load the call address directly from - // the indirection cell instead of duplicating the tree. In BuildCall - // we ensure that get an extra register for the purpose. Note that for - // CFG the call might have changed to - // CORINFO_HELP_DISPATCH_INDIRECT_CALL in which case we still have the - // indirection cell but we should not try to optimize. - WellKnownArg indirectionCellArgKind = WellKnownArg::None; - if (!call->IsHelperCall(CORINFO_HELP_DISPATCH_INDIRECT_CALL)) - { - indirectionCellArgKind = call->GetIndirectionCellArgKind(); - } + // Generate a direct call to a non-virtual user defined or helper method + assert(call->IsHelperCall() || (call->gtCallType == CT_USER_FUNC)); - if (indirectionCellArgKind != WellKnownArg::None) - { - assert(call->IsR2ROrVirtualStubRelativeIndir()); + assert(call->gtEntryPoint.addr == NULL); - params.callType = EC_INDIR_R; - // params.ireg = targetAddrReg; - genEmitCallWithCurrentGC(params); + if (call->IsHelperCall()) + { + assert(!call->IsFastTailCall()); + CorInfoHelpFunc helperNum = m_compiler->eeGetHelperNum(params.methHnd); + noway_assert(helperNum != CORINFO_HELP_UNDEF); + CORINFO_CONST_LOOKUP helperLookup = m_compiler->compGetHelperFtn(helperNum); + assert(helperLookup.accessType == IAT_VALUE); + params.addr = helperLookup.addr; } else { - // Generate a direct call to a non-virtual user defined or helper method - assert(call->IsHelperCall() || (call->gtCallType == CT_USER_FUNC)); - - assert(call->gtEntryPoint.addr == NULL); - - if (call->IsHelperCall()) - { - assert(!call->IsFastTailCall()); - CorInfoHelpFunc helperNum = m_compiler->eeGetHelperNum(params.methHnd); - noway_assert(helperNum != CORINFO_HELP_UNDEF); - CORINFO_CONST_LOOKUP helperLookup = m_compiler->compGetHelperFtn(helperNum); - assert(helperLookup.accessType == IAT_VALUE); - params.addr = helperLookup.addr; - } - else - { - // Direct call to a non-virtual user function. - params.addr = call->gtDirectCallAddress; - } - - params.callType = EC_FUNC_TOKEN; - genEmitCallWithCurrentGC(params); + // Direct call to a non-virtual user function. + params.addr = call->gtDirectCallAddress; } + + params.callType = EC_FUNC_TOKEN; + genEmitCallWithCurrentGC(params); } } diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 42992fcf7ef0f0..f009f9dc497130 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -3019,9 +3019,9 @@ GenTree* Lowering::LowerCall(GenTree* node) } #ifdef TARGET_WASM - // For non-helper, managed calls, if we have portable entry points enabled, we need to lower + // For any type of managed call, if we have portable entry points enabled, we need to lower // the call according to the portable entrypoint abi - if (!call->IsUnmanaged() && !call->IsHelperCall() && + if (!call->IsUnmanaged() && m_compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PORTABLE_ENTRY_POINTS)) { LowerPEPCall(call); @@ -3669,6 +3669,7 @@ void Lowering::LowerPEPCall(GenTreeCall* call) JITDUMP("Begin lowering PEP call\n"); DISPTREERANGE(BlockRange(), call); + assert(call->gtControlExpr != nullptr, "PEP call must always have a control expression"); LIR::Use callTargetUse(BlockRange(), &call->gtControlExpr, call); JITDUMP("Creating new local variable for PEP"); @@ -3676,7 +3677,6 @@ void Lowering::LowerPEPCall(GenTreeCall* call) GenTreeLclVar* callTargetLclForArg = m_compiler->gtNewLclvNode(callTargetLclNum, TYP_I_IMPL); DISPTREE(call) - JITDUMP("Add new arg to call arg list corresponding to PEP target"); NewCallArg pepTargetArg = NewCallArg::Primitive(callTargetLclForArg).WellKnown(WellKnownArg::WasmPortableEntryPoint); CallArg* pepArg = call->gtArgs.PushBack(m_compiler, pepTargetArg); @@ -3696,9 +3696,7 @@ void Lowering::LowerPEPCall(GenTreeCall* call) // Lower the new PEP arg now that the call abi info is updated and lcl var is inserted LowerArg(call, pepArg); - - JITDUMP("Call is now:\n"); - DISPTREERANGE(BlockRange(), call); + DISPTREE(call); JITDUMP("Rewrite PEP call's control expression to indirect through the new local variable\n"); // Rewrite the call's control expression to have an additional load from the PEP local diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 3139cd066f92da..6fcd07089534d6 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -1756,23 +1756,23 @@ void CallArgs::AddFinalArgsAndDetermineABIInfo(Compiler* comp, GenTreeCall* call // That's ok; after making something a tailcall, we will invalidate this information // and reconstruct it if necessary. The tailcalling decision does not change since // this is a non-standard arg in a register. - bool needsIndirectionCell = call->IsR2RRelativeIndir() && !call->IsDelegateInvoke(); +#ifndef TARGET_WASM + bool needsIndirectionCellArg = call->IsR2RRelativeIndir() && !call->IsDelegateInvoke(); +#else + bool needsIndirectionCellArg = false; +#endif + #if defined(TARGET_XARCH) - needsIndirectionCell &= call->IsFastTailCall(); + needsIndirectionCellArg &= call->IsFastTailCall(); #endif - if (needsIndirectionCell) + if (needsIndirectionCellArg) { assert(call->gtEntryPoint.addr != nullptr); size_t addrValue = (size_t)call->gtEntryPoint.addr; GenTree* indirectCellAddress = comp->gtNewIconHandleNode(addrValue, GTF_ICON_FTN_ADDR); INDEBUG(indirectCellAddress->AsIntCon()->gtTargetHandle = (size_t)call->gtCallMethHnd); -#if defined(TARGET_WASM) - // On WASM, the address in the R2R table is actually the address of something that should - // be treated as a PortableEntryPoint. To actually dispatch, we need to indirect once more. - indirectCellAddress = comp->gtNewOperNode(GT_IND, TYP_I_IMPL, indirectCellAddress); -#endif #ifdef TARGET_ARM // TODO-ARM: We currently do not properly kill this register in LSRA // (see getKillSetForCall which does so only for VSD calls). @@ -1785,12 +1785,7 @@ void CallArgs::AddFinalArgsAndDetermineABIInfo(Compiler* comp, GenTreeCall* call // Push the stub address onto the list of arguments. NewCallArg indirCellAddrArg = NewCallArg::Primitive(indirectCellAddress).WellKnown(WellKnownArg::R2RIndirectionCell); -#ifdef TARGET_WASM - // On wasm we need to ensure we put the indirection cell address last in LIR, after the SP and formal args. - PushBack(comp, indirCellAddrArg); -#else InsertAfterThisOrFirst(comp, indirCellAddrArg); -#endif // TARGET_WASM } #endif // defined(FEATURE_READYTORUN) From df103cf1632f430e5bb20ae54782cf48169f420c Mon Sep 17 00:00:00 2001 From: adamperlin Date: Fri, 24 Apr 2026 11:40:43 -0700 Subject: [PATCH 07/13] Fix assert in lower.cpp, remove wasm case in GetIndirectionCellKind() to fix assumptions about presence of indirection cell in call args in value numbering --- src/coreclr/jit/gentree.h | 2 +- src/coreclr/jit/lower.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/gentree.h b/src/coreclr/jit/gentree.h index 67e4b5516684cc..d5902b2912f31f 100644 --- a/src/coreclr/jit/gentree.h +++ b/src/coreclr/jit/gentree.h @@ -5751,7 +5751,7 @@ struct GenTreeCall final : public GenTree return WellKnownArg::VirtualStubCell; } -#if defined(TARGET_ARMARCH) || defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) || defined(TARGET_WASM) +#if defined(TARGET_ARMARCH) || defined(TARGET_RISCV64) || defined(TARGET_LOONGARCH64) // For ARM architectures, we always use an indirection cell for R2R calls. if (IsR2RRelativeIndir() && !IsDelegateInvoke()) { diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index f009f9dc497130..86d8fb00f12f4d 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -3669,7 +3669,8 @@ void Lowering::LowerPEPCall(GenTreeCall* call) JITDUMP("Begin lowering PEP call\n"); DISPTREERANGE(BlockRange(), call); - assert(call->gtControlExpr != nullptr, "PEP call must always have a control expression"); + // PEP call must always have a control expression + assert(call->gtControlExpr != nullptr); LIR::Use callTargetUse(BlockRange(), &call->gtControlExpr, call); JITDUMP("Creating new local variable for PEP"); From b2f6e144196421c9c4a9ed5708c2b20f9e5a7bb0 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Fri, 24 Apr 2026 12:11:31 -0700 Subject: [PATCH 08/13] jit-format --- src/coreclr/jit/codegenwasm.cpp | 4 ++-- src/coreclr/jit/lower.cpp | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 7904594b81e8ec..123f9de43a53e9 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2655,8 +2655,8 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, if (helperIsManaged) { // Push PEP onto the stack because we are calling a managed helper that expects it as the last parameter. - // The helper function address is the address of an indirection cell, so we load from the cell to get the PEP address - // to push. + // The helper function address is the address of an indirection cell, so we load from the cell to get the PEP + // address to push. assert(helperFunction.accessType == IAT_PVALUE); GetEmitter()->emitAddressConstant(helperFunction.addr); GetEmitter()->emitIns_I(INS_i32_load, EA_PTRSIZE, 0); diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index e5b197b4ec303a..343bdcfb25e0f9 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -3025,12 +3025,11 @@ GenTree* Lowering::LowerCall(GenTree* node) #ifdef TARGET_WASM // For any type of managed call, if we have portable entry points enabled, we need to lower // the call according to the portable entrypoint abi - if (!call->IsUnmanaged() && - m_compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PORTABLE_ENTRY_POINTS)) + if (!call->IsUnmanaged() && m_compiler->opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PORTABLE_ENTRY_POINTS)) { LowerPEPCall(call); } -#endif // TARGET_WASM +#endif // TARGET_WASM if (varTypeIsStruct(call)) { @@ -3651,7 +3650,7 @@ GenTree* Lowering::LowerTailCallViaJitHelper(GenTreeCall* call, GenTree* callTar #ifdef TARGET_WASM //--------------------------------------------------------------------------------------------- // LowerPEPCall: Lower a call node dispatched through a PortableEntryPoint (PEP) -// +// // Given a call node with gtControlExpr representing a call target which is the address of a portable entrypoint, // this function lowers the call to appropriately dispatch through the portable entrypoint using the Portable // entrypoint calling convention. @@ -3678,12 +3677,13 @@ void Lowering::LowerPEPCall(GenTreeCall* call) LIR::Use callTargetUse(BlockRange(), &call->gtControlExpr, call); JITDUMP("Creating new local variable for PEP"); - unsigned int callTargetLclNum = callTargetUse.ReplaceWithLclVar(m_compiler); + unsigned int callTargetLclNum = callTargetUse.ReplaceWithLclVar(m_compiler); GenTreeLclVar* callTargetLclForArg = m_compiler->gtNewLclvNode(callTargetLclNum, TYP_I_IMPL); DISPTREE(call) JITDUMP("Add new arg to call arg list corresponding to PEP target"); - NewCallArg pepTargetArg = NewCallArg::Primitive(callTargetLclForArg).WellKnown(WellKnownArg::WasmPortableEntryPoint); + NewCallArg pepTargetArg = + NewCallArg::Primitive(callTargetLclForArg).WellKnown(WellKnownArg::WasmPortableEntryPoint); CallArg* pepArg = call->gtArgs.PushBack(m_compiler, pepTargetArg); pepArg->SetEarlyNode(nullptr); @@ -3691,12 +3691,11 @@ void Lowering::LowerPEPCall(GenTreeCall* call) call->gtArgs.PushLateBack(pepArg); // Set up ABI information for this arg; PEP's should be passed as the last param to a wasm function - unsigned pepIndex = call->gtArgs.CountArgs() - 1; + unsigned pepIndex = call->gtArgs.CountArgs() - 1; regNumber pepReg = MakeWasmReg(pepIndex, WasmValueType::I); pepArg->AbiInfo = ABIPassingInformation::FromSegmentByValue(m_compiler, - ABIPassingSegment::InRegister(pepReg, - 0, TARGET_POINTER_SIZE)); + ABIPassingSegment::InRegister(pepReg, 0, TARGET_POINTER_SIZE)); BlockRange().InsertBefore(call, callTargetLclForArg); // Lower the new PEP arg now that the call abi info is updated and lcl var is inserted @@ -3706,14 +3705,14 @@ void Lowering::LowerPEPCall(GenTreeCall* call) JITDUMP("Rewrite PEP call's control expression to indirect through the new local variable\n"); // Rewrite the call's control expression to have an additional load from the PEP local GenTree* controlExpr = call->gtControlExpr; - GenTree* target = Ind(controlExpr); + GenTree* target = Ind(controlExpr); BlockRange().InsertAfter(controlExpr, target); call->gtControlExpr = target; JITDUMP("Finished lowering PEP call\n"); DISPTREERANGE(BlockRange(), call); } -#endif // TARGET_WASM +#endif // TARGET_WASM //------------------------------------------------------------------------ // LowerCFGCall: Potentially lower a call to use control-flow guard. This From 0cdc541f08d740f1922ba893d21c3185528dec51 Mon Sep 17 00:00:00 2001 From: Adam Perlin Date: Fri, 24 Apr 2026 14:48:29 -0700 Subject: [PATCH 09/13] Update src/coreclr/jit/lower.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/jit/lower.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 343bdcfb25e0f9..273de9f031856a 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -3656,8 +3656,8 @@ GenTree* Lowering::LowerTailCallViaJitHelper(GenTreeCall* call, GenTree* callTar // entrypoint calling convention. // To do this, it: // 1. Introduces a new local variable to hold the PEP address -// 1. Adds a new well-known argument to the call passing this local -// 2. Rewrites the control expression to indirect through the new local, since for PEP's, the actual call target +// 2. Adds a new well-known argument to the call passing this local +// 3. Rewrites the control expression to indirect through the new local, since for PEP's, the actual call target // must be loaded from the portable entry point address. // // Arguments: From 4a4ae2c62120581ae8952573f9436a29b7fbb68a Mon Sep 17 00:00:00 2001 From: adamperlin Date: Mon, 27 Apr 2026 10:43:09 -0700 Subject: [PATCH 10/13] Use INS_I_LOAD in genEmitHelperCall to ensure compatibility with wasm64 --- src/coreclr/jit/codegenwasm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 123f9de43a53e9..2af944988cc6d4 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2668,8 +2668,8 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, // and then the PEP pointed to by the indirection cell. assert(helperFunction.accessType == IAT_PVALUE); GetEmitter()->emitAddressConstant(helperFunction.addr); - GetEmitter()->emitIns_I(INS_i32_load, EA_PTRSIZE, 0); - GetEmitter()->emitIns_I(INS_i32_load, EA_PTRSIZE, 0); + GetEmitter()->emitIns_I(INS_I_load, EA_PTRSIZE, 0); + GetEmitter()->emitIns_I(INS_I_load, EA_PTRSIZE, 0); } genEmitCallWithCurrentGC(params); From 4a60460dbbcd7635d903bd21c6e2980136fd4a6d Mon Sep 17 00:00:00 2001 From: adamperlin Date: Mon, 27 Apr 2026 11:19:42 -0700 Subject: [PATCH 11/13] Move LowerPEPCall def into lowerwasm.cpp --- src/coreclr/jit/codegenwasm.cpp | 2 +- src/coreclr/jit/lower.cpp | 67 --------------------------------- src/coreclr/jit/lower.h | 1 + src/coreclr/jit/lowerwasm.cpp | 65 ++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 68 deletions(-) diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 2af944988cc6d4..dc6b2946c22ebe 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -2659,7 +2659,7 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, // address to push. assert(helperFunction.accessType == IAT_PVALUE); GetEmitter()->emitAddressConstant(helperFunction.addr); - GetEmitter()->emitIns_I(INS_i32_load, EA_PTRSIZE, 0); + GetEmitter()->emitIns_I(INS_I_load, EA_PTRSIZE, 0); } if (params.callType == EC_INDIR_R) diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 273de9f031856a..53a7dff6169144 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -3647,73 +3647,6 @@ GenTree* Lowering::LowerTailCallViaJitHelper(GenTreeCall* call, GenTree* callTar return result; } -#ifdef TARGET_WASM -//--------------------------------------------------------------------------------------------- -// LowerPEPCall: Lower a call node dispatched through a PortableEntryPoint (PEP) -// -// Given a call node with gtControlExpr representing a call target which is the address of a portable entrypoint, -// this function lowers the call to appropriately dispatch through the portable entrypoint using the Portable -// entrypoint calling convention. -// To do this, it: -// 1. Introduces a new local variable to hold the PEP address -// 2. Adds a new well-known argument to the call passing this local -// 3. Rewrites the control expression to indirect through the new local, since for PEP's, the actual call target -// must be loaded from the portable entry point address. -// -// Arguments: -// call - The call node to lower. It is expected that the call node has gtControlExpr set to the original -// call target and that the call does not have a PEP arg already. -// -// Return Value: -// None. The call node is modified in place. -// -void Lowering::LowerPEPCall(GenTreeCall* call) -{ - JITDUMP("Begin lowering PEP call\n"); - DISPTREERANGE(BlockRange(), call); - - // PEP call must always have a control expression - assert(call->gtControlExpr != nullptr); - LIR::Use callTargetUse(BlockRange(), &call->gtControlExpr, call); - - JITDUMP("Creating new local variable for PEP"); - unsigned int callTargetLclNum = callTargetUse.ReplaceWithLclVar(m_compiler); - GenTreeLclVar* callTargetLclForArg = m_compiler->gtNewLclvNode(callTargetLclNum, TYP_I_IMPL); - DISPTREE(call) - - JITDUMP("Add new arg to call arg list corresponding to PEP target"); - NewCallArg pepTargetArg = - NewCallArg::Primitive(callTargetLclForArg).WellKnown(WellKnownArg::WasmPortableEntryPoint); - CallArg* pepArg = call->gtArgs.PushBack(m_compiler, pepTargetArg); - - pepArg->SetEarlyNode(nullptr); - pepArg->SetLateNode(callTargetLclForArg); - call->gtArgs.PushLateBack(pepArg); - - // Set up ABI information for this arg; PEP's should be passed as the last param to a wasm function - unsigned pepIndex = call->gtArgs.CountArgs() - 1; - regNumber pepReg = MakeWasmReg(pepIndex, WasmValueType::I); - pepArg->AbiInfo = - ABIPassingInformation::FromSegmentByValue(m_compiler, - ABIPassingSegment::InRegister(pepReg, 0, TARGET_POINTER_SIZE)); - BlockRange().InsertBefore(call, callTargetLclForArg); - - // Lower the new PEP arg now that the call abi info is updated and lcl var is inserted - LowerArg(call, pepArg); - DISPTREE(call); - - JITDUMP("Rewrite PEP call's control expression to indirect through the new local variable\n"); - // Rewrite the call's control expression to have an additional load from the PEP local - GenTree* controlExpr = call->gtControlExpr; - GenTree* target = Ind(controlExpr); - BlockRange().InsertAfter(controlExpr, target); - call->gtControlExpr = target; - - JITDUMP("Finished lowering PEP call\n"); - DISPTREERANGE(BlockRange(), call); -} -#endif // TARGET_WASM - //------------------------------------------------------------------------ // LowerCFGCall: Potentially lower a call to use control-flow guard. This // expands indirect calls into either a validate+call sequence or to a dispatch diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index cc83d38184318e..fe4b107e0911b0 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -419,6 +419,7 @@ class Lowering final : public Phase #ifdef TARGET_WASM GenTree* LowerNeg(GenTreeOp* node); void LowerIndexAddr(GenTreeIndexAddr* indexAddr); + void LowerPEPCall(GenTreeCall* call); #endif bool TryCreateAddrMode(GenTree* addr, bool isContainable, GenTree* parent); diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index ab9c56164fa1a8..5d1b7077fd12d7 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -41,6 +41,71 @@ bool Lowering::IsCallTargetInRange(void* addr) return true; } +//--------------------------------------------------------------------------------------------- +// LowerPEPCall: Lower a call node dispatched through a PortableEntryPoint (PEP) +// +// Given a call node with gtControlExpr representing a call target which is the address of a portable entrypoint, +// this function lowers the call to appropriately dispatch through the portable entrypoint using the Portable +// entrypoint calling convention. +// To do this, it: +// 1. Introduces a new local variable to hold the PEP address +// 2. Adds a new well-known argument to the call passing this local +// 3. Rewrites the control expression to indirect through the new local, since for PEP's, the actual call target +// must be loaded from the portable entry point address. +// +// Arguments: +// call - The call node to lower. It is expected that the call node has gtControlExpr set to the original +// call target and that the call does not have a PEP arg already. +// +// Return Value: +// None. The call node is modified in place. +// +void Lowering::LowerPEPCall(GenTreeCall* call) +{ + JITDUMP("Begin lowering PEP call\n"); + DISPTREERANGE(BlockRange(), call); + + // PEP call must always have a control expression + assert(call->gtControlExpr != nullptr); + LIR::Use callTargetUse(BlockRange(), &call->gtControlExpr, call); + + JITDUMP("Creating new local variable for PEP"); + unsigned int callTargetLclNum = callTargetUse.ReplaceWithLclVar(m_compiler); + GenTreeLclVar* callTargetLclForArg = m_compiler->gtNewLclvNode(callTargetLclNum, TYP_I_IMPL); + DISPTREE(call) + + JITDUMP("Add new arg to call arg list corresponding to PEP target"); + NewCallArg pepTargetArg = + NewCallArg::Primitive(callTargetLclForArg).WellKnown(WellKnownArg::WasmPortableEntryPoint); + CallArg* pepArg = call->gtArgs.PushBack(m_compiler, pepTargetArg); + + pepArg->SetEarlyNode(nullptr); + pepArg->SetLateNode(callTargetLclForArg); + call->gtArgs.PushLateBack(pepArg); + + // Set up ABI information for this arg; PEP's should be passed as the last param to a wasm function + unsigned pepIndex = call->gtArgs.CountArgs() - 1; + regNumber pepReg = MakeWasmReg(pepIndex, WasmValueType::I); + pepArg->AbiInfo = + ABIPassingInformation::FromSegmentByValue(m_compiler, + ABIPassingSegment::InRegister(pepReg, 0, TARGET_POINTER_SIZE)); + BlockRange().InsertBefore(call, callTargetLclForArg); + + // Lower the new PEP arg now that the call abi info is updated and lcl var is inserted + LowerArg(call, pepArg); + DISPTREE(call); + + JITDUMP("Rewrite PEP call's control expression to indirect through the new local variable\n"); + // Rewrite the call's control expression to have an additional load from the PEP local + GenTree* controlExpr = call->gtControlExpr; + GenTree* target = Ind(controlExpr); + BlockRange().InsertAfter(controlExpr, target); + call->gtControlExpr = target; + + JITDUMP("Finished lowering PEP call\n"); + DISPTREERANGE(BlockRange(), call); +} + //------------------------------------------------------------------------ // IsContainableImmed: Is an immediate encodable in-place? // From d74680ee2d460c9155f4fc4b6cc5e01f3a3b1433 Mon Sep 17 00:00:00 2001 From: adamperlin Date: Mon, 27 Apr 2026 13:00:37 -0700 Subject: [PATCH 12/13] More review feedback --- src/coreclr/jit/lower.h | 9 +++++---- src/coreclr/jit/morph.cpp | 10 +++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/coreclr/jit/lower.h b/src/coreclr/jit/lower.h index fe4b107e0911b0..da5ee0c863c3fc 100644 --- a/src/coreclr/jit/lower.h +++ b/src/coreclr/jit/lower.h @@ -153,9 +153,11 @@ class Lowering final : public Phase bool LowerCallMemcmp(GenTreeCall* call, GenTree** next); bool LowerCallMemset(GenTreeCall* call, GenTree** next); void LowerCFGCall(GenTreeCall* call); - void LowerPEPCall(GenTreeCall* call); - void MovePutArgNodesUpToCall(GenTreeCall* call); - void MovePutArgUpToCall(GenTreeCall* call, GenTree* node); +#ifdef TARGET_WASM + void LowerPEPCall(GenTreeCall* call); +#endif + void MovePutArgNodesUpToCall(GenTreeCall* call); + void MovePutArgUpToCall(GenTreeCall* call, GenTree* node); #ifndef TARGET_64BIT GenTree* DecomposeLongCompare(GenTree* cmp); #endif @@ -419,7 +421,6 @@ class Lowering final : public Phase #ifdef TARGET_WASM GenTree* LowerNeg(GenTreeOp* node); void LowerIndexAddr(GenTreeIndexAddr* indexAddr); - void LowerPEPCall(GenTreeCall* call); #endif bool TryCreateAddrMode(GenTree* addr, bool isContainable, GenTree* parent); diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 7f80fe300ffe88..4c8688f625e17f 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -1758,14 +1758,18 @@ void CallArgs::AddFinalArgsAndDetermineABIInfo(Compiler* comp, GenTreeCall* call // this is a non-standard arg in a register. #ifndef TARGET_WASM bool needsIndirectionCellArg = call->IsR2RRelativeIndir() && !call->IsDelegateInvoke(); -#else - bool needsIndirectionCellArg = false; -#endif #if defined(TARGET_XARCH) needsIndirectionCellArg &= call->IsFastTailCall(); #endif +#else + // TARGET_WASM does not use an explicit indirection cell arg for the R2R calling convention, + // the address of the indirection cell is recoverable from the portable entrypoint which + // we pass as part of the Wasm managed calling convention (See LowerPEPCall). + bool needsIndirectionCellArg = false; +#endif + if (needsIndirectionCellArg) { assert(call->gtEntryPoint.addr != nullptr); From 29cf0608a6890514efebbd637e5d0b8471ad9438 Mon Sep 17 00:00:00 2001 From: Adam Perlin Date: Mon, 27 Apr 2026 13:46:35 -0700 Subject: [PATCH 13/13] Update src/coreclr/jit/lowerwasm.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/jit/lowerwasm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index 5d1b7077fd12d7..15b7cdcf052b87 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -72,7 +72,7 @@ void Lowering::LowerPEPCall(GenTreeCall* call) JITDUMP("Creating new local variable for PEP"); unsigned int callTargetLclNum = callTargetUse.ReplaceWithLclVar(m_compiler); GenTreeLclVar* callTargetLclForArg = m_compiler->gtNewLclvNode(callTargetLclNum, TYP_I_IMPL); - DISPTREE(call) + DISPTREE(call); JITDUMP("Add new arg to call arg list corresponding to PEP target"); NewCallArg pepTargetArg =