Skip to content

Commit 6d91b3e

Browse files
authored
JIT: Remove GT_MKREFANY (dotnet#100204)
The operation of `mkrefany` can easily be represented with more generally handled nodes within the JIT today. This also allows promotion to remain enabled for methods using this construct, so CQ improvements are expected when optimizing.
1 parent 22fcf9a commit 6d91b3e

File tree

11 files changed

+32
-170
lines changed

11 files changed

+32
-170
lines changed

docs/design/coreclr/jit/first-class-structs.md

-7
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,6 @@ encountered by most phases of the JIT:
7474
if it's a promoted struct field, or to a `GT_LCL_FLD` or GT_IND` by `fgMorphField()`.
7575
* Proposed: A non-promoted struct typed field should be transformed into a `GT_OBJ`, so that consistently all struct
7676
nodes, even r-values, have `ClassLayout`.
77-
* `GT_MKREFANY`: This produces a "known" struct type, which is currently obtained by
78-
calling `impGetRefAnyClass()` which is a call over the JIT/EE interface. This node is always
79-
eliminated, and its source address used to create a copy. If it is on the rhs
80-
of an assignment, it will be eliminated during the importer. If it is a call argument it will
81-
be eliminated during morph.
82-
* The presence of any of these in a method disables struct promotion. See `case CEE_MKREFANY` in the
83-
`Importer`, where it is asserted that these are rare, and therefore not worth the trouble to handle.
8477

8578
### Struct “objects” as lvalues
8679

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,10 @@ we force that argument and any previous argument that is marked with any of the
9595
we haven't marked as needing a temp but still need to store in the outgoing args
9696
area is marked as needing a placeholder temp using `needPlace`.
9797
3. We force any arguments that use `localloc` to be evaluated into temps.
98-
4. We mark any address taken locals with the `GTF_GLOB_REF` flag. For two special
99-
cases we call `SetNeedsTemp()` and set up the temp in `fgMorphArgs`. `SetNeedsTemp`
100-
records the tmpNum used and sets `isTmp` so that we handle it like the other temps.
101-
The special cases are for `GT_MKREFANY` and for a `TYP_STRUCT` argument passed by
98+
4. We mark any address taken locals with the `GTF_GLOB_REF` flag. For a special
99+
case we call `SetNeedsTemp()` and set up the temp in `fgMorphArgs`.
100+
`SetNeedsTemp` records the tmpNum used and sets `isTmp` so that we handle it
101+
like the other temps. The special case is for a `TYP_STRUCT` argument passed by
102102
value when we can't optimize away the extra copy.
103103

104104

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ This method is responsible for the first part of what is currently `fgMorphArgs(
8585
- Note that the `isSplit` property would evaluate to false on targets where
8686
it is not supported, reducing the need for `ifdef`s (we can rely on the compiler
8787
to eliminate those dead paths).
88-
- Validate that each struct argument is either a `GT_LCL_VAR`, a `GT_OBJ`,
89-
or a `GT_MKREFANY`.
88+
- Validate that each struct argument is either a `GT_LCL_VAR` or a `GT_OBJ`
9089

9190
During the initial `fgMorph` phase, `fgMorphArgs()` does the following:
9291

src/coreclr/jit/fginline.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ class SubstitutePlaceholdersAndDevirtualizeWalker : public GenTreeVisitor<Substi
461461
//
462462
GenTree* StoreStructInlineeToVar(GenTree* inlinee, CORINFO_CLASS_HANDLE retClsHnd)
463463
{
464-
assert(!inlinee->OperIs(GT_MKREFANY, GT_RET_EXPR));
464+
assert(!inlinee->OperIs(GT_RET_EXPR));
465465

466466
unsigned lclNum = m_compiler->lvaGrabTemp(false DEBUGARG("RetBuf for struct inline return candidates."));
467467
LclVarDsc* varDsc = m_compiler->lvaGetDesc(lclNum);
@@ -1691,7 +1691,7 @@ void Compiler::fgInsertInlineeArgument(
16911691
*newStmt = nullptr;
16921692
bool append = true;
16931693

1694-
if (argNode->gtOper == GT_BLK || argNode->gtOper == GT_MKREFANY)
1694+
if (argNode->gtOper == GT_BLK)
16951695
{
16961696
// Don't put GT_BLK node under a GT_COMMA.
16971697
// Codegen can't deal with it.

src/coreclr/jit/forwardsub.cpp

+2-3
Original file line numberDiff line numberDiff line change
@@ -746,10 +746,9 @@ bool Compiler::fgForwardSubStatement(Statement* stmt)
746746
//
747747
// Don't substitute nodes args morphing doesn't handle into struct args.
748748
//
749-
if (fsv.IsCallArg() && fsv.GetNode()->TypeIs(TYP_STRUCT) &&
750-
!fwdSubNode->OperIs(GT_BLK, GT_LCL_VAR, GT_LCL_FLD, GT_MKREFANY))
749+
if (fsv.IsCallArg() && fsv.GetNode()->TypeIs(TYP_STRUCT) && !fwdSubNode->OperIs(GT_BLK, GT_LCL_VAR, GT_LCL_FLD))
751750
{
752-
JITDUMP(" use is a struct arg; fwd sub node is not BLK/LCL_VAR/LCL_FLD/MKREFANY\n");
751+
JITDUMP(" use is a struct arg; fwd sub node is not BLK/LCL_VAR/LCL_FLD\n");
753752
return false;
754753
}
755754

src/coreclr/jit/gentree.cpp

+2-8
Original file line numberDiff line numberDiff line change
@@ -731,10 +731,6 @@ ClassLayout* GenTree::GetLayout(Compiler* compiler) const
731731
return AsHWIntrinsic()->GetLayout(compiler);
732732
#endif // FEATURE_HW_INTRINSICS
733733

734-
case GT_MKREFANY:
735-
structHnd = compiler->impGetRefAnyClass();
736-
break;
737-
738734
case GT_CALL:
739735
structHnd = AsCall()->gtRetClsHnd;
740736
break;
@@ -5708,9 +5704,8 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree)
57085704
costSz = 2;
57095705
break;
57105706

5711-
case GT_MKREFANY:
57125707
case GT_BLK:
5713-
// We estimate the cost of a GT_BLK or GT_MKREFANY to be two loads (GT_INDs)
5708+
// We estimate the cost of a GT_BLK to be two loads (GT_INDs)
57145709
costEx = 2 * IND_COST_EX;
57155710
costSz = 2 * 2;
57165711
break;
@@ -6154,7 +6149,6 @@ unsigned Compiler::gtSetEvalOrder(GenTree* tree)
61546149

61556150
case GT_QMARK:
61566151
case GT_COLON:
6157-
case GT_MKREFANY:
61586152
break;
61596153

61606154
default:
@@ -15652,7 +15646,7 @@ GenTree* Compiler::gtFoldExprConst(GenTree* tree)
1565215646

1565315647
case TYP_INT:
1565415648

15655-
assert(tree->TypeIs(TYP_INT) || varTypeIsGC(tree) || tree->OperIs(GT_MKREFANY));
15649+
assert(tree->TypeIs(TYP_INT) || varTypeIsGC(tree));
1565615650
// No GC pointer types should be folded here...
1565715651
assert(!varTypeIsGC(op1->TypeGet()) && !varTypeIsGC(op2->TypeGet()));
1565815652

src/coreclr/jit/gentree.h

-1
Original file line numberDiff line numberDiff line change
@@ -9262,7 +9262,6 @@ inline GenTree* GenTree::gtGetOp1() const
92629262
case GT_QMARK:
92639263
case GT_COLON:
92649264
case GT_INDEX_ADDR:
9265-
case GT_MKREFANY:
92669265
return true;
92679266
default:
92689267
return false;

src/coreclr/jit/gtlist.h

-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ GTNODE(QMARK , GenTreeQmark ,0,1,GTK_BINOP|GTK_EXOP|DBK_NOTLIR)
150150
GTNODE(COLON , GenTreeColon ,0,1,GTK_BINOP|DBK_NOTLIR)
151151

152152
GTNODE(INDEX_ADDR , GenTreeIndexAddr ,0,0,GTK_BINOP|GTK_EXOP) // Address of SZ-array-element.
153-
GTNODE(MKREFANY , GenTreeOp ,0,0,GTK_BINOP|DBK_NOTLIR)
154153
GTNODE(LEA , GenTreeAddrMode ,0,0,GTK_BINOP|GTK_EXOP|DBK_NOTHIR)
155154

156155
#if !defined(TARGET_64BIT)

src/coreclr/jit/importer.cpp

+20-75
Original file line numberDiff line numberDiff line change
@@ -965,39 +965,6 @@ GenTree* Compiler::impStoreStruct(GenTree* store,
965965
return src;
966966
}
967967
}
968-
else if (src->OperIs(GT_MKREFANY))
969-
{
970-
// Since we are assigning the result of a GT_MKREFANY, "destAddr" must point to a refany.
971-
// TODO-CQ: we can do this without address-exposing the local on the LHS.
972-
GenTreeFlags indirFlags = GTF_EMPTY;
973-
GenTree* destAddr = impGetNodeAddr(store, CHECK_SPILL_ALL, &indirFlags);
974-
GenTree* destAddrClone;
975-
destAddr = impCloneExpr(destAddr, &destAddrClone, curLevel, pAfterStmt DEBUGARG("MKREFANY assignment"));
976-
977-
assert(OFFSETOF__CORINFO_TypedReference__dataPtr == 0);
978-
assert(destAddr->gtType == TYP_I_IMPL || destAddr->gtType == TYP_BYREF);
979-
980-
// Append the store of the pointer value.
981-
// TODO-Bug: the pointer value can be a byref. Use its actual type here instead of TYP_I_IMPL.
982-
GenTree* ptrFieldStore = gtNewStoreIndNode(TYP_I_IMPL, destAddr, src->AsOp()->gtOp1, indirFlags);
983-
if (pAfterStmt)
984-
{
985-
Statement* newStmt = gtNewStmt(ptrFieldStore, usedDI);
986-
fgInsertStmtAfter(block, *pAfterStmt, newStmt);
987-
*pAfterStmt = newStmt;
988-
}
989-
else
990-
{
991-
impAppendTree(ptrFieldStore, curLevel, usedDI);
992-
}
993-
994-
GenTree* typeFieldOffset = gtNewIconNode(OFFSETOF__CORINFO_TypedReference__type, TYP_I_IMPL);
995-
GenTree* typeFieldAddr = gtNewOperNode(GT_ADD, genActualType(destAddr), destAddrClone, typeFieldOffset);
996-
GenTree* typeFieldStore = gtNewStoreIndNode(TYP_I_IMPL, typeFieldAddr, src->AsOp()->gtOp2);
997-
998-
// Return the store of the type value, to be appended.
999-
return typeFieldStore;
1000-
}
1001968
else if (src->OperIs(GT_COMMA))
1002969
{
1003970
if (pAfterStmt)
@@ -9601,31 +9568,14 @@ void Compiler::impImportBlockCode(BasicBlock* block)
96019568
{
96029569
op1 = impPopStack().val;
96039570

9604-
if (op1->OperIs(GT_MKREFANY))
9605-
{
9606-
// The pointer may have side-effects
9607-
if (op1->AsOp()->gtOp1->gtFlags & GTF_SIDE_EFFECT)
9608-
{
9609-
impAppendTree(op1->AsOp()->gtOp1, CHECK_SPILL_ALL, impCurStmtDI);
9610-
#ifdef DEBUG
9611-
impNoteLastILoffs();
9612-
#endif
9613-
}
9571+
// Get the address of the refany
9572+
GenTreeFlags indirFlags = GTF_EMPTY;
9573+
op1 = impGetNodeAddr(op1, CHECK_SPILL_ALL, &indirFlags);
96149574

9615-
// We already have the class handle
9616-
op1 = op1->AsOp()->gtOp2;
9617-
}
9618-
else
9619-
{
9620-
// Get the address of the refany
9621-
GenTreeFlags indirFlags = GTF_EMPTY;
9622-
op1 = impGetNodeAddr(op1, CHECK_SPILL_ALL, &indirFlags);
9623-
9624-
// Fetch the type from the correct slot
9625-
op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1,
9626-
gtNewIconNode(OFFSETOF__CORINFO_TypedReference__type, TYP_I_IMPL));
9627-
op1 = gtNewIndir(TYP_BYREF, op1, indirFlags);
9628-
}
9575+
// Fetch the type from the correct slot
9576+
op1 = gtNewOperNode(GT_ADD, TYP_BYREF, op1,
9577+
gtNewIconNode(OFFSETOF__CORINFO_TypedReference__type, TYP_I_IMPL));
9578+
op1 = gtNewIndir(TYP_BYREF, op1, indirFlags);
96299579

96309580
// Convert native TypeHandle to RuntimeTypeHandle.
96319581
op1 = gtNewHelperCallNode(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPEHANDLE_MAYBENULL, TYP_STRUCT, op1);
@@ -10309,16 +10259,9 @@ void Compiler::impImportBlockCode(BasicBlock* block)
1030910259
}
1031010260

1031110261
case CEE_MKREFANY:
10312-
10262+
{
1031310263
assert(!compIsForInlining());
1031410264

10315-
// Being lazy here. Refanys are tricky in terms of gc tracking.
10316-
// Since it is uncommon, just don't perform struct promotion in any method that contains mkrefany.
10317-
10318-
JITDUMP("disabling struct promotion because of mkrefany\n");
10319-
fgNoStructPromotion = true;
10320-
10321-
oper = GT_MKREFANY;
1032210265
assertImp(sz == sizeof(unsigned));
1032310266

1032410267
_impResolveToken(CORINFO_TOKENKIND_Class);
@@ -10339,13 +10282,21 @@ void Compiler::impImportBlockCode(BasicBlock* block)
1033910282

1034010283
// @SPECVIOLATION: TYP_INT should not be allowed here by a strict reading of the spec.
1034110284
// But JIT32 allowed it, so we continue to allow it.
10342-
assertImp(op1->TypeGet() == TYP_BYREF || op1->TypeGet() == TYP_I_IMPL || op1->TypeGet() == TYP_INT);
10285+
assertImp(op1->TypeIs(TYP_BYREF, TYP_I_IMPL, TYP_INT));
1034310286

10344-
// MKREFANY returns a struct. op2 is the class token.
10345-
op1 = gtNewOperNode(oper, TYP_STRUCT, op1, op2);
10287+
unsigned refAnyLcl = lvaGrabTemp(false DEBUGARG("mkrefany temp"));
10288+
lvaSetStruct(refAnyLcl, impGetRefAnyClass(), false);
1034610289

10347-
impPushOnStack(op1, verMakeTypeInfo(impGetRefAnyClass()));
10290+
GenTree* storeData =
10291+
gtNewStoreLclFldNode(refAnyLcl, op1->TypeGet(), OFFSETOF__CORINFO_TypedReference__dataPtr, op1);
10292+
GenTree* storeType =
10293+
gtNewStoreLclFldNode(refAnyLcl, op2->TypeGet(), OFFSETOF__CORINFO_TypedReference__type, op2);
10294+
impAppendTree(storeData, CHECK_SPILL_ALL, impCurStmtDI);
10295+
impAppendTree(storeType, CHECK_SPILL_ALL, impCurStmtDI);
10296+
10297+
impPushOnStack(gtNewLclVarNode(refAnyLcl, TYP_STRUCT), verMakeTypeInfo(impGetRefAnyClass()));
1034810298
break;
10299+
}
1034910300

1035010301
case CEE_LDOBJ:
1035110302
{
@@ -12639,12 +12590,6 @@ void Compiler::impInlineRecordArgInfo(InlineInfo* pInlineInfo,
1263912590

1264012591
assert(!curArgVal->OperIs(GT_RET_EXPR));
1264112592

12642-
if (curArgVal->gtOper == GT_MKREFANY)
12643-
{
12644-
inlineResult->NoteFatal(InlineObservation::CALLSITE_ARG_IS_MKREFANY);
12645-
return;
12646-
}
12647-
1264812593
GenTree* lclVarTree;
1264912594
const bool isAddressInLocal = impIsAddressInLocal(curArgVal, &lclVarTree);
1265012595
if (isAddressInLocal)

src/coreclr/jit/inline.def

-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@ INLINE_OBSERVATION(HAS_NEWOBJ, bool, "has newobj",
122122
// ------ Call Site Correctness -------
123123

124124
INLINE_OBSERVATION(ARG_HAS_NULL_THIS, bool, "this pointer argument is null", FATAL, CALLSITE)
125-
INLINE_OBSERVATION(ARG_IS_MKREFANY, bool, "argument is mkrefany", FATAL, CALLSITE)
126125
INLINE_OBSERVATION(ARG_NO_BASH_TO_INT, bool, "argument can't bash to int", FATAL, CALLSITE)
127126
INLINE_OBSERVATION(ARG_NO_BASH_TO_REF, bool, "argument can't bash to ref", FATAL, CALLSITE)
128127
INLINE_OBSERVATION(ARG_TYPES_INCOMPATIBLE, bool, "argument types incompatible", FATAL, CALLSITE)

src/coreclr/jit/morph.cpp

+1-66
Original file line numberDiff line numberDiff line change
@@ -1696,36 +1696,6 @@ void CallArgs::EvalArgsToTemps(Compiler* comp, GenTreeCall* call)
16961696
#endif
16971697

16981698
unsigned tmpVarNum = comp->lvaGrabTemp(true DEBUGARG("argument with side effect"));
1699-
if (argx->gtOper == GT_MKREFANY)
1700-
{
1701-
// For GT_MKREFANY, typically the actual struct copying does
1702-
// not have any side-effects and can be delayed. So instead
1703-
// of using a temp for the whole struct, we can just use a temp
1704-
// for operand that has a side-effect.
1705-
GenTree* operand;
1706-
if ((argx->AsOp()->gtOp2->gtFlags & GTF_ALL_EFFECT) == 0)
1707-
{
1708-
operand = argx->AsOp()->gtOp1;
1709-
1710-
// In the early argument evaluation, place an assignment to the temp
1711-
// from the source operand of the mkrefany
1712-
setupArg = comp->gtNewTempStore(tmpVarNum, operand);
1713-
1714-
// Replace the operand for the mkrefany with the new temp.
1715-
argx->AsOp()->gtOp1 = comp->gtNewLclvNode(tmpVarNum, operand->TypeGet());
1716-
}
1717-
else if ((argx->AsOp()->gtOp1->gtFlags & GTF_ALL_EFFECT) == 0)
1718-
{
1719-
operand = argx->AsOp()->gtOp2;
1720-
1721-
// In the early argument evaluation, place an assignment to the temp
1722-
// from the source operand of the mkrefany
1723-
setupArg = comp->gtNewTempStore(tmpVarNum, operand);
1724-
1725-
// Replace the operand for the mkrefany with the new temp.
1726-
argx->AsOp()->gtOp2 = comp->gtNewLclvNode(tmpVarNum, operand->TypeGet());
1727-
}
1728-
}
17291699

17301700
if (setupArg != nullptr)
17311701
{
@@ -3201,7 +3171,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
32013171
GenTree* argObj = argx->gtEffectiveVal();
32023172
bool makeOutArgCopy = false;
32033173

3204-
if (isStructArg && !reMorphing && !argObj->OperIs(GT_MKREFANY))
3174+
if (isStructArg && !reMorphing)
32053175
{
32063176
unsigned originalSize;
32073177
if (argObj->TypeGet() == TYP_STRUCT)
@@ -3399,40 +3369,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call)
33993369
}
34003370
}
34013371

3402-
if (argx->gtOper == GT_MKREFANY)
3403-
{
3404-
// 'Lower' the MKREFANY tree and insert it.
3405-
noway_assert(!reMorphing);
3406-
3407-
#ifdef TARGET_X86
3408-
// Build the mkrefany as a GT_FIELD_LIST
3409-
GenTreeFieldList* fieldList = new (this, GT_FIELD_LIST) GenTreeFieldList();
3410-
fieldList->AddField(this, argx->AsOp()->gtGetOp1(), OFFSETOF__CORINFO_TypedReference__dataPtr, TYP_BYREF);
3411-
fieldList->AddField(this, argx->AsOp()->gtGetOp2(), OFFSETOF__CORINFO_TypedReference__type, TYP_I_IMPL);
3412-
arg.SetEarlyNode(fieldList);
3413-
#else // !TARGET_X86
3414-
3415-
// Get a new temp
3416-
// Here we don't need unsafe value cls check since the addr of temp is used only in mkrefany
3417-
unsigned tmp = lvaGrabTemp(true DEBUGARG("by-value mkrefany struct argument"));
3418-
lvaSetStruct(tmp, impGetRefAnyClass(), false);
3419-
lvaSetVarAddrExposed(tmp DEBUGARG(AddressExposedReason::TOO_CONSERVATIVE));
3420-
3421-
// Build the mkrefany as a comma node: (tmp.ptr=argx),(tmp.type=handle)
3422-
GenTree* storePtrSlot =
3423-
gtNewStoreLclFldNode(tmp, TYP_BYREF, OFFSETOF__CORINFO_TypedReference__dataPtr, argx->AsOp()->gtOp1);
3424-
GenTree* storeTypeSlot =
3425-
gtNewStoreLclFldNode(tmp, TYP_I_IMPL, OFFSETOF__CORINFO_TypedReference__type, argx->AsOp()->gtOp2);
3426-
GenTree* store = gtNewOperNode(GT_COMMA, TYP_VOID, storePtrSlot, storeTypeSlot);
3427-
3428-
// Change the expression to "(tmp=val)"
3429-
arg.SetEarlyNode(store);
3430-
call->gtArgs.SetTemp(&arg, tmp);
3431-
flagsSummary |= GTF_ASG;
3432-
hasMultiregStructArgs |= ((arg.AbiInfo.ArgType == TYP_STRUCT) && !arg.AbiInfo.PassedByRef);
3433-
#endif // !TARGET_X86
3434-
}
3435-
34363372
#if FEATURE_MULTIREG_ARGS
34373373
if (!isStructArg)
34383374
{
@@ -3949,7 +3885,6 @@ GenTreeFieldList* Compiler::fgMorphLclArgToFieldlist(GenTreeLclVarCommon* lcl)
39493885
void Compiler::fgMakeOutgoingStructArgCopy(GenTreeCall* call, CallArg* arg)
39503886
{
39513887
GenTree* argx = arg->GetEarlyNode();
3952-
noway_assert(!argx->OperIs(GT_MKREFANY));
39533888

39543889
#if FEATURE_IMPLICIT_BYREFS
39553890
// If we're optimizing, see if we can avoid making a copy.

0 commit comments

Comments
 (0)