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

using Internal.IL;
using Internal.Text;
using Internal.TypeSystem;

using ILCompiler.DependencyAnalysisFramework;

namespace ILCompiler.DependencyAnalysis
{
    public abstract partial class ReadyToRunGenericHelperNode : AssemblyStubNode, INodeWithRuntimeDeterminedDependencies
    {
        private readonly ReadyToRunHelperId _id;
        private readonly object _target;
        protected readonly TypeSystemEntity _dictionaryOwner;
        protected readonly GenericLookupResult _lookupSignature;

        // True if any of slots in dictionaries associated with this layout could not be filled
        // at compile time due to a TypeSystemException. Only query through HandlesInvalidEntries
        // below so that we can assert this is not queried at an inappropriate time before
        // the whole program view has been established.
        private bool _hasInvalidEntries;

        public ReadyToRunHelperId Id => _id;
        public object Target => _target;
        public TypeSystemEntity DictionaryOwner => _dictionaryOwner;
        public GenericLookupResult LookupSignature => _lookupSignature;

        public bool HandlesInvalidEntries(NodeFactory factory)
        {
            Debug.Assert(factory.MarkingComplete);
            return _hasInvalidEntries;
        }

        public ReadyToRunGenericHelperNode(NodeFactory factory, ReadyToRunHelperId helperId, object target, TypeSystemEntity dictionaryOwner)
        {
            Debug.Assert(
                (dictionaryOwner is TypeDesc type && type.HasInstantiation)
                || (dictionaryOwner is MethodDesc method && method.HasInstantiation));
            _id = helperId;
            _dictionaryOwner = dictionaryOwner;
            _target = target;

            _lookupSignature = GetLookupSignature(factory, helperId, target);
        }

        public static GenericLookupResult GetLookupSignature(NodeFactory factory, ReadyToRunHelperId id, object target)
        {
            switch (id)
            {
                case ReadyToRunHelperId.TypeHandle:
                    return factory.GenericLookup.Type((TypeDesc)target);
                case ReadyToRunHelperId.NecessaryTypeHandle:
                    return factory.GenericLookup.NecessaryType((TypeDesc)target);
                case ReadyToRunHelperId.MetadataTypeHandle:
                    return factory.GenericLookup.MetadataType((TypeDesc)target);
                case ReadyToRunHelperId.TypeHandleForCasting:
                    // Check that we unwrapped the cases that could be unwrapped to prevent duplicate entries
                    Debug.Assert(factory.GenericLookup.Type((TypeDesc)target) != factory.GenericLookup.UnwrapNullableType((TypeDesc)target));
                    return factory.GenericLookup.UnwrapNullableType((TypeDesc)target);
                case ReadyToRunHelperId.MethodHandle:
                    return factory.GenericLookup.MethodHandle((MethodDesc)target);
                case ReadyToRunHelperId.FieldHandle:
                    return factory.GenericLookup.FieldHandle((FieldDesc)target);
                case ReadyToRunHelperId.GetGCStaticBase:
                    return factory.GenericLookup.TypeGCStaticBase((TypeDesc)target);
                case ReadyToRunHelperId.GetNonGCStaticBase:
                    return factory.GenericLookup.TypeNonGCStaticBase((TypeDesc)target);
                case ReadyToRunHelperId.GetThreadStaticBase:
                    return factory.GenericLookup.TypeThreadStaticBaseIndex((TypeDesc)target);
                case ReadyToRunHelperId.MethodDictionary:
                    return factory.GenericLookup.MethodDictionary((MethodDesc)target);
                case ReadyToRunHelperId.DispatchCell:
                    return factory.GenericLookup.DispatchCell((MethodDesc)target);
                case ReadyToRunHelperId.MethodEntry:
                    return factory.GenericLookup.MethodEntry((MethodDesc)target);
                case ReadyToRunHelperId.DelegateCtor:
                    return ((DelegateCreationInfo)target).GetLookupKind(factory);
                case ReadyToRunHelperId.DefaultConstructor:
                    return factory.GenericLookup.DefaultCtorLookupResult((TypeDesc)target);
                case ReadyToRunHelperId.ObjectAllocator:
                    return factory.GenericLookup.ObjectAllocator((TypeDesc)target);
                case ReadyToRunHelperId.ConstrainedDirectCall:
                    return factory.GenericLookup.ConstrainedMethodUse(
                        ((ConstrainedCallInfo)target).Method,
                        ((ConstrainedCallInfo)target).ConstrainedType,
                        directCall: !((ConstrainedCallInfo)target).Method.HasInstantiation);
                default:
                    throw new NotImplementedException();
            }
        }

        protected override bool IsVisibleFromManagedCode => false;

        protected sealed override string GetName(NodeFactory factory) => this.GetMangledName(factory.NameMangler);
        public override bool IsShareable => true;

        protected sealed override void OnMarked(NodeFactory factory)
        {
            DictionaryLayoutNode layout = factory.GenericDictionaryLayout(_dictionaryOwner);

            if (layout.HasUnfixedSlots)
            {
                // When the helper call gets marked, ensure the generic layout for the associated dictionaries
                // includes the signature.
                layout.EnsureEntry(_lookupSignature);

                if ((_id == ReadyToRunHelperId.GetGCStaticBase || _id == ReadyToRunHelperId.GetThreadStaticBase) &&
                    TriggersLazyStaticConstructor(factory))
                {
                    // If the type has a lazy static constructor, we also need the non-GC static base
                    // because that's where the class constructor context is.
                    layout.EnsureEntry(factory.GenericLookup.TypeNonGCStaticBase((TypeDesc)_target));
                }
            }
        }

        private bool TriggersLazyStaticConstructor(NodeFactory factory)
        {
            TypeDesc type = (TypeDesc)_target;
            return factory.PreinitializationManager.HasLazyStaticConstructor(type.ConvertToCanonForm(CanonicalFormKind.Specific));
        }

#nullable enable
        public void AddDependencies(
            DependencySink sink,
            NodeFactory factory,
            Instantiation typeInstantiation,
            Instantiation methodInstantiation,
            bool isConcreteInstantiation,
            DependencyNodeCore<NodeFactory>? otherReasonNode)
        {
            var lookupContext = new GenericLookupResultContext(_dictionaryOwner, typeInstantiation, methodInstantiation);

            switch (_id)
            {
                case ReadyToRunHelperId.GetGCStaticBase:
                case ReadyToRunHelperId.GetThreadStaticBase:
                    {
                        // If the type has a lazy static constructor, we also need the non-GC static base
                        // because that's where the class constructor context is.
                        if (TriggersLazyStaticConstructor(factory))
                        {
                            var lookupTarget = factory.GenericLookup.TypeNonGCStaticBase((TypeDesc)_target).GetTarget(factory, lookupContext, isConcreteInstantiation);
                            if (lookupTarget != null)
                            {
                                AddDependency(sink, lookupTarget, "Dictionary dependency", otherReasonNode);
                            }
                        }
                    }
                    break;

                case ReadyToRunHelperId.DelegateCtor:
                    {
                        DelegateCreationInfo createInfo = (DelegateCreationInfo)_target;
                        if (createInfo.NeedsVirtualMethodUseTracking)
                        {
                            MethodDesc instantiatedTargetMethod = createInfo.TargetMethod.GetNonRuntimeDeterminedMethodFromRuntimeDeterminedMethodViaSubstitution(typeInstantiation, methodInstantiation);
                            if (!factory.VTable(instantiatedTargetMethod.OwningType).HasKnownVirtualMethodUse)
                            {
                                AddDependency(
                                    sink,
                                    factory.VirtualMethodUse(instantiatedTargetMethod),
                                    "Dictionary dependency",
                                    otherReasonNode);
                            }

                            factory.MetadataManager.AddDependenciesDueToVirtualMethodReflectability(sink, factory, instantiatedTargetMethod, otherReasonNode);
                        }
                    }
                    break;
            }

            try
            {
                var lookupTarget = _lookupSignature.GetTarget(factory, lookupContext, isConcreteInstantiation);
                if (lookupTarget != null)
                {
                    // All generic lookups depend on the thing they point to
                    AddDependency(sink, lookupTarget, "Dictionary dependency", otherReasonNode);
                }
            }
            catch (TypeSystemException)
            {
                // If there was an exception, we're going to generate a null slot in the associated
                // dictionary. The helper needs to be able to handle a null slot and tailcall
                // and exception throwing helper instead of returning a result.
                _hasInvalidEntries = true;
                AddDependency(sink, GetBadSlotHelper(factory), "Failure to build dictionary slot", otherReasonNode);
            }
        }

        private static void AddDependency(
            DependencySink sink,
            object dependency,
            string reason,
            DependencyNodeCore<NodeFactory>? otherReasonNode)
        {
            if (otherReasonNode is null)
            {
                sink.Add(dependency, reason);
            }
            else
            {
                sink.AddConditional(dependency, otherReasonNode, reason);
            }
        }
#nullable restore

        private static IMethodNode GetBadSlotHelper(NodeFactory factory)
        {
            return factory.MethodEntrypoint(factory.TypeSystemContext.GetHelperEntryPoint("ThrowHelpers"u8, "ThrowUnavailableType"u8));
        }

        protected void AppendLookupSignatureMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
        {
            if (_id != ReadyToRunHelperId.DelegateCtor)
            {
                _lookupSignature.AppendMangledName(nameMangler, sb);
            }
            else
            {
                ((DelegateCreationInfo)_target).AppendMangledName(nameMangler, sb);
            }
        }

        protected override void ComputeNonRelocationBasedDependencies(DependencySink sink, NodeFactory factory)
        {
            if (_dictionaryOwner is TypeDesc type)
            {
                // The generic lookup will need to consult the vtable of the owning type to find the
                // vtable slot where the generic dictionary is placed - report the dependency.
                sink.Add(factory.VTable(type), "Owning type vtable");
            }

            sink.Add(factory.GenericDictionaryLayout(_dictionaryOwner), "Layout");

            foreach (DependencyNodeCore<NodeFactory> dependency in _lookupSignature.NonRelocDependenciesFromUsage(factory))
            {
                sink.Add(new DependencyListEntry(dependency, "GenericLookupResultDependency"));
            }

            if (_id == ReadyToRunHelperId.DelegateCtor)
            {
                var delegateCreationInfo = (DelegateCreationInfo)_target;
                MethodDesc targetMethod = delegateCreationInfo.PossiblyUnresolvedTargetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);
                factory.MetadataManager.GetDependenciesDueToDelegateCreation(sink, factory, delegateCreationInfo.DelegateType, targetMethod);
            }
        }

        public override bool HasConditionalStaticDependencies => true;
        public override void AddConditionalDependencies(DependencySink sink, NodeFactory factory)
        {
            NativeLayoutSavedVertexNode templateLayout;
            if (_dictionaryOwner is MethodDesc)
            {
                templateLayout = factory.NativeLayout.TemplateMethodLayout((MethodDesc)_dictionaryOwner);
                sink.Add(new CombinedDependencyListEntry(_lookupSignature.TemplateDictionaryNode(factory),
                                                                templateLayout,
                                                                "Type loader template"));
            }
            else
            {
                templateLayout = factory.NativeLayout.TemplateTypeLayout((TypeDesc)_dictionaryOwner);
                sink.Add(new CombinedDependencyListEntry(_lookupSignature.TemplateDictionaryNode(factory),
                                                                templateLayout,
                                                                "Type loader template"));
            }

            if (_id == ReadyToRunHelperId.GetGCStaticBase || _id == ReadyToRunHelperId.GetThreadStaticBase)
            {
                // If the type has a lazy static constructor, we also need the non-GC static base to be available as
                // a template dictionary node.
                TypeDesc type = (TypeDesc)_target;
                Debug.Assert(templateLayout != null);
                if (TriggersLazyStaticConstructor(factory))
                {
                    GenericLookupResult nonGcRegionLookup = factory.GenericLookup.TypeNonGCStaticBase(type);
                    sink.Add(new CombinedDependencyListEntry(nonGcRegionLookup.TemplateDictionaryNode(factory),
                                                                templateLayout,
                                                                "Type loader template"));
                }
            }

            if (_id == ReadyToRunHelperId.DelegateCtor)
            {
                var delegateCreationInfo = (DelegateCreationInfo)_target;
                MethodDesc targetMethod = delegateCreationInfo.PossiblyUnresolvedTargetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);
                factory.MetadataManager.GetConditionalDependenciesDueToDelegateCreation(sink, factory, delegateCreationInfo.DelegateType, targetMethod);
            }

        }

        public override int CompareToImpl(ISortableNode other, CompilerComparer comparer)
        {
            var compare = _id.CompareTo(((ReadyToRunGenericHelperNode)other)._id);
            if (compare != 0)
                return compare;

            if (_dictionaryOwner is MethodDesc)
            {
                if (((ReadyToRunGenericHelperNode)other)._dictionaryOwner is TypeDesc)
                    return -1;

                compare = comparer.Compare((MethodDesc)_dictionaryOwner, (MethodDesc)((ReadyToRunGenericHelperNode)other)._dictionaryOwner);
            }
            else
            {
                if (((ReadyToRunGenericHelperNode)other)._dictionaryOwner is MethodDesc)
                    return 1;

                compare = comparer.Compare((TypeDesc)_dictionaryOwner, (TypeDesc)((ReadyToRunGenericHelperNode)other)._dictionaryOwner);
            }

            if (compare != 0)
                return compare;

            switch (_id)
            {
                case ReadyToRunHelperId.TypeHandle:
                case ReadyToRunHelperId.NecessaryTypeHandle:
                case ReadyToRunHelperId.GetGCStaticBase:
                case ReadyToRunHelperId.GetNonGCStaticBase:
                case ReadyToRunHelperId.GetThreadStaticBase:
                case ReadyToRunHelperId.DefaultConstructor:
                case ReadyToRunHelperId.ObjectAllocator:
                    return comparer.Compare((TypeDesc)_target, (TypeDesc)((ReadyToRunGenericHelperNode)other)._target);
                case ReadyToRunHelperId.MethodHandle:
                case ReadyToRunHelperId.MethodDictionary:
                case ReadyToRunHelperId.DispatchCell:
                case ReadyToRunHelperId.MethodEntry:
                    return comparer.Compare((MethodDesc)_target, (MethodDesc)((ReadyToRunGenericHelperNode)other)._target);
                case ReadyToRunHelperId.FieldHandle:
                    return comparer.Compare((FieldDesc)_target, (FieldDesc)((ReadyToRunGenericHelperNode)other)._target);
                case ReadyToRunHelperId.DelegateCtor:
                    return ((DelegateCreationInfo)_target).CompareTo((DelegateCreationInfo)((ReadyToRunGenericHelperNode)other)._target, comparer);
                case ReadyToRunHelperId.ConstrainedDirectCall:
                    return ((ConstrainedCallInfo)_target).CompareTo((ConstrainedCallInfo)((ReadyToRunGenericHelperNode)other)._target, comparer);
                default:
                    throw new NotImplementedException();
            }
        }
    }

    public partial class ReadyToRunGenericLookupFromDictionaryNode : ReadyToRunGenericHelperNode
    {
        public ReadyToRunGenericLookupFromDictionaryNode(NodeFactory factory, ReadyToRunHelperId helperId, object target, TypeSystemEntity dictionaryOwner)
            : base(factory, helperId, target, dictionaryOwner)
        {
        }

        public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
        {
            Utf8String mangledContextName;
            if (_dictionaryOwner is MethodDesc)
                mangledContextName = nameMangler.GetMangledMethodName((MethodDesc)_dictionaryOwner);
            else
                mangledContextName = nameMangler.GetMangledTypeName((TypeDesc)_dictionaryOwner);

            sb.Append("__GenericLookupFromDict_"u8).Append(mangledContextName).Append("_"u8);
            AppendLookupSignatureMangledName(nameMangler, sb);
        }

        public override int ClassCode => 1055354299;
    }

    public partial class ReadyToRunGenericLookupFromTypeNode : ReadyToRunGenericHelperNode
    {
        public ReadyToRunGenericLookupFromTypeNode(NodeFactory factory, ReadyToRunHelperId helperId, object target, TypeSystemEntity dictionaryOwner)
            : base(factory, helperId, target, dictionaryOwner)
        {
        }

        public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
        {
            Utf8String mangledContextName;
            if (_dictionaryOwner is MethodDesc)
                mangledContextName = nameMangler.GetMangledMethodName((MethodDesc)_dictionaryOwner);
            else
                mangledContextName = nameMangler.GetMangledTypeName((TypeDesc)_dictionaryOwner);

            sb.Append("__GenericLookupFromType_"u8).Append(mangledContextName).Append("_"u8);
            AppendLookupSignatureMangledName(nameMangler, sb);
        }

        public override int ClassCode => 913214059;
    }
}