File: Compiler\VirtualMethodCallHelper.cs
Web Access
Project: 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;
            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);
                // For types that have a generic dictionary, the introduced virtual method slots are
                // prefixed with a pointer to the generic dictionary.
                if (baseType.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;
            }

            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));
        }
    }
}