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
45 changes: 34 additions & 11 deletions src/coreclr/tools/Common/Compiler/MethodExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using ILCompiler.DependencyAnalysis;

using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;

using Debug = System.Diagnostics.Debug;

namespace ILCompiler
{
public static class MethodExtensions
Expand Down Expand Up @@ -73,6 +77,7 @@ public static string GetUnmanagedCallersOnlyExportName(this EcmaMethod This)
return null;
}

#if !READYTORUN
/// <summary>
/// Determine whether a method can go into the sealed vtable of a type. Such method must be a sealed virtual
/// method that is not overriding any method on a base type.
Expand All @@ -82,25 +87,43 @@ public static string GetUnmanagedCallersOnlyExportName(this EcmaMethod This)
/// since all of their collection interface methods are sealed and implemented on the System.Array and
/// System.Array&lt;T&gt; base types, and therefore we can minimize the vtable sizes of all derived array types.
/// </summary>
public static bool CanMethodBeInSealedVTable(this MethodDesc method)
public static bool CanMethodBeInSealedVTable(this MethodDesc method, NodeFactory factory)
{
bool isInterfaceMethod = method.OwningType.IsInterface;
Debug.Assert(!method.OwningType.ContainsSignatureVariables(treatGenericParameterLikeSignatureVariable: true));

TypeDesc owningType = method.OwningType;

// Methods on interfaces never go into sealed vtable
// We would hit this code path for default implementations of interface methods (they are newslot+final).
// Interface types don't get physical slots, but they have logical slot numbers and that logic shouldn't
// attempt to place final+newslot methods differently.
if (method.IsFinal && method.IsNewSlot && !isInterfaceMethod)
// Interface types don't have physical slots so we never optimize to sealed slots
if (owningType.IsInterface)
return false;

// Implementations of static virtual methods go into the sealed vtable.
if (method.Signature.IsStatic)
return true;

// Implementations of static virtual method also go into the sealed vtable.
// Again, we don't let that happen for interface methods because the slot numbers are only logical,
// not physical.
if (method.Signature.IsStatic && !isInterfaceMethod)
// If the owning type is already considered sealed, there's little benefit in placing the slots
// in the sealed vtable: the sealed vtable has these properties:
Copy link
Member

Choose a reason for hiding this comment

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

What are the downsides of keeping this simple and placing them in the sealed vtable? Are you trying to save the extra pointer to the sealed vtable?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, the extra pointer to the vtable. The old logic was just method.IsFinal && method.IsNewSlot so we didn't even look whether the type is sealed. Now we end up doing this for a lot more methods, often on valuetypes where it makes little sense.

//
// 1. We don't need to repeat them in derived classes.
// 2. The slots use 4-byte relative pointers, so they can be smaller.
// 3. The sealed vtable is shared among canonically-equivalent types.
//
// Benefit 1 doesn't apply to sealed types by definition. Benefit 2 doesn't manifest itself
// when data dehydration is enabled (which is the default) since pointers are compressed either way.
// Benefit 3 is still real, so we condition this opt out on type not having a canonical form.
if (factory.DevirtualizationManager.IsEffectivelySealed(owningType)
&& !owningType.ConvertToCanonForm(CanonicalFormKind.Specific).IsCanonicalSubtype(CanonicalFormKind.Any))
{
return false;
}

// Newslot final methods go into the sealed vtable.
if (method.IsNewSlot && factory.DevirtualizationManager.IsEffectivelySealed(method))
return true;

return false;
}
#endif

public static bool NotCallableWithoutOwningEEType(this MethodDesc method)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public DelegateCreationInfo GetDelegateCtor(TypeDesc delegateType, MethodDesc ta
// If we're creating a delegate to a virtual method that cannot be overridden, devirtualize.
// This is not just an optimization - it's required for correctness in the presence of sealed
// vtable slots.
if (followVirtualDispatch && (target.IsFinal || target.OwningType.IsSealed()))
if (followVirtualDispatch && NodeFactory.DevirtualizationManager.IsEffectivelySealed(target))
followVirtualDispatch = false;

if (followVirtualDispatch)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ public sealed override IEnumerable<CombinedDependencyListEntry> GetConditionalSt
// We don't do this if the method can be placed in the sealed vtable since
// those can never be overriden by children anyway.
bool canUseTentativeMethod = isNonInterfaceAbstractType
&& !decl.CanMethodBeInSealedVTable()
&& !decl.CanMethodBeInSealedVTable(factory)
&& factory.CompilationModuleGroup.AllowVirtualMethodOnAbstractTypeOptimization(canonImpl);
IMethodNode implNode = canUseTentativeMethod ?
factory.TentativeMethodEntrypoint(canonImpl, impl.OwningType.IsValueType) :
Expand Down Expand Up @@ -1039,7 +1039,7 @@ private void OutputVirtualSlots(NodeFactory factory, ref ObjectDataBuilder objDa

// Final NewSlot methods cannot be overridden, and therefore can be placed in the sealed-vtable to reduce the size of the vtable
// of this type and any type that inherits from it.
if (declMethod.CanMethodBeInSealedVTable() && !declType.IsArrayTypeWithoutGenericInterfaces())
if (declMethod.CanMethodBeInSealedVTable(factory) && !declType.IsArrayTypeWithoutGenericInterfaces())
continue;

bool shouldEmitImpl = !implMethod.IsAbstract;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1433,7 +1433,7 @@ private static void ProcessVTableEntriesForCallingConventionSignatureGeneration(

MethodDesc implMethod = closestDefType.FindVirtualFunctionTargetMethodOnObjectType(declMethod);

if (implMethod.CanMethodBeInSealedVTable() && !implType.IsArrayTypeWithoutGenericInterfaces())
if (implMethod.CanMethodBeInSealedVTable(factory) && !implType.IsArrayTypeWithoutGenericInterfaces())
{
// Sealed vtable entries on other types in the hierarchy should not be reported (types read entries
// from their own sealed vtables, and not from the sealed vtables of base types).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
}
else
{
if (ReflectionVirtualInvokeMapNode.NeedsVirtualInvokeInfo(slotDefiningMethod) && !factory.VTable(slotDefiningMethod.OwningType).HasFixedSlots)
if (ReflectionVirtualInvokeMapNode.NeedsVirtualInvokeInfo(factory, slotDefiningMethod) && !factory.VTable(slotDefiningMethod.OwningType).HasFixedSlots)
dependencies.Add(factory.VirtualMethodUse(slotDefiningMethod), "Virtually callable reflectable method");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
if (method.IsDefaultConstructor)
flags |= InvokeTableFlags.IsDefaultConstructor;

if (ReflectionVirtualInvokeMapNode.NeedsVirtualInvokeInfo(method))
if (ReflectionVirtualInvokeMapNode.NeedsVirtualInvokeInfo(factory, method))
flags |= InvokeTableFlags.HasVirtualInvoke;

if (!method.IsAbstract)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,12 @@ public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
public override bool StaticDependenciesAreComputed => true;
protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);

public static bool NeedsVirtualInvokeInfo(MethodDesc method)
public static bool NeedsVirtualInvokeInfo(NodeFactory factory, MethodDesc method)
{
if (!method.IsVirtual)
return false;

if (method.IsFinal)
return false;

if (method.OwningType.IsSealed())
if (factory.DevirtualizationManager.IsEffectivelySealed(method))
return false;

return true;
Expand Down Expand Up @@ -83,7 +80,7 @@ public static MethodDesc GetDeclaringVirtualMethodAndHierarchyDistance(MethodDes

public static void GetVirtualInvokeMapDependencies(ref DependencyList dependencies, NodeFactory factory, MethodDesc method)
{
if (NeedsVirtualInvokeInfo(method))
if (NeedsVirtualInvokeInfo(factory, method))
{
dependencies ??= new DependencyList();

Expand Down Expand Up @@ -137,7 +134,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
continue;

// Only virtual methods are interesting
if (!NeedsVirtualInvokeInfo(method))
if (!NeedsVirtualInvokeInfo(factory, method))
continue;

//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)

MethodDesc implMethod = declType.FindVirtualFunctionTargetMethodOnObjectType(virtualSlots[i]);

if (implMethod.CanMethodBeInSealedVTable())
if (implMethod.CanMethodBeInSealedVTable(factory))
_sealedVTableEntries.Add(SealedVTableEntry.FromVirtualMethod(implMethod));
}

Expand Down Expand Up @@ -162,8 +162,7 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)
// dispatch will walk the inheritance chain).
if (implMethod != null)
{
if (implMethod.Signature.IsStatic ||
(implMethod.CanMethodBeInSealedVTable() && !implMethod.OwningType.HasSameTypeDefinition(declType)))
if (implMethod.Signature.IsStatic || !implMethod.OwningType.HasSameTypeDefinition(declType))
{
TypeDesc implType = declType;
while (!implType.HasSameTypeDefinition(implMethod.OwningType))
Expand All @@ -173,7 +172,8 @@ public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)
if (!implType.IsTypeDefinition)
targetMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(implMethod.GetTypicalMethodDefinition(), (InstantiatedType)implType);

_sealedVTableEntries.Add(SealedVTableEntry.FromVirtualMethod(targetMethod));
if (targetMethod.CanMethodBeInSealedVTable(factory) || implMethod.Signature.IsStatic)
_sealedVTableEntries.Add(SealedVTableEntry.FromVirtualMethod(targetMethod));
}
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ protected override void EmitCode(NodeFactory factory, ref ARMEmitter encoder, bo
MethodDesc targetMethod = (MethodDesc)Target;

Debug.Assert(!targetMethod.OwningType.IsInterface);
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable(factory));

int pointerSize = factory.Target.PointerSize;

Expand Down Expand Up @@ -126,7 +126,7 @@ protected override void EmitCode(NodeFactory factory, ref ARMEmitter encoder, bo

if (target.TargetNeedsVTableLookup)
{
Debug.Assert(!target.TargetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!target.TargetMethod.CanMethodBeInSealedVTable(factory));

encoder.EmitLDR(encoder.TargetRegister.Arg2, encoder.TargetRegister.Arg1);

Expand Down Expand Up @@ -175,7 +175,7 @@ protected override void EmitCode(NodeFactory factory, ref ARMEmitter encoder, bo

encoder.EmitLDR(encoder.TargetRegister.Result, encoder.TargetRegister.Arg0);

Debug.Assert(!targetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable(factory));

int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod, targetMethod.OwningType);
Debug.Assert(slot != -1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ protected override void EmitCode(NodeFactory factory, ref ARM64Emitter encoder,
MethodDesc targetMethod = (MethodDesc)Target;

Debug.Assert(!targetMethod.OwningType.IsInterface);
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable(factory));

int pointerSize = factory.Target.PointerSize;

Expand Down Expand Up @@ -161,7 +161,7 @@ protected override void EmitCode(NodeFactory factory, ref ARM64Emitter encoder,

if (target.TargetNeedsVTableLookup)
{
Debug.Assert(!target.TargetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!target.TargetMethod.CanMethodBeInSealedVTable(factory));

encoder.EmitLDR(encoder.TargetRegister.Arg2, encoder.TargetRegister.Arg1);

Expand Down Expand Up @@ -210,7 +210,7 @@ protected override void EmitCode(NodeFactory factory, ref ARM64Emitter encoder,

encoder.EmitLDR(encoder.TargetRegister.Result, encoder.TargetRegister.Arg0);

Debug.Assert(!targetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable(factory));

int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod, targetMethod.OwningType);
Debug.Assert(slot != -1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ protected override void EmitCode(NodeFactory factory, ref LoongArch64Emitter enc
MethodDesc targetMethod = (MethodDesc)Target;

Debug.Assert(!targetMethod.OwningType.IsInterface);
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable(factory));

int pointerSize = factory.Target.PointerSize;

Expand Down Expand Up @@ -133,7 +133,7 @@ protected override void EmitCode(NodeFactory factory, ref LoongArch64Emitter enc

if (target.TargetNeedsVTableLookup)
{
Debug.Assert(!target.TargetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!target.TargetMethod.CanMethodBeInSealedVTable(factory));

encoder.EmitLD(encoder.TargetRegister.Arg2, encoder.TargetRegister.Arg1, 0);

Expand Down Expand Up @@ -182,7 +182,7 @@ protected override void EmitCode(NodeFactory factory, ref LoongArch64Emitter enc

encoder.EmitLD(encoder.TargetRegister.Result, encoder.TargetRegister.Arg0, 0);

Debug.Assert(!targetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable(factory));

int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod, targetMethod.OwningType);
Debug.Assert(slot != -1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ protected override void EmitCode(NodeFactory factory, ref RiscV64Emitter encoder
MethodDesc targetMethod = (MethodDesc)Target;

Debug.Assert(!targetMethod.OwningType.IsInterface);
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable(factory));

int pointerSize = factory.Target.PointerSize;

Expand Down Expand Up @@ -130,7 +130,7 @@ protected override void EmitCode(NodeFactory factory, ref RiscV64Emitter encoder

if (target.TargetNeedsVTableLookup)
{
Debug.Assert(!target.TargetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!target.TargetMethod.CanMethodBeInSealedVTable(factory));

encoder.EmitLD(encoder.TargetRegister.Arg2, encoder.TargetRegister.Arg1, 0);

Expand Down Expand Up @@ -179,7 +179,7 @@ protected override void EmitCode(NodeFactory factory, ref RiscV64Emitter encoder

encoder.EmitLD(encoder.TargetRegister.Result, encoder.TargetRegister.Arg0, 0);

Debug.Assert(!targetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable(factory));

int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod, targetMethod.OwningType);
Debug.Assert(slot != -1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ protected override void EmitCode(NodeFactory factory, ref X64Emitter encoder, bo
MethodDesc targetMethod = (MethodDesc)Target;

Debug.Assert(!targetMethod.OwningType.IsInterface);
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable(factory));

AddrMode loadFromThisPtr = new AddrMode(encoder.TargetRegister.Arg0, null, 0, 0, AddrModeSize.Int64);
encoder.EmitMOV(encoder.TargetRegister.Result, ref loadFromThisPtr);
Expand Down Expand Up @@ -162,7 +162,7 @@ protected override void EmitCode(NodeFactory factory, ref X64Emitter encoder, bo

if (target.TargetNeedsVTableLookup)
{
Debug.Assert(!target.TargetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!target.TargetMethod.CanMethodBeInSealedVTable(factory));

AddrMode loadFromThisPtr = new AddrMode(encoder.TargetRegister.Arg1, null, 0, 0, AddrModeSize.Int64);
encoder.EmitMOV(encoder.TargetRegister.Arg2, ref loadFromThisPtr);
Expand Down Expand Up @@ -210,7 +210,7 @@ protected override void EmitCode(NodeFactory factory, ref X64Emitter encoder, bo
AddrMode loadFromThisPtr = new AddrMode(encoder.TargetRegister.Arg0, null, 0, 0, AddrModeSize.Int64);
encoder.EmitMOV(encoder.TargetRegister.Result, ref loadFromThisPtr);

Debug.Assert(!targetMethod.CanMethodBeInSealedVTable());
Debug.Assert(!targetMethod.CanMethodBeInSealedVTable(factory));

int slot = VirtualMethodSlotHelper.GetVirtualMethodSlot(factory, targetMethod, targetMethod.OwningType);
Debug.Assert(slot != -1);
Expand Down
Loading