Skip to content

Commit

Permalink
Simplify "secret stub arg" handling between JIT and EE (#100823)
Browse files Browse the repository at this point in the history
When a pinvoke/reverse pinvoke needs marshalling the VM creates an IL stub to
perform this marshalling. These IL stubs have a non-standard parameter called
the "secret stub parameter". The VM uses this parameter to map the IL stub back
to the user defined function that caused the IL stub to be required (multiple
user functions can share the same IL stubs, so the mapping cannot be done by
other means).

To facilitate the access of this value the JIT must make the parameter's value
available to the VM somehow. Previously this was done in two separate ways for
32-bit and 64-bit target:
- For 32-bit targets the parameter was marked as do-not-enregister and always
  spilled to the stack frame at a location that was known by the VM.
- For 64-bit targets the parameter was saved in the `InlinedCallFrame` as part
  of a VM helper call. We still marked it as do-not-enregister, probably because
  parameter homing did not handle it.

For 64-bit targets this introduces a bit of inefficiency: the secret stub
parameter is only needed for the IL stubs case, but `InlinedCallFrame` is used
for all inlined pinvokes. So we ended up with a larger frame than necessary and
with an additional store to the frame, even outside IL stubs.

This change removes that inefficiency by unifying how 32-bit and 64-bit targets
work:

- Switch all platforms to only allocate space in `InlinedCallFrame` for the
  secret stub parameter when necessary
- Move responsibility of storing the secret stub parameter out of
  `CORINFO_HELP_INIT_PINVOKE_FRAME` and to the JIT generated code
- Remove special casing of frame layout around the secret stub parameter and
  frame structures within the JIT
- Enable enregistration for the secret stub parameter

Fix #100662
  • Loading branch information
jakobbotsch authored May 12, 2024
1 parent 3de068c commit 073e35e
Show file tree
Hide file tree
Showing 33 changed files with 191 additions and 281 deletions.
2 changes: 1 addition & 1 deletion eng/native/sanitizer-ignorelist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ fun:_ZN11CMiniMdBase19UsesAllocatedMemoryEP11CMiniColDef
# 2 0x4e8051 in __ubsan_handle_dynamic_type_cache_miss
# 3 0x7f02ce676cd8 in JIT_InitPInvokeFrame(InlinedCallFrame*, void*) /home/steveharter/git/dotnet_coreclr/vm/jithelpers.cpp:6491:9
# 4 0x7f0252bbceb2 (<unknown module>)
fun:_Z20JIT_InitPInvokeFrameP16InlinedCallFramePv
fun:_Z20JIT_InitPInvokeFrameP16InlinedCallFrame

4 changes: 4 additions & 0 deletions src/coreclr/inc/corinfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -1754,13 +1754,17 @@ struct CORINFO_EE_INFO
// Size of the Frame structure
unsigned size;

// Size of the Frame structure when it also contains the secret stub arg
unsigned sizeWithSecretStubArg;

unsigned offsetOfGSCookie;
unsigned offsetOfFrameVptr;
unsigned offsetOfFrameLink;
unsigned offsetOfCallSiteSP;
unsigned offsetOfCalleeSavedFP;
unsigned offsetOfCallTarget;
unsigned offsetOfReturnAddress;
unsigned offsetOfSecretStubArg;
// This offset is used only for ARM
unsigned offsetOfSPAfterProlog;
}
Expand Down
10 changes: 5 additions & 5 deletions src/coreclr/inc/jiteeversionguid.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,11 @@ typedef const GUID *LPCGUID;
#define GUID_DEFINED
#endif // !GUID_DEFINED

constexpr GUID JITEEVersionIdentifier = { /* 43854594-cd60-45df-a89f-5b7697586f46 */
0x43854594,
0xcd60,
0x45df,
{0xa8, 0x9f, 0x5b, 0x76, 0x97, 0x58, 0x6f, 0x46}
constexpr GUID JITEEVersionIdentifier = { /* 227e46fa-1be3-4770-b613-4a239e7c28aa */
0x227e46fa,
0x1be3,
0x4770,
{0xb6, 0x13, 0x4a, 0x23, 0x9e, 0x7c, 0x28, 0xaa}
};

//////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/jit/codegen.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ class CodeGen final : public CodeGenInterface

var_types genParamStackStoreType(LclVarDsc* dsc, const ABIPassingSegment& seg);
void genSpillOrAddRegisterParam(unsigned lclNum, class RegGraph* graph);
void genSpillOrAddNonStandardRegisterParam(unsigned lclNum, regNumber sourceReg, class RegGraph* graph);
void genEnregisterIncomingStackArgs();
#if defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
void genEnregisterOSRArgsAndLocals(regNumber initReg, bool* pInitRegZeroed);
Expand Down
53 changes: 36 additions & 17 deletions src/coreclr/jit/codegencommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3246,6 +3246,31 @@ void CodeGen::genSpillOrAddRegisterParam(unsigned lclNum, RegGraph* graph)
}
}

// -----------------------------------------------------------------------------
// genSpillOrAddNonStandardRegisterParam: Handle a non-standard register parameter either
// by homing it to stack immediately, or by adding it to the register graph.
//
// Parameters:
// lclNum - Local that represents the non-standard parameter
// sourceReg - Register that the non-standard parameter is in on entry to the function
// graph - The register graph to add to
//
void CodeGen::genSpillOrAddNonStandardRegisterParam(unsigned lclNum, regNumber sourceReg, RegGraph* graph)
{
LclVarDsc* varDsc = compiler->lvaGetDesc(lclNum);
if (varDsc->lvOnFrame && (!varDsc->lvIsInReg() || varDsc->lvLiveInOutOfHndlr))
{
GetEmitter()->emitIns_S_R(ins_Store(varDsc->TypeGet()), emitActualTypeSize(varDsc), sourceReg, lclNum, 0);
}

if (varDsc->lvIsInReg())
{
RegNode* sourceRegNode = graph->GetOrAdd(sourceReg);
RegNode* destRegNode = graph->GetOrAdd(varDsc->GetRegNum());
graph->AddEdge(sourceRegNode, destRegNode, TYP_I_IMPL, 0);
}
}

// -----------------------------------------------------------------------------
// genHomeRegisterParams: Move all register parameters to their initial
// assigned location.
Expand Down Expand Up @@ -3289,6 +3314,12 @@ void CodeGen::genHomeRegisterParams(regNumber initReg, bool* initRegStillZeroed)
}
}

if (compiler->info.compPublishStubParam && ((paramRegs & RBM_SECRET_STUB_PARAM) != RBM_NONE))
{
GetEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, REG_SECRET_STUB_PARAM,
compiler->lvaStubArgumentVar, 0);
}

return;
}

Expand Down Expand Up @@ -3321,6 +3352,11 @@ void CodeGen::genHomeRegisterParams(regNumber initReg, bool* initRegStillZeroed)
}
}

if (compiler->info.compPublishStubParam && ((paramRegs & RBM_SECRET_STUB_PARAM) != RBM_NONE))
{
genSpillOrAddNonStandardRegisterParam(compiler->lvaStubArgumentVar, REG_SECRET_STUB_PARAM, &graph);
}

DBEXEC(VERBOSE, graph.Dump());

regMaskTP busyRegs = intRegState.rsCalleeRegArgMaskLiveIn | floatRegState.rsCalleeRegArgMaskLiveIn;
Expand Down Expand Up @@ -3784,13 +3820,6 @@ void CodeGen::genCheckUseBlockInit()
{
regMaskTP maskCalleeRegArgMask = intRegState.rsCalleeRegArgMaskLiveIn;

// If there is a secret stub param, don't count it, as it will no longer
// be live when we do block init.
if (compiler->info.compPublishStubParam)
{
maskCalleeRegArgMask &= ~RBM_SECRET_STUB_PARAM;
}

#ifdef TARGET_ARM
//
// On the Arm if we are using a block init to initialize, then we
Expand Down Expand Up @@ -5572,16 +5601,6 @@ void CodeGen::genFnProlog()
}
#endif // TARGET_ARM

if (compiler->info.compPublishStubParam)
{
GetEmitter()->emitIns_S_R(ins_Store(TYP_I_IMPL), EA_PTRSIZE, REG_SECRET_STUB_PARAM,
compiler->lvaStubArgumentVar, 0);
assert(intRegState.rsCalleeRegArgMaskLiveIn & RBM_SECRET_STUB_PARAM);

// It's no longer live; clear it out so it can be used after this in the prolog
intRegState.rsCalleeRegArgMaskLiveIn &= ~RBM_SECRET_STUB_PARAM;
}

//
// Zero out the frame as needed
//
Expand Down
5 changes: 0 additions & 5 deletions src/coreclr/jit/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4602,11 +4602,6 @@ void Compiler::compCompile(void** methodCodePtr, uint32_t* methodCodeSize, JitFl
assert(lvaStubArgumentVar == BAD_VAR_NUM);
lvaStubArgumentVar = lvaGrabTempWithImplicitUse(false DEBUGARG("stub argument"));
lvaGetDesc(lvaStubArgumentVar)->lvType = TYP_I_IMPL;
// TODO-CQ: there is no need to mark it as doNotEnreg. There are no stores for this local
// before codegen so liveness and LSRA mark it as "liveIn" and always allocate a stack slot for it.
// However, it would be better to process it like other argument locals and keep it in
// a reg for the whole method without spilling to the stack when possible.
lvaSetVarDoNotEnregister(lvaStubArgumentVar DEBUGARG(DoNotEnregisterReason::VMNeedsStackAddr));
}
};
DoPhase(this, PHASE_PRE_IMPORT, preImportPhase);
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/compiler.h
Original file line number Diff line number Diff line change
Expand Up @@ -4243,7 +4243,7 @@ class Compiler
return lvaGetDesc(lclNum)->lvInSsa;
}

unsigned lvaStubArgumentVar; // variable representing the secret stub argument coming in EAX
unsigned lvaStubArgumentVar; // variable representing the secret stub argument

unsigned lvaPSPSym; // variable representing the PSPSym

Expand Down
5 changes: 4 additions & 1 deletion src/coreclr/jit/flowgraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2438,7 +2438,10 @@ PhaseStatus Compiler::fgAddInternal()

LclVarDsc* varDsc = lvaGetDesc(lvaInlinedPInvokeFrameVar);
// Make room for the inlined frame.
lvaSetStruct(lvaInlinedPInvokeFrameVar, typGetBlkLayout(eeGetEEInfo()->inlinedCallFrameInfo.size), false);
const CORINFO_EE_INFO* eeInfo = eeGetEEInfo();
unsigned frameSize = info.compPublishStubParam ? eeInfo->inlinedCallFrameInfo.sizeWithSecretStubArg
: eeInfo->inlinedCallFrameInfo.size;
lvaSetStruct(lvaInlinedPInvokeFrameVar, typGetBlkLayout(frameSize), false);
}

// Do we need to insert a "JustMyCode" callback?
Expand Down
2 changes: 0 additions & 2 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13136,8 +13136,6 @@ const char* Compiler::gtGetWellKnownArgNameForArgMsg(WellKnownArg arg)
return "retbuf";
case WellKnownArg::PInvokeFrame:
return "pinv frame";
case WellKnownArg::SecretStubParam:
return "stub param";
case WellKnownArg::WrapperDelegateCell:
return "wrap cell";
case WellKnownArg::ShiftLow:
Expand Down
1 change: 0 additions & 1 deletion src/coreclr/jit/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -4465,7 +4465,6 @@ enum class WellKnownArg : unsigned
InstParam,
RetBuffer,
PInvokeFrame,
SecretStubParam,
WrapperDelegateCell,
ShiftLow,
ShiftHigh,
Expand Down
35 changes: 0 additions & 35 deletions src/coreclr/jit/lclvars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6694,22 +6694,6 @@ void Compiler::lvaAssignVirtualFrameOffsetsToLocals()
continue;
}

// This should be low on the stack. Hence, it will be assigned later.
if (lclNum == lvaStubArgumentVar)
{
#ifdef JIT32_GCENCODER
noway_assert(codeGen->isFramePointerUsed());
#endif
continue;
}

// This should be low on the stack. Hence, it will be assigned later.
if (lclNum == lvaInlinedPInvokeFrameVar)
{
noway_assert(codeGen->isFramePointerUsed());
continue;
}

if (varDsc->lvIsParam)
{
#ifdef TARGET_ARM64
Expand Down Expand Up @@ -6852,25 +6836,6 @@ void Compiler::lvaAssignVirtualFrameOffsetsToLocals()
*-------------------------------------------------------------------------
*/

// lvaInlinedPInvokeFrameVar and lvaStubArgumentVar need to be assigned last
// Important: The stack walker depends on lvaStubArgumentVar immediately
// following lvaInlinedPInvokeFrameVar in the frame.

if (lvaStubArgumentVar != BAD_VAR_NUM)
{
#ifdef JIT32_GCENCODER
noway_assert(codeGen->isFramePointerUsed());
#endif
stkOffs = lvaAllocLocalAndSetVirtualOffset(lvaStubArgumentVar, lvaLclSize(lvaStubArgumentVar), stkOffs);
}

if (lvaInlinedPInvokeFrameVar != BAD_VAR_NUM)
{
noway_assert(codeGen->isFramePointerUsed());
stkOffs =
lvaAllocLocalAndSetVirtualOffset(lvaInlinedPInvokeFrameVar, lvaLclSize(lvaInlinedPInvokeFrameVar), stkOffs);
}

#ifdef JIT32_GCENCODER
// JIT32 encoder cannot handle GS cookie at fp+0 since NO_GS_COOKIE == 0.
// Add some padding if it is the last allocated local.
Expand Down
78 changes: 30 additions & 48 deletions src/coreclr/jit/lower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5515,7 +5515,7 @@ GenTree* Lowering::CreateFrameLinkUpdate(FrameLinkAction action)
const CORINFO_EE_INFO* pInfo = comp->eeGetEEInfo();
const CORINFO_EE_INFO::InlinedCallFrameInfo& callFrameInfo = pInfo->inlinedCallFrameInfo;

GenTree* TCB = new (comp, GT_LCL_VAR) GenTreeLclVar(GT_LCL_VAR, TYP_I_IMPL, comp->info.compLvFrameListRoot);
GenTree* TCB = comp->gtNewLclVarNode(comp->info.compLvFrameListRoot, TYP_I_IMPL);

// Thread->m_pFrame
GenTree* addr = new (comp, GT_LEA) GenTreeAddrMode(TYP_I_IMPL, TCB, nullptr, 1, pInfo->offsetOfThreadFrame);
Expand All @@ -5525,18 +5525,17 @@ GenTree* Lowering::CreateFrameLinkUpdate(FrameLinkAction action)
if (action == PushFrame)
{
// Thread->m_pFrame = &inlinedCallFrame;
data = new (comp, GT_LCL_ADDR)
GenTreeLclFld(GT_LCL_ADDR, TYP_BYREF, comp->lvaInlinedPInvokeFrameVar, callFrameInfo.offsetOfFrameVptr);
data = comp->gtNewLclAddrNode(comp->lvaInlinedPInvokeFrameVar, callFrameInfo.offsetOfFrameVptr);
}
else
{
assert(action == PopFrame);
// Thread->m_pFrame = inlinedCallFrame.m_pNext;

data = new (comp, GT_LCL_FLD) GenTreeLclFld(GT_LCL_FLD, TYP_BYREF, comp->lvaInlinedPInvokeFrameVar,
pInfo->inlinedCallFrameInfo.offsetOfFrameLink);
data = comp->gtNewLclFldNode(comp->lvaInlinedPInvokeFrameVar, TYP_I_IMPL,
pInfo->inlinedCallFrameInfo.offsetOfFrameLink);
}
GenTree* storeInd = new (comp, GT_STOREIND) GenTreeStoreInd(TYP_I_IMPL, addr, data);
GenTree* storeInd = comp->gtNewStoreIndNode(TYP_I_IMPL, addr, data);
return storeInd;
}

Expand All @@ -5558,28 +5557,22 @@ GenTree* Lowering::CreateFrameLinkUpdate(FrameLinkAction action)
// +08h +04h vptr for class InlinedCallFrame offsetOfFrameVptr method prolog
// +10h +08h m_Next offsetOfFrameLink method prolog
// +18h +0Ch m_Datum offsetOfCallTarget call site
// +20h n/a m_StubSecretArg not set by JIT
// +28h +10h m_pCallSiteSP offsetOfCallSiteSP x86: call site, and zeroed in method
// +20h +10h m_pCallSiteSP offsetOfCallSiteSP x86: call site, and zeroed in method
// prolog;
// non-x86: method prolog (SP remains
// constant in function, after prolog: no
// localloc and PInvoke in same function)
// +30h +14h m_pCallerReturnAddress offsetOfReturnAddress call site
// +38h +18h m_pCalleeSavedFP offsetOfCalleeSavedFP not set by JIT
// +1Ch m_pThread
// +28h +14h m_pCallerReturnAddress offsetOfReturnAddress call site
// +30h +18h m_pCalleeSavedFP offsetOfCalleeSavedFP not set by JIT
// +38h +1Ch m_pThread
// +20h m_pSPAfterProlog offsetOfSPAfterProlog arm only
// +20/24h JIT retval spill area (int) before call_gc ???
// +24/28h JIT retval spill area (long) before call_gc ???
// +28/2Ch Saved value of EBP method prolog ???
// +40h +20/24h m_StubSecretArg offsetOfSecretStubArg method prolog of IL stubs with secret arg
//
// Note that in the VM, InlinedCallFrame is a C++ class whose objects have a 'this' pointer that points
// to the InlinedCallFrame vptr (the 2nd field listed above), and the GS cookie is stored *before*
// the object. When we link the InlinedCallFrame onto the Frame chain, we must point at this location,
// and not at the beginning of the InlinedCallFrame local, which is actually the GS cookie.
//
// Return Value:
// none
//
// See the usages for USE_PER_FRAME_PINVOKE_INIT for more information.
void Lowering::InsertPInvokeMethodProlog()
{
Expand All @@ -5601,47 +5594,38 @@ void Lowering::InsertPInvokeMethodProlog()
const CORINFO_EE_INFO* pInfo = comp->eeGetEEInfo();
const CORINFO_EE_INFO::InlinedCallFrameInfo& callFrameInfo = pInfo->inlinedCallFrameInfo;

// First arg: &compiler->lvaInlinedPInvokeFrameVar + callFrameInfo.offsetOfFrameVptr
#if defined(DEBUG)
const LclVarDsc* inlinedPInvokeDsc = comp->lvaGetDesc(comp->lvaInlinedPInvokeFrameVar);
assert(inlinedPInvokeDsc->IsAddressExposed());
#endif // DEBUG
GenTree* frameAddr = new (comp, GT_LCL_ADDR)
GenTreeLclFld(GT_LCL_ADDR, TYP_BYREF, comp->lvaInlinedPInvokeFrameVar, callFrameInfo.offsetOfFrameVptr);
assert(comp->lvaGetDesc(comp->lvaInlinedPInvokeFrameVar)->IsAddressExposed());

// Call runtime helper to fill in our InlinedCallFrame and push it on the Frame list:
// TCB = CORINFO_HELP_INIT_PINVOKE_FRAME(&symFrameStart, secretArg);
GenTreeCall* call = comp->gtNewHelperCallNode(CORINFO_HELP_INIT_PINVOKE_FRAME, TYP_I_IMPL);
GenTree* const insertionPoint = firstBlockRange.FirstNonCatchArgNode();

NewCallArg frameAddrArg = NewCallArg::Primitive(frameAddr).WellKnown(WellKnownArg::PInvokeFrame);
call->gtArgs.PushBack(comp, frameAddrArg);
// for x86/arm32 don't pass the secretArg.
#if !defined(TARGET_X86) && !defined(TARGET_ARM)
GenTree* argNode;
// Store the stub secret arg if necessary. This has to be done before the
// call to the init helper below, which links the frame into the thread
// list on 32-bit platforms.
// InlinedCallFrame.m_StubSecretArg = stubSecretArg;
if (comp->info.compPublishStubParam)
{
argNode = comp->gtNewLclvNode(comp->lvaStubArgumentVar, TYP_I_IMPL);
GenTree* value = comp->gtNewLclvNode(comp->lvaStubArgumentVar, TYP_I_IMPL);
GenTree* store = comp->gtNewStoreLclFldNode(comp->lvaInlinedPInvokeFrameVar, TYP_I_IMPL,
callFrameInfo.offsetOfSecretStubArg, value);
firstBlockRange.InsertBefore(insertionPoint, LIR::SeqTree(comp, store));
DISPTREERANGE(firstBlockRange, store);
}
else
{
argNode = comp->gtNewIconNode(0, TYP_I_IMPL);
}
NewCallArg stubParamArg = NewCallArg::Primitive(argNode).WellKnown(WellKnownArg::SecretStubParam);
call->gtArgs.PushBack(comp, stubParamArg);
#endif

// Call runtime helper to fill in our InlinedCallFrame and push it on the Frame list:
// TCB = CORINFO_HELP_INIT_PINVOKE_FRAME(&symFrameStart);
GenTree* frameAddr = comp->gtNewLclAddrNode(comp->lvaInlinedPInvokeFrameVar, callFrameInfo.offsetOfFrameVptr);
NewCallArg frameAddrArg = NewCallArg::Primitive(frameAddr).WellKnown(WellKnownArg::PInvokeFrame);

GenTreeCall* call = comp->gtNewHelperCallNode(CORINFO_HELP_INIT_PINVOKE_FRAME, TYP_I_IMPL);
call->gtArgs.PushBack(comp, frameAddrArg);

// some sanity checks on the frame list root vardsc
const unsigned lclNum = comp->info.compLvFrameListRoot;
const LclVarDsc* varDsc = comp->lvaGetDesc(lclNum);
noway_assert(!varDsc->lvIsParam);
noway_assert(varDsc->lvType == TYP_I_IMPL);

GenTree* store = new (comp, GT_STORE_LCL_VAR) GenTreeLclVar(GT_STORE_LCL_VAR, TYP_I_IMPL, lclNum);
store->AsOp()->gtOp1 = call;
store->gtFlags |= GTF_VAR_DEF;

GenTree* const insertionPoint = firstBlockRange.FirstNonCatchArgNode();

GenTree* store = comp->gtNewStoreLclVarNode(lclNum, call);
comp->fgMorphTree(store);
firstBlockRange.InsertBefore(insertionPoint, LIR::SeqTree(comp, store));
DISPTREERANGE(firstBlockRange, store);
Expand All @@ -5656,7 +5640,6 @@ void Lowering::InsertPInvokeMethodProlog()
GenTree* spValue = PhysReg(REG_SPBASE);
GenTreeLclFld* storeSP = comp->gtNewStoreLclFldNode(comp->lvaInlinedPInvokeFrameVar, TYP_I_IMPL,
callFrameInfo.offsetOfCallSiteSP, spValue);
assert(inlinedPInvokeDsc->lvDoNotEnregister);

firstBlockRange.InsertBefore(insertionPoint, LIR::SeqTree(comp, storeSP));
DISPTREERANGE(firstBlockRange, storeSP);
Expand All @@ -5672,7 +5655,6 @@ void Lowering::InsertPInvokeMethodProlog()
GenTree* fpValue = PhysReg(REG_FPBASE);
GenTreeLclFld* storeFP = comp->gtNewStoreLclFldNode(comp->lvaInlinedPInvokeFrameVar, TYP_I_IMPL,
callFrameInfo.offsetOfCalleeSavedFP, fpValue);
assert(inlinedPInvokeDsc->lvDoNotEnregister);

firstBlockRange.InsertBefore(insertionPoint, LIR::SeqTree(comp, storeFP));
DISPTREERANGE(firstBlockRange, storeFP);
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/jit/lsra.h
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,8 @@ class LinearScan : public LinearScanInterface
template <bool localVarsEnregistered>
void buildIntervals();

void buildInitialParamDef(const LclVarDsc* varDsc, regNumber paramReg);

// This is where the actual assignment is done for scenarios where
// no local var enregistration is done.
void allocateRegistersMinimal();
Expand Down
Loading

0 comments on commit 073e35e

Please sign in to comment.