Skip to content

Commit 7b71281

Browse files
authored
Convert Array.IsSimpleCopy and CanAssignArray type to managed (#104103)
* Revert "Push back changes around IsSimpleCopy and CanAssignArrayType" This reverts commit 4ce4f51. * Reduce use range of CorElementType * Add test for pointer array * Rearrange test * NativeAot compat * Mono compat * Disable test on mono * Don't lookup cast cache in QCall
1 parent acfb91f commit 7b71281

File tree

11 files changed

+195
-195
lines changed

11 files changed

+195
-195
lines changed

src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs

+105-24
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array de
6262
if ((uint)(destinationIndex + length) > destinationArray.NativeLength)
6363
throw new ArgumentException(SR.Arg_LongerThanDestArray, nameof(destinationArray));
6464

65-
if (sourceArray.GetType() == destinationArray.GetType() || IsSimpleCopy(sourceArray, destinationArray))
65+
ArrayAssignType assignType = ArrayAssignType.WrongType;
66+
67+
if (sourceArray.GetType() == destinationArray.GetType()
68+
|| (assignType = CanAssignArrayType(sourceArray, destinationArray)) == ArrayAssignType.SimpleCopy)
6669
{
6770
MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray);
6871

@@ -86,44 +89,50 @@ private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array de
8689
throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_ConstrainedCopy);
8790

8891
// Rare
89-
CopySlow(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
92+
CopySlow(sourceArray, sourceIndex, destinationArray, destinationIndex, length, assignType);
9093
}
9194

92-
[MethodImpl(MethodImplOptions.InternalCall)]
93-
private static extern bool IsSimpleCopy(Array sourceArray, Array destinationArray);
95+
private static CorElementType GetNormalizedIntegralArrayElementType(CorElementType elementType)
96+
{
97+
Debug.Assert(elementType.IsPrimitiveType());
98+
99+
// Array Primitive types such as E_T_I4 and E_T_U4 are interchangeable
100+
// Enums with interchangeable underlying types are interchangeable
101+
// BOOL is NOT interchangeable with I1/U1, neither CHAR -- with I2/U2
102+
103+
// U1/U2/U4/U8/U
104+
int shift = (0b0010_0000_0000_0000_1010_1010_0000 >> (int)elementType) & 1;
105+
return (CorElementType)((int)elementType - shift);
106+
}
94107

95108
// Reliability-wise, this method will either possibly corrupt your
96109
// instance & might fail when called from within a CER, or if the
97110
// reliable flag is true, it will either always succeed or always
98111
// throw an exception with no side effects.
99-
private static unsafe void CopySlow(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
112+
private static unsafe void CopySlow(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, ArrayAssignType assignType)
100113
{
101114
Debug.Assert(sourceArray.Rank == destinationArray.Rank);
102115

103-
void* srcTH = RuntimeHelpers.GetMethodTable(sourceArray)->ElementType;
104-
void* destTH = RuntimeHelpers.GetMethodTable(destinationArray)->ElementType;
105-
AssignArrayEnum r = CanAssignArrayType(srcTH, destTH);
106-
107-
if (r == AssignArrayEnum.AssignWrongType)
116+
if (assignType == ArrayAssignType.WrongType)
108117
throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType);
109118

110119
if (length > 0)
111120
{
112-
switch (r)
121+
switch (assignType)
113122
{
114-
case AssignArrayEnum.AssignUnboxValueClass:
123+
case ArrayAssignType.UnboxValueClass:
115124
CopyImplUnBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
116125
break;
117126

118-
case AssignArrayEnum.AssignBoxValueClassOrPrimitive:
127+
case ArrayAssignType.BoxValueClassOrPrimitive:
119128
CopyImplBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
120129
break;
121130

122-
case AssignArrayEnum.AssignMustCast:
131+
case ArrayAssignType.MustCast:
123132
CopyImplCastCheckEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
124133
break;
125134

126-
case AssignArrayEnum.AssignPrimitiveWiden:
135+
case ArrayAssignType.PrimitiveWiden:
127136
CopyImplPrimitiveWiden(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
128137
break;
129138

@@ -134,18 +143,90 @@ private static unsafe void CopySlow(Array sourceArray, int sourceIndex, Array de
134143
}
135144
}
136145

137-
// Must match the definition in arraynative.cpp
138-
private enum AssignArrayEnum
146+
private enum ArrayAssignType
139147
{
140-
AssignWrongType,
141-
AssignMustCast,
142-
AssignBoxValueClassOrPrimitive,
143-
AssignUnboxValueClass,
144-
AssignPrimitiveWiden,
148+
SimpleCopy,
149+
WrongType,
150+
MustCast,
151+
BoxValueClassOrPrimitive,
152+
UnboxValueClass,
153+
PrimitiveWiden,
145154
}
146155

147-
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Array_CanAssignArrayType")]
148-
private static unsafe partial AssignArrayEnum CanAssignArrayType(void* srcTH, void* dstTH);
156+
private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Array destinationArray)
157+
{
158+
TypeHandle srcTH = RuntimeHelpers.GetMethodTable(sourceArray)->GetArrayElementTypeHandle();
159+
TypeHandle destTH = RuntimeHelpers.GetMethodTable(destinationArray)->GetArrayElementTypeHandle();
160+
161+
if (TypeHandle.AreSameType(srcTH, destTH)) // This check kicks for different array kind or dimensions
162+
return ArrayAssignType.SimpleCopy;
163+
164+
if (!srcTH.IsTypeDesc && !destTH.IsTypeDesc)
165+
{
166+
MethodTable* pMTsrc = srcTH.AsMethodTable();
167+
MethodTable* pMTdest = destTH.AsMethodTable();
168+
169+
// Value class boxing
170+
if (pMTsrc->IsValueType && !pMTdest->IsValueType)
171+
{
172+
if (srcTH.CanCastTo(destTH))
173+
return ArrayAssignType.BoxValueClassOrPrimitive;
174+
else
175+
return ArrayAssignType.WrongType;
176+
}
177+
178+
// Value class unboxing.
179+
if (!pMTsrc->IsValueType && pMTdest->IsValueType)
180+
{
181+
if (srcTH.CanCastTo(destTH))
182+
return ArrayAssignType.UnboxValueClass;
183+
else if (destTH.CanCastTo(srcTH)) // V extends IV. Copying from IV to V, or Object to V.
184+
return ArrayAssignType.UnboxValueClass;
185+
else
186+
return ArrayAssignType.WrongType;
187+
}
188+
189+
// Copying primitives from one type to another
190+
if (pMTsrc->IsPrimitive && pMTdest->IsPrimitive)
191+
{
192+
CorElementType srcElType = pMTsrc->GetPrimitiveCorElementType();
193+
CorElementType destElType = pMTdest->GetPrimitiveCorElementType();
194+
195+
if (GetNormalizedIntegralArrayElementType(srcElType) == GetNormalizedIntegralArrayElementType(destElType))
196+
return ArrayAssignType.SimpleCopy;
197+
else if (RuntimeHelpers.CanPrimitiveWiden(srcElType, destElType))
198+
return ArrayAssignType.PrimitiveWiden;
199+
else
200+
return ArrayAssignType.WrongType;
201+
}
202+
203+
// src Object extends dest
204+
if (srcTH.CanCastTo(destTH))
205+
return ArrayAssignType.SimpleCopy;
206+
207+
// dest Object extends src
208+
if (destTH.CanCastTo(srcTH))
209+
return ArrayAssignType.MustCast;
210+
211+
// class X extends/implements src and implements dest.
212+
if (pMTdest->IsInterface)
213+
return ArrayAssignType.MustCast;
214+
215+
// class X implements src and extends/implements dest
216+
if (pMTsrc->IsInterface)
217+
return ArrayAssignType.MustCast;
218+
}
219+
else
220+
{
221+
// Only pointers are valid for TypeDesc in array element
222+
223+
// Compatible pointers
224+
if (srcTH.CanCastTo(destTH))
225+
return ArrayAssignType.SimpleCopy;
226+
}
227+
228+
return ArrayAssignType.WrongType;
229+
}
149230

150231
// Unboxes from an Object[] into a value class or primitive array.
151232
private static unsafe void CopyImplUnBoxEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)

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

+24-1
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ public bool CanCompareBitsOrUseFastGetHashCode
818818
/// <summary>
819819
/// A type handle, which can wrap either a pointer to a <c>TypeDesc</c> or to a <see cref="MethodTable"/>.
820820
/// </summary>
821-
internal unsafe struct TypeHandle
821+
internal readonly unsafe partial struct TypeHandle
822822
{
823823
// Subset of src\vm\typehandle.h
824824

@@ -865,6 +865,29 @@ public static TypeHandle TypeHandleOf<T>()
865865
{
866866
return new TypeHandle((void*)RuntimeTypeHandle.ToIntPtr(typeof(T).TypeHandle));
867867
}
868+
869+
public static bool AreSameType(TypeHandle left, TypeHandle right) => left.m_asTAddr == right.m_asTAddr;
870+
871+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
872+
public bool CanCastTo(TypeHandle destTH)
873+
{
874+
if (m_asTAddr == destTH.m_asTAddr)
875+
return true;
876+
877+
if (!IsTypeDesc && destTH.IsTypeDesc)
878+
return false;
879+
880+
CastResult result = CastCache.TryGet(CastHelpers.s_table!, (nuint)m_asTAddr, (nuint)destTH.m_asTAddr);
881+
882+
if (result != CastResult.MaybeCast)
883+
return result == CastResult.CanCast;
884+
885+
return CanCastTo_NoCacheLookup(m_asTAddr, destTH.m_asTAddr);
886+
}
887+
888+
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TypeHandle_CanCastTo_NoCacheLookup")]
889+
[return: MarshalAs(UnmanagedType.Bool)]
890+
private static partial bool CanCastTo_NoCacheLookup(void* fromTypeHnd, void* toTypeHnd);
868891
}
869892

870893
// Helper structs used for tail calls via helper.

src/coreclr/classlibnative/bcltype/arraynative.cpp

-160
Original file line numberDiff line numberDiff line change
@@ -47,166 +47,6 @@ extern "C" PCODE QCALLTYPE Array_GetElementConstructorEntrypoint(QCall::TypeHand
4747
return ctorEntrypoint;
4848
}
4949

50-
// Returns whether you can directly copy an array of srcType into destType.
51-
FCIMPL2(FC_BOOL_RET, ArrayNative::IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst)
52-
{
53-
FCALL_CONTRACT;
54-
55-
_ASSERTE(pSrc != NULL);
56-
_ASSERTE(pDst != NULL);
57-
58-
// This case is expected to be handled by the fast path
59-
_ASSERTE(pSrc->GetMethodTable() != pDst->GetMethodTable());
60-
61-
TypeHandle srcTH = pSrc->GetMethodTable()->GetArrayElementTypeHandle();
62-
TypeHandle destTH = pDst->GetMethodTable()->GetArrayElementTypeHandle();
63-
if (srcTH == destTH) // This check kicks for different array kind or dimensions
64-
FC_RETURN_BOOL(true);
65-
66-
if (srcTH.IsValueType())
67-
{
68-
// Value class boxing
69-
if (!destTH.IsValueType())
70-
FC_RETURN_BOOL(false);
71-
72-
const CorElementType srcElType = srcTH.GetVerifierCorElementType();
73-
const CorElementType destElType = destTH.GetVerifierCorElementType();
74-
_ASSERTE(srcElType < ELEMENT_TYPE_MAX);
75-
_ASSERTE(destElType < ELEMENT_TYPE_MAX);
76-
77-
// Copying primitives from one type to another
78-
if (CorTypeInfo::IsPrimitiveType_NoThrow(srcElType) && CorTypeInfo::IsPrimitiveType_NoThrow(destElType))
79-
{
80-
if (GetNormalizedIntegralArrayElementType(srcElType) == GetNormalizedIntegralArrayElementType(destElType))
81-
FC_RETURN_BOOL(true);
82-
}
83-
}
84-
else
85-
{
86-
// Value class unboxing
87-
if (destTH.IsValueType())
88-
FC_RETURN_BOOL(false);
89-
}
90-
91-
TypeHandle::CastResult r = srcTH.CanCastToCached(destTH);
92-
if (r != TypeHandle::MaybeCast)
93-
{
94-
FC_RETURN_BOOL(r);
95-
}
96-
97-
struct
98-
{
99-
OBJECTREF src;
100-
OBJECTREF dst;
101-
} gc;
102-
103-
gc.src = ObjectToOBJECTREF(pSrc);
104-
gc.dst = ObjectToOBJECTREF(pDst);
105-
106-
BOOL iRetVal = FALSE;
107-
108-
HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc);
109-
iRetVal = srcTH.CanCastTo(destTH);
110-
HELPER_METHOD_FRAME_END();
111-
112-
FC_RETURN_BOOL(iRetVal);
113-
}
114-
FCIMPLEND
115-
116-
117-
// Return values for CanAssignArrayType
118-
enum AssignArrayEnum
119-
{
120-
AssignWrongType,
121-
AssignMustCast,
122-
AssignBoxValueClassOrPrimitive,
123-
AssignUnboxValueClass,
124-
AssignPrimitiveWiden,
125-
};
126-
127-
// Returns an enum saying whether you can copy an array of srcType into destType.
128-
static AssignArrayEnum CanAssignArrayType(const TypeHandle srcTH, const TypeHandle destTH)
129-
{
130-
CONTRACTL
131-
{
132-
THROWS;
133-
GC_TRIGGERS;
134-
PRECONDITION(!srcTH.IsNull());
135-
PRECONDITION(!destTH.IsNull());
136-
}
137-
CONTRACTL_END;
138-
139-
_ASSERTE(srcTH != destTH); // Handled by fast path
140-
141-
// Value class boxing
142-
if (srcTH.IsValueType() && !destTH.IsValueType())
143-
{
144-
if (srcTH.CanCastTo(destTH))
145-
return AssignBoxValueClassOrPrimitive;
146-
else
147-
return AssignWrongType;
148-
}
149-
150-
// Value class unboxing.
151-
if (!srcTH.IsValueType() && destTH.IsValueType())
152-
{
153-
if (srcTH.CanCastTo(destTH))
154-
return AssignUnboxValueClass;
155-
else if (destTH.CanCastTo(srcTH)) // V extends IV. Copying from IV to V, or Object to V.
156-
return AssignUnboxValueClass;
157-
else
158-
return AssignWrongType;
159-
}
160-
161-
const CorElementType srcElType = srcTH.GetVerifierCorElementType();
162-
const CorElementType destElType = destTH.GetVerifierCorElementType();
163-
_ASSERTE(srcElType < ELEMENT_TYPE_MAX);
164-
_ASSERTE(destElType < ELEMENT_TYPE_MAX);
165-
166-
// Copying primitives from one type to another
167-
if (CorTypeInfo::IsPrimitiveType_NoThrow(srcElType) && CorTypeInfo::IsPrimitiveType_NoThrow(destElType))
168-
{
169-
_ASSERTE(srcElType != destElType); // Handled by fast path
170-
if (InvokeUtil::CanPrimitiveWiden(destElType, srcElType))
171-
return AssignPrimitiveWiden;
172-
else
173-
return AssignWrongType;
174-
}
175-
176-
// dest Object extends src
177-
_ASSERTE(!srcTH.CanCastTo(destTH)); // Handled by fast path
178-
179-
// src Object extends dest
180-
if (destTH.CanCastTo(srcTH))
181-
return AssignMustCast;
182-
183-
// class X extends/implements src and implements dest.
184-
if (destTH.IsInterface() && srcElType != ELEMENT_TYPE_VALUETYPE)
185-
return AssignMustCast;
186-
187-
// class X implements src and extends/implements dest
188-
if (srcTH.IsInterface() && destElType != ELEMENT_TYPE_VALUETYPE)
189-
return AssignMustCast;
190-
191-
return AssignWrongType;
192-
}
193-
194-
extern "C" int QCALLTYPE Array_CanAssignArrayType(void* srcTH, void* destTH)
195-
{
196-
QCALL_CONTRACT;
197-
198-
INT32 ret = 0;
199-
200-
BEGIN_QCALL;
201-
202-
ret = CanAssignArrayType(TypeHandle::FromPtr(srcTH), TypeHandle::FromPtr(destTH));
203-
204-
END_QCALL;
205-
206-
return ret;
207-
}
208-
209-
21050
//
21151
// This is a GC safe variant of the memmove intrinsic. It sets the cards, and guarantees that the object references in the GC heap are
21252
// updated atomically.

0 commit comments

Comments
 (0)