Skip to content

Commit 457f4fa

Browse files
committed
better TP
1 parent 349228c commit 457f4fa

3 files changed

Lines changed: 106 additions & 74 deletions

File tree

src/coreclr/jit/compiler.h

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6801,18 +6801,7 @@ class Compiler
68016801
GenTree* fgMorphIntoHelperCall(
68026802
GenTree* tree, int helper, bool morphArgs, GenTree* arg1 = nullptr, GenTree* arg2 = nullptr);
68036803

6804-
// A "MorphAddrContext" carries information from the surrounding context. If we are evaluating a byref address,
6805-
// it is useful to know whether the address will be immediately dereferenced, or whether the address value will
6806-
// be used, perhaps by passing it as an argument to a called method. This affects how null checking is done:
6807-
// for sufficiently small offsets, we can rely on OS page protection to implicitly null-check addresses that we
6808-
// know will be dereferenced. To know that reliance on implicit null checking is sound, we must further know that
6809-
// all offsets between the top-level indirection and the bottom are constant, and that their sum is sufficiently
6810-
// small; hence the other fields of MorphAddrContext.
6811-
struct MorphAddrContext
6812-
{
6813-
GenTreeIndir* m_user = nullptr; // Indirection using this address.
6814-
size_t m_totalOffset = 0; // Sum of offsets between the top-level indirection and here (current context).
6815-
};
6804+
void fgMarkAddrModeForFieldAddr(GenTreeIndir* indir);
68166805

68176806
#ifdef FEATURE_SIMD
68186807
GenTree* getSIMDStructFromField(GenTree* tree,
@@ -6847,8 +6836,8 @@ class Compiler
68476836
void fgAssignSetVarDef(GenTree* tree);
68486837

68496838
private:
6850-
GenTree* fgMorphFieldAddr(GenTree* tree, MorphAddrContext* mac);
6851-
GenTree* fgMorphExpandInstanceField(GenTree* tree, MorphAddrContext* mac);
6839+
GenTree* fgMorphFieldAddr(GenTree* tree);
6840+
GenTree* fgMorphExpandInstanceField(GenTree* tree);
68526841
GenTree* fgMorphExpandTlsFieldAddr(GenTree* tree);
68536842
bool fgCanFastTailCall(GenTreeCall* call, const char** failReason);
68546843
#if FEATURE_FASTTAILCALL
@@ -6903,7 +6892,7 @@ class Compiler
69036892
GenTree* fgMorphInitBlock(GenTree* tree);
69046893
GenTree* fgMorphCopyBlock(GenTree* tree);
69056894
private:
6906-
GenTree* fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optAssertionPropDone = nullptr);
6895+
GenTree* fgMorphSmpOp(GenTree* tree, bool* optAssertionPropDone = nullptr);
69076896
bool fgTryReplaceStructLocalWithFields(GenTree** use);
69086897
GenTree* fgMorphFinalizeIndir(GenTreeIndir* indir);
69096898
GenTree* fgOptimizeCast(GenTreeCast* cast);
@@ -6940,7 +6929,7 @@ class Compiler
69406929
GenTree* fgMorphReduceAddOps(GenTree* tree);
69416930

69426931
public:
6943-
GenTree* fgMorphTree(GenTree* tree, MorphAddrContext* mac = nullptr);
6932+
GenTree* fgMorphTree(GenTree* tree);
69446933

69456934
private:
69466935
void fgAssertionGen(GenTree* tree);

src/coreclr/jit/gentree.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,7 @@ enum GenTreeFlags : unsigned
469469

470470
GTF_FLD_TLS = 0x80000000, // GT_FIELD_ADDR -- field address is a Windows x86 TLS reference
471471
GTF_FLD_DEREFERENCED = 0x40000000, // GT_FIELD_ADDR -- used to preserve previous behavior
472+
GTF_FLD_TGT_NONFAULTING = 0x20000000, // GT_FIELD_ADDR -- field address cannot fault on dereference.
472473

473474
GTF_INX_RNGCHK = 0x80000000, // GT_INDEX_ADDR -- this array address should be range-checked
474475
GTF_INX_ADDR_NONNULL = 0x40000000, // GT_INDEX_ADDR -- this array address is not null

src/coreclr/jit/morph.cpp

Lines changed: 100 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3527,19 +3527,95 @@ unsigned Compiler::fgGetFieldMorphingTemp(GenTreeFieldAddr* fieldNode)
35273527
return lclNum;
35283528
}
35293529

3530+
//------------------------------------------------------------------------
3531+
// fgMarkAddrModeForFieldAddr: Walk an indirection's unmorphed address tree to
3532+
// identify FIELD_ADDR nodes whose explicit null check can be elided because
3533+
// this indirection will fault on null instead.
3534+
//
3535+
// For each instance FIELD_ADDR whose base object could be null:
3536+
// * If the cumulative offset (including this field's offset) is small enough
3537+
// that the OS page guard catches the access, mark the FIELD_ADDR with
3538+
// GTF_FLD_TGT_NONFAULTING so morphing can elide the explicit null check,
3539+
// and clear GTF_IND_NONFAULTING on the consuming indirection so it faults
3540+
// on null instead.
3541+
// * Otherwise, the FIELD_ADDR will introduce an explicit NULLCHECK and stop
3542+
// propagation: mark the indirection as having an ordering side effect (so
3543+
// downstream optimizations treat the inserted NULLCHECK conservatively),
3544+
// and stop walking past this FIELD_ADDR.
3545+
//
3546+
// Arguments:
3547+
// indir - The indirection whose address operand will be morphed.
3548+
//
3549+
void Compiler::fgMarkAddrModeForFieldAddr(GenTreeIndir* indir)
3550+
{
3551+
GenTree* addr = indir->Addr();
3552+
ClrSafeInt<target_ssize_t> total((target_ssize_t)0);
3553+
3554+
while (true)
3555+
{
3556+
while (addr->OperIs(GT_ADD) && !addr->gtOverflow())
3557+
{
3558+
GenTree* offset = addr->gtGetOp2();
3559+
if (!offset->IsCnsIntOrI() || !offset->TypeIs(TYP_I_IMPL) || offset->AsIntCon()->IsIconHandle())
3560+
{
3561+
break;
3562+
}
3563+
3564+
total += ClrSafeInt<target_ssize_t>((target_ssize_t)offset->AsIntCon()->IconValue());
3565+
if (total.IsOverflow())
3566+
{
3567+
return;
3568+
}
3569+
3570+
addr = addr->gtGetOp1();
3571+
}
3572+
3573+
if (!addr->OperIs(GT_FIELD_ADDR) || !addr->AsFieldAddr()->IsInstance())
3574+
{
3575+
return;
3576+
}
3577+
3578+
GenTreeFieldAddr* fld = addr->AsFieldAddr();
3579+
total += ClrSafeInt<target_ssize_t>(fld->gtFldOffset);
3580+
if (total.IsOverflow())
3581+
{
3582+
return;
3583+
}
3584+
3585+
GenTree* objRef = fld->GetFldObj();
3586+
if (fgAddrCouldBeNull(objRef))
3587+
{
3588+
target_ssize_t accessOffset = total.Value();
3589+
if ((accessOffset < 0) || fgIsBigOffset((size_t)accessOffset))
3590+
{
3591+
// The FIELD_ADDR will introduce an explicit NULLCHECK, which acts as a
3592+
// barrier in the consuming indirection's address chain.
3593+
indir->SetHasOrderingSideEffect();
3594+
return;
3595+
}
3596+
3597+
fld->gtFlags |= GTF_FLD_TGT_NONFAULTING;
3598+
// We can elide the null check only by letting it happen as part of the
3599+
// consuming indirection, so it is no longer non-faulting.
3600+
indir->gtFlags &= ~GTF_IND_NONFAULTING;
3601+
}
3602+
3603+
addr = objRef;
3604+
}
3605+
}
3606+
35303607
//------------------------------------------------------------------------
35313608
// fgMorphFieldAddr: Fully morph a FIELD_ADDR tree.
35323609
//
35333610
// Expands the field node into explicit additions.
35343611
//
35353612
// Arguments:
35363613
// tree - The FIELD_ADDR tree
3537-
// mac - The morphing context, used to elide adding null checks
35383614
//
35393615
// Return Value:
35403616
// The fully morphed "tree".
35413617
//
3542-
GenTree* Compiler::fgMorphFieldAddr(GenTree* tree, MorphAddrContext* mac)
3618+
GenTree* Compiler::fgMorphFieldAddr(GenTree* tree)
35433619
{
35443620
assert(tree->OperIs(GT_FIELD_ADDR));
35453621

@@ -3549,7 +3625,7 @@ GenTree* Compiler::fgMorphFieldAddr(GenTree* tree, MorphAddrContext* mac)
35493625

35503626
if (fieldNode->IsInstance())
35513627
{
3552-
tree = fgMorphExpandInstanceField(tree, mac);
3628+
tree = fgMorphExpandInstanceField(tree);
35533629
}
35543630
else if (fieldNode->IsTlsStatic())
35553631
{
@@ -3560,11 +3636,10 @@ GenTree* Compiler::fgMorphFieldAddr(GenTree* tree, MorphAddrContext* mac)
35603636
assert(!"Normal statics are expected to be handled in the importer");
35613637
}
35623638

3563-
// Pass down the current mac; if non null we are computing an address
35643639
GenTree* result;
35653640
if (tree->OperIsSimple())
35663641
{
3567-
result = fgMorphSmpOp(tree, mac);
3642+
result = fgMorphSmpOp(tree);
35683643
result->SetMorphed(this);
35693644

35703645
// Quirk: preserve previous behavior with this NO_CSE.
@@ -3575,7 +3650,7 @@ GenTree* Compiler::fgMorphFieldAddr(GenTree* tree, MorphAddrContext* mac)
35753650
}
35763651
else
35773652
{
3578-
result = fgMorphTree(tree, mac);
3653+
result = fgMorphTree(tree);
35793654
}
35803655

35813656
JITDUMP("\nFinal value of Compiler::fgMorphFieldAddr after morphing:\n");
@@ -3591,12 +3666,11 @@ GenTree* Compiler::fgMorphFieldAddr(GenTree* tree, MorphAddrContext* mac)
35913666
//
35923667
// Arguments:
35933668
// tree - The FIELD_ADDR tree
3594-
// mac - The morphing context, used to elide adding null checks
35953669
//
35963670
// Return Value:
35973671
// The expanded "tree" of an arbitrary shape.
35983672
//
3599-
GenTree* Compiler::fgMorphExpandInstanceField(GenTree* tree, MorphAddrContext* mac)
3673+
GenTree* Compiler::fgMorphExpandInstanceField(GenTree* tree)
36003674
{
36013675
assert(tree->OperIs(GT_FIELD_ADDR) && tree->AsFieldAddr()->IsInstance());
36023676

@@ -3673,29 +3747,18 @@ GenTree* Compiler::fgMorphExpandInstanceField(GenTree* tree, MorphAddrContext* m
36733747

36743748
if (fgAddrCouldBeNull(objRef))
36753749
{
3676-
// A non-null context here implies our [+ some offset] parent is an indirection, one that
3677-
// will implicitly null-check the produced address.
3678-
addExplicitNullCheck = (mac == nullptr) || fgIsBigOffset(mac->m_totalOffset + fieldOffset);
3750+
// The pre-walk performed by fgMarkAddrModeForFieldAddr sets GTF_FLD_TGT_NONFAULTING when
3751+
// the consuming indirection will implicitly null-check the produced address (the cumulative
3752+
// constant offset down to this FIELD_ADDR is small enough that the OS page guard catches it).
3753+
addExplicitNullCheck = (tree->gtFlags & GTF_FLD_TGT_NONFAULTING) == 0;
36793754

36803755
// The transformation here turns a value dependency (FIELD_ADDR being a
36813756
// known non-null operand) into a control-flow dependency (introducing
36823757
// explicit COMMA(NULLCHECK, ...)). This effectively "disconnects" the
3683-
// null check from the parent of the FIELD_ADDR node. For the cases
3684-
// where we made use of non-nullness we need to make the dependency
3685-
// explicit now.
3686-
if (addExplicitNullCheck)
3687-
{
3688-
if (mac != nullptr)
3689-
{
3690-
mac->m_user->SetHasOrderingSideEffect();
3691-
}
3692-
}
3693-
else
3694-
{
3695-
// We can elide the null check only by letting it happen as part of
3696-
// the consuming indirection, so it is no longer non-faulting.
3697-
mac->m_user->gtFlags &= ~GTF_IND_NONFAULTING;
3698-
}
3758+
// null check from the parent of the FIELD_ADDR node.
3759+
//
3760+
// For the elided case, the consuming indirection has already been marked
3761+
// (~GTF_IND_NONFAULTING) by fgMarkAddrModeForFieldAddr; nothing else to do here.
36993762
}
37003763

37013764
if (addExplicitNullCheck)
@@ -6921,14 +6984,13 @@ GenTreeOp* Compiler::fgMorphCommutative(GenTreeOp* tree)
69216984
//
69226985
// Arguments:
69236986
// tree - tree to morph
6924-
// mac - address context for morphing
69256987
// optAssertionPropDone - [out, optional] set true if local assertions
69266988
// were killed/genned while morphing this tree
69276989
//
69286990
// Returns:
69296991
// Tree, possibly updated
69306992
//
6931-
GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optAssertionPropDone)
6993+
GenTree* Compiler::fgMorphSmpOp(GenTree* tree, bool* optAssertionPropDone)
69326994
{
69336995
assert(tree->OperKind() & GTK_SMPOP);
69346996

@@ -7023,7 +7085,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA
70237085
break;
70247086

70257087
case GT_FIELD_ADDR:
7026-
return fgMorphFieldAddr(tree, mac);
7088+
return fgMorphFieldAddr(tree);
70277089

70287090
case GT_INDEX_ADDR:
70297091
return fgMorphIndexAddr(tree->AsIndexAddr());
@@ -7152,7 +7214,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA
71527214
{
71537215
assert(tree->OperIs(GT_DIV));
71547216
tree->ChangeOper(GT_UDIV, GenTree::PRESERVE_VN);
7155-
return fgMorphSmpOp(tree, mac);
7217+
return fgMorphSmpOp(tree);
71567218
}
71577219

71587220
#if !defined(TARGET_64BIT) && !defined(TARGET_WASM)
@@ -7219,7 +7281,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA
72197281
{
72207282
assert(tree->OperIs(GT_MOD));
72217283
tree->ChangeOper(GT_UMOD, GenTree::PRESERVE_VN);
7222-
return fgMorphSmpOp(tree, mac);
7284+
return fgMorphSmpOp(tree);
72237285
}
72247286

72257287
// Do not use optimizations (unlike UMOD's idiv optimizing during codegen) for signed mod.
@@ -7561,34 +7623,14 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA
75617623
// the other operands in case this is a store. However, doing so unconditionally preserves previous
75627624
// behavior and "fixes up" field store importation that places the null check in the wrong location
75637625
// (before the 'value' operand is evaluated).
7564-
MorphAddrContext indMac;
75657626
if (tree->OperIsIndir() && !tree->OperIsAtomicOp())
75667627
{
7567-
// Communicate to FIELD_ADDR morphing that the parent is an indirection.
7568-
indMac.m_user = tree->AsIndir();
7569-
mac = &indMac;
7570-
}
7571-
// For additions, if we already have a context, keep track of whether all offsets added
7572-
// to the address are constant, and their sum does not overflow.
7573-
else if ((mac != nullptr) && tree->OperIs(GT_ADD) && op2->IsCnsIntOrI())
7574-
{
7575-
ClrSafeInt<size_t> offset(mac->m_totalOffset);
7576-
offset += op2->AsIntCon()->IconValue();
7577-
if (!offset.IsOverflow())
7578-
{
7579-
mac->m_totalOffset = offset.Value();
7580-
}
7581-
else
7582-
{
7583-
mac = nullptr;
7584-
}
7585-
}
7586-
else // Reset the context.
7587-
{
7588-
mac = nullptr;
7628+
// Examine the unmorphed address tree to identify any FIELD_ADDR whose null check
7629+
// can be elided because this indirection will fault on null instead.
7630+
fgMarkAddrModeForFieldAddr(tree->AsIndir());
75897631
}
75907632

7591-
tree->AsOp()->gtOp1 = op1 = fgMorphTree(op1, mac);
7633+
tree->AsOp()->gtOp1 = op1 = fgMorphTree(op1);
75927634

75937635
// If we are exiting the "then" part of a Qmark-Colon we must
75947636
// save the state of the current assertions table so that we
@@ -12530,7 +12572,7 @@ GenTreeOp* Compiler::fgMorphLongMul(GenTreeOp* mul)
1253012572
* Transform the given tree for code generation and return an equivalent tree.
1253112573
*/
1253212574

12533-
GenTree* Compiler::fgMorphTree(GenTree* tree, MorphAddrContext* mac)
12575+
GenTree* Compiler::fgMorphTree(GenTree* tree)
1253412576
{
1253512577
// We should never be called from CSE. Various optimizations below
1253612578
// assume that it is safe to swap operands if one is a constant.
@@ -12644,7 +12686,7 @@ GenTree* Compiler::fgMorphTree(GenTree* tree, MorphAddrContext* mac)
1264412686

1264512687
if (kind & GTK_SMPOP)
1264612688
{
12647-
tree = fgMorphSmpOp(tree, mac, &optAssertionPropDone);
12689+
tree = fgMorphSmpOp(tree, &optAssertionPropDone);
1264812690
goto DONE;
1264912691
}
1265012692

0 commit comments

Comments
 (0)