Skip to content

Commit

Permalink
Push back changes around IsSimpleCopy and CanAssignArrayType
Browse files Browse the repository at this point in the history
  • Loading branch information
huoyaoyuan committed Jun 10, 2024
1 parent 5465a97 commit 4ce4f51
Show file tree
Hide file tree
Showing 8 changed files with 197 additions and 143 deletions.
122 changes: 24 additions & 98 deletions src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array de
if ((uint)(destinationIndex + length) > destinationArray.NativeLength)
throw new ArgumentException(SR.Arg_LongerThanDestArray, nameof(destinationArray));

ArrayAssignType assignType = ArrayAssignType.WrongType;

if (sourceArray.GetType() == destinationArray.GetType()
|| (assignType = CanAssignArrayType(sourceArray, destinationArray)) == ArrayAssignType.SimpleCopy)
if (sourceArray.GetType() == destinationArray.GetType() || IsSimpleCopy(sourceArray, destinationArray))
{
MethodTable* pMT = RuntimeHelpers.GetMethodTable(sourceArray);

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

// Rare
CopySlow(sourceArray, sourceIndex, destinationArray, destinationIndex, length, assignType);
CopySlow(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
}

private static CorElementType GetNormalizedIntegralArrayElementType(CorElementType elementType)
{
Debug.Assert(elementType.IsPrimitiveType());

// Array Primitive types such as E_T_I4 and E_T_U4 are interchangeable
// Enums with interchangeable underlying types are interchangeable
// BOOL is NOT interchangeable with I1/U1, neither CHAR -- with I2/U2
switch (elementType)
{
case CorElementType.ELEMENT_TYPE_U1:
case CorElementType.ELEMENT_TYPE_U2:
case CorElementType.ELEMENT_TYPE_U4:
case CorElementType.ELEMENT_TYPE_U8:
case CorElementType.ELEMENT_TYPE_U:
return elementType - 1; // normalize to signed type
default:
return elementType;
}
}
[MethodImpl(MethodImplOptions.InternalCall)]
private static extern bool IsSimpleCopy(Array sourceArray, Array destinationArray);

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

if (assignType == ArrayAssignType.WrongType)
void* srcTH = RuntimeHelpers.GetMethodTable(sourceArray)->ElementType;
void* destTH = RuntimeHelpers.GetMethodTable(destinationArray)->ElementType;
AssignArrayEnum r = CanAssignArrayType(srcTH, destTH);

if (r == AssignArrayEnum.AssignWrongType)
throw new ArrayTypeMismatchException(SR.ArrayTypeMismatch_CantAssignType);

if (length > 0)
{
switch (assignType)
switch (r)
{
case ArrayAssignType.UnboxValueClass:
case AssignArrayEnum.AssignUnboxValueClass:
CopyImplUnBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
break;

case ArrayAssignType.BoxValueClassOrPrimitive:
case AssignArrayEnum.AssignBoxValueClassOrPrimitive:
CopyImplBoxEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
break;

case ArrayAssignType.MustCast:
case AssignArrayEnum.AssignMustCast:
CopyImplCastCheckEachElement(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
break;

case ArrayAssignType.PrimitiveWiden:
case AssignArrayEnum.AssignPrimitiveWiden:
CopyImplPrimitiveWiden(sourceArray, sourceIndex, destinationArray, destinationIndex, length);
break;

Expand All @@ -150,76 +134,18 @@ private static unsafe void CopySlow(Array sourceArray, int sourceIndex, Array de
}
}

private enum ArrayAssignType
// Must match the definition in arraynative.cpp
private enum AssignArrayEnum
{
SimpleCopy,
WrongType,
MustCast,
BoxValueClassOrPrimitive,
UnboxValueClass,
PrimitiveWiden,
AssignWrongType,
AssignMustCast,
AssignBoxValueClassOrPrimitive,
AssignUnboxValueClass,
AssignPrimitiveWiden,
}

private static unsafe ArrayAssignType CanAssignArrayType(Array sourceArray, Array destinationArray)
{
TypeHandle srcTH = RuntimeHelpers.GetMethodTable(sourceArray)->GetArrayElementTypeHandle();
TypeHandle destTH = RuntimeHelpers.GetMethodTable(destinationArray)->GetArrayElementTypeHandle();

if (TypeHandle.AreSameType(srcTH, destTH)) // This check kicks for different array kind or dimensions
return ArrayAssignType.SimpleCopy;

// Value class boxing
if (srcTH.IsValueType && !destTH.IsValueType)
{
if (srcTH.CanCastTo(destTH))
return ArrayAssignType.BoxValueClassOrPrimitive;
else
return ArrayAssignType.WrongType;
}

// Value class unboxing.
if (!srcTH.IsValueType && destTH.IsValueType)
{
if (srcTH.CanCastTo(destTH))
return ArrayAssignType.UnboxValueClass;
else if (destTH.CanCastTo(srcTH)) // V extends IV. Copying from IV to V, or Object to V.
return ArrayAssignType.UnboxValueClass;
else
return ArrayAssignType.WrongType;
}

CorElementType srcElType = srcTH.GetVerifierCorElementType();
CorElementType destElType = destTH.GetVerifierCorElementType();

// Copying primitives from one type to another
if (srcElType.IsPrimitiveType() && destElType.IsPrimitiveType())
{
if (GetNormalizedIntegralArrayElementType(srcElType) == GetNormalizedIntegralArrayElementType(destElType))
return ArrayAssignType.SimpleCopy;
else if (RuntimeHelpers.CanPrimitiveWiden(srcElType, destElType))
return ArrayAssignType.PrimitiveWiden;
else
return ArrayAssignType.WrongType;
}

// src Object extends dest
if (srcTH.CanCastTo(destTH))
return ArrayAssignType.SimpleCopy;

// dest Object extends src
if (destTH.CanCastTo(srcTH))
return ArrayAssignType.MustCast;

// class X extends/implements src and implements dest.
if (destTH.IsInterface && srcElType != CorElementType.ELEMENT_TYPE_VALUETYPE)
return ArrayAssignType.MustCast;

// class X implements src and extends/implements dest
if (srcTH.IsInterface && srcElType != CorElementType.ELEMENT_TYPE_VALUETYPE)
return ArrayAssignType.MustCast;

return ArrayAssignType.WrongType;
}
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Array_CanAssignArrayType")]
private static unsafe partial AssignArrayEnum CanAssignArrayType(void* srcTH, void* dstTH);

// Unboxes from an Object[] into a value class or primitive array.
private static unsafe void CopyImplUnBoxEachElement(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -862,34 +862,6 @@ public static TypeHandle TypeHandleOf<T>()

public static bool AreSameType(TypeHandle left, TypeHandle right) => left.m_asTAddr == right.m_asTAddr;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanCastTo(TypeHandle destTH)
{
CastResult result = CastCache.TryGet(CastHelpers.s_table!, (nuint)m_asTAddr, (nuint)destTH.m_asTAddr);

if (result != CastResult.MaybeCast)
return result == CastResult.CanCast;

return CanCastTo_NoCacheLookup(m_asTAddr, destTH.m_asTAddr);
}

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "TypeHandle_CanCastTo")]
[return: MarshalAs(UnmanagedType.Bool)]
private static partial bool CanCastTo_NoCacheLookup(void* fromTypeHnd, void* toTypeHnd);

public bool IsValueType
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return IsTypeDesc
? AsTypeDesc()->GetInternalCorElementType() == CorElementType.ELEMENT_TYPE_VALUETYPE
: AsMethodTable()->IsValueType;
}
}

public bool IsInterface => !IsTypeDesc && AsMethodTable()->IsInterface;

public CorElementType GetVerifierCorElementType() => IsTypeDesc
? AsTypeDesc()->GetInternalCorElementType()
: AsMethodTable()->GetVerifierCorElementType();
Expand Down
159 changes: 159 additions & 0 deletions src/coreclr/classlibnative/bcltype/arraynative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,165 @@ extern "C" PCODE QCALLTYPE Array_GetElementConstructorEntrypoint(QCall::TypeHand
return ctorEntrypoint;
}

// Returns whether you can directly copy an array of srcType into destType.
FCIMPL2(FC_BOOL_RET, ArrayNative::IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst)
{
FCALL_CONTRACT;

_ASSERTE(pSrc != NULL);
_ASSERTE(pDst != NULL);

// This case is expected to be handled by the fast path
_ASSERTE(pSrc->GetMethodTable() != pDst->GetMethodTable());

TypeHandle srcTH = pSrc->GetMethodTable()->GetArrayElementTypeHandle();
TypeHandle destTH = pDst->GetMethodTable()->GetArrayElementTypeHandle();
if (srcTH == destTH) // This check kicks for different array kind or dimensions
FC_RETURN_BOOL(true);

if (srcTH.IsValueType())
{
// Value class boxing
if (!destTH.IsValueType())
FC_RETURN_BOOL(false);

const CorElementType srcElType = srcTH.GetVerifierCorElementType();
const CorElementType destElType = destTH.GetVerifierCorElementType();
_ASSERTE(srcElType < ELEMENT_TYPE_MAX);
_ASSERTE(destElType < ELEMENT_TYPE_MAX);

// Copying primitives from one type to another
if (CorTypeInfo::IsPrimitiveType_NoThrow(srcElType) && CorTypeInfo::IsPrimitiveType_NoThrow(destElType))
{
if (GetNormalizedIntegralArrayElementType(srcElType) == GetNormalizedIntegralArrayElementType(destElType))
FC_RETURN_BOOL(true);
}
}
else
{
// Value class unboxing
if (destTH.IsValueType())
FC_RETURN_BOOL(false);
}

TypeHandle::CastResult r = srcTH.CanCastToCached(destTH);
if (r != TypeHandle::MaybeCast)
{
FC_RETURN_BOOL(r);
}

struct
{
OBJECTREF src;
OBJECTREF dst;
} gc;

gc.src = ObjectToOBJECTREF(pSrc);
gc.dst = ObjectToOBJECTREF(pDst);

BOOL iRetVal = FALSE;

HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc);
iRetVal = srcTH.CanCastTo(destTH);
HELPER_METHOD_FRAME_END();

FC_RETURN_BOOL(iRetVal);
}
FCIMPLEND


// Return values for CanAssignArrayType
enum AssignArrayEnum
{
AssignWrongType,
AssignMustCast,
AssignBoxValueClassOrPrimitive,
AssignUnboxValueClass,
AssignPrimitiveWiden,
};

// Returns an enum saying whether you can copy an array of srcType into destType.
static AssignArrayEnum CanAssignArrayType(const TypeHandle srcTH, const TypeHandle destTH)
{
CONTRACTL
{
THROWS;
GC_TRIGGERS;
PRECONDITION(!srcTH.IsNull());
PRECONDITION(!destTH.IsNull());
}
CONTRACTL_END;

_ASSERTE(srcTH != destTH); // Handled by fast path

// Value class boxing
if (srcTH.IsValueType() && !destTH.IsValueType())
{
if (srcTH.CanCastTo(destTH))
return AssignBoxValueClassOrPrimitive;
else
return AssignWrongType;
}

// Value class unboxing.
if (!srcTH.IsValueType() && destTH.IsValueType())
{
if (srcTH.CanCastTo(destTH))
return AssignUnboxValueClass;
else if (destTH.CanCastTo(srcTH)) // V extends IV. Copying from IV to V, or Object to V.
return AssignUnboxValueClass;
else
return AssignWrongType;
}

const CorElementType srcElType = srcTH.GetVerifierCorElementType();
const CorElementType destElType = destTH.GetVerifierCorElementType();
_ASSERTE(srcElType < ELEMENT_TYPE_MAX);
_ASSERTE(destElType < ELEMENT_TYPE_MAX);

// Copying primitives from one type to another
if (CorTypeInfo::IsPrimitiveType_NoThrow(srcElType) && CorTypeInfo::IsPrimitiveType_NoThrow(destElType))
{
_ASSERTE(srcElType != destElType); // Handled by fast path
if (InvokeUtil::CanPrimitiveWiden(destElType, srcElType))
return AssignPrimitiveWiden;
else
return AssignWrongType;
}

// dest Object extends src
_ASSERTE(!srcTH.CanCastTo(destTH)); // Handled by fast path

// src Object extends dest
if (destTH.CanCastTo(srcTH))
return AssignMustCast;

// class X extends/implements src and implements dest.
if (destTH.IsInterface() && srcElType != ELEMENT_TYPE_VALUETYPE)
return AssignMustCast;

// class X implements src and extends/implements dest
if (srcTH.IsInterface() && destElType != ELEMENT_TYPE_VALUETYPE)
return AssignMustCast;

return AssignWrongType;
}

extern "C" int QCALLTYPE Array_CanAssignArrayType(void* srcTH, void* destTH)
{
QCALL_CONTRACT;

INT32 ret = 0;

BEGIN_QCALL;

ret = CanAssignArrayType(TypeHandle::FromPtr(srcTH), TypeHandle::FromPtr(destTH));

END_QCALL;

return ret;
}


//
// 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
Expand Down
8 changes: 8 additions & 0 deletions src/coreclr/classlibnative/bcltype/arraynative.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@
#ifndef _ARRAYNATIVE_H_
#define _ARRAYNATIVE_H_

#include "fcall.h"
#include "qcall.h"

class ArrayNative
{
public:
static FCDECL2(FC_BOOL_RET, IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst);
};

extern "C" void QCALLTYPE Array_CreateInstance(QCall::TypeHandle pTypeHnd, INT32 rank, INT32* pLengths, INT32* pBounds, BOOL createFromArrayType, QCall::ObjectHandleOnStack retArray);
extern "C" PCODE QCALLTYPE Array_GetElementConstructorEntrypoint(QCall::TypeHandle pArrayTypeHnd);
extern "C" INT32 QCALLTYPE Array_CanAssignArrayType(void* srcTH, void* destTH);

#endif // _ARRAYNATIVE_H_
Loading

0 comments on commit 4ce4f51

Please sign in to comment.