File: Compiler\DependencyAnalysis\ReadyToRun\AttributePresenceFilterNode.cs
Web Access
Project: src\src\runtime\src\coreclr\tools\aot\ILCompiler.ReadyToRun\ILCompiler.ReadyToRun.csproj (ILCompiler.ReadyToRun)
// 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.Collections.Immutable;
using System.Diagnostics;
using System.Numerics;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;

using Internal;
using Internal.Text;
using Internal.TypeSystem.Ecma;

namespace ILCompiler.DependencyAnalysis.ReadyToRun
{
    public class AttributePresenceFilterNode : HeaderTableNode
    {
        private EcmaModule _module;

        public override int ClassCode => 56456113;

        public AttributePresenceFilterNode(EcmaModule module)
        {
            _module = module;
        }

        public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
        {
            sb.Append(nameMangler.CompilationUnitPrefix);
            sb.Append("__ReadyToRunAttributePresenceFilter__"u8);
            sb.Append(_module.Assembly.GetName().Name);
        }

        private struct CustomAttributeEntry
        {
            public string TypeNamespace;
            public string TypeName;
            public int Parent;
        }

        private List<CustomAttributeEntry> GetCustomAttributeEntries()
        {
            MetadataReader reader = _module.MetadataReader;
            List<CustomAttributeEntry> customAttributeEntries = new List<CustomAttributeEntry>();
            foreach (var handle in reader.CustomAttributes)
            {
                CustomAttribute customAttribute = reader.GetCustomAttribute(handle);
                EntityHandle customAttributeConstructorHandle = customAttribute.Constructor;
                string customAttributeTypeNamespace, customAttributeTypeName;
                ReadCustomAttributeTypeNameWithoutResolving(customAttributeConstructorHandle, out customAttributeTypeNamespace, out customAttributeTypeName);
                // System.Runtime.CompilerServices.NullableAttribute is NEVER added to the table (There are *many* of these, and they provide no useful value to the runtime)
                if (customAttributeTypeNamespace == "System.Runtime.CompilerServices" && customAttributeTypeName == "NullableAttribute")
                {
                    continue;
                }
                bool addToTable = false;
                if (customAttributeTypeNamespace.StartsWith("System.Runtime."))
                {
                    addToTable = true;
                }
                else if (customAttributeTypeNamespace == "System")
                {
                    // Some historical well known attributes were placed in the System namespace. Special case them
                    if (customAttributeTypeName == "ParamArrayAttribute")
                    {
                        addToTable = true;
                    }
                    else if (customAttributeTypeName == "ThreadStaticAttribute")
                    {
                        addToTable = true;
                    }
                }
                else if (customAttributeTypeNamespace == "System.Reflection")
                {
                    // Historical attribute in the System.Reflection namespace
                    if (customAttributeTypeName == "DefaultMemberAttribute")
                    {
                        addToTable = true;
                    }
                }

                if (!addToTable)
                    continue;

                customAttributeEntries.Add(new CustomAttributeEntry
                {
                    TypeNamespace = customAttributeTypeNamespace,
                    TypeName = customAttributeTypeName,
                    Parent = reader.GetToken(customAttribute.Parent)
                });
            }
            return customAttributeEntries;
        }

        /**
         * This class is used to extract the first type handle in a signature.
         *
         * In the case that a custom attribute's constructor is a MemberReference,
         * and its parent is a TypeSpec, we have to parse the signature, but we do
         * not want to actually resolve the types. So we used this dummy signature
         * type provider to extract the first type handle.
         */
        private class FirstTypeHandleExtractor : ISignatureTypeProvider<DummyType, DummyGenericContext>
        {
            private EntityHandle _firstTypeHandle;

            public EntityHandle FirstTypeHandle => _firstTypeHandle;

            public DummyType GetTypeFromDefinition(MetadataReader reader, TypeDefinitionHandle handle, byte rawTypeKind)
            {
                if (_firstTypeHandle.IsNil)
                {
                    _firstTypeHandle = handle;
                }
                return new DummyType();
            }

            public DummyType GetTypeFromReference(MetadataReader reader, TypeReferenceHandle handle, byte rawTypeKind)
            {
                if (_firstTypeHandle.IsNil)
                {
                    _firstTypeHandle = handle;
                }
                return DummyType.Instance;
            }

            #region Uninteresting dummy methods

            // These methods are required by the interface, but it is otherwise uninteresting for our purpose here

            public DummyType GetArrayType(DummyType elementType, ArrayShape shape)
            {
                return DummyType.Instance;
            }

            public DummyType GetByReferenceType(DummyType elementType)
            {
                return DummyType.Instance;
            }

            public DummyType GetFunctionPointerType(MethodSignature<DummyType> signature)
            {
                return DummyType.Instance;
            }

            public DummyType GetGenericInstantiation(DummyType genericType, ImmutableArray<DummyType> typeArguments)
            {
                return DummyType.Instance;
            }

            public DummyType GetGenericMethodParameter(DummyGenericContext genericContext, int index)
            {
                return DummyType.Instance;
            }

            public DummyType GetGenericTypeParameter(DummyGenericContext genericContext, int index)
            {
                return DummyType.Instance;
            }

            public DummyType GetModifiedType(DummyType modifier, DummyType unmodifiedType, bool isRequired)
            {
                return DummyType.Instance;
            }

            public DummyType GetPinnedType(DummyType elementType)
            {
                return DummyType.Instance;
            }

            public DummyType GetPointerType(DummyType elementType)
            {
                return DummyType.Instance;
            }

            public DummyType GetPrimitiveType(PrimitiveTypeCode typeCode)
            {
                return new DummyType();
            }

            public DummyType GetSZArrayType(DummyType elementType)
            {
                return DummyType.Instance;
            }

            public DummyType GetTypeFromSpecification(MetadataReader reader, DummyGenericContext genericContext, TypeSpecificationHandle handle, byte rawTypeKind)
            {
                return DummyType.Instance;
            }

            #endregion
        }

        #region Uninteresting dummy types

        private class DummyType
        {
            public static DummyType Instance = new DummyType();
        }

        private class DummyGenericContext
        {
        }

        #endregion

        private void ReadCustomAttributeTypeNameWithoutResolving(EntityHandle customAttributeConstructorHandle, out string customAttributeTypeNamespace, out string customAttributeTypeName)
        {
            /**
             * It is possible that the assembly that defines the attribute is not provided as a reference assembly.
             *
             * Most the time, as long as the custom attribute is not accessed or the reference assembly is available at runtime, the code will work just fine.
             *
             * If we used _module.GetMethod(customAttributeConstructorHandle), we should have caused an exception and failing the compilation.
             *
             * Therefore, we have this alternate path to obtain the type namespace and name.
             */
            if (customAttributeConstructorHandle.Kind == HandleKind.MethodDefinition)
            {
                MethodDefinitionHandle customAttributeConstructorDefinitionHandle = (MethodDefinitionHandle)customAttributeConstructorHandle;
                MethodDefinition customAttributeConstructorDefinition = _module.MetadataReader.GetMethodDefinition(customAttributeConstructorDefinitionHandle);
                TypeDefinitionHandle customAttributeConstructorTypeDefinitionHandle = customAttributeConstructorDefinition.GetDeclaringType();
                GetTypeNameFromTypeDefinitionHandle(customAttributeConstructorTypeDefinitionHandle, out customAttributeTypeNamespace, out customAttributeTypeName);
            }
            else if (customAttributeConstructorHandle.Kind == HandleKind.MemberReference)
            {
                MemberReferenceHandle customAttributeConstructorReferenceHandle = (MemberReferenceHandle)customAttributeConstructorHandle;
                MemberReference customAttributeConstructorReference = _module.MetadataReader.GetMemberReference(customAttributeConstructorReferenceHandle);
                EntityHandle customAttributeConstructorReferenceParentHandle = customAttributeConstructorReference.Parent;
                if (customAttributeConstructorReferenceParentHandle.Kind == HandleKind.TypeReference)
                {
                    TypeReferenceHandle customAttributeConstructorTypeReferenceHandle = (TypeReferenceHandle)customAttributeConstructorReferenceParentHandle;
                    GetTypeNameFromTypeReferenceHandle(customAttributeConstructorTypeReferenceHandle, out customAttributeTypeNamespace, out customAttributeTypeName);
                }
                else
                {
                    Debug.Assert(customAttributeConstructorReferenceParentHandle.Kind == HandleKind.TypeSpecification);
                    TypeSpecificationHandle customAttributeConstructorTypeSpecificationHandle = (TypeSpecificationHandle)customAttributeConstructorReferenceParentHandle;
                    TypeSpecification customAttributeConstructorTypeSpecification = _module.MetadataReader.GetTypeSpecification(customAttributeConstructorTypeSpecificationHandle);
                    FirstTypeHandleExtractor fakeSignatureTypeProvider = new FirstTypeHandleExtractor();
                    customAttributeConstructorTypeSpecification.DecodeSignature(fakeSignatureTypeProvider, new DummyGenericContext());
                    EntityHandle firstTypeHandle = fakeSignatureTypeProvider.FirstTypeHandle;
                    if (firstTypeHandle.Kind == HandleKind.TypeDefinition)
                    {
                        TypeDefinitionHandle customAttributeConstructorTypeDefinitionHandle = (TypeDefinitionHandle)firstTypeHandle;
                        GetTypeNameFromTypeDefinitionHandle(customAttributeConstructorTypeDefinitionHandle, out customAttributeTypeNamespace, out customAttributeTypeName);
                    }
                    else
                    {
                        Debug.Assert(firstTypeHandle.Kind == HandleKind.TypeReference);
                        TypeReferenceHandle customAttributeConstructorTypeReferenceHandle = (TypeReferenceHandle)firstTypeHandle;
                        GetTypeNameFromTypeReferenceHandle(customAttributeConstructorTypeReferenceHandle, out customAttributeTypeNamespace, out customAttributeTypeName);
                    }
                }
            }
            else
            {
                Debug.Assert(false);
                customAttributeTypeNamespace = null;
                customAttributeTypeName = null;
            }
        }

        private void GetTypeNameFromTypeReferenceHandle(TypeReferenceHandle typeReferenceHandle, out string typeNamespace, out string typeName)
        {
            TypeReference typeReference = _module.MetadataReader.GetTypeReference(typeReferenceHandle);
            StringHandle typeNamespaceHandle = typeReference.Namespace;
            StringHandle typeNameHandle = typeReference.Name;
            typeNamespace = _module.MetadataReader.GetString(typeNamespaceHandle);
            typeName = _module.MetadataReader.GetString(typeNameHandle);
        }

        private void GetTypeNameFromTypeDefinitionHandle(TypeDefinitionHandle typeDefinitionHandle, out string typeNamespace, out string typeName)
        {
            TypeDefinition typeDefinition = _module.MetadataReader.GetTypeDefinition(typeDefinitionHandle);
            StringHandle typeNamespaceHandle = typeDefinition.Namespace;
            StringHandle typeNameHandle = typeDefinition.Name;
            typeNamespace = _module.MetadataReader.GetString(typeNamespaceHandle);
            typeName = _module.MetadataReader.GetString(typeNameHandle);
        }

        internal uint Xoshiro128StarStar(uint[] s)
        {
            uint result = BitOperations.RotateLeft(s[1] * 5, 7) * 9;
            uint t = s[1] << 9;

            s[2] ^= s[0];
            s[3] ^= s[1];
            s[1] ^= s[2];
            s[0] ^= s[3];

            s[2] ^= t;
            s[3] = BitOperations.RotateLeft(s[3], 11);

            return result;
        }

        public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
        {
            // This node does not trigger generation of other nodes.
            if (relocsOnly)
                return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this });

            List<CustomAttributeEntry> customAttributeEntries = GetCustomAttributeEntries();
            int countOfEntries = customAttributeEntries.Count;
            if (countOfEntries == 0)
            {
                return new ObjectData(Array.Empty<byte>(), Array.Empty<Relocation>(), 1, new ISymbolDefinitionNode[] { this });
            }

            // Buckets have 8 entries
            uint minTableBucketCount = (uint)(countOfEntries / 8) + 1;
            uint bucketCount = 1;

            // Bucket count must be power of two
            while (bucketCount < minTableBucketCount)
            {
                bucketCount *= 2;
            }

            // Resize the array.
            bool tryAgainWithBiggerTable = false;
            int countOfRetries = 0;
            ushort[] pTable;
            do
            {
                tryAgainWithBiggerTable = false;
                uint actualSizeOfTable = bucketCount * 8; // Buckets have 8 entries in them
                pTable = new ushort[actualSizeOfTable];
                uint[] state = new uint[] {729055690, 833774698, 218408041, 493449127};
                // Attempt to fill table
                foreach (var customAttributeEntry in customAttributeEntries)
                {
                    string name = customAttributeEntry.TypeNamespace + "." + customAttributeEntry.TypeName;
                    // This hashing algorithm MUST match exactly the logic in NativeCuckooFilter
                    int hashOfAttribute = VersionResilientHashCode.NameHashCode(System.Text.Encoding.UTF8.GetBytes(name));
                    uint hash = unchecked((uint)VersionResilientHashCode.CombineTwoValuesIntoHash((uint)hashOfAttribute, (uint)customAttributeEntry.Parent));
                    ushort fingerprint = (ushort)(hash >> 16);
                    if (fingerprint == 0)
                    {
                        fingerprint = 1;
                    }
                    uint bucketAIndex = hash % bucketCount;
                    uint fingerprintHash = (uint)fingerprint;
                    uint bucketBIndex = (bucketAIndex ^ (fingerprintHash % bucketCount));
                    Debug.Assert(bucketAIndex == (bucketBIndex ^ (fingerprintHash % bucketCount)));
                    if ((Xoshiro128StarStar(state) & 1) != 0) // Randomly choose which bucket to attempt to fill first
                    {
                        uint temp = bucketAIndex;
                        bucketAIndex = bucketBIndex;
                        bucketBIndex = temp;
                    }
                    Func<uint, ushort, bool> hasEntryInBucket = (uint bucketIndex, ushort fprint) =>
                    {
                        for (int i = 0; i < 8; i++)
                        {
                            if (pTable[(bucketIndex * 8) + i] == fprint)
                            {
                                return true;
                            }
                        }
                        return false;
                    };
                    Func<uint, bool> isEmptyEntryInBucket = (uint bucketIndex) =>
                    {
                        for (int i = 0; i < 8; i++)
                        {
                            if (pTable[(bucketIndex * 8) + i] == 0)
                            {
                                return true;
                            }
                        }
                        return false;
                    };
                    Action<uint, ushort> fillEmptyEntryInBucket = (uint bucketIndex, ushort fprint) =>
                    {
                        for (int i = 0; i < 8; i++)
                        {
                            if (pTable[(bucketIndex * 8) + i] == 0)
                            {
                                pTable[(bucketIndex * 8) + i] = fprint;
                                return;
                            }
                        }
                        Debug.Fail("Not possible to reach here");
                    };
                    // Scan for pre-existing fingerprint entry in buckets
                    if (hasEntryInBucket(bucketAIndex, fingerprint) || hasEntryInBucket(bucketBIndex, fingerprint))
                    {
                        continue;
                    }

                    // Determine if there is space in a bucket to add a new entry
                    if (isEmptyEntryInBucket(bucketAIndex))
                    {
                        fillEmptyEntryInBucket(bucketAIndex, fingerprint);
                        continue;
                    }

                    if (isEmptyEntryInBucket(bucketBIndex))
                    {
                        fillEmptyEntryInBucket(bucketBIndex, fingerprint);
                        continue;
                    }

                    bool success = false;
                    int MaxNumKicks = 256;
                    // Note, that bucketAIndex itself was chosen randomly above.
                    for (int n = 0; !success && n < MaxNumKicks; n++)
                    {
                        // Randomly swap an entry in bucket bucketAIndex with fingerprint
                        uint entryIndexInBucket = Xoshiro128StarStar(state) & 0x7;
                        ushort temp = fingerprint;
                        fingerprint = pTable[(bucketAIndex * 8) + entryIndexInBucket];
                        pTable[(bucketAIndex * 8) + entryIndexInBucket] = temp;

                        // Find other bucket
                        fingerprintHash = (uint)fingerprint;
                        bucketAIndex = bucketAIndex ^ (fingerprintHash % bucketCount);
                        if (isEmptyEntryInBucket(bucketAIndex))
                        {
                            fillEmptyEntryInBucket(bucketAIndex, fingerprint);
                            success = true;
                        }
                    }

                    if (success)
                    {
                        continue;
                    }

                    tryAgainWithBiggerTable = true;
                }

                if (tryAgainWithBiggerTable)
                {
                    // bucket entry kicking path requires bucket counts to be power of two in size due to use of xor to retrieve second hash
                    bucketCount *= 2;
                }
            }
            while (tryAgainWithBiggerTable && ((countOfRetries++) < 2));

            byte[] result;
            if (tryAgainWithBiggerTable)
            {
                result = Array.Empty<byte>();
            }
            else
            {
                result = new byte[pTable.Length * 2];
                for (int i = 0; i < pTable.Length; i++)
                {
                    result[i * 2] = (byte)(pTable[i] % 256);
                    result[i * 2 + 1] = (byte)(pTable[i] >> 8);
                }
            }

            ObjectDataBuilder builder = new ObjectDataBuilder(factory, relocsOnly);
            builder.RequireInitialAlignment(16);
            builder.AddSymbol(this);
            builder.EmitBytes(result);

            return builder.ToObjectData();
        }
    }
}