File: Compiler\DependencyAnalysis\SealedVTableNode.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.Compiler\ILCompiler.Compiler.csproj (ILCompiler.Compiler)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.


using System;
using System.Collections.Generic;
using System.Diagnostics;
using Internal.Runtime;
using Internal.Text;
using Internal.TypeSystem;

namespace ILCompiler.DependencyAnalysis
{
    public class SealedVTableNode : ObjectNode, ISymbolDefinitionNode
    {
        private readonly TypeDesc _type;
        private List<SealedVTableEntry> _sealedVTableEntries;
        private DependencyList _nonRelocationDependencies;

        public SealedVTableNode(TypeDesc type)
        {
            // Multidimensional arrays should not get a sealed vtable or a dispatch map. Runtime should use the
            // sealed vtable and dispatch map of the System.Array basetype instead.
            // Pointer arrays also follow the same path
            Debug.Assert(!type.IsArrayTypeWithoutGenericInterfaces());
            Debug.Assert(!type.IsRuntimeDeterminedSubtype);

            _type = type;
        }

        protected override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);

        public override ObjectNodeSection GetSection(NodeFactory factory) => _type.Context.Target.IsWindows ? ObjectNodeSection.FoldableReadOnlyDataSection : ObjectNodeSection.DataSection;

        public virtual void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
        {
            sb.Append(nameMangler.CompilationUnitPrefix).Append("__SealedVTable_"u8).Append(nameMangler.NodeMangler.MethodTable(_type));
        }

        int ISymbolNode.Offset => 0;
        int ISymbolDefinitionNode.Offset => 0;
        public override bool IsShareable => EETypeNode.IsTypeNodeShareable(_type);
        public override bool StaticDependenciesAreComputed => true;

        /// <summary>
        /// Returns the number of sealed vtable slots on the type. This API should only be called after successfully
        /// building the sealed vtable slots.
        /// </summary>
        public int NumSealedVTableEntries
        {
            get
            {
                if (_sealedVTableEntries == null)
                    throw new NotSupportedException();

                return _sealedVTableEntries.Count;
            }
        }

        public override bool ShouldSkipEmittingObjectNode(NodeFactory factory)
        {
            BuildSealedVTableSlots(factory, relocsOnly: false);
            return NumSealedVTableEntries == 0;
        }

        /// <summary>
        /// Returns the slot of a method in the sealed vtable, or -1 if not found. This API should only be called after
        /// successfully building the sealed vtable slots.
        /// </summary>
        public int ComputeSealedVTableSlot(MethodDesc method)
        {
            if (_sealedVTableEntries == null)
                throw new NotSupportedException();

            for (int i = 0; i < _sealedVTableEntries.Count; i++)
            {
                if (_sealedVTableEntries[i].Matches(method))
                    return i;
            }

            return -1;
        }

        public int ComputeDefaultInterfaceMethodSlot(MethodDesc method, DefType interfaceOnDefinition)
        {
            if (_sealedVTableEntries == null)
                throw new NotSupportedException();

            for (int i = 0; i < _sealedVTableEntries.Count; i++)
            {
                if (_sealedVTableEntries[i].Matches(method, interfaceOnDefinition))
                    return i;
            }

            return -1;
        }

        public bool BuildSealedVTableSlots(NodeFactory factory, bool relocsOnly)
        {
            // Sealed vtable already built
            if (_sealedVTableEntries != null)
                return true;

            DefType declType = _type.GetClosestDefType();
            VTableSliceNode declTypeVTable = factory.VTable(declType);

            // It's only okay to touch the actual list of slots if we're in the final emission phase
            // or the vtable is not built lazily.
            if (relocsOnly && !declTypeVTable.HasKnownVirtualMethodUse)
                return false;

            _sealedVTableEntries = new List<SealedVTableEntry>();

            // Interfaces don't have any instance virtual slots with the exception of interfaces that provide
            // IDynamicInterfaceCastable implementation.
            // Normal interface don't need one because the dispatch is done at the class level.
            // For IDynamicInterfaceCastable, we don't have an implementing class.
            bool isInterface = declType.IsInterface;
            bool needsEntriesForInstanceInterfaceMethodImpls = !isInterface
                    || ((MetadataType)declType).IsDynamicInterfaceCastableImplementation();

            IReadOnlyList<MethodDesc> virtualSlots = declTypeVTable.Slots;

            for (int i = 0; i < virtualSlots.Count; i++)
            {
                if (!declTypeVTable.IsSlotUsed(virtualSlots[i]))
                    continue;

                if (!virtualSlots[i].Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
                    continue;

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

                if (implMethod.CanMethodBeInSealedVTable(factory))
                {
                    IMethodNode node;

                    if (factory.DelegateTargetVirtualMethod(virtualSlots[i].GetCanonMethodTarget(CanonicalFormKind.Specific)).Marked)
                        node = factory.AddressTakenMethodEntrypoint(implMethod.GetCanonMethodTarget(CanonicalFormKind.Specific), unboxingStub: !implMethod.Signature.IsStatic && declType.IsValueType);
                    else
                        node = factory.MethodEntrypoint(implMethod.GetCanonMethodTarget(CanonicalFormKind.Specific), unboxingStub: !implMethod.Signature.IsStatic && declType.IsValueType);

                    _sealedVTableEntries.Add(SealedVTableEntry.FromVirtualMethod(implMethod, node));
                }
            }

            TypeDesc declTypeDefinition = declType.GetTypeDefinition();

            DefType[] declTypeRuntimeInterfaces = declType.RuntimeInterfaces;
            DefType[] declTypeDefinitionRuntimeInterfaces = declTypeDefinition.RuntimeInterfaces;

            // Catch any runtime interface collapsing. We shouldn't have any
            Debug.Assert(declTypeRuntimeInterfaces.Length == declTypeDefinitionRuntimeInterfaces.Length);

            for (int interfaceIndex = 0; interfaceIndex < declTypeRuntimeInterfaces.Length; interfaceIndex++)
            {
                var interfaceType = declTypeRuntimeInterfaces[interfaceIndex];
                var definitionInterfaceType = declTypeDefinitionRuntimeInterfaces[interfaceIndex];

                VTableSliceNode interfaceVTable = factory.VTable(interfaceType);
                virtualSlots = interfaceVTable.Slots;

                for (int interfaceMethodSlot = 0; interfaceMethodSlot < virtualSlots.Count; interfaceMethodSlot++)
                {
                    MethodDesc declMethod = virtualSlots[interfaceMethodSlot];

                    if (!interfaceVTable.IsSlotUsed(declMethod))
                        continue;

                    if (!declMethod.Signature.IsStatic && !needsEntriesForInstanceInterfaceMethodImpls)
                        continue;

                    MethodDesc interfaceDefDeclMethod = declMethod;
                    if  (!interfaceType.IsTypeDefinition)
                        interfaceDefDeclMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(declMethod.GetTypicalMethodDefinition(), (InstantiatedType)definitionInterfaceType);

                    var implMethod = declMethod.Signature.IsStatic ?
                        declTypeDefinition.ResolveInterfaceMethodToStaticVirtualMethodOnType(interfaceDefDeclMethod) :
                        declTypeDefinition.ResolveInterfaceMethodToVirtualMethodOnType(interfaceDefDeclMethod);

                    // Interface methods first implemented by a base type in the hierarchy will return null for the implMethod (runtime interface
                    // dispatch will walk the inheritance chain).
                    if (implMethod != null)
                    {
                        if (implMethod.Signature.IsStatic || !implMethod.OwningType.HasSameTypeDefinition(declType))
                        {
                            TypeDesc implType = declType;
                            while (!implType.HasSameTypeDefinition(implMethod.OwningType))
                                implType = implType.BaseType;

                            MethodDesc targetMethod = implMethod;
                            if (!implType.IsTypeDefinition)
                                targetMethod = factory.TypeSystemContext.GetMethodForInstantiatedType(implMethod.GetTypicalMethodDefinition(), (InstantiatedType)implType);

                            if (targetMethod.CanMethodBeInSealedVTable(factory) || implMethod.Signature.IsStatic)
                            {
                                IMethodNode node;
                                if (factory.DelegateTargetVirtualMethod(declMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)).Marked)
                                    node = factory.AddressTakenMethodEntrypoint(targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific), unboxingStub: !targetMethod.Signature.IsStatic && declType.IsValueType);
                                else
                                    node = factory.MethodEntrypoint(targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific), unboxingStub: !targetMethod.Signature.IsStatic && declType.IsValueType);

                                _sealedVTableEntries.Add(SealedVTableEntry.FromVirtualMethod(targetMethod, node));
                            }
                        }
                    }
                    else
                    {
                        // If the interface method is provided by a default implementation, add the default implementation
                        // to the sealed vtable.
                        var resolution = declTypeDefinition.ResolveInterfaceMethodToDefaultImplementationOnType(interfaceDefDeclMethod, out implMethod);
                        if (resolution == DefaultInterfaceMethodResolution.DefaultImplementation)
                        {
                            DefType providingInterfaceDefinitionType = (DefType)implMethod.OwningType;
                            implMethod = implMethod.InstantiateSignature(declType.Instantiation, Instantiation.Empty);

                            MethodDesc canonImplMethod = implMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);
                            if (canonImplMethod.IsCanonicalMethod(CanonicalFormKind.Any) && !canonImplMethod.Signature.IsStatic)
                            {
                                // Canonical instance default interface methods need to go through a thunk that acquires the generic context from `this`.
                                // Static methods have their generic context passed explicitly.
                                canonImplMethod = factory.TypeSystemContext.GetDefaultInterfaceMethodImplementationThunk(canonImplMethod, declType.ConvertToCanonForm(CanonicalFormKind.Specific), providingInterfaceDefinitionType, out int providingInterfaceIndex);

                                // The above thunk will index into interface list to find the right context. Make sure to keep all interfaces prior to this one
                                for (int i = 0; i <= providingInterfaceIndex; i++)
                                {
                                    _nonRelocationDependencies ??= new DependencyList();
                                    _nonRelocationDependencies.Add(factory.InterfaceUse(declTypeRuntimeInterfaces[i].GetTypeDefinition()), "Interface with shared default methods folows this");
                                }
                            }

                            IMethodNode node;
                            if (factory.DelegateTargetVirtualMethod(declMethod.GetCanonMethodTarget(CanonicalFormKind.Specific)).Marked)
                                node = factory.AddressTakenMethodEntrypoint(canonImplMethod, unboxingStub: implMethod.OwningType.IsValueType && !implMethod.Signature.IsStatic);
                            else
                                node = factory.MethodEntrypoint(canonImplMethod, unboxingStub: implMethod.OwningType.IsValueType && !implMethod.Signature.IsStatic);

                            _sealedVTableEntries.Add(SealedVTableEntry.FromDefaultInterfaceMethod(implMethod, providingInterfaceDefinitionType, node));
                        }
                    }
                }
            }

            return true;
        }

        protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFactory factory)
        {
            BuildSealedVTableSlots(factory, relocsOnly: true);

            var result = new DependencyList(_nonRelocationDependencies ?? []);

            // When building the sealed vtable, we consult the vtable layout of these types
            TypeDesc declType = _type.GetClosestDefType();
            result.Add(factory.VTable(declType), "VTable of the type");

            foreach (var interfaceType in declType.RuntimeInterfaces)
                result.Add(factory.VTable(interfaceType), "VTable of the interface");

            return result;
        }

        public override ObjectData GetData(NodeFactory factory, bool relocsOnly)
        {
            ObjectDataBuilder objData = new ObjectDataBuilder(factory, relocsOnly);
            objData.RequireInitialAlignment(factory.Target.SupportsRelativePointers ? 4 : factory.Target.PointerSize);
            objData.AddSymbol(this);

            if (BuildSealedVTableSlots(factory, relocsOnly))
            {
                bool isSharedDynamicInterfaceCastableImpl = _type.IsInterface
                    && _type.IsCanonicalSubtype(CanonicalFormKind.Any)
                    && ((MetadataType)_type).IsDynamicInterfaceCastableImplementation();

                for (int i = 0; i < _sealedVTableEntries.Count; i++)
                {
                    IMethodNode relocTarget = _sealedVTableEntries[i].Target;

                    int delta = isSharedDynamicInterfaceCastableImpl ? DispatchMapCodePointerFlags.RequiresInstantiatingThunkFlag : 0;

                    if (factory.Target.SupportsRelativePointers)
                        objData.EmitReloc(relocTarget, RelocType.IMAGE_REL_BASED_RELPTR32, delta);
                    else
                        objData.EmitPointerReloc(relocTarget, delta);
                }
            }

            return objData.ToObjectData();
        }

        public override int ClassCode => 1632890252;
        public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
        {
            return comparer.Compare(_type, ((SealedVTableNode)other)._type);
        }

        private readonly struct SealedVTableEntry
        {
            private readonly MethodDesc _method;
            private readonly DefType _interfaceDefinition;
            public readonly IMethodNode Target;

            private SealedVTableEntry(MethodDesc method, DefType interfaceDefinition, IMethodNode target)
            {
                Debug.Assert(interfaceDefinition == null || method.GetTypicalMethodDefinition().OwningType == interfaceDefinition.GetTypeDefinition());
                (_method, _interfaceDefinition, Target) = (method, interfaceDefinition, target);
            }

            public static SealedVTableEntry FromVirtualMethod(MethodDesc method, IMethodNode target)
                => new SealedVTableEntry(method, null, target);

            public static SealedVTableEntry FromDefaultInterfaceMethod(MethodDesc method, DefType interfaceOnDefinition, IMethodNode target)
                => new SealedVTableEntry(method, interfaceOnDefinition, target);

            public bool Matches(MethodDesc method)
            {
                if (_method == method)
                {
                    // It is not valid to ask for slots of default implementations of interfaces on canonical version of the type.
                    //
                    // Consider:
                    //
                    // interface IFoo<T> { void Frob() => Console.WriteLine(typeof(T)) }
                    // class Base<T> : IFoo<T> { }
                    // class Derived<T, U> : Base<T>, IFoo<U> { }
                    //
                    // If we ask what's the slot of IFoo<__Canon>.Frob on Derived<__Canon, __Canon>, the answer is actually
                    // "two slots". We need extra data (the interface implementation on the definition of the type -
                    // e.g. "IFace<!0>") to disambiguate. Use the other overload.
                    Debug.Assert(_interfaceDefinition == null || !method.IsCanonicalMethod(CanonicalFormKind.Any));
                    return true;
                }

                return false;
            }

            public bool Matches(MethodDesc method, DefType interfaceDefinition)
            {
                Debug.Assert(method.GetTypicalMethodDefinition().OwningType == interfaceDefinition.GetTypeDefinition());
                Debug.Assert(interfaceDefinition.IsInterface);

                if (_method == method && _interfaceDefinition == interfaceDefinition)
                {
                    return true;
                }

                return false;
            }
        }
    }
}