Skip to content

Commit 37fa590

Browse files
jkotasmatouskozak
authored andcommitted
[NativeAOT] Fix Activator.CreateInstance for shared generic structs (dotnet#101021)
The default constructor has to be invoked using fat pointer in shared generic structs.
1 parent a095f12 commit 37fa590

File tree

10 files changed

+87
-23
lines changed

10 files changed

+87
-23
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public static partial class Activator
6363
else
6464
{
6565
t = default!;
66-
RawCalliHelper.Call(defaultConstructor, ref Unsafe.As<T, byte>(ref t));
66+
RawCalliHelper.CallDefaultStructConstructor(defaultConstructor, ref Unsafe.As<T, byte>(ref t));
6767

6868
// Debugger goo so that stepping in works. Only affects debug info generation.
6969
// The call gets optimized away.

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,14 @@ public unsafe void Initialize()
143143
if (constructorEntryPoint == IntPtr.Zero)
144144
return;
145145

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

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

151151
for (int i = 0; i < Length; i++)
152152
{
153-
constructorFtn(ref arrayRef);
153+
RawCalliHelper.CallDefaultStructConstructor(constructorFtn, ref arrayRef);
154154
arrayRef = ref Unsafe.Add(ref arrayRef, elementSize);
155155
}
156156
}

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/TypeLoaderExports.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
using Internal.Runtime;
1212
using Internal.Runtime.Augments;
13+
using Internal.Runtime.CompilerServices;
1314

1415
namespace System.Runtime
1516
{
@@ -162,8 +163,20 @@ private static unsafe Value CacheMiss(IntPtr context, IntPtr signature, RuntimeO
162163
internal static unsafe class RawCalliHelper
163164
{
164165
[MethodImpl(MethodImplOptions.AggressiveInlining)]
165-
public static void Call(System.IntPtr pfn, ref byte data)
166-
=> ((delegate*<ref byte, void>)pfn)(ref data);
166+
public static void CallDefaultStructConstructor(System.IntPtr pfn, ref byte data)
167+
{
168+
// Manually expand call of the instance method fat pointer. We cannot use a regular static C# function
169+
// pointer call since it would not work for shared generic instance method.
170+
if (FunctionPointerOps.IsGenericMethodPointer(pfn))
171+
{
172+
GenericMethodDescriptor* gmd = FunctionPointerOps.ConvertToGenericDescriptor(pfn);
173+
((delegate*<ref byte, IntPtr, void>)gmd->MethodFunctionPointer)(ref data, gmd->InstantiationArgument);
174+
}
175+
else
176+
{
177+
((delegate*<ref byte, void>)pfn)(ref data);
178+
}
179+
}
167180

168181
[MethodImpl(MethodImplOptions.AggressiveInlining)]
169182
public static T Call<T>(System.IntPtr pfn, IntPtr arg)

src/coreclr/nativeaot/System.Private.TypeLoader/src/Internal/Runtime/TypeLoader/GenericDictionaryCell.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,9 +337,19 @@ internal override IntPtr Create(TypeBuilder builder)
337337
{
338338
IntPtr result = TypeLoaderEnvironment.TryGetDefaultConstructorForType(Type);
339339

340-
341-
if (result == IntPtr.Zero)
340+
if (result != IntPtr.Zero)
341+
{
342+
if (Type.IsValueType)
343+
{
344+
result = TypeLoaderEnvironment.ConvertUnboxingFunctionPointerToUnderlyingNonUnboxingPointer(result,
345+
builder.GetRuntimeTypeHandle(Type));
346+
}
347+
}
348+
else
349+
{
342350
result = RuntimeAugments.GetFallbackDefaultConstructor();
351+
}
352+
343353
return result;
344354
}
345355
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Compilation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ public ISymbolNode ComputeConstantLookup(ReadyToRunHelperId lookupKind, object t
326326
{
327327
var type = (TypeDesc)targetOfLookup;
328328
MethodDesc ctor = GetConstructorForCreateInstanceIntrinsic(type);
329-
return NodeFactory.CanonicalEntrypoint(ctor);
329+
return type.IsValueType ? NodeFactory.ExactCallableAddress(ctor) : NodeFactory.CanonicalEntrypoint(ctor);
330330
}
331331
case ReadyToRunHelperId.ObjectAllocator:
332332
{

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/GenericLookupResult.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ public override ISymbolNode GetTarget(NodeFactory factory, GenericLookupResultCo
781781
{
782782
TypeDesc instantiatedType = _type.GetNonRuntimeDeterminedTypeFromRuntimeDeterminedSubtypeViaSubstitution(dictionary.TypeInstantiation, dictionary.MethodInstantiation);
783783
MethodDesc defaultCtor = Compilation.GetConstructorForCreateInstanceIntrinsic(instantiatedType);
784-
return factory.CanonicalEntrypoint(defaultCtor);
784+
return instantiatedType.IsValueType ? factory.ExactCallableAddress(defaultCtor) : factory.CanonicalEntrypoint(defaultCtor);
785785
}
786786

787787
public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)

src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,9 @@ private void ImportCall(ILOpcode opcode, int token)
360360
}
361361
else
362362
{
363-
MethodDesc ctor = Compilation.GetConstructorForCreateInstanceIntrinsic(method.Instantiation[0]);
364-
_dependencies.Add(_factory.CanonicalEntrypoint(ctor), reason);
363+
TypeDesc type = method.Instantiation[0];
364+
MethodDesc ctor = Compilation.GetConstructorForCreateInstanceIntrinsic(type);
365+
_dependencies.Add(type.IsValueType ? _factory.ExactCallableAddress(ctor) : _factory.CanonicalEntrypoint(ctor), reason);
365366
}
366367

367368
return;

src/libraries/System.Runtime/tests/System.Runtime.Tests/System/ActivatorTests.Generic.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,22 @@ public void CreateInstanceT_StructWithoutDefaultConstructor_InvokesConstructor()
6161
public void CreateInstanceT_StructWithDefaultConstructorThatThrows_ThrowsTargetInvocationException() =>
6262
Assert.Throws<TargetInvocationException>(() => Activator.CreateInstance<StructWithDefaultConstructorThatThrows>());
6363

64+
[Fact]
65+
public void CreateInstanceT_GenericTypes()
66+
{
67+
TestGenericClassWithDefaultConstructor<string>();
68+
TestGenericClassWithDefaultConstructor<int>();
69+
70+
TestGenericStructWithDefaultConstructor<string>();
71+
TestGenericStructWithDefaultConstructor<int>();
72+
73+
void TestGenericClassWithDefaultConstructor<T>()
74+
=> Assert.Equal(typeof(T), Activator.CreateInstance<GenericClassWithDefaultConstructor<T>>().TypeOfT);
75+
76+
void TestGenericStructWithDefaultConstructor<T>()
77+
=> Assert.Equal(typeof(T), Activator.CreateInstance<GenericStructWithDefaultConstructor<T>>().TypeOfT);
78+
}
79+
6480
private interface IInterface
6581
{
6682
}
@@ -96,5 +112,27 @@ private class ClassWithDefaultConstructorThatThrows
96112
public ClassWithDefaultConstructorThatThrows() =>
97113
throw new Exception();
98114
}
115+
116+
public class GenericClassWithDefaultConstructor<T>
117+
{
118+
public GenericClassWithDefaultConstructor() =>
119+
TypeOfT = typeof(T);
120+
121+
public Type TypeOfT { get; }
122+
}
123+
124+
public struct StructWithDefaultConstructorThatThrows
125+
{
126+
public StructWithDefaultConstructorThatThrows() =>
127+
throw new Exception();
128+
}
129+
130+
public struct GenericStructWithDefaultConstructor<T>
131+
{
132+
public GenericStructWithDefaultConstructor() =>
133+
TypeOfT = typeof(T);
134+
135+
public Type TypeOfT { get; }
136+
}
99137
}
100138
}

src/libraries/System.Runtime/tests/System.Runtime.Tests/TestStructs/System.TestStructs.il

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -57,18 +57,6 @@
5757
ret
5858
}
5959
}
60-
61-
.class public sequential sealed StructWithDefaultConstructorThatThrows
62-
extends [System.Runtime]System.ValueType
63-
{
64-
.size 1
65-
66-
.method public hidebysig specialname rtspecialname instance void .ctor () cil managed
67-
{
68-
newobj instance void [System.Runtime]System.Exception::.ctor()
69-
throw
70-
}
71-
}
7260
}
7361

7462
.class public sequential sealed '.GlobalStructStartingWithDot'

src/tests/nativeaot/SmokeTests/DynamicGenerics/activation.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,18 @@ public override string ToString()
131131
return memberVar;
132132
}
133133
}
134+
struct StructToString<T>
135+
{
136+
string memberVar;
137+
public StructToString()
138+
{
139+
memberVar = typeof(T).Name;
140+
}
141+
public override string ToString()
142+
{
143+
return memberVar;
144+
}
145+
}
134146
class SomeUnrealtedType<T>
135147
{
136148
string memberVar;
@@ -195,5 +207,7 @@ public static void TestDefaultCtorInLazyGenerics()
195207
AllocViaGVMBase typeWithGVM = AllocViaGVMDerived.Alloc();
196208
Assert.AreEqual("ToStringIsInteresting1", typeWithGVM.Alloc<ToStringIsInteresting1>().ToString());
197209
Assert.AreEqual("ToStringIsInteresting2", typeWithGVM.Alloc<ToStringIsInteresting2>().ToString());
210+
Assert.AreEqual("ToStringIsInteresting1", typeWithGVM.Alloc<StructToString<ToStringIsInteresting1>>().ToString());
211+
Assert.AreEqual("ToStringIsInteresting2", typeWithGVM.Alloc<StructToString<ToStringIsInteresting2>>().ToString());
198212
}
199213
}

0 commit comments

Comments
 (0)