Skip to content

Commit ddf4f4b

Browse files
authored
JIT: invoke nullable box optimizations earlier (dotnet#32269)
Move the logic in `fgMorphRecognizeBoxNullable` into `gtFoldExprSpecial` so it can be invoked earlier. This may prevent the original struct from becoming address exposed and allow subsequent optimizations when the `hasValue` field has a known value. Fixes dotnet#31661.
1 parent 3be5238 commit ddf4f4b

File tree

3 files changed

+116
-107
lines changed

3 files changed

+116
-107
lines changed

src/coreclr/src/jit/compiler.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2773,6 +2773,7 @@ class Compiler
27732773
#endif // __clang__
27742774
gtFoldExprConst(GenTree* tree);
27752775
GenTree* gtFoldExprSpecial(GenTree* tree);
2776+
GenTree* gtFoldBoxNullable(GenTree* tree);
27762777
GenTree* gtFoldExprCompare(GenTree* tree);
27772778
GenTree* gtCreateHandleCompare(genTreeOps oper,
27782779
GenTree* op1,
@@ -5450,7 +5451,6 @@ class Compiler
54505451
GenTree* fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac = nullptr);
54515452
GenTree* fgMorphModToSubMulDiv(GenTreeOp* tree);
54525453
GenTree* fgMorphSmpOpOptional(GenTreeOp* tree);
5453-
GenTree* fgMorphRecognizeBoxNullable(GenTree* compare);
54545454

54555455
GenTree* fgMorphToEmulatedFP(GenTree* tree);
54565456
GenTree* fgMorphConst(GenTree* tree);

src/coreclr/src/jit/gentree.cpp

Lines changed: 105 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12581,13 +12581,16 @@ CORINFO_CLASS_HANDLE Compiler::gtGetHelperArgClassHandle(GenTree* tree,
1258112581
return result;
1258212582
}
1258312583

12584-
/*****************************************************************************
12585-
*
12586-
* Some binary operators can be folded even if they have only one
12587-
* operand constant - e.g. boolean operators, add with 0
12588-
* multiply with 1, etc
12589-
*/
12590-
12584+
//------------------------------------------------------------------------
12585+
// gtFoldExprSpecial -- optimize binary ops with one constant operand
12586+
//
12587+
// Arguments:
12588+
// tree - tree to optimize
12589+
//
12590+
// Return value:
12591+
// Tree (possibly modified at root or below), or a new tree
12592+
// Any new tree is fully morphed, if necessary.
12593+
//
1259112594
GenTree* Compiler::gtFoldExprSpecial(GenTree* tree)
1259212595
{
1259312596
GenTree* op1 = tree->AsOp()->gtOp1;
@@ -12690,7 +12693,7 @@ GenTree* Compiler::gtFoldExprSpecial(GenTree* tree)
1269012693
// Optimize boxed value classes; these are always false. This IL is
1269112694
// generated when a generic value is tested against null:
1269212695
// <T> ... foo(T x) { ... if ((object)x == null) ...
12693-
if (val == 0 && op->IsBoxedValue())
12696+
if ((val == 0) && op->IsBoxedValue())
1269412697
{
1269512698
JITDUMP("\nAttempting to optimize BOX(valueType) %s null [%06u]\n", GenTree::OpName(oper),
1269612699
dspTreeID(tree));
@@ -12744,6 +12747,10 @@ GenTree* Compiler::gtFoldExprSpecial(GenTree* tree)
1274412747
}
1274512748
}
1274612749
}
12750+
else
12751+
{
12752+
return gtFoldBoxNullable(tree);
12753+
}
1274712754

1274812755
break;
1274912756

@@ -12905,6 +12912,96 @@ GenTree* Compiler::gtFoldExprSpecial(GenTree* tree)
1290512912
return op;
1290612913
}
1290712914

12915+
//------------------------------------------------------------------------
12916+
// gtFoldBoxNullable -- optimize a boxed nullable feeding a compare to zero
12917+
//
12918+
// Arguments:
12919+
// tree - binop tree to potentially optimize, must be
12920+
// GT_GT, GT_EQ, or GT_NE
12921+
//
12922+
// Return value:
12923+
// Tree (possibly modified below the root).
12924+
//
12925+
GenTree* Compiler::gtFoldBoxNullable(GenTree* tree)
12926+
{
12927+
assert(tree->OperKind() & GTK_BINOP);
12928+
assert(tree->OperIs(GT_GT, GT_EQ, GT_NE));
12929+
12930+
genTreeOps const oper = tree->OperGet();
12931+
12932+
if ((oper == GT_GT) && !tree->IsUnsigned())
12933+
{
12934+
return tree;
12935+
}
12936+
12937+
GenTree* const op1 = tree->AsOp()->gtOp1;
12938+
GenTree* const op2 = tree->AsOp()->gtOp2;
12939+
GenTree* op;
12940+
GenTree* cons;
12941+
12942+
if (op1->IsCnsIntOrI())
12943+
{
12944+
op = op2;
12945+
cons = op1;
12946+
}
12947+
else if (op2->IsCnsIntOrI())
12948+
{
12949+
op = op1;
12950+
cons = op2;
12951+
}
12952+
else
12953+
{
12954+
return tree;
12955+
}
12956+
12957+
ssize_t const val = cons->AsIntConCommon()->IconValue();
12958+
12959+
if (val != 0)
12960+
{
12961+
return tree;
12962+
}
12963+
12964+
if (!op->IsCall())
12965+
{
12966+
return tree;
12967+
}
12968+
12969+
GenTreeCall* const call = op->AsCall();
12970+
12971+
if (!call->IsHelperCall(this, CORINFO_HELP_BOX_NULLABLE))
12972+
{
12973+
return tree;
12974+
}
12975+
12976+
JITDUMP("\nAttempting to optimize BOX_NULLABLE(&x) %s null [%06u]\n", GenTree::OpName(oper), dspTreeID(tree));
12977+
12978+
// Get the address of the struct being boxed
12979+
GenTree* const arg = call->gtCallArgs->GetNext()->GetNode();
12980+
12981+
if (arg->OperIs(GT_ADDR) && ((arg->gtFlags & GTF_LATE_ARG) == 0))
12982+
{
12983+
CORINFO_CLASS_HANDLE nullableHnd = gtGetStructHandle(arg->AsOp()->gtOp1);
12984+
CORINFO_FIELD_HANDLE fieldHnd = info.compCompHnd->getFieldInClass(nullableHnd, 0);
12985+
12986+
// Replace the box with an access of the nullable 'hasValue' field.
12987+
JITDUMP("\nSuccess: replacing BOX_NULLABLE(&x) [%06u] with x.hasValue\n", dspTreeID(op));
12988+
GenTree* newOp = gtNewFieldRef(TYP_BOOL, fieldHnd, arg, 0);
12989+
12990+
if (op == op1)
12991+
{
12992+
tree->AsOp()->gtOp1 = newOp;
12993+
}
12994+
else
12995+
{
12996+
tree->AsOp()->gtOp2 = newOp;
12997+
}
12998+
12999+
cons->gtType = TYP_INT;
13000+
}
13001+
13002+
return tree;
13003+
}
13004+
1290813005
//------------------------------------------------------------------------
1290913006
// gtTryRemoveBoxUpstreamEffects: given an unused value type box,
1291013007
// try and remove the upstream allocation and unnecessary parts of

src/coreclr/src/jit/morph.cpp

Lines changed: 10 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -10557,101 +10557,6 @@ GenTree* Compiler::fgMorphForRegisterFP(GenTree* tree)
1055710557
return tree;
1055810558
}
1055910559

10560-
//--------------------------------------------------------------------------------------------------------------
10561-
// fgMorphRecognizeBoxNullable:
10562-
// Recognize this pattern:
10563-
//
10564-
// stmtExpr void (IL 0x000... ???)
10565-
// return int
10566-
// CNS_INT ref null
10567-
// EQ/NE/GT int
10568-
// CALL help ref HELPER.CORINFO_HELP_BOX_NULLABLE
10569-
// CNS_INT(h) long 0x7fed96836c8 class
10570-
// ADDR byref
10571-
// FIELD struct value
10572-
// LCL_VAR ref V00 this
10573-
//
10574-
// which comes from this code:
10575-
//
10576-
// return this.value==null;
10577-
//
10578-
// and transform it into
10579-
//
10580-
// stmtExpr void (IL 0x000... ???)
10581-
// return int
10582-
// CNS_INT ref null
10583-
// EQ/NE/GT int
10584-
// IND bool
10585-
// ADDR byref
10586-
// FIELD struct value
10587-
// LCL_VAR ref V00 this
10588-
//
10589-
// Arguments:
10590-
// compare - Compare tree to optimize.
10591-
//
10592-
// return value:
10593-
// A tree that has a call to CORINFO_HELP_BOX_NULLABLE optimized away if the pattern is found;
10594-
// the original tree otherwise.
10595-
//
10596-
10597-
GenTree* Compiler::fgMorphRecognizeBoxNullable(GenTree* compare)
10598-
{
10599-
GenTree* op1 = compare->AsOp()->gtOp1;
10600-
GenTree* op2 = compare->AsOp()->gtOp2;
10601-
GenTree* opCns;
10602-
GenTreeCall* opCall;
10603-
10604-
if (op1->IsCnsIntOrI() && op2->IsHelperCall())
10605-
{
10606-
opCns = op1;
10607-
opCall = op2->AsCall();
10608-
}
10609-
else if (op1->IsHelperCall() && op2->IsCnsIntOrI())
10610-
{
10611-
opCns = op2;
10612-
opCall = op1->AsCall();
10613-
}
10614-
else
10615-
{
10616-
return compare;
10617-
}
10618-
10619-
if (!opCns->IsIntegralConst(0))
10620-
{
10621-
return compare;
10622-
}
10623-
10624-
if (eeGetHelperNum(opCall->gtCallMethHnd) != CORINFO_HELP_BOX_NULLABLE)
10625-
{
10626-
return compare;
10627-
}
10628-
10629-
// Get the nullable struct argument
10630-
GenTree* arg = opCall->AsCall()->gtCallArgs->GetNext()->GetNode();
10631-
10632-
// Check for cases that are unsafe to optimize and return the unchanged tree
10633-
if (arg->IsArgPlaceHolderNode() || arg->IsNothingNode() || ((arg->gtFlags & GTF_LATE_ARG) != 0))
10634-
{
10635-
return compare;
10636-
}
10637-
10638-
// Replace the box with an access of the nullable 'hasValue' field which is at the zero offset
10639-
GenTree* newOp = gtNewOperNode(GT_IND, TYP_BOOL, arg);
10640-
10641-
if (opCall == op1)
10642-
{
10643-
compare->AsOp()->gtOp1 = newOp;
10644-
}
10645-
else
10646-
{
10647-
compare->AsOp()->gtOp2 = newOp;
10648-
}
10649-
10650-
opCns->gtType = TYP_INT;
10651-
10652-
return compare;
10653-
}
10654-
1065510560
#ifdef FEATURE_SIMD
1065610561

1065710562
//--------------------------------------------------------------------------------------------------------------
@@ -11409,17 +11314,24 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac)
1140911314
__fallthrough;
1141011315

1141111316
case GT_GT:
11317+
{
11318+
// Try and optimize nullable boxes feeding compares
11319+
GenTree* optimizedTree = gtFoldBoxNullable(tree);
1141211320

11413-
// Try to optimize away calls to CORINFO_HELP_BOX_NULLABLE for GT_EQ, GT_NE, and unsigned GT_GT.
11414-
if ((oper != GT_GT) || tree->IsUnsigned())
11321+
if (optimizedTree->OperGet() != tree->OperGet())
1141511322
{
11416-
fgMorphRecognizeBoxNullable(tree);
11323+
return optimizedTree;
11324+
}
11325+
else
11326+
{
11327+
tree = optimizedTree;
1141711328
}
1141811329

1141911330
op1 = tree->AsOp()->gtOp1;
1142011331
op2 = tree->gtGetOp2IfPresent();
1142111332

1142211333
break;
11334+
}
1142311335

1142411336
case GT_RUNTIMELOOKUP:
1142511337
return fgMorphTree(op1);

0 commit comments

Comments
 (0)