diff --git a/src/coreclr/classlibnative/bcltype/arraynative.cpp b/src/coreclr/classlibnative/bcltype/arraynative.cpp index 246c5398276073..02ff0360ac4b7d 100644 --- a/src/coreclr/classlibnative/bcltype/arraynative.cpp +++ b/src/coreclr/classlibnative/bcltype/arraynative.cpp @@ -17,6 +17,17 @@ #include "arraynative.inl" +// Returns a bool to indicate if the array is of primitive types or not. +FCIMPL1(INT32, ArrayNative::GetCorElementTypeOfElementType, ArrayBase* arrayUNSAFE) +{ + FCALL_CONTRACT; + + _ASSERTE(arrayUNSAFE != NULL); + + return arrayUNSAFE->GetArrayElementTypeHandle().GetVerifierCorElementType(); +} +FCIMPLEND + extern "C" PCODE QCALLTYPE Array_GetElementConstructorEntrypoint(QCall::TypeHandle pArrayTypeHnd) { QCALL_CONTRACT; @@ -36,6 +47,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 @@ -195,3 +365,203 @@ void QCALLTYPE Array_CreateInstance(QCall::TypeHandle pTypeHnd, INT32 rank, INT3 Done: ; END_QCALL; } + +FCIMPL3(void, ArrayNative::SetValue, ArrayBase* refThisUNSAFE, Object* objUNSAFE, INT_PTR flattenedIndex) +{ + FCALL_CONTRACT; + + BASEARRAYREF refThis(refThisUNSAFE); + OBJECTREF obj(objUNSAFE); + + TypeHandle arrayElementType = refThis->GetArrayElementTypeHandle(); + + // Legacy behavior (this handles pointers and function pointers) + if (arrayElementType.IsTypeDesc()) + { + FCThrowResVoid(kNotSupportedException, W("NotSupported_Type")); + } + + _ASSERTE((SIZE_T)flattenedIndex < refThis->GetNumComponents()); + + MethodTable* pElementTypeMT = arrayElementType.GetMethodTable(); + PREFIX_ASSUME(NULL != pElementTypeMT); + + void* pData = refThis->GetDataPtr() + flattenedIndex * refThis->GetComponentSize(); + + if (obj == NULL) + { + // Null is the universal zero... + if (pElementTypeMT->IsValueType()) + InitValueClass(pData,pElementTypeMT); + else + ClearObjectReference((OBJECTREF*)pData); + } + else + if (arrayElementType == TypeHandle(g_pObjectClass)) + { + // Everything is compatible with Object + SetObjectReference((OBJECTREF*)pData,(OBJECTREF)obj); + } + else + if (!pElementTypeMT->IsValueType()) + { + if (ObjIsInstanceOfCached(OBJECTREFToObject(obj), arrayElementType) != TypeHandle::CanCast) + { + HELPER_METHOD_FRAME_BEGIN_2(refThis, obj); + + if (!ObjIsInstanceOf(OBJECTREFToObject(obj), arrayElementType)) + COMPlusThrow(kInvalidCastException,W("InvalidCast_StoreArrayElement")); + + HELPER_METHOD_FRAME_END(); + + // Refresh pData in case GC moved objects around + pData = refThis->GetDataPtr() + flattenedIndex * refThis->GetComponentSize(); + } + + SetObjectReference((OBJECTREF*)pData,obj); + } + else + { + // value class or primitive type + + if (!pElementTypeMT->UnBoxInto(pData, obj)) + { + HELPER_METHOD_FRAME_BEGIN_2(refThis, obj); + + ARG_SLOT value = 0; + + // Allow enum -> primitive conversion, disallow primitive -> enum conversion + TypeHandle thSrc = obj->GetTypeHandle(); + CorElementType srcType = thSrc.GetVerifierCorElementType(); + CorElementType targetType = arrayElementType.GetSignatureCorElementType(); + + if (!InvokeUtil::IsPrimitiveType(srcType) || !InvokeUtil::IsPrimitiveType(targetType)) + COMPlusThrow(kInvalidCastException, W("InvalidCast_StoreArrayElement")); + + // Get a properly widened type + InvokeUtil::CreatePrimitiveValue(targetType,srcType,obj,&value); + + // Refresh pData in case GC moved objects around + pData = refThis->GetDataPtr() + flattenedIndex * refThis->GetComponentSize(); + + UINT cbSize = CorTypeInfo::Size(targetType); + memcpyNoGCRefs(pData, ArgSlotEndiannessFixup(&value, cbSize), cbSize); + + HELPER_METHOD_FRAME_END(); + } + } +} +FCIMPLEND + +// This method will initialize an array from a TypeHandle to a field. + +FCIMPL2_IV(void, ArrayNative::InitializeArray, ArrayBase* pArrayRef, FCALLRuntimeFieldHandle structField) +{ + FCALL_CONTRACT; + + BASEARRAYREF arr = BASEARRAYREF(pArrayRef); + REFLECTFIELDREF refField = (REFLECTFIELDREF)ObjectToOBJECTREF(FCALL_RFH_TO_REFLECTFIELD(structField)); + HELPER_METHOD_FRAME_BEGIN_2(arr, refField); + + if ((arr == 0) || (refField == NULL)) + COMPlusThrow(kArgumentNullException); + + FieldDesc* pField = (FieldDesc*) refField->GetField(); + + if (!pField->IsRVA()) + COMPlusThrow(kArgumentException); + + // Note that we do not check that the field is actually in the PE file that is initializing + // the array. Basically the data being published is can be accessed by anyone with the proper + // permissions (C# marks these as assembly visibility, and thus are protected from outside + // snooping) + + if (!CorTypeInfo::IsPrimitiveType(arr->GetArrayElementType()) && !arr->GetArrayElementTypeHandle().IsEnum()) + COMPlusThrow(kArgumentException); + + SIZE_T dwCompSize = arr->GetComponentSize(); + SIZE_T dwElemCnt = arr->GetNumComponents(); + SIZE_T dwTotalSize = dwCompSize * dwElemCnt; + + DWORD size = pField->LoadSize(); + + // make certain you don't go off the end of the rva static + if (dwTotalSize > size) + COMPlusThrow(kArgumentException); + + void *src = pField->GetStaticAddressHandle(NULL); + void *dest = arr->GetDataPtr(); + +#if BIGENDIAN + DWORD i; + switch (dwCompSize) { + case 1: + memcpyNoGCRefs(dest, src, dwElemCnt); + break; + case 2: + for (i = 0; i < dwElemCnt; i++) + *((UINT16*)dest + i) = GET_UNALIGNED_VAL16((UINT16*)src + i); + break; + case 4: + for (i = 0; i < dwElemCnt; i++) + *((UINT32*)dest + i) = GET_UNALIGNED_VAL32((UINT32*)src + i); + break; + case 8: + for (i = 0; i < dwElemCnt; i++) + *((UINT64*)dest + i) = GET_UNALIGNED_VAL64((UINT64*)src + i); + break; + default: + // should not reach here. + UNREACHABLE_MSG("Incorrect primitive type size!"); + break; + } +#else + memcpyNoGCRefs(dest, src, dwTotalSize); +#endif + + HELPER_METHOD_FRAME_END(); +} +FCIMPLEND + +FCIMPL3_VVI(void*, ArrayNative::GetSpanDataFrom, FCALLRuntimeFieldHandle structField, FCALLRuntimeTypeHandle targetTypeUnsafe, INT32* count) +{ + FCALL_CONTRACT; + struct + { + REFLECTFIELDREF refField; + REFLECTCLASSBASEREF refClass; + } gc; + gc.refField = (REFLECTFIELDREF)ObjectToOBJECTREF(FCALL_RFH_TO_REFLECTFIELD(structField)); + gc.refClass = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(FCALL_RTH_TO_REFLECTCLASS(targetTypeUnsafe)); + void* data = NULL; + HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(gc); + + FieldDesc* pField = (FieldDesc*)gc.refField->GetField(); + + if (!pField->IsRVA()) + COMPlusThrow(kArgumentException); + + TypeHandle targetTypeHandle = gc.refClass->GetType(); + if (!CorTypeInfo::IsPrimitiveType(targetTypeHandle.GetSignatureCorElementType()) && !targetTypeHandle.IsEnum()) + COMPlusThrow(kArgumentException); + + DWORD totalSize = pField->LoadSize(); + DWORD targetTypeSize = targetTypeHandle.GetSize(); + + data = pField->GetStaticAddressHandle(NULL); + _ASSERTE(data != NULL); + _ASSERTE(count != NULL); + + if (AlignUp((UINT_PTR)data, targetTypeSize) != (UINT_PTR)data) + COMPlusThrow(kArgumentException); + + *count = (INT32)totalSize / targetTypeSize; + +#if BIGENDIAN + COMPlusThrow(kPlatformNotSupportedException); +#endif + + HELPER_METHOD_FRAME_END(); + return data; +} +FCIMPLEND diff --git a/src/coreclr/classlibnative/bcltype/arraynative.h b/src/coreclr/classlibnative/bcltype/arraynative.h index 5dd3570ce5cbba..aeb264a9b28ceb 100644 --- a/src/coreclr/classlibnative/bcltype/arraynative.h +++ b/src/coreclr/classlibnative/bcltype/arraynative.h @@ -13,9 +13,36 @@ #ifndef _ARRAYNATIVE_H_ #define _ARRAYNATIVE_H_ -#include "qcall.h" +#include "fcall.h" +#include "runtimehandles.h" + +struct FCALLRuntimeFieldHandle +{ + ReflectFieldObject *pFieldDONOTUSEDIRECTLY; +}; +#define FCALL_RFH_TO_REFLECTFIELD(x) (x).pFieldDONOTUSEDIRECTLY + +class ArrayNative +{ +public: + static FCDECL1(INT32, GetCorElementTypeOfElementType, ArrayBase* arrayUNSAFE); + + static FCDECL2(FC_BOOL_RET, IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst); + + // This set of methods will set a value in an array + static FCDECL3(void, SetValue, ArrayBase* refThisUNSAFE, Object* objUNSAFE, INT_PTR flattenedIndex); + + // This method will initialize an array from a TypeHandle + // to a field. + static FCDECL2_IV(void, InitializeArray, ArrayBase* vArrayRef, FCALLRuntimeFieldHandle structField); + + // This method will acquire data to create a span from a TypeHandle + // to a field. + static FCDECL3_VVI(void*, GetSpanDataFrom, FCALLRuntimeFieldHandle structField, FCALLRuntimeTypeHandle targetTypeUnsafe, INT32* count); +}; 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_ diff --git a/src/coreclr/vm/ecalllist.h b/src/coreclr/vm/ecalllist.h index 3ad62b22824ac1..f06fc007e2ccb1 100644 --- a/src/coreclr/vm/ecalllist.h +++ b/src/coreclr/vm/ecalllist.h @@ -359,6 +359,12 @@ FCFuncStart(gCastHelpers) FCFuncElement("WriteBarrier", ::WriteBarrier_Helper) FCFuncEnd() +FCFuncStart(gArrayFuncs) + FCFuncElement("GetCorElementTypeOfElementType", ArrayNative::GetCorElementTypeOfElementType) + FCFuncElement("IsSimpleCopy", ArrayNative::IsSimpleCopy) + FCFuncElement("InternalSetValue", ArrayNative::SetValue) +FCFuncEnd() + FCFuncStart(gBufferFuncs) FCFuncElement("__BulkMoveWithWriteBarrier", Buffer::BulkMoveWithWriteBarrier) FCFuncEnd() @@ -434,6 +440,8 @@ FCFuncStart(gMonitorFuncs) FCFuncEnd() FCFuncStart(gRuntimeHelpers) + FCFuncElement("InitializeArray", ArrayNative::InitializeArray) + FCFuncElement("GetSpanDataFrom", ArrayNative::GetSpanDataFrom) FCFuncElement("PrepareDelegate", ReflectionInvocation::PrepareDelegate) FCFuncElement("GetHashCode", ObjectNative::GetHashCode) FCFuncElement("TryGetHashCode", ObjectNative::TryGetHashCode) @@ -531,6 +539,7 @@ FCFuncEnd() // Note these have to remain sorted by name:namespace pair (Assert will wack you if you don't) // The sorting is case-sensitive +FCClassElement("Array", "System", gArrayFuncs) FCClassElement("AssemblyLoadContext", "System.Runtime.Loader", gAssemblyLoadContextFuncs) FCClassElement("Buffer", "System", gBufferFuncs) FCClassElement("CastHelpers", "System.Runtime.CompilerServices", gCastHelpers) diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index 05a62c15df1eb3..084e6df167aca1 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -173,6 +173,7 @@ static const Entry s_QCall[] = DllImportEntry(MdUtf8String_EqualsCaseInsensitive) DllImportEntry(Array_CreateInstance) DllImportEntry(Array_GetElementConstructorEntrypoint) + DllImportEntry(Array_CanAssignArrayType) DllImportEntry(AssemblyName_InitializeAssemblySpec) DllImportEntry(AssemblyNative_GetFullName) DllImportEntry(AssemblyNative_GetLocation)