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

Convert HELPER_METHOD_FRAME to QCalls (2/N) #95233

Merged
merged 16 commits into from
Nov 28, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public partial class Object
// so that other object may only call this method on themselves. It is intended to
// support the ICloneable interface.
[Intrinsic]
protected unsafe object MemberwiseClone()
protected internal unsafe object MemberwiseClone()
{
object clone = RuntimeHelpers.AllocateUninitializedClone(this);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,23 @@ public static partial class RuntimeHelpers
// cloned when you pass them around, and are always passed by value.
// Of course, reference types are not cloned.
//
[MethodImpl(MethodImplOptions.InternalCall)]
[return: NotNullIfNotNull(nameof(obj))]
public static extern object? GetObjectValue(object? obj);
public static unsafe object? GetObjectValue(object? obj)
{
if (obj == null)
return null;

MethodTable* pMT = GetMethodTable(obj);

if (!pMT->IsValueType || pMT->IsPrimitive)
return obj;

// Technically we could return boxed DateTimes and Decimals without
// copying them here, but VB realized that this would be a breaking change
// for their customers. So copy them.

return obj.MemberwiseClone();
}

// RunClassConstructor causes the class constructor for the given type to be triggered
// in the current domain. After this call returns, the class constructor is guaranteed to
Expand Down Expand Up @@ -487,6 +501,8 @@ internal unsafe struct MethodTable
private const uint enum_flag_Category_Mask = 0x000F0000;
private const uint enum_flag_Category_ValueType = 0x00040000;
private const uint enum_flag_Category_Nullable = 0x00050000;
private const uint enum_flag_Category_PrimitiveValueType = 0x00060000; // sub-category of ValueType, Enum or primitive value type
private const uint enum_flag_Category_TruePrimitive = 0x00070000; // sub-category of ValueType, Primitive (ELEMENT_TYPE_I, etc.)
private const uint enum_flag_Category_ValueType_Mask = 0x000C0000;
private const uint enum_flag_Category_Interface = 0x000C0000;
// Types that require non-trivial interface cast have this bit set in the category
Expand Down Expand Up @@ -565,6 +581,9 @@ public int MultiDimensionalArrayRank

public bool IsByRefLike => (Flags & (enum_flag_HasComponentSize | enum_flag_IsByRefLike)) == enum_flag_IsByRefLike;

// Warning! UNLIKE the similarly named Reflection api, this method also returns "true" for Enums.
public bool IsPrimitive => (Flags & enum_flag_Category_Mask) is enum_flag_Category_PrimitiveValueType or enum_flag_Category_TruePrimitive;

public bool HasInstantiation => (Flags & enum_flag_HasComponentSize) == 0 && (Flags & enum_flag_GenericsMask) != enum_flag_GenericsMask_NonGeneric;

public bool IsGenericTypeDefinition => (Flags & (enum_flag_HasComponentSize | enum_flag_GenericsMask)) == enum_flag_GenericsMask_TypicalInst;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System.Runtime
{
Expand All @@ -26,7 +27,7 @@ namespace System.Runtime
/// properties are instead thread-safe, and safe to use if <see cref="Dispose"/> is not concurrently invoked as well.
/// </para>
/// </remarks>
public struct DependentHandle : IDisposable
public partial struct DependentHandle : IDisposable
{
// =========================================================================================
// This struct collects all operations on native DependentHandles. The DependentHandle
Expand Down Expand Up @@ -62,16 +63,18 @@ public struct DependentHandle : IDisposable
/// <param name="dependent">The dependent object instance to associate with <paramref name="target"/>.</param>
public DependentHandle(object? target, object? dependent)
{
// no need to check for null result: InternalInitialize expected to throw OOM.
_handle = InternalInitialize(target, dependent);
IntPtr handle = InternalAlloc(target, dependent);
if (handle == 0)
handle = InternalAllocWithGCTransition(target, dependent);
_handle = handle;
}

/// <summary>
/// Gets a value indicating whether this instance was constructed with
/// <see cref="DependentHandle(object?, object?)"/> and has not yet been disposed.
/// </summary>
/// <remarks>This property is thread-safe.</remarks>
public readonly bool IsAllocated => (nint)_handle != 0;
public readonly bool IsAllocated => _handle != 0;

/// <summary>
/// Gets or sets the target object instance for the current handle. The target can only be set to a <see langword="null"/> value
Expand All @@ -87,7 +90,7 @@ readonly get
{
IntPtr handle = _handle;

if ((nint)handle == 0)
if (handle == 0)
{
ThrowHelper.ThrowInvalidOperationException();
}
Expand All @@ -98,7 +101,7 @@ readonly get
{
IntPtr handle = _handle;

if ((nint)handle == 0 || value is not null)
if (handle == 0 || value is not null)
{
ThrowHelper.ThrowInvalidOperationException();
}
Expand All @@ -124,7 +127,7 @@ readonly get
{
IntPtr handle = _handle;

if ((nint)handle == 0)
if (handle == 0)
{
ThrowHelper.ThrowInvalidOperationException();
}
Expand All @@ -135,7 +138,7 @@ readonly get
{
IntPtr handle = _handle;

if ((nint)handle == 0)
if (handle == 0)
{
ThrowHelper.ThrowInvalidOperationException();
}
Expand All @@ -160,7 +163,7 @@ public readonly (object? Target, object? Dependent) TargetAndDependent
{
IntPtr handle = _handle;

if ((nint)handle == 0)
if (handle == 0)
{
ThrowHelper.ThrowInvalidOperationException();
}
Expand Down Expand Up @@ -222,16 +225,26 @@ public void Dispose()
// (if not already there) and frees the handle if needed.
IntPtr handle = _handle;

if ((nint)handle != 0)
if (handle != 0)
{
_handle = IntPtr.Zero;
_handle = 0;

InternalFree(handle);
if (!InternalFree(handle))
{
InternalFreeWithGCTransition(handle);
}
}
}

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern IntPtr InternalInitialize(object? target, object? dependent);
private static extern IntPtr InternalAlloc(object? target, object? dependent);

[MethodImpl(MethodImplOptions.NoInlining)]
private static IntPtr InternalAllocWithGCTransition(object? target, object? dependent)
=> _InternalAllocWithGCTransition(ObjectHandleOnStack.Create(ref target), ObjectHandleOnStack.Create(ref dependent));

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "DependentHandle_InternalAllocWithGCTransition")]
private static partial IntPtr _InternalAllocWithGCTransition(ObjectHandleOnStack target, ObjectHandleOnStack dependent);

#if DEBUG
[MethodImpl(MethodImplOptions.InternalCall)]
Expand All @@ -258,6 +271,13 @@ public void Dispose()
private static extern void InternalSetTargetToNull(IntPtr dependentHandle);

[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void InternalFree(IntPtr dependentHandle);
private static extern bool InternalFree(IntPtr dependentHandle);

[MethodImpl(MethodImplOptions.NoInlining)]
private static void InternalFreeWithGCTransition(IntPtr dependentHandle)
=> _InternalFreeWithGCTransition(dependentHandle);

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "DependentHandle_InternalFreeWithGCTransition")]
private static partial void _InternalFreeWithGCTransition(IntPtr dependentHandle);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,39 @@ namespace System.Runtime.InteropServices
{
public partial struct GCHandle
{
internal static IntPtr InternalAlloc(object? value, GCHandleType type)
{
IntPtr handle = _InternalAlloc(value, type);
if (handle == 0)
handle = InternalAllocWithGCTransition(value, type);
return handle;
}

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern IntPtr InternalAlloc(object? value, GCHandleType type);
private static extern IntPtr _InternalAlloc(object? value, GCHandleType type);

[MethodImpl(MethodImplOptions.NoInlining)]
private static IntPtr InternalAllocWithGCTransition(object? value, GCHandleType type)
=> _InternalAllocWithGCTransition(ObjectHandleOnStack.Create(ref value), type);

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCHandle_InternalAllocWithGCTransition")]
private static partial IntPtr _InternalAllocWithGCTransition(ObjectHandleOnStack value, GCHandleType type);

internal static void InternalFree(IntPtr handle)
{
if (!_InternalFree(handle))
InternalFreeWithGCTransition(handle);
}

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern void InternalFree(IntPtr handle);
private static extern bool _InternalFree(IntPtr handle);

[MethodImpl(MethodImplOptions.NoInlining)]
private static void InternalFreeWithGCTransition(IntPtr dependentHandle)
=> _InternalFreeWithGCTransition(dependentHandle);

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "GCHandle_InternalFreeWithGCTransition")]
private static partial void _InternalFreeWithGCTransition(IntPtr dependentHandle);

#if DEBUG
// The runtime performs additional checks in debug builds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ public static partial class Marshal
internal static Guid IID_IUnknown = new Guid(0, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
#endif //FEATURE_COMINTEROP

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern int SizeOfHelper(Type t, bool throwIfNotMarshalable);
internal static int SizeOfHelper(RuntimeType t, [MarshalAs(UnmanagedType.Bool)] bool throwIfNotMarshalable)
=> SizeOfHelper(new QCallTypeHandle(ref t), throwIfNotMarshalable);

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MarshalNative_SizeOfHelper")]
private static partial int SizeOfHelper(QCallTypeHandle t, [MarshalAs(UnmanagedType.Bool)] bool throwIfNotMarshalable);

[UnconditionalSuppressMessage("ReflectionAnalysis", "IL2070:UnrecognizedReflectionPattern",
Justification = "Trimming doesn't affect types eligible for marshalling. Different exception for invalid inputs doesn't matter.")]
Expand Down Expand Up @@ -234,24 +237,95 @@ private static void PrelinkCore(MethodInfo m)
/// true, this routine will call DestroyStructure() first.
/// </summary>
[RequiresDynamicCode("Marshalling code for the object might not be available. Use the StructureToPtr<T> overload instead.")]
[MethodImpl(MethodImplOptions.InternalCall)]
[EditorBrowsable(EditorBrowsableState.Never)]
public static extern void StructureToPtr(object structure, IntPtr ptr, bool fDeleteOld);
public static unsafe void StructureToPtr(object structure, IntPtr ptr, bool fDeleteOld)
{
ArgumentNullException.ThrowIfNull(ptr);
ArgumentNullException.ThrowIfNull(structure);

MethodTable* pMT = RuntimeHelpers.GetMethodTable(structure);

if (pMT->HasInstantiation)
throw new ArgumentException(SR.Argument_NeedNonGenericObject, nameof(structure));

delegate*<ref byte, byte*, int, ref CleanupWorkListElement, void> structMarshalStub;
nuint size;
if (!TryGetStructMarshalStub((IntPtr)pMT, &structMarshalStub, &size))
throw new ArgumentException(SR.Argument_MustHaveLayoutOrBeBlittable, nameof(structure));

if (structMarshalStub != null)
{
if (fDeleteOld)
{
structMarshalStub(ref structure.GetRawData(), (byte*)ptr, MarshalOperation.Cleanup, ref Unsafe.NullRef<CleanupWorkListElement>());
}

structMarshalStub(ref structure.GetRawData(), (byte*)ptr, MarshalOperation.Marshal, ref Unsafe.NullRef<CleanupWorkListElement>());
}
else
{
Buffer.Memmove(ref *(byte*)ptr, ref structure.GetRawData(), size);
}
}

/// <summary>
/// Helper function to copy a pointer into a preallocated structure.
/// </summary>
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern void PtrToStructureHelper(IntPtr ptr, object structure, bool allowValueClasses);
private static unsafe void PtrToStructureHelper(IntPtr ptr, object structure, bool allowValueClasses)
{
MethodTable* pMT = RuntimeHelpers.GetMethodTable(structure);

if (!allowValueClasses && pMT->IsValueType)
throw new ArgumentException(SR.Argument_StructMustNotBeValueClass, nameof(structure));

delegate*<ref byte, byte*, int, ref CleanupWorkListElement, void> structMarshalStub;
nuint size;
if (!TryGetStructMarshalStub((IntPtr)pMT, &structMarshalStub, &size))
throw new ArgumentException(SR.Argument_MustHaveLayoutOrBeBlittable, nameof(structure));

if (structMarshalStub != null)
{
structMarshalStub(ref structure.GetRawData(), (byte*)ptr, MarshalOperation.Unmarshal, ref Unsafe.NullRef<CleanupWorkListElement>());
}
else
{
Buffer.Memmove(ref structure.GetRawData(), ref *(byte*)ptr, size);
}
}

/// <summary>
/// Frees all substructures pointed to by the native memory block.
/// "structuretype" is used to provide layout information.
/// nameof(structuretype) is used to provide layout information.
/// </summary>
[RequiresDynamicCode("Marshalling code for the object might not be available. Use the DestroyStructure<T> overload instead.")]
[MethodImpl(MethodImplOptions.InternalCall)]
[EditorBrowsable(EditorBrowsableState.Never)]
public static extern void DestroyStructure(IntPtr ptr, Type structuretype);
public static unsafe void DestroyStructure(IntPtr ptr, Type structuretype)
{
ArgumentNullException.ThrowIfNull(ptr);
ArgumentNullException.ThrowIfNull(structuretype);

if (structuretype is not RuntimeType rt)
throw new ArgumentException(SR.Argument_MustBeRuntimeType, nameof(structuretype));

if (rt.IsGenericType)
throw new ArgumentException(SR.Argument_NeedNonGenericType, nameof(structuretype));

delegate*<ref byte, byte*, int, ref CleanupWorkListElement, void> structMarshalStub;
nuint size;
if (!TryGetStructMarshalStub(rt.GetUnderlyingNativeHandle(), &structMarshalStub, &size))
throw new ArgumentException(SR.Argument_MustHaveLayoutOrBeBlittable, nameof(structuretype));

GC.KeepAlive(rt);

if (structMarshalStub != null)
{
structMarshalStub(ref Unsafe.NullRef<byte>(), (byte*)ptr, MarshalOperation.Cleanup, ref Unsafe.NullRef<CleanupWorkListElement>());
}
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "MarshalNative_TryGetStructMarshalStub")]
[return: MarshalAs(UnmanagedType.Bool)]
private static unsafe partial bool TryGetStructMarshalStub(IntPtr th, delegate*<ref byte, byte*, int, ref CleanupWorkListElement, void>* structMarshalStub, nuint* size);

// Note: Callers are required to keep obj alive
internal static unsafe bool IsPinnable(object? obj)
Expand Down
Loading
Loading