Skip to content

Commit dbf5c58

Browse files
authored
JIT: Refactor call argument representation (#67238)
This refactors the JIT's representation of call arguments. It replaces `GenTreeCall::Use` and `fgArgTabEntry` with a single class `CallArg`. `CallArg` always contains space for ABI information and contains two intrusive linked list nodes: one for all args (similar to current `gtCallArgs`) where all standard arguments are always in argument order, and one for late args (similar to current `gtCallLateArgs`) that may be reordered. The late args list may also not contain all arguments. `fgArgInfo` is also replaced by a new class `CallArgs` that is stored inline in `GenTreeCall`. It encapsulates all handling of arguments (insertion/removal). The change also begins treating the 'this' argument as a normal argument with `CallArgs` providing convenient access to it when necessary. The main benefit of this change is to avoid keeping track of the side table `fgArgInfo` and having to scan through this side table repeatedly when we need to query argument information. In addition it gives more convenient ways to access well known arguments like 'this', the ret buffer, VSD cell, etc. Finally, it also serves as a nice clean-up in the JIT.
1 parent 6543a04 commit dbf5c58

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3447
-4422
lines changed

docs/design/coreclr/jit/jit-call-morphing.md

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,21 @@ call site and is created by the Importer when it sees a call in the IL. It is a
4343
used for internal calls that the JIT needs such as helper calls. Every `GT_CALL` node
4444
should have a `GTF_CALL` flag set on it. Nodes that may be implemented using a function
4545
call also should have the `GTF_CALL` flag set on them. The arguments for a single call
46-
site are held by three fields in the `GenTreeCall`: `gtCallObjp`, `gtCallArgs`, and
47-
`gtCallLateArgs`. The first one, `gtCallObjp`, contains the instance pointer ("this"
48-
pointer) when you are calling a method that takes an instance pointer, otherwise it is
49-
null. The `gtCallArgs` contains all of the normal arguments in a null terminated `GT_LIST`
50-
format. When the `GenTreeCall` is first created the `gtCallLateArgs` is null and is
51-
set up later when we call `fgMorphArgs()` during the global Morph of all nodes. To
52-
accurately record and track all of the information about call site arguments we create
53-
a `fgArgInfo` that records information and decisions that we make about how each argument
54-
for this call site is handled. It has a dynamically sized array member `argTable` that
55-
contains details about each argument. This per-argument information is contained in the
56-
`fgArgTabEntry` struct.
57-
46+
site are encapsulated in the `CallArgs` class. Every call has an instance of this class
47+
in `GenTreeCall::gtArgs`. `CallArgs` contains two linked list of arguments: the "normal"
48+
linked list, which can be enumerated via `CallArgs::Args`, and the "late" linked list,
49+
enumerated via `CallArgs::LateArgs`.
50+
51+
The normal linked list is a linked list of `CallArg` structures, in normal argument
52+
order. When the `GenTreeCall` is first created the late args list is empty and is
53+
set up later when we call `fgMorphArgs()` during the global Morph of all nodes. The short
54+
explanation of why we need two lists is that we may need to force the correct evaluation
55+
order of arguments and also architecture-specific ways of passing some arguments. See
56+
below and the documentation of `fgMorphArgs` and `AddFinalArgsAndDetermineABIInfo` for
57+
more information about late args.
58+
59+
In addition to containing IR nodes, each `CallArg` entry also contains information about
60+
how it was evaluated and ABI information describing how to pass it.
5861

5962
`FEATURE_FIXED_OUT_ARGS`
6063
-----------------
@@ -77,9 +80,9 @@ calls for x86 but do not allow them for the other architectures.
7780
Rules for when Arguments must be evaluated into temp LclVars
7881
-----------------
7982

80-
During the first Morph phase known as global Morph we call `fgArgInfo::ArgsComplete()`
81-
after we have completed building the `argTable` for `fgArgInfo` struct. This method
82-
applies the following rules:
83+
During the first Morph phase known as global Morph we call `CallArgs::ArgsComplete()`
84+
after we have completed determining ABI information for each arg. This method applies
85+
the following rules:
8386

8487
1. When an argument is marked as containing an assignment using `GTF_ASG`, then we
8588
force all previous non-constant arguments to be evaluated into temps. This is very
@@ -93,7 +96,7 @@ we force that argument and any previous argument that is marked with any of the
9396
area is marked as needing a placeholder temp using `needPlace`.
9497
3. We force any arguments that use `localloc` to be evaluated into temps.
9598
4. We mark any address taken locals with the `GTF_GLOB_REF` flag. For two special
96-
cases we call `EvalToTmp()` and set up the temp in `fgMorphArgs`. `EvalToTmp`
99+
cases we call `SetNeedsTemp()` and set up the temp in `fgMorphArgs`. `SetNeedsTemp`
97100
records the tmpNum used and sets `isTmp` so that we handle it like the other temps.
98101
The special cases are for `GT_MKREFANY` and for a `TYP_STRUCT` argument passed by
99102
value when we can't optimize away the extra copy.
@@ -104,7 +107,7 @@ Rules use to determine the order of argument evaluation
104107

105108
After calling `ArgsComplete()` the `SortArgs()` method is called to determine the
106109
optimal way to evaluate the arguments. This sorting controls the order that we place
107-
the nodes in the `gtCallLateArgs` list.
110+
the nodes in the late argument list.
108111

109112
1. We iterate over the arguments and move any constant arguments to be evaluated
110113
last and remove them from further consideration by marking them as processed.
@@ -123,35 +126,35 @@ Evaluating Args into new LclVar temps and the creation of the LateArgs
123126
After calling `SortArgs()`, the `EvalArgsToTemps()` method is called to create
124127
the temp assignments and to populate the LateArgs list.
125128

126-
Arguments that are marked with `needTmp == true`.
129+
For arguments that are marked as needing a temp:
127130
-----------------
128131

129132
1. We create an assignment using `gtNewTempAssign`. This assignment replaces
130-
the original argument in the `gtCallArgs` list. After we create the assignment
131-
the argument is marked as `isTmp`. The new assignment is marked with the
133+
the original argument in the early argument list. After we create the assignment
134+
the argument is marked with `m_isTmp = true`. The new assignment is marked with the
132135
`GTF_LATE_ARG` flag.
133-
2. Arguments that are already marked with `isTmp` are treated similarly as
136+
2. Arguments that are already marked with `m_isTmp` are treated similarly as
134137
above except we don't create an assignment for them.
135-
3. A `TYP_STRUCT` argument passed by value will have `isTmp` set to true
138+
3. A `TYP_STRUCT` argument passed by value will have `m_isTmp` set to true
136139
and will use a `GT_COPYBLK` or a `GT_COPYOBJ` to perform the assignment of the temp.
137140
4. The assignment node or the CopyBlock node is referred to as `arg1 SETUP` in the JitDump.
138141

139142

140-
Argument that are marked with `needTmp == false`.
143+
For arguments that are marked as not needing a temp:
141144
-----------------
142145

143146
1. If this is an argument that is passed in a register, then the existing
144-
node is moved to the `gtCallLateArgs` list and a new `GT_ARGPLACE` (placeholder)
145-
node replaces it in the `gtArgList` list.
146-
2. Additionally, if `needPlace` is true (only for `FEATURE_FIXED_OUT_ARGS`)
147-
then the existing node is moved to the `gtCallLateArgs` list and a new
148-
`GT_ARGPLACE` (placeholder) node replaces it in the `gtArgList` list.
149-
3. Otherwise the argument is left in the `gtCallArgs` and it will be
150-
evaluated into the outgoing arg area or pushed on the stack.
147+
node is moved to the late argument list and a new `GT_ARGPLACE` (placeholder)
148+
node replaces it in the early argument list.
149+
2. Additionally, if `m_needPlace` is true (only for `FEATURE_FIXED_OUT_ARGS`)
150+
then the existing node is moved to the late argument list and a new
151+
`GT_ARGPLACE` (placeholder) node replaces it in the `early argument list.
152+
3. Otherwise the argument is left in the early argument and it will be
153+
evaluated directly into the outgoing arg area or pushed on the stack.
151154

152155
After the Call node is fully morphed the LateArgs list will contain the arguments
153-
passed in registers as well as additional ones for `needPlace` marked
156+
passed in registers as well as additional ones for `m_needPlace` marked
154157
arguments whenever we have a nested call for a stack based argument.
155-
When `needTmp` is true the LateArg will be a LclVar that was created
156-
to evaluate the arg (single-def/single-use). When `needTmp` is false
158+
When `m_needTmp` is true the LateArg will be a LclVar that was created
159+
to evaluate the arg (single-def/single-use). When `m_needTmp` is false
157160
the LateArg can be an arbitrary expression tree.

docs/design/coreclr/jit/struct-abi.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ when it comes to the handling of structs (aka value types).
5050

5151
High-Level Proposed Design
5252
--------------------------
53-
This is a preliminary design, and is likely to change as the implementation proceeds:
53+
Note that much of the below work has already been carried out and further refactoring has replaced the side `fgArgInfo` table with `CallArgs`.
54+
The plan here is intended to provide some historical context and may not completely reflect JIT sources.
5455

5556
First, the `fgArgInfo` is extended to contain all the information needed to determine
5657
how an argument is passed. Ideally, most of the `#ifdef`s relating to ABI differences

src/coreclr/inc/corinfo.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1115,7 +1115,7 @@ struct CORINFO_SIG_INFO
11151115
CorInfoCallConv getCallConv() { return CorInfoCallConv((callConv & CORINFO_CALLCONV_MASK)); }
11161116
bool hasThis() { return ((callConv & CORINFO_CALLCONV_HASTHIS) != 0); }
11171117
bool hasExplicitThis() { return ((callConv & CORINFO_CALLCONV_EXPLICITTHIS) != 0); }
1118-
unsigned totalILArgs() { return (numArgs + hasThis()); }
1118+
unsigned totalILArgs() { return (numArgs + (hasThis() ? 1 : 0)); }
11191119
bool isVarArg() { return ((getCallConv() == CORINFO_CALLCONV_VARARG) || (getCallConv() == CORINFO_CALLCONV_NATIVEVARARG)); }
11201120
bool hasTypeArg() { return ((callConv & CORINFO_CALLCONV_PARAMTYPE) != 0); }
11211121
};

src/coreclr/jit/assertionprop.cpp

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2548,9 +2548,8 @@ AssertionInfo Compiler::optAssertionGenJtrue(GenTree* tree)
25482548
(call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_ISINSTANCEOFCLASS)) ||
25492549
(call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_ISINSTANCEOFANY)))
25502550
{
2551-
fgArgInfo* const argInfo = call->fgArgInfo;
2552-
GenTree* objectNode = argInfo->GetArgNode(1);
2553-
GenTree* methodTableNode = argInfo->GetArgNode(0);
2551+
GenTree* objectNode = call->gtArgs.GetArgByIndex(1)->GetNode();
2552+
GenTree* methodTableNode = call->gtArgs.GetArgByIndex(0)->GetNode();
25542553

25552554
assert(objectNode->TypeGet() == TYP_REF);
25562555
assert(methodTableNode->TypeGet() == TYP_I_IMPL);
@@ -2697,7 +2696,7 @@ void Compiler::optAssertionGen(GenTree* tree)
26972696
if (call->NeedsNullCheck() || (call->IsVirtual() && !call->IsTailCall()))
26982697
{
26992698
// Retrieve the 'this' arg.
2700-
GenTree* thisArg = gtGetThisArg(call);
2699+
GenTree* thisArg = call->gtArgs.GetThisArg()->GetNode();
27012700
assert(thisArg != nullptr);
27022701
assertionInfo = optCreateAssertion(thisArg, nullptr, OAK_NOT_EQUAL);
27032702
}
@@ -4485,7 +4484,7 @@ GenTree* Compiler::optNonNullAssertionProp_Call(ASSERT_VALARG_TP assertions, Gen
44854484
{
44864485
return nullptr;
44874486
}
4488-
GenTree* op1 = gtGetThisArg(call);
4487+
GenTree* op1 = call->gtArgs.GetThisArg()->GetNode();
44894488
noway_assert(op1 != nullptr);
44904489
if (op1->gtOper != GT_LCL_VAR)
44914490
{
@@ -4544,13 +4543,13 @@ GenTree* Compiler::optAssertionProp_Call(ASSERT_VALARG_TP assertions, GenTreeCal
45444543
call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_CHKCASTANY) ||
45454544
call->gtCallMethHnd == eeFindHelper(CORINFO_HELP_CHKCASTCLASS_SPECIAL))
45464545
{
4547-
GenTree* arg1 = gtArgEntryByArgNum(call, 1)->GetNode();
4546+
GenTree* arg1 = call->gtArgs.GetArgByIndex(1)->GetNode();
45484547
if (arg1->gtOper != GT_LCL_VAR)
45494548
{
45504549
return nullptr;
45514550
}
45524551

4553-
GenTree* arg2 = gtArgEntryByArgNum(call, 0)->GetNode();
4552+
GenTree* arg2 = call->gtArgs.GetArgByIndex(0)->GetNode();
45544553

45554554
unsigned index = optAssertionIsSubtype(arg1, arg2, assertions);
45564555
if (index != NO_ASSERTION_INDEX)

src/coreclr/jit/codegen.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1365,7 +1365,7 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
13651365
#endif
13661366
bool genEmitOptimizedGCWriteBarrier(GCInfo::WriteBarrierForm writeBarrierForm, GenTree* addr, GenTree* data);
13671367
GenTree* getCallTarget(const GenTreeCall* call, CORINFO_METHOD_HANDLE* methHnd);
1368-
regNumber getCallIndirectionCellReg(const GenTreeCall* call);
1368+
regNumber getCallIndirectionCellReg(GenTreeCall* call);
13691369
void genCall(GenTreeCall* call);
13701370
void genCallInstruction(GenTreeCall* call X86_ARG(target_ssize_t stackArgBytes));
13711371
void genJmpMethod(GenTree* jmp);

src/coreclr/jit/codegenarmarch.cpp

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -763,9 +763,9 @@ void CodeGen::genPutArgStk(GenTreePutArgStk* treeNode)
763763
unsigned argOffsetOut = treeNode->getArgOffset();
764764

765765
#ifdef DEBUG
766-
fgArgTabEntry* curArgTabEntry = compiler->gtArgEntryByNode(treeNode->gtCall, treeNode);
767-
assert(curArgTabEntry != nullptr);
768-
DEBUG_ARG_SLOTS_ASSERT(argOffsetOut == (curArgTabEntry->slotNum * TARGET_POINTER_SIZE));
766+
CallArg* callArg = treeNode->gtCall->gtArgs.FindByNode(treeNode);
767+
assert(callArg != nullptr);
768+
DEBUG_ARG_SLOTS_ASSERT(argOffsetOut == (callArg->AbiInfo.SlotNum * TARGET_POINTER_SIZE));
769769
#endif // DEBUG
770770

771771
// Whether to setup stk arg in incoming or out-going arg area?
@@ -3125,23 +3125,21 @@ void CodeGen::genCodeForInitBlkHelper(GenTreeBlk* initBlkNode)
31253125
void CodeGen::genCall(GenTreeCall* call)
31263126
{
31273127
// Consume all the arg regs
3128-
for (GenTreeCall::Use& use : call->LateArgs())
3128+
for (CallArg& arg : call->gtArgs.LateArgs())
31293129
{
3130-
GenTree* argNode = use.GetNode();
3131-
3132-
fgArgTabEntry* curArgTabEntry = compiler->gtArgEntryByNode(call, argNode);
3133-
assert(curArgTabEntry);
3130+
CallArgABIInformation& abiInfo = arg.AbiInfo;
3131+
GenTree* argNode = arg.GetLateNode();
31343132

31353133
// GT_RELOAD/GT_COPY use the child node
31363134
argNode = argNode->gtSkipReloadOrCopy();
31373135

3138-
if (curArgTabEntry->GetRegNum() == REG_STK)
3136+
if (abiInfo.GetRegNum() == REG_STK)
31393137
continue;
31403138

31413139
// Deal with multi register passed struct args.
31423140
if (argNode->OperGet() == GT_FIELD_LIST)
31433141
{
3144-
regNumber argReg = curArgTabEntry->GetRegNum();
3142+
regNumber argReg = abiInfo.GetRegNum();
31453143
for (GenTreeFieldList::Use& use : argNode->AsFieldList()->Uses())
31463144
{
31473145
GenTree* putArgRegNode = use.GetNode();
@@ -3162,22 +3160,22 @@ void CodeGen::genCall(GenTreeCall* call)
31623160
#endif // TARGET_ARM
31633161
}
31643162
}
3165-
else if (curArgTabEntry->IsSplit())
3163+
else if (abiInfo.IsSplit())
31663164
{
31673165
assert(compFeatureArgSplit());
3168-
assert(curArgTabEntry->numRegs >= 1);
3166+
assert(abiInfo.NumRegs >= 1);
31693167
genConsumeArgSplitStruct(argNode->AsPutArgSplit());
3170-
for (unsigned idx = 0; idx < curArgTabEntry->numRegs; idx++)
3168+
for (unsigned idx = 0; idx < abiInfo.NumRegs; idx++)
31713169
{
3172-
regNumber argReg = (regNumber)((unsigned)curArgTabEntry->GetRegNum() + idx);
3170+
regNumber argReg = (regNumber)((unsigned)abiInfo.GetRegNum() + idx);
31733171
regNumber allocReg = argNode->AsPutArgSplit()->GetRegNumByIdx(idx);
31743172
inst_Mov_Extend(argNode->TypeGet(), /* srcInReg */ true, argReg, allocReg, /* canSkip */ true,
31753173
emitActualTypeSize(TYP_I_IMPL));
31763174
}
31773175
}
31783176
else
31793177
{
3180-
regNumber argReg = curArgTabEntry->GetRegNum();
3178+
regNumber argReg = abiInfo.GetRegNum();
31813179
genConsumeReg(argNode);
31823180
inst_Mov_Extend(argNode->TypeGet(), /* srcInReg */ true, argReg, argNode->GetRegNum(), /* canSkip */ true,
31833181
emitActualTypeSize(TYP_I_IMPL));
@@ -3409,12 +3407,11 @@ void CodeGen::genCallInstruction(GenTreeCall* call)
34093407
trashedByEpilog |= genRegMask(REG_GSCOOKIE_TMP_1);
34103408
}
34113409

3412-
for (unsigned i = 0; i < call->fgArgInfo->ArgCount(); i++)
3410+
for (CallArg& arg : call->gtArgs.Args())
34133411
{
3414-
fgArgTabEntry* entry = call->fgArgInfo->GetArgEntry(i);
3415-
for (unsigned j = 0; j < entry->numRegs; j++)
3412+
for (unsigned j = 0; j < arg.AbiInfo.NumRegs; j++)
34163413
{
3417-
regNumber reg = entry->GetRegNum(j);
3414+
regNumber reg = arg.AbiInfo.GetRegNum(j);
34183415
if ((trashedByEpilog & genRegMask(reg)) != 0)
34193416
{
34203417
JITDUMP("Tail call node:\n");

src/coreclr/jit/codegencommon.cpp

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6488,38 +6488,29 @@ GenTree* CodeGen::getCallTarget(const GenTreeCall* call, CORINFO_METHOD_HANDLE*
64886488
// Notes:
64896489
// We currently use indirection cells for VSD on all platforms and for R2R calls on ARM architectures.
64906490
//
6491-
regNumber CodeGen::getCallIndirectionCellReg(const GenTreeCall* call)
6491+
regNumber CodeGen::getCallIndirectionCellReg(GenTreeCall* call)
64926492
{
64936493
regNumber result = REG_NA;
64946494
switch (call->GetIndirectionCellArgKind())
64956495
{
6496-
case NonStandardArgKind::None:
6496+
case WellKnownArg::None:
64976497
break;
6498-
case NonStandardArgKind::R2RIndirectionCell:
6498+
case WellKnownArg::R2RIndirectionCell:
64996499
result = REG_R2R_INDIRECT_PARAM;
65006500
break;
6501-
case NonStandardArgKind::VirtualStubCell:
6501+
case WellKnownArg::VirtualStubCell:
65026502
result = compiler->virtualStubParamInfo->GetReg();
65036503
break;
65046504
default:
65056505
unreached();
65066506
}
65076507

65086508
#ifdef DEBUG
6509-
regNumber foundReg = REG_NA;
6510-
unsigned argCount = call->fgArgInfo->ArgCount();
6511-
fgArgTabEntry** argTable = call->fgArgInfo->ArgTable();
6512-
for (unsigned i = 0; i < argCount; i++)
6509+
if (call->GetIndirectionCellArgKind() != WellKnownArg::None)
65136510
{
6514-
NonStandardArgKind kind = argTable[i]->nonStandardArgKind;
6515-
if ((kind == NonStandardArgKind::R2RIndirectionCell) || (kind == NonStandardArgKind::VirtualStubCell))
6516-
{
6517-
foundReg = argTable[i]->GetRegNum();
6518-
break;
6519-
}
6511+
CallArg* indirCellArg = call->gtArgs.FindWellKnownArg(call->GetIndirectionCellArgKind());
6512+
assert((indirCellArg != nullptr) && (indirCellArg->AbiInfo.GetRegNum() == result));
65206513
}
6521-
6522-
assert(foundReg == result);
65236514
#endif
65246515

65256516
return result;

0 commit comments

Comments
 (0)