Skip to content

Commit 115199d

Browse files
AlexRadchadamsitnikjkotas
authored
[API Proposal]: Create array from array type (#88620)
* Create array from array type * InternalCreateFromArrayType * code review: - add missing comments for new public APIs - use the new API in DataTypeImplementation (and solve a TODO) - fix typo - remove code that was commented out - improve test name * Fix perf and corner-case behaviors * Delete superfluous validation * Skip handling of void arrays for this PR --------- Co-authored-by: Adam Sitnik <[email protected]> Co-authored-by: Jan Kotas <[email protected]>
1 parent 4325acc commit 115199d

File tree

15 files changed

+388
-139
lines changed

15 files changed

+388
-139
lines changed

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

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Collections;
55
using System.Collections.Generic;
66
using System.Diagnostics;
7-
using System.Diagnostics.CodeAnalysis;
87
using System.Reflection;
98
using System.Runtime.CompilerServices;
109
using System.Runtime.InteropServices;
@@ -15,9 +14,25 @@ namespace System
1514
// IList<U> and IReadOnlyList<U>, where T : U dynamically. See the SZArrayHelper class for details.
1615
public abstract partial class Array : ICloneable, IList, IStructuralComparable, IStructuralEquatable
1716
{
18-
[MethodImpl(MethodImplOptions.InternalCall)]
19-
private static extern unsafe Array InternalCreate(RuntimeType elementType, int rank, int* pLengths, int* pLowerBounds);
17+
[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Array_CreateInstance")]
18+
private static unsafe partial void InternalCreate(QCallTypeHandle type, int rank, int* pLengths, int* pLowerBounds,
19+
[MarshalAs(UnmanagedType.Bool)] bool fromArrayType, ObjectHandleOnStack retArray);
20+
21+
private static unsafe Array InternalCreate(RuntimeType elementType, int rank, int* pLengths, int* pLowerBounds)
22+
{
23+
Array? retArray = null;
24+
InternalCreate(new QCallTypeHandle(ref elementType), rank, pLengths, pLowerBounds,
25+
fromArrayType: false, ObjectHandleOnStack.Create(ref retArray));
26+
return retArray!;
27+
}
2028

29+
private static unsafe Array InternalCreateFromArrayType(RuntimeType arrayType, int rank, int* pLengths, int* pLowerBounds)
30+
{
31+
Array? retArray = null;
32+
InternalCreate(new QCallTypeHandle(ref arrayType), rank, pLengths, pLowerBounds,
33+
fromArrayType: true, ObjectHandleOnStack.Create(ref retArray));
34+
return retArray!;
35+
}
2136

2237
private static unsafe void CopyImpl(Array sourceArray, int sourceIndex, Array destinationArray, int destinationIndex, int length, bool reliable)
2338
{

src/coreclr/classlibnative/bcltype/arraynative.cpp

Lines changed: 48 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -720,14 +720,8 @@ FCIMPLEND
720720

721721

722722
// Check we're allowed to create an array with the given element type.
723-
void ArrayNative::CheckElementType(TypeHandle elementType)
723+
static void CheckElementType(TypeHandle elementType)
724724
{
725-
// Checks apply recursively for arrays of arrays etc.
726-
while (elementType.IsArray())
727-
{
728-
elementType = elementType.GetArrayElementTypeHandle();
729-
}
730-
731725
// Check for simple types first.
732726
if (!elementType.IsTypeDesc())
733727
{
@@ -738,7 +732,7 @@ void ArrayNative::CheckElementType(TypeHandle elementType)
738732
COMPlusThrow(kNotSupportedException, W("NotSupported_ByRefLikeArray"));
739733

740734
// Check for open generic types.
741-
if (pMT->IsGenericTypeDefinition() || pMT->ContainsGenericVariables())
735+
if (pMT->ContainsGenericVariables())
742736
COMPlusThrow(kNotSupportedException, W("NotSupported_OpenType"));
743737

744738
// Check for Void.
@@ -753,62 +747,67 @@ void ArrayNative::CheckElementType(TypeHandle elementType)
753747
}
754748
}
755749

756-
FCIMPL4(Object*, ArrayNative::CreateInstance, ReflectClassBaseObject* pElementTypeUNSAFE, INT32 rank, INT32* pLengths, INT32* pLowerBounds)
750+
void QCALLTYPE Array_CreateInstance(QCall::TypeHandle pTypeHnd, INT32 rank, INT32* pLengths, INT32* pLowerBounds, BOOL createFromArrayType, QCall::ObjectHandleOnStack retArray)
757751
{
758752
CONTRACTL {
759-
FCALL_CHECK;
753+
QCALL_CHECK;
760754
PRECONDITION(rank > 0);
761755
PRECONDITION(CheckPointer(pLengths));
762756
PRECONDITION(CheckPointer(pLowerBounds, NULL_OK));
763757
} CONTRACTL_END;
764758

765-
OBJECTREF pRet = NULL;
766-
767-
REFLECTCLASSBASEREF pElementType = (REFLECTCLASSBASEREF)ObjectToOBJECTREF(pElementTypeUNSAFE);
768-
769-
// pLengths and pLowerBounds are pinned buffers. No need to protect them.
770-
HELPER_METHOD_FRAME_BEGIN_RET_PROTECT(pElementType);
771-
772-
TypeHandle elementType(pElementType->GetType());
759+
BEGIN_QCALL;
773760

774-
CheckElementType(elementType);
761+
TypeHandle typeHnd = pTypeHnd.AsTypeHandle();
775762

776-
CorElementType CorType = elementType.GetSignatureCorElementType();
763+
if (createFromArrayType)
764+
{
765+
_ASSERTE((INT32)typeHnd.GetRank() == rank);
766+
_ASSERTE(typeHnd.IsArray());
777767

778-
CorElementType kind = ELEMENT_TYPE_ARRAY;
768+
if (typeHnd.GetArrayElementTypeHandle().ContainsGenericVariables())
769+
COMPlusThrow(kNotSupportedException, W("NotSupported_OpenType"));
779770

780-
// Is it ELEMENT_TYPE_SZARRAY array?
781-
if (rank == 1 && (pLowerBounds == NULL || pLowerBounds[0] == 0)
782-
#ifdef FEATURE_64BIT_ALIGNMENT
783-
// On platforms where 64-bit types require 64-bit alignment and don't obtain it naturally force us
784-
// through the slow path where this will be handled.
785-
&& (CorType != ELEMENT_TYPE_I8)
786-
&& (CorType != ELEMENT_TYPE_U8)
787-
&& (CorType != ELEMENT_TYPE_R8)
788-
#endif
789-
)
790-
{
791-
// Shortcut for common cases
792-
if (CorTypeInfo::IsPrimitiveType(CorType))
771+
if (!typeHnd.AsMethodTable()->IsMultiDimArray())
793772
{
794-
pRet = AllocatePrimitiveArray(CorType,pLengths[0]);
773+
_ASSERTE(pLowerBounds == NULL || pLowerBounds[0] == 0);
774+
775+
GCX_COOP();
776+
retArray.Set(AllocateSzArray(typeHnd, pLengths[0]));
795777
goto Done;
796778
}
797-
else
798-
if (CorTypeInfo::IsObjRef(CorType))
779+
}
780+
else
781+
{
782+
CheckElementType(typeHnd);
783+
784+
// Is it ELEMENT_TYPE_SZARRAY array?
785+
if (rank == 1 && (pLowerBounds == NULL || pLowerBounds[0] == 0))
799786
{
800-
pRet = AllocateObjectArray(pLengths[0],elementType);
801-
goto Done;
787+
CorElementType corType = typeHnd.GetSignatureCorElementType();
788+
789+
// Shortcut for common cases
790+
if (CorTypeInfo::IsPrimitiveType(corType))
791+
{
792+
GCX_COOP();
793+
retArray.Set(AllocatePrimitiveArray(corType, pLengths[0]));
794+
goto Done;
795+
}
796+
797+
typeHnd = ClassLoader::LoadArrayTypeThrowing(typeHnd);
798+
799+
{
800+
GCX_COOP();
801+
retArray.Set(AllocateSzArray(typeHnd, pLengths[0]));
802+
goto Done;
803+
}
802804
}
803805

804-
kind = ELEMENT_TYPE_SZARRAY;
805-
pLowerBounds = NULL;
806+
// Find the Array class...
807+
typeHnd = ClassLoader::LoadArrayTypeThrowing(typeHnd, ELEMENT_TYPE_ARRAY, rank);
806808
}
807809

808810
{
809-
// Find the Array class...
810-
TypeHandle typeHnd = ClassLoader::LoadArrayTypeThrowing(elementType, kind, rank);
811-
812811
_ASSERTE(rank <= MAX_RANK); // Ensures that the stack buffer size allocations below won't overflow
813812

814813
DWORD boundsSize = 0;
@@ -834,15 +833,15 @@ FCIMPL4(Object*, ArrayNative::CreateInstance, ReflectClassBaseObject* pElementTy
834833
bounds[i] = pLengths[i];
835834
}
836835

837-
pRet = AllocateArrayEx(typeHnd, bounds, boundsSize);
836+
{
837+
GCX_COOP();
838+
retArray.Set(AllocateArrayEx(typeHnd, bounds, boundsSize));
839+
}
838840
}
839841

840842
Done: ;
841-
HELPER_METHOD_FRAME_END();
842-
843-
return OBJECTREFToObject(pRet);
843+
END_QCALL;
844844
}
845-
FCIMPLEND
846845

847846
FCIMPL3(void, ArrayNative::SetValue, ArrayBase* refThisUNSAFE, Object* objUNSAFE, INT_PTR flattenedIndex)
848847
{

src/coreclr/classlibnative/bcltype/arraynative.h

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ class ArrayNative
3030
static FCDECL2(FC_BOOL_RET, IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst);
3131
static FCDECL5(void, CopySlow, ArrayBase* pSrc, INT32 iSrcIndex, ArrayBase* pDst, INT32 iDstIndex, INT32 iLength);
3232

33-
static FCDECL4(Object*, CreateInstance, ReflectClassBaseObject* pElementTypeUNSAFE, INT32 rank, INT32* pLengths, INT32* pBounds);
34-
3533
// This set of methods will set a value in an array
3634
static FCDECL3(void, SetValue, ArrayBase* refThisUNSAFE, Object* objUNSAFE, INT_PTR flattenedIndex);
3735

@@ -44,9 +42,6 @@ class ArrayNative
4442
static FCDECL3_VVI(void*, GetSpanDataFrom, FCALLRuntimeFieldHandle structField, FCALLRuntimeTypeHandle targetTypeUnsafe, INT32* count);
4543

4644
private:
47-
// Helper for CreateInstance
48-
static void CheckElementType(TypeHandle elementType);
49-
5045
// Return values for CanAssignArrayType
5146
enum AssignArrayEnum
5247
{
@@ -66,6 +61,7 @@ class ArrayNative
6661

6762
};
6863

64+
extern "C" void QCALLTYPE Array_CreateInstance(QCall::TypeHandle pTypeHnd, INT32 rank, INT32* pLengths, INT32* pBounds, BOOL createFromArrayType, QCall::ObjectHandleOnStack retArray);
6965
extern "C" PCODE QCALLTYPE Array_GetElementConstructorEntrypoint(QCall::TypeHandle pArrayTypeHnd);
7066

7167
#endif // _ARRAYNATIVE_H_

src/coreclr/nativeaot/System.Private.CoreLib/src/Internal/Reflection/Extensions/NonPortable/CustomAttributeInstantiator.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,6 @@ public static Attribute Instantiate(this CustomAttributeData cad)
126126
//
127127
// Convert the argument value reported by Reflection into an actual object.
128128
//
129-
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
130-
Justification = "The AOT compiler ensures array types required by custom attribute blobs are generated.")]
131129
private static object? Convert(this CustomAttributeTypedArgument typedArgument)
132130
{
133131
Type argumentType = typedArgument.ArgumentType;
@@ -144,8 +142,7 @@ public static Attribute Instantiate(this CustomAttributeData cad)
144142
IList<CustomAttributeTypedArgument>? typedElements = (IList<CustomAttributeTypedArgument>?)(typedArgument.Value);
145143
if (typedElements == null)
146144
return null;
147-
Type? elementType = argumentType.GetElementType();
148-
Array array = Array.CreateInstance(elementType, typedElements.Count);
145+
Array array = Array.CreateInstanceFromArrayType(argumentType, typedElements.Count);
149146
for (int i = 0; i < typedElements.Count; i++)
150147
{
151148
object? elementValue = typedElements[i].Convert();

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Array.NativeAot.cs

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,12 @@ internal unsafe bool IsSzArray
5959
[RequiresDynamicCode("The code for an array of the specified type might not be available.")]
6060
private static unsafe Array InternalCreate(RuntimeType elementType, int rank, int* pLengths, int* pLowerBounds)
6161
{
62-
ValidateElementType(elementType);
62+
if (elementType.IsByRef || elementType.IsByRefLike)
63+
throw new NotSupportedException(SR.NotSupported_ByRefLikeArray);
64+
if (elementType == typeof(void))
65+
throw new NotSupportedException(SR.NotSupported_VoidArray);
66+
if (elementType.ContainsGenericParameters)
67+
throw new NotSupportedException(SR.NotSupported_OpenType);
6368

6469
if (pLowerBounds != null)
6570
{
@@ -73,34 +78,59 @@ private static unsafe Array InternalCreate(RuntimeType elementType, int rank, in
7378
if (rank == 1)
7479
{
7580
return RuntimeImports.RhNewArray(elementType.MakeArrayType().TypeHandle.ToEETypePtr(), pLengths[0]);
76-
7781
}
7882
else
7983
{
80-
// Create a local copy of the lengths that cannot be motified by the caller
84+
Type arrayType = elementType.MakeArrayType(rank);
85+
86+
// Create a local copy of the lengths that cannot be modified by the caller
8187
int* pImmutableLengths = stackalloc int[rank];
8288
for (int i = 0; i < rank; i++)
8389
pImmutableLengths[i] = pLengths[i];
8490

85-
return NewMultiDimArray(elementType.MakeArrayType(rank).TypeHandle.ToEETypePtr(), pImmutableLengths, rank);
91+
return NewMultiDimArray(arrayType.TypeHandle.ToEETypePtr(), pImmutableLengths, rank);
8692
}
8793
}
8894

89-
#pragma warning disable CA1859 // https://github.com/dotnet/roslyn-analyzers/issues/6451
90-
private static void ValidateElementType(Type elementType)
95+
[UnconditionalSuppressMessage("AotAnalysis", "IL3050:RequiresDynamicCode",
96+
Justification = "The compiler ensures that if we have a TypeHandle of a Rank-1 MdArray, we also generated the SzArray.")]
97+
private static unsafe Array InternalCreateFromArrayType(RuntimeType arrayType, int rank, int* pLengths, int* pLowerBounds)
9198
{
92-
while (elementType.IsArray)
99+
Debug.Assert(arrayType.IsArray);
100+
Debug.Assert(arrayType.GetArrayRank() == rank);
101+
102+
if (arrayType.ContainsGenericParameters)
103+
throw new NotSupportedException(SR.NotSupported_OpenType);
104+
105+
if (pLowerBounds != null)
93106
{
94-
elementType = elementType.GetElementType()!;
107+
for (int i = 0; i < rank; i++)
108+
{
109+
if (pLowerBounds[i] != 0)
110+
throw new PlatformNotSupportedException(SR.PlatformNotSupported_NonZeroLowerBound);
111+
}
112+
}
113+
114+
EETypePtr eeType = arrayType.TypeHandle.ToEETypePtr();
115+
if (rank == 1)
116+
{
117+
// Multidimensional array of rank 1 with 0 lower bounds gets actually allocated
118+
// as an SzArray. SzArray is castable to MdArray rank 1.
119+
if (!eeType.IsSzArray)
120+
eeType = arrayType.GetElementType().MakeArrayType().TypeHandle.ToEETypePtr();
121+
122+
return RuntimeImports.RhNewArray(eeType, pLengths[0]);
123+
}
124+
else
125+
{
126+
// Create a local copy of the lengths that cannot be modified by the caller
127+
int* pImmutableLengths = stackalloc int[rank];
128+
for (int i = 0; i < rank; i++)
129+
pImmutableLengths[i] = pLengths[i];
130+
131+
return NewMultiDimArray(eeType, pImmutableLengths, rank);
95132
}
96-
if (elementType.IsByRef || elementType.IsByRefLike)
97-
throw new NotSupportedException(SR.NotSupported_ByRefLikeArray);
98-
if (elementType == typeof(void))
99-
throw new NotSupportedException(SR.NotSupported_VoidArray);
100-
if (elementType.ContainsGenericParameters)
101-
throw new NotSupportedException(SR.NotSupported_OpenType);
102133
}
103-
#pragma warning restore CA1859
104134

105135
public unsafe void Initialize()
106136
{

src/coreclr/vm/comsynchronizable.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ friend class ThreadBaseObject;
7070

7171

7272
static FCDECL0(INT32, GetOptimalMaxSpinWaitsPerSpinIteration);
73-
static FCDECL1(void, SpinWait, int iterations);
7473
static FCDECL0(Object*, GetCurrentThread);
7574
static FCDECL1(void, Finalize, ThreadBaseObject* pThis);
7675
#ifdef FEATURE_COMINTEROP

src/coreclr/vm/ecalllist.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -422,7 +422,6 @@ FCFuncStart(gArrayFuncs)
422422
FCFuncElement("GetCorElementTypeOfElementType", ArrayNative::GetCorElementTypeOfElementType)
423423
FCFuncElement("IsSimpleCopy", ArrayNative::IsSimpleCopy)
424424
FCFuncElement("CopySlow", ArrayNative::CopySlow)
425-
FCFuncElement("InternalCreate", ArrayNative::CreateInstance)
426425
FCFuncElement("InternalSetValue", ArrayNative::SetValue)
427426
FCFuncEnd()
428427

src/coreclr/vm/qcallentrypoints.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ static const Entry s_QCall[] =
151151
DllImportEntry(TypeBuilder_SetConstantValue)
152152
DllImportEntry(TypeBuilder_DefineCustomAttribute)
153153
DllImportEntry(MdUtf8String_EqualsCaseInsensitive)
154+
DllImportEntry(Array_CreateInstance)
154155
DllImportEntry(Array_GetElementConstructorEntrypoint)
155156
DllImportEntry(AssemblyName_InitializeAssemblySpec)
156157
DllImportEntry(AssemblyNative_GetFullName)

0 commit comments

Comments
 (0)