diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 380981993451e..74f6f5beeb3b6 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -270,24 +270,27 @@ internal static void GetActivationInfo( RuntimeType rt, out delegate* pfnAllocator, out void* vAllocatorFirstArg, - out delegate* pfnCtor, + out delegate* pfnRefCtor, + out delegate* pfnValueCtor, out bool ctorIsPublic) { Debug.Assert(rt != null); delegate* pfnAllocatorTemp = default; void* vAllocatorFirstArgTemp = default; - delegate* pfnCtorTemp = default; + delegate* pfnRefCtorTemp = default; + delegate* 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; } @@ -296,7 +299,8 @@ private static partial void GetActivationInfo( ObjectHandleOnStack pRuntimeType, delegate** ppfnAllocator, void** pvAllocatorFirstArg, - delegate** ppfnCtor, + delegate** ppfnRefCtor, + delegate** ppfnValueCtor, Interop.BOOL* pfCtorIsPublic); #if FEATURE_COMINTEROP diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs index 4d73cfad39143..9c84e1c10a11e 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.ActivatorCache.cs @@ -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* _pfnCtor; + private readonly delegate* _pfnRefCtor; + private readonly delegate* _pfnValueCtor; private readonly bool _ctorIsPublic; private CreateUninitializedCache? _createUninitializedCache; @@ -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) { @@ -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 @@ -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) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs index c6ea76fde9b85..9eca40dfcad50 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @@ -3932,7 +3932,7 @@ internal object GetUninitializedObject() object? obj = cache.CreateUninitializedObject(this); try { - cache.CallConstructor(obj); + cache.CallRefConstructor(obj); } catch (Exception e) when (wrapExceptions) { @@ -3942,7 +3942,7 @@ internal object GetUninitializedObject() return obj; } - // Specialized version of the above for Activator.CreateInstance() + // Specialized version of CreateInstanceDefaultCtor() for Activator.CreateInstance() [DebuggerStepThrough] [DebuggerHidden] internal object? CreateInstanceOfT() @@ -3961,7 +3961,7 @@ internal object GetUninitializedObject() object? obj = cache.CreateUninitializedObject(this); try { - cache.CallConstructor(obj); + cache.CallRefConstructor(obj); } catch (Exception e) { @@ -3971,6 +3971,34 @@ internal object GetUninitializedObject() return obj; } + // Specialized version of CreateInstanceDefaultCtor() for Activator.CreateInstance() + [DebuggerStepThrough] + [DebuggerHidden] + internal void CallDefaultStructConstructor(ref byte data) + { + Debug.Assert(IsValueType); + + 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); diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index f57e21801253a..6c240448ddfd6 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -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 ) { @@ -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; @@ -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 @@ -1771,7 +1775,8 @@ extern "C" void QCALLTYPE RuntimeTypeHandle_GetActivationInfo( // CreateInstance returns null given Nullable *ppfnAllocator = (PCODE)NULL; *pvAllocatorFirstArg = NULL; - *ppfnCtor = (PCODE)NULL; + *ppfnRefCtor = (PCODE)NULL; + *ppfnValueCtor = (PCODE)NULL; *pfCtorIsPublic = TRUE; // no ctor call needed => assume 'public' equivalent } else @@ -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 diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 56bb2df245f9f..1adb6b25ebc3f 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -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); diff --git a/src/libraries/System.Private.CoreLib/src/System/Activator.RuntimeType.cs b/src/libraries/System.Private.CoreLib/src/System/Activator.RuntimeType.cs index 4d077d8ac3f9c..b0837a535f00b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Activator.RuntimeType.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Activator.RuntimeType.cs @@ -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(ref o); + } + else + { + T t = default!; + rtType.CallDefaultStructConstructor(ref Unsafe.As(ref t)); + return t; + } } private static T CreateDefaultInstance() where T : struct => default; diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs index e59b30fd7633f..9f0129aef0a72 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/Unsafe.cs @@ -83,6 +83,8 @@ public static T As(object? o) where T : class? [NonVersionable] [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ref TTo As(ref TFrom source) + where TFrom : allows ref struct + where TTo : allows ref struct { throw new PlatformNotSupportedException(); diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 37bd783042bdb..30c6aa55360f4 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -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 { @@ -13357,7 +13357,7 @@ public static partial class Unsafe public static ref T AsRef(scoped ref readonly T source) { throw null; } [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNullAttribute("o")] public static T? As(object? o) where T : class? { throw null; } - public static ref TTo As(ref TFrom source) { throw null; } + public static ref TTo As (ref TFrom source) where TFrom : allows ref struct where TTo : allows ref struct { throw null; } public static TTo BitCast(TFrom source) { throw null; } public static System.IntPtr ByteOffset([System.Diagnostics.CodeAnalysis.AllowNull] ref readonly T origin, [System.Diagnostics.CodeAnalysis.AllowNull] ref readonly T target) { throw null; } [System.CLSCompliantAttribute(false)] diff --git a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs index f289911d790f3..f946ce7bc09da 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeType.Mono.cs @@ -37,7 +37,7 @@ internal enum TypeNameFormatFlags FormatFullInst } - internal partial class RuntimeType + internal unsafe partial class RuntimeType { #region Definitions @@ -1607,7 +1607,7 @@ private void CreateInstanceCheckThis() return CreateInstanceMono(!publicOnly, wrapExceptions); } - // Specialized version of the above for Activator.CreateInstance() + // Specialized version of CreateInstanceDefaultCtor() for Activator.CreateInstance() [DebuggerStepThroughAttribute] [Diagnostics.DebuggerHidden] internal object? CreateInstanceOfT() @@ -1615,6 +1615,43 @@ private void CreateInstanceCheckThis() return CreateInstanceMono(false, true); } + // Specialized version of CreateInstanceDefaultCtor() for Activator.CreateInstance() + [DebuggerStepThroughAttribute] + [Diagnostics.DebuggerHidden] + internal void CallDefaultStructConstructor(ref byte value) + { + Debug.Assert(IsValueType); + + RuntimeConstructorInfo? ctor = GetDefaultConstructor(); + if (ctor == null) + { + return; + } + + if (!ctor.IsPublic) + { + throw new MissingMethodException(SR.Format(SR.Arg_NoDefCTor, this)); + } + + // Important: when using the interpreter, GetFunctionPointer is an intrinsic that + // returns a function descriptor suitable for casting to a managed function pointer. + // Other ways of obtaining a function pointer might not work. + IntPtr ptr = ctor.MethodHandle.GetFunctionPointer(); + delegate* valueCtor = (delegate*)ptr; + if (valueCtor == null) + { + throw new ExecutionEngineException(); + } + + try + { + valueCtor(ref value); + } + catch (Exception e) + { + throw new TargetInvocationException(e); + } + } #endregion private TypeCache? cache; diff --git a/src/tests/Loader/classloader/generics/ByRefLike/Validate.cs b/src/tests/Loader/classloader/generics/ByRefLike/Validate.cs index 867cccd32d5be..45de0af7d211b 100644 --- a/src/tests/Loader/classloader/generics/ByRefLike/Validate.cs +++ b/src/tests/Loader/classloader/generics/ByRefLike/Validate.cs @@ -11,6 +11,17 @@ public class Validate { + [Fact] + public static void Validate_Activation() + { + Console.WriteLine($"{nameof(Validate_Activation)}..."); + + Assert.Equal("System.Span[0]", Activator.CreateInstance>().ToString()); + Assert.Equal("System.Span[0]", Activator.CreateInstance>().ToString()); + Assert.Equal("System.ReadOnlySpan[0]", Activator.CreateInstance>().ToString()); + Assert.Equal("System.ReadOnlySpan[0]", Activator.CreateInstance>().ToString()); + } + [Fact] public static void Validate_TypeLoad() {