File: Internal\Runtime\TypeLoader\TypeLoaderEnvironment.ConstructedGenericMethodsLookup.cs
Web Access
Project: src\src\runtime\src\coreclr\nativeaot\System.Private.TypeLoader\src\System.Private.TypeLoader.csproj (System.Private.TypeLoader)
// 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.Diagnostics;
using System.Reflection.Runtime.General;
using System.Threading;

using Internal.Metadata.NativeFormat;
using Internal.NativeFormat;
using Internal.Runtime.CompilerServices;
using Internal.TypeSystem;

namespace Internal.Runtime.TypeLoader
{
    public sealed partial class TypeLoaderEnvironment
    {
        internal class GenericMethodEntry
        {
            private int? _hashCode;
            public bool _isRegisteredSuccessfully;
            public bool _isAsyncVariant;
            public bool _isReturnDroppingAsyncThunk;
            public IntPtr _methodDictionary;
            public RuntimeTypeHandle _declaringTypeHandle;
            public MethodNameAndSignature _methodNameAndSignature;
            public RuntimeTypeHandle[] _genericMethodArgumentHandles;

            public override int GetHashCode()
            {
                if (!_hashCode.HasValue)
                {
                    _hashCode = _declaringTypeHandle.GetHashCode() ^ VersionResilientHashCode.GenericInstanceHashCode(VersionResilientHashCode.NameHashCode(_methodNameAndSignature.Name), _genericMethodArgumentHandles);
                }
                return _hashCode.Value;
            }

            public override bool Equals(object obj)
            {
                // There are no scenarios where we call .Equals to check the componentized equality (we explicitly use IsEqualToEntryByComponentsComparison for that),
                // so making sure we revert to the reference equality in case someone calls here.
                return base.Equals(obj);
            }

            public virtual bool IsEqualToEntryByComponentsComparison(GenericMethodEntry other)
            {
                if (!other._declaringTypeHandle.Equals(_declaringTypeHandle))
                    return false;

                if (_isAsyncVariant != other._isAsyncVariant)
                    return false;

                if (_isReturnDroppingAsyncThunk != other._isReturnDroppingAsyncThunk)
                    return false;

                if (!other._methodNameAndSignature.Equals(_methodNameAndSignature))
                    return false;

                if (other._genericMethodArgumentHandles == null)
                    return false;

                if (other._genericMethodArgumentHandles.Length != _genericMethodArgumentHandles.Length)
                    return false;

                for (int i = 0; i < _genericMethodArgumentHandles.Length; i++)
                    if (!other._genericMethodArgumentHandles[i].Equals(_genericMethodArgumentHandles[i]))
                        return false;

                return true;
            }
        }

        internal class DynamicGenericMethodsHashtable : LockFreeReaderHashtable<GenericMethodLookupData, GenericMethodEntry>
        {
            protected override int GetKeyHashCode(GenericMethodLookupData key)
            {
                return key.LookupHashCode();
            }
            protected override bool CompareKeyToValue(GenericMethodLookupData key, GenericMethodEntry value)
            {
                return key.MatchGenericMethodEntry(value);
            }

            protected override int GetValueHashCode(GenericMethodEntry value)
            {
                return value.GetHashCode();
            }

            protected override bool CompareValueToValue(GenericMethodEntry value1, GenericMethodEntry value2)
            {
                // Comparisons should *only* be done using the generic method components
                return value1.IsEqualToEntryByComponentsComparison(value2);
            }

            protected override GenericMethodEntry CreateValueFromKey(GenericMethodLookupData key)
            {
                // Feature not used by the TypeBuilder
                throw NotImplemented.ByDesign;
            }
        }

        internal class DynamicGenericMethodComponentsHashtable : LockFreeReaderHashtable<IntPtr, GenericMethodEntry>
        {
            protected override int GetKeyHashCode(IntPtr key)
            {
                return key.GetHashCode();
            }
            protected override bool CompareKeyToValue(IntPtr key, GenericMethodEntry value)
            {
                return key.Equals(value._methodDictionary);
            }

            protected override int GetValueHashCode(GenericMethodEntry value)
            {
                Debug.Assert(value._methodDictionary != IntPtr.Zero);
                return value._methodDictionary.GetHashCode();
            }

            protected override bool CompareValueToValue(GenericMethodEntry value1, GenericMethodEntry value2)
            {
                // Comparisons should *only* be done using the generic method components
                return value1.IsEqualToEntryByComponentsComparison(value2);
            }

            protected override GenericMethodEntry CreateValueFromKey(IntPtr key)
            {
                // Feature not used by the TypeBuilder
                throw NotImplemented.ByDesign;
            }
        }

        internal abstract class GenericMethodLookupData
        {
            internal abstract int LookupHashCode();
            internal abstract bool MatchParsedEntry(ref NativeParser entryParser, ref ExternalReferencesTable externalReferencesLookup, TypeManagerHandle moduleHandle);
            internal abstract bool MatchGenericMethodEntry(GenericMethodEntry entry);
        }
        internal class MethodDescBasedGenericMethodLookup : GenericMethodLookupData
        {
            protected InstantiatedMethod _methodToLookup;

            internal MethodDescBasedGenericMethodLookup(InstantiatedMethod methodToLookup) { _methodToLookup = methodToLookup; }

            internal override int LookupHashCode() { return _methodToLookup.GetHashCode(); }

            internal override bool MatchParsedEntry(ref NativeParser entryParser, ref ExternalReferencesTable externalReferencesLookup, TypeManagerHandle moduleHandle)
            {
                //
                // Entries read from the hashtable are loaded as GenericMethodDescs, and compared to the input.
                // This lookup is slower than the lookups using RuntimeTypeHandles, but can handle cases where we don't have
                // RuntimeTypeHandle values for all of the components of the input GenericMethodDesc, but still need to look it up in case the
                // method dictionary statically really exists
                //
                TypeSystemContext context = _methodToLookup.Context;

                RuntimeTypeHandle parsedDeclaringTypeHandle = externalReferencesLookup.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned());
                TypeDesc parsedDeclaringType = context.ResolveRuntimeTypeHandle(parsedDeclaringTypeHandle);
                if (_methodToLookup.OwningType != parsedDeclaringType)
                    return false;

                int flagsAndToken = (int)entryParser.GetUnsigned();
                bool isAsyncVariant = (flagsAndToken & GenericMethodsHashtableConstants.IsAsyncVariant) != 0;
                bool isReturnDroppingAsyncThunk = (flagsAndToken & GenericMethodsHashtableConstants.IsReturnDroppingAsyncThunk) != 0;
                if (_methodToLookup.AsyncVariant != isAsyncVariant)
                    return false;
                if (_methodToLookup.ReturnDroppingAsyncThunk != isReturnDroppingAsyncThunk)
                    return false;

                int token = ((int)HandleType.Method << 25) | (flagsAndToken & ~(GenericMethodsHashtableConstants.IsAsyncVariant | GenericMethodsHashtableConstants.IsReturnDroppingAsyncThunk));

                MethodNameAndSignature nameAndSignature = TypeLoaderEnvironment.GetMethodNameAndSignatureFromToken(moduleHandle, (uint)token);
                if (!_methodToLookup.NameAndSignature.Equals(nameAndSignature))
                    return false;

                RuntimeTypeHandle[] parsedArgsHandles = GetTypeSequence(ref externalReferencesLookup, ref entryParser);
                if (parsedArgsHandles.Length != _methodToLookup.Instantiation.Length)
                    return false;

                for (int i = 0; i < _methodToLookup.Instantiation.Length; i++)
                {
                    TypeDesc leftType = context.ResolveRuntimeTypeHandle(parsedArgsHandles[i]);
                    TypeDesc rightType = _methodToLookup.Instantiation[i];
                    if (leftType != rightType)
                        return false;
                }

                return true;
            }

            internal override bool MatchGenericMethodEntry(GenericMethodEntry entry)
            {
                TypeSystemContext context = _methodToLookup.Context;

                TypeDesc parsedDeclaringType = context.ResolveRuntimeTypeHandle(entry._declaringTypeHandle);
                if (_methodToLookup.OwningType != parsedDeclaringType)
                    return false;

                if (_methodToLookup.AsyncVariant != entry._isAsyncVariant)
                    return false;

                if (_methodToLookup.ReturnDroppingAsyncThunk != entry._isReturnDroppingAsyncThunk)
                    return false;

                if (!_methodToLookup.NameAndSignature.Equals(entry._methodNameAndSignature))
                    return false;

                if (entry._genericMethodArgumentHandles.Length != _methodToLookup.Instantiation.Length)
                    return false;

                for (int i = 0; i < _methodToLookup.Instantiation.Length; i++)
                {
                    TypeDesc leftType = context.ResolveRuntimeTypeHandle(entry._genericMethodArgumentHandles[i]);
                    TypeDesc rightType = _methodToLookup.Instantiation[i];
                    if (leftType != rightType)
                        return false;
                }

                return true;
            }
        }

        private DynamicGenericMethodsHashtable _dynamicGenericMethods = new DynamicGenericMethodsHashtable();
        private DynamicGenericMethodComponentsHashtable _dynamicGenericMethodComponents = new DynamicGenericMethodComponentsHashtable();

        internal bool TryLookupGenericMethodDictionary(GenericMethodLookupData lookupData, out IntPtr result)
        {
            if (!TryGetStaticGenericMethodDictionary(lookupData, out result))
                if (!TryGetDynamicGenericMethodDictionary(lookupData, out result))
                    return false;

            return true;
        }

        public bool TryGetGenericMethodComponents(IntPtr methodDictionary, out RuntimeTypeHandle declaringType, out MethodNameAndSignature nameAndSignature, out RuntimeTypeHandle[] genericMethodArgumentHandles, out bool isAsyncVariant)
        {
            if (!TryGetDynamicGenericMethodComponents(methodDictionary, out declaringType, out nameAndSignature, out genericMethodArgumentHandles, out isAsyncVariant))
            {
                if (!TryGetStaticGenericMethodComponents(methodDictionary, out declaringType, out nameAndSignature, out genericMethodArgumentHandles, out isAsyncVariant))
                    return false;
            }

            return true;
        }

        public static bool TryGetGenericMethodComponents(IntPtr methodDictionary, out RuntimeTypeHandle declaringType, out RuntimeTypeHandle[] genericMethodArgumentHandles)
        {
            TypeLoaderEnvironment instance = TypeLoaderEnvironment.InstanceOrNull;
            if (instance == null || !instance.TryGetDynamicGenericMethodComponents(methodDictionary, out declaringType, out _, out genericMethodArgumentHandles, out _))
                if (!TryGetStaticGenericMethodComponents(methodDictionary, out declaringType, out _, out genericMethodArgumentHandles, out _))
                    return false;

            return true;
        }


        public bool TryLookupExactMethodPointer(InstantiatedMethod method, out IntPtr result)
        {
            int lookupHashcode = method.OwningType.GetHashCode();

            NativeHashtable hashtable;
            ExternalReferencesTable externalReferencesLookup;

            MethodDescBasedGenericMethodLookup lookupData = new MethodDescBasedGenericMethodLookup(method);

            foreach (NativeFormatModuleInfo module in ModuleList.EnumerateModules())
            {
                if (!GetHashtableFromBlob(module, ReflectionMapBlob.ExactMethodInstantiationsHashtable, out hashtable, out externalReferencesLookup))
                    continue;

                var enumerator = hashtable.Lookup(lookupHashcode);

                NativeParser entryParser;
                while (!(entryParser = enumerator.GetNext()).IsNull)
                {
                    if (!lookupData.MatchParsedEntry(ref entryParser, ref externalReferencesLookup, module.Handle))
                        continue;

                    // We found a match
                    result = externalReferencesLookup.GetIntPtrFromIndex(entryParser.GetUnsigned());
                    return true;
                }
            }

            result = IntPtr.Zero;
            return false;
        }

        // This method computes the method pointer and dictionary pointer for a GVM.
        // Inputs:
        //      - method: the GVM whose pointer and dictionary to retrieve
        // Outputs:
        //      - methodPointer: pointer to the GVM's implementation
        //      - dictionaryPointer: (if applicable) pointer to the dictionary to be used with the GVM call
        public bool TryGetGenericVirtualMethodPointer(InstantiatedMethod method, out IntPtr methodPointer, out IntPtr dictionaryPointer)
        {
            if (!method.CanShareNormalGenericCode())
            {
                if (TryLookupExactMethodPointer(method, out methodPointer))
                {
                    Debug.Assert(methodPointer != IntPtr.Zero);
                    dictionaryPointer = IntPtr.Zero;
                    return true;
                }
            }

            InstantiatedMethod nonTemplateMethod = method;

            // Templates are always unboxing stubs for valuetype instance methods
            if (!method.UnboxingStub && method.OwningType.IsValueType && !IsStaticMethodSignature(method.NameAndSignature))
            {
                // Make it an unboxing stub, note the first parameter which is true
                nonTemplateMethod = (InstantiatedMethod)method.Context.ResolveGenericMethodInstantiation(true, method.AsyncVariant, method.ReturnDroppingAsyncThunk, (DefType)method.OwningType, method.NameAndSignature, method.Instantiation);
            }

            // If we cannot find an exact method entry point, look for an equivalent template and compute the generic dictionary
            InstantiatedMethod templateMethod = TemplateLocator.TryGetGenericMethodTemplate(nonTemplateMethod, out _, out _);
            if (templateMethod == null)
            {
                methodPointer = default;
                dictionaryPointer = default;
                return false;
            }

            methodPointer = templateMethod.FunctionPointer;

            if (!TryLookupGenericMethodDictionary(new MethodDescBasedGenericMethodLookup(method), out dictionaryPointer))
            {
                using (_typeLoaderLock.EnterScope())
                {
                    // Now that we hold the lock, we may find that existing types can now find
                    // their associated RuntimeTypeHandle. Flush the type builder states as a way
                    // to force the reresolution of RuntimeTypeHandles which couldn't be found before.
                    method.Context.FlushTypeBuilderStates();

                    if (!TypeBuilder.TryBuildGenericMethod(method, out dictionaryPointer))
                    {
                        return false;
                    }
                }
            }

            Debug.Assert(methodPointer != IntPtr.Zero && dictionaryPointer != IntPtr.Zero);
            return true;
        }

#region Privates
        private bool TryGetDynamicGenericMethodDictionary(GenericMethodLookupData lookupData, out IntPtr result)
        {
            result = IntPtr.Zero;

            using (_dynamicGenericsLock.EnterScope())
            {
                GenericMethodEntry entry;
                if (!_dynamicGenericMethods.TryGetValue(lookupData, out entry))
                    return false;

                if (!entry._isRegisteredSuccessfully)
                    return false;

                result = entry._methodDictionary;
                return true;
            }
        }
        private static bool TryGetStaticGenericMethodDictionary(GenericMethodLookupData lookupData, out IntPtr result)
        {
            // Search the hashtable for a generic instantiation match

            ExternalReferencesTable externalReferencesLookup;
            NativeHashtable genericMethodsHashtable;

            foreach (NativeFormatModuleInfo module in ModuleList.EnumerateModules())
            {
                if (!GetHashtableFromBlob(module, ReflectionMapBlob.GenericMethodsHashtable, out genericMethodsHashtable, out externalReferencesLookup))
                    continue;

                int lookupHashcode = lookupData.LookupHashCode();
                var enumerator = genericMethodsHashtable.Lookup(lookupHashcode);

                NativeParser entryParser;
                while (!(entryParser = enumerator.GetNext()).IsNull)
                {
                    uint dictionaryIndex = entryParser.GetUnsigned();

                    if (!lookupData.MatchParsedEntry(ref entryParser, ref externalReferencesLookup, module.Handle))
                        continue;

                    // Current entry matched all inputs, return success
                    result = externalReferencesLookup.GetIntPtrFromIndex(dictionaryIndex);
                    return true;
                }
            }

            result = IntPtr.Zero;
            return false;
        }

        private bool TryGetDynamicGenericMethodComponents(IntPtr methodDictionary, out RuntimeTypeHandle declaringType, out MethodNameAndSignature methodNameAndSignature, out RuntimeTypeHandle[] genericMethodArgumentHandles, out bool isAsyncVariant)
        {
            declaringType = default(RuntimeTypeHandle);
            methodNameAndSignature = null;
            genericMethodArgumentHandles = null;
            isAsyncVariant = false;

            using (_dynamicGenericsLock.EnterScope())
            {
                GenericMethodEntry entry;
                if (!_dynamicGenericMethodComponents.TryGetValue(methodDictionary, out entry))
                    return false;

                if (!entry._isRegisteredSuccessfully)
                    return false;

                declaringType = entry._declaringTypeHandle;
                methodNameAndSignature = entry._methodNameAndSignature;
                genericMethodArgumentHandles = entry._genericMethodArgumentHandles;
                isAsyncVariant = entry._isAsyncVariant;
                return true;
            }
        }

        private static unsafe bool TryGetStaticGenericMethodComponents(IntPtr methodDictionary, out RuntimeTypeHandle declaringType, out MethodNameAndSignature nameAndSignature, out RuntimeTypeHandle[] genericMethodArgumentHandles, out bool isAsyncVariant)
        {
            // Generic method dictionaries have a header that has the hash code in it. Locate the header
            IntPtr dictionaryHeader = IntPtr.Subtract(methodDictionary, IntPtr.Size);
            int lookupHashcode = *(int*)dictionaryHeader;

            ExternalReferencesTable externalReferencesLookup;
            NativeHashtable genericMethodsHashtable;

            foreach (NativeFormatModuleInfo module in ModuleList.EnumerateModules())
            {
                if (!GetHashtableFromBlob(module, ReflectionMapBlob.GenericMethodsHashtable, out genericMethodsHashtable, out externalReferencesLookup))
                    continue;

                var enumerator = genericMethodsHashtable.Lookup(lookupHashcode);

                NativeParser entryParser;
                while (!(entryParser = enumerator.GetNext()).IsNull)
                {
                    // Is this entry the dictionary we are looking for?
                    uint dictionaryIndex = entryParser.GetUnsigned();
                    IntPtr parsedMethodDictionary = externalReferencesLookup.GetIntPtrFromIndex(dictionaryIndex);
                    if (parsedMethodDictionary != methodDictionary)
                        continue;

                    // We have a match - fill in the results
                    declaringType = externalReferencesLookup.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned());


                    int flagsAndToken = (int)entryParser.GetUnsigned();
                    isAsyncVariant = (flagsAndToken & GenericMethodsHashtableConstants.IsAsyncVariant) != 0;

                    int token = ((int)HandleType.Method << 25) | (flagsAndToken & ~(GenericMethodsHashtableConstants.IsAsyncVariant | GenericMethodsHashtableConstants.IsReturnDroppingAsyncThunk));

                    nameAndSignature = new MethodNameAndSignature(module.MetadataReader, token.AsHandle().ToMethodHandle(module.MetadataReader));

                    uint arity = entryParser.GetSequenceCount();
                    genericMethodArgumentHandles = new RuntimeTypeHandle[arity];

                    for (int i = 0; i < arity; i++)
                    {
                        genericMethodArgumentHandles[i] = externalReferencesLookup.GetRuntimeTypeHandleFromIndex(entryParser.GetUnsigned());
                    }

                    return true;
                }
            }

            declaringType = default(RuntimeTypeHandle);
            nameAndSignature = null;
            genericMethodArgumentHandles = null;
            isAsyncVariant = false;
            return false;
        }

#endregion
    }
}