File: Compiler\DependencyAnalysis\VTableSliceNode.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 Internal.TypeSystem;
using ILCompiler.DependencyAnalysisFramework;

using Debug = System.Diagnostics.Debug;

namespace ILCompiler.DependencyAnalysis
{
    /// <summary>
    /// Represents the VTable for a type's slice. For example, System.String's VTableSliceNode includes virtual
    /// slots added by System.String itself, System.Object's VTableSliceNode contains the virtuals it defines.
    /// </summary>
    public abstract class VTableSliceNode : DependencyNodeCore<NodeFactory>
    {
        protected TypeDesc _type;

        public VTableSliceNode(TypeDesc type)
        {
            Debug.Assert(!type.IsArray, "Wanted to call GetClosestDefType?");
            Debug.Assert(!type.IsGenericDefinition);
            _type = type;
        }

        protected static MethodDesc[] ComputeSlots(TypeDesc type)
        {
            var slots = default(ArrayBuilder<MethodDesc>);

            bool isObjectType = type.IsObject;
            DefType defType = type.GetClosestDefType();

            IEnumerable<MethodDesc> allSlots = defType.EnumAllVirtualSlots();

            foreach (var method in allSlots)
            {
                // GVMs are not emitted in the type's vtable.
                if (method.HasInstantiation)
                    continue;

                // Finalizers are called via a field on the MethodTable, not through the VTable
                if (isObjectType && method.Name.SequenceEqual("Finalize"u8))
                    continue;

                // Current type doesn't define this slot.
                if (method.OwningType != defType)
                    continue;

                slots.Add(method);
            }

            return slots.ToArray();
        }

        public abstract IReadOnlyList<MethodDesc> Slots
        {
            get;
        }

        public abstract bool IsSlotUsed(MethodDesc slot);

        public TypeDesc Type => _type;

        /// <summary>
        /// Gets a value indicating whether <see cref="VirtualMethodUseNode"> is needed to track virtual method uses
        /// in this vtable slice.
        /// </summary>
        public abstract bool HasKnownVirtualMethodUse
        {
            get;
        }

        protected override string GetName(NodeFactory factory) => $"__vtable_{factory.NameMangler.GetMangledTypeName(_type)}";

        public override bool StaticDependenciesAreComputed => true;

        public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
        {
            if (_type.HasBaseType)
            {
                yield return new DependencyListEntry(factory.VTable(_type.BaseType), "Base type VTable");
            }

            TypeDesc canonType = _type.ConvertToCanonForm(CanonicalFormKind.Specific);
            if (_type != canonType)
            {
                yield return new DependencyListEntry(factory.VTable(canonType), "Canonical type VTable");
            }
        }

        public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory) => null;
        public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory factory) => null;

        public override bool InterestingForDynamicDependencyAnalysis => false;
        public override bool HasDynamicDependencies => false;
        public override bool HasConditionalStaticDependencies => false;
    }

    /// <summary>
    /// Represents a VTable slice with fixed slots whose assignment was determined at the time the slice was allocated.
    /// </summary>
    internal class PrecomputedVTableSliceNode : VTableSliceNode
    {
        private readonly MethodDesc[] _slots;

        public PrecomputedVTableSliceNode(TypeDesc type, MethodDesc[] slots)
            : base(type)
        {
            _slots = slots;
        }

        public override IReadOnlyList<MethodDesc> Slots
        {
            get
            {
                return _slots;
            }
        }

        public override bool IsSlotUsed(MethodDesc slot)
        {
            Debug.Assert(Array.IndexOf(_slots, slot) >= 0);
            return true;
        }

        public override bool HasKnownVirtualMethodUse
        {
            get
            {
                return true;
            }
        }
    }

    /// <summary>
    /// Represents a VTable slice for a complete type - a type with all virtual method slots generated,
    /// irrespective of whether they are used.
    /// </summary>
    internal sealed class EagerlyBuiltVTableSliceNode : PrecomputedVTableSliceNode
    {
        public EagerlyBuiltVTableSliceNode(TypeDesc type)
            : base(type, ComputeSlots(type))
        {
        }
    }

    /// <summary>
    /// Represents a VTable slice where slots are built on demand. Only the slots that are actually used
    /// will be generated.
    /// </summary>
    internal sealed class LazilyBuiltVTableSliceNode : VTableSliceNode
    {
        private readonly HashSet<MethodDesc> _usedMethods = new HashSet<MethodDesc>();
        private readonly MethodDesc[] _slots;
#if DEBUG
        private bool _isLocked;
#endif

        public LazilyBuiltVTableSliceNode(TypeDesc type, MethodDesc[] slots = null)
            : base(type)
        {
            _slots = slots ?? ComputeSlots(type);
        }

        public override IReadOnlyList<MethodDesc> Slots
        {
            get
            {
                return _slots;
            }
        }

        public override bool IsSlotUsed(MethodDesc slot)
        {
            Debug.Assert(Array.IndexOf(_slots, slot) >= 0);
#if DEBUG
            _isLocked = true;
#endif

            return _usedMethods.Contains(slot);
        }

        public override bool HasKnownVirtualMethodUse
        {
            get
            {
                return false;
            }
        }

        public void AddEntry(MethodDesc virtualMethod)
        {
            // GVMs are not emitted in the type's vtable.
            Debug.Assert(!virtualMethod.HasInstantiation);
            Debug.Assert(virtualMethod.IsVirtual);
            Debug.Assert(virtualMethod.OwningType == _type);
            Debug.Assert(Array.IndexOf(_slots, virtualMethod) >= 0);
#if DEBUG
            Debug.Assert(!_isLocked);
#endif

            // Finalizers are called via a field on the MethodTable, not through the VTable
            if (_type.IsObject && virtualMethod.Name.SequenceEqual("Finalize"u8))
                return;

            _usedMethods.Add(virtualMethod);
        }

        public override bool HasConditionalStaticDependencies
        {
            get
            {
                return _type.ConvertToCanonForm(CanonicalFormKind.Specific) != _type;
            }
        }

        public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory)
        {
            // VirtualMethodUse of Foo<SomeType>.Method will bring in VirtualMethodUse
            // of Foo<__Canon>.Method. This in turn should bring in Foo<OtherType>.Method.
            DefType defType = _type.GetClosestDefType();

            IEnumerable<MethodDesc> allSlots = defType.EnumAllVirtualSlots();

            foreach (var method in allSlots)
            {
                // Generic virtual methods are tracked by an orthogonal mechanism.
                if (method.HasInstantiation)
                    continue;

                // Current type doesn't define this slot. Another VTableSlice will take care of this.
                if (method.OwningType != defType)
                    continue;

                if (defType.Context.SupportsCanon)
                    yield return new CombinedDependencyListEntry(
                        factory.VirtualMethodUse(method),
                        factory.VirtualMethodUse(method.GetCanonMethodTarget(CanonicalFormKind.Specific)),
                        "Canonically equivalent virtual method use");
            }
        }
    }
}