@@ -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