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 733e3a664bcc5..69506c2feda11 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
@@ -416,6 +416,48 @@ private static unsafe void DispatchTailCalls(
}
}
}
+
+ ///
+ /// Create a boxed object of the specified type from the data located at the target reference.
+ ///
+ /// The target data
+ /// The type of box to create.
+ /// A boxed object containing the specified data.
+ /// The specified type handle is null.
+ /// The specified type cannot have a boxed instance of itself created.
+ /// The passed in type is a by-ref-like type.
+ public static unsafe object? Box(ref byte target, RuntimeTypeHandle type)
+ {
+ if (type.IsNullHandle())
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type);
+
+ TypeHandle handle = type.GetNativeTypeHandle();
+
+ if (handle.IsTypeDesc)
+ throw new ArgumentException(SR.Arg_TypeNotSupported);
+
+ MethodTable* pMT = handle.AsMethodTable();
+
+ if (pMT->ContainsGenericVariables)
+ throw new ArgumentException(SR.Arg_TypeNotSupported);
+
+ if (pMT->IsValueType)
+ {
+ if (pMT->IsByRefLike)
+ throw new NotSupportedException(SR.NotSupported_ByRefLike);
+
+ if (MethodTable.AreSameType(pMT, (MethodTable*)RuntimeTypeHandle.ToIntPtr(typeof(void).TypeHandle)))
+ throw new ArgumentException(SR.Arg_TypeNotSupported);
+
+ object? result = Box(pMT, ref target);
+ GC.KeepAlive(type);
+ return result;
+ }
+ else
+ {
+ return Unsafe.As(ref target);
+ }
+ }
}
// Helper class to assist with unsafe pinning of arbitrary objects.
// It's used by VM code.
@@ -523,6 +565,7 @@ internal unsafe struct MethodTable
// WFLAGS_HIGH_ENUM
private const uint enum_flag_ContainsPointers = 0x01000000;
+ private const uint enum_flag_ContainsGenericVariables = 0x20000000;
private const uint enum_flag_HasComponentSize = 0x80000000;
private const uint enum_flag_HasTypeEquivalence = 0x02000000;
private const uint enum_flag_Category_Mask = 0x000F0000;
@@ -630,6 +673,8 @@ public bool IsConstructedGenericType
}
}
+ public bool ContainsGenericVariables => (Flags & enum_flag_ContainsGenericVariables) != 0;
+
///
/// Gets a for the element type of the current type.
///
diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs
index c6c7b9a563b4d..001a9fcdfee6a 100644
--- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs
+++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs
@@ -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);
diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CompilerServices/Unsafe.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CompilerServices/Unsafe.cs
index 341cd83260471..89eedc1638a72 100644
--- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CompilerServices/Unsafe.cs
+++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CompilerServices/Unsafe.cs
@@ -120,6 +120,16 @@ public static T ReadUnaligned(void* source)
throw new PlatformNotSupportedException();
}
+ ///
+ /// Reads a value of type from the given location.
+ ///
+ [Intrinsic]
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T ReadUnaligned(ref readonly byte source)
+ {
+ throw new PlatformNotSupportedException();
+ }
+
///
/// Copies bytes from the source address to the destination address.
///
diff --git a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs
index 378fabf56836f..6fa6c5460dbb8 100644
--- a/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs
+++ b/src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/RuntimeExports.cs
@@ -75,6 +75,9 @@ public static unsafe object RhNewArray(MethodTable* pEEType, int length)
[RuntimeExport("RhBox")]
public static unsafe object RhBox(MethodTable* pEEType, ref byte data)
{
+ // A null can be passed for boxing of a null ref.
+ _ = Unsafe.ReadUnaligned(ref data);
+
ref byte dataAdjustedForNullable = ref data;
// Can box non-ByRefLike value types only (which also implies no finalizers).
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 262ddb9857b75..c790682ecfcbc 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
@@ -361,6 +361,44 @@ public static unsafe object GetUninitializedObject(
return RuntimeImports.RhNewObject(mt);
}
+
+ ///
+ /// Create a boxed object of the specified type from the data located at the target reference.
+ ///
+ /// The target data
+ /// The type of box to create.
+ /// A boxed object containing the specified data.
+ /// The specified type handle is null.
+ /// The specified type cannot have a boxed instance of itself created.
+ /// The passed in type is a by-ref-like type.
+ public static unsafe object? Box(ref byte target, RuntimeTypeHandle type)
+ {
+ if (type.IsNull)
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type);
+
+ MethodTable* mt = type.ToMethodTable();
+
+ if (mt->ElementType == EETypeElementType.Void || mt->IsGenericTypeDefinition || mt->IsByRef || mt->IsPointer || mt->IsFunctionPointer)
+ throw new ArgumentException(SR.Arg_TypeNotSupported);
+
+ if (mt->NumVtableSlots == 0)
+ {
+ // This is a type without a vtable or GCDesc. We must not allow creating an instance of it
+ throw ReflectionCoreExecution.ExecutionEnvironment.CreateMissingMetadataException(Type.GetTypeFromHandle(type));
+ }
+ // Paranoid check: not-meant-for-GC-heap types should be reliably identifiable by empty vtable.
+ Debug.Assert(!mt->ContainsGCPointers || RuntimeImports.RhGetGCDescSize(mt) != 0);
+
+ if (!mt->IsValueType)
+ {
+ return Unsafe.As(ref target);
+ }
+
+ if (mt->IsByRefLike)
+ throw new NotSupportedException(SR.NotSupported_ByRefLike);
+
+ return RuntimeImports.RhBox(mt, ref target);
+ }
}
// CLR arrays are laid out in memory as follows (multidimensional array bounds are optional):
diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/TypedReference.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/TypedReference.cs
index 3b209cd489c44..56e8ef05dd1cd 100644
--- a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/TypedReference.cs
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/TypedReference.cs
@@ -50,23 +50,18 @@ internal static RuntimeTypeHandle RawTargetTypeToken(TypedReference value)
public static unsafe object ToObject(TypedReference value)
{
- RuntimeTypeHandle typeHandle = value._typeHandle;
- if (typeHandle.IsNull)
+ RuntimeTypeHandle handle = RawTargetTypeToken(value);
+
+ if (handle.IsNull)
ThrowHelper.ThrowArgumentException_ArgumentNull_TypedRefType();
- MethodTable* eeType = typeHandle.ToMethodTable();
- if (eeType->IsValueType)
- {
- return RuntimeImports.RhBox(eeType, ref value.Value);
- }
- else if (eeType->IsPointer || eeType->IsFunctionPointer)
+ MethodTable* mt = handle.ToMethodTable();
+ if (mt->IsPointer || mt->IsFunctionPointer)
{
- return RuntimeImports.RhBox(MethodTable.Of(), ref value.Value);
- }
- else
- {
- return Unsafe.As(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(); }
diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs
index ef4bc568fb56f..0371e3037e265 100644
--- a/src/libraries/System.Runtime/ref/System.Runtime.cs
+++ b/src/libraries/System.Runtime/ref/System.Runtime.cs
@@ -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 CreateSpan(System.RuntimeFieldHandle fldHandle) { throw null; }
public static void EnsureSufficientExecutionStack() { }
public static new bool Equals(object? o1, object? o2) { throw null; }
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 5148ca0c488d8..afb72bbe0949f 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
@@ -437,6 +437,173 @@ public static void FixedAddressValueTypeTest()
Assert.Equal(fixedPtr1, fixedPtr2);
}
+
+ [Fact]
+ public static void BoxPrimitive()
+ {
+ int value = 4;
+ object result = RuntimeHelpers.Box(ref Unsafe.As(ref value), typeof(int).TypeHandle);
+ Assert.Equal(value, Assert.IsType(result));
+ }
+
+ [Fact]
+ public static void BoxPointer()
+ {
+ Assert.Throws(() =>
+ {
+ nint value = 3;
+ object result = RuntimeHelpers.Box(ref Unsafe.As(ref value), typeof(void*).TypeHandle);
+ });
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private ref struct ByRefLikeType
+ {
+ public int i;
+ }
+
+ [Fact]
+ public static void BoxByRefLike()
+ {
+ Assert.Throws(() =>
+ {
+ int value = 3;
+ object result = RuntimeHelpers.Box(ref Unsafe.As(ref value), typeof(ByRefLikeType).TypeHandle);
+ });
+ }
+
+ [Fact]
+ public static void BoxStruct()
+ {
+ Span 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(result));
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ private struct GenericStruct
+ {
+ public T data;
+ }
+
+ [Fact]
+ 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);
+ }
+
+ [Fact]
+ public static void BoxManagedGenericStruct()
+ {
+ object value = new();
+ object result = RuntimeHelpers.Box(ref Unsafe.As