Skip to content

Commit 7029008

Browse files
authored
JIT: Remove CallArg::m_tmpNum (#104429)
Before this change the JIT has two places where it may introduce temps during call args morphing: 1. Directly during `fgMorphArgs` as part of making struct copies for some struct args, like implicit byref args and other struct args requiring to be of `LCL_VAR` shape 2. During `EvalArgsToTemps` as part of making sure evaluation order is maintained while we get the call into the right shape with registers To make this work we have `CallArg::m_tmpNum` that communicates the local from 1 to 2; the responsibility of creating the actual `LCL_VAR` node to put in the late argument list was left to `EvalArgsToTemps` while `fgMorphArgs` would just change the early setup node to a store into the temporary. This PR changes it so that 1 directly introduces the right late node when it creates its temporary. That is, it puts the call argument into the right shape immediately and avoids relying on `EvalArgsToTemps` to create the local node in the late list. The benefit of that is that we no longer need to communicate the temporary as part of every `CallArg`. However, the motivation here is something else: as part of #104370 we may have arguments that need multiple temporaries to copy, so having things working in this way introduces complications for that.
1 parent 812cb93 commit 7029008

File tree

3 files changed

+69
-119
lines changed

3 files changed

+69
-119
lines changed

src/coreclr/jit/gentree.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9640,12 +9640,10 @@ void CallArgs::InternalCopyFrom(Compiler* comp, CallArgs* other, CopyNodeFunc co
96409640
carg->m_earlyNode = arg.m_earlyNode != nullptr ? copyNode(arg.m_earlyNode) : nullptr;
96419641
carg->m_lateNode = arg.m_lateNode != nullptr ? copyNode(arg.m_lateNode) : nullptr;
96429642
carg->m_signatureClsHnd = arg.m_signatureClsHnd;
9643-
carg->m_tmpNum = arg.m_tmpNum;
96449643
carg->m_signatureType = arg.m_signatureType;
96459644
carg->m_wellKnownArg = arg.m_wellKnownArg;
96469645
carg->m_needTmp = arg.m_needTmp;
96479646
carg->m_needPlace = arg.m_needPlace;
9648-
carg->m_isTmp = arg.m_isTmp;
96499647
carg->m_processed = arg.m_processed;
96509648
carg->AbiInfo = arg.AbiInfo;
96519649
carg->NewAbiInfo = arg.NewAbiInfo;

src/coreclr/jit/gentree.h

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4708,8 +4708,6 @@ class CallArg
47084708

47094709
// The class handle for the signature type (when varTypeIsStruct(SignatureType)).
47104710
CORINFO_CLASS_HANDLE m_signatureClsHnd;
4711-
// The LclVar number if we had to force evaluation of this arg.
4712-
unsigned m_tmpNum;
47134711
// The type of the argument in the signature.
47144712
var_types m_signatureType : 5;
47154713
// The type of well-known argument this is.
@@ -4718,8 +4716,6 @@ class CallArg
47184716
bool m_needTmp : 1;
47194717
// True when we must replace this argument with a placeholder node.
47204718
bool m_needPlace : 1;
4721-
// True when we setup a temp LclVar for this argument.
4722-
bool m_isTmp : 1;
47234719
// True when we have decided the evaluation order for this argument in LateArgs
47244720
bool m_processed : 1;
47254721

@@ -4730,12 +4726,10 @@ class CallArg
47304726
, m_next(nullptr)
47314727
, m_lateNext(nullptr)
47324728
, m_signatureClsHnd(NO_CLASS_HANDLE)
4733-
, m_tmpNum(BAD_VAR_NUM)
47344729
, m_signatureType(TYP_UNDEF)
47354730
, m_wellKnownArg(WellKnownArg::None)
47364731
, m_needTmp(false)
47374732
, m_needPlace(false)
4738-
, m_isTmp(false)
47394733
, m_processed(false)
47404734
{
47414735
}
@@ -4772,7 +4766,6 @@ class CallArg
47724766
CORINFO_CLASS_HANDLE GetSignatureClassHandle() { return m_signatureClsHnd; }
47734767
var_types GetSignatureType() { return m_signatureType; }
47744768
WellKnownArg GetWellKnownArg() { return m_wellKnownArg; }
4775-
bool IsTemp() { return m_isTmp; }
47764769
// clang-format on
47774770

47784771
// Get the real argument node, i.e. not a setup or placeholder node.
@@ -4884,8 +4877,7 @@ class CallArgs
48844877
void SetNeedsTemp(CallArg* arg);
48854878
bool IsNonStandard(Compiler* comp, GenTreeCall* call, CallArg* arg);
48864879

4887-
GenTree* MakeTmpArgNode(Compiler* comp, CallArg* arg);
4888-
void SetTemp(CallArg* arg, unsigned tmpNum);
4880+
GenTree* MakeTmpArgNode(Compiler* comp, CallArg* arg, unsigned lclNum);
48894881

48904882
// clang-format off
48914883
bool HasThisPointer() const { return m_hasThisPointer; }

src/coreclr/jit/morph.cpp

Lines changed: 68 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -821,18 +821,10 @@ void CallArg::Dump(Compiler* comp)
821821
{
822822
printf(", isSplit");
823823
}
824-
if (m_needTmp)
825-
{
826-
printf(", tmpNum=V%02u", m_tmpNum);
827-
}
828824
if (m_needPlace)
829825
{
830826
printf(", needPlace");
831827
}
832-
if (m_isTmp)
833-
{
834-
printf(", isTmp");
835-
}
836828
if (m_processed)
837829
{
838830
printf(", processed");
@@ -849,15 +841,6 @@ void CallArg::Dump(Compiler* comp)
849841
}
850842
#endif
851843

852-
//------------------------------------------------------------------------
853-
// SetTemp: Set that the specified argument was evaluated into a temp.
854-
//
855-
void CallArgs::SetTemp(CallArg* arg, unsigned tmpNum)
856-
{
857-
arg->m_tmpNum = tmpNum;
858-
arg->m_isTmp = true;
859-
}
860-
861844
//------------------------------------------------------------------------
862845
// ArgsComplete: Make final decisions on which arguments to evaluate into temporaries.
863846
//
@@ -874,13 +857,7 @@ void CallArgs::ArgsComplete(Compiler* comp, GenTreeCall* call)
874857
for (CallArg& arg : Args())
875858
{
876859
GenTree* argx = arg.GetEarlyNode();
877-
878-
if (argx == nullptr)
879-
{
880-
// Should only happen if remorphing in which case we do not need to
881-
// make a decision about temps.
882-
continue;
883-
}
860+
assert(argx != nullptr);
884861

885862
bool canEvalToTemp = true;
886863
if (arg.AbiInfo.GetRegNum() == REG_STK)
@@ -909,19 +886,16 @@ void CallArgs::ArgsComplete(Compiler* comp, GenTreeCall* call)
909886
//
910887
if ((argx->gtFlags & GTF_ASG) != 0)
911888
{
912-
// If this is not the only argument, or it's a copyblk, or it
913-
// already evaluates the expression to a tmp then we need a temp in
914-
// the late arg list.
915-
// In the latter case this might not even be a value;
916-
// fgMakeOutgoingStructArgCopy will leave the copying nodes here
917-
// for FEATURE_FIXED_OUT_ARGS.
918-
if (canEvalToTemp && ((argCount > 1) || argx->OperIsCopyBlkOp() || (FEATURE_FIXED_OUT_ARGS && arg.m_isTmp)))
889+
// fgMakeOutgoingStructArgCopy can have introduced a temp already,
890+
// in which case it will have created a setup node in the early
891+
// node.
892+
if (!argx->IsValue())
919893
{
920-
SetNeedsTemp(&arg);
894+
assert(arg.m_needTmp);
921895
}
922-
else
896+
else if (canEvalToTemp && (argCount > 1))
923897
{
924-
assert(argx->IsValue());
898+
SetNeedsTemp(&arg);
925899
}
926900

927901
// For all previous arguments that may interfere with the store we
@@ -1318,8 +1292,6 @@ void CallArgs::SortArgs(Compiler* comp, GenTreeCall* call, CallArg** sortedArgs)
13181292
regCount++;
13191293
}
13201294

1321-
assert(arg->GetLateNode() == nullptr);
1322-
13231295
// Skip any already processed args
13241296
//
13251297
if (!arg->m_processed)
@@ -1556,9 +1528,8 @@ void CallArgs::SortArgs(Compiler* comp, GenTreeCall* call, CallArg** sortedArgs)
15561528
// Return Value:
15571529
// the newly created temp var tree.
15581530
//
1559-
GenTree* CallArgs::MakeTmpArgNode(Compiler* comp, CallArg* arg)
1531+
GenTree* CallArgs::MakeTmpArgNode(Compiler* comp, CallArg* arg, unsigned lclNum)
15601532
{
1561-
unsigned lclNum = arg->m_tmpNum;
15621533
LclVarDsc* varDsc = comp->lvaGetDesc(lclNum);
15631534
var_types argType = varDsc->TypeGet();
15641535
assert(genActualType(argType) == genActualType(arg->GetSignatureType()));
@@ -1633,7 +1604,16 @@ void CallArgs::EvalArgsToTemps(Compiler* comp, GenTreeCall* call)
16331604
for (size_t i = 0; i < numArgs; i++)
16341605
{
16351606
CallArg& arg = *(sortedArgs[i]);
1636-
assert(arg.GetLateNode() == nullptr);
1607+
1608+
if (arg.GetLateNode() != nullptr)
1609+
{
1610+
// We may already have created the temp as part of
1611+
// fgMakeOutgoingStructArgCopy. In that case there is no work to be
1612+
// done.
1613+
*lateTail = &arg;
1614+
lateTail = &arg.LateNextRef();
1615+
continue;
1616+
}
16371617

16381618
GenTree* argx = arg.GetEarlyNode();
16391619
assert(argx != nullptr);
@@ -1655,82 +1635,60 @@ void CallArgs::EvalArgsToTemps(Compiler* comp, GenTreeCall* call)
16551635

16561636
if (arg.m_needTmp)
16571637
{
1658-
if (arg.m_isTmp)
1659-
{
1660-
// Create a copy of the temp to go into the late argument list
1661-
defArg = MakeTmpArgNode(comp, &arg);
1662-
}
1663-
else
1664-
{
1665-
// Create a temp store for the argument
1666-
// Put the temp in the late arg list
1638+
// Create a temp store for the argument
1639+
// Put the temp in the late arg list
16671640

16681641
#ifdef DEBUG
1669-
if (comp->verbose)
1670-
{
1671-
printf("Argument with 'side effect'...\n");
1672-
comp->gtDispTree(argx);
1673-
}
1642+
if (comp->verbose)
1643+
{
1644+
printf("Argument with 'side effect'...\n");
1645+
comp->gtDispTree(argx);
1646+
}
16741647
#endif
16751648

16761649
#if defined(TARGET_AMD64) && !defined(UNIX_AMD64_ABI)
1677-
noway_assert(argx->gtType != TYP_STRUCT);
1650+
noway_assert(argx->gtType != TYP_STRUCT);
16781651
#endif
16791652

1680-
unsigned tmpVarNum = comp->lvaGrabTemp(true DEBUGARG("argument with side effect"));
1653+
unsigned tmpVarNum = comp->lvaGrabTemp(true DEBUGARG("argument with side effect"));
16811654

1682-
if (setupArg != nullptr)
1683-
{
1684-
// Now keep the mkrefany for the late argument list
1685-
defArg = argx;
1655+
setupArg = comp->gtNewTempStore(tmpVarNum, argx);
16861656

1687-
// Clear the side-effect flags because now both op1 and op2 have no side-effects
1688-
defArg->gtFlags &= ~GTF_ALL_EFFECT;
1689-
}
1690-
else
1691-
{
1692-
setupArg = comp->gtNewTempStore(tmpVarNum, argx);
1657+
LclVarDsc* varDsc = comp->lvaGetDesc(tmpVarNum);
1658+
var_types lclVarType = genActualType(argx->gtType);
1659+
var_types scalarType = TYP_UNKNOWN;
16931660

1694-
LclVarDsc* varDsc = comp->lvaGetDesc(tmpVarNum);
1695-
var_types lclVarType = genActualType(argx->gtType);
1696-
var_types scalarType = TYP_UNKNOWN;
1697-
1698-
if (setupArg->OperIsCopyBlkOp())
1699-
{
1700-
setupArg = comp->fgMorphCopyBlock(setupArg);
1661+
if (setupArg->OperIsCopyBlkOp())
1662+
{
1663+
setupArg = comp->fgMorphCopyBlock(setupArg);
17011664
#if defined(TARGET_ARMARCH) || defined(UNIX_AMD64_ABI) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
1702-
if ((lclVarType == TYP_STRUCT) && (arg.AbiInfo.ArgType != TYP_STRUCT))
1703-
{
1704-
scalarType = arg.AbiInfo.ArgType;
1705-
}
1665+
if ((lclVarType == TYP_STRUCT) && (arg.AbiInfo.ArgType != TYP_STRUCT))
1666+
{
1667+
scalarType = arg.AbiInfo.ArgType;
1668+
}
17061669
#endif // TARGET_ARMARCH || defined (UNIX_AMD64_ABI) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)
1707-
}
1708-
1709-
// scalarType can be set to a wider type for ARM or unix amd64 architectures: (3 => 4) or (5,6,7 =>
1710-
// 8)
1711-
if ((scalarType != TYP_UNKNOWN) && (scalarType != lclVarType))
1712-
{
1713-
// Create a GT_LCL_FLD using the wider type to go to the late argument list
1714-
defArg = comp->gtNewLclFldNode(tmpVarNum, scalarType, 0);
1715-
}
1716-
else
1717-
{
1718-
// Create a copy of the temp to go to the late argument list
1719-
defArg = comp->gtNewLclvNode(tmpVarNum, lclVarType);
1720-
}
1670+
}
17211671

1722-
arg.m_isTmp = true;
1723-
arg.m_tmpNum = tmpVarNum;
1724-
}
1672+
// scalarType can be set to a wider type for ARM or unix amd64 architectures: (3 => 4) or (5,6,7 =>
1673+
// 8)
1674+
if ((scalarType != TYP_UNKNOWN) && (scalarType != lclVarType))
1675+
{
1676+
// Create a GT_LCL_FLD using the wider type to go to the late argument list
1677+
defArg = comp->gtNewLclFldNode(tmpVarNum, scalarType, 0);
1678+
}
1679+
else
1680+
{
1681+
// Create a copy of the temp to go to the late argument list
1682+
defArg = comp->gtNewLclvNode(tmpVarNum, lclVarType);
1683+
}
17251684

17261685
#ifdef DEBUG
1727-
if (comp->verbose)
1728-
{
1729-
printf("\n Evaluate to a temp:\n");
1730-
comp->gtDispTree(setupArg);
1731-
}
1732-
#endif
1686+
if (comp->verbose)
1687+
{
1688+
printf("\n Evaluate to a temp:\n");
1689+
comp->gtDispTree(setupArg);
17331690
}
1691+
#endif
17341692
}
17351693
else // curArgTabEntry->needTmp == false
17361694
{
@@ -3289,29 +3247,31 @@ void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg)
32893247
assert(!fgGlobalMorph);
32903248
}
32913249

3250+
call->gtArgs.SetNeedsTemp(arg);
3251+
32923252
// Copy the valuetype to the temp
32933253
GenTree* copyBlk = gtNewStoreLclVarNode(tmp, argx);
32943254
copyBlk = fgMorphCopyBlock(copyBlk);
32953255

3296-
call->gtArgs.SetTemp(arg, tmp);
32973256
#if FEATURE_FIXED_OUT_ARGS
32983257

3299-
// Do the copy early, and evaluate the temp later (see EvalArgsToTemps)
3300-
// When on Unix create LCL_FLD for structs passed in more than one registers. See fgMakeTmpArgNode
3301-
GenTree* argNode = copyBlk;
3258+
// For fixed out args we create the setup node here; EvalArgsToTemps knows
3259+
// to handle the case of "already have a setup node" properly.
3260+
arg->SetEarlyNode(copyBlk);
3261+
arg->SetLateNode(call->gtArgs.MakeTmpArgNode(this, arg, tmp));
33023262

33033263
#else // !FEATURE_FIXED_OUT_ARGS
33043264

33053265
// Structs are always on the stack, and thus never need temps
33063266
// so we have to put the copy and temp all into one expression.
3307-
GenTree* argNode = call->gtArgs.MakeTmpArgNode(this, arg);
3267+
GenTree* argNode = call->gtArgs.MakeTmpArgNode(this, arg, tmp);
33083268

33093269
// Change the expression to "(tmp=val),tmp"
33103270
argNode = gtNewOperNode(GT_COMMA, argNode->TypeGet(), copyBlk, argNode);
33113271

3312-
#endif // !FEATURE_FIXED_OUT_ARGS
3313-
33143272
arg->SetEarlyNode(argNode);
3273+
3274+
#endif // !FEATURE_FIXED_OUT_ARGS
33153275
}
33163276

33173277
/*****************************************************************************
@@ -6772,12 +6732,12 @@ Statement* Compiler::fgAssignRecursiveCallArgToCallerParam(GenTree* arg,
67726732
// TODO-CQ: enable calls with struct arguments passed in registers.
67736733
noway_assert(!varTypeIsStruct(arg->TypeGet()));
67746734

6775-
if (callArg->IsTemp() || arg->IsCnsIntOrI() || arg->IsCnsFltOrDbl())
6735+
if (arg->IsCnsIntOrI() || arg->IsCnsFltOrDbl())
67766736
{
67776737
// The argument is already assigned to a temp or is a const.
67786738
argInTemp = arg;
67796739
}
6780-
else if (arg->OperGet() == GT_LCL_VAR)
6740+
else if (arg->OperIs(GT_LCL_VAR))
67816741
{
67826742
unsigned lclNum = arg->AsLclVar()->GetLclNum();
67836743
LclVarDsc* varDsc = lvaGetDesc(lclNum);

0 commit comments

Comments
 (0)