Skip to content

Commit 1753956

Browse files
authored
Fix regression from RuntimeType.AllocateValueType rewrite (#101137)
1 parent 837eb27 commit 1753956

File tree

12 files changed

+221
-40
lines changed

12 files changed

+221
-40
lines changed

src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@
237237
<Compile Include="$(CommonPath)System\Collections\Generic\ArrayBuilder.cs">
238238
<Link>Common\System\Collections\Generic\ArrayBuilder.cs</Link>
239239
</Compile>
240+
<Compile Include="src\System\RuntimeType.BoxCache.cs" />
240241
<Compile Include="src\System\RuntimeType.CreateUninitializedCache.CoreCLR.cs" />
241242
<Compile Include="src\System\RuntimeType.GenericCache.cs" />
242243
</ItemGroup>

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

+1-26
Original file line numberDiff line numberDiff line change
@@ -432,32 +432,7 @@ private static unsafe void DispatchTailCalls(
432432
if (type.IsNullHandle())
433433
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type);
434434

435-
TypeHandle handle = type.GetNativeTypeHandle();
436-
437-
if (handle.IsTypeDesc)
438-
throw new ArgumentException(SR.Arg_TypeNotSupported);
439-
440-
MethodTable* pMT = handle.AsMethodTable();
441-
442-
if (pMT->ContainsGenericVariables)
443-
throw new ArgumentException(SR.Arg_TypeNotSupported);
444-
445-
if (pMT->IsValueType)
446-
{
447-
if (pMT->IsByRefLike)
448-
throw new NotSupportedException(SR.NotSupported_ByRefLike);
449-
450-
if (MethodTable.AreSameType(pMT, (MethodTable*)RuntimeTypeHandle.ToIntPtr(typeof(void).TypeHandle)))
451-
throw new ArgumentException(SR.Arg_TypeNotSupported);
452-
453-
object? result = Box(pMT, ref target);
454-
GC.KeepAlive(type);
455-
return result;
456-
}
457-
else
458-
{
459-
return Unsafe.As<byte, object?>(ref target);
460-
}
435+
return type.GetRuntimeType().Box(ref target);
461436
}
462437

463438
[LibraryImport(QCall, EntryPoint = "ReflectionInvocation_SizeOf")]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
7+
8+
namespace System
9+
{
10+
internal sealed partial class RuntimeType
11+
{
12+
/// <summary>
13+
/// A cache which allows optimizing <see cref="RuntimeHelpers.Box(ref byte, RuntimeTypeHandle)"/>.
14+
/// </summary>
15+
internal sealed unsafe partial class BoxCache : IGenericCacheEntry<BoxCache>
16+
{
17+
public static BoxCache Create(RuntimeType type) => new(type);
18+
public void InitializeCompositeCache(CompositeCacheEntry compositeEntry) => compositeEntry._boxCache = this;
19+
public static ref BoxCache? GetStorageRef(CompositeCacheEntry compositeEntry) => ref compositeEntry._boxCache;
20+
21+
// The managed calli to the newobj allocator, plus its first argument
22+
private readonly delegate*<void*, object> _pfnAllocator;
23+
private readonly void* _allocatorFirstArg;
24+
private readonly int _nullableValueOffset;
25+
private readonly uint _valueTypeSize;
26+
private readonly MethodTable* _pMT;
27+
28+
#if DEBUG
29+
private readonly RuntimeType _originalRuntimeType;
30+
#endif
31+
32+
private BoxCache(RuntimeType rt)
33+
{
34+
Debug.Assert(rt != null);
35+
36+
#if DEBUG
37+
_originalRuntimeType = rt;
38+
#endif
39+
40+
TypeHandle handle = rt.TypeHandle.GetNativeTypeHandle();
41+
42+
if (handle.IsTypeDesc)
43+
throw new ArgumentException(SR.Arg_TypeNotSupported);
44+
45+
_pMT = handle.AsMethodTable();
46+
47+
// For value types, this is checked in GetBoxInfo,
48+
// but for non-value types, we still need to check this case for consistent behavior.
49+
if (_pMT->ContainsGenericVariables)
50+
throw new ArgumentException(SR.Arg_TypeNotSupported);
51+
52+
if (_pMT->IsValueType)
53+
{
54+
GetBoxInfo(rt, out _pfnAllocator, out _allocatorFirstArg, out _nullableValueOffset, out _valueTypeSize);
55+
}
56+
}
57+
58+
internal object? Box(RuntimeType rt, ref byte data)
59+
{
60+
#if DEBUG
61+
if (_originalRuntimeType != rt)
62+
{
63+
Debug.Fail("Caller passed the wrong RuntimeType to this routine."
64+
+ Environment.NewLineConst + "Expected: " + (_originalRuntimeType ?? (object)"<null>")
65+
+ Environment.NewLineConst + "Actual: " + (rt ?? (object)"<null>"));
66+
}
67+
#endif
68+
if (_pfnAllocator == null)
69+
{
70+
// If the allocator is null, then we shouldn't allocate and make a copy,
71+
// we should return the data as the object it currently is.
72+
return Unsafe.As<byte, object>(ref data);
73+
}
74+
75+
ref byte source = ref data;
76+
77+
byte maybeNullableHasValue = Unsafe.ReadUnaligned<byte>(ref source);
78+
79+
if (_nullableValueOffset != 0)
80+
{
81+
if (maybeNullableHasValue == 0)
82+
{
83+
return null;
84+
}
85+
source = ref Unsafe.Add(ref source, _nullableValueOffset);
86+
}
87+
88+
object result = _pfnAllocator(_allocatorFirstArg);
89+
GC.KeepAlive(rt);
90+
91+
if (_pMT->ContainsGCPointers)
92+
{
93+
Buffer.BulkMoveWithWriteBarrier(ref result.GetRawData(), ref source, _valueTypeSize);
94+
}
95+
else
96+
{
97+
SpanHelpers.Memmove(ref result.GetRawData(), ref source, _valueTypeSize);
98+
}
99+
100+
return result;
101+
}
102+
103+
/// <summary>
104+
/// Given a RuntimeType, returns information about how to box instances
105+
/// of it via calli semantics.
106+
/// </summary>
107+
private static void GetBoxInfo(
108+
RuntimeType rt,
109+
out delegate*<void*, object> pfnAllocator,
110+
out void* vAllocatorFirstArg,
111+
out int nullableValueOffset,
112+
out uint valueTypeSize)
113+
{
114+
Debug.Assert(rt != null);
115+
116+
delegate*<void*, object> pfnAllocatorTemp = default;
117+
void* vAllocatorFirstArgTemp = default;
118+
int nullableValueOffsetTemp = default;
119+
uint valueTypeSizeTemp = default;
120+
121+
GetBoxInfo(
122+
new QCallTypeHandle(ref rt),
123+
&pfnAllocatorTemp, &vAllocatorFirstArgTemp,
124+
&nullableValueOffsetTemp, &valueTypeSizeTemp);
125+
126+
pfnAllocator = pfnAllocatorTemp;
127+
vAllocatorFirstArg = vAllocatorFirstArgTemp;
128+
nullableValueOffset = nullableValueOffsetTemp;
129+
valueTypeSize = valueTypeSizeTemp;
130+
}
131+
132+
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ReflectionInvocation_GetBoxInfo")]
133+
private static partial void GetBoxInfo(
134+
QCallTypeHandle type,
135+
delegate*<void*, object>* ppfnAllocator,
136+
void** pvAllocatorFirstArg,
137+
int* pNullableValueOffset,
138+
uint* pValueTypeSize);
139+
}
140+
141+
internal object? Box(ref byte data)
142+
{
143+
return GetOrCreateCacheEntry<BoxCache>().Box(this, ref data);
144+
}
145+
}
146+
}

src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.cs

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ internal sealed class CompositeCacheEntry : IGenericCacheEntry
2222
internal RuntimeTypeCache.FunctionPointerCache? _functionPointerCache;
2323
internal Array.ArrayInitializeCache? _arrayInitializeCache;
2424
internal IGenericCacheEntry? _enumInfo;
25+
internal BoxCache? _boxCache;
2526

2627
void IGenericCacheEntry.InitializeCompositeCache(CompositeCacheEntry compositeEntry) => throw new UnreachableException();
2728
}

src/coreclr/vm/object.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -1623,6 +1623,15 @@ BOOL Nullable::IsNullableForTypeHelperNoGC(MethodTable* nullableMT, MethodTable*
16231623
}
16241624

16251625
//===============================================================================
1626+
int32_t Nullable::GetValueAddrOffset(MethodTable* nullableMT)
1627+
{
1628+
LIMITED_METHOD_CONTRACT;
1629+
1630+
_ASSERTE(IsNullableType(nullableMT));
1631+
_ASSERTE(strcmp(nullableMT->GetApproxFieldDescListRaw()[1].GetDebugName(), "value") == 0);
1632+
return nullableMT->GetApproxFieldDescListRaw()[1].GetOffset();
1633+
}
1634+
16261635
CLR_BOOL* Nullable::HasValueAddr(MethodTable* nullableMT) {
16271636

16281637
LIMITED_METHOD_CONTRACT;

src/coreclr/vm/object.h

+2
Original file line numberDiff line numberDiff line change
@@ -2473,6 +2473,8 @@ class Nullable {
24732473
return nullable->ValueAddr(nullableMT);
24742474
}
24752475

2476+
static int32_t GetValueAddrOffset(MethodTable* nullableMT);
2477+
24762478
private:
24772479
static BOOL IsNullableForTypeHelper(MethodTable* nullableMT, MethodTable* paramMT);
24782480
static BOOL IsNullableForTypeHelperNoGC(MethodTable* nullableMT, MethodTable* paramMT);

src/coreclr/vm/qcallentrypoints.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ static const Entry s_QCall[] =
340340
DllImportEntry(ReflectionInvocation_CompileMethod)
341341
DllImportEntry(ReflectionInvocation_PrepareMethod)
342342
DllImportEntry(ReflectionInvocation_SizeOf)
343+
DllImportEntry(ReflectionInvocation_GetBoxInfo)
343344
DllImportEntry(ReflectionSerialization_GetCreateUninitializedObjectInfo)
344345
#if defined(FEATURE_COMWRAPPERS)
345346
DllImportEntry(ComWrappers_GetIUnknownImpl)

src/coreclr/vm/reflectioninvocation.cpp

+46
Original file line numberDiff line numberDiff line change
@@ -2069,3 +2069,49 @@ extern "C" int32_t QCALLTYPE ReflectionInvocation_SizeOf(QCall::TypeHandle pType
20692069

20702070
return handle.GetSize();
20712071
}
2072+
2073+
extern "C" void QCALLTYPE ReflectionInvocation_GetBoxInfo(
2074+
QCall::TypeHandle pType,
2075+
PCODE* ppfnAllocator,
2076+
void** pvAllocatorFirstArg,
2077+
int32_t* pValueOffset,
2078+
uint32_t* pValueSize)
2079+
{
2080+
CONTRACTL
2081+
{
2082+
QCALL_CHECK;
2083+
PRECONDITION(CheckPointer(ppfnAllocator));
2084+
PRECONDITION(CheckPointer(pvAllocatorFirstArg));
2085+
PRECONDITION(*ppfnAllocator == NULL);
2086+
PRECONDITION(*pvAllocatorFirstArg == NULL);
2087+
}
2088+
CONTRACTL_END;
2089+
2090+
BEGIN_QCALL;
2091+
2092+
TypeHandle type = pType.AsTypeHandle();
2093+
2094+
RuntimeTypeHandle::ValidateTypeAbleToBeInstantiated(type, true /* fForGetUninitializedInstance */);
2095+
2096+
MethodTable* pMT = type.AsMethodTable();
2097+
2098+
_ASSERTE(pMT->IsValueType() || pMT->IsNullable() || pMT->IsEnum() || pMT->IsTruePrimitive());
2099+
2100+
*pValueOffset = 0;
2101+
2102+
// If it is a nullable, return the allocator for the underlying type instead.
2103+
if (pMT->IsNullable())
2104+
{
2105+
*pValueOffset = Nullable::GetValueAddrOffset(pMT);
2106+
pMT = pMT->GetInstantiation()[0].GetMethodTable();
2107+
}
2108+
2109+
bool fHasSideEffectsUnused;
2110+
*ppfnAllocator = CEEJitInfo::getHelperFtnStatic(CEEInfo::getNewHelperStatic(pMT, &fHasSideEffectsUnused));
2111+
*pvAllocatorFirstArg = pMT;
2112+
*pValueSize = pMT->GetNumInstanceFieldBytes();
2113+
2114+
pMT->EnsureInstanceActive();
2115+
2116+
END_QCALL;
2117+
}

src/coreclr/vm/reflectioninvocation.h

+7
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ extern "C" void QCALLTYPE ReflectionInvocation_PrepareMethod(MethodDesc* pMD, Ty
7272

7373
extern "C" void QCALLTYPE ReflectionSerialization_GetCreateUninitializedObjectInfo(QCall::TypeHandle pType, PCODE* ppfnAllocator, void** pvAllocatorFirstArg);
7474

75+
extern "C" void QCALLTYPE ReflectionInvocation_GetBoxInfo(
76+
QCall::TypeHandle pType,
77+
PCODE* ppfnAllocator,
78+
void** pvAllocatorFirstArg,
79+
int32_t* pValueOffset,
80+
uint32_t* pValueSize);
81+
7582
class ReflectionEnum {
7683
public:
7784
static FCDECL1(INT32, InternalGetCorElementType, MethodTable* pMT);

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

+2-3
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ private static extern unsafe IntPtr GetSpanDataFrom(
214214
private static extern bool SufficientExecutionStack();
215215

216216
[MethodImplAttribute(MethodImplOptions.InternalCall)]
217-
private static extern void InternalBox(QCallTypeHandle type, ref byte target, ObjectHandleOnStack result);
217+
private static extern object InternalBox(QCallTypeHandle type, ref byte target);
218218

219219
/// <summary>
220220
/// 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(
255255
if (rtType.IsByRefLike)
256256
throw new NotSupportedException(SR.NotSupported_ByRefLike);
257257

258-
object? result = null;
259-
InternalBox(new QCallTypeHandle(ref rtType), ref target, ObjectHandleOnStack.Create(ref result));
258+
object? result = InternalBox(new QCallTypeHandle(ref rtType), ref target);
260259
return result;
261260
}
262261

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ HANDLES(RUNH_1, "GetObjectValue", ves_icall_System_Runtime_CompilerServices_Runt
434434
HANDLES(RUNH_6, "GetSpanDataFrom", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_GetSpanDataFrom, gpointer, 3, (MonoClassField_ptr, MonoType_ptr, gpointer))
435435
HANDLES(RUNH_2, "GetUninitializedObjectInternal", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_GetUninitializedObjectInternal, MonoObject, 1, (MonoType_ptr))
436436
HANDLES(RUNH_3, "InitializeArray", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InitializeArray, void, 2, (MonoArray, MonoClassField_ptr))
437-
HANDLES(RUNH_8, "InternalBox", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalBox, void, 3, (MonoQCallTypeHandle, char_ref, MonoObjectHandleOnStack))
437+
HANDLES(RUNH_8, "InternalBox", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalBox, MonoObject, 2, (MonoQCallTypeHandle, char_ref))
438438
HANDLES(RUNH_7, "InternalGetHashCode", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalGetHashCode, int, 1, (MonoObject))
439439
HANDLES(RUNH_3a, "PrepareMethod", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_PrepareMethod, void, 3, (MonoMethod_ptr, gpointer, int))
440440
HANDLES(RUNH_4, "RunClassConstructor", ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_RunClassConstructor, void, 1, (MonoType_ptr))

src/mono/mono/metadata/icall.c

+4-10
Original file line numberDiff line numberDiff line change
@@ -1213,24 +1213,18 @@ ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_PrepareMethod (MonoMeth
12131213
// FIXME: Implement
12141214
}
12151215

1216-
void
1217-
ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalBox (MonoQCallTypeHandle type_handle, char* data, MonoObjectHandleOnStack obj, MonoError *error)
1216+
MonoObjectHandle
1217+
ves_icall_System_Runtime_CompilerServices_RuntimeHelpers_InternalBox (MonoQCallTypeHandle type_handle, char* data, MonoError *error)
12181218
{
12191219
MonoType *type = type_handle.type;
12201220
MonoClass *klass = mono_class_from_mono_type_internal (type);
12211221

12221222
g_assert (m_class_is_valuetype (klass));
12231223

12241224
mono_class_init_checked (klass, error);
1225-
goto_if_nok (error, error_ret);
1226-
1227-
MonoObject* raw_obj = mono_value_box_checked (klass, data, error);
1228-
goto_if_nok (error, error_ret);
1225+
return_val_if_nok (error, NULL_HANDLE);
12291226

1230-
HANDLE_ON_STACK_SET(obj, raw_obj);
1231-
return;
1232-
error_ret:
1233-
HANDLE_ON_STACK_SET (obj, NULL);
1227+
return mono_value_box_handle (klass, data, error);
12341228
}
12351229

12361230
gint32

0 commit comments

Comments
 (0)