Skip to content

Commit

Permalink
Implement RuntimeHelpers.SizeOf (dotnet#100618)
Browse files Browse the repository at this point in the history
Co-authored-by: Jan Kotas <[email protected]>
  • Loading branch information
2 people authored and matouskozak committed Apr 30, 2024
1 parent 31ff73e commit 90795f5
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,32 @@ private static unsafe void DispatchTailCalls(
return Unsafe.As<byte, object?>(ref target);
}
}

[LibraryImport(QCall, EntryPoint = "ReflectionInvocation_SizeOf")]
[SuppressGCTransition]
private static partial int SizeOf(QCallTypeHandle handle);

/// <summary>
/// Get the size of an object of the given type.
/// </summary>
/// <param name="type">The type to get the size of.</param>
/// <returns>The size of instances of the type.</returns>
/// <exception cref="ArgumentException">The passed-in type is not a valid type to get the size of.</exception>
/// <remarks>
/// This API returns the same value as <see cref="Unsafe.SizeOf{T}"/> for the type that <paramref name="type"/> represents.
/// </remarks>
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,36 @@ public static unsafe object GetUninitializedObject(

return RuntimeImports.RhBox(mt, ref target);
}

/// <summary>
/// Get the size of an object of the given type.
/// </summary>
/// <param name="type">The type to get the size of.</param>
/// <returns>The size of instances of the type.</returns>
/// <exception cref="ArgumentException">The passed-in type is not a valid type to get the size of.</exception>
/// <remarks>
/// This API returns the same value as <see cref="Unsafe.SizeOf{T}"/> for the type that <paramref name="type"/> represents.
/// </remarks>
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):
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/qcallentrypoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
13 changes: 13 additions & 0 deletions src/coreclr/vm/reflectioninvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
2 changes: 2 additions & 0 deletions src/coreclr/vm/reflectioninvocation.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_
1 change: 1 addition & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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* <void>).TypeHandle));
Assert.Equal(nint.Size, RuntimeHelpers.SizeOf(typeof(int).MakeByRefType().TypeHandle));
Assert.Throws<ArgumentNullException>(() => RuntimeHelpers.SizeOf(default));
Assert.ThrowsAny<ArgumentException>(() => RuntimeHelpers.SizeOf(typeof(List<>).TypeHandle));
Assert.ThrowsAny<ArgumentException>(() => 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<ArgumentException>(() => 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<ArgumentException>(() => RuntimeHelpers.SizeOf(typeof(Dictionary<,>).MakeGenericType(typeof(object), typeof(Dictionary<,>).GetGenericArguments()[1]).TypeHandle));
}

[Fact]
public static void BoxPrimitive()
{
Expand Down Expand Up @@ -498,7 +544,7 @@ public static void BoxUnmanagedGenericStruct()
{
int value = 3;
object result = RuntimeHelpers.Box(ref Unsafe.As<int, byte>(ref value), typeof(GenericStruct<int>).TypeHandle);

Assert.Equal(value, Assert.IsType<GenericStruct<int>>(result).data);
}

Expand All @@ -507,7 +553,7 @@ public static void BoxManagedGenericStruct()
{
object value = new();
object result = RuntimeHelpers.Box(ref Unsafe.As<object, byte>(ref value), typeof(GenericStruct<object>).TypeHandle);

Assert.Same(value, Assert.IsType<GenericStruct<object>>(result).data);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/// <summary>
/// Get the size of an object of the given type.
/// </summary>
/// <param name="type">The type to get the size of.</param>
/// <returns>The size of instances of the type.</returns>
/// <exception cref="ArgumentException">The passed-in type is not a valid type to get the size of.</exception>
/// <remarks>
/// This API returns the same value as <see cref="Unsafe.SizeOf{T}"/> for the type that <paramref name="type"/> represents.
/// </remarks>
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));
}
}
}
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 @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions src/mono/mono/metadata/icall.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down

0 comments on commit 90795f5

Please sign in to comment.