Skip to content

Commit

Permalink
Generate interface lists for necessary EETypes
Browse files Browse the repository at this point in the history
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 dotnet#66716 and start trimming interface lists.

Fixes dotnet#95574.
  • Loading branch information
MichalStrehovsky committed Dec 6, 2023
1 parent 71f3bf1 commit 6adb423
Show file tree
Hide file tree
Showing 4 changed files with 39 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public CanonicalEETypeNode(NodeFactory factory, TypeDesc type) : base(factory, t

public override bool StaticDependenciesAreComputed => true;
public override bool IsShareable => IsTypeNodeShareable(_type);
protected override bool EmitVirtualSlotsAndInterfaces => true;
protected override bool EmitVirtualSlots => true;
public override bool ShouldSkipEmittingObjectNode(NodeFactory factory) => false;

protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public ConstructedEETypeNode(NodeFactory factory, TypeDesc type) : base(factory,

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

protected override bool EmitVirtualSlotsAndInterfaces => true;
protected override bool EmitVirtualSlots => true;

protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public EETypeNode(NodeFactory factory, TypeDesc type)
_writableDataNode = SupportsWritableData(factory.Target) && !_type.IsCanonicalSubtype(CanonicalFormKind.Any) ? new WritableDataNode(this) : null;
_hasConditionalDependenciesFromMetadataManager = factory.MetadataManager.HasConditionalDependenciesDueToEETypePresence(type);

if (EmitVirtualSlotsAndInterfaces)
if (EmitVirtualSlots)
_virtualMethodAnalysisFlags = AnalyzeVirtualMethods(type);

factory.TypeSystemContext.EnsureLoadableType(type);
Expand Down Expand Up @@ -201,7 +201,7 @@ protected bool MightHaveInterfaceDispatchMap(NodeFactory factory)
{
if (!_mightHaveInterfaceDispatchMap.HasValue)
{
_mightHaveInterfaceDispatchMap = EmitVirtualSlotsAndInterfaces && InterfaceDispatchMapNode.MightHaveInterfaceDispatchMap(_type, factory);
_mightHaveInterfaceDispatchMap = EmitVirtualSlots && InterfaceDispatchMapNode.MightHaveInterfaceDispatchMap(_type, factory);
}

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

protected virtual bool EmitVirtualSlotsAndInterfaces => false;
protected virtual bool EmitVirtualSlots => false;

public override bool InterestingForDynamicDependencyAnalysis
=> (_virtualMethodAnalysisFlags & VirtualMethodAnalysisFlags.InterestingForDynamicDependencies) != 0;
Expand Down Expand Up @@ -305,7 +305,7 @@ public sealed override bool HasConditionalStaticDependencies
return true;
}

if (!EmitVirtualSlotsAndInterfaces)
if (!EmitVirtualSlots)
return false;

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

if (!EmitVirtualSlotsAndInterfaces)
if (!EmitVirtualSlots)
return result;

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

if (EmitVirtualSlotsAndInterfaces)
if (EmitVirtualSlots)
{
if (!_type.IsArrayTypeWithoutGenericInterfaces())
{
Expand Down Expand Up @@ -677,7 +677,7 @@ protected override ObjectData GetDehydratableData(NodeFactory factory, bool relo

objData.EmitInt(_type.GetHashCode());

if (EmitVirtualSlotsAndInterfaces)
if (EmitVirtualSlots)
{
// Emit VTable
Debug.Assert(objData.CountBytes - ((ISymbolDefinitionNode)this).Offset == GetVTableOffset(objData.TargetPointerSize));
Expand All @@ -687,23 +687,21 @@ protected override ObjectData GetDehydratableData(NodeFactory factory, bool relo
// Update slot count
int numberOfVtableSlots = virtualSlotCounter.CountSlots(ref /* readonly */ objData);
objData.EmitShort(vtableSlotCountReservation, checked((short)numberOfVtableSlots));

// Emit interface map
SlotCounter interfaceSlotCounter = SlotCounter.BeginCounting(ref /* readonly */ objData);
OutputInterfaceMap(factory, ref objData);

// Update slot count
int numberOfInterfaceSlots = interfaceSlotCounter.CountSlots(ref /* readonly */ objData);
objData.EmitShort(interfaceCountReservation, checked((short)numberOfInterfaceSlots));

}
else
{
// If we're not emitting any slots, the number of slots is zero.
objData.EmitShort(vtableSlotCountReservation, 0);
objData.EmitShort(interfaceCountReservation, 0);
}

// Emit interface map
SlotCounter interfaceSlotCounter = SlotCounter.BeginCounting(ref /* readonly */ objData);
OutputInterfaceMap(factory, ref objData);

// Update slot count
int numberOfInterfaceSlots = interfaceSlotCounter.CountSlots(ref /* readonly */ objData);
objData.EmitShort(interfaceCountReservation, checked((short)numberOfInterfaceSlots));

OutputTypeManagerIndirection(factory, ref objData);
OutputWritableData(factory, ref objData);
OutputDispatchMap(factory, ref objData);
Expand Down Expand Up @@ -751,7 +749,7 @@ private void OutputFlags(NodeFactory factory, ref ObjectDataBuilder objData, boo
flags |= (uint)EETypeFlags.GenericVarianceFlag;
}

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

private void OutputVirtualSlots(NodeFactory factory, ref ObjectDataBuilder objData, TypeDesc implType, TypeDesc declType, TypeDesc templateType, bool relocsOnly)
{
Debug.Assert(EmitVirtualSlotsAndInterfaces);
Debug.Assert(EmitVirtualSlots);

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

protected virtual void OutputInterfaceMap(NodeFactory factory, ref ObjectDataBuilder objData)
{
Debug.Assert(EmitVirtualSlotsAndInterfaces);

foreach (var itf in _type.RuntimeInterfaces)
{
objData.EmitPointerReloc(GetInterfaceTypeNode(factory, itf));
Expand Down Expand Up @@ -1150,7 +1146,7 @@ protected void OutputOptionalFields(NodeFactory factory, ref ObjectDataBuilder o

private void OutputSealedVTable(NodeFactory factory, bool relocsOnly, ref ObjectDataBuilder objData)
{
if (EmitVirtualSlotsAndInterfaces && !_type.IsArrayTypeWithoutGenericInterfaces())
if (EmitVirtualSlots && !_type.IsArrayTypeWithoutGenericInterfaces())
{
// Sealed vtables have relative pointers, so to minimize size, we build sealed vtables for the canonical types
SealedVTableNode sealedVTable = factory.SealedVTable(_type.ConvertToCanonForm(CanonicalFormKind.Specific));
Expand Down
19 changes: 19 additions & 0 deletions src/tests/nativeaot/SmokeTests/UnitTests/Generics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ internal static int Run()
TestRefAny.Run();
TestNullableCasting.Run();
TestVariantCasting.Run();
TestVariantDispatchUnconstructedTypes.Run();
TestMDArrayAddressMethod.Run();
TestNativeLayoutGeneration.Run();
TestByRefLikeVTables.Run();
Expand Down Expand Up @@ -1159,6 +1160,24 @@ public static void Run()
}
}

class TestVariantDispatchUnconstructedTypes
{
interface IFoo { }
class Foo : IFoo { }

[MethodImpl(MethodImplOptions.NoInlining)]
static IEnumerable<IFoo> GetFoos() => new Foo[5];

public static void Run()
{
int j = 0;
foreach (var f in GetFoos())
j++;
if (j != 5)
throw new Exception();
}
}

class TestMDArrayAddressMethod
{
[MethodImpl(MethodImplOptions.NoInlining)]
Expand Down

0 comments on commit 6adb423

Please sign in to comment.