Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,32 @@ private static unsafe void DispatchTailCalls(
}
}
}

[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 has the same behavior as if you were to use the IL sizeof instruction with the passed in type as the operand.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// This API has the same behavior as if you were to use the IL sizeof instruction with the passed in type as the operand.
/// This API returns the same value as the IL sizeof instruction with the passed in type as the operand.

Nit: It does not have the same behavior for error cases.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/// </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 @@ -87,6 +87,11 @@ internal bool IsNullHandle()
return m_type == null;
}

internal TypeHandle GetNativeTypeHandle()
{
return m_type.GetNativeTypeHandle();
}

internal static bool IsTypeDefinition(RuntimeType type)
{
CorElementType corElemType = GetCorElementType(type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,36 @@ public static unsafe object GetUninitializedObject(

return RuntimeImports.RhNewObject(mt);
}

/// <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 has the same behavior as if you were to use the IL sizeof instruction with the passed in type as the operand.
/// </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 @@ -13175,6 +13175,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 @@ -437,6 +437,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));
}
}

public struct Age
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,5 +211,29 @@ private static extern unsafe IntPtr GetSpanDataFrom(

[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern bool SufficientExecutionStack();

[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 has the same behavior as if you were to use the IL sizeof instruction with the passed in type as the operand.
/// </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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a limitation of mono runtime (which doesn't exist in coreclr)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this is the same limitation in CoreCLR and NativeAOT (though the implementations are slightly different due to how the different type systems are implemented).


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 @@ -439,6 +439,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 @@ -1210,6 +1210,13 @@ ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_PrepareMethod (MonoMeth
// FIXME: Implement
}

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