Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs
Original file line number Diff line number Diff line change
Expand Up @@ -381,8 +381,52 @@ private unsafe bool IsValueOfElementType(object value)
// if this is an array of value classes and that value class has a default constructor
// then this calls this default constructor on every element in the value class array.
// otherwise this is a no-op. Generally this method is called automatically by the compiler
[MethodImpl(MethodImplOptions.InternalCall)]
public extern void Initialize();
public unsafe void Initialize()

@jkotas jkotas Oct 23, 2022

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dotnet/ilc-contrib Should Array.Initialize be marked with RequiresUnreferenceCode, or is the value type default constructor the special-cased by the trimmer and always preserved?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should:

var b = new ElementType[1];
b.Initialize();

struct ElementType
{
    public ElementType()
    {
        Console.WriteLine(".ctor called");
    }
}

dotnet run prints out ".ctor called".
But trimmed the app doesn't print out anything.

Related question - what should default do in this case, for example:

var c = new ElementType[1];
c[0] = default; // Should this call the default .ctor?

Running it seems like it will NOT call the .ctor - but what does it do?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same goes for AOT - published as Native AOT the app above also doesn't print out anything.

@teo-tsirpanis teo-tsirpanis Oct 24, 2022

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related question - what should default do in this case, for example:

According to the specification, default ignores the parameterless constructor and generates a zeroed instance.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, adding RequiresUnreferencedCode on Array.Initialize has a ripple effect. It introduces warnings in situations where array type is passed into DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All).

I guess it will need more careful thought.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #77426

Comment thread
reflectronic marked this conversation as resolved.
{
MethodTable* pArrayMT = RuntimeHelpers.GetMethodTable(this);
TypeHandle thElem = pArrayMT->GetArrayElementTypeHandle();
if (thElem.IsTypeDesc)
{
return;
}

MethodTable* pElemMT = thElem.AsMethodTable();
if (!pElemMT->HasDefaultConstructor || !pElemMT->IsValueType)
{
return;
}

RuntimeType arrayType = (RuntimeType)GetType();

if (arrayType.GenericCache is not ArrayInitializeCache cache)
{
cache = new ArrayInitializeCache(arrayType);
arrayType.GenericCache = cache;
}

delegate*<ref byte, void> constructorFtn = cache.ConstructorEntrypoint;
ref byte arrayRef = ref MemoryMarshal.GetArrayDataReference(this);
nuint elementSize = pArrayMT->ComponentSize;

for (int i = 0; i < Length; i++)
{
constructorFtn(ref arrayRef);
arrayRef = ref Unsafe.Add(ref arrayRef, elementSize);
}
}

private sealed unsafe partial class ArrayInitializeCache
{
internal readonly delegate*<ref byte, void> ConstructorEntrypoint;

[LibraryImport(RuntimeHelpers.QCall, EntryPoint = "Array_GetElementConstructorEntrypoint")]
private static partial delegate*<ref byte, void> GetElementConstructorEntrypoint(QCallTypeHandle arrayType);

public ArrayInitializeCache(RuntimeType arrayType)
Comment thread
reflectronic marked this conversation as resolved.
{
ConstructorEntrypoint = GetElementConstructorEntrypoint(new QCallTypeHandle(ref arrayType));
}
}
}

#pragma warning disable CA1822 // Mark members as static
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,9 @@ internal unsafe struct MethodTable
[FieldOffset(InterfaceMapOffset)]
public MethodTable** InterfaceMap;

// WFLAGS_LOW_ENUM
private const uint enum_flag_HasDefaultCtor = 0x00000200;

// WFLAGS_HIGH_ENUM
private const uint enum_flag_ContainsPointers = 0x01000000;
private const uint enum_flag_HasComponentSize = 0x80000000;
Expand Down Expand Up @@ -560,6 +563,14 @@ public bool HasTypeEquivalence
}
}

public bool HasDefaultConstructor
{
get
{
return ((HasComponentSize ? 0 : Flags) & enum_flag_HasDefaultCtor) != 0;
}
}

public bool IsMultiDimensionalArray
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
112 changes: 10 additions & 102 deletions src/coreclr/classlibnative/bcltype/arraynative.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,116 +28,24 @@ FCIMPL1(INT32, ArrayNative::GetCorElementTypeOfElementType, ArrayBase* arrayUNSA
}
FCIMPLEND

// array is GC protected by caller
void ArrayInitializeWorker(ARRAYBASEREF * arrayRef,
MethodTable* pArrayMT,
MethodTable* pElemMT)
extern "C" PCODE QCALLTYPE Array_GetElementConstructorEntrypoint(QCall::TypeHandle pArrayTypeHnd)
{
STATIC_CONTRACT_MODE_COOPERATIVE;

// Ensure that the array element type is fully loaded before executing its code
pElemMT->EnsureInstanceActive();
Comment thread
reflectronic marked this conversation as resolved.

//can not use contract here because of SEH
_ASSERTE(IsProtectedByGCFrame (arrayRef));

SIZE_T offset = ArrayBase::GetDataPtrOffset(pArrayMT);
SIZE_T size = pArrayMT->GetComponentSize();
SIZE_T cElements = (*arrayRef)->GetNumComponents();

MethodTable * pCanonMT = pElemMT->GetCanonicalMethodTable();
WORD slot = pCanonMT->GetDefaultConstructorSlot();

PCODE ctorFtn = pCanonMT->GetSlot(slot);

#if defined(TARGET_X86) && !defined(TARGET_UNIX)
BEGIN_CALL_TO_MANAGED();


for (SIZE_T i = 0; i < cElements; i++)
{
// Since GetSlot() is not idempotent and may have returned
// a non-optimal entry-point the first time round.
if (i == 1)
{
ctorFtn = pCanonMT->GetSlot(slot);
}

BYTE* thisPtr = (((BYTE*) OBJECTREFToObject (*arrayRef)) + offset);

#ifdef _DEBUG
__asm {
mov ECX, thisPtr
mov EDX, pElemMT // Instantiation argument if the type is generic
call [ctorFtn]
nop // Mark the fact that we can call managed code
}
#else // _DEBUG
typedef void (__fastcall * CtorFtnType)(BYTE*, BYTE*);
(*(CtorFtnType)ctorFtn)(thisPtr, (BYTE*)pElemMT);
#endif // _DEBUG

offset += size;
}

END_CALL_TO_MANAGED();
#else // TARGET_X86 && !TARGET_UNIX
//
// This is quite a bit slower, but it is portable.
//

for (SIZE_T i =0; i < cElements; i++)
{
// Since GetSlot() is not idempotent and may have returned
// a non-optimal entry-point the first time round.
if (i == 1)
{
ctorFtn = pCanonMT->GetSlot(slot);
}

BYTE* thisPtr = (((BYTE*) OBJECTREFToObject (*arrayRef)) + offset);

PREPARE_NONVIRTUAL_CALLSITE_USING_CODE(ctorFtn);
DECLARE_ARGHOLDER_ARRAY(args, 2);
args[ARGNUM_0] = PTR_TO_ARGHOLDER(thisPtr);
args[ARGNUM_1] = PTR_TO_ARGHOLDER(pElemMT); // Instantiation argument if the type is generic
CALL_MANAGED_METHOD_NORET(args);

offset += size;
}
#endif // !TARGET_X86 || TARGET_UNIX
}


FCIMPL1(void, ArrayNative::Initialize, ArrayBase* array)
{
FCALL_CONTRACT;

if (array == NULL)
{
FCThrowVoid(kNullReferenceException);
}
QCALL_CONTRACT;

PCODE ctorEntrypoint = NULL;

MethodTable* pArrayMT = array->GetMethodTable();
BEGIN_QCALL;

TypeHandle thElem = pArrayMT->GetArrayElementTypeHandle();
if (thElem.IsTypeDesc())
return;
TypeHandle th = pArrayTypeHnd.AsTypeHandle();
MethodTable* pElemMT = th.GetArrayElementTypeHandle().AsMethodTable();
ctorEntrypoint = pElemMT->GetDefaultConstructor()->GetMultiCallableAddrOfCode();

MethodTable * pElemMT = thElem.AsMethodTable();
if (!pElemMT->HasDefaultConstructor() || !pElemMT->IsValueType())
return;

ARRAYBASEREF arrayRef (array);
HELPER_METHOD_FRAME_BEGIN_1(arrayRef);
pElemMT->EnsureInstanceActive();

ArrayInitializeWorker(&arrayRef, pArrayMT, pElemMT);
END_QCALL;

HELPER_METHOD_FRAME_END();
return ctorEntrypoint;
}
FCIMPLEND


// Returns whether you can directly copy an array of srcType into destType.
FCIMPL2(FC_BOOL_RET, ArrayNative::IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst)
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/classlibnative/bcltype/arraynative.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ class ArrayNative
public:
static FCDECL1(INT32, GetCorElementTypeOfElementType, ArrayBase* arrayUNSAFE);

static FCDECL1(void, Initialize, ArrayBase* pArray);

static FCDECL2(FC_BOOL_RET, IsSimpleCopy, ArrayBase* pSrc, ArrayBase* pDst);
static FCDECL5(void, CopySlow, ArrayBase* pSrc, INT32 iSrcIndex, ArrayBase* pDst, INT32 iDstIndex, INT32 iLength);

Expand Down Expand Up @@ -68,4 +66,6 @@ class ArrayNative

};

extern "C" PCODE QCALLTYPE Array_GetElementConstructorEntrypoint(QCall::TypeHandle pArrayTypeHnd);

#endif // _ARRAYNATIVE_H_
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,26 @@ private static void ValidateElementType(Type elementType)
throw new NotSupportedException(SR.NotSupported_OpenType);
}

public void Initialize()
public unsafe void Initialize()
{
// This api is a nop unless the array element type is a value type with an explicit nullary constructor.
// Such a type could not be expressed in C# up until recently.
// TODO: Implement this
return;
EETypePtr pElementEEType = ElementEEType;
if (!pElementEEType.IsValueType)
return;

IntPtr constructorEntryPoint = RuntimeAugments.TypeLoaderCallbacks.TryGetDefaultConstructorForType(new RuntimeTypeHandle(pElementEEType));
if (constructorEntryPoint == IntPtr.Zero)
return;

var constructorFtn = (delegate*<ref byte, void>)RuntimeAugments.TypeLoaderCallbacks.ConvertUnboxingFunctionPointerToUnderlyingNonUnboxingPointer(constructorEntryPoint, new RuntimeTypeHandle(pElementEEType));

ref byte arrayRef = ref MemoryMarshal.GetArrayDataReference(this);
nuint elementSize = ElementSize;

for (int i = 0; i < Length; i++)
{
constructorFtn(ref arrayRef);
arrayRef = ref Unsafe.Add(ref arrayRef, elementSize);
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
Expand Down
1 change: 0 additions & 1 deletion src/coreclr/vm/ecalllist.h
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,6 @@ FCFuncEnd()

FCFuncStart(gArrayFuncs)
FCFuncElement("GetCorElementTypeOfElementType", ArrayNative::GetCorElementTypeOfElementType)
FCFuncElement("Initialize", ArrayNative::Initialize)
FCFuncElement("IsSimpleCopy", ArrayNative::IsSimpleCopy)
FCFuncElement("CopySlow", ArrayNative::CopySlow)
FCFuncElement("InternalCreate", ArrayNative::CreateInstance)
Expand Down
1 change: 1 addition & 0 deletions src/coreclr/vm/qcallentrypoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ static const Entry s_QCall[] =
DllImportEntry(TypeName_GetTypeArguments)
DllImportEntry(TypeName_GetModifiers)
DllImportEntry(TypeName_GetAssemblyName)
DllImportEntry(Array_GetElementConstructorEntrypoint)
DllImportEntry(AssemblyName_InitializeAssemblySpec)
DllImportEntry(AssemblyNative_GetFullName)
DllImportEntry(AssemblyNative_GetLocation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,13 @@ private static void TestTypeCommonInvariants(this Type type)
const BindingFlags bf = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy;
foreach (MemberInfo mem in type.GetMember("*", MemberTypes.All, bf))
{
// Workaround: Do not try to test Array.Initialize, since one of its locals is a function pointer.
// Delete this workaround when https://github.com/dotnet/runtime/issues/69273 is addressed.
if (mem.DeclaringType == mem.DeclaringType.Assembly.GetType("System.Array") && mem.Name == "Initialize")
{
continue;
}

string s = mem.ToString();
Assert.Equal(type, mem.ReflectedType);
Type declaringType = mem.DeclaringType;
Expand Down
52 changes: 37 additions & 15 deletions src/libraries/System.Runtime/tests/System/ArrayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1614,7 +1614,7 @@ public static IEnumerable<object[]> Copy_UnreliableConversion_CantPerform_TestDa
yield return new object[] { new object[] { "1" }, new int[1] };

IEquatable<int>[] interfaceArray1 = new IEquatable<int>[10] { 0, 0, 0, 0, new NotInt32(), 0, 0, 0, 0, 0 };
yield return new object[] { interfaceArray1, new int[10]};
yield return new object[] { interfaceArray1, new int[10] };

IEquatable<int>[] interfaceArray2 = new IEquatable<int>[10] { 0, 0, 0, 0, new NotInt32(), 0, 0, 0, 0, 0 };
yield return new object[] { interfaceArray2, new int[10] };
Expand Down Expand Up @@ -3178,7 +3178,7 @@ public static IEnumerable<object[]> Reverse_TestData()

// Char
yield return new object[] { new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2', '3', '4', '5', '6', '7' }, 0, 33, new char[] { '7', '6', '5', '4', '3', '2', '1', 'z', 'y', 'x', 'w', 'v', 'u', 't', 's', 'r', 'q', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a' } };
yield return new object[] { new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q' }, 0, 17, new char[] { 'q', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a' } };
yield return new object[] { new char[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q' }, 0, 17, new char[] { 'q', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a' } };
yield return new object[] { new char[] { '1', '2', '3', '4', '5' }, 2, 3, new char[] { '1', '2', '5', '4', '3' } };
yield return new object[] { new char[] { '1', '2', '3', '4', '5' }, 0, 0, new char[] { '1', '2', '3', '4', '5' } };
yield return new object[] { new char[] { '1', '2', '3', '4', '5' }, 5, 0, new char[] { '1', '2', '3', '4', '5' } };
Expand Down Expand Up @@ -3225,8 +3225,8 @@ public static IEnumerable<object[]> Reverse_TestData()
var enumArray = new Int32Enum[] { Int32Enum.Case1, Int32Enum.Case2, Int32Enum.Case3, Int32Enum.Case1 };
yield return new object[] { enumArray, 0, 4, new Int32Enum[] { Int32Enum.Case1, Int32Enum.Case3, Int32Enum.Case2, Int32Enum.Case1 } };
yield return new object[] { enumArray, 2, 2, new Int32Enum[] { Int32Enum.Case1, Int32Enum.Case2, Int32Enum.Case1, Int32Enum.Case3 } };
yield return new object[] { enumArray, 0, 0, enumArray};
yield return new object[] { enumArray, 4, 0, enumArray};
yield return new object[] { enumArray, 0, 0, enumArray };
yield return new object[] { enumArray, 4, 0, enumArray };

// ValueType array
ComparableValueType[] valueTypeArray = new ComparableValueType[] { new ComparableValueType(0), new ComparableValueType(1) };
Expand Down Expand Up @@ -3331,18 +3331,18 @@ public static IEnumerable<object[]> Sort_Array_TestData()
expectedPartial.SetValue(2, 3);
expectedPartial.SetValue(4, 4);

yield return new object[] { actual, 1, 4, null, expectedFull };
yield return new object[] { actual, 1, 4, new IntegerComparer(), expectedFull };
yield return new object[] { actual, 2, 2, null, expectedPartial };
yield return new object[] { actual, 2, 2, new IntegerComparer(), expectedPartial };
yield return new object[] { actual, 1, 0, null, actual };
yield return new object[] { actual, 1, 0, new IntegerComparer(), actual };
yield return new object[] { actual, 1, 4, null, expectedFull };
yield return new object[] { actual, 1, 4, new IntegerComparer(), expectedFull };
yield return new object[] { actual, 2, 2, null, expectedPartial };
yield return new object[] { actual, 2, 2, new IntegerComparer(), expectedPartial };
yield return new object[] { actual, 1, 0, null, actual };
yield return new object[] { actual, 1, 0, new IntegerComparer(), actual };
}
}

public static IEnumerable<object[]> Sort_SZArray_TestData()
{
// Int
// Int
yield return new object[] { new int[0], 0, 0, new IntegerComparer(), new int[0] };
yield return new object[] { new int[] { 5 }, 0, 1, new IntegerComparer(), new int[] { 5 } };
yield return new object[] { new int[] { 5, 2 }, 0, 2, new IntegerComparer(), new int[] { 2, 5 } };
Expand All @@ -3359,17 +3359,17 @@ public static IEnumerable<object[]> Sort_SZArray_TestData()
yield return new object[] { new int[] { 4, 3, 2 }, 0, 3, new IntegerComparer(), new int[] { 2, 3, 4 } };
yield return new object[] { new int[] { 4, 3, 2 }, 0, 3, null, new int[] { 2, 3, 4 } };

// String
// String
yield return new object[] { new string[0], 0, 0, null, new string[0] };
yield return new object[] { new string[0], 0, 0, new StringComparer(), new string[0] };
yield return new object[] { new string[] { "5" }, 0, 1, null, new string[] { "5" } };
yield return new object[] { new string[] { "5" }, 0, 1, new StringComparer(), new string[] { "5" } };
yield return new object[] { new string[] { "5", "2" }, 0, 2, null, new string[] { "2", "5" } };
yield return new object[] { new string[] { "5", "2" }, 0, 2, new StringComparer(), new string[] { "2", "5" } };
yield return new object[] { new string[] { "5", "2", "3" }, 0, 3, null, new string[] { "2", "3", "5" } };
yield return new object[] { new string[] { "5", "2", "3"}, 0, 3, new StringComparer(), new string[] { "2", "3", "5" } };
yield return new object[] { new string[] { "5", "2", null }, 0, 3, null, new string[] {null, "2", "5" } };
yield return new object[] { new string[] { "5", "2", null}, 0, 3, new StringComparer(), new string[] { null, "2", "5" } };
yield return new object[] { new string[] { "5", "2", "3" }, 0, 3, new StringComparer(), new string[] { "2", "3", "5" } };
yield return new object[] { new string[] { "5", "2", null }, 0, 3, null, new string[] { null, "2", "5" } };
yield return new object[] { new string[] { "5", "2", null }, 0, 3, new StringComparer(), new string[] { null, "2", "5" } };
yield return new object[] { new string[] { "5", "2", "9", "8", "4", "3", "2", "4", "6" }, 0, 9, new StringComparer(), new string[] { "2", "2", "3", "4", "4", "5", "6", "8", "9" } };
yield return new object[] { new string[] { "5", null, "2", "9", "8", "4", "3", "2", "4", "6" }, 0, 10, new StringComparer(), new string[] { null, "2", "2", "3", "4", "4", "5", "6", "8", "9" } };
yield return new object[] { new string[] { "5", null, "2", "9", "8", "4", "3", "2", "4", "6" }, 3, 4, new StringComparer(), new string[] { "5", null, "2", "3", "4", "8", "9", "2", "4", "6" } };
Expand Down Expand Up @@ -4446,6 +4446,18 @@ public static void MaxSizes()
Assert.Equal(0x7FFFFFC7, Array.MaxLength);
}

[Fact]
public static void Array_Initialize()
{
var array = new StructWithDefaultConstructor[10, 10];
array.Initialize();
Assert.All(array.OfType<StructWithDefaultConstructor>(), a => Assert.True(a.Constructed));

var array2 = new NonGenericClass1[10];
array2.Initialize();
Assert.All(array2, Assert.Null);
}

private static void VerifyArray(Array array, Type elementType, int[] lengths, int[] lowerBounds, object repeatedValue)
{
VerifyArray(array, elementType, lengths, lowerBounds);
Expand Down Expand Up @@ -4590,6 +4602,16 @@ private struct NonGenericStruct
public int z;
}

private struct StructWithDefaultConstructor
{
public StructWithDefaultConstructor()
{
Constructed = true;
}

public bool Constructed;
}

private class IntegerComparer : IComparer, IComparer<int>, IEqualityComparer
{
public int Compare(object x, object y) => Compare((int)x, (int)y);
Expand Down
Loading