From 90795f5bb2c1b929f11543dfc2e0b462230315e1 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 9 Apr 2024 12:55:26 -0700 Subject: [PATCH] Implement RuntimeHelpers.SizeOf (#100618) Co-authored-by: Jan Kotas --- .../RuntimeHelpers.CoreCLR.cs | 26 ++++++++++ .../RuntimeHelpers.NativeAot.cs | 30 +++++++++++ src/coreclr/vm/qcallentrypoints.cpp | 1 + src/coreclr/vm/reflectioninvocation.cpp | 13 +++++ src/coreclr/vm/reflectioninvocation.h | 2 + .../System.Runtime/ref/System.Runtime.cs | 1 + .../CompilerServices/RuntimeHelpersTests.cs | 50 ++++++++++++++++++- .../CompilerServices/RuntimeHelpers.Mono.cs | 24 +++++++++ src/mono/mono/metadata/icall-def.h | 1 + src/mono/mono/metadata/icall.c | 7 +++ 10 files changed, 153 insertions(+), 2 deletions(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs index 69506c2feda11f..43ddbfecf006fa 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs @@ -458,6 +458,32 @@ private static unsafe void DispatchTailCalls( return Unsafe.As(ref target); } } + + [LibraryImport(QCall, EntryPoint = "ReflectionInvocation_SizeOf")] + [SuppressGCTransition] + private static partial int SizeOf(QCallTypeHandle handle); + + /// + /// Get the size of an object of the given type. + /// + /// The type to get the size of. + /// The size of instances of the type. + /// The passed-in type is not a valid type to get the size of. + /// + /// This API returns the same value as for the type that represents. + /// + public static unsafe int SizeOf(RuntimeTypeHandle type) + { + if (type.IsNullHandle()) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type); + + int result = SizeOf(new QCallTypeHandle(ref type)); + + if (result <= 0) + throw new ArgumentException(SR.Arg_TypeNotSupported); + + return result; + } } // Helper class to assist with unsafe pinning of arbitrary objects. // It's used by VM code. diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs index c790682ecfcbc8..a2f5b48b498fb6 100644 --- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs +++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs @@ -399,6 +399,36 @@ public static unsafe object GetUninitializedObject( return RuntimeImports.RhBox(mt, ref target); } + + /// + /// Get the size of an object of the given type. + /// + /// The type to get the size of. + /// The size of instances of the type. + /// The passed-in type is not a valid type to get the size of. + /// + /// This API returns the same value as for the type that represents. + /// + public static unsafe int SizeOf(RuntimeTypeHandle type) + { + if (type.IsNull) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type); + + MethodTable* mt = type.ToMethodTable(); + + if (mt->ElementType == EETypeElementType.Void + || mt->IsGenericTypeDefinition) + { + throw new ArgumentException(SR.Arg_TypeNotSupported); + } + + if (mt->IsValueType) + { + return (int)mt->ValueTypeSize; + } + + return nint.Size; + } } // CLR arrays are laid out in memory as follows (multidimensional array bounds are optional): diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 1150c55aa36d1a..233899582572e9 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -337,6 +337,7 @@ static const Entry s_QCall[] = DllImportEntry(ReflectionInvocation_RunModuleConstructor) DllImportEntry(ReflectionInvocation_CompileMethod) DllImportEntry(ReflectionInvocation_PrepareMethod) + DllImportEntry(ReflectionInvocation_SizeOf) DllImportEntry(ReflectionSerialization_GetCreateUninitializedObjectInfo) #if defined(FEATURE_COMWRAPPERS) DllImportEntry(ComWrappers_GetIUnknownImpl) diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp index a7f88289d037a4..65b7453f64c74b 100644 --- a/src/coreclr/vm/reflectioninvocation.cpp +++ b/src/coreclr/vm/reflectioninvocation.cpp @@ -2120,3 +2120,16 @@ FCIMPL2_IV(Object*, ReflectionEnum::InternalBoxEnum, ReflectClassBaseObject* tar return OBJECTREFToObject(ret); } FCIMPLEND + +extern "C" int32_t QCALLTYPE ReflectionInvocation_SizeOf(QCall::TypeHandle pType) +{ + QCALL_CONTRACT_NO_GC_TRANSITION; + + TypeHandle handle = pType.AsTypeHandle(); + + // -1 is the same sentinel value returned by GetSize for an invalid type. + if (handle.ContainsGenericVariables()) + return -1; + + return handle.GetSize(); +} diff --git a/src/coreclr/vm/reflectioninvocation.h b/src/coreclr/vm/reflectioninvocation.h index 967e9744790e93..d84e40f3a93ebd 100644 --- a/src/coreclr/vm/reflectioninvocation.h +++ b/src/coreclr/vm/reflectioninvocation.h @@ -82,4 +82,6 @@ class ReflectionEnum { extern "C" void QCALLTYPE Enum_GetValuesAndNames(QCall::TypeHandle pEnumType, QCall::ObjectHandleOnStack pReturnValues, QCall::ObjectHandleOnStack pReturnNames, BOOL fGetNames); +extern "C" int32_t QCALLTYPE ReflectionInvocation_SizeOf(QCall::TypeHandle pType); + #endif // _REFLECTIONINVOCATION_H_ diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 0371e3037e2653..5f04a80b548e66 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -13176,6 +13176,7 @@ public static void ProbeForSufficientStack() { } [System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Trimmer can't guarantee existence of class constructor")] public static void RunClassConstructor(System.RuntimeTypeHandle type) { } public static void RunModuleConstructor(System.ModuleHandle module) { } + public static int SizeOf(System.RuntimeTypeHandle type) { throw null; } public static bool TryEnsureSufficientExecutionStack() { throw null; } public delegate void CleanupCode(object? userData, bool exceptionThrown); public delegate void TryCode(object? userData); diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs index afb72bbe0949fb..9e2dc8059fa8bc 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs @@ -438,6 +438,52 @@ public static void FixedAddressValueTypeTest() Assert.Equal(fixedPtr1, fixedPtr2); } + [InlineArray(3)] + private struct Byte3 + { + public byte b1; + } + + [Fact] + public static unsafe void SizeOf() + { + Assert.Equal(1, RuntimeHelpers.SizeOf(typeof(sbyte).TypeHandle)); + Assert.Equal(1, RuntimeHelpers.SizeOf(typeof(byte).TypeHandle)); + Assert.Equal(2, RuntimeHelpers.SizeOf(typeof(short).TypeHandle)); + Assert.Equal(2, RuntimeHelpers.SizeOf(typeof(ushort).TypeHandle)); + Assert.Equal(4, RuntimeHelpers.SizeOf(typeof(int).TypeHandle)); + Assert.Equal(4, RuntimeHelpers.SizeOf(typeof(uint).TypeHandle)); + Assert.Equal(8, RuntimeHelpers.SizeOf(typeof(long).TypeHandle)); + Assert.Equal(8, RuntimeHelpers.SizeOf(typeof(ulong).TypeHandle)); + Assert.Equal(4, RuntimeHelpers.SizeOf(typeof(float).TypeHandle)); + Assert.Equal(8, RuntimeHelpers.SizeOf(typeof(double).TypeHandle)); + Assert.Equal(3, RuntimeHelpers.SizeOf(typeof(Byte3).TypeHandle)); + Assert.Equal(nint.Size, RuntimeHelpers.SizeOf(typeof(void*).TypeHandle)); + Assert.Equal(nint.Size, RuntimeHelpers.SizeOf(typeof(delegate* ).TypeHandle)); + Assert.Equal(nint.Size, RuntimeHelpers.SizeOf(typeof(int).MakeByRefType().TypeHandle)); + Assert.Throws(() => RuntimeHelpers.SizeOf(default)); + Assert.ThrowsAny(() => RuntimeHelpers.SizeOf(typeof(List<>).TypeHandle)); + Assert.ThrowsAny(() => RuntimeHelpers.SizeOf(typeof(void).TypeHandle)); + } + + // We can't even get a RuntimeTypeHandle for a generic parameter type on NativeAOT, + // so we don't even get to the method we're testing. + // So, let's not even waste time running this test on NativeAOT + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + public static void SizeOfGenericParameter() + { + Assert.ThrowsAny(() => RuntimeHelpers.SizeOf(typeof(List<>).GetGenericArguments()[0].TypeHandle)); + } + + // We can't even get a RuntimeTypeHandle for a partially-open-generic type on NativeAOT, + // so we don't even get to the method we're testing. + // So, let's not even waste time running this test on NativeAOT + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))] + public static void SizeOfPartiallyOpenGeneric() + { + Assert.ThrowsAny(() => RuntimeHelpers.SizeOf(typeof(Dictionary<,>).MakeGenericType(typeof(object), typeof(Dictionary<,>).GetGenericArguments()[1]).TypeHandle)); + } + [Fact] public static void BoxPrimitive() { @@ -498,7 +544,7 @@ public static void BoxUnmanagedGenericStruct() { int value = 3; object result = RuntimeHelpers.Box(ref Unsafe.As(ref value), typeof(GenericStruct).TypeHandle); - + Assert.Equal(value, Assert.IsType>(result).data); } @@ -507,7 +553,7 @@ public static void BoxManagedGenericStruct() { object value = new(); object result = RuntimeHelpers.Box(ref Unsafe.As(ref value), typeof(GenericStruct).TypeHandle); - + Assert.Same(value, Assert.IsType>(result).data); } diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs index 1e2241de12b35c..37a655841d652a 100644 --- a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs +++ b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs @@ -258,5 +258,29 @@ private static extern unsafe IntPtr GetSpanDataFrom( InternalBox(new QCallTypeHandle(ref rtType), ref target, ObjectHandleOnStack.Create(ref result)); return result; } + + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private static extern int SizeOf(QCallTypeHandle handle); + + /// + /// Get the size of an object of the given type. + /// + /// The type to get the size of. + /// The size of instances of the type. + /// The passed-in type is not a valid type to get the size of. + /// + /// This API returns the same value as for the type that represents. + /// + public static int SizeOf(RuntimeTypeHandle type) + { + if (type.Value == IntPtr.Zero) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type); + + Type typeObj = Type.GetTypeFromHandle(type)!; + if (typeObj.ContainsGenericParameters || typeObj.IsGenericParameter || typeObj == typeof(void)) + throw new ArgumentException(SR.Arg_TypeNotSupported); + + return SizeOf(new QCallTypeHandle(ref type)); + } } } diff --git a/src/mono/mono/metadata/icall-def.h b/src/mono/mono/metadata/icall-def.h index 766754d520080e..f6b3b08cfc6188 100644 --- a/src/mono/mono/metadata/icall-def.h +++ b/src/mono/mono/metadata/icall-def.h @@ -440,6 +440,7 @@ HANDLES(RUNH_7, "InternalGetHashCode", ves_icall_System_Runtime_CompilerServices HANDLES(RUNH_3a, "PrepareMethod", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_PrepareMethod, void, 3, (MonoMethod_ptr, gpointer, int)) HANDLES(RUNH_4, "RunClassConstructor", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_RunClassConstructor, void, 1, (MonoType_ptr)) HANDLES(RUNH_5, "RunModuleConstructor", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_RunModuleConstructor, void, 1, (MonoImage_ptr)) +HANDLES(RUNH_9, "SizeOf", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_SizeOf, gint32, 1, (MonoQCallTypeHandle)) NOHANDLES(ICALL(RUNH_5h, "SufficientExecutionStack", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_SufficientExecutionStack)) ICALL_TYPE(GCH, "System.Runtime.InteropServices.GCHandle", GCH_1) diff --git a/src/mono/mono/metadata/icall.c b/src/mono/mono/metadata/icall.c index 54cae6cef38d23..584c75c2c42157 100644 --- a/src/mono/mono/metadata/icall.c +++ b/src/mono/mono/metadata/icall.c @@ -1230,6 +1230,13 @@ ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalBox (MonoQCallT HANDLE_ON_STACK_SET (obj, NULL); } +gint32 +ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_SizeOf (MonoQCallTypeHandle type, MonoError* error) +{ + int align; + return mono_type_size (type.type, &align); +} + MonoObjectHandle ves_icall_System_Object_MemberwiseClone (MonoObjectHandle this_obj, MonoError *error) {