File: Compiler\VirtualMethodCallHelper.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.Collections.Generic;
using System.Diagnostics;
using Internal.TypeSystem;
using ILCompiler.DependencyAnalysis;

namespace ILCompiler
{
    public static class VirtualMethodSlotHelper
    {
        public static int GetDefaultInterfaceMethodSlot(NodeFactory factory, MethodDesc method, TypeDesc implType, DefType interfaceOnDefinition, bool countDictionarySlots = true)
        {
            Debug.Assert(method.GetTypicalMethodDefinition().OwningType == interfaceOnDefinition.GetTypeDefinition());

            SealedVTableNode sealedVTable = factory.SealedVTable(implType);

            // Ensure the sealed vtable is built before computing the slot
            sealedVTable.BuildSealedVTableSlots(factory, relocsOnly: false /* GetVirtualMethodSlot is called in the final emission phase */);

            int sealedVTableSlot = sealedVTable.ComputeDefaultInterfaceMethodSlot(method, interfaceOnDefinition);
            if (sealedVTableSlot == -1)
                return -1;

            int numVTableSlots = GetNumberOfSlotsInCurrentType(factory, implType, countDictionarySlots);

            return numVTableSlots + sealedVTableSlot;
        }

        /// <summary>
        /// Given a virtual method decl, return its VTable slot if the method is used on its containing type.
        /// Return -1 if the virtual method is not used.
        /// </summary>
        public static int GetVirtualMethodSlot(NodeFactory factory, MethodDesc method, TypeDesc implType, bool countDictionarySlots = true)
        {
            if (method.CanMethodBeInSealedVTable(factory))
            {
                // If the method is a sealed newslot method, it will be put in the sealed vtable instead of the type's vtable. In this
                // case, the slot index return should be the index in the sealed vtable, plus the total number of vtable slots.

                // First, make sure we are not attempting to resolve the slot of a sealed vtable method on a special array type, which
                // does not get any sealed vtable entries
                Debug.Assert(!implType.IsArrayTypeWithoutGenericInterfaces());

                SealedVTableNode sealedVTable = factory.SealedVTable(implType);

                // Ensure the sealed vtable is built before computing the slot
                sealedVTable.BuildSealedVTableSlots(factory, relocsOnly: false /* GetVirtualMethodSlot is called in the final emission phase */);

                int sealedVTableSlot = sealedVTable.ComputeSealedVTableSlot(method);
                if (sealedVTableSlot == -1)
                    return -1;

                int numVTableSlots = GetNumberOfSlotsInCurrentType(factory, implType, countDictionarySlots);

                return numVTableSlots + sealedVTableSlot;
            }
            else
            {
                // TODO: More efficient lookup of the slot
                TypeDesc owningType = method.OwningType;
                int baseSlots = GetNumberOfBaseSlots(factory, owningType, countDictionarySlots);

                // For types that have a generic dictionary, the introduced virtual method slots are
                // prefixed with a pointer to the generic dictionary.
                if (owningType.HasGenericDictionarySlot() && countDictionarySlots)
                    baseSlots++;

                IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(owningType).Slots;
                int methodSlot = -1;
                int numSealedVTableEntries = 0;
                for (int slot = 0; slot < virtualSlots.Count; slot++)
                {
                    if (virtualSlots[slot].CanMethodBeInSealedVTable(factory))
                    {
                        numSealedVTableEntries++;
                        continue;
                    }

                    if (virtualSlots[slot] == method)
                    {
                        methodSlot = slot;
                        break;
                    }
                }

                return methodSlot == -1 ? -1 : baseSlots + methodSlot - numSealedVTableEntries;
            }
        }

        private static int GetNumberOfSlotsInCurrentType(NodeFactory factory, TypeDesc implType, bool countDictionarySlots)
        {
            if (implType.IsInterface)
            {
                // Interface types don't have physically assigned virtual slots, so the number of slots
                // is always 0. They may have sealed slots.
                return (implType.HasGenericDictionarySlot() && countDictionarySlots) ? 1 : 0;
            }

            // Now compute the total number of vtable slots that would exist on the type
            int baseSlots = GetNumberOfBaseSlots(factory, implType, countDictionarySlots);

            // For types that have a generic dictionary, the introduced virtual method slots are
            // prefixed with a pointer to the generic dictionary.
            if (implType.HasGenericDictionarySlot() && countDictionarySlots)
                baseSlots++;

            int numVTableSlots = baseSlots;
            IReadOnlyList<MethodDesc> virtualSlots = factory.VTable(implType).Slots;
            for (int slot = 0; slot < virtualSlots.Count; slot++)
            {
                if (virtualSlots[slot].CanMethodBeInSealedVTable(factory))
                    continue;
                numVTableSlots++;
            }

            return numVTableSlots;
        }

        private static int GetNumberOfBaseSlots(NodeFactory factory, TypeDesc owningType, bool countDictionarySlots)
        {
            int baseSlots = 0;

            TypeDesc baseType = owningType.BaseType;
            TypeDesc templateBaseType = owningType.ConvertToCanonForm(CanonicalFormKind.Specific).BaseType;

            while (baseType != null)
            {
                // Normalize the base type. Necessary to make this work with the lazy vtable slot
                // concept - if we start with a canonical type, the base type could end up being
                // something like Base<__Canon, string>. We would get "0 slots used" for weird
                // base types like this.
                baseType = baseType.ConvertToCanonForm(CanonicalFormKind.Specific);
                templateBaseType = templateBaseType.ConvertToCanonForm(CanonicalFormKind.Specific);

                //
                // In the universal canonical types case, we could have base types in the hierarchy that are partial universal canonical types.
                // The presence of these types could cause incorrect vtable layouts, so we need to fully canonicalize them and walk the
                // hierarchy of the template type of the original input type to detect these cases.
                //
                // Exmaple: we begin with Derived<__UniversalCanon> and walk the template hierarchy:
                //
                //    class Derived<T> : Middle<T, MyStruct> { }    // -> Template is Derived<__UniversalCanon> and needs a dictionary slot
                //                                                  // -> Basetype tempalte is Middle<__UniversalCanon, MyStruct>. It's a partial
                //                                                        Universal canonical type, so we need to fully canonicalize it.
                //
                //    class Middle<T, U> : Base<U> { }              // -> Template is Middle<__UniversalCanon, __UniversalCanon> and needs a dictionary slot
                //                                                  // -> Basetype template is Base<__UniversalCanon>
                //
                //    class Base<T> { }                             // -> Template is Base<__UniversalCanon> and needs a dictionary slot.
                //
                // If we had not fully canonicalized the Middle class template, we would have ended up with Base<MyStruct>, which does not need
                // a dictionary slot, meaning we would have created a vtable layout that the runtime does not expect.
                //

                // For types that have a generic dictionary, the introduced virtual method slots are
                // prefixed with a pointer to the generic dictionary.
                if ((baseType.HasGenericDictionarySlot() || templateBaseType.HasGenericDictionarySlot()) && countDictionarySlots)
                    baseSlots++;

                IReadOnlyList<MethodDesc> baseVirtualSlots = factory.VTable(baseType).Slots;
                foreach (var vtableMethod in baseVirtualSlots)
                {
                    // Methods in the sealed vtable should be excluded from the count
                    if (vtableMethod.CanMethodBeInSealedVTable(factory))
                        continue;
                    baseSlots++;
                }

                baseType = baseType.BaseType;
                templateBaseType = templateBaseType.BaseType;
            }

            return baseSlots;
        }

        /// <summary>
        /// Gets the vtable slot that holds the generic dictionary of this type.
        /// </summary>
        public static int GetGenericDictionarySlot(NodeFactory factory, TypeDesc type)
        {
            Debug.Assert(type.HasGenericDictionarySlot());
            return GetNumberOfBaseSlots(factory, type, countDictionarySlots: true);
        }

        /// <summary>
        /// Gets a value indicating whether the virtual method slots introduced by this type are prefixed
        /// by a pointer to the generic dictionary of the type.
        /// </summary>
        public static bool HasGenericDictionarySlot(this TypeDesc type)
        {
            // Dictionary slots on generic interfaces are necessary to support static methods on interfaces
            // The reason behind making this unconditional is simplicity, and keeping method slot indices for methods on IFoo<int>
            // and IFoo<string> identical. That won't change.
            if (type.IsInterface)
                return type.HasInstantiation;

            return type.HasInstantiation &&
                (type.ConvertToCanonForm(CanonicalFormKind.Specific) != type || type.IsCanonicalSubtype(CanonicalFormKind.Any));
        }
    }
}