Skip to content

Commit a32aa66

Browse files
committed
Rewrite RuntimeHelpers.Box to insert into the generic cache.
1 parent d5cdf17 commit a32aa66

File tree

9 files changed

+216
-26
lines changed

9 files changed

+216
-26
lines changed

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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -431,32 +431,7 @@ private static unsafe void DispatchTailCalls(
431431
if (type.IsNullHandle())
432432
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.type);
433433

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

462437
[LibraryImport(QCall, EntryPoint = "ReflectionInvocation_SizeOf")]
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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+
if (_pMT->ContainsGenericVariables)
48+
throw new ArgumentException(SR.Arg_TypeNotSupported);
49+
50+
if (_pMT->IsValueType)
51+
{
52+
if (_pMT->IsByRefLike)
53+
throw new NotSupportedException(SR.NotSupported_ByRefLike);
54+
55+
if (MethodTable.AreSameType(_pMT, (MethodTable*)RuntimeTypeHandle.ToIntPtr(typeof(void).TypeHandle)))
56+
throw new ArgumentException(SR.Arg_TypeNotSupported);
57+
58+
GetBoxInfo(rt, out _pfnAllocator, out _allocatorFirstArg, out _nullableValueOffset, out _valueTypeSize);
59+
}
60+
}
61+
62+
internal object? Box(RuntimeType rt, ref byte data)
63+
{
64+
#if DEBUG
65+
if (_originalRuntimeType != rt)
66+
{
67+
Debug.Fail("Caller passed the wrong RuntimeType to this routine."
68+
+ Environment.NewLineConst + "Expected: " + (_originalRuntimeType ?? (object)"<null>")
69+
+ Environment.NewLineConst + "Actual: " + (rt ?? (object)"<null>"));
70+
}
71+
#endif
72+
if (_pfnAllocator == null)
73+
{
74+
// If the allocator is null, then we shouldn't allocate and make a copy,
75+
// we should return the data as the object it currently is.
76+
return Unsafe.As<byte, object>(ref data);
77+
}
78+
79+
ref byte source = ref data;
80+
81+
byte maybeNullableHasValue = Unsafe.ReadUnaligned<byte>(ref source);
82+
83+
if (_nullableValueOffset != 0)
84+
{
85+
if (maybeNullableHasValue == 0)
86+
{
87+
return null;
88+
}
89+
source = ref Unsafe.Add(ref source, _nullableValueOffset);
90+
}
91+
92+
object result = _pfnAllocator(_allocatorFirstArg);
93+
GC.KeepAlive(rt);
94+
95+
if (_pMT->ContainsGCPointers)
96+
{
97+
Buffer.BulkMoveWithWriteBarrier(ref result.GetRawData(), ref source, _valueTypeSize);
98+
}
99+
else
100+
{
101+
SpanHelpers.Memmove(ref result.GetRawData(), ref source, _valueTypeSize);
102+
}
103+
104+
return result;
105+
}
106+
107+
/// <summary>
108+
/// Given a RuntimeType, returns information about how to box instances
109+
/// of it via calli semantics.
110+
/// </summary>
111+
private static void GetBoxInfo(
112+
RuntimeType rt,
113+
out delegate*<void*, object> pfnAllocator,
114+
out void* vAllocatorFirstArg,
115+
out int nullableValueOffset,
116+
out uint valueTypeSize)
117+
{
118+
Debug.Assert(rt != null);
119+
120+
delegate*<void*, object> pfnAllocatorTemp = default;
121+
void* vAllocatorFirstArgTemp = default;
122+
int nullableValueOffsetTemp = default;
123+
uint valueTypeSizeTemp = default;
124+
125+
GetBoxInfo(
126+
new QCallTypeHandle(ref rt),
127+
&pfnAllocatorTemp, &vAllocatorFirstArgTemp,
128+
&nullableValueOffsetTemp, &valueTypeSizeTemp);
129+
130+
pfnAllocator = pfnAllocatorTemp;
131+
vAllocatorFirstArg = vAllocatorFirstArgTemp;
132+
nullableValueOffset = nullableValueOffsetTemp;
133+
valueTypeSize = valueTypeSizeTemp;
134+
}
135+
136+
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "ReflectionInvocation_GetBoxInfo")]
137+
private static partial void GetBoxInfo(
138+
QCallTypeHandle type,
139+
delegate*<void*, object>* ppfnAllocator,
140+
void** pvAllocatorFirstArg,
141+
int* pNullableValueOffset,
142+
uint* pValueTypeSize);
143+
}
144+
145+
internal object? Box(ref byte data)
146+
{
147+
return GetOrCreateCacheEntry<BoxCache>().Box(this, ref data);
148+
}
149+
}
150+
}

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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 9 additions & 0 deletions
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

Lines changed: 2 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2069,3 +2069,47 @@ 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+
*pValueOffset = 0;
2099+
2100+
// If it is a nullable, return the allocator for the underlying type instead.
2101+
if (pMT->IsNullable())
2102+
{
2103+
pMT = pMT->GetInstantiation()[0].GetMethodTable();
2104+
*pValueOffset = Nullable::GetValueAddrOffset(pMT);
2105+
}
2106+
2107+
bool fHasSideEffectsUnused;
2108+
*ppfnAllocator = CEEJitInfo::getHelperFtnStatic(CEEInfo::getNewHelperStatic(pMT, &fHasSideEffectsUnused));
2109+
*pvAllocatorFirstArg = pMT;
2110+
*pValueSize = pMT->GetNumInstanceFieldBytes();
2111+
2112+
pMT->EnsureInstanceActive();
2113+
2114+
END_QCALL;
2115+
}

src/coreclr/vm/reflectioninvocation.h

Lines changed: 7 additions & 0 deletions
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);

0 commit comments

Comments
 (0)