Skip to content

Commit

Permalink
Remove Helper Method Frames (HMF) from Reflection (dotnet#110211)
Browse files Browse the repository at this point in the history
Create general purpose RuntimeTypeHandle.InternalAlloc() and RuntimeTypeHandle.InternalAllocNoChecks().
Convert RuntimeMethodHandle::ReboxToNullable() to managed.
Convert RuntimeMethodHandle::ReboxFromNullable() to managed.
Convert RuntimeMethodHandle::InvokeMethod() to QCall.
  • Loading branch information
AaronRobinsonMSFT authored and mikelle-rogers committed Dec 4, 2024
1 parent 576b8a3 commit eae4f37
Show file tree
Hide file tree
Showing 14 changed files with 188 additions and 200 deletions.
17 changes: 2 additions & 15 deletions src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -434,23 +434,10 @@ private bool BindToMethodInfo(object? target, IRuntimeMethodInfo method, Runtime

private static MulticastDelegate InternalAlloc(RuntimeType type)
{
MulticastDelegate? d = null;
InternalAlloc(new QCallTypeHandle(ref type), ObjectHandleOnStack.Create(ref d));
return d!;
Debug.Assert(type.IsAssignableTo(typeof(MulticastDelegate)));
return Unsafe.As<MulticastDelegate>(RuntimeTypeHandle.InternalAlloc(type));
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_InternalAlloc")]
private static partial void InternalAlloc(QCallTypeHandle type, ObjectHandleOnStack d);

internal static MulticastDelegate InternalAllocLike(MulticastDelegate d)
{
InternalAllocLike(ObjectHandleOnStack.Create(ref d));
return d;
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_InternalAllocLike")]
private static partial void InternalAllocLike(ObjectHandleOnStack d);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static unsafe bool InternalEqualTypes(object a, object b)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ private static bool TrySetSlot(object?[] a, int index, object o)
private MulticastDelegate NewMulticastDelegate(object[] invocationList, int invocationCount, bool thisIsMultiCastAlready)
{
// First, allocate a new multicast delegate just like this one, i.e. same type as the this object
MulticastDelegate result = InternalAllocLike(this);
MulticastDelegate result = Unsafe.As<MulticastDelegate>(RuntimeTypeHandle.InternalAllocNoChecks((RuntimeType)GetType()));

// Performance optimization - if this already points to a true multicast delegate,
// copy _methodPtr and _methodPtrAux fields rather than calling into the EE to get them
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,32 @@ internal static void Unbox_Nullable(ref byte destPtr, MethodTable* typeMT, objec
}
}

[DebuggerHidden]
internal static object? ReboxFromNullable(MethodTable* srcMT, object src)
{
Debug.Assert(srcMT->IsNullable);

ref byte nullableData = ref src.GetRawData();

// If 'hasValue' is false, return null.
if (!Unsafe.As<byte, bool>(ref nullableData))
return null;

// Allocate a new instance of the T in Nullable<T>.
MethodTable* dstMT = srcMT->InstantiationArg0();
object dst = RuntimeTypeHandle.InternalAlloc(dstMT);

// Copy data from the Nullable<T>.
ref byte srcData = ref Unsafe.Add(ref nullableData, srcMT->NullableValueAddrOffset);
ref byte dstData = ref RuntimeHelpers.GetRawData(dst);
if (dstMT->ContainsGCPointers)
Buffer.BulkMoveWithWriteBarrier(ref dstData, ref srcData, dstMT->GetNumInstanceFieldBytesIfContainsGCPointers());
else
SpanHelpers.Memmove(ref dstData, ref srcData, dstMT->GetNumInstanceFieldBytes());

return dst;
}

[DebuggerHidden]
[MethodImpl(MethodImplOptions.NoInlining)]
private static ref byte Unbox_Helper(MethodTable* pMT1, object obj)
Expand Down
87 changes: 81 additions & 6 deletions src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,36 @@ private static partial void CreateInstanceForAnotherGenericParameter(
int cTypeHandles,
ObjectHandleOnStack instantiatedObject);

internal static unsafe object InternalAlloc(MethodTable* pMT)
{
object? result = null;
InternalAlloc(pMT, ObjectHandleOnStack.Create(ref result));
return result!;
}

internal static object InternalAlloc(RuntimeType type)
{
Debug.Assert(!type.GetNativeTypeHandle().IsTypeDesc);
object result = InternalAlloc(type.GetNativeTypeHandle().AsMethodTable());
GC.KeepAlive(type);
return result;
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeTypeHandle_InternalAlloc")]
private static unsafe partial void InternalAlloc(MethodTable* pMT, ObjectHandleOnStack result);

internal static object InternalAllocNoChecks(RuntimeType type)
{
Debug.Assert(!type.GetNativeTypeHandle().IsTypeDesc);
object? result = null;
InternalAllocNoChecks(type.GetNativeTypeHandle().AsMethodTable(), ObjectHandleOnStack.Create(ref result));
GC.KeepAlive(type);
return result!;
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeTypeHandle_InternalAllocNoChecks")]
private static unsafe partial void InternalAllocNoChecks(MethodTable* pMT, ObjectHandleOnStack result);

/// <summary>
/// Given a RuntimeType, returns information about how to activate it via calli
/// semantics. This method will ensure the type object is fully initialized within
Expand Down Expand Up @@ -1026,14 +1056,59 @@ internal static MdUtf8String GetUtf8Name(RuntimeMethodHandleInternal method)

[DebuggerStepThrough]
[DebuggerHidden]
[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object? InvokeMethod(object? target, void** arguments, Signature sig, bool isConstructor);
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeMethodHandle_InvokeMethod")]
private static partial void InvokeMethod(ObjectHandleOnStack target, void** arguments, ObjectHandleOnStack sig, Interop.BOOL isConstructor, ObjectHandleOnStack result);

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object? ReboxFromNullable(object? src);
[DebuggerStepThrough]
[DebuggerHidden]
internal static object? InvokeMethod(object? target, void** arguments, Signature sig, bool isConstructor)
{
object? result = null;
InvokeMethod(
ObjectHandleOnStack.Create(ref target),
arguments,
ObjectHandleOnStack.Create(ref sig),
isConstructor ? Interop.BOOL.TRUE : Interop.BOOL.FALSE,
ObjectHandleOnStack.Create(ref result));
return result;
}

[MethodImpl(MethodImplOptions.InternalCall)]
internal static extern object ReboxToNullable(object? src, RuntimeType destNullableType);
/// <summary>
/// For a true boxed Nullable{T}, re-box to a boxed {T} or null, otherwise just return the input.
/// </summary>
internal static object? ReboxFromNullable(object? src)
{
// If src is null or not NullableOfT, just return that state.
if (src is null)
{
return null;
}

MethodTable* pMT = RuntimeHelpers.GetMethodTable(src);
if (!pMT->IsNullable)
{
return src;
}

return CastHelpers.ReboxFromNullable(pMT, src);
}

/// <summary>
/// Convert a boxed value of {T} (which is either {T} or null) to a true boxed Nullable{T}.
/// </summary>
internal static object ReboxToNullable(object? src, RuntimeType destNullableType)
{
Debug.Assert(destNullableType.IsNullableOfT);
MethodTable* pMT = destNullableType.GetNativeTypeHandle().AsMethodTable();
object obj = RuntimeTypeHandle.InternalAlloc(pMT);
GC.KeepAlive(destNullableType); // The obj instance will keep the type alive.

CastHelpers.Unbox_Nullable(
ref obj.GetRawData(),
pMT,
src);
return obj;
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeMethodHandle_GetMethodInstantiation")]
private static partial void GetMethodInstantiation(RuntimeMethodHandleInternal method, ObjectHandleOnStack types, Interop.BOOL fAsRuntimeTypeArray);
Expand Down
30 changes: 0 additions & 30 deletions src/coreclr/vm/comdelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2024,36 +2024,6 @@ Stub* COMDelegate::GetInvokeMethodStub(EEImplMethodDesc* pMD)
}
}

extern "C" void QCALLTYPE Delegate_InternalAlloc(QCall::TypeHandle pType, QCall::ObjectHandleOnStack d)
{
QCALL_CONTRACT;

BEGIN_QCALL;

GCX_COOP();

_ASSERTE(pType.AsTypeHandle().AsMethodTable()->IsDelegate());

d.Set(pType.AsTypeHandle().AsMethodTable()->Allocate());

END_QCALL;
}

extern "C" void QCALLTYPE Delegate_InternalAllocLike(QCall::ObjectHandleOnStack d)
{
QCALL_CONTRACT;

BEGIN_QCALL;

GCX_COOP();

_ASSERTE(d.Get()->GetMethodTable()->IsDelegate());

d.Set(d.Get()->GetMethodTable()->AllocateNoChecks());

END_QCALL;
}

void COMDelegate::ThrowIfInvalidUnmanagedCallersOnlyUsage(MethodDesc* pMD)
{
CONTRACTL
Expand Down
4 changes: 0 additions & 4 deletions src/coreclr/vm/comdelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,6 @@ extern "C" BOOL QCALLTYPE Delegate_BindToMethodName(QCall::ObjectHandleOnStack d
extern "C" BOOL QCALLTYPE Delegate_BindToMethodInfo(QCall::ObjectHandleOnStack d, QCall::ObjectHandleOnStack target,
MethodDesc * method, QCall::TypeHandle pMethodType, DelegateBindingFlags flags);

extern "C" void QCALLTYPE Delegate_InternalAlloc(QCall::TypeHandle pType, QCall::ObjectHandleOnStack d);

extern "C" void QCALLTYPE Delegate_InternalAllocLike(QCall::ObjectHandleOnStack d);

extern "C" void QCALLTYPE Delegate_FindMethodHandle(QCall::ObjectHandleOnStack d, QCall::ObjectHandleOnStack retMethodInfo);

extern "C" BOOL QCALLTYPE Delegate_InternalEqualMethodHandles(QCall::ObjectHandleOnStack left, QCall::ObjectHandleOnStack right);
Expand Down
3 changes: 0 additions & 3 deletions src/coreclr/vm/ecalllist.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,6 @@ FCFuncStart(gSignatureNative)
FCFuncEnd()

FCFuncStart(gRuntimeMethodHandle)
FCFuncElement("InvokeMethod", RuntimeMethodHandle::InvokeMethod)
FCFuncElement("ReboxFromNullable", RuntimeMethodHandle::ReboxFromNullable)
FCFuncElement("ReboxToNullable", RuntimeMethodHandle::ReboxToNullable)
FCFuncElement("GetImplAttributes", RuntimeMethodHandle::GetImplAttributes)
FCFuncElement("GetAttributes", RuntimeMethodHandle::GetAttributes)
FCFuncElement("GetMethodTable", RuntimeMethodHandle::GetMethodTable)
Expand Down
15 changes: 7 additions & 8 deletions src/coreclr/vm/methodtable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3940,18 +3940,17 @@ void MethodTable::CheckRunClassInitAsIfConstructingThrowing()
THROWS;
GC_TRIGGERS;
MODE_ANY;
PRECONDITION(HasPreciseInitCctors());
}
CONTRACTL_END;
if (HasPreciseInitCctors())

MethodTable *pMTCur = this;
while (pMTCur != NULL)
{
MethodTable *pMTCur = this;
while (pMTCur != NULL)
{
if (!pMTCur->GetClass()->IsBeforeFieldInit())
pMTCur->CheckRunClassInitThrowing();
if (!pMTCur->GetClass()->IsBeforeFieldInit())
pMTCur->CheckRunClassInitThrowing();

pMTCur = pMTCur->GetParentMethodTable();
}
pMTCur = pMTCur->GetParentMethodTable();
}
}

Expand Down
7 changes: 3 additions & 4 deletions src/coreclr/vm/methodtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -2676,8 +2676,8 @@ class MethodTable
// This flavor of Allocate is more efficient, but can only be used
// if CheckInstanceActivated(), IsClassInited() are known to be true.
// A sufficient condition is that another instance of the exact same type already
// exists in the same appdomain. It's currently called only from Delegate.Combine
// via COMDelegate::InternalAllocLike.
// exists in the same ALC. It's currently called only from Delegate.Combine
// via RuntimeTypeHandle_InternalAllocNoChecks.
OBJECTREF AllocateNoChecks();

OBJECTREF Box(void* data);
Expand Down Expand Up @@ -3921,12 +3921,11 @@ public :
TADDR m_ElementTypeHnd;
};
public:
union
union
{
PTR_InterfaceInfo m_pInterfaceMap;
TADDR m_encodedNullableUnboxData; // Used for Nullable<T> to represent the offset to the value field, and the size of the value field
};


// VTable slots go here

Expand Down
3 changes: 1 addition & 2 deletions src/coreclr/vm/methodtable.inl
Original file line number Diff line number Diff line change
Expand Up @@ -1230,7 +1230,7 @@ inline OBJECTREF MethodTable::AllocateNoChecks()
}
CONTRACTL_END;

// we know an instance of this class already exists in the same appdomain
// We know an instance of this class already exists
// therefore, some checks become redundant.
// this currently only happens for Delegate.Combine

Expand All @@ -1239,7 +1239,6 @@ inline OBJECTREF MethodTable::AllocateNoChecks()
return AllocateObject(this);
}


#ifndef DACCESS_COMPILE
//==========================================================================================
// unbox src into dest, No checks are done
Expand Down
5 changes: 3 additions & 2 deletions src/coreclr/vm/qcallentrypoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ static const Entry s_QCall[] =
DllImportEntry(Delegate_GetMulticastInvokeSlow)
DllImportEntry(Delegate_AdjustTarget)
DllImportEntry(Delegate_Construct)
DllImportEntry(Delegate_InternalAlloc)
DllImportEntry(Delegate_InternalAllocLike)
DllImportEntry(Delegate_FindMethodHandle)
DllImportEntry(Delegate_InternalEqualMethodHandles)
DllImportEntry(Environment_Exit)
Expand Down Expand Up @@ -140,9 +138,12 @@ static const Entry s_QCall[] =
#endif // FEATURE_COMINTEROP
DllImportEntry(RuntimeTypeHandle_GetRuntimeTypeFromHandleSlow)
DllImportEntry(RuntimeTypeHandle_CreateInstanceForAnotherGenericParameter)
DllImportEntry(RuntimeTypeHandle_InternalAlloc)
DllImportEntry(RuntimeTypeHandle_InternalAllocNoChecks)
DllImportEntry(RuntimeTypeHandle_AllocateTypeAssociatedMemory)
DllImportEntry(RuntimeTypeHandle_RegisterCollectibleTypeDependency)
DllImportEntry(MethodBase_GetCurrentMethod)
DllImportEntry(RuntimeMethodHandle_InvokeMethod)
DllImportEntry(RuntimeMethodHandle_ConstructInstantiation)
DllImportEntry(RuntimeMethodHandle_GetFunctionPointer)
DllImportEntry(RuntimeMethodHandle_GetIsCollectible)
Expand Down
Loading

0 comments on commit eae4f37

Please sign in to comment.