// loading it entirely.
//CacheFriendAssemblyInfo();
+#ifndef CROSSGEN_COMPILE
+ if (IsCollectible())
+ {
+ COUNT_T size;
+ BYTE *start = (BYTE*)m_pManifest->GetFile()->GetLoadedImageContents(&size);
+ if (start != NULL)
+ {
+ GCX_COOP();
+ LoaderAllocator::AssociateMemoryWithLoaderAllocator(start, start + size, m_pLoaderAllocator);
+ }
+ }
+#endif
+
{
CANNOTTHROWCOMPLUSEXCEPTION();
FAULT_FORBID();
if (pContent != NULL)
memcpy(pvBlob, pContent, length);
+ if (pReflectionModule->IsCollectible())
+ {
+ GCX_COOP();
+ LoaderAllocator::AssociateMemoryWithLoaderAllocator((BYTE*)pvBlob, ((BYTE*)pvBlob) + length, pReflectionModule->GetLoaderAllocator());
+ }
+
// set FieldRVA into metadata. Note that this is not final RVA in the image if save to disk. We will do another round of fix up upon save.
IfFailThrow( pRCW->GetEmitter()->SetFieldRVA(tkField, dwRVA) );
STRESS_LOG1(LF_CLASSLOADER, LL_INFO100, "Begin LoaderAllocator::Destroy for loader allocator %p\n", reinterpret_cast<void *>(static_cast<PTR_LoaderAllocator>(pLoaderAllocator)));
LoaderAllocatorID *pID = pLoaderAllocator->Id();
+ {
+ GCX_COOP();
+ LoaderAllocator::RemoveMemoryToLoaderAllocatorAssociation(pLoaderAllocator);
+ }
+
// This will probably change for shared code unloading
_ASSERTE(pID->GetType() == LAT_Assembly);
return m_pUMEntryThunkCache;
}
+/* static */
+void LoaderAllocator::RemoveMemoryToLoaderAllocatorAssociation(LoaderAllocator* pLoaderAllocator)
+{
+ CONTRACTL {
+ THROWS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ GlobalLoaderAllocator* pGlobalAllocator = (GlobalLoaderAllocator*)SystemDomain::GetGlobalLoaderAllocator();
+ pGlobalAllocator->m_memoryAssociations.RemoveRanges(pLoaderAllocator);
+}
+
+/* static */
+void LoaderAllocator::AssociateMemoryWithLoaderAllocator(BYTE *start, const BYTE *end, LoaderAllocator* pLoaderAllocator)
+{
+ CONTRACTL {
+ THROWS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ GlobalLoaderAllocator* pGlobalAllocator = (GlobalLoaderAllocator*)SystemDomain::GetGlobalLoaderAllocator();
+ pGlobalAllocator->m_memoryAssociations.AddRange(start, end, pLoaderAllocator);
+}
+
+/* static */
+PTR_LoaderAllocator LoaderAllocator::GetAssociatedLoaderAllocator_Unsafe(TADDR ptr)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ GlobalLoaderAllocator* pGlobalAllocator = (GlobalLoaderAllocator*)SystemDomain::GetGlobalLoaderAllocator();
+ LoaderAllocator* pLoaderAllocator;
+ if (pGlobalAllocator->m_memoryAssociations.IsInRangeWorker_Unlocked(ptr, reinterpret_cast<TADDR *>(&pLoaderAllocator)))
+ {
+ return pLoaderAllocator;
+ }
+ return NULL;
+}
+
#endif // !CROSSGEN_COMPILE
#ifdef FEATURE_COMINTEROP
#include "methoddescbackpatchinfo.h"
#include "crossloaderallocatorhash.h"
#include "onstackreplacement.h"
+#include "lockedrangelist.h"
#define VPTRU_LoaderAllocator 0x3200
virtual LoaderAllocatorID* Id() =0;
BOOL IsCollectible() { WRAPPER_NO_CONTRACT; return m_IsCollectible; }
+ // This function may only be called while the runtime is suspended
+ // As it does not lock around access to a RangeList
+ static PTR_LoaderAllocator GetAssociatedLoaderAllocator_Unsafe(TADDR ptr);
+
+ static void AssociateMemoryWithLoaderAllocator(BYTE *start, const BYTE *end, LoaderAllocator* pLoaderAllocator);
+ static void RemoveMemoryToLoaderAllocatorAssociation(LoaderAllocator* pLoaderAllocator);
+
#ifdef DACCESS_COMPILE
void EnumMemoryRegions(CLRDataEnumMemoryFlags flags);
#endif
class GlobalLoaderAllocator : public LoaderAllocator
{
+ friend class LoaderAllocator;
VPTR_VTABLE_CLASS(GlobalLoaderAllocator, LoaderAllocator)
VPTR_UNIQUE(VPTRU_LoaderAllocator+1)
DAC_ALIGNAS(LoaderAllocator) // Align the first member to the alignment of the base class
BYTE m_ExecutableHeapInstance[sizeof(LoaderHeap)];
+ // Associate memory regions with loader allocator objects
+ LockedRangeList m_memoryAssociations;
+
protected:
LoaderAllocatorID m_Id;
typedef VPTR(AssemblyLoaderAllocator) PTR_AssemblyLoaderAllocator;
-
#include "loaderallocator.inl"
#endif // __LoaderAllocator_h__
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+#ifndef __LockedRangeList_h__
+#define __LockedRangeList_h__
+
+// -------------------------------------------------------
+// This just wraps the RangeList methods in a read or
+// write lock depending on the operation.
+// -------------------------------------------------------
+
+class LockedRangeList : public RangeList
+{
+ public:
+ VPTR_VTABLE_CLASS(LockedRangeList, RangeList)
+
+ LockedRangeList() : RangeList(), m_RangeListRWLock(COOPERATIVE_OR_PREEMPTIVE, LOCK_TYPE_DEFAULT)
+ {
+ LIMITED_METHOD_CONTRACT;
+ }
+
+ ~LockedRangeList()
+ {
+ LIMITED_METHOD_CONTRACT;
+ }
+
+ BOOL IsInRangeWorker_Unlocked(TADDR address, TADDR *pID = NULL)
+ {
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+ return RangeList::IsInRangeWorker(address, pID);
+ }
+
+ protected:
+
+ virtual BOOL AddRangeWorker(const BYTE *start, const BYTE *end, void *id)
+ {
+ WRAPPER_NO_CONTRACT;
+ SimpleWriteLockHolder lh(&m_RangeListRWLock);
+ return RangeList::AddRangeWorker(start,end,id);
+ }
+
+ virtual void RemoveRangesWorker(void *id, const BYTE *start = NULL, const BYTE *end = NULL)
+ {
+ WRAPPER_NO_CONTRACT;
+ SimpleWriteLockHolder lh(&m_RangeListRWLock);
+ RangeList::RemoveRangesWorker(id,start,end);
+ }
+
+ virtual BOOL IsInRangeWorker(TADDR address, TADDR *pID = NULL)
+ {
+ WRAPPER_NO_CONTRACT;
+ SUPPORTS_DAC;
+ SimpleReadLockHolder lh(&m_RangeListRWLock);
+ return RangeList::IsInRangeWorker(address, pID);
+ }
+
+ SimpleRWLock m_RangeListRWLock;
+};
+
+#endif // __LockedRangeList_h__
\ No newline at end of file
return;
}
+#ifndef CROSSGEN_COMPILE
+ if (sc->promotion)
+ {
+ LoaderAllocator*pLoaderAllocator = LoaderAllocator::GetAssociatedLoaderAllocator_Unsafe(PTR_TO_TADDR(*ppObj));
+ if (pLoaderAllocator != NULL)
+ {
+ GcReportLoaderAllocator(fn, sc, pLoaderAllocator);
+ }
+ }
+#endif // CROSSGEN_COMPILE
#endif // !defined(DACCESS_COMPILE)
(*fn) (ppObj, sc, flags);
#define __stubmgr_h__
#include "simplerwlock.hpp"
+#include "lockedrangelist.h"
// When 'TraceStub' returns, it gives the address of where the 'target' is for a stub'
// TraceType indicates what this 'target' is
#endif // !CROSSGEN_COMPILE
};
-// -------------------------------------------------------
-// This just wraps the RangeList methods in a read or
-// write lock depending on the operation.
-// -------------------------------------------------------
-
-class LockedRangeList : public RangeList
-{
- public:
- VPTR_VTABLE_CLASS(LockedRangeList, RangeList)
-
- LockedRangeList() : RangeList(), m_RangeListRWLock(COOPERATIVE_OR_PREEMPTIVE, LOCK_TYPE_DEFAULT)
- {
- LIMITED_METHOD_CONTRACT;
- }
-
- ~LockedRangeList()
- {
- LIMITED_METHOD_CONTRACT;
- }
-
- protected:
-
- virtual BOOL AddRangeWorker(const BYTE *start, const BYTE *end, void *id)
- {
- WRAPPER_NO_CONTRACT;
- SimpleWriteLockHolder lh(&m_RangeListRWLock);
- return RangeList::AddRangeWorker(start,end,id);
- }
-
- virtual void RemoveRangesWorker(void *id, const BYTE *start = NULL, const BYTE *end = NULL)
- {
- WRAPPER_NO_CONTRACT;
- SimpleWriteLockHolder lh(&m_RangeListRWLock);
- RangeList::RemoveRangesWorker(id,start,end);
- }
-
- virtual BOOL IsInRangeWorker(TADDR address, TADDR *pID = NULL)
- {
- WRAPPER_NO_CONTRACT;
- SUPPORTS_DAC;
- SimpleReadLockHolder lh(&m_RangeListRWLock);
- return RangeList::IsInRangeWorker(address, pID);
- }
-
- SimpleRWLock m_RangeListRWLock;
-};
-
#ifndef CROSSGEN_COMPILE
//-----------------------------------------------------------
<!-- Known failures for mono runtime on *all* architectures/operating systems -->
<ItemGroup Condition="'$(RuntimeFlavor)' == 'mono'" >
+ <ExcludeList Include="$(XunitTestBinBase)/Loader/CollectibleAssemblies/ByRefLocals/**">
+ <Issue>https://github.com/dotnet/runtime/issues/40394</Issue>
+ </ExcludeList>
<ExcludeList Include="$(XunitTestBinBase)/Interop/PInvoke/Miscellaneous/CopyCtor/**">
<Issue>Handling for Copy constructors isn't present in mono interop</Issue>
</ExcludeList>
--- /dev/null
+// 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.IO;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.Loader;
+
+class Program
+{
+ static int Main(string[] args)
+ {
+ var holdResult = HoldAssembliesAliveThroughByRefFields(out GCHandle gch1, out GCHandle gch2);
+ if (holdResult != 100)
+ return holdResult;
+
+ // At this point, nothing should keep the collectible assembly alive
+ // Loop for a bit forcing the GC to run, and then it should be freed
+ for (int i = 0; i < 10; i++)
+ {
+ GC.Collect(2);
+ GC.WaitForPendingFinalizers();
+ }
+
+ if (gch1.Target != null)
+ {
+ return 3;
+ }
+ if (gch2.Target != null)
+ {
+ return 4;
+ }
+
+ return 100;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ static int HoldAssembliesAliveThroughByRefFields(out GCHandle gch1, out GCHandle gch2)
+ {
+ // ThreadStatic lifetime check. Here we don't require the actual assembly to remain loaded, but we do require the field to remain accessible
+ var spanThreadStatic = LoadAssemblyThreadStatic(out GCHandle gchThreadStatic);
+ GC.Collect(2);
+ GC.WaitForPendingFinalizers();
+ GC.Collect(2);
+ GC.WaitForPendingFinalizers();
+ GC.Collect(2);
+ GC.WaitForPendingFinalizers();
+
+ var span1 = LoadAssembly(out gch1);
+ GC.Collect(2);
+ GC.WaitForPendingFinalizers();
+ GC.Collect(2);
+ GC.WaitForPendingFinalizers();
+ GC.Collect(2);
+ GC.WaitForPendingFinalizers();
+ var span2 = CreateAssemblyDynamically(out gch2);
+ for (int i = 0; i < 10; i++)
+ {
+ Console.WriteLine(span1[0]);
+ Console.WriteLine(span2[0]);
+ Console.WriteLine(spanThreadStatic[0]);
+ GC.Collect(2);
+ GC.WaitForPendingFinalizers();
+ if (gch1.Target == null)
+ {
+ return 1;
+ }
+ if (gch2.Target == null)
+ {
+ return 2;
+ }
+ if (spanThreadStatic[0] != 7)
+ {
+ Console.WriteLine($"spanThreadStatic[0] = {spanThreadStatic[0]}");
+ return 5;
+ }
+ }
+
+ return 100;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ReadOnlySpan<byte> LoadAssembly(out GCHandle gchToAssembly)
+ {
+ var alc = new AssemblyLoadContext("test", isCollectible: true);
+ var a = alc.LoadFromAssemblyPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Unloaded.dll"));
+ gchToAssembly = GCHandle.Alloc(a, GCHandleType.WeakTrackResurrection);
+
+ var spanAccessor = (IReturnSpan)Activator.CreateInstance(a.GetType("SpanAccessor"));
+
+ alc.Unload();
+
+ return spanAccessor.GetSpan();
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ReadOnlySpan<byte> LoadAssemblyThreadStatic(out GCHandle gchToAssembly)
+ {
+ var alc = new AssemblyLoadContext("test", isCollectible: true);
+ var a = alc.LoadFromAssemblyPath(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Unloaded.dll"));
+ gchToAssembly = GCHandle.Alloc(a, GCHandleType.WeakTrackResurrection);
+
+ var spanAccessor = (IReturnSpan)Activator.CreateInstance(a.GetType("ThreadStaticSpanAccessor"));
+
+ alc.Unload();
+
+ return spanAccessor.GetSpan();
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ReadOnlySpan<byte> CreateAssemblyDynamically(out GCHandle gchToAssembly)
+ {
+ AssemblyBuilder ab =
+ AssemblyBuilder.DefineDynamicAssembly(
+ new AssemblyName("tempAssembly"),
+ AssemblyBuilderAccess.RunAndCollect);
+ ModuleBuilder modb = ab.DefineDynamicModule("tempAssembly.dll");
+
+ var byRefAccessField = modb.DefineInitializedData("RawBytes", new byte[] {1,2,3,4,5}, FieldAttributes.Public | FieldAttributes.Static);
+ modb.CreateGlobalFunctions();
+
+ TypeBuilder tb = modb.DefineType("GetSpanType", TypeAttributes.Class, typeof(object), new Type[]{typeof(IReturnSpan)});
+ var mb = tb.DefineMethod("GetSpan", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.Virtual, typeof(ReadOnlySpan<byte>), new Type[]{});
+ ILGenerator myMethodIL = mb.GetILGenerator();
+ myMethodIL.Emit(OpCodes.Ldsflda, byRefAccessField);
+ myMethodIL.Emit(OpCodes.Ldc_I4_4);
+ myMethodIL.Emit(OpCodes.Newobj, typeof(ReadOnlySpan<byte>).GetConstructor(new Type[]{typeof(void*), typeof(int)}));
+ myMethodIL.Emit(OpCodes.Ret);
+
+ var getSpanType = tb.CreateType();
+
+ gchToAssembly = GCHandle.Alloc(getSpanType, GCHandleType.WeakTrackResurrection);
+
+ var spanAccessor = (IReturnSpan)Activator.CreateInstance(getSpanType);
+
+ return spanAccessor.GetSpan();
+ }
+
+}
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <OutputType>Exe</OutputType>
+ <CLRTestKind>BuildAndRun</CLRTestKind>
+ <CLRTestPriority>0</CLRTestPriority>
+ </PropertyGroup>
+ <ItemGroup>
+ <ProjectReference Include="Unloaded.csproj" />
+ <ProjectReference Include="SpanAccessor.csproj" />
+ <Compile Include="ByRefLocals.cs" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+
+public interface IReturnSpan
+{
+ ReadOnlySpan<byte> GetSpan();
+}
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <OutputType>Library</OutputType>
+ <CLRTestKind>BuildOnly</CLRTestKind>
+ <CLRTestPriority>0</CLRTestPriority>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="SpanAccessor.cs" />
+ </ItemGroup>
+</Project>
\ No newline at end of file
--- /dev/null
+// 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 class SpanAccessor : IReturnSpan
+{
+ public static ReadOnlySpan<byte> RawData => new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
+
+ public ReadOnlySpan<byte> GetSpan()
+ {
+ return RawData;
+ }
+}
+
+public class ThreadStaticSpanAccessor : IReturnSpan
+{
+ [ThreadStatic]
+ public static byte ThreadStaticByte = 7;
+
+ public unsafe ReadOnlySpan<byte> GetSpan()
+ {
+ return new ReadOnlySpan<byte>(Unsafe.AsPointer(ref ThreadStaticByte), 1);
+ }
+}
\ No newline at end of file
--- /dev/null
+<Project Sdk="Microsoft.NET.Sdk">
+ <PropertyGroup>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ <OutputType>Library</OutputType>
+ <CLRTestKind>BuildOnly</CLRTestKind>
+ <CLRTestPriority>0</CLRTestPriority>
+ </PropertyGroup>
+ <ItemGroup>
+ <ProjectReference Include="SpanAccessor.csproj" />
+ <Compile Include="Unloaded.cs" />
+ </ItemGroup>
+</Project>
\ No newline at end of file