@@ -3527,19 +3527,113 @@ 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+ // Replaces the legacy "MorphAddrContext" thread-through mechanism: peels
3536+ // constant offsets off ADD nodes (matching the previous "ADD(_, const)"
3537+ // propagation) and recurses through chained instance FIELD_ADDR nodes,
3538+ // accumulating the cumulative constant offset that would be added on top of
3539+ // the FIELD_ADDR's base object.
3540+ //
3541+ // For each instance FIELD_ADDR whose base object could be null:
3542+ // * If the cumulative offset (including this field's offset) is small enough
3543+ // that the OS page guard catches the access, mark the FIELD_ADDR with
3544+ // GTF_FLD_TGT_NONFAULTING so morphing can elide the explicit null check,
3545+ // and clear GTF_IND_NONFAULTING on the consuming indirection so it faults
3546+ // on null instead.
3547+ // * Otherwise, the FIELD_ADDR will introduce an explicit NULLCHECK and stop
3548+ // propagation: mark the indirection as having an ordering side effect (so
3549+ // downstream optimizations treat the inserted NULLCHECK conservatively),
3550+ // and stop walking past this FIELD_ADDR.
3551+ //
3552+ // Arguments:
3553+ // indir - The indirection whose address operand will be morphed.
3554+ //
3555+ void Compiler::fgMarkAddrModeForFieldAddr(GenTreeIndir* indir)
3556+ {
3557+ GenTree* addr = indir->Addr();
3558+ size_t offset = 0;
3559+
3560+ while (true)
3561+ {
3562+ // Peel ADD(addr, CNS_INT) -- only follow the op1 spine, like the legacy mac propagation did.
3563+ if (addr->OperIs(GT_ADD) && !addr->gtOverflow())
3564+ {
3565+ GenTree* op2 = addr->gtGetOp2();
3566+ if (op2->IsCnsIntOrI() && op2->TypeIs(TYP_I_IMPL) && !op2->AsIntCon()->IsIconHandle())
3567+ {
3568+ ClrSafeInt<size_t> total(offset);
3569+ total += op2->AsIntCon()->IconValue();
3570+ if (total.IsOverflow())
3571+ {
3572+ return;
3573+ }
3574+ offset = total.Value();
3575+ addr = addr->gtGetOp1();
3576+ continue;
3577+ }
3578+ return;
3579+ }
3580+
3581+ // Peel through a chained instance FIELD_ADDR.
3582+ if (addr->OperIs(GT_FIELD_ADDR) && addr->AsFieldAddr()->IsInstance())
3583+ {
3584+ GenTreeFieldAddr* fld = addr->AsFieldAddr();
3585+ GenTree* objRef = fld->GetFldObj();
3586+ unsigned fldOffset = fld->gtFldOffset;
3587+
3588+ if (fgAddrCouldBeNull(objRef))
3589+ {
3590+ ClrSafeInt<size_t> total(offset);
3591+ total += fldOffset;
3592+
3593+ bool elide = !total.IsOverflow() && !fgIsBigOffset(total.Value());
3594+ if (elide)
3595+ {
3596+ fld->gtFlags |= GTF_FLD_TGT_NONFAULTING;
3597+ // We can elide the null check only by letting it happen as part of the
3598+ // consuming indirection, so it is no longer non-faulting.
3599+ indir->gtFlags &= ~GTF_IND_NONFAULTING;
3600+ }
3601+ else
3602+ {
3603+ // The FIELD_ADDR will introduce an explicit NULLCHECK, which acts as a
3604+ // barrier in the consuming indirection's address chain.
3605+ indir->SetHasOrderingSideEffect();
3606+ return;
3607+ }
3608+ }
3609+
3610+ ClrSafeInt<size_t> totalOff(offset);
3611+ totalOff += fldOffset;
3612+ if (totalOff.IsOverflow())
3613+ {
3614+ return;
3615+ }
3616+ offset = totalOff.Value();
3617+ addr = objRef;
3618+ continue;
3619+ }
3620+
3621+ return;
3622+ }
3623+ }
3624+
35303625//------------------------------------------------------------------------
35313626// fgMorphFieldAddr: Fully morph a FIELD_ADDR tree.
35323627//
35333628// Expands the field node into explicit additions.
35343629//
35353630// Arguments:
35363631// tree - The FIELD_ADDR tree
3537- // mac - The morphing context, used to elide adding null checks
35383632//
35393633// Return Value:
35403634// The fully morphed "tree".
35413635//
3542- GenTree* Compiler::fgMorphFieldAddr(GenTree* tree, MorphAddrContext* mac )
3636+ GenTree* Compiler::fgMorphFieldAddr(GenTree* tree)
35433637{
35443638 assert(tree->OperIs(GT_FIELD_ADDR));
35453639
@@ -3549,7 +3643,7 @@ GenTree* Compiler::fgMorphFieldAddr(GenTree* tree, MorphAddrContext* mac)
35493643
35503644 if (fieldNode->IsInstance())
35513645 {
3552- tree = fgMorphExpandInstanceField(tree, mac );
3646+ tree = fgMorphExpandInstanceField(tree);
35533647 }
35543648 else if (fieldNode->IsTlsStatic())
35553649 {
@@ -3560,11 +3654,10 @@ GenTree* Compiler::fgMorphFieldAddr(GenTree* tree, MorphAddrContext* mac)
35603654 assert(!"Normal statics are expected to be handled in the importer");
35613655 }
35623656
3563- // Pass down the current mac; if non null we are computing an address
35643657 GenTree* result;
35653658 if (tree->OperIsSimple())
35663659 {
3567- result = fgMorphSmpOp(tree, mac );
3660+ result = fgMorphSmpOp(tree);
35683661 result->SetMorphed(this);
35693662
35703663 // Quirk: preserve previous behavior with this NO_CSE.
@@ -3575,7 +3668,7 @@ GenTree* Compiler::fgMorphFieldAddr(GenTree* tree, MorphAddrContext* mac)
35753668 }
35763669 else
35773670 {
3578- result = fgMorphTree(tree, mac );
3671+ result = fgMorphTree(tree);
35793672 }
35803673
35813674 JITDUMP("\nFinal value of Compiler::fgMorphFieldAddr after morphing:\n");
@@ -3591,12 +3684,11 @@ GenTree* Compiler::fgMorphFieldAddr(GenTree* tree, MorphAddrContext* mac)
35913684//
35923685// Arguments:
35933686// tree - The FIELD_ADDR tree
3594- // mac - The morphing context, used to elide adding null checks
35953687//
35963688// Return Value:
35973689// The expanded "tree" of an arbitrary shape.
35983690//
3599- GenTree* Compiler::fgMorphExpandInstanceField(GenTree* tree, MorphAddrContext* mac )
3691+ GenTree* Compiler::fgMorphExpandInstanceField(GenTree* tree)
36003692{
36013693 assert(tree->OperIs(GT_FIELD_ADDR) && tree->AsFieldAddr()->IsInstance());
36023694
@@ -3673,29 +3765,18 @@ GenTree* Compiler::fgMorphExpandInstanceField(GenTree* tree, MorphAddrContext* m
36733765
36743766 if (fgAddrCouldBeNull(objRef))
36753767 {
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);
3768+ // The pre-walk performed by fgMarkAddrModeForFieldAddr sets GTF_FLD_TGT_NONFAULTING when
3769+ // the consuming indirection will implicitly null-check the produced address (the cumulative
3770+ // constant offset down to this FIELD_ADDR is small enough that the OS page guard catches it).
3771+ addExplicitNullCheck = (tree->gtFlags & GTF_FLD_TGT_NONFAULTING) == 0;
36793772
36803773 // The transformation here turns a value dependency (FIELD_ADDR being a
36813774 // known non-null operand) into a control-flow dependency (introducing
36823775 // 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- }
3776+ // null check from the parent of the FIELD_ADDR node.
3777+ //
3778+ // For the elided case, the consuming indirection has already been marked
3779+ // (~GTF_IND_NONFAULTING) by fgMarkAddrModeForFieldAddr; nothing else to do here.
36993780 }
37003781
37013782 if (addExplicitNullCheck)
@@ -6920,14 +7001,13 @@ GenTreeOp* Compiler::fgMorphCommutative(GenTreeOp* tree)
69207001//
69217002// Arguments:
69227003// tree - tree to morph
6923- // mac - address context for morphing
69247004// optAssertionPropDone - [out, optional] set true if local assertions
69257005// were killed/genned while morphing this tree
69267006//
69277007// Returns:
69287008// Tree, possibly updated
69297009//
6930- GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optAssertionPropDone)
7010+ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, bool* optAssertionPropDone)
69317011{
69327012 assert(tree->OperKind() & GTK_SMPOP);
69337013
@@ -7011,7 +7091,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA
70117091 break;
70127092
70137093 case GT_FIELD_ADDR:
7014- return fgMorphFieldAddr(tree, mac );
7094+ return fgMorphFieldAddr(tree);
70157095
70167096 case GT_INDEX_ADDR:
70177097 return fgMorphIndexAddr(tree->AsIndexAddr());
@@ -7140,7 +7220,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA
71407220 {
71417221 assert(tree->OperIs(GT_DIV));
71427222 tree->ChangeOper(GT_UDIV, GenTree::PRESERVE_VN);
7143- return fgMorphSmpOp(tree, mac );
7223+ return fgMorphSmpOp(tree);
71447224 }
71457225
71467226#if !defined(TARGET_64BIT) && !defined(TARGET_WASM)
@@ -7207,7 +7287,7 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA
72077287 {
72087288 assert(tree->OperIs(GT_MOD));
72097289 tree->ChangeOper(GT_UMOD, GenTree::PRESERVE_VN);
7210- return fgMorphSmpOp(tree, mac );
7290+ return fgMorphSmpOp(tree);
72117291 }
72127292
72137293 // Do not use optimizations (unlike UMOD's idiv optimizing during codegen) for signed mod.
@@ -7533,34 +7613,14 @@ GenTree* Compiler::fgMorphSmpOp(GenTree* tree, MorphAddrContext* mac, bool* optA
75337613 // the other operands in case this is a store. However, doing so unconditionally preserves previous
75347614 // behavior and "fixes up" field store importation that places the null check in the wrong location
75357615 // (before the 'value' operand is evaluated).
7536- MorphAddrContext indMac;
75377616 if (tree->OperIsIndir() && !tree->OperIsAtomicOp())
75387617 {
7539- // Communicate to FIELD_ADDR morphing that the parent is an indirection.
7540- indMac.m_user = tree->AsIndir();
7541- mac = &indMac;
7542- }
7543- // For additions, if we already have a context, keep track of whether all offsets added
7544- // to the address are constant, and their sum does not overflow.
7545- else if ((mac != nullptr) && tree->OperIs(GT_ADD) && op2->IsCnsIntOrI())
7546- {
7547- ClrSafeInt<size_t> offset(mac->m_totalOffset);
7548- offset += op2->AsIntCon()->IconValue();
7549- if (!offset.IsOverflow())
7550- {
7551- mac->m_totalOffset = offset.Value();
7552- }
7553- else
7554- {
7555- mac = nullptr;
7556- }
7557- }
7558- else // Reset the context.
7559- {
7560- mac = nullptr;
7618+ // Examine the unmorphed address tree to identify any FIELD_ADDR whose null check
7619+ // can be elided because this indirection will fault on null instead.
7620+ fgMarkAddrModeForFieldAddr(tree->AsIndir());
75617621 }
75627622
7563- tree->AsOp()->gtOp1 = op1 = fgMorphTree(op1, mac );
7623+ tree->AsOp()->gtOp1 = op1 = fgMorphTree(op1);
75647624
75657625 // If we are exiting the "then" part of a Qmark-Colon we must
75667626 // save the state of the current assertions table so that we
@@ -12448,7 +12508,7 @@ GenTreeOp* Compiler::fgMorphLongMul(GenTreeOp* mul)
1244812508 * Transform the given tree for code generation and return an equivalent tree.
1244912509 */
1245012510
12451- GenTree* Compiler::fgMorphTree(GenTree* tree, MorphAddrContext* mac )
12511+ GenTree* Compiler::fgMorphTree(GenTree* tree)
1245212512{
1245312513 assert(tree);
1245412514 tree->ClearMorphed();
@@ -12558,7 +12618,7 @@ GenTree* Compiler::fgMorphTree(GenTree* tree, MorphAddrContext* mac)
1255812618
1255912619 if (kind & GTK_SMPOP)
1256012620 {
12561- tree = fgMorphSmpOp(tree, mac, &optAssertionPropDone);
12621+ tree = fgMorphSmpOp(tree, &optAssertionPropDone);
1256212622 goto DONE;
1256312623 }
1256412624
0 commit comments