Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stop using CLS_VAR for "boxed statics" #63845

3 changes: 3 additions & 0 deletions src/coreclr/jit/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
31 changes: 21 additions & 10 deletions src/coreclr/jit/morph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down
78 changes: 43 additions & 35 deletions src/coreclr/jit/valuenum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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]));
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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()))
{
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/jit/valuenumfuncs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions src/coreclr/jit/valuenumtype.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ struct ValueNumPair
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;
Expand Down