Skip to content

Commit f54c778

Browse files
jkoritzinskyjkotas
andauthored
Implement RuntimeHelpers.SizeOf (#100618)
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
1 parent 09153a3 commit f54c778

10 files changed

Lines changed: 153 additions & 2 deletions

File tree

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,32 @@ private static unsafe void DispatchTailCalls(
458458
return Unsafe.As<byte, object?>(ref target);
459459
}
460460
}
461+
462+
[LibraryImport(QCall, EntryPoint = "ReflectionInvocation_SizeOf")]
463+
[SuppressGCTransition]
464+
private static partial int SizeOf(QCallTypeHandle handle);
465+
466+
/// <summary>
467+
/// Get the size of an object of the given type.
468+
/// </summary>
469+
/// <param name="type">The type to get the size of.</param>
470+
/// <returns>The size of instances of the type.</returns>
471+
/// <exception cref="ArgumentException">The passed-in type is not a valid type to get the size of.</exception>
472+
/// <remarks>
473+
/// This API returns the same value as <see cref="Unsafe.SizeOf{T}"/> for the type that <paramref name="type"/> represents.
474+
/// </remarks>
475+
public static unsafe int SizeOf(RuntimeTypeHandle type)
476+
{
477+
if (type.IsNullHandle())
478+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type);
479+
480+
int result = SizeOf(new QCallTypeHandle(ref type));
481+
482+
if (result <= 0)
483+
throw new ArgumentException(SR.Arg_TypeNotSupported);
484+
485+
return result;
486+
}
461487
}
462488
// Helper class to assist with unsafe pinning of arbitrary objects.
463489
// It's used by VM code.

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.NativeAot.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,36 @@ public static unsafe object GetUninitializedObject(
399399

400400
return RuntimeImports.RhBox(mt, ref target);
401401
}
402+
403+
/// <summary>
404+
/// Get the size of an object of the given type.
405+
/// </summary>
406+
/// <param name="type">The type to get the size of.</param>
407+
/// <returns>The size of instances of the type.</returns>
408+
/// <exception cref="ArgumentException">The passed-in type is not a valid type to get the size of.</exception>
409+
/// <remarks>
410+
/// This API returns the same value as <see cref="Unsafe.SizeOf{T}"/> for the type that <paramref name="type"/> represents.
411+
/// </remarks>
412+
public static unsafe int SizeOf(RuntimeTypeHandle type)
413+
{
414+
if (type.IsNull)
415+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type);
416+
417+
MethodTable* mt = type.ToMethodTable();
418+
419+
if (mt->ElementType == EETypeElementType.Void
420+
|| mt->IsGenericTypeDefinition)
421+
{
422+
throw new ArgumentException(SR.Arg_TypeNotSupported);
423+
}
424+
425+
if (mt->IsValueType)
426+
{
427+
return (int)mt->ValueTypeSize;
428+
}
429+
430+
return nint.Size;
431+
}
402432
}
403433

404434
// CLR arrays are laid out in memory as follows (multidimensional array bounds are optional):

src/coreclr/vm/qcallentrypoints.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ static const Entry s_QCall[] =
337337
DllImportEntry(ReflectionInvocation_RunModuleConstructor)
338338
DllImportEntry(ReflectionInvocation_CompileMethod)
339339
DllImportEntry(ReflectionInvocation_PrepareMethod)
340+
DllImportEntry(ReflectionInvocation_SizeOf)
340341
DllImportEntry(ReflectionSerialization_GetCreateUninitializedObjectInfo)
341342
#if defined(FEATURE_COMWRAPPERS)
342343
DllImportEntry(ComWrappers_GetIUnknownImpl)

src/coreclr/vm/reflectioninvocation.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2120,3 +2120,16 @@ FCIMPL2_IV(Object*, ReflectionEnum::InternalBoxEnum, ReflectClassBaseObject* tar
21202120
return OBJECTREFToObject(ret);
21212121
}
21222122
FCIMPLEND
2123+
2124+
extern "C" int32_t QCALLTYPE ReflectionInvocation_SizeOf(QCall::TypeHandle pType)
2125+
{
2126+
QCALL_CONTRACT_NO_GC_TRANSITION;
2127+
2128+
TypeHandle handle = pType.AsTypeHandle();
2129+
2130+
// -1 is the same sentinel value returned by GetSize for an invalid type.
2131+
if (handle.ContainsGenericVariables())
2132+
return -1;
2133+
2134+
return handle.GetSize();
2135+
}

src/coreclr/vm/reflectioninvocation.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,6 @@ class ReflectionEnum {
8282

8383
extern "C" void QCALLTYPE Enum_GetValuesAndNames(QCall::TypeHandle pEnumType, QCall::ObjectHandleOnStack pReturnValues, QCall::ObjectHandleOnStack pReturnNames, BOOL fGetNames);
8484

85+
extern "C" int32_t QCALLTYPE ReflectionInvocation_SizeOf(QCall::TypeHandle pType);
86+
8587
#endif // _REFLECTIONINVOCATION_H_

src/libraries/System.Runtime/ref/System.Runtime.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13176,6 +13176,7 @@ public static void ProbeForSufficientStack() { }
1317613176
[System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute("Trimmer can't guarantee existence of class constructor")]
1317713177
public static void RunClassConstructor(System.RuntimeTypeHandle type) { }
1317813178
public static void RunModuleConstructor(System.ModuleHandle module) { }
13179+
public static int SizeOf(System.RuntimeTypeHandle type) { throw null; }
1317913180
public static bool TryEnsureSufficientExecutionStack() { throw null; }
1318013181
public delegate void CleanupCode(object? userData, bool exceptionThrown);
1318113182
public delegate void TryCode(object? userData);

src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,52 @@ public static void FixedAddressValueTypeTest()
438438
Assert.Equal(fixedPtr1, fixedPtr2);
439439
}
440440

441+
[InlineArray(3)]
442+
private struct Byte3
443+
{
444+
public byte b1;
445+
}
446+
447+
[Fact]
448+
public static unsafe void SizeOf()
449+
{
450+
Assert.Equal(1, RuntimeHelpers.SizeOf(typeof(sbyte).TypeHandle));
451+
Assert.Equal(1, RuntimeHelpers.SizeOf(typeof(byte).TypeHandle));
452+
Assert.Equal(2, RuntimeHelpers.SizeOf(typeof(short).TypeHandle));
453+
Assert.Equal(2, RuntimeHelpers.SizeOf(typeof(ushort).TypeHandle));
454+
Assert.Equal(4, RuntimeHelpers.SizeOf(typeof(int).TypeHandle));
455+
Assert.Equal(4, RuntimeHelpers.SizeOf(typeof(uint).TypeHandle));
456+
Assert.Equal(8, RuntimeHelpers.SizeOf(typeof(long).TypeHandle));
457+
Assert.Equal(8, RuntimeHelpers.SizeOf(typeof(ulong).TypeHandle));
458+
Assert.Equal(4, RuntimeHelpers.SizeOf(typeof(float).TypeHandle));
459+
Assert.Equal(8, RuntimeHelpers.SizeOf(typeof(double).TypeHandle));
460+
Assert.Equal(3, RuntimeHelpers.SizeOf(typeof(Byte3).TypeHandle));
461+
Assert.Equal(nint.Size, RuntimeHelpers.SizeOf(typeof(void*).TypeHandle));
462+
Assert.Equal(nint.Size, RuntimeHelpers.SizeOf(typeof(delegate* <void>).TypeHandle));
463+
Assert.Equal(nint.Size, RuntimeHelpers.SizeOf(typeof(int).MakeByRefType().TypeHandle));
464+
Assert.Throws<ArgumentNullException>(() => RuntimeHelpers.SizeOf(default));
465+
Assert.ThrowsAny<ArgumentException>(() => RuntimeHelpers.SizeOf(typeof(List<>).TypeHandle));
466+
Assert.ThrowsAny<ArgumentException>(() => RuntimeHelpers.SizeOf(typeof(void).TypeHandle));
467+
}
468+
469+
// We can't even get a RuntimeTypeHandle for a generic parameter type on NativeAOT,
470+
// so we don't even get to the method we're testing.
471+
// So, let's not even waste time running this test on NativeAOT
472+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))]
473+
public static void SizeOfGenericParameter()
474+
{
475+
Assert.ThrowsAny<ArgumentException>(() => RuntimeHelpers.SizeOf(typeof(List<>).GetGenericArguments()[0].TypeHandle));
476+
}
477+
478+
// We can't even get a RuntimeTypeHandle for a partially-open-generic type on NativeAOT,
479+
// so we don't even get to the method we're testing.
480+
// So, let's not even waste time running this test on NativeAOT
481+
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotNativeAot))]
482+
public static void SizeOfPartiallyOpenGeneric()
483+
{
484+
Assert.ThrowsAny<ArgumentException>(() => RuntimeHelpers.SizeOf(typeof(Dictionary<,>).MakeGenericType(typeof(object), typeof(Dictionary<,>).GetGenericArguments()[1]).TypeHandle));
485+
}
486+
441487
[Fact]
442488
public static void BoxPrimitive()
443489
{
@@ -498,7 +544,7 @@ public static void BoxUnmanagedGenericStruct()
498544
{
499545
int value = 3;
500546
object result = RuntimeHelpers.Box(ref Unsafe.As<int, byte>(ref value), typeof(GenericStruct<int>).TypeHandle);
501-
547+
502548
Assert.Equal(value, Assert.IsType<GenericStruct<int>>(result).data);
503549
}
504550

@@ -507,7 +553,7 @@ public static void BoxManagedGenericStruct()
507553
{
508554
object value = new();
509555
object result = RuntimeHelpers.Box(ref Unsafe.As<object, byte>(ref value), typeof(GenericStruct<object>).TypeHandle);
510-
556+
511557
Assert.Same(value, Assert.IsType<GenericStruct<object>>(result).data);
512558
}
513559

src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,5 +258,29 @@ private static extern unsafe IntPtr GetSpanDataFrom(
258258
InternalBox(new QCallTypeHandle(ref rtType), ref target, ObjectHandleOnStack.Create(ref result));
259259
return result;
260260
}
261+
262+
[MethodImplAttribute(MethodImplOptions.InternalCall)]
263+
private static extern int SizeOf(QCallTypeHandle handle);
264+
265+
/// <summary>
266+
/// Get the size of an object of the given type.
267+
/// </summary>
268+
/// <param name="type">The type to get the size of.</param>
269+
/// <returns>The size of instances of the type.</returns>
270+
/// <exception cref="ArgumentException">The passed-in type is not a valid type to get the size of.</exception>
271+
/// <remarks>
272+
/// This API returns the same value as <see cref="Unsafe.SizeOf{T}"/> for the type that <paramref name="type"/> represents.
273+
/// </remarks>
274+
public static int SizeOf(RuntimeTypeHandle type)
275+
{
276+
if (type.Value == IntPtr.Zero)
277+
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type);
278+
279+
Type typeObj = Type.GetTypeFromHandle(type)!;
280+
if (typeObj.ContainsGenericParameters || typeObj.IsGenericParameter || typeObj == typeof(void))
281+
throw new ArgumentException(SR.Arg_TypeNotSupported);
282+
283+
return SizeOf(new QCallTypeHandle(ref type));
284+
}
261285
}
262286
}

src/mono/mono/metadata/icall-def.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,7 @@ HANDLES(RUNH_7, "InternalGetHashCode", ves_icall_System_Runtime_CompilerServices
440440
HANDLES(RUNH_3a, "PrepareMethod", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_PrepareMethod, void, 3, (MonoMethod_ptr, gpointer, int))
441441
HANDLES(RUNH_4, "RunClassConstructor", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_RunClassConstructor, void, 1, (MonoType_ptr))
442442
HANDLES(RUNH_5, "RunModuleConstructor", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_RunModuleConstructor, void, 1, (MonoImage_ptr))
443+
HANDLES(RUNH_9, "SizeOf", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_SizeOf, gint32, 1, (MonoQCallTypeHandle))
443444
NOHANDLES(ICALL(RUNH_5h, "SufficientExecutionStack", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_SufficientExecutionStack))
444445

445446
ICALL_TYPE(GCH, "System.Runtime.InteropServices.GCHandle", GCH_1)

src/mono/mono/metadata/icall.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1230,6 +1230,13 @@ ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalBox (MonoQCallT
12301230
HANDLE_ON_STACK_SET (obj, NULL);
12311231
}
12321232

1233+
gint32
1234+
ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_SizeOf (MonoQCallTypeHandle type, MonoError* error)
1235+
{
1236+
int align;
1237+
return mono_type_size (type.type, &align);
1238+
}
1239+
12331240
MonoObjectHandle
12341241
ves_icall_System_Object_MemberwiseClone (MonoObjectHandle this_obj, MonoError *error)
12351242
{

0 commit comments

Comments
 (0)