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

Activator.CreateInstance supports ByRefLike types #102636

Merged
14 changes: 9 additions & 5 deletions src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -270,24 +270,27 @@ internal static void GetActivationInfo(
RuntimeType rt,
out delegate*<void*, object> pfnAllocator,
out void* vAllocatorFirstArg,
out delegate*<object, void> pfnCtor,
out delegate*<object, void> pfnRefCtor,
out delegate*<ref byte, void> pfnValueCtor,
out bool ctorIsPublic)
{
Debug.Assert(rt != null);

delegate*<void*, object> pfnAllocatorTemp = default;
void* vAllocatorFirstArgTemp = default;
delegate*<object, void> pfnCtorTemp = default;
delegate*<object, void> pfnRefCtorTemp = default;
delegate*<ref byte, void> pfnValueCtorTemp = default;
Interop.BOOL fCtorIsPublicTemp = default;

GetActivationInfo(
ObjectHandleOnStack.Create(ref rt),
&pfnAllocatorTemp, &vAllocatorFirstArgTemp,
&pfnCtorTemp, &fCtorIsPublicTemp);
&pfnRefCtorTemp, &pfnValueCtorTemp, &fCtorIsPublicTemp);

pfnAllocator = pfnAllocatorTemp;
vAllocatorFirstArg = vAllocatorFirstArgTemp;
pfnCtor = pfnCtorTemp;
pfnRefCtor = pfnRefCtorTemp;
pfnValueCtor = pfnValueCtorTemp;
ctorIsPublic = fCtorIsPublicTemp != Interop.BOOL.FALSE;
}

Expand All @@ -296,7 +299,8 @@ private static partial void GetActivationInfo(
ObjectHandleOnStack pRuntimeType,
delegate*<void*, object>* ppfnAllocator,
void** pvAllocatorFirstArg,
delegate*<object, void>* ppfnCtor,
delegate*<object, void>* ppfnRefCtor,
delegate*<ref byte, void>* ppfnValueCtor,
Interop.BOOL* pfCtorIsPublic);

#if FEATURE_COMINTEROP
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ private sealed unsafe class ActivatorCache
private readonly void* _allocatorFirstArg;

// The managed calli to the parameterless ctor, taking "this" (as object) as its first argument.
private readonly delegate*<object?, void> _pfnCtor;
private readonly delegate*<object?, void> _pfnRefCtor;
private readonly delegate*<ref byte, void> _pfnValueCtor;
private readonly bool _ctorIsPublic;

private CreateUninitializedCache? _createUninitializedCache;
Expand All @@ -48,7 +49,7 @@ internal ActivatorCache(RuntimeType rt)
{
RuntimeTypeHandle.GetActivationInfo(rt,
out _pfnAllocator!, out _allocatorFirstArg,
out _pfnCtor!, out _ctorIsPublic);
out _pfnRefCtor!, out _pfnValueCtor!, out _ctorIsPublic);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -87,14 +88,29 @@ internal ActivatorCache(RuntimeType rt)
// would have thrown an exception if 'rt' were a normal reference type
// without a ctor.

if (_pfnCtor == null)
if (_pfnRefCtor == null)
{
static void CtorNoopStub(object? uninitializedObject) { }
_pfnCtor = &CtorNoopStub; // we use null singleton pattern if no ctor call is necessary
static void RefCtorNoopStub(object? uninitializedObject) { }
_pfnRefCtor = &RefCtorNoopStub; // we use null singleton pattern if no ctor call is necessary

Debug.Assert(_ctorIsPublic); // implicit parameterless ctor is always considered public
}

if (rt.IsValueType)
{
if (_pfnValueCtor == null)
{
static void ValueRefCtorNoopStub(ref byte uninitializedObject) { }
_pfnValueCtor = &ValueRefCtorNoopStub; // we use null singleton pattern if no ctor call is necessary

Debug.Assert(_ctorIsPublic); // implicit parameterless ctor is always considered public
}
}
else
{
Debug.Assert(_pfnValueCtor == null); // Non-value types shouldn't have a value constructor.
}

// We don't need to worry about invoking cctors here. The runtime will figure it
// out for us when the instance ctor is called. For value types, because we're
// creating a boxed default(T), the static cctor is called when *any* instance
Expand All @@ -120,7 +136,16 @@ static void CtorNoopStub(object? uninitializedObject) { }
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void CallConstructor(object? uninitializedObject) => _pfnCtor(uninitializedObject);
internal void CallRefConstructor(object? uninitializedObject) => _pfnRefCtor(uninitializedObject);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void CallValueConstructor(ref byte uninitializedObject)
{
#if DEBUG
Debug.Assert(_originalRuntimeType.IsValueType);
#endif
_pfnValueCtor(ref uninitializedObject);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal CreateUninitializedCache GetCreateUninitializedCache(RuntimeType rt)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3932,7 +3932,7 @@ internal object GetUninitializedObject()
object? obj = cache.CreateUninitializedObject(this);
try
{
cache.CallConstructor(obj);
cache.CallRefConstructor(obj);
}
catch (Exception e) when (wrapExceptions)
{
Expand Down Expand Up @@ -3961,7 +3961,7 @@ internal object GetUninitializedObject()
object? obj = cache.CreateUninitializedObject(this);
try
{
cache.CallConstructor(obj);
cache.CallRefConstructor(obj);
}
catch (Exception e)
{
Expand All @@ -3971,6 +3971,32 @@ internal object GetUninitializedObject()
return obj;
}

// Specialized version of the above for Activator.CreateInstance<T>()
[DebuggerStepThrough]
[DebuggerHidden]
internal void CallDefaultStructConstructor(ref byte data)
{
if (GenericCache is not ActivatorCache cache)
{
cache = new ActivatorCache(this);
GenericCache = cache;
}

if (!cache.CtorIsPublic)
{
throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, this));
}

try
{
cache.CallValueConstructor(ref data);
}
catch (Exception e)
{
throw new TargetInvocationException(e);
}
}

internal void InvalidateCachedNestedType() => Cache.InvalidateCachedNestedType();

internal bool IsGenericCOMObjectImpl() => RuntimeTypeHandle.IsComObject(this, true);
Expand Down
35 changes: 26 additions & 9 deletions src/coreclr/vm/reflectioninvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1694,7 +1694,8 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_GetActivationInfo(
QCall::ObjectHandleOnStack pRuntimeType,
PCODE* ppfnAllocator,
void** pvAllocatorFirstArg,
PCODE* ppfnCtor,
PCODE* ppfnRefCtor,
PCODE* ppfnValueCtor,
BOOL* pfCtorIsPublic
)
{
Expand All @@ -1703,11 +1704,13 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_GetActivationInfo(
QCALL_CHECK;
PRECONDITION(CheckPointer(ppfnAllocator));
PRECONDITION(CheckPointer(pvAllocatorFirstArg));
PRECONDITION(CheckPointer(ppfnCtor));
PRECONDITION(CheckPointer(ppfnRefCtor));
PRECONDITION(CheckPointer(ppfnValueCtor));
PRECONDITION(CheckPointer(pfCtorIsPublic));
PRECONDITION(*ppfnAllocator == NULL);
PRECONDITION(*pvAllocatorFirstArg == NULL);
PRECONDITION(*ppfnCtor == NULL);
PRECONDITION(*ppfnRefCtor == NULL);
PRECONDITION(*ppfnValueCtor == NULL);
PRECONDITION(*pfCtorIsPublic == FALSE);
}
CONTRACTL_END;
Expand Down Expand Up @@ -1761,7 +1764,8 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_GetActivationInfo(
// managed sig: ComClassFactory* -> object (via FCALL)
*ppfnAllocator = CoreLibBinder::GetMethod(METHOD__RT_TYPE_HANDLE__ALLOCATECOMOBJECT)->GetMultiCallableAddrOfCode();
*pvAllocatorFirstArg = pClassFactory;
*ppfnCtor = NULL; // no ctor call needed; activation handled entirely by the allocator
*ppfnRefCtor = NULL; // no ctor call needed; activation handled entirely by the allocator
*ppfnValueCtor = NULL; // no value ctor for reference type
*pfCtorIsPublic = TRUE; // no ctor call needed => assume 'public' equivalent
}
else
Expand All @@ -1771,7 +1775,8 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_GetActivationInfo(
// CreateInstance returns null given Nullable<T>
*ppfnAllocator = (PCODE)NULL;
*pvAllocatorFirstArg = NULL;
*ppfnCtor = (PCODE)NULL;
*ppfnRefCtor = (PCODE)NULL;
*ppfnValueCtor = (PCODE)NULL;
*pfCtorIsPublic = TRUE; // no ctor call needed => assume 'public' equivalent
}
else
Expand All @@ -1781,22 +1786,34 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_GetActivationInfo(
*ppfnAllocator = CEEJitInfo::getHelperFtnStatic(CEEInfo::getNewHelperStatic(pMT, &fHasSideEffectsUnused));
*pvAllocatorFirstArg = pMT;

BOOL isValueType = pMT->IsValueType();
if (pMT->HasDefaultConstructor())
{
// managed sig: object -> void
// for ctors on value types, lookup boxed entry point stub
MethodDesc* pMD = pMT->GetDefaultConstructor(pMT->IsValueType() /* forceBoxedEntryPoint */);
MethodDesc* pMD = pMT->GetDefaultConstructor(isValueType /* forceBoxedEntryPoint */);
_ASSERTE(pMD != NULL);

PCODE pCode = pMD->GetMultiCallableAddrOfCode();
_ASSERTE(pCode != (PCODE)NULL);

*ppfnCtor = pCode;
*ppfnRefCtor = pCode;
*pfCtorIsPublic = pMD->IsPublic();

// If we have a value type, get the non-boxing entry point too.
if (isValueType)
{
pMD = pMT->GetDefaultConstructor(FALSE /* forceBoxedEntryPoint */);
_ASSERTE(pMD != NULL);
pCode = pMD->GetMultiCallableAddrOfCode();
_ASSERTE(pCode != (PCODE)NULL);
*ppfnValueCtor = pCode;
}
}
else if (pMT->IsValueType())
else if (isValueType)
{
*ppfnCtor = (PCODE)NULL; // no ctor call needed; we're creating a boxed default(T)
*ppfnRefCtor = (PCODE)NULL; // no ctor call needed; we're creating a boxed default(T)
*ppfnValueCtor = (PCODE)NULL;
*pfCtorIsPublic = TRUE; // no ctor call needed => assume 'public' equivalent
}
else
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/runtimehandles.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_GetActivationInfo(
PCODE* ppfnAllocator,
void** pvAllocatorFirstArg,
PCODE* ppfnCtor,
PCODE* ppfnValueCtor,
BOOL* pfCtorIsPublic);
extern "C" void QCALLTYPE RuntimeTypeHandle_MakeByRef(QCall::TypeHandle pTypeHandle, QCall::ObjectHandleOnStack retType);
extern "C" void QCALLTYPE RuntimeTypeHandle_MakePointer(QCall::TypeHandle pTypeHandle, QCall::ObjectHandleOnStack retType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,27 @@ public static partial class Activator

[Intrinsic]
public static T CreateInstance<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>()
where T : allows ref struct
{
return (T)((RuntimeType)typeof(T)).CreateInstanceOfT()!;
var rtType = (RuntimeType)typeof(T);
if (!rtType.IsValueType)
{
object o = rtType.CreateInstanceOfT()!;

// Casting the above object to T is technically invalid because
// T can be ByRefLike (that is, ref struct). Roslyn blocks the
// cast this in function with a "CS0030: Cannot convert type 'object' to 'T'",
// which is correct. However, since we are doing the IsValueType
// check above, we know this code path will only be taken with
// reference types and therefore the below Unsafe.As<> is safe.
return Unsafe.As<object, T>(ref o);
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved
}
else
{
T t = default!;
rtType.CallDefaultStructConstructor(ref Unsafe.As<T, byte>(ref t));
return t;
}
}

private static T CreateDefaultInstance<T>() where T : struct => default;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ public static T As<T>(object? o) where T : class?
[NonVersionable]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ref TTo As<TFrom, TTo>(ref TFrom source)
where TFrom : allows ref struct
where TTo : allows ref struct
{
throw new PlatformNotSupportedException();

Expand Down
4 changes: 2 additions & 2 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public static partial class Activator
public static System.Runtime.Remoting.ObjectHandle? CreateInstanceFrom(string assemblyFile, string typeName, bool ignoreCase, System.Reflection.BindingFlags bindingAttr, System.Reflection.Binder? binder, object?[]? args, System.Globalization.CultureInfo? culture, object?[]? activationAttributes) { throw null; }
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Type and its constructor could be removed")]
public static System.Runtime.Remoting.ObjectHandle? CreateInstanceFrom(string assemblyFile, string typeName, object?[]? activationAttributes) { throw null; }
public static T CreateInstance<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>() { throw null; }
public static T CreateInstance<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembersAttribute(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]T>() where T : allows ref struct { throw null; }
}
public partial class AggregateException : System.Exception
{
Expand Down Expand Up @@ -13357,7 +13357,7 @@ public static partial class Unsafe
public static ref T AsRef<T>(scoped ref readonly T source) { throw null; }
[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("o")]
public static T? As<T>(object? o) where T : class? { throw null; }
public static ref TTo As<TFrom, TTo>(ref TFrom source) { throw null; }
public static ref TTo As<TFrom, TTo> (ref TFrom source) where TFrom : allows ref struct where TTo : allows ref struct { throw null; }
public static TTo BitCast<TFrom, TTo>(TFrom source) { throw null; }
public static System.IntPtr ByteOffset<T>([System.Diagnostics.CodeAnalysis.AllowNull] ref readonly T origin, [System.Diagnostics.CodeAnalysis.AllowNull] ref readonly T target) { throw null; }
[System.CLSCompliantAttribute(false)]
Expand Down
30 changes: 29 additions & 1 deletion src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ internal enum TypeNameFormatFlags
FormatFullInst
}

internal partial class RuntimeType
internal unsafe partial class RuntimeType
{
#region Definitions

Expand Down Expand Up @@ -1615,6 +1615,34 @@ private void CreateInstanceCheckThis()
return CreateInstanceMono(false, true);
}

// Specialized version of the above for Activator.CreateInstance<T>()
[DebuggerStepThroughAttribute]
[Diagnostics.DebuggerHidden]
internal void CallDefaultStructConstructor(ref byte value)
{
Debug.Assert(IsValueType);

RuntimeConstructorInfo? ctor = GetDefaultConstructor();
if (ctor == null)
return;
AaronRobinsonMSFT marked this conversation as resolved.
Show resolved Hide resolved

delegate*<ref byte, void> valueCtor = GetValueConstructor(ObjectHandleOnStack.Create(ref ctor));
if (valueCtor == null)
throw new ExecutionEngineException();

try
{
valueCtor(ref value);
}
catch (Exception e)
{
throw new TargetInvocationException(e);
}
}

[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern delegate*<ref byte, void> GetValueConstructor(ObjectHandleOnStack rtCtorInfo);

#endregion

private TypeCache? cache;
Expand Down
1 change: 1 addition & 0 deletions src/mono/mono/metadata/icall-def.h
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ HANDLES(RT_24, "GetNamespace", ves_icall_RuntimeType_GetNamespace, void, 2, (Mon
HANDLES(RT_13, "GetNestedTypes_native", ves_icall_RuntimeType_GetNestedTypes_native, GPtrArray_ptr, 4, (MonoQCallTypeHandle, char_ptr, guint32, guint32))
HANDLES(RT_14, "GetPacking", ves_icall_RuntimeType_GetPacking, void, 3, (MonoQCallTypeHandle, guint32_ref, guint32_ref))
HANDLES(RT_15, "GetPropertiesByName_native", ves_icall_RuntimeType_GetPropertiesByName_native, GPtrArray_ptr, 4, (MonoQCallTypeHandle, char_ptr, guint32, guint32))
HANDLES(RT_34, "GetValueConstructor", ves_icall_RuntimeType_GetValueConstructor, gpointer, 1, (MonoObjectHandleOnStack))
NOHANDLES(ICALL(RT_29, "IsUnmanagedFunctionPointerInternal", ves_icall_RuntimeType_IsUnmanagedFunctionPointerInternal))
HANDLES(RT_17, "MakeGenericType", ves_icall_RuntimeType_MakeGenericType, void, 3, (MonoReflectionType, MonoArray, MonoObjectHandleOnStack))
HANDLES(RT_19, "getFullName", ves_icall_System_RuntimeType_getFullName, void, 4, (MonoQCallTypeHandle, MonoObjectHandleOnStack, MonoBoolean, MonoBoolean))
Expand Down
11 changes: 11 additions & 0 deletions src/mono/mono/metadata/icall.c
Original file line number Diff line number Diff line change
Expand Up @@ -4246,6 +4246,17 @@ ves_icall_RuntimeType_GetPropertiesByName_native (MonoQCallTypeHandle type_handl
return NULL;
}

gpointer
ves_icall_RuntimeType_GetValueConstructor (MonoObjectHandleOnStack ref_ctor_info, MonoError *error)
{
gpointer ctor_fptr;
MonoReflectionMethod* ctor = *(MonoReflectionMethod**)ref_ctor_info;
ctor_fptr = mono_compile_method_checked (ctor->method, error);
if (!is_ok (error))
return NULL;
lambdageek marked this conversation as resolved.
Show resolved Hide resolved
return ctor_fptr;
}

static guint
event_hash (gconstpointer data)
{
Expand Down
11 changes: 11 additions & 0 deletions src/tests/Loader/classloader/generics/ByRefLike/Validate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@

public class Validate
{
[Fact]
public static void Validate_Activation()
{
Console.WriteLine($"{nameof(Validate_Activation)}...");

Assert.Equal("System.Span<Int32>[0]", Activator.CreateInstance<Span<int>>().ToString());
Assert.Equal("System.Span<String>[0]", Activator.CreateInstance<Span<string>>().ToString());
Assert.Equal("System.ReadOnlySpan<Int32>[0]", Activator.CreateInstance<ReadOnlySpan<int>>().ToString());
Assert.Equal("System.ReadOnlySpan<String>[0]", Activator.CreateInstance<ReadOnlySpan<string>>().ToString());
}

[Fact]
public static void Validate_TypeLoad()
{
Expand Down
Loading