Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
JIT: Optimize expansion of indir cell addresses for CFG
On ARM64 it is expensive to materialize the address of an indirection
cell since that requires multiple instructions. This is particular a
problem when CFG is enabled where validation + call uses the indir cell
twice. This changes the CFG logic to create a local in this case.

We also were forcing the indir cell argument node into the register.
This is not ideal for this particular case, so remove this logic and let
LSRA deal with it. This also required fixing a bug where LSRA on ARM32
would not properly kill the callee-saved indir cell register for R2R
calls.

Fix #65076
  • Loading branch information
jakobbotsch committed Jun 9, 2022
commit 1b7ff390d4ebb2a5086b6f815325fc96af113c63
56 changes: 52 additions & 4 deletions src/coreclr/jit/lower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2283,10 +2283,58 @@ void Lowering::LowerCFGCall(GenTreeCall* call)
}

GenTree* callTarget = call->gtCallType == CT_INDIRECT ? call->gtCallAddr : call->gtControlExpr;
if ((callTarget == nullptr) || callTarget->IsIntegralConst())
if (callTarget == nullptr)
{
// This is a direct call, no CFG check is necessary.
return;
assert((call->gtCallType != CT_INDIRECT) && (!call->IsVirtual() || call->IsVirtualStubRelativeIndir()));
if (!call->IsVirtual())
{
// Direct call with stashed address
return;
}

// This is a VSD call with the call target being null because we are
// supposed to load it from the indir cell. Due to CFG we will need
// this address twice, and at least on ARM64 we do not want to
// materialize the constant both times.
CallArg* indirCellArg = call->gtArgs.FindWellKnownArg(WellKnownArg::VirtualStubCell);
assert((indirCellArg != nullptr) && indirCellArg->GetNode()->OperIs(GT_PUTARG_REG));

GenTreeOp* putArgNode = indirCellArg->GetNode()->AsOp();
LIR::Use indirCellArgUse(BlockRange(), &putArgNode->gtOp1, putArgNode);

// On non-xarch, we create a local even for constants. On xarch cloning
// the constant is better since it can be contained in the load below.
bool cloneConsts = false;
#ifdef TARGET_XARCH
cloneConsts = true;
#endif

GenTree* indirCellClone;

if (indirCellArgUse.Def()->OperIs(GT_LCL_VAR) || (cloneConsts && indirCellArgUse.Def()->IsCnsIntOrI()))
{
indirCellClone = comp->gtClone(indirCellArgUse.Def());
}
else
{
unsigned newLcl = indirCellArgUse.ReplaceWithLclVar(comp);
indirCellClone = comp->gtNewLclvNode(newLcl, TYP_I_IMPL);
}

callTarget = Ind(indirCellClone);
LIR::Range controlExprRange = LIR::SeqTree(comp, callTarget);
ContainCheckRange(controlExprRange);

BlockRange().InsertBefore(call, std::move(controlExprRange));
call->gtControlExpr = callTarget;
}
else
{
if (callTarget->IsIntegralConst())
{
// This is a direct call, no CFG check is necessary.
return;
}
}

CFGCallKind cfgKind = call->GetCFGCallKind();
Expand Down Expand Up @@ -5127,7 +5175,7 @@ GenTree* Lowering::LowerVirtualStubCall(GenTreeCall* call)
// Skip inserting the indirection node to load the address that is already
// computed in the VSD stub arg register as a hidden parameter. Instead during the
// codegen, just load the call target from there.
shouldOptimizeVirtualStubCall = !comp->opts.IsCFGEnabled();
shouldOptimizeVirtualStubCall = true;
#endif

if (!shouldOptimizeVirtualStubCall)
Expand Down
14 changes: 13 additions & 1 deletion src/coreclr/jit/lsrabuild.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -893,8 +893,20 @@ regMaskTP LinearScan::getKillSetForCall(GenTreeCall* call)
killMask &= ~RBM_FLT_CALLEE_TRASH;
}
#ifdef TARGET_ARM
if (call->IsVirtualStub())
// Indirection cell arg may go in callee saved register on ARM.
// Technically these may not be killed by the call, but we have special
// handling for arguments to keep their registers busy until they are
// killed expecting to see this kill at the call site.
WellKnownArg indirCellKind = call->GetIndirectionCellArgKind();
if (indirCellKind != WellKnownArg::None)
{
#ifdef DEBUG
// Assume the different indir cells use the same register to avoid
// having to find the arg in release builds, but assert it here.
CallArg* indirCellArg = call->gtArgs.FindWellKnownArg(indirCellKind);
assert((indirCellArg != nullptr) &&
(indirCellArg->AbiInfo.GetRegNum() == compiler->virtualStubParamInfo->GetReg()));
#endif
killMask |= compiler->virtualStubParamInfo->GetRegMask();
}
#else // !TARGET_ARM
Expand Down
14 changes: 3 additions & 11 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2110,18 +2110,11 @@ void CallArgs::AddFinalArgsAndDetermineABIInfo(Compiler* comp, GenTreeCall* call
#ifdef DEBUG
indirectCellAddress->AsIntCon()->gtTargetHandle = (size_t)call->gtCallMethHnd;
#endif
indirectCellAddress->SetRegNum(REG_R2R_INDIRECT_PARAM);
#ifdef TARGET_ARM
// Issue #xxxx : Don't attempt to CSE this constant on ARM32
//
// This constant has specific register requirements, and LSRA doesn't currently correctly
// handle them when the value is in a CSE'd local.
indirectCellAddress->SetDoNotCSE();
#endif // TARGET_ARM

// Push the stub address onto the list of arguments.
InsertAfterThisOrFirst(comp,
NewCallArg::Primitive(indirectCellAddress).WellKnown(WellKnownArg::R2RIndirectionCell));
NewCallArg indirCellAddrArg =
NewCallArg::Primitive(indirectCellAddress).WellKnown(WellKnownArg::R2RIndirectionCell);
InsertAfterThisOrFirst(comp, indirCellAddrArg);
}
#endif

Expand Down Expand Up @@ -7938,7 +7931,6 @@ GenTree* Compiler::fgGetStubAddrArg(GenTreeCall* call)
#endif
}
assert(stubAddrArg != nullptr);
stubAddrArg->SetRegNum(virtualStubParamInfo->GetReg());
return stubAddrArg;
}

Expand Down
15 changes: 5 additions & 10 deletions src/coreclr/jit/valuenum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9650,19 +9650,14 @@ void Compiler::fgValueNumberHelperCallFunc(GenTreeCall* call, VNFunc vnf, ValueN
vnpUniq.SetBoth(vnStore->VNForExpr(compCurBB, call->TypeGet()));
}

#if defined(FEATURE_READYTORUN) && (defined(TARGET_ARMARCH) || defined(TARGET_LOONGARCH64))
if (call->IsR2RRelativeIndir())
if (call->GetIndirectionCellArgKind() != WellKnownArg::None)
{
#ifdef DEBUG
GenTree* indirectCellAddress = args->GetArgByIndex(0)->GetNode();
assert(indirectCellAddress->IsCnsIntOrI() && indirectCellAddress->GetRegNum() == REG_R2R_INDIRECT_PARAM);
#endif // DEBUG

// For ARM indirectCellAddress is consumed by the call itself, so it should have added as an implicit argument
// in morph. So we do not need to use EntryPointAddrAsArg0, because arg0 is already an entry point addr.
// If we are VN'ing a call with indirection cell arg (e.g. because this
// is a helper in a R2R compilation) then morph should already have
// added this arg, so we do not need to use EntryPointAddrAsArg0
// because the indirection cell itself allows us to disambiguate.
useEntryPointAddrAsArg0 = false;
}
#endif // FEATURE_READYTORUN && (TARGET_ARMARCH || TARGET_LOONGARCH64)

CallArg* curArg = &*args->Args().begin();
if (nArgs == 0)
Expand Down