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

Detect class inited status more correctly in prestub/jitinterface #104253

Merged
merged 4 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/coreclr/vm/appdomain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ void AppDomain::SetNativeDllSearchDirectories(LPCWSTR wszNativeDllSearchDirector
}
}

OBJECTREF* BaseDomain::AllocateObjRefPtrsInLargeTable(int nRequested, DynamicStaticsInfo* pStaticsInfo, MethodTable *pMTToFillWithStaticBoxes)
OBJECTREF* BaseDomain::AllocateObjRefPtrsInLargeTable(int nRequested, DynamicStaticsInfo* pStaticsInfo, MethodTable *pMTToFillWithStaticBoxes, bool isClassInitdeByUpdatingStaticPointer)
{
CONTRACTL
{
Expand Down Expand Up @@ -707,7 +707,7 @@ OBJECTREF* BaseDomain::AllocateObjRefPtrsInLargeTable(int nRequested, DynamicSta
if (pStaticsInfo)
{
// race with other threads that might be doing the same concurrent allocation
if (!pStaticsInfo->InterlockedUpdateStaticsPointer(/*isGCPointer*/ true, (TADDR)result))
if (!pStaticsInfo->InterlockedUpdateStaticsPointer(/*isGCPointer*/ true, (TADDR)result, isClassInitdeByUpdatingStaticPointer))
{
// we lost the race, release our handles and use the handles from the
// winning thread
Expand Down Expand Up @@ -2930,7 +2930,7 @@ void AppDomain::SetupSharedStatics()

FieldDesc * pEmptyStringFD = CoreLibBinder::GetField(FIELD__STRING__EMPTY);
OBJECTREF* pEmptyStringHandle = (OBJECTREF*)
((TADDR)g_pStringClass->GetDynamicStaticsInfo()->m_pGCStatics+pEmptyStringFD->GetOffset());
((TADDR)g_pStringClass->GetDynamicStaticsInfo()->GetGCStaticsPointer()+pEmptyStringFD->GetOffset());
SetObjectReference( pEmptyStringHandle, StringObject::GetEmptyString());
}

Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/appdomain.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ class BaseDomain
// Statics and reflection info (Types, MemberInfo,..) are stored this way
// If pStaticsInfo != 0, allocation will only take place if GC statics in the DynamicStaticsInfo are NULL (and the allocation
// will be properly serialized)
OBJECTREF *AllocateObjRefPtrsInLargeTable(int nRequested, DynamicStaticsInfo* pStaticsInfo = NULL, MethodTable *pMTToFillWithStaticBoxes = NULL);
OBJECTREF *AllocateObjRefPtrsInLargeTable(int nRequested, DynamicStaticsInfo* pStaticsInfo = NULL, MethodTable *pMTToFillWithStaticBoxes = NULL, bool isClassInitdeByUpdatingStaticPointer = false);

//****************************************************************************************
// Handles
Expand Down
15 changes: 6 additions & 9 deletions src/coreclr/vm/jitinterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1210,10 +1210,9 @@ CorInfoHelpFunc CEEInfo::getSharedStaticsHelper(FieldDesc * pField, MethodTable
{
STANDARD_VM_CONTRACT;

pFieldMT->AttemptToPreinit();
bool GCStatic = (pField->GetFieldType() == ELEMENT_TYPE_CLASS ||
pField->GetFieldType() == ELEMENT_TYPE_VALUETYPE);
bool noCtor = pFieldMT->IsClassInited();
bool noCtor = pFieldMT->IsClassInitedOrPreinited();
bool threadStatic = pField->IsThreadStatic();
bool isInexactMT = pFieldMT->IsSharedByGenericInstantiations();
bool isCollectible = pFieldMT->Collectible();
Expand Down Expand Up @@ -1451,7 +1450,7 @@ void CEEInfo::getFieldInfo (CORINFO_RESOLVED_TOKEN * pResolvedToken,
}

// We are not going through a helper. The constructor has to be triggered explicitly.
if (!pFieldMT->IsClassInited())
if (!pFieldMT->IsClassInitedOrPreinited())
fieldFlags |= CORINFO_FLG_FIELD_INITCLASS;
}
else
Expand Down Expand Up @@ -1536,10 +1535,9 @@ void CEEInfo::getFieldInfo (CORINFO_RESOLVED_TOKEN * pResolvedToken,
// Allocate space for the local class if necessary, but don't trigger
// class construction.
pFieldMT->EnsureStaticDataAllocated();
pFieldMT->AttemptToPreinit();

// We are not going through a helper. The constructor has to be triggered explicitly.
if (!pFieldMT->IsClassInited())
if (!pFieldMT->IsClassInitedOrPreinited())
fieldFlags |= CORINFO_FLG_FIELD_INITCLASS;

GCX_COOP();
Expand Down Expand Up @@ -3884,8 +3882,7 @@ CorInfoInitClassResult CEEInfo::initClass(

MethodTable *pTypeToInitMT = typeToInitTH.AsMethodTable();

pTypeToInitMT->AttemptToPreinit();
if (pTypeToInitMT->IsClassInited())
if (pTypeToInitMT->IsClassInitedOrPreinited())
{
// If the type is initialized there really is nothing to do.
result = CORINFO_INITCLASS_INITIALIZED;
Expand Down Expand Up @@ -11725,7 +11722,7 @@ bool CEEInfo::getStaticFieldContent(CORINFO_FIELD_HANDLE fieldHnd, uint8_t* buff
// class construction.
pEnclosingMT->EnsureStaticDataAllocated();

if (!field->IsThreadStatic() && pEnclosingMT->IsClassInited() && IsFdInitOnly(field->GetAttributes()))
if (!field->IsThreadStatic() && pEnclosingMT->IsClassInitedOrPreinited() && IsFdInitOnly(field->GetAttributes()))
{
if (field->IsObjRef())
{
Expand Down Expand Up @@ -11913,7 +11910,7 @@ CORINFO_CLASS_HANDLE CEEJitInfo::getStaticFieldCurrentClass(CORINFO_FIELD_HANDLE
VALIDATEOBJECTREF(fieldObj);

// Check for initialization before looking at the value
isClassInitialized = !!pEnclosingMT->IsClassInited();
isClassInitialized = !!pEnclosingMT->IsClassInitedOrPreinited();

if (fieldObj != NULL)
{
Expand Down
12 changes: 6 additions & 6 deletions src/coreclr/vm/loaderallocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2254,7 +2254,7 @@ PTR_OnStackReplacementManager LoaderAllocator::GetOnStackReplacementManager()
#endif // FEATURE_ON_STACK_REPLACEMENT

#ifndef DACCESS_COMPILE
void LoaderAllocator::AllocateBytesForStaticVariables(DynamicStaticsInfo* pStaticsInfo, uint32_t cbMem)
void LoaderAllocator::AllocateBytesForStaticVariables(DynamicStaticsInfo* pStaticsInfo, uint32_t cbMem, bool isClassInitedByUpdatingStaticPointer)
{
CONTRACTL
{
Expand Down Expand Up @@ -2294,7 +2294,7 @@ void LoaderAllocator::AllocateBytesForStaticVariables(DynamicStaticsInfo* pStati
WeakInteriorHandleHolder weakHandleHolder = GetAppDomain()->CreateWeakInteriorHandle(ptrArray, &pStaticsInfo->m_pNonGCStatics);
RegisterHandleForCleanupLocked(weakHandleHolder.GetValue());
weakHandleHolder.SuppressRelease();
bool didUpdateStaticsPointer = pStaticsInfo->InterlockedUpdateStaticsPointer(/* isGCPointer */false, (TADDR)ptrArray->GetDataPtr());
bool didUpdateStaticsPointer = pStaticsInfo->InterlockedUpdateStaticsPointer(/* isGCPointer */false, (TADDR)ptrArray->GetDataPtr(), isClassInitedByUpdatingStaticPointer);
_ASSERTE(didUpdateStaticsPointer);
}
}
Expand Down Expand Up @@ -2324,11 +2324,11 @@ void LoaderAllocator::AllocateBytesForStaticVariables(DynamicStaticsInfo* pStati
pbMem = (uint8_t*)ALIGN_UP(pbMem, 8);
}
#endif
pStaticsInfo->InterlockedUpdateStaticsPointer(/* isGCPointer */false, (TADDR)pbMem);
pStaticsInfo->InterlockedUpdateStaticsPointer(/* isGCPointer */false, (TADDR)pbMem, isClassInitedByUpdatingStaticPointer);
}
}

void LoaderAllocator::AllocateGCHandlesBytesForStaticVariables(DynamicStaticsInfo* pStaticsInfo, uint32_t cSlots, MethodTable* pMTToFillWithStaticBoxes)
void LoaderAllocator::AllocateGCHandlesBytesForStaticVariables(DynamicStaticsInfo* pStaticsInfo, uint32_t cSlots, MethodTable* pMTToFillWithStaticBoxes, bool isClassInitedByUpdatingStaticPointer)
{
CONTRACTL
{
Expand Down Expand Up @@ -2374,15 +2374,15 @@ void LoaderAllocator::AllocateGCHandlesBytesForStaticVariables(DynamicStaticsInf
WeakInteriorHandleHolder weakHandleHolder = GetAppDomain()->CreateWeakInteriorHandle(ptrArray, &pStaticsInfo->m_pGCStatics);
RegisterHandleForCleanupLocked(weakHandleHolder.GetValue());
weakHandleHolder.SuppressRelease();
bool didUpdateStaticsPointer = pStaticsInfo->InterlockedUpdateStaticsPointer(/* isGCPointer */true, (TADDR)ptrArray->GetDataPtr());
bool didUpdateStaticsPointer = pStaticsInfo->InterlockedUpdateStaticsPointer(/* isGCPointer */true, (TADDR)ptrArray->GetDataPtr(), isClassInitedByUpdatingStaticPointer);
_ASSERTE(didUpdateStaticsPointer);
}
}
GCPROTECT_END();
}
else
{
GetDomain()->AllocateObjRefPtrsInLargeTable(cSlots, pStaticsInfo, pMTToFillWithStaticBoxes);
GetDomain()->AllocateObjRefPtrsInLargeTable(cSlots, pStaticsInfo, pMTToFillWithStaticBoxes, isClassInitedByUpdatingStaticPointer);
}
}
#endif // !DACCESS_COMPILE
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/vm/loaderallocator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -761,8 +761,8 @@ class LoaderAllocator
LIMITED_METHOD_CONTRACT;
return m_nGCCount;
}
void AllocateBytesForStaticVariables(DynamicStaticsInfo* pStaticsInfo, uint32_t cbMem);
void AllocateGCHandlesBytesForStaticVariables(DynamicStaticsInfo* pStaticsInfo, uint32_t cSlots, MethodTable* pMTWithStaticBoxes);
void AllocateBytesForStaticVariables(DynamicStaticsInfo* pStaticsInfo, uint32_t cbMem, bool isClassInitedByUpdatingStaticPointer);
void AllocateGCHandlesBytesForStaticVariables(DynamicStaticsInfo* pStaticsInfo, uint32_t cSlots, MethodTable* pMTWithStaticBoxes, bool isClassInitedByUpdatingStaticPointer);

static BOOL Destroy(QCall::LoaderAllocatorHandle pLoaderAllocator);

Expand Down
59 changes: 40 additions & 19 deletions src/coreclr/vm/methodtable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3782,20 +3782,42 @@ void MethodTable::EnsureStaticDataAllocated()
CONTRACTL_END;

PTR_MethodTableAuxiliaryData pAuxiliaryData = GetAuxiliaryDataForWrite();
if (!pAuxiliaryData->IsStaticDataAllocated() && IsDynamicStatics())
if (!pAuxiliaryData->IsStaticDataAllocated())
{
DynamicStaticsInfo *pDynamicStaticsInfo = GetDynamicStaticsInfo();
// Allocate space for normal statics if we might have them
if (pDynamicStaticsInfo->GetNonGCStaticsPointer() == NULL)
GetLoaderAllocator()->AllocateBytesForStaticVariables(pDynamicStaticsInfo, GetClass()->GetNonGCRegularStaticFieldBytes());
bool isInitedIfStaticDataAllocated = IsInitedIfStaticDataAllocated();
if (IsDynamicStatics() && !IsSharedByGenericInstantiations())
{
DynamicStaticsInfo *pDynamicStaticsInfo = GetDynamicStaticsInfo();
// Allocate space for normal statics if we might have them
if (pDynamicStaticsInfo->GetNonGCStaticsPointer() == NULL)
GetLoaderAllocator()->AllocateBytesForStaticVariables(pDynamicStaticsInfo, GetClass()->GetNonGCRegularStaticFieldBytes(), isInitedIfStaticDataAllocated);

if (pDynamicStaticsInfo->GetGCStaticsPointer() == NULL)
GetLoaderAllocator()->AllocateGCHandlesBytesForStaticVariables(pDynamicStaticsInfo, GetClass()->GetNumHandleRegularStatics(), this->HasBoxedRegularStatics() ? this : NULL);
if (pDynamicStaticsInfo->GetGCStaticsPointer() == NULL)
GetLoaderAllocator()->AllocateGCHandlesBytesForStaticVariables(pDynamicStaticsInfo, GetClass()->GetNumHandleRegularStatics(), this->HasBoxedRegularStatics() ? this : NULL, isInitedIfStaticDataAllocated);
}
pAuxiliaryData->SetIsStaticDataAllocated(isInitedIfStaticDataAllocated);
}
pAuxiliaryData->SetIsStaticDataAllocated();
}

void MethodTable::AttemptToPreinit()
bool MethodTable::IsClassInitedOrPreinited()
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
INJECT_FAULT(COMPlusThrowOM());
}
CONTRACTL_END;

bool initResult;
if (GetAuxiliaryData()->IsClassInitedOrPreinitedDecided(&initResult))
return initResult;

EnsureStaticDataAllocated();
return IsClassInited();
}

bool MethodTable::IsInitedIfStaticDataAllocated()
{
CONTRACTL
{
Expand All @@ -3806,33 +3828,32 @@ void MethodTable::AttemptToPreinit()
CONTRACTL_END;

if (IsClassInited())
return;
{
return true;
}

if (HasClassConstructor())
{
// If there is a class constructor, then the class cannot be preinitted.
return;
return false;
}

if (GetClass()->GetNonGCRegularStaticFieldBytes() == 0 && GetClass()->GetNumHandleRegularStatics() == 0)
{
// If there are static fields that are not thread statics, then the class is preinitted.
SetClassInited();
return;
// If there aren't static fields that are not thread statics, then the class is preinitted.
return true;
}

// At this point, we are looking at a class that has no class constructor, but does have static fields

if (IsSharedByGenericInstantiations())
{
// If we don't know the exact type, we can't pre-allocate the fields
return;
// If we don't know the exact type, we can't pre-init the the fields
return false;
}

// All this class needs to be initialized is to allocate the memory for the static fields. Do so, and mark the type as initialized
EnsureStaticDataAllocated();
SetClassInited();
return;
return true;
}

void MethodTable::EnsureTlsIndexAllocated()
Expand Down
44 changes: 37 additions & 7 deletions src/coreclr/vm/methodtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ struct MethodTableAuxiliaryData
enum_flag_DependenciesLoaded = 0x0080, // class and all dependencies loaded up to CLASS_LOADED_BUT_NOT_VERIFIED

enum_flag_IsInitError = 0x0100,
enum_flag_IsStaticDataAllocated = 0x0200,
enum_flag_IsStaticDataAllocated = 0x0200, // When this is set, if the class can be marked as initialized without any further code execution it will be.
// unum_unused = 0x0400,
enum_flag_IsTlsIndexAllocated = 0x0800,
enum_flag_MayHaveOpenInterfaceInInterfaceMap = 0x1000,
Expand Down Expand Up @@ -447,9 +447,19 @@ struct MethodTableAuxiliaryData

inline BOOL IsClassInited() const
{
LIMITED_METHOD_DAC_CONTRACT;
return VolatileLoad(&m_dwFlags) & enum_flag_Initialized;
}

inline bool IsClassInitedOrPreinitedDecided(bool *initResult) const
{
LIMITED_METHOD_DAC_CONTRACT;

DWORD dwFlags = VolatileLoad(&m_dwFlags);
*initResult = m_dwFlags & enum_flag_Initialized;
return (dwFlags & (enum_flag_IsStaticDataAllocated|enum_flag_Initialized)) != 0;
}

#ifndef DACCESS_COMPILE
inline void SetClassInited()
{
Expand All @@ -465,10 +475,10 @@ struct MethodTableAuxiliaryData
}

#ifndef DACCESS_COMPILE
inline void SetIsStaticDataAllocated()
inline void SetIsStaticDataAllocated(bool markAsInitedToo)
{
LIMITED_METHOD_CONTRACT;
InterlockedOr((LONG*)&m_dwFlags, (LONG)enum_flag_IsStaticDataAllocated);
InterlockedOr((LONG*)&m_dwFlags, markAsInitedToo ? (LONG)(enum_flag_IsStaticDataAllocated|enum_flag_Initialized) : (LONG)enum_flag_IsStaticDataAllocated);
}
#endif

Expand Down Expand Up @@ -567,7 +577,7 @@ struct DynamicStaticsInfo
bool GetIsInitedAndNonGCStaticsPointerIfInited(PTR_BYTE *ptrResult) { TADDR staticsVal = VolatileLoadWithoutBarrier(&m_pNonGCStatics); *ptrResult = dac_cast<PTR_BYTE>(staticsVal); return !(staticsVal & ISCLASSNOTINITED); }

// This function sets the pointer portion of a statics pointer. It returns false if the statics value was already set.
bool InterlockedUpdateStaticsPointer(bool isGC, TADDR newVal)
bool InterlockedUpdateStaticsPointer(bool isGC, TADDR newVal, bool isClassInitedByUpdatingStaticPointer)
{
TADDR oldVal;
TADDR oldValFromInterlockedOp;
Expand All @@ -583,7 +593,14 @@ struct DynamicStaticsInfo
return false;
}

oldValFromInterlockedOp = InterlockedCompareExchangeT(pAddr, newVal | oldVal, oldVal);
if (isClassInitedByUpdatingStaticPointer)
{
oldValFromInterlockedOp = InterlockedCompareExchangeT(pAddr, newVal, oldVal);
}
else
{
oldValFromInterlockedOp = InterlockedCompareExchangeT(pAddr, newVal | oldVal, oldVal);
}
} while(oldValFromInterlockedOp != oldVal);
return true;
}
Expand Down Expand Up @@ -1042,18 +1059,31 @@ class MethodTable
#ifndef DACCESS_COMPILE
void SetClassInited()
{
GetAuxiliaryDataForWrite()->SetClassInited();
// This must be before setting the MethodTable level flag, as otherwise there is a race condition where
// the MethodTable flag is set, which would allows the JIT to generate a call to a helper which assumes
// the DynamicStaticInfo level flag is set.
// The other race in the other direction is not a concern, as it can only cause allows reads/write from the static
// fields, which are effectively inited in any case once we reach this point.
if (IsDynamicStatics())
{
GetDynamicStaticsInfo()->SetClassInited();
}
GetAuxiliaryDataForWrite()->SetClassInited();
}

void AttemptToPreinit();
private:
bool IsInitedIfStaticDataAllocated();
public:
// Is the MethodTable current initialized, and/or can the runtime initialize the MethodTable
// without running any user code. (This function may allocate memory, and may throw OutOfMemory)
bool IsClassInitedOrPreinited();
#endif

// Is the MethodTable current known to be initialized
// If you want to know if it is initialized and allocation/throwing is permitted, call IsClassInitedOrPreinited instead
BOOL IsClassInited()
{
LIMITED_METHOD_DAC_CONTRACT;
return GetAuxiliaryDataForWrite()->IsClassInited();
}

Expand Down
5 changes: 2 additions & 3 deletions src/coreclr/vm/prestub.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3545,9 +3545,8 @@ static PCODE getHelperForStaticBase(Module * pModule, CORCOMPILE_FIXUP_BLOB_KIND
{
STANDARD_VM_CONTRACT;

pMT->AttemptToPreinit();
bool GCStatic = (kind == ENCODE_STATIC_BASE_GC_HELPER || kind == ENCODE_THREAD_STATIC_BASE_GC_HELPER);
bool noCtor = pMT->IsClassInited();
bool noCtor = pMT->IsClassInitedOrPreinited();
bool threadStatic = (kind == ENCODE_THREAD_STATIC_BASE_NONGC_HELPER || kind == ENCODE_THREAD_STATIC_BASE_GC_HELPER);

CorInfoHelpFunc helper;
Expand Down Expand Up @@ -3916,7 +3915,7 @@ PCODE DynamicHelperFixup(TransitionBlock * pTransitionBlock, TADDR * pCell, DWOR
else
{
// Delay the creation of the helper until the type is initialized
if (pMT->IsClassInited())
if (pMT->IsClassInitedOrPreinited())
pHelper = getHelperForInitializedStatic(pModule, (CORCOMPILE_FIXUP_BLOB_KIND)kind, pMT, pFD);
}
}
Expand Down
Loading