Skip to content
Merged
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
40 changes: 38 additions & 2 deletions src/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1402,6 +1402,17 @@ internal static class MarshalOperation

internal static unsafe class StructureMarshaler<T> where T : notnull
{
// Blittable types have a no-op FreeCore (the [Intrinsic] C# body is used) and need no NativeMemory.Clear.
// Non-blittable types have a JIT-generated FreeCore stub and require NativeMemory.Clear after cleanup.
private static readonly bool s_isBlittable = InitIsBlittable();

private static bool InitIsBlittable()
{
RuntimeType type = (RuntimeType)typeof(T);
Marshal.HasLayout(new QCallTypeHandle(ref type), out bool isBlittable, out _);
return isBlittable;
}
Comment thread
tannergooding marked this conversation as resolved.
Comment thread
jkoritzinsky marked this conversation as resolved.

[Conditional("DEBUG")]
private static void Validate()
{
Expand Down Expand Up @@ -1457,7 +1468,10 @@ private static void FreeCore(ref T managed, byte* unmanaged, ref CleanupWorkList

public static void Free(ref T managed, byte* unmanaged, int nativeSize, ref CleanupWorkListElement? cleanupWorkList)
{
if (unmanaged != null)
// For blittable types, FreeCore is a no-op and there are no native sub-structures to free.
// Calling NativeMemory.Clear on a potentially invalid pointer (e.g., in DestroyStructure tests)
// would cause a fault, so we skip cleanup entirely for blittable types.
if (unmanaged != null && !s_isBlittable)
{
FreeCore(ref managed, unmanaged, ref cleanupWorkList);
NativeMemory.Clear(unmanaged, (nuint)nativeSize);
Expand Down Expand Up @@ -1519,6 +1533,9 @@ private static void BlittableFree(ref byte managed, byte* unmanaged, ref Cleanup
internal static delegate*<ref byte, byte*, ref CleanupWorkListElement?, void> ConvertToManaged => _convertToManaged;

internal static delegate*<ref byte, byte*, ref CleanupWorkListElement?, void> Free => _free;

// s_nativeSizeForBlittableTypes is non-zero for blittable types and zero for non-blittable types.
internal static bool IsBlittable => s_nativeSizeForBlittableTypes != 0;
}

private static void ConvertToUnmanagedCore(T managed, byte* unmanaged, ref CleanupWorkListElement? cleanupWorkList)
Expand Down Expand Up @@ -1598,9 +1615,28 @@ static void CallFree(T? managed, byte* unmanaged, ref CleanupWorkListElement? cl
}
}

private static bool GetIsBlittable()
{
try
{
return CallIsBlittable();
}
catch (TypeInitializationException ex)
{
ExceptionDispatchInfo.Capture(ex.InnerException ?? ex).Throw();
return false; // unreachable
}

[MethodImpl(MethodImplOptions.NoInlining)]
static bool CallIsBlittable() => Methods.IsBlittable;
}
Comment thread
jkoritzinsky marked this conversation as resolved.

public static void Free(T? managed, byte* unmanaged, int nativeSize, ref CleanupWorkListElement? cleanupWorkList)
{
if (unmanaged != null)
// For blittable types, FreeCore is a no-op and there are no native sub-structures to free.
// Calling NativeMemory.Clear on a potentially invalid pointer (e.g., in DestroyStructure tests)
// would cause a fault, so we skip cleanup entirely for blittable types.
if (unmanaged != null && !GetIsBlittable())
{
FreeCore(managed, unmanaged, ref cleanupWorkList);
NativeMemory.Clear(unmanaged, (nuint)nativeSize);
Expand Down
Loading