diff --git a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
index f14f27121b6d35..1586ba41321204 100644
--- a/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
+++ b/src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj
@@ -237,6 +237,7 @@
Common\System\Collections\Generic\ArrayBuilder.cs
+
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 43ddbfecf006fa..631b6c9ef41257 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
@@ -431,32 +431,7 @@ private static unsafe void DispatchTailCalls(
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);
- }
+ return type.GetRuntimeType().Box(ref target);
}
[LibraryImport(QCall, EntryPoint = "ReflectionInvocation_SizeOf")]
diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.BoxCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.BoxCache.cs
new file mode 100644
index 00000000000000..b687947f4dde80
--- /dev/null
+++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.BoxCache.cs
@@ -0,0 +1,146 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace System
+{
+ internal sealed partial class RuntimeType
+ {
+ ///
+ /// A cache which allows optimizing .
+ ///
+ internal sealed unsafe partial class BoxCache : IGenericCacheEntry
+ {
+ public static BoxCache Create(RuntimeType type) => new(type);
+ public void InitializeCompositeCache(CompositeCacheEntry compositeEntry) => compositeEntry._boxCache = this;
+ public static ref BoxCache? GetStorageRef(CompositeCacheEntry compositeEntry) => ref compositeEntry._boxCache;
+
+ // The managed calli to the newobj allocator, plus its first argument
+ private readonly delegate* _pfnAllocator;
+ private readonly void* _allocatorFirstArg;
+ private readonly int _nullableValueOffset;
+ private readonly uint _valueTypeSize;
+ private readonly MethodTable* _pMT;
+
+#if DEBUG
+ private readonly RuntimeType _originalRuntimeType;
+#endif
+
+ private BoxCache(RuntimeType rt)
+ {
+ Debug.Assert(rt != null);
+
+#if DEBUG
+ _originalRuntimeType = rt;
+#endif
+
+ TypeHandle handle = rt.TypeHandle.GetNativeTypeHandle();
+
+ if (handle.IsTypeDesc)
+ throw new ArgumentException(SR.Arg_TypeNotSupported);
+
+ _pMT = handle.AsMethodTable();
+
+ // For value types, this is checked in GetBoxInfo,
+ // but for non-value types, we still need to check this case for consistent behavior.
+ if (_pMT->ContainsGenericVariables)
+ throw new ArgumentException(SR.Arg_TypeNotSupported);
+
+ if (_pMT->IsValueType)
+ {
+ GetBoxInfo(rt, out _pfnAllocator, out _allocatorFirstArg, out _nullableValueOffset, out _valueTypeSize);
+ }
+ }
+
+ internal object? Box(RuntimeType rt, ref byte data)
+ {
+#if DEBUG
+ if (_originalRuntimeType != rt)
+ {
+ Debug.Fail("Caller passed the wrong RuntimeType to this routine."
+ + Environment.NewLineConst + "Expected: " + (_originalRuntimeType ?? (object)"")
+ + Environment.NewLineConst + "Actual: " + (rt ?? (object)""));
+ }
+#endif
+ if (_pfnAllocator == null)
+ {
+ // If the allocator is null, then we shouldn't allocate and make a copy,
+ // we should return the data as the object it currently is.
+ return Unsafe.As(ref data);
+ }
+
+ ref byte source = ref data;
+
+ byte maybeNullableHasValue = Unsafe.ReadUnaligned(ref source);
+
+ if (_nullableValueOffset != 0)
+ {
+ if (maybeNullableHasValue == 0)
+ {
+ return null;
+ }
+ source = ref Unsafe.Add(ref source, _nullableValueOffset);
+ }
+
+ object result = _pfnAllocator(_allocatorFirstArg);
+ GC.KeepAlive(rt);
+
+ if (_pMT->ContainsGCPointers)
+ {
+ Buffer.BulkMoveWithWriteBarrier(ref result.GetRawData(), ref source, _valueTypeSize);
+ }
+ else
+ {
+ SpanHelpers.Memmove(ref result.GetRawData(), ref source, _valueTypeSize);
+ }
+
+ return result;
+ }
+
+ ///
+ /// Given a RuntimeType, returns information about how to box instances
+ /// of it via calli semantics.
+ ///
+ private static void GetBoxInfo(
+ RuntimeType rt,
+ out delegate* pfnAllocator,
+ out void* vAllocatorFirstArg,
+ out int nullableValueOffset,
+ out uint valueTypeSize)
+ {
+ Debug.Assert(rt != null);
+
+ delegate* pfnAllocatorTemp = default;
+ void* vAllocatorFirstArgTemp = default;
+ int nullableValueOffsetTemp = default;
+ uint valueTypeSizeTemp = default;
+
+ GetBoxInfo(
+ new QCallTypeHandle(ref rt),
+ &pfnAllocatorTemp, &vAllocatorFirstArgTemp,
+ &nullableValueOffsetTemp, &valueTypeSizeTemp);
+
+ pfnAllocator = pfnAllocatorTemp;
+ vAllocatorFirstArg = vAllocatorFirstArgTemp;
+ nullableValueOffset = nullableValueOffsetTemp;
+ valueTypeSize = valueTypeSizeTemp;
+ }
+
+ [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ReflectionInvocation_GetBoxInfo")]
+ private static partial void GetBoxInfo(
+ QCallTypeHandle type,
+ delegate** ppfnAllocator,
+ void** pvAllocatorFirstArg,
+ int* pNullableValueOffset,
+ uint* pValueTypeSize);
+ }
+
+ internal object? Box(ref byte data)
+ {
+ return GetOrCreateCacheEntry().Box(this, ref data);
+ }
+ }
+}
diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs
index f7cb6b6060c79f..d59e096828c75c 100644
--- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs
+++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs
@@ -22,6 +22,7 @@ internal sealed class CompositeCacheEntry : IGenericCacheEntry
internal RuntimeTypeCache.FunctionPointerCache? _functionPointerCache;
internal Array.ArrayInitializeCache? _arrayInitializeCache;
internal IGenericCacheEntry? _enumInfo;
+ internal BoxCache? _boxCache;
void IGenericCacheEntry.InitializeCompositeCache(CompositeCacheEntry compositeEntry) => throw new UnreachableException();
}
diff --git a/src/coreclr/vm/object.cpp b/src/coreclr/vm/object.cpp
index cf41103441844c..c98607ade0a826 100644
--- a/src/coreclr/vm/object.cpp
+++ b/src/coreclr/vm/object.cpp
@@ -1623,6 +1623,15 @@ BOOL Nullable::IsNullableForTypeHelperNoGC(MethodTable* nullableMT, MethodTable*
}
//===============================================================================
+int32_t Nullable::GetValueAddrOffset(MethodTable* nullableMT)
+{
+ LIMITED_METHOD_CONTRACT;
+
+ _ASSERTE(IsNullableType(nullableMT));
+ _ASSERTE(strcmp(nullableMT->GetApproxFieldDescListRaw()[1].GetDebugName(), "value") == 0);
+ return nullableMT->GetApproxFieldDescListRaw()[1].GetOffset();
+}
+
CLR_BOOL* Nullable::HasValueAddr(MethodTable* nullableMT) {
LIMITED_METHOD_CONTRACT;
diff --git a/src/coreclr/vm/object.h b/src/coreclr/vm/object.h
index c486177ee3187c..c2bf72c1dee02b 100644
--- a/src/coreclr/vm/object.h
+++ b/src/coreclr/vm/object.h
@@ -2473,6 +2473,8 @@ class Nullable {
return nullable->ValueAddr(nullableMT);
}
+ static int32_t GetValueAddrOffset(MethodTable* nullableMT);
+
private:
static BOOL IsNullableForTypeHelper(MethodTable* nullableMT, MethodTable* paramMT);
static BOOL IsNullableForTypeHelperNoGC(MethodTable* nullableMT, MethodTable* paramMT);
diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp
index e038db62831d93..4008a5957eaac8 100644
--- a/src/coreclr/vm/qcallentrypoints.cpp
+++ b/src/coreclr/vm/qcallentrypoints.cpp
@@ -340,6 +340,7 @@ static const Entry s_QCall[] =
DllImportEntry(ReflectionInvocation_CompileMethod)
DllImportEntry(ReflectionInvocation_PrepareMethod)
DllImportEntry(ReflectionInvocation_SizeOf)
+ DllImportEntry(ReflectionInvocation_GetBoxInfo)
DllImportEntry(ReflectionSerialization_GetCreateUninitializedObjectInfo)
#if defined(FEATURE_COMWRAPPERS)
DllImportEntry(ComWrappers_GetIUnknownImpl)
diff --git a/src/coreclr/vm/reflectioninvocation.cpp b/src/coreclr/vm/reflectioninvocation.cpp
index 6c240448ddfd67..a98dafd62a558b 100644
--- a/src/coreclr/vm/reflectioninvocation.cpp
+++ b/src/coreclr/vm/reflectioninvocation.cpp
@@ -2069,3 +2069,49 @@ extern "C" int32_t QCALLTYPE ReflectionInvocation_SizeOf(QCall::TypeHandle pType
return handle.GetSize();
}
+
+extern "C" void QCALLTYPE ReflectionInvocation_GetBoxInfo(
+ QCall::TypeHandle pType,
+ PCODE* ppfnAllocator,
+ void** pvAllocatorFirstArg,
+ int32_t* pValueOffset,
+ uint32_t* pValueSize)
+{
+ CONTRACTL
+ {
+ QCALL_CHECK;
+ PRECONDITION(CheckPointer(ppfnAllocator));
+ PRECONDITION(CheckPointer(pvAllocatorFirstArg));
+ PRECONDITION(*ppfnAllocator == NULL);
+ PRECONDITION(*pvAllocatorFirstArg == NULL);
+ }
+ CONTRACTL_END;
+
+ BEGIN_QCALL;
+
+ TypeHandle type = pType.AsTypeHandle();
+
+ RuntimeTypeHandle::ValidateTypeAbleToBeInstantiated(type, true /* fForGetUninitializedInstance */);
+
+ MethodTable* pMT = type.AsMethodTable();
+
+ _ASSERTE(pMT->IsValueType() || pMT->IsNullable() || pMT->IsEnum() || pMT->IsTruePrimitive());
+
+ *pValueOffset = 0;
+
+ // If it is a nullable, return the allocator for the underlying type instead.
+ if (pMT->IsNullable())
+ {
+ *pValueOffset = Nullable::GetValueAddrOffset(pMT);
+ pMT = pMT->GetInstantiation()[0].GetMethodTable();
+ }
+
+ bool fHasSideEffectsUnused;
+ *ppfnAllocator = CEEJitInfo::getHelperFtnStatic(CEEInfo::getNewHelperStatic(pMT, &fHasSideEffectsUnused));
+ *pvAllocatorFirstArg = pMT;
+ *pValueSize = pMT->GetNumInstanceFieldBytes();
+
+ pMT->EnsureInstanceActive();
+
+ END_QCALL;
+}
\ No newline at end of file
diff --git a/src/coreclr/vm/reflectioninvocation.h b/src/coreclr/vm/reflectioninvocation.h
index 3bbf1d57bb1ed7..c26f137cdd9278 100644
--- a/src/coreclr/vm/reflectioninvocation.h
+++ b/src/coreclr/vm/reflectioninvocation.h
@@ -72,6 +72,13 @@ extern "C" void QCALLTYPE ReflectionInvocation_PrepareMethod(MethodDesc* pMD, Ty
extern "C" void QCALLTYPE ReflectionSerialization_GetCreateUninitializedObjectInfo(QCall::TypeHandle pType, PCODE* ppfnAllocator, void** pvAllocatorFirstArg);
+extern "C" void QCALLTYPE ReflectionInvocation_GetBoxInfo(
+ QCall::TypeHandle pType,
+ PCODE* ppfnAllocator,
+ void** pvAllocatorFirstArg,
+ int32_t* pValueOffset,
+ uint32_t* pValueSize);
+
class ReflectionEnum {
public:
static FCDECL1(INT32, InternalGetCorElementType, MethodTable* pMT);
diff --git a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs
index 9be5b3d817dced..66d1ba879501f6 100644
--- a/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs
+++ b/src/mono/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs
@@ -214,7 +214,7 @@ private static extern unsafe IntPtr GetSpanDataFrom(
private static extern bool SufficientExecutionStack();
[MethodImplAttribute(MethodImplOptions.InternalCall)]
- private static extern void InternalBox(QCallTypeHandle type, ref byte target, ObjectHandleOnStack result);
+ private static extern object InternalBox(QCallTypeHandle type, ref byte target);
///
/// Create a boxed object of the specified type from the data located at the target reference.
@@ -255,8 +255,7 @@ private static extern unsafe IntPtr GetSpanDataFrom(
if (rtType.IsByRefLike)
throw new NotSupportedException(SR.NotSupported_ByRefLike);
- object? result = null;
- InternalBox(new QCallTypeHandle(ref rtType), ref target, ObjectHandleOnStack.Create(ref result));
+ object? result = InternalBox(new QCallTypeHandle(ref rtType), ref target);
return result;
}
diff --git a/src/mono/mono/metadata/icall-def.h b/src/mono/mono/metadata/icall-def.h
index 53c95c8245faeb..ad7044a08439ff 100644
--- a/src/mono/mono/metadata/icall-def.h
+++ b/src/mono/mono/metadata/icall-def.h
@@ -434,7 +434,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_8, "InternalBox", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalBox, MonoObject, 2, (MonoQCallTypeHandle, char_ref))
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))
diff --git a/src/mono/mono/metadata/icall.c b/src/mono/mono/metadata/icall.c
index a1ab947afd36c1..be76808491569d 100644
--- a/src/mono/mono/metadata/icall.c
+++ b/src/mono/mono/metadata/icall.c
@@ -1213,8 +1213,8 @@ 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)
+MonoObjectHandle
+ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalBox (MonoQCallTypeHandle type_handle, char* data, MonoError *error)
{
MonoType *type = type_handle.type;
MonoClass *klass = mono_class_from_mono_type_internal (type);
@@ -1222,15 +1222,9 @@ ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalBox (MonoQCallT
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);
+ return_val_if_nok (error, NULL_HANDLE);
- HANDLE_ON_STACK_SET(obj, raw_obj);
- return;
-error_ret:
- HANDLE_ON_STACK_SET (obj, NULL);
+ return mono_value_box_handle (klass, data, error);
}
gint32