Stop using `CLS_VAR` for "boxed statics" (#63845)
authorSingleAccretion <62474226+SingleAccretion@users.noreply.github.com>
Wed, 2 Feb 2022 10:52:25 +0000 (13:52 +0300)
committerGitHub <noreply@github.com>
Wed, 2 Feb 2022 10:52:25 +0000 (11:52 +0100)
* Number GTF_STATIC_BOX_PTR INDs as VNF_PtrToStatic

This allows us to recognize loads made through indirections
off of locals to which these addresses are assigned.

There is an interesting regression here: VNs for addresses that
represent symbolically different fields with the same physical
offset will now be numbered differently. Fortunately, it does not
seem like this is a widespread problem.

The next step towards deleting CLS_VAR.

* Add VNF_PtrToStatic support to store numbering

This is needed because we can see cases where code
refers to the same field both via the VNF_PtrToStatic
mechanism and the "IsFieldAddr" mechanism, causing
mismatches along the way (since the VNF_PtrToStatic
path does use "first field maps").

* Use INDs for boxed statics

Some diffs for this change: they're caused by the fact
we sometimes have inlining or source code refer to the
same static both via "ldsflda" (which was numbered with
an address for the box) and "ldsfld" (which used CLS_VAR
and the handle).

* Remove handling of CLS_VAR boxed statics

No longer needed.

No diffs.

* Do not add NRE sets for VNF_PtrToStatic addresses

This solves some of the bad regressions where we now have
VNF_PtrToStatic addresses instead of ADD(NonNullInd, Const).

In principle, this should be done for all VNFuncs known to
not be null, but that is a larger change which leads to lots
of regressions, so the special casing will have to do for now.

src/coreclr/jit/gentree.cpp
src/coreclr/jit/morph.cpp
src/coreclr/jit/valuenum.cpp
src/coreclr/jit/valuenumfuncs.h
src/coreclr/jit/valuenumtype.h

index 35b9d2d..7a29093 100644 (file)
@@ -15917,6 +15917,9 @@ bool GenTree::IsFieldAddr(Compiler* comp, GenTree** pBaseAddr, FieldSeqNode** pF
     // will effectively treat such cases ("possible" in unsafe code) as undefined behavior.
     if (comp->eeIsFieldStatic(fldSeq->GetFieldHandle()))
     {
+        // TODO-VNTypes: this code is out of sync w.r.t. boxed statics that are numbered with
+        // VNF_PtrToStatic and treated as "simple" while here we treat them as "complex".
+
         // TODO-VNTypes: we will always return the "baseAddr" here for now, but strictly speaking,
         // we only need to do that if we have a shared field, to encode the logical "instantiation"
         // argument. In all other cases, this serves no purpose and just leads to redundant maps.
index f07fe3c..4e458d5 100644 (file)
@@ -6265,20 +6265,32 @@ GenTree* Compiler::fgMorphField(GenTree* tree, MorphAddrContext* mac)
             FieldSeqNode* fldSeq =
                 !isBoxedStatic ? GetFieldSeqStore()->CreateSingleton(symHnd) : FieldSeqStore::NotAField();
 
-#ifdef TARGET_64BIT
+            // TODO-CQ: enable this optimization for 32 bit targets.
             bool isStaticReadOnlyInited = false;
-            bool plsSpeculative         = true;
-            if (info.compCompHnd->getStaticFieldCurrentClass(symHnd, &plsSpeculative) != NO_CLASS_HANDLE)
+#ifdef TARGET_64BIT
+            if (tree->TypeIs(TYP_REF) && !isBoxedStatic)
             {
-                isStaticReadOnlyInited = !plsSpeculative;
+                bool pIsSpeculative = true;
+                if (info.compCompHnd->getStaticFieldCurrentClass(symHnd, &pIsSpeculative) != NO_CLASS_HANDLE)
+                {
+                    isStaticReadOnlyInited = !pIsSpeculative;
+                }
             }
+#endif // TARGET_64BIT
 
-            // even if RelocTypeHint is REL32 let's still prefer IND over GT_CLS_VAR
-            // for static readonly fields of statically initialized classes - thus we can
-            // apply GTF_IND_INVARIANT flag and make it hoistable/CSE-friendly
-            if (isStaticReadOnlyInited || (IMAGE_REL_BASED_REL32 != eeGetRelocTypeHint(fldAddr)))
+            // TODO: choices made below have mostly historical reasons and
+            // should be unified to always use the IND(<address>) form.
+            CLANG_FORMAT_COMMENT_ANCHOR;
+
+#ifdef TARGET_64BIT
+            bool preferIndir =
+                isBoxedStatic || isStaticReadOnlyInited || (IMAGE_REL_BASED_REL32 != eeGetRelocTypeHint(fldAddr));
+#else  // !TARGET_64BIT
+            bool preferIndir = isBoxedStatic;
+#endif // !TARGET_64BIT
+
+            if (preferIndir)
             {
-                // The address is not directly addressible, so force it into a constant, so we handle it properly.
                 GenTreeFlags handleKind = GTF_EMPTY;
                 if (isBoxedStatic)
                 {
@@ -6321,7 +6333,6 @@ GenTree* Compiler::fgMorphField(GenTree* tree, MorphAddrContext* mac)
                 return fgMorphSmpOp(tree);
             }
             else
-#endif // TARGET_64BIT
             {
                 // Only volatile or classinit could be set, and they map over
                 noway_assert((tree->gtFlags & ~(GTF_FLD_VOLATILE | GTF_FLD_INITCLASS | GTF_COMMON_MASK)) == 0);
index 704cefd..3ec6645 100644 (file)
@@ -7802,8 +7802,24 @@ void Compiler::fgValueNumberAssignment(GenTreeOp* tree)
                 GenTree*      baseAddr = nullptr;
                 FieldSeqNode* fldSeq   = nullptr;
 
+                if (argIsVNFunc && funcApp.m_func == VNF_PtrToStatic)
+                {
+                    FieldSeqNode* fldSeq = vnStore->FieldSeqVNToFieldSeq(funcApp.m_args[1]);
+                    assert(fldSeq != nullptr); // We should never see an empty sequence here.
+
+                    if (fldSeq != FieldSeqStore::NotAField())
+                    {
+                        ValueNum newHeapVN = vnStore->VNApplySelectorsAssign(VNK_Liberal, fgCurMemoryVN[GcHeap], fldSeq,
+                                                                             rhsVNPair.GetLiberal(), lhs->TypeGet());
+                        recordGcHeapStore(tree, newHeapVN DEBUGARG("static field store"));
+                    }
+                    else
+                    {
+                        fgMutateGcHeap(tree DEBUGARG("indirect store at NotAField PtrToStatic address"));
+                    }
+                }
                 // Is the LHS an array index expression?
-                if (argIsVNFunc && funcApp.m_func == VNF_PtrToArrElem)
+                else if (argIsVNFunc && funcApp.m_func == VNF_PtrToArrElem)
                 {
                     CORINFO_CLASS_HANDLE elemTypeEq =
                         CORINFO_CLASS_HANDLE(vnStore->ConstantValue<ssize_t>(funcApp.m_args[0]));
@@ -8625,39 +8641,19 @@ void Compiler::fgValueNumberTree(GenTree* tree)
                     ValueNumPair   clsVarVNPair;
                     GenTreeClsVar* clsVar = tree->AsClsVar();
                     FieldSeqNode*  fldSeq = clsVar->gtFieldSeq;
-                    assert(fldSeq != nullptr); // We need to have one.
-                    CORINFO_FIELD_HANDLE fieldHnd = clsVar->gtClsVarHnd;
-                    assert(fieldHnd != NO_FIELD_HANDLE);
-                    ValueNum selectedStaticVar = ValueNumStore::NoVN;
-
-                    if (fldSeq == FieldSeqStore::NotAField())
-                    {
-                        // This is the box for a "boxed static" - see "fgMorphField".
-                        assert(gtIsStaticFieldPtrToBoxedStruct(clsVar->TypeGet(), fieldHnd));
-
-                        // We will create an empty field sequence for VNF_PtrToStatic here. We will assume
-                        // the actual sequence will get appended later, when processing the TARGET_POINTER_SIZE
-                        // offset that is always added to this box to get to its payload.
+                    assert((fldSeq != nullptr) && (fldSeq != FieldSeqStore::NotAField())); // We need to have one.
 
-                        ValueNum fieldHndVN = vnStore->VNForHandle(ssize_t(fieldHnd), GTF_ICON_FIELD_HDL);
-                        ValueNum fieldSeqVN = vnStore->VNForFieldSeq(nullptr);
-                        clsVarVNPair.SetBoth(vnStore->VNForFunc(TYP_REF, VNF_PtrToStatic, fieldHndVN, fieldSeqVN));
-                    }
-                    else
-                    {
-                        // This is a reference to heap memory.
-                        // We model statics as indices into GcHeap (which is a subset of ByrefExposed).
+                    // This is a reference to heap memory.
+                    // We model statics as indices into GcHeap (which is a subset of ByrefExposed).
+                    size_t   structSize = 0;
+                    ValueNum selectedStaticVar =
+                        vnStore->VNApplySelectors(VNK_Liberal, fgCurMemoryVN[GcHeap], fldSeq, &structSize);
+                    selectedStaticVar =
+                        vnStore->VNApplySelectorsTypeCheck(selectedStaticVar, tree->TypeGet(), structSize);
 
-                        size_t structSize = 0;
-                        selectedStaticVar =
-                            vnStore->VNApplySelectors(VNK_Liberal, fgCurMemoryVN[GcHeap], fldSeq, &structSize);
-                        selectedStaticVar =
-                            vnStore->VNApplySelectorsTypeCheck(selectedStaticVar, tree->TypeGet(), structSize);
-
-                        clsVarVNPair.SetLiberal(selectedStaticVar);
-                        // The conservative interpretation always gets a new, unique VN.
-                        clsVarVNPair.SetConservative(vnStore->VNForExpr(compCurBB, tree->TypeGet()));
-                    }
+                    clsVarVNPair.SetLiberal(selectedStaticVar);
+                    // The conservative interpretation always gets a new, unique VN.
+                    clsVarVNPair.SetConservative(vnStore->VNForExpr(compCurBB, tree->TypeGet()));
 
                     // The ValueNum returned must represent the full-sized IL-Stack value
                     // If we need to widen this value then we need to introduce a VNF_Cast here to represent
@@ -8834,9 +8830,22 @@ void Compiler::fgValueNumberTree(GenTree* tree)
 
                 if (!wasNewobj)
                 {
+                    // Indirections off of addresses for boxed statics represent bases for
+                    // the address of the static itself. Here we will use "nullptr" for the
+                    // field sequence and assume the actual static field will be appended to
+                    // it later, as part of numbering the method table pointer offset addition.
+                    if (addr->IsCnsIntOrI() && addr->IsIconHandle(GTF_ICON_STATIC_BOX_PTR))
+                    {
+                        assert(addrNvnp.BothEqual() && (addrXvnp == vnStore->VNPForEmptyExcSet()));
+                        ValueNum boxAddrVN  = addrNvnp.GetLiberal();
+                        ValueNum fieldSeqVN = vnStore->VNForFieldSeq(nullptr);
+                        ValueNum staticAddrVN =
+                            vnStore->VNForFunc(tree->TypeGet(), VNF_PtrToStatic, boxAddrVN, fieldSeqVN);
+                        tree->gtVNPair = ValueNumPair(staticAddrVN, staticAddrVN);
+                    }
                     // Is this invariant indirect expected to always return a non-null value?
                     // TODO-VNTypes: non-null indirects should only be used for TYP_REFs.
-                    if ((tree->gtFlags & GTF_IND_NONNULL) != 0)
+                    else if ((tree->gtFlags & GTF_IND_NONNULL) != 0)
                     {
                         assert(tree->gtFlags & GTF_IND_NONFAULTING);
                         tree->gtVNPair = vnStore->VNPairForFunc(tree->TypeGet(), VNF_NonNullIndirect, addrNvnp);
@@ -10614,8 +10623,7 @@ void Compiler::fgValueNumberAddExceptionSetForIndirection(GenTree* tree, GenTree
 
     // The normal VN for base address is used to create the NullPtrExc
     ValueNumPair vnpBaseNorm = vnStore->VNPNormalPair(baseVNP);
-
-    ValueNumPair excChkSet = vnStore->VNPForEmptyExcSet();
+    ValueNumPair excChkSet   = vnStore->VNPForEmptyExcSet();
 
     if (!vnStore->IsKnownNonNull(vnpBaseNorm.GetLiberal()))
     {
index fc10c05..f82c2ff 100644 (file)
@@ -16,7 +16,7 @@ ValueNumFuncDef(NotAField, 0, false, false, false)  // Value number function for
 ValueNumFuncDef(PtrToLoc, 2, false, true, false)            // Pointer (byref) to a local variable.  Args: VN's of: 0: var num, 1: FieldSeq.
 ValueNumFuncDef(PtrToArrElem, 4, false, false, false)       // Pointer (byref) to an array element.  Args: 0: array elem type eq class var_types value, VN's of: 1: array, 2: index, 3: FieldSeq.
 ValueNumFuncDef(PtrToStatic, 2, false, true, false)         // Pointer (byref) to a static variable (or possibly a field thereof, if the static variable is a struct).
-                                                            // Args: 0: (VN of) the field handle, 1: the field sequence, of which the first element is the static itself.
+                                                            // Args: 0: (VN of) the box's address if the static is "boxed", 1: the field sequence, of which the first element is the static itself.
 
 ValueNumFuncDef(Phi, 2, false, false, false)        // A phi function.  Only occurs as arg of PhiDef or PhiMemoryDef.  Arguments are SSA numbers of var being defined.
 ValueNumFuncDef(PhiDef, 3, false, false, false)     // Args: 0: local var # (or -1 for memory), 1: SSA #, 2: VN of definition.
index 1c991e5..5ed44e2 100644 (file)
@@ -96,6 +96,16 @@ public:
         m_conservative = vn;
     }
 
+    bool operator==(const ValueNumPair& other) const
+    {
+        return (m_liberal == other.m_liberal) && (m_conservative == other.m_conservative);
+    }
+
+    bool operator!=(const ValueNumPair& other) const
+    {
+        return !(*this == other);
+    }
+
     void operator=(const ValueNumPair& vn2)
     {
         m_liberal      = vn2.m_liberal;