[crossgen2] Attribute Presence Filter (dotnet/coreclr#27084)
authorAndrew Au <andrewau@microsoft.com>
Thu, 10 Oct 2019 06:26:50 +0000 (23:26 -0700)
committerGitHub <noreply@github.com>
Thu, 10 Oct 2019 06:26:50 +0000 (23:26 -0700)
Commit migrated from https://github.com/dotnet/coreclr/commit/c50db1a85928e97a13af7450e4b9c58c6f56a002

src/coreclr/src/tools/crossgen2/Common/Internal/Runtime/ModuleHeaders.cs
src/coreclr/src/tools/crossgen2/Common/TypeSystem/Common/TypeHashingAlgorithms.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/AttributePresenceFilterNode.cs [new file with mode: 0644]
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunHashCode.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj

index 7f0ab90..c18b0d0 100644 (file)
@@ -62,6 +62,7 @@ namespace Internal.Runtime
         InliningInfo = 110, // Added in v2.1
         ProfileDataInfo = 111, // Added in v2.2
         ManifestMetadata = 112, // Added in v2.3
+        AttributePresence = 113, // Added in V3.1
 
         //
         // CoreRT ReadyToRun sections
index acd6c00..8e84efc 100644 (file)
@@ -115,7 +115,7 @@ namespace Internal.NativeFormat
             return hash1 ^ hash2;
         }
 
-        // This function may be needed in a portion of the codebase which is too low level to use 
+        // This function may be needed in a portion of the codebase which is too low level to use
         // globalization, ergo, we cannot call ToString on the integer.
         private static string IntToString(int arg)
         {
@@ -145,7 +145,7 @@ namespace Internal.NativeFormat
 
         public static int ComputeArrayTypeHashCode(int elementTypeHashCode, int rank)
         {
-            // Arrays are treated as generic types in some parts of our system. The array hashcodes are 
+            // Arrays are treated as generic types in some parts of our system. The array hashcodes are
             // carefully crafted to be the same as the hashcodes of their implementation generic types.
 
             int hashCode;
diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/AttributePresenceFilterNode.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/AttributePresenceFilterNode.cs
new file mode 100644 (file)
index 0000000..b7c34fc
--- /dev/null
@@ -0,0 +1,312 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+using Internal.Text;
+using Internal.TypeSystem;
+using Internal.TypeSystem.Ecma;
+
+namespace ILCompiler.DependencyAnalysis.ReadyToRun
+{
+    public class AttributePresenceFilterNode : HeaderTableNode
+    {
+        private EcmaModule _module;
+
+        // TODO: Eliminate the cache (https://github.com/dotnet/coreclr/issues/27116)
+        private ObjectData _computedData;
+
+        public override int ClassCode => 56456113;
+
+        public AttributePresenceFilterNode(EcmaModule module)
+            : base(module.Context.Target)
+        {
+            this._module = module;
+        }
+
+        public override void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
+        {
+            sb.Append(nameMangler.CompilationUnitPrefix);
+            sb.Append("__ReadyToRunAttributePresenceFilter");
+        }
+
+        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 == "Windows.Foundation.Metadata")
+                {
+                    // Windows.Foundation.Metadata attributes are a similar construct to compilerservices attributes. Add them to the table
+                    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;
+        }
+
+        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.
+             */
+            // TODO: Handle MethodRef (https://github.com/dotnet/coreclr/issues/27118)
+            Debug.Assert(customAttributeConstructorHandle.Kind == HandleKind.MethodDefinition);
+            MethodDefinitionHandle customAttributeConstructorDefinitionHandle = (MethodDefinitionHandle)customAttributeConstructorHandle;
+            MethodDefinition customAttributeConstructorDefinition = _module.MetadataReader.GetMethodDefinition(customAttributeConstructorDefinitionHandle);
+            TypeDefinitionHandle customAttributeConstructorTypeDefinitionHandle = customAttributeConstructorDefinition.GetDeclaringType();
+            TypeDefinition customAttributeConstructorTypeDefinition = _module.MetadataReader.GetTypeDefinition(customAttributeConstructorTypeDefinitionHandle);
+            StringHandle customAttributeConstructorTypeNamespaceHandle = customAttributeConstructorTypeDefinition.Namespace;
+            StringHandle customAttributeConstructorTypeNameHandle = customAttributeConstructorTypeDefinition.Name;
+            customAttributeTypeNamespace = _module.MetadataReader.GetString(customAttributeConstructorTypeNamespaceHandle);
+            customAttributeTypeName = _module.MetadataReader.GetString(customAttributeConstructorTypeNameHandle);
+        }
+
+        // Algorithm "xor128" from p. 5 of Marsaglia, "Xorshift RNGs"
+        private uint XorShift128(uint[] state)
+        {
+            uint s, t = state[3];
+            state[3] = state[2];
+            state[2] = state[1];
+            state[1] = s = state[0];
+            t ^= t << 11;
+            t ^= t >> 8;
+            return state[0] = t ^ s ^ (s >> 19);
+        }
+
+        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 });
+
+            if (_computedData != null)
+            {
+                return _computedData;
+            }
+
+            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 = ReadyToRunHashCode.NameHashCode(name);
+                    uint hash = unchecked((uint)ReadyToRunHashCode.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 ((XorShift128(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.Assert(false, "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 = XorShift128(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);
+            _computedData = builder.ToObjectData();
+            return _computedData;
+        }
+    }
+}
index f0c048c..4e31e2b 100644 (file)
@@ -216,17 +216,21 @@ namespace ILCompiler.DependencyAnalysis
             ModuleTokenResolver moduleTokenResolver,
             SignatureContext signatureContext,
             CopiedCorHeaderNode corHeaderNode,
-            ResourceData win32Resources)
+            ResourceData win32Resources,
+            AttributePresenceFilterNode attributePresenceFilterNode)
             : base(context,
                   compilationModuleGroup,
                   nameMangler,
                   new ReadyToRunTableManager(context))
         {
+            // To make the code future compatible to the composite R2R story
+            // do NOT attempt to pass and store _inputModule here
             _importMethods = new Dictionary<TypeAndMethod, IMethodNode>();
 
             Resolver = moduleTokenResolver;
             InputModuleContext = signatureContext;
             CopiedCorHeaderNode = corHeaderNode;
+            AttributePresenceFilter = attributePresenceFilterNode;
             if (!win32Resources.IsEmpty)
                 Win32ResourcesNode = new Win32ResourcesNode(win32Resources);
         }
@@ -265,6 +269,8 @@ namespace ILCompiler.DependencyAnalysis
 
         public DebugInfoTableNode DebugInfoTable;
 
+        public AttributePresenceFilterNode AttributePresenceFilter;
+
         public ImportSectionNode EagerImports;
 
         public ImportSectionNode MethodImports;
@@ -478,6 +484,15 @@ namespace ILCompiler.DependencyAnalysis
             DebugInfoTable = new DebugInfoTableNode(Target);
             Header.Add(Internal.Runtime.ReadyToRunSectionType.DebugInfo, DebugInfoTable, DebugInfoTable);
 
+            // Core library attributes are checked FAR more often than other dlls
+            // attributes, so produce a highly efficient table for determining if they are
+            // present. Other assemblies *MAY* benefit from this feature, but it doesn't show
+            // as useful at this time.
+            if (this.AttributePresenceFilter != null)
+            {
+                Header.Add(Internal.Runtime.ReadyToRunSectionType.AttributePresence, AttributePresenceFilter, AttributePresenceFilter);
+            }
+
             EagerImports = new ImportSectionNode(
                 "EagerImports",
                 CorCompileImportType.CORCOMPILE_IMPORT_TYPE_UNKNOWN,
index 0bb9b01..6405bba 100644 (file)
@@ -89,6 +89,16 @@ namespace ILCompiler
             ModuleTokenResolver moduleTokenResolver = new ModuleTokenResolver(_compilationGroup, _context);
             SignatureContext signatureContext = new SignatureContext(_inputModule, moduleTokenResolver);
             CopiedCorHeaderNode corHeaderNode = new CopiedCorHeaderNode(_inputModule);
+            AttributePresenceFilterNode attributePresenceFilterNode = null;
+            
+            // Core library attributes are checked FAR more often than other dlls
+            // attributes, so produce a highly efficient table for determining if they are
+            // present. Other assemblies *MAY* benefit from this feature, but it doesn't show
+            // as useful at this time.
+            if (_inputModule == _inputModule.Context.SystemModule)
+            {
+                 attributePresenceFilterNode = new AttributePresenceFilterNode(_inputModule);
+            }
 
             // Produce a ResourceData where the IBC PROFILE_DATA entry has been filtered out
             ResourceData win32Resources = new ResourceData(_inputModule, (object type, object name, ushort language) =>
@@ -114,7 +124,8 @@ namespace ILCompiler
                 moduleTokenResolver,
                 signatureContext,
                 corHeaderNode,
-                win32Resources);
+                win32Resources,
+                attributePresenceFilterNode);
 
             DependencyAnalyzerBase<NodeFactory> graph = CreateDependencyGraph(factory);
 
index b6d7e23..5d9e5cb 100644 (file)
@@ -215,5 +215,38 @@ namespace ILCompiler
         {
             return unchecked((int)(((uint)value << bitCount) | ((uint)value >> (32 - bitCount))));
         }
+
+        private static uint XXHash32_MixEmptyState()
+        {
+            // Unlike System.HashCode, these hash values are required to be stable, so don't
+            // mixin a random process specific value
+            return 374761393U; // Prime5
+        }
+
+        private static uint XXHash32_QueueRound(uint hash, uint queuedValue)
+        {
+            return ((uint)RotateLeft((int)(hash + queuedValue * 3266489917U/*Prime3*/), 17)) * 668265263U/*Prime4*/;
+        }
+
+        private static uint XXHash32_MixFinal(uint hash)
+        {
+            hash ^= hash >> 15;
+            hash *= 2246822519U/*Prime2*/;
+            hash ^= hash >> 13;
+            hash *= 3266489917U/*Prime3*/;
+            hash ^= hash >> 16;
+            return hash;
+        }
+
+        public static uint CombineTwoValuesIntoHash(uint value1, uint value2)
+        {
+            // This matches the behavior of System.HashCode.Combine(value1, value2) as of the time of authoring
+            uint hash = XXHash32_MixEmptyState();
+            hash += 8;
+            hash = XXHash32_QueueRound(hash, value1);
+            hash = XXHash32_QueueRound(hash, value2);
+            hash = XXHash32_MixFinal(hash);
+            return hash;
+        }
     }
 }
index 91a2515..45d1489 100644 (file)
     <Compile Include="CodeGen\ReadyToRunObjectWriter.cs" />
     <Compile Include="Compiler\CompilationModuleGroup.ReadyToRun.cs" />
     <Compile Include="Compiler\DependencyAnalysis\ReadyToRun\ArgIterator.cs" />
+    <Compile Include="Compiler\DependencyAnalysis\ReadyToRun\AttributePresenceFilterNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\ReadyToRun\CopiedFieldRvaNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\ReadyToRun\CopiedManagedResourcesNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\ReadyToRun\CopiedMetadataBlobNode.cs" />