diff --git a/src/coreclr/inc/CrstTypes.def b/src/coreclr/inc/CrstTypes.def index 2179a45cadf628..be59e705528223 100644 --- a/src/coreclr/inc/CrstTypes.def +++ b/src/coreclr/inc/CrstTypes.def @@ -577,3 +577,7 @@ End Crst PgoData AcquiredBefore LoaderHeap End + +Crst StaticBoxInit + AcquiredBefore LoaderHeap FrozenObjectHeap AssemblyLoader +End diff --git a/src/coreclr/inc/crsttypes_generated.h b/src/coreclr/inc/crsttypes_generated.h index 366e60cf9d29f2..75964724c7e51d 100644 --- a/src/coreclr/inc/crsttypes_generated.h +++ b/src/coreclr/inc/crsttypes_generated.h @@ -112,28 +112,29 @@ enum CrstType CrstSingleUseLock = 94, CrstSpecialStatics = 95, CrstStackSampler = 96, - CrstStressLog = 97, - CrstStubCache = 98, - CrstStubDispatchCache = 99, - CrstStubUnwindInfoHeapSegments = 100, - CrstSyncBlockCache = 101, - CrstSyncHashLock = 102, - CrstSystemBaseDomain = 103, - CrstSystemDomain = 104, - CrstSystemDomainDelayedUnloadList = 105, - CrstThreadIdDispenser = 106, - CrstThreadStore = 107, - CrstTieredCompilation = 108, - CrstTypeEquivalenceMap = 109, - CrstTypeIDMap = 110, - CrstUMEntryThunkCache = 111, - CrstUMEntryThunkFreeListLock = 112, - CrstUniqueStack = 113, - CrstUnresolvedClassLock = 114, - CrstUnwindInfoTableLock = 115, - CrstVSDIndirectionCellLock = 116, - CrstWrapperTemplate = 117, - kNumberOfCrstTypes = 118 + CrstStaticBoxInit = 97, + CrstStressLog = 98, + CrstStubCache = 99, + CrstStubDispatchCache = 100, + CrstStubUnwindInfoHeapSegments = 101, + CrstSyncBlockCache = 102, + CrstSyncHashLock = 103, + CrstSystemBaseDomain = 104, + CrstSystemDomain = 105, + CrstSystemDomainDelayedUnloadList = 106, + CrstThreadIdDispenser = 107, + CrstThreadStore = 108, + CrstTieredCompilation = 109, + CrstTypeEquivalenceMap = 110, + CrstTypeIDMap = 111, + CrstUMEntryThunkCache = 112, + CrstUMEntryThunkFreeListLock = 113, + CrstUniqueStack = 114, + CrstUnresolvedClassLock = 115, + CrstUnwindInfoTableLock = 116, + CrstVSDIndirectionCellLock = 117, + CrstWrapperTemplate = 118, + kNumberOfCrstTypes = 119 }; #endif // __CRST_TYPES_INCLUDED @@ -241,6 +242,7 @@ int g_rgCrstLevelMap[] = 5, // CrstSingleUseLock 0, // CrstSpecialStatics 0, // CrstStackSampler + 13, // CrstStaticBoxInit -1, // CrstStressLog 5, // CrstStubCache 0, // CrstStubDispatchCache @@ -364,6 +366,7 @@ LPCSTR g_rgCrstNameMap[] = "CrstSingleUseLock", "CrstSpecialStatics", "CrstStackSampler", + "CrstStaticBoxInit", "CrstStressLog", "CrstStubCache", "CrstStubDispatchCache", diff --git a/src/coreclr/vm/appdomain.cpp b/src/coreclr/vm/appdomain.cpp index 74c74041a49d6a..1397c1673d0bae 100644 --- a/src/coreclr/vm/appdomain.cpp +++ b/src/coreclr/vm/appdomain.cpp @@ -637,6 +637,7 @@ void BaseDomain::Init() m_NativeTypeLoadLock.Init(CrstInteropData, CrstFlags(CRST_REENTRANCY), TRUE); m_crstLoaderAllocatorReferences.Init(CrstLoaderAllocatorReferences); + m_crstStaticBoxInitLock.Init(CrstStaticBoxInit); // Has to switch thread to GC_NOTRIGGER while being held (see code:BaseDomain#AssemblyListLock) m_crstAssemblyList.Init(CrstAssemblyList, CrstFlags( CRST_GC_NOTRIGGER_WHEN_TAKEN | CRST_DEBUGGER_THREAD | CRST_TAKEN_DURING_SHUTDOWN)); diff --git a/src/coreclr/vm/appdomain.hpp b/src/coreclr/vm/appdomain.hpp index 18b4d4fd3f562b..1296adbed0d59c 100644 --- a/src/coreclr/vm/appdomain.hpp +++ b/src/coreclr/vm/appdomain.hpp @@ -1088,6 +1088,12 @@ class BaseDomain return &m_crstLoaderAllocatorReferences; } + CrstExplicitInit* GetStaticBoxInitLock() + { + LIMITED_METHOD_CONTRACT; + return &m_crstStaticBoxInitLock; + } + static CrstStatic* GetMethodTableExposedClassObjectLock() { LIMITED_METHOD_CONTRACT; @@ -1112,6 +1118,7 @@ class BaseDomain CrstExplicitInit m_DomainLocalBlockCrst; // Used to protect the reference lists in the collectible loader allocators attached to this appdomain CrstExplicitInit m_crstLoaderAllocatorReferences; + CrstExplicitInit m_crstStaticBoxInitLock; //#AssemblyListLock // Used to protect the assembly list. Taken also by GC or debugger thread, therefore we have to avoid diff --git a/src/coreclr/vm/frozenobjectheap.cpp b/src/coreclr/vm/frozenobjectheap.cpp index a1ed1c8b46dc44..8fe40c3cc37b61 100644 --- a/src/coreclr/vm/frozenobjectheap.cpp +++ b/src/coreclr/vm/frozenobjectheap.cpp @@ -37,6 +37,10 @@ Object* FrozenObjectHeapManager::TryAllocateObject(PTR_MethodTable type, size_t _ASSERT(type != nullptr); _ASSERT(FOH_COMMIT_SIZE >= MIN_OBJECT_SIZE); +#ifdef FEATURE_64BIT_ALIGNMENT + _ASSERT(!type->RequiresAlign8()); +#endif + // NOTE: objectSize is expected be the full size including header _ASSERT(objectSize >= MIN_OBJECT_SIZE); diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 7c675d1a38957d..1b1fceb334b88c 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -1560,8 +1560,32 @@ void CEEInfo::getFieldInfo (CORINFO_RESOLVED_TOKEN * pResolvedToken, GCX_COOP(); + _ASSERT(!pFieldMT->Collectible()); + // Field address is expected to be pinned so we don't need to protect it from GC here pResult->fieldLookup.addr = pField->GetStaticAddressHandle((void*)pField->GetBase()); pResult->fieldLookup.accessType = IAT_VALUE; + if (fieldFlags & CORINFO_FLG_FIELD_STATIC_IN_HEAP) + { + Object* frozenObj = VolatileLoad((Object**)pResult->fieldLookup.addr); + + if (frozenObj == nullptr) + { + // Boxed static is not yet set, allocate it + pFieldMT->AllocateRegularStaticBox(pField, (Object**)pResult->fieldLookup.addr); + frozenObj = VolatileLoad((Object**)pResult->fieldLookup.addr); + } + + _ASSERT(frozenObj != nullptr); + + // ContainsPointers here is unnecessary but it's cheaper than IsInFrozenSegment + // for structs containing gc handles + if (!frozenObj->GetMethodTable()->ContainsPointers() && + GCHeapUtilities::GetGCHeap()->IsInFrozenSegment(frozenObj)) + { + pResult->fieldLookup.addr = frozenObj->GetData(); + fieldFlags &= ~CORINFO_FLG_FIELD_STATIC_IN_HEAP; + } + } } } diff --git a/src/coreclr/vm/methodtable.cpp b/src/coreclr/vm/methodtable.cpp index aee002516bda24..0c97fe40642376 100644 --- a/src/coreclr/vm/methodtable.cpp +++ b/src/coreclr/vm/methodtable.cpp @@ -58,6 +58,7 @@ #include "array.h" #include "castcache.h" #include "dynamicinterfacecastable.h" +#include "frozenobjectheap.h" #ifdef FEATURE_INTERPRETER #include "interpreter.h" @@ -3495,13 +3496,7 @@ void MethodTable::AllocateRegularStaticBoxes() if (!pField->IsSpecialStatic() && pField->IsByValue()) { - TypeHandle th = pField->GetFieldTypeHandleThrowing(); - MethodTable* pFieldMT = th.GetMethodTable(); - - LOG((LF_CLASSLOADER, LL_INFO10000, "\tInstantiating static of type %s\n", pFieldMT->GetDebugClassName())); - OBJECTREF obj = AllocateStaticBox(pFieldMT, HasFixedAddressVTStatics()); - - SetObjectReference( (OBJECTREF*)(pStaticBase + pField->GetOffset()), obj); + AllocateRegularStaticBox(pField, (Object**)(pStaticBase + pField->GetOffset())); } pField++; @@ -3510,14 +3505,47 @@ void MethodTable::AllocateRegularStaticBoxes() GCPROTECT_END(); } +void MethodTable::AllocateRegularStaticBox(FieldDesc* pField, Object** boxedStaticHandle) +{ + CONTRACTL + { + THROWS; + GC_TRIGGERS; + MODE_COOPERATIVE; + CONTRACTL_END; + } + _ASSERT(pField->IsStatic() && !pField->IsSpecialStatic() && pField->IsByValue()); + + // Static fields are not pinned in collectible types so we need to protect the address + GCPROTECT_BEGININTERIOR(boxedStaticHandle); + if (VolatileLoad(boxedStaticHandle) == nullptr) + { + // Grab field's type handle before we enter lock + MethodTable* pFieldMT = pField->GetFieldTypeHandleThrowing().GetMethodTable(); + bool hasFixedAddr = HasFixedAddressVTStatics(); + + // Taking a lock since we might come here from multiple threads/places + CrstHolder crst(GetAppDomain()->GetStaticBoxInitLock()); + + // double-checked locking + if (VolatileLoad(boxedStaticHandle) == nullptr) + { + LOG((LF_CLASSLOADER, LL_INFO10000, "\tInstantiating static of type %s\n", pFieldMT->GetDebugClassName())); + OBJECTREF obj = AllocateStaticBox(pFieldMT, hasFixedAddr, NULL, false); + SetObjectReference((OBJECTREF*)(boxedStaticHandle), obj); + } + } + GCPROTECT_END(); +} + //========================================================================================== -OBJECTREF MethodTable::AllocateStaticBox(MethodTable* pFieldMT, BOOL fPinned, OBJECTHANDLE* pHandle) +OBJECTREF MethodTable::AllocateStaticBox(MethodTable* pFieldMT, BOOL fPinned, OBJECTHANDLE* pHandle, bool canBeFrozen) { CONTRACTL { THROWS; GC_TRIGGERS; - MODE_ANY; + MODE_COOPERATIVE; CONTRACTL_END; } @@ -3526,7 +3554,22 @@ OBJECTREF MethodTable::AllocateStaticBox(MethodTable* pFieldMT, BOOL fPinned, OB // Activate any dependent modules if necessary pFieldMT->EnsureInstanceActive(); - OBJECTREF obj = AllocateObject(pFieldMT); + OBJECTREF obj = NULL; + if (canBeFrozen) + { + // In case if we don't plan to collect this handle we may try to allocate it on FOH + _ASSERT(!pFieldMT->ContainsPointers()); + _ASSERT(pHandle == nullptr); + FrozenObjectHeapManager* foh = SystemDomain::GetFrozenObjectHeapManager(); + obj = ObjectToOBJECTREF(foh->TryAllocateObject(pFieldMT, pFieldMT->GetBaseSize())); + // obj can be null in case if struct is huge (>64kb) + if (obj != NULL) + { + return obj; + } + } + + obj = AllocateObject(pFieldMT); // Pin the object if necessary if (fPinned) diff --git a/src/coreclr/vm/methodtable.h b/src/coreclr/vm/methodtable.h index a378ec69ad5d06..2a12d9aee125c2 100644 --- a/src/coreclr/vm/methodtable.h +++ b/src/coreclr/vm/methodtable.h @@ -831,7 +831,8 @@ class MethodTable // instantiations of the superclass or interfaces e.g. System.Int32 : IComparable void AllocateRegularStaticBoxes(); - static OBJECTREF AllocateStaticBox(MethodTable* pFieldMT, BOOL fPinned, OBJECTHANDLE* pHandle = 0); + void AllocateRegularStaticBox(FieldDesc* pField, Object** boxedStaticHandle); + static OBJECTREF AllocateStaticBox(MethodTable* pFieldMT, BOOL fPinned, OBJECTHANDLE* pHandle = 0, bool canBeFrozen = false); void CheckRestore(); diff --git a/src/tests/baseservices/compilerservices/FixedAddressValueType/FixedAddressValueType.cs b/src/tests/baseservices/compilerservices/FixedAddressValueType/FixedAddressValueType.cs index a8a61132b1972e..56667c70cd2921 100644 --- a/src/tests/baseservices/compilerservices/FixedAddressValueType/FixedAddressValueType.cs +++ b/src/tests/baseservices/compilerservices/FixedAddressValueType/FixedAddressValueType.cs @@ -1,59 +1,50 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // + using System; using System.Runtime.CompilerServices; -public struct Age { - public int years; - public int months; -} - -public class FreeClass +public struct Age { - public static Age FreeAge; - - public static unsafe IntPtr AddressOfFreeAge() - { - fixed (Age* pointer = &FreeAge) - { return (IntPtr) pointer; } - } + public int years; + public int months; } public class FixedClass { - [FixedAddressValueType] - public static Age FixedAge; - - public static unsafe IntPtr AddressOfFixedAge() - { - fixed (Age* pointer = &FixedAge) - { return (IntPtr) pointer; } - } + [FixedAddressValueType] + public static Age FixedAge; + + public static unsafe IntPtr AddressOfFixedAge() + { + fixed (Age* pointer = &FixedAge) + { + return (IntPtr)pointer; + } + } } public class Example { - public static int Main() - { - // Get addresses of static Age fields. - IntPtr freePtr1 = FreeClass.AddressOfFreeAge(); - - IntPtr fixedPtr1 = FixedClass.AddressOfFixedAge(); - - // Garbage collection. - GC.Collect(3, GCCollectionMode.Forced, true, true); - GC.WaitForPendingFinalizers(); - - // Get addresses of static Age fields after garbage collection. - IntPtr freePtr2 = FreeClass.AddressOfFreeAge(); - IntPtr fixedPtr2 = FixedClass.AddressOfFixedAge(); - - if(freePtr1 != freePtr2 && fixedPtr1 == fixedPtr2) - { - return 100; - } - - return -1; - } + public static int Main() + { + for (int i = 0; i < 1000; i++) + { + IntPtr fixedPtr1 = FixedClass.AddressOfFixedAge(); + + // Garbage collection. + GC.Collect(3, GCCollectionMode.Forced, true, true); + GC.WaitForPendingFinalizers(); + + // Get addresses of static Age fields after garbage collection. + IntPtr fixedPtr2 = FixedClass.AddressOfFixedAge(); + + if (fixedPtr1 != fixedPtr2) + { + return -1; + } + } + return 100; + } }