Skip to content
Draft
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
286 changes: 286 additions & 0 deletions docs/design/features/runtime-delegate-type-factory.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\QCallExceptionStatus.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeAsyncTaskContinuation.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeHelpers.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeHelpers.DelegateTypeFactory.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\StaticsHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\VirtualDispatchHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\ControlledExecution.CoreCLR.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;

namespace System.Runtime.CompilerServices;

public static partial class RuntimeHelpers
{
private static partial class DelegateTypeFactory
{
private static readonly DelegateTypeCache s_nonCollectibleCache = new();
private static readonly ConditionalWeakTable<LoaderAllocator, DelegateTypeCache> s_collectibleCaches = new();

[ErrorHandler(typeof(QCallExceptionStatusMarshaller), ErrorLocation.HiddenLastParameter)]
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_GetTypeLoaderAllocator")]
private static unsafe partial void GetTypeLoaderAllocator(nint* signature, int signatureLength, ObjectHandleOnStack loaderAllocator);

[ErrorHandler(typeof(QCallExceptionStatusMarshaller), ErrorLocation.HiddenLastParameter)]
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Delegate_CreateType")]
private static unsafe partial void CreateType(nint* signature, int signatureLength, ObjectHandleOnStack assembly, ObjectHandleOnStack result);

internal static unsafe Type GetCustomDelegateType(Type[] typeArgs)
{
RuntimeType[] signature = GetSignature(typeArgs, out bool isCollectible);

if (!isCollectible)
{
return s_nonCollectibleCache.GetDelegateType(signature);
}

nint[] handles = GetTypeHandles(signature);
LoaderAllocator? loaderAllocator = null;
fixed (nint* signaturePtr = handles)
{
GetTypeLoaderAllocator(signaturePtr, signature.Length, ObjectHandleOnStack.Create(ref loaderAllocator));
}

DelegateTypeCache cache = s_collectibleCaches.GetValue(loaderAllocator!, static _ => new DelegateTypeCache());
Type result = cache.GetDelegateType(signature, handles);
GC.KeepAlive(signature);
return result;
}

private static nint[] GetTypeHandles(RuntimeType[] signature)
{
nint[] handles = new nint[signature.Length];
for (int i = 0; i < handles.Length; i++)
{
handles[i] = signature[i].TypeHandle.Value;
}
return handles;
}

private sealed class DelegateTypeCache
{
private readonly Dictionary<RuntimeType[], Type> _types = new(SignatureComparer.Instance);
private RuntimeAssembly? _assembly;

internal unsafe Type GetDelegateType(RuntimeType[] signature, nint[]? handles = null)
{
lock (_types)
{
if (_types.TryGetValue(signature, out Type? result))
{
return result;
}

handles ??= GetTypeHandles(signature);
RuntimeAssembly? assembly = _assembly;
try
{
fixed (nint* signaturePtr = handles)
{
CreateType(signaturePtr, signature.Length, ObjectHandleOnStack.Create(ref assembly), ObjectHandleOnStack.Create(ref result));
}
}
finally
{
_assembly = assembly;
GC.KeepAlive(signature);
}

_types.Add(signature, result!);
return result!;
}
}
}

}
}
3 changes: 2 additions & 1 deletion src/coreclr/inc/utilcode.h
Original file line number Diff line number Diff line change
Expand Up @@ -2936,7 +2936,8 @@ HRESULT validateTokenSig(
PCCOR_SIGNATURE pbSig, // [IN] Signature.
ULONG cbSig, // [IN] Size in bytes of the signature.
DWORD dwFlags, // [IN] Method flags.
IMDInternalImport* pImport); // [IN] Internal MD Import interface ptr
IMDInternalImport* pImport, // [IN] Internal MD Import interface ptr
bool allowInternalTypes = false); // Only for runtime-constructed signatures

//*****************************************************************************
// The registry keys and values that contain the information regarding
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ namespace System.Runtime.CompilerServices
{
public static partial class RuntimeHelpers
{
private static partial class DelegateTypeFactory
{
internal static Type GetCustomDelegateType(Type[] typeArgs) =>
throw new PlatformNotSupportedException(SR.PlatformNotSupported_ReflectionEmit);
}

[Intrinsic]
public static void InitializeArray(Array array, RuntimeFieldHandle fldHandle)
{
Expand Down
31 changes: 19 additions & 12 deletions src/coreclr/utilcode/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1462,7 +1462,8 @@ HRESULT validateOneArg(
SigParser *pSig,
ULONG *pulNSentinels, // [IN/OUT] Number of sentinels
IMDInternalImport* pImport, // [IN] Internal MD Import interface ptr
BOOL bNoVoidAllowed) // [IN] Flag indicating whether "void" is disallowed for this arg
BOOL bNoVoidAllowed, // [IN] Flag indicating whether "void" is disallowed for this arg
bool allowInternalTypes)

{
CONTRACTL
Expand Down Expand Up @@ -1532,15 +1533,20 @@ HRESULT validateOneArg(
case ELEMENT_TYPE_U:
case ELEMENT_TYPE_I:
break;
case ELEMENT_TYPE_INTERNAL:
if (!allowInternalTypes)
IfFailGo(VLDTR_E_SIG_BADELTYPE);
IfFailGo(pSig->GetPointer(nullptr));
break;
case ELEMENT_TYPE_PTR:
// Validate the referenced type.
if(FAILED(hr = validateOneArg(tk, pSig, pulNSentinels, pImport, FALSE))) IfFailGo(hr);
if(FAILED(hr = validateOneArg(tk, pSig, pulNSentinels, pImport, FALSE, allowInternalTypes))) IfFailGo(hr);
break;
case ELEMENT_TYPE_BYREF:
case ELEMENT_TYPE_PINNED:
case ELEMENT_TYPE_SZARRAY:
// Validate the referenced type.
if(FAILED(hr = validateOneArg(tk, pSig, pulNSentinels, pImport, TRUE))) IfFailGo(hr);
if(FAILED(hr = validateOneArg(tk, pSig, pulNSentinels, pImport, TRUE, allowInternalTypes))) IfFailGo(hr);
break;
case ELEMENT_TYPE_CMOD_OPT:
case ELEMENT_TYPE_CMOD_REQD:
Expand Down Expand Up @@ -1583,18 +1589,18 @@ HRESULT validateOneArg(

// FNPTR signature must follow the rules of MethodDef
// Validate and consume return type.
IfFailGo(validateOneArg(mdtMethodDef, pSig, NULL, pImport, FALSE));
IfFailGo(validateOneArg(mdtMethodDef, pSig, NULL, pImport, FALSE, allowInternalTypes));

// Validate and consume the arguments.
while(ulArgCnt--)
{
IfFailGo(validateOneArg(mdtMethodDef, pSig, NULL, pImport, TRUE));
IfFailGo(validateOneArg(mdtMethodDef, pSig, NULL, pImport, TRUE, allowInternalTypes));
}
break;

case ELEMENT_TYPE_ARRAY:
// Validate and consume the base type.
IfFailGo(validateOneArg(tk, pSig, pulNSentinels, pImport, TRUE));
IfFailGo(validateOneArg(tk, pSig, pulNSentinels, pImport, TRUE, allowInternalTypes));

// Validate that the rank is present.
if (FAILED(pSig->GetData(&ulRank)))
Expand Down Expand Up @@ -1651,7 +1657,7 @@ HRESULT validateOneArg(

case ELEMENT_TYPE_GENERICINST:
// Validate the generic type.
IfFailGo(validateOneArg(tk, pSig, pulNSentinels, pImport, TRUE));
IfFailGo(validateOneArg(tk, pSig, pulNSentinels, pImport, TRUE, allowInternalTypes));

// Validate that parameter count is present.
if (FAILED(pSig->GetData(&ulArgCnt)))
Expand All @@ -1664,7 +1670,7 @@ HRESULT validateOneArg(
// Validate and consume the parameters.
while(ulArgCnt--)
{
IfFailGo(validateOneArg(tk, pSig, NULL, pImport, TRUE));
IfFailGo(validateOneArg(tk, pSig, NULL, pImport, TRUE, allowInternalTypes));
}
break;

Expand All @@ -1690,7 +1696,8 @@ HRESULT validateTokenSig(
PCCOR_SIGNATURE pbSig, // [IN] Signature.
ULONG cbSig, // [IN] Size in bytes of the signature.
DWORD dwFlags, // [IN] Method flags.
IMDInternalImport* pImport) // [IN] Internal MD Import interface ptr
IMDInternalImport* pImport, // [IN] Internal MD Import interface ptr
bool allowInternalTypes)
{
CONTRACTL
{
Expand Down Expand Up @@ -1734,7 +1741,7 @@ HRESULT validateTokenSig(
FALLTHROUGH;

case mdtMemberRef:
if(i == IMAGE_CEE_CS_CALLCONV_FIELD) return validateOneArg(tk, &sig, NULL, pImport, TRUE);
if(i == IMAGE_CEE_CS_CALLCONV_FIELD) return validateOneArg(tk, &sig, NULL, pImport, TRUE, allowInternalTypes);

// EXPLICITTHIS and native call convs are for stand-alone sigs only (for calli)
if(((i != IMAGE_CEE_CS_CALLCONV_DEFAULT)&&( i != IMAGE_CEE_CS_CALLCONV_VARARG))
Expand All @@ -1754,7 +1761,7 @@ HRESULT validateTokenSig(

case mdtFieldDef:
if(i != IMAGE_CEE_CS_CALLCONV_FIELD) return VLDTR_E_MD_BADCALLINGCONV;
return validateOneArg(tk, &sig, NULL, pImport, TRUE);
return validateOneArg(tk, &sig, NULL, pImport, TRUE, allowInternalTypes);
}
// Is there any sig left for arguments?

Expand All @@ -1777,7 +1784,7 @@ HRESULT validateTokenSig(
// (at this moment ulArgCount = num.args+1, ulArgIx = (standalone sig. ? 1 :0); )
for(; ulArgIx < ulArgCount; ulArgIx++)
{
if(FAILED(hr = validateOneArg(tk, &sig, &ulNSentinels, pImport, (ulArgIx!=0)))) return hr;
if(FAILED(hr = validateOneArg(tk, &sig, &ulNSentinels, pImport, (ulArgIx!=0), allowInternalTypes))) return hr;
}

// <TODO>@todo: we allow junk to be at the end of the signature (we may not consume it all)
Expand Down
5 changes: 3 additions & 2 deletions src/coreclr/vm/assembly.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ Assembly * Assembly::Create(
return pAssembly;
} // Assembly::Create

Assembly *Assembly::CreateDynamic(AssemblyBinder* pBinder, NativeAssemblyNameParts* pAssemblyNameParts, INT32 hashAlgorithm, INT32 access, LOADERALLOCATORREF* pKeepAlive)
Assembly *Assembly::CreateDynamic(AssemblyBinder* pBinder, NativeAssemblyNameParts* pAssemblyNameParts, INT32 hashAlgorithm, INT32 access, LOADERALLOCATORREF* pKeepAlive, LoaderAllocator* pExistingLoaderAllocator)
{
// WARNING: not backout clean
CONTRACTL
Expand Down Expand Up @@ -470,7 +470,8 @@ Assembly *Assembly::CreateDynamic(AssemblyBinder* pBinder, NativeAssemblyNamePar
}
else
{
pLoaderAllocator = pBinderLoaderAllocator == nullptr ? pDomain->GetLoaderAllocator() : pBinderLoaderAllocator;
pLoaderAllocator = pExistingLoaderAllocator != nullptr ? pExistingLoaderAllocator :
(pBinderLoaderAllocator == nullptr ? pDomain->GetLoaderAllocator() : pBinderLoaderAllocator);
}

if (!createdNewAssemblyLoaderAllocator)
Expand Down
2 changes: 1 addition & 1 deletion src/coreclr/vm/assembly.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ class Assembly

bool IsSystem() { WRAPPER_NO_CONTRACT; return m_pPEAssembly->IsSystem(); }

static Assembly* CreateDynamic(AssemblyBinder* pBinder, NativeAssemblyNameParts* pAssemblyNameParts, INT32 hashAlgorithm, INT32 access, LOADERALLOCATORREF* pKeepAlive);
static Assembly* CreateDynamic(AssemblyBinder* pBinder, NativeAssemblyNameParts* pAssemblyNameParts, INT32 hashAlgorithm, INT32 access, LOADERALLOCATORREF* pKeepAlive, LoaderAllocator* pExistingLoaderAllocator = nullptr);

MethodDesc *GetEntryPoint();

Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/vm/ceeload.h
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,7 @@ class Module : public ModuleBase
PROF_DISABLE_INLINING = 0x00000100, // indicates if Profiler disabled JIT Inlining event mask was set when loaded

IS_ENC_CAPABLE = 0x00000200, // Cached result of IsEditAndContinueCapable() at Module creation
IS_RUNTIME_DELEGATE_MODULE = 0x00010000, // Signatures are constructed by the runtime and may contain TypeHandles

//
// Note: The values below must match the ones defined in
Expand Down Expand Up @@ -933,6 +934,13 @@ class Module : public ModuleBase
#endif

BOOL IsReflectionEmit() const { WRAPPER_NO_CONTRACT; SUPPORTS_DAC; return (m_dwTransientFlags & IS_REFLECTION_EMIT) != 0; }
BOOL IsRuntimeDelegateModule() const { LIMITED_METHOD_CONTRACT; return (m_dwTransientFlags & IS_RUNTIME_DELEGATE_MODULE) != 0; }
void SetRuntimeDelegateModule()
{
LIMITED_METHOD_CONTRACT;
_ASSERTE(IsReflectionEmit());
SetTransientFlagInterlocked(IS_RUNTIME_DELEGATE_MODULE);
}
bool IsSystem() { WRAPPER_NO_CONTRACT; SUPPORTS_DAC; return m_pPEAssembly->IsSystem(); }

virtual BOOL IsEditAndContinueCapable() const { return FALSE; }
Expand Down
Loading
Loading