Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement RuntimeHelpers.Box #100561

Merged
merged 12 commits into from
Apr 6, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,31 @@ private static unsafe void DispatchTailCalls(
}
}
}

public static unsafe object? Box(ref byte target, RuntimeTypeHandle type)
{
if (type.IsNullHandle())
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type);

RuntimeType rtType = (RuntimeType)Type.GetTypeFromHandle(type)!;
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved

if (rtType.IsByRefLike)
throw new NotSupportedException(SR.NotSupported_ByRefLike);

if (rtType.IsPointer || rtType.IsFunctionPointer)
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
throw new NotSupportedException(SR.NotSupported_BoxedPointer);

if (rtType.IsActualValueType)
{
object? result = Box(rtType.GetNativeTypeHandle().AsMethodTable(), ref target);
GC.KeepAlive(rtType);
return result;
}
else
{
return Unsafe.As<byte, object?>(ref target);
}
}
}
// 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 @@ -361,6 +361,26 @@ public static unsafe object GetUninitializedObject(

return RuntimeImports.RhNewObject(mt);
}

public static unsafe object Box(ref byte target, RuntimeTypeHandle type)
{
if (type.IsNull)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type);

// Compatibility with CoreCLR, throw on a null reference to the unboxed data.
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
if (Unsafe.IsNullRef(ref target))
throw new NullReferenceException();

MethodTable* eeType = type.ToMethodTable();

if (eeType->IsByRefLike)
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
throw new NotSupportedException(SR.NotSupported_ByRefLike);

if (eeType->IsPointer || eeType->IsFunctionPointer)
throw new NotSupportedException(SR.NotSupported_BoxedPointer);

return RuntimeImports.RhBoxAny(ref target, eeType);
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
}
}

// CLR arrays are laid out in memory as follows (multidimensional array bounds are optional):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,15 @@ internal static RuntimeTypeHandle RawTargetTypeToken(TypedReference value)

public static unsafe object ToObject(TypedReference value)
{
RuntimeTypeHandle typeHandle = value._typeHandle;
if (typeHandle.IsNull)
jkoritzinsky marked this conversation as resolved.
Show resolved Hide resolved
ThrowHelper.ThrowArgumentException_ArgumentNull_TypedRefType();
RuntimeTypeHandle handle = RawTargetTypeToken(value);

MethodTable* eeType = typeHandle.ToMethodTable();
if (eeType->IsValueType)
MethodTable* mt = handle.ToMethodTable();
if (mt->IsPointer || mt->IsFunctionPointer)
{
return RuntimeImports.RhBox(eeType, ref value.Value);
}
else if (eeType->IsPointer || eeType->IsFunctionPointer)
{
return RuntimeImports.RhBox(MethodTable.Of<UIntPtr>(), ref value.Value);
}
else
{
return Unsafe.As<byte, object>(ref value.Value);
handle = typeof(UIntPtr).TypeHandle;
}

return RuntimeHelpers.Box(ref value.Value, handle);
}

public static void SetTypedReference(TypedReference target, object? value) { throw new NotSupportedException(); }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2954,6 +2954,9 @@
<data name="NotSupported_AssemblyLoadFromHash" xml:space="preserve">
<value>Assembly.LoadFrom with hashValue is not supported.</value>
</data>
<data name="NotSupported_BoxedPointer" xml:space="preserve">
<value>Cannot create boxed pointer values.</value>
</data>
<data name="NotSupported_ByRefLike" xml:space="preserve">
<value>Cannot create boxed ByRef-like values.</value>
</data>
Expand Down
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 @@ -13149,6 +13149,7 @@ public static partial class RuntimeHelpers
[System.ObsoleteAttribute("OffsetToStringData has been deprecated. Use string.GetPinnableReference() instead.")]
public static int OffsetToStringData { get { throw null; } }
public static System.IntPtr AllocateTypeAssociatedMemory(System.Type type, int size) { throw null; }
public static object? Box(ref byte target, System.RuntimeTypeHandle type) { throw null; }
public static System.ReadOnlySpan<T> CreateSpan<T>(System.RuntimeFieldHandle fldHandle) { throw null; }
public static void EnsureSufficientExecutionStack() { }
public static new bool Equals(object? o1, object? o2) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,110 @@ public static void FixedAddressValueTypeTest()

Assert.Equal(fixedPtr1, fixedPtr2);
}

[Fact]
public static void BoxPrimitive()
{
int value = 4;
object result = RuntimeHelpers.Box(ref Unsafe.As<int, byte>(ref value), typeof(int).TypeHandle);
Assert.Equal(value, Assert.IsType<int>(result));
}

[Fact]
public static void BoxPointer()
{
Assert.Throws<NotSupportedException>(() =>
{
nint value = 3;
object result = RuntimeHelpers.Box(ref Unsafe.As<nint, byte>(ref value), typeof(void*).TypeHandle);
});
}

[StructLayout(LayoutKind.Sequential)]
private ref struct ByRefLikeType
{
public int i;
}

[Fact]
public static void BoxByRefLike()
{
Assert.Throws<NotSupportedException>(() =>
{
int value = 3;
object result = RuntimeHelpers.Box(ref Unsafe.As<int, byte>(ref value), typeof(ByRefLikeType).TypeHandle);
});
}

[Fact]
public static void BoxStruct()
{
Span<int> buffer = [0, 42, int.MaxValue];
StructWithoutReferences expected = new()
{
a = buffer[0],
b = buffer[1],
c = buffer[2]
};
object result = RuntimeHelpers.Box(ref MemoryMarshal.AsBytes(buffer)[0], typeof(StructWithoutReferences).TypeHandle);

Assert.Equal(expected, Assert.IsType<StructWithoutReferences>(result));
}

[StructLayout(LayoutKind.Sequential)]
private struct GenericStruct<T>
{
public T data;
}

[Fact]
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);
}

[Fact]
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);
}

[Fact]
public static void BoxNullable()
{
float? value = 3.14f;
object result = RuntimeHelpers.Box(ref Unsafe.As<float?, byte>(ref value), typeof(float?).TypeHandle);
Assert.Equal(value, Assert.IsType<float>(result));
}

[Fact]
public static void NullBox()
{
Assert.Throws<NullReferenceException>(() => RuntimeHelpers.Box(ref Unsafe.NullRef<byte>(), typeof(byte).TypeHandle));
}

[Fact]
public static void BoxInvalidType()
{
Assert.Throws<ArgumentNullException>(() =>
{
byte value = 3;
RuntimeHelpers.Box(ref value, default(RuntimeTypeHandle));
});
}

[Fact]
public static void BoxReferenceType()
{
string str = "ABC";
Assert.Same(str, RuntimeHelpers.Box(ref Unsafe.As<string, byte>(ref str), typeof(string).TypeHandle));
}
}

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

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

[MethodImplAttribute(MethodImplOptions.InternalCall)]
private static extern void InternalBox(QCallTypeHandle type, ref byte target, ObjectHandleOnStack result);

public static object? Box(ref byte target, RuntimeTypeHandle type)
{
if (type.Value is 0)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type);

// Compatibility with CoreCLR, throw on a null reference to the unboxed data.
if (Unsafe.IsNullRef(ref target))
throw new NullReferenceException();

RuntimeType rtType = (RuntimeType)Type.GetTypeFromHandle(type)!;

if (rtType.IsByRefLike)
throw new NotSupportedException(SR.NotSupported_ByRefLike);

if (rtType.IsPointer || rtType.IsFunctionPointer)
throw new NotSupportedException(SR.NotSupported_BoxedPointer);


if (rtType.IsValueType)
{
object? result = null;
InternalBox(new QCallTypeHandle(ref rtType), ref target, ObjectHandleOnStack.Create(ref result));
return result;
}
else
{
return Unsafe.As<byte, object?>(ref target);
}
}
}
}
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 @@ -435,6 +435,7 @@ HANDLES(RUNH_1, "GetObjectValue", ves_icall_System_Runtime_CompilerServices_Runt
HANDLES(RUNH_6, "GetSpanDataFrom", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_GetSpanDataFrom, gpointer, 3, (MonoClassField_ptr, MonoType_ptr, gpointer))
HANDLES(RUNH_2, "GetUninitializedObjectInternal", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_GetUninitializedObjectInternal, MonoObject, 1, (MonoType_ptr))
HANDLES(RUNH_3, "InitializeArray", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InitializeArray, void, 2, (MonoArray, MonoClassField_ptr))
HANDLES(RUNH_8, "InternalBox", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalBox, void, 3, (MonoQCallTypeHandle, char_ref, MonoObjectHandleOnStack))
HANDLES(RUNH_7, "InternalGetHashCode", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalGetHashCode, int, 1, (MonoObject))
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))
Expand Down
2 changes: 2 additions & 0 deletions src/mono/mono/metadata/icall-table.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ typedef mono_unichar2 *mono_unichar2_ptr;
typedef mono_unichar4 *mono_unichar4_ptr;
typedef MonoSpanOfObjects *MonoSpanOfObjects_ref;

typedef char *char_ref;
typedef char **char_ptr_ref;
typedef gint32 *gint32_ref;
typedef gint64 *gint64_ref;
Expand Down Expand Up @@ -173,6 +174,7 @@ typedef MonoStringHandle MonoStringOutHandle;
#define MONO_HANDLE_TYPE_WRAP_int_ref ICALL_HANDLES_WRAP_VALUETYPE_REF
#define MONO_HANDLE_TYPE_WRAP_gint32_ref ICALL_HANDLES_WRAP_VALUETYPE_REF
#define MONO_HANDLE_TYPE_WRAP_int_ptr_ref ICALL_HANDLES_WRAP_VALUETYPE_REF
#define MONO_HANDLE_TYPE_WRAP_char_ref ICALL_HANDLES_WRAP_VALUETYPE_REF
#define MONO_HANDLE_TYPE_WRAP_char_ptr_ref ICALL_HANDLES_WRAP_VALUETYPE_REF
#define MONO_HANDLE_TYPE_WRAP_guint8_ptr_ref ICALL_HANDLES_WRAP_VALUETYPE_REF
#define MONO_HANDLE_TYPE_WRAP_MonoResolveTokenError_ref ICALL_HANDLES_WRAP_VALUETYPE_REF
Expand Down
20 changes: 20 additions & 0 deletions src/mono/mono/metadata/icall.c
Original file line number Diff line number Diff line change
Expand Up @@ -1210,6 +1210,26 @@ ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_PrepareMethod (MonoMeth
// FIXME: Implement
}

void
ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalBox (MonoQCallTypeHandle type_handle, char* data, MonoObjectHandleOnStack obj, MonoError *error)
{
MonoType *type = type_handle.type;
MonoClass *klass = mono_class_from_mono_type_internal (type);

g_assert (m_class_is_valuetype (klass));

mono_class_init_checked (klass, error);
goto_if_nok (error, error_ret);

MonoObject* raw_obj = mono_value_box_checked (klass, data, error);
goto_if_nok (error, error_ret);

HANDLE_ON_STACK_SET(obj, raw_obj);
return;
error_ret:
HANDLE_ON_STACK_SET (obj, NULL);
}

MonoObjectHandle
ves_icall_System_Object_MemberwiseClone (MonoObjectHandle this_obj, MonoError *error)
{
Expand Down
Loading