Skip to content

Commit 261bc86

Browse files
Generate interface lists for necessary EETypes (#95683)
* Generate interface lists for necessary EETypes The compiler can generate two kinds of `MethodTable` structures: constructed and unconstructed. The constructed one has a fully populated vtable and GCInfo and is required whenever the object type could be allocated on the heap. The unconstructed one is generated for all other scenarios as a size optimization. We were previously also skipping emission of the interface list in the unconstructed case. But interface list might be required for variant casting. We could introduce yet another `MethodTable` kind for this specific scenario, but it doesn't seem to warrant the complexity. Emitting interface list for all types is a less than 0.1% size regression for the Todos app. It is a 0.5% size regression for Hello World. That part is unfortunate. It's mostly due to the useless numeric interfaces. We can get this size back if we do #66716 and start trimming interface lists. Fixes #95574. * Update NecessaryCanonicalEETypeNode.cs
1 parent 6dc1087 commit 261bc86

File tree

5 files changed

+48
-24
lines changed

5 files changed

+48
-24
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public CanonicalEETypeNode(NodeFactory factory, TypeDesc type) : base(factory, t
2929

3030
public override bool StaticDependenciesAreComputed => true;
3131
public override bool IsShareable => IsTypeNodeShareable(_type);
32-
protected override bool EmitVirtualSlotsAndInterfaces => true;
32+
protected override bool EmitVirtualSlots => true;
3333
public override bool ShouldSkipEmittingObjectNode(NodeFactory factory) => false;
3434

3535
protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public ConstructedEETypeNode(NodeFactory factory, TypeDesc type) : base(factory,
2020

2121
public override bool ShouldSkipEmittingObjectNode(NodeFactory factory) => false;
2222

23-
protected override bool EmitVirtualSlotsAndInterfaces => true;
23+
protected override bool EmitVirtualSlots => true;
2424

2525
protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
2626
{

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

+18-22
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ public EETypeNode(NodeFactory factory, TypeDesc type)
9797
_writableDataNode = SupportsWritableData(factory.Target) && !_type.IsCanonicalSubtype(CanonicalFormKind.Any) ? new WritableDataNode(this) : null;
9898
_hasConditionalDependenciesFromMetadataManager = factory.MetadataManager.HasConditionalDependenciesDueToEETypePresence(type);
9999

100-
if (EmitVirtualSlotsAndInterfaces)
100+
if (EmitVirtualSlots)
101101
_virtualMethodAnalysisFlags = AnalyzeVirtualMethods(type);
102102

103103
factory.TypeSystemContext.EnsureLoadableType(type);
@@ -201,7 +201,7 @@ protected bool MightHaveInterfaceDispatchMap(NodeFactory factory)
201201
{
202202
if (!_mightHaveInterfaceDispatchMap.HasValue)
203203
{
204-
_mightHaveInterfaceDispatchMap = EmitVirtualSlotsAndInterfaces && InterfaceDispatchMapNode.MightHaveInterfaceDispatchMap(_type, factory);
204+
_mightHaveInterfaceDispatchMap = EmitVirtualSlots && InterfaceDispatchMapNode.MightHaveInterfaceDispatchMap(_type, factory);
205205
}
206206

207207
return _mightHaveInterfaceDispatchMap.Value;
@@ -238,7 +238,7 @@ protected override ObjectNodeSection GetDehydratedSection(NodeFactory factory)
238238
public static int GetMinimumObjectSize(TypeSystemContext typeSystemContext)
239239
=> typeSystemContext.Target.PointerSize * 3;
240240

241-
protected virtual bool EmitVirtualSlotsAndInterfaces => false;
241+
protected virtual bool EmitVirtualSlots => false;
242242

243243
public override bool InterestingForDynamicDependencyAnalysis
244244
=> (_virtualMethodAnalysisFlags & VirtualMethodAnalysisFlags.InterestingForDynamicDependencies) != 0;
@@ -305,7 +305,7 @@ public sealed override bool HasConditionalStaticDependencies
305305
return true;
306306
}
307307

308-
if (!EmitVirtualSlotsAndInterfaces)
308+
if (!EmitVirtualSlots)
309309
return false;
310310

311311
// Since the vtable is dependency driven, generate conditional static dependencies for
@@ -373,7 +373,7 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
373373
"Information about static bases for type with template"));
374374
}
375375

376-
if (!EmitVirtualSlotsAndInterfaces)
376+
if (!EmitVirtualSlots)
377377
return result;
378378

379379
DefType defType = _type.GetClosestDefType();
@@ -586,7 +586,7 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact
586586
// emitting it.
587587
dependencies.Add(new DependencyListEntry(_optionalFieldsNode, "Optional fields"));
588588

589-
if (EmitVirtualSlotsAndInterfaces)
589+
if (EmitVirtualSlots)
590590
{
591591
if (!_type.IsArrayTypeWithoutGenericInterfaces())
592592
{
@@ -677,7 +677,7 @@ protected override ObjectData GetDehydratableData(NodeFactory factory, bool relo
677677

678678
objData.EmitInt(_type.GetHashCode());
679679

680-
if (EmitVirtualSlotsAndInterfaces)
680+
if (EmitVirtualSlots)
681681
{
682682
// Emit VTable
683683
Debug.Assert(objData.CountBytes - ((ISymbolDefinitionNode)this).Offset == GetVTableOffset(objData.TargetPointerSize));
@@ -687,23 +687,21 @@ protected override ObjectData GetDehydratableData(NodeFactory factory, bool relo
687687
// Update slot count
688688
int numberOfVtableSlots = virtualSlotCounter.CountSlots(ref /* readonly */ objData);
689689
objData.EmitShort(vtableSlotCountReservation, checked((short)numberOfVtableSlots));
690-
691-
// Emit interface map
692-
SlotCounter interfaceSlotCounter = SlotCounter.BeginCounting(ref /* readonly */ objData);
693-
OutputInterfaceMap(factory, ref objData);
694-
695-
// Update slot count
696-
int numberOfInterfaceSlots = interfaceSlotCounter.CountSlots(ref /* readonly */ objData);
697-
objData.EmitShort(interfaceCountReservation, checked((short)numberOfInterfaceSlots));
698-
699690
}
700691
else
701692
{
702693
// If we're not emitting any slots, the number of slots is zero.
703694
objData.EmitShort(vtableSlotCountReservation, 0);
704-
objData.EmitShort(interfaceCountReservation, 0);
705695
}
706696

697+
// Emit interface map
698+
SlotCounter interfaceSlotCounter = SlotCounter.BeginCounting(ref /* readonly */ objData);
699+
OutputInterfaceMap(factory, ref objData);
700+
701+
// Update slot count
702+
int numberOfInterfaceSlots = interfaceSlotCounter.CountSlots(ref /* readonly */ objData);
703+
objData.EmitShort(interfaceCountReservation, checked((short)numberOfInterfaceSlots));
704+
707705
OutputTypeManagerIndirection(factory, ref objData);
708706
OutputWritableData(factory, ref objData);
709707
OutputDispatchMap(factory, ref objData);
@@ -751,7 +749,7 @@ private void OutputFlags(NodeFactory factory, ref ObjectDataBuilder objData, boo
751749
flags |= (uint)EETypeFlags.GenericVarianceFlag;
752750
}
753751

754-
if (EmitVirtualSlotsAndInterfaces && !_type.IsArrayTypeWithoutGenericInterfaces())
752+
if (EmitVirtualSlots && !_type.IsArrayTypeWithoutGenericInterfaces())
755753
{
756754
SealedVTableNode sealedVTable = factory.SealedVTable(_type.ConvertToCanonForm(CanonicalFormKind.Specific));
757755
if (sealedVTable.BuildSealedVTableSlots(factory, relocsOnly) && sealedVTable.NumSealedVTableEntries > 0)
@@ -949,7 +947,7 @@ protected virtual void OutputRelatedType(NodeFactory factory, ref ObjectDataBuil
949947

950948
private void OutputVirtualSlots(NodeFactory factory, ref ObjectDataBuilder objData, TypeDesc implType, TypeDesc declType, TypeDesc templateType, bool relocsOnly)
951949
{
952-
Debug.Assert(EmitVirtualSlotsAndInterfaces);
950+
Debug.Assert(EmitVirtualSlots);
953951

954952
declType = declType.GetClosestDefType();
955953
templateType = templateType.ConvertToCanonForm(CanonicalFormKind.Specific);
@@ -1096,8 +1094,6 @@ protected virtual IEETypeNode GetInterfaceTypeNode(NodeFactory factory, TypeDesc
10961094

10971095
protected virtual void OutputInterfaceMap(NodeFactory factory, ref ObjectDataBuilder objData)
10981096
{
1099-
Debug.Assert(EmitVirtualSlotsAndInterfaces);
1100-
11011097
foreach (var itf in _type.RuntimeInterfaces)
11021098
{
11031099
objData.EmitPointerReloc(GetInterfaceTypeNode(factory, itf));
@@ -1150,7 +1146,7 @@ protected void OutputOptionalFields(NodeFactory factory, ref ObjectDataBuilder o
11501146

11511147
private void OutputSealedVTable(NodeFactory factory, bool relocsOnly, ref ObjectDataBuilder objData)
11521148
{
1153-
if (EmitVirtualSlotsAndInterfaces && !_type.IsArrayTypeWithoutGenericInterfaces())
1149+
if (EmitVirtualSlots && !_type.IsArrayTypeWithoutGenericInterfaces())
11541150
{
11551151
// Sealed vtables have relative pointers, so to minimize size, we build sealed vtables for the canonical types
11561152
SealedVTableNode sealedVTable = factory.SealedVTable(_type.ConvertToCanonForm(CanonicalFormKind.Specific));

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

+9
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ public NecessaryCanonicalEETypeNode(NodeFactory factory, TypeDesc type) : base(f
2121
Debug.Assert(!type.IsMdArray || factory.Target.Abi == TargetAbi.CppCodegen);
2222
}
2323

24+
protected override void OutputInterfaceMap(NodeFactory factory, ref ObjectDataBuilder objData)
25+
{
26+
for (int i = 0; i < _type.RuntimeInterfaces.Length; i++)
27+
{
28+
// Interface omitted for canonical instantiations (constructed at runtime for dynamic types from the native layout info)
29+
objData.EmitZeroPointer();
30+
}
31+
}
32+
2433
protected override ISymbolNode GetBaseTypeNode(NodeFactory factory)
2534
{
2635
return _type.BaseType != null ? factory.NecessaryTypeSymbol(_type.BaseType.NormalizeInstantiation()) : null;

src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs

+19
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ internal static int Run()
5454
TestRefAny.Run();
5555
TestNullableCasting.Run();
5656
TestVariantCasting.Run();
57+
TestVariantDispatchUnconstructedTypes.Run();
5758
TestMDArrayAddressMethod.Run();
5859
TestNativeLayoutGeneration.Run();
5960
TestByRefLikeVTables.Run();
@@ -1159,6 +1160,24 @@ public static void Run()
11591160
}
11601161
}
11611162

1163+
class TestVariantDispatchUnconstructedTypes
1164+
{
1165+
interface IFoo { }
1166+
class Foo : IFoo { }
1167+
1168+
[MethodImpl(MethodImplOptions.NoInlining)]
1169+
static IEnumerable<IFoo> GetFoos() => new Foo[5];
1170+
1171+
public static void Run()
1172+
{
1173+
int j = 0;
1174+
foreach (var f in GetFoos())
1175+
j++;
1176+
if (j != 5)
1177+
throw new Exception();
1178+
}
1179+
}
1180+
11621181
class TestMDArrayAddressMethod
11631182
{
11641183
[MethodImpl(MethodImplOptions.NoInlining)]

0 commit comments

Comments
 (0)