File: Compiler\DelegateCreationInfo.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 Internal.IL;
using Internal.TypeSystem;
using Internal.Text;
using ILCompiler.DependencyAnalysis;

using Debug = System.Diagnostics.Debug;

namespace ILCompiler
{
    /// <summary>
    /// Captures information required to generate a ReadyToRun helper to create a delegate type instance
    /// pointing to a specific target method.
    /// </summary>
    public sealed class DelegateCreationInfo
    {
        private enum TargetKind
        {
            CanonicalEntrypoint,
            ExactCallableAddress,
            InterfaceDispatch,
            VTableLookup,
            MethodHandle,
            ConstrainedMethod,
        }

        private TargetKind _targetKind;

        private TypeDesc _constrainedType;
        private MethodDesc _targetMethod;

        /// <summary>
        /// Gets the node corresponding to the method that initializes the delegate.
        /// </summary>
        public IMethodNode Constructor
        {
            get;
        }

        public MethodDesc TargetMethod
        {
            get
            {
                Debug.Assert(_constrainedType == null);
                return _targetMethod;
            }
        }

        // The target method might be constrained if this was a "constrained ldftn" IL instruction.
        // The real target can be computed after resolving the constraint.
        public MethodDesc PossiblyUnresolvedTargetMethod
        {
            get
            {
                return _targetMethod;
            }
        }

        private bool TargetMethodIsUnboxingThunk
        {
            get
            {
                return TargetMethod.OwningType.IsValueType && !TargetMethod.Signature.IsStatic;
            }
        }

        public bool TargetNeedsVTableLookup => _targetKind == TargetKind.VTableLookup;

        public bool NeedsVirtualMethodUseTracking
        {
            get
            {
                return _targetKind == TargetKind.VTableLookup || _targetKind == TargetKind.InterfaceDispatch;
            }
        }

        public bool NeedsRuntimeLookup
        {
            get
            {
                switch (_targetKind)
                {
                    case TargetKind.VTableLookup:
                        return false;

                    case TargetKind.CanonicalEntrypoint:
                    case TargetKind.ExactCallableAddress:
                    case TargetKind.InterfaceDispatch:
                    case TargetKind.MethodHandle:
                        return TargetMethod.IsRuntimeDeterminedExactMethod;

                    case TargetKind.ConstrainedMethod:
                        Debug.Assert(_targetMethod.IsRuntimeDeterminedExactMethod || _constrainedType.IsRuntimeDeterminedSubtype);
                        return true;

                    default:
                        Debug.Assert(false);
                        return false;
                }
            }
        }

        // None of the data structures that support shared generics have been ported to the JIT
        // codebase which makes this a huge PITA. Not including the method for JIT since nobody
        // uses it in that mode anyway.
#if !SUPPORT_JIT
        public GenericLookupResult GetLookupKind(NodeFactory factory)
        {
            Debug.Assert(NeedsRuntimeLookup);
            switch (_targetKind)
            {
                case TargetKind.ExactCallableAddress:
                    return factory.GenericLookup.MethodEntry(TargetMethod, TargetMethodIsUnboxingThunk);

                case TargetKind.InterfaceDispatch:
                    return factory.GenericLookup.VirtualDispatchCell(TargetMethod);

                case TargetKind.MethodHandle:
                    return factory.GenericLookup.MethodHandle(TargetMethod);

                case TargetKind.ConstrainedMethod:
                    return factory.GenericLookup.ConstrainedMethodUse(_targetMethod, _constrainedType, directCall: !_targetMethod.HasInstantiation);

                default:
                    Debug.Assert(false);
                    return null;
            }
        }
#endif

        /// <summary>
        /// Gets the node representing the target method of the delegate if no runtime lookup is needed.
        /// </summary>
        public ISymbolNode GetTargetNode(NodeFactory factory)
        {
            Debug.Assert(!NeedsRuntimeLookup);
            switch (_targetKind)
            {
                case TargetKind.CanonicalEntrypoint:
                    return factory.AddressTakenMethodEntrypoint(TargetMethod, TargetMethodIsUnboxingThunk);

                case TargetKind.ExactCallableAddress:
                    return factory.ExactCallableAddressTakenAddress(TargetMethod, TargetMethodIsUnboxingThunk);

                case TargetKind.InterfaceDispatch:
                    return factory.InterfaceDispatchCell(TargetMethod);

                case TargetKind.MethodHandle:
                    return factory.RuntimeMethodHandle(TargetMethod);

                case TargetKind.VTableLookup:
                    Debug.Fail("Need to do runtime lookup");
                    return null;

                default:
                    Debug.Assert(false);
                    return null;
            }
        }

        /// <summary>
        /// Gets an optional node passed as an additional argument to the constructor.
        /// </summary>
        public IMethodNode Thunk
        {
            get;
        }

        public TypeDesc DelegateType
        {
            get;
        }

        private DelegateCreationInfo(TypeDesc delegateType, IMethodNode constructor, MethodDesc targetMethod, TypeDesc constrainedType, TargetKind targetKind, IMethodNode thunk = null)
        {
            Debug.Assert(targetKind != TargetKind.VTableLookup
                || MetadataVirtualMethodAlgorithm.FindSlotDefiningMethodForVirtualMethod(targetMethod) == targetMethod);
            DelegateType = delegateType;
            Constructor = constructor;
            _targetMethod = targetMethod;
            _constrainedType = constrainedType;
            _targetKind = targetKind;
            Thunk = thunk;
        }

        /// <summary>
        /// Constructs a new instance of <see cref="DelegateCreationInfo"/> set up to construct a delegate of type
        /// '<paramref name="delegateType"/>' pointing to '<paramref name="targetMethod"/>'.
        /// </summary>
        public static DelegateCreationInfo Create(TypeDesc delegateType, MethodDesc targetMethod, TypeDesc constrainedType, NodeFactory factory, bool followVirtualDispatch)
        {
            CompilerTypeSystemContext context = factory.TypeSystemContext;
            DefType systemDelegate = context.GetWellKnownType(WellKnownType.MulticastDelegate).BaseType;

            int paramCountTargetMethod = targetMethod.Signature.Length;
            if (!targetMethod.Signature.IsStatic)
            {
                paramCountTargetMethod++;
            }

            DelegateInfo delegateInfo = context.GetDelegateInfo(delegateType.GetTypeDefinition());
            int paramCountDelegateClosed = delegateInfo.Signature.Length + 1;
            bool closed = false;
            if (paramCountDelegateClosed == paramCountTargetMethod)
            {
                closed = true;
            }
            else
            {
                Debug.Assert(paramCountDelegateClosed == paramCountTargetMethod + 1);
            }

            if (targetMethod.Signature.IsStatic)
            {
                MethodDesc invokeThunk;
                MethodDesc initMethod;

                if (!closed)
                {
                    initMethod = systemDelegate.GetKnownMethod("InitializeOpenStaticThunk"u8, null);
                    invokeThunk = delegateInfo.Thunks[DelegateThunkKind.OpenStaticThunk];
                }
                else
                {
                    // Closed delegate to a static method (i.e. delegate to an extension method that locks the first parameter)
                    invokeThunk = delegateInfo.Thunks[DelegateThunkKind.ClosedStaticThunk];
                    initMethod = systemDelegate.GetKnownMethod("InitializeClosedStaticThunk"u8, null);
                }

                var instantiatedDelegateType = delegateType as InstantiatedType;
                if (instantiatedDelegateType != null)
                    invokeThunk = context.GetMethodForInstantiatedType(invokeThunk, instantiatedDelegateType);

                return new DelegateCreationInfo(
                    delegateType,
                    factory.MethodEntrypoint(initMethod),
                    targetMethod,
                    constrainedType,
                    constrainedType == null ? TargetKind.ExactCallableAddress : TargetKind.ConstrainedMethod,
                    factory.MethodEntrypoint(invokeThunk));
            }
            else
            {
                if (!closed)
                    throw new NotImplementedException("Open instance delegates");

                ReadOnlySpan<byte> initializeMethodName = "InitializeClosedInstance"u8;
                MethodDesc targetCanonMethod = targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);
                TargetKind kind;
                if (targetMethod.HasInstantiation)
                {
                    if (followVirtualDispatch && targetMethod.IsVirtual)
                    {
                        initializeMethodName = "InitializeClosedInstanceWithGVMResolution"u8;
                        kind = TargetKind.MethodHandle;
                    }
                    else
                    {
                        if (targetMethod != targetCanonMethod)
                        {
                            // Closed delegates to generic instance methods need to be constructed through a slow helper that
                            // checks for the fat function pointer case (function pointer + instantiation argument in a single
                            // pointer) and injects an invocation thunk to unwrap the fat function pointer as part of
                            // the invocation if necessary.
                            initializeMethodName = "InitializeClosedInstanceSlow"u8;
                        }

                        kind = TargetKind.ExactCallableAddress;
                    }
                }
                else
                {
                    if (followVirtualDispatch && targetMethod.IsVirtual)
                    {
                        if (targetMethod.OwningType.IsInterface)
                        {
                            kind = TargetKind.InterfaceDispatch;
                            initializeMethodName = "InitializeClosedInstanceToInterface"u8;
                        }
                        else
                        {
                            kind = TargetKind.VTableLookup;
                            targetMethod = targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);
                        }
                    }
                    else
                    {
                        kind = TargetKind.CanonicalEntrypoint;
                        targetMethod = targetMethod.GetCanonMethodTarget(CanonicalFormKind.Specific);
                    }
                }

                Debug.Assert(constrainedType == null);
                return new DelegateCreationInfo(
                    delegateType,
                    factory.MethodEntrypoint(systemDelegate.GetKnownMethod(initializeMethodName, null)),
                    targetMethod,
                    constrainedType,
                    kind);
            }
        }

        public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
        {
            sb.Append("__DelegateCtor_"u8);
            if (TargetNeedsVTableLookup)
                sb.Append("FromVtbl_"u8);
            Constructor.AppendMangledName(nameMangler, sb);
            sb.Append("__"u8);
            sb.Append(nameMangler.GetMangledMethodName(_targetMethod));
            if (_constrainedType != null)
            {
                sb.Append("__"u8);
                nameMangler.GetMangledTypeName(_constrainedType);
            }
            if (Thunk != null)
            {
                sb.Append("__"u8);
                Thunk.AppendMangledName(nameMangler, sb);
            }
        }

        public override bool Equals(object obj)
        {
            var other = obj as DelegateCreationInfo;
            return other != null
                && Constructor == other.Constructor
                && _targetMethod == other._targetMethod
                && _constrainedType == other._constrainedType
                && _targetKind == other._targetKind
                && Thunk == other.Thunk;
        }

        public override int GetHashCode()
        {
            return Constructor.GetHashCode() ^ _targetMethod.GetHashCode();
        }

#if !SUPPORT_JIT
        internal int CompareTo(DelegateCreationInfo other, TypeSystemComparer comparer)
        {
            var compare = _targetKind - other._targetKind;
            if (compare != 0)
                return compare;

            compare = comparer.Compare(_targetMethod, other._targetMethod);
            if (compare != 0)
                return compare;

            compare = comparer.Compare(Constructor.Method, other.Constructor.Method);
            if (compare != 0)
                return compare;

            if (_constrainedType != null && other._constrainedType != null)
            {
                compare = comparer.Compare(_constrainedType, other._constrainedType);
                if (compare != 0)
                    return compare;
            }
            else
            {
                if (_constrainedType != null)
                    return 1;
                if (other._constrainedType != null)
                    return -1;
            }

            if (Thunk == other.Thunk)
                return 0;

            if (Thunk == null)
                return -1;

            if (other.Thunk == null)
                return 1;

            return comparer.Compare(Thunk.Method, other.Thunk.Method);
        }
#endif
    }
}