Make textual stack trace metadata smaller (#89377)
authorMichal Strehovský <MichalStrehovsky@users.noreply.github.com>
Tue, 25 Jul 2023 04:49:44 +0000 (13:49 +0900)
committerGitHub <noreply@github.com>
Tue, 25 Jul 2023 04:49:44 +0000 (13:49 +0900)
When we need to generate a textual stack trace in NativeAOT, we rely on two things - reflection metadata for methods, or a supplementary stack trace metadata format.

The reflection metadata case is simple - reflection keeps track of method addresses and the associated metadata like names and signatures. We ask for metadata handles (tokens, basically) and we get the handles back. We then walk the metadata and format textual strings.

When reflection metadata is not available, we have to emit some other metadata in the compiler. We use the same metadata format and conceptually the closest thing were unresolvable `MemberReference`s, so we basically generated a pair of method pointer + MemberReference handle for each method body that doesn't have reflection metadata. It's a lot less metadata than for a resolvable definition, but is still quite a bit wasteful.

This change avoids generating full `MemberReference` and instead inlines owning type/name/signature handle/token into the mapping table. We also do it in a way that avoids emitting owning type/name/signature if it's the same as the record before that.

src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/MethodNameFormatter.cs
src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/StackTraceMetadata.cs
src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/System.Private.StackTraceMetadata.csproj
src/coreclr/tools/Common/Internal/Runtime/StackTraceData.cs [new file with mode: 0644]
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/AnalysisBasedMetadataManager.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/StackTraceMethodMappingNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/GeneratingMetadataManager.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs
src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj

index 4122638..677571c 100644 (file)
@@ -35,10 +35,38 @@ namespace Internal.StackTraceMetadata
             _typeContext = typeContext;
         }
 
-        public static string FormatMethodName(MetadataReader metadataReader, Handle methodHandle)
+        public static string FormatMethodName(MetadataReader metadataReader, Handle owningType, ConstantStringValueHandle name, MethodSignatureHandle signature, ConstantStringArrayHandle genericArguments)
         {
-            MethodNameFormatter formatter = new MethodNameFormatter(metadataReader, SigTypeContext.FromMethod(metadataReader, methodHandle));
-            formatter.EmitMethodName(methodHandle);
+            MethodNameFormatter formatter = new MethodNameFormatter(metadataReader, SigTypeContext.FromMethod(metadataReader, owningType, genericArguments));
+            formatter.EmitTypeName(owningType, namespaceQualified: true);
+            formatter._outputBuilder.Append('.');
+            formatter.EmitString(name);
+
+            if (!genericArguments.IsNull(metadataReader))
+            {
+                var args = metadataReader.GetConstantStringArray(genericArguments);
+                bool first = true;
+                foreach (Handle handle in args.Value)
+                {
+                    if (first)
+                    {
+                        first = false;
+                        formatter._outputBuilder.Append('[');
+                    }
+                    else
+                    {
+                        formatter._outputBuilder.Append(',');
+                    }
+                    formatter.EmitString(handle.ToConstantStringValueHandle(metadataReader));
+                }
+                if (!first)
+                {
+                    formatter._outputBuilder.Append(']');
+                }
+            }
+
+            formatter.EmitMethodParameters(metadataReader.GetMethodSignature(signature));
+
             return formatter._outputBuilder.ToString();
         }
 
@@ -76,99 +104,6 @@ namespace Internal.StackTraceMetadata
         }
 
         /// <summary>
-        /// Emit a given method signature to a specified string builder.
-        /// </summary>
-        /// <param name="methodHandle">Method reference or instantiation token</param>
-        private void EmitMethodName(Handle methodHandle)
-        {
-            switch (methodHandle.HandleType)
-            {
-                case HandleType.MemberReference:
-                    EmitMethodReferenceName(methodHandle.ToMemberReferenceHandle(_metadataReader));
-                    break;
-
-                case HandleType.MethodInstantiation:
-                    EmitMethodInstantiationName(methodHandle.ToMethodInstantiationHandle(_metadataReader));
-                    break;
-
-                case HandleType.QualifiedMethod:
-                    EmitMethodDefinitionName(methodHandle.ToQualifiedMethodHandle(_metadataReader));
-                    break;
-
-                default:
-                    Debug.Assert(false);
-                    _outputBuilder.Append("???");
-                    break;
-            }
-        }
-
-        /// <summary>
-        /// Emit method reference to the output string builder.
-        /// </summary>
-        /// <param name="memberRefHandle">Member reference handle</param>
-        private void EmitMethodReferenceName(MemberReferenceHandle memberRefHandle)
-        {
-            MemberReference methodRef = _metadataReader.GetMemberReference(memberRefHandle);
-            MethodSignature methodSignature;
-            EmitContainingTypeAndMethodName(methodRef, out methodSignature);
-            EmitMethodParameters(methodSignature);
-        }
-
-        /// <summary>
-        /// Emit generic method instantiation to the output string builder.
-        /// </summary>
-        /// <param name="methodInstHandle">Method instantiation handle</param>
-        private void EmitMethodInstantiationName(MethodInstantiationHandle methodInstHandle)
-        {
-            MethodInstantiation methodInst = _metadataReader.GetMethodInstantiation(methodInstHandle);
-
-            if (methodInst.Method.HandleType == HandleType.MemberReference)
-            {
-                MemberReferenceHandle methodRefHandle = methodInst.Method.ToMemberReferenceHandle(_metadataReader);
-                MemberReference methodRef = methodRefHandle.GetMemberReference(_metadataReader);
-                EmitContainingTypeAndMethodName(methodRef, out MethodSignature methodSignature);
-                EmitGenericArguments(methodInst.GenericTypeArguments);
-                EmitMethodParameters(methodSignature);
-            }
-            else
-            {
-                QualifiedMethodHandle qualifiedMethodHandle = methodInst.Method.ToQualifiedMethodHandle(_metadataReader);
-                QualifiedMethod qualifiedMethod = _metadataReader.GetQualifiedMethod(qualifiedMethodHandle);
-                EmitContainingTypeAndMethodName(qualifiedMethod);
-                EmitGenericArguments(methodInst.GenericTypeArguments);
-                EmitMethodParameters(qualifiedMethod.Method);
-            }
-        }
-
-        private void EmitMethodDefinitionName(QualifiedMethodHandle qualifiedMethodHandle)
-        {
-            QualifiedMethod qualifiedMethod = _metadataReader.GetQualifiedMethod(qualifiedMethodHandle);
-            EmitContainingTypeAndMethodName(qualifiedMethod);
-            EmitMethodParameters(qualifiedMethod.Method);
-        }
-
-        /// <summary>
-        /// Emit containing type and method name and extract the method signature from a method reference.
-        /// </summary>
-        /// <param name="methodRef">Method reference to format</param>
-        /// <param name="methodSignature">Output method signature</param>
-        private void EmitContainingTypeAndMethodName(MemberReference methodRef, out MethodSignature methodSignature)
-        {
-            methodSignature = _metadataReader.GetMethodSignature(methodRef.Signature.ToMethodSignatureHandle(_metadataReader));
-            EmitTypeName(methodRef.Parent, namespaceQualified: true);
-            _outputBuilder.Append('.');
-            EmitString(methodRef.Name);
-        }
-
-        private void EmitContainingTypeAndMethodName(QualifiedMethod qualifiedMethod)
-        {
-            Method method = _metadataReader.GetMethod(qualifiedMethod.Method);
-            EmitTypeName(qualifiedMethod.EnclosingType, namespaceQualified: true);
-            _outputBuilder.Append('.');
-            EmitString(method.Name);
-        }
-
-        /// <summary>
         /// Emit parenthesized method argument type list.
         /// </summary>
         /// <param name="methodSignature">Method signature to use for parameter formatting</param>
@@ -308,6 +243,11 @@ namespace Internal.StackTraceMetadata
                     EmitFunctionPointerTypeName();
                     break;
 
+                // This is not an actual type, but we don't always bother representing generic arguments on generic methods as types
+                case HandleType.ConstantStringValue:
+                    EmitString(typeHandle.ToConstantStringValueHandle(_metadataReader));
+                    break;
+
                 default:
                     Debug.Assert(false, $"Type handle {typeHandle.HandleType} was not handled");
                     _outputBuilder.Append("???");
@@ -588,35 +528,12 @@ namespace Internal.StackTraceMetadata
                 }
             }
 
-            public static SigTypeContext FromMethod(MetadataReader metadataReader, Handle methodHandle)
+            public static SigTypeContext FromMethod(MetadataReader metadataReader, Handle enclosingTypeHandle, ConstantStringArrayHandle methodInst)
             {
-                object typeContext;
-                object methodContext;
-
-                switch (methodHandle.HandleType)
-                {
-                    case HandleType.MemberReference:
-                        typeContext = GetTypeContext(metadataReader, methodHandle);
-                        methodContext = default(HandleCollection);
-                        break;
-
-                    case HandleType.MethodInstantiation:
-                        MethodInstantiation methodInst = methodHandle.ToMethodInstantiationHandle(metadataReader).GetMethodInstantiation(metadataReader);
-                        typeContext = GetTypeContext(metadataReader, methodInst.Method);
-                        methodContext = methodInst.GenericTypeArguments;
-                        break;
-
-                    case HandleType.QualifiedMethod:
-                        QualifiedMethod qualifiedMethod = methodHandle.ToQualifiedMethodHandle(metadataReader).GetQualifiedMethod(metadataReader);
-                        typeContext = GetTypeContext(metadataReader, qualifiedMethod.EnclosingType);
-                        methodContext = qualifiedMethod.Method.GetMethod(metadataReader).GenericParameters;
-                        break;
-                    default:
-                        Debug.Assert(false);
-                        return default(SigTypeContext);
-                }
-
-                return new SigTypeContext(typeContext, methodContext);
+                object methodContext = null;
+                if (!methodInst.IsNull(metadataReader))
+                    methodContext = methodInst.GetConstantStringArray(metadataReader).Value;
+                return new SigTypeContext(GetTypeContext(metadataReader, enclosingTypeHandle), methodContext);
             }
 
             public static SigTypeContext FromMethod(MetadataReader metadataReader, TypeDefinitionHandle enclosingTypeHandle, MethodHandle methodHandle)
index 416b553..a203b7a 100644 (file)
@@ -5,12 +5,14 @@ using System;
 using System.Collections.Generic;
 
 using Internal.Metadata.NativeFormat;
+using Internal.NativeFormat;
 using Internal.Runtime;
 using Internal.Runtime.Augments;
 using Internal.Runtime.TypeLoader;
 using Internal.TypeSystem;
 
 using ReflectionExecution = Internal.Reflection.Execution.ReflectionExecution;
+using Debug = System.Diagnostics.Debug;
 
 namespace Internal.StackTraceMetadata
 {
@@ -144,7 +146,7 @@ namespace Internal.StackTraceMetadata
             /// <summary>
             /// Dictionary mapping method RVA's to tokens within the metadata blob.
             /// </summary>
-            private readonly Dictionary<int, int> _methodRvaToTokenMap;
+            private readonly StackTraceData[] _stacktraceDatas;
 
             /// <summary>
             /// Metadata reader for the stack trace metadata.
@@ -195,10 +197,10 @@ namespace Internal.StackTraceMetadata
                 {
                     _metadataReader = new MetadataReader(new IntPtr(metadataBlob), (int)metadataBlobSize);
 
-                    // RVA to token map consists of pairs of integers (method RVA - token)
-                    int rvaToTokenMapEntryCount = (int)(rvaToTokenMapBlobSize / (2 * sizeof(int)));
-                    _methodRvaToTokenMap = new Dictionary<int, int>(rvaToTokenMapEntryCount);
-                    PopulateRvaToTokenMap(handle, (int *)rvaToTokenMapBlob, rvaToTokenMapEntryCount);
+                    int entryCount = *(int*)rvaToTokenMapBlob;
+                    _stacktraceDatas = new StackTraceData[entryCount];
+
+                    PopulateRvaToTokenMap(handle, rvaToTokenMapBlob + sizeof(int), rvaToTokenMapBlobSize - sizeof(int));
                 }
             }
 
@@ -207,18 +209,66 @@ namespace Internal.StackTraceMetadata
             /// within a single binary module.
             /// </summary>
             /// <param name="handle">Module to use to construct the mapping</param>
-            /// <param name="rvaToTokenMap">List of RVA - token pairs</param>
-            /// <param name="entryCount">Number of the RVA - token pairs in the list</param>
-            private unsafe void PopulateRvaToTokenMap(TypeManagerHandle handle, int *rvaToTokenMap, int entryCount)
+            /// <param name="pMap">List of RVA - token pairs</param>
+            /// <param name="length">Length of the blob</param>
+            private unsafe void PopulateRvaToTokenMap(TypeManagerHandle handle, byte* pMap, uint length)
             {
-                for (int entryIndex = 0; entryIndex < entryCount; entryIndex++)
+                Handle currentOwningType = default;
+                MethodSignatureHandle currentSignature = default;
+                ConstantStringValueHandle currentName = default;
+                ConstantStringArrayHandle currentMethodInst = default;
+
+                int current = 0;
+                byte* pCurrent = pMap;
+                while (pCurrent < pMap + length)
                 {
-                    int* pRelPtr32 = &rvaToTokenMap[2 * entryIndex + 0];
-                    byte* pointer = (byte*)pRelPtr32 + *pRelPtr32;
-                    int methodRva = (int)(pointer - (byte*)handle.OsModuleBase);
-                    int token = rvaToTokenMap[2 * entryIndex + 1];
-                    _methodRvaToTokenMap[methodRva] = token;
+                    byte command = *pCurrent++;
+
+                    if ((command & StackTraceDataCommand.UpdateOwningType) != 0)
+                    {
+                        currentOwningType = Handle.FromIntToken((int)NativePrimitiveDecoder.ReadUInt32(ref pCurrent));
+                        Debug.Assert(currentOwningType.HandleType is HandleType.TypeDefinition or HandleType.TypeReference or HandleType.TypeSpecification);
+                    }
+
+                    if ((command & StackTraceDataCommand.UpdateName) != 0)
+                    {
+                        currentName = new Handle(HandleType.ConstantStringValue, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToConstantStringValueHandle(_metadataReader);
+                    }
+
+                    if ((command & StackTraceDataCommand.UpdateSignature) != 0)
+                    {
+                        currentSignature = new Handle(HandleType.MethodSignature, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToMethodSignatureHandle(_metadataReader);
+                        currentMethodInst = default;
+                    }
+
+                    if ((command & StackTraceDataCommand.UpdateGenericSignature) != 0)
+                    {
+                        currentSignature = new Handle(HandleType.MethodSignature, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToMethodSignatureHandle(_metadataReader);
+                        currentMethodInst = new Handle(HandleType.ConstantStringArray, (int)NativePrimitiveDecoder.DecodeUnsigned(ref pCurrent)).ToConstantStringArrayHandle(_metadataReader);
+                    }
+
+                    void* pMethod = ReadRelPtr32(pCurrent);
+                    pCurrent += sizeof(int);
+
+                    Debug.Assert((nint)pMethod > handle.OsModuleBase);
+                    int methodRva = (int)((nint)pMethod - handle.OsModuleBase);
+
+                    _stacktraceDatas[current++] = new StackTraceData
+                    {
+                        Rva = methodRva,
+                        OwningType = currentOwningType,
+                        Name = currentName,
+                        Signature = currentSignature,
+                        GenericArguments = currentMethodInst,
+                    };
+
+                    static void* ReadRelPtr32(byte* address)
+                        => address + *(int*)address;
                 }
+
+                Debug.Assert(current == _stacktraceDatas.Length);
+
+                Array.Sort(_stacktraceDatas);
             }
 
             /// <summary>
@@ -226,20 +276,32 @@ namespace Internal.StackTraceMetadata
             /// </summary>
             public string GetMethodNameFromRvaIfAvailable(int rva)
             {
-                if (_methodRvaToTokenMap == null)
+                if (_stacktraceDatas == null)
                 {
                     // No stack trace metadata for this module
                     return null;
                 }
 
-                int rawToken;
-                if (!_methodRvaToTokenMap.TryGetValue(rva, out rawToken))
+                int index = Array.BinarySearch(_stacktraceDatas, new StackTraceData() { Rva = rva });
+                if (index < 0)
                 {
                     // Method RVA not found in the map
                     return null;
                 }
 
-                return MethodNameFormatter.FormatMethodName(_metadataReader, Handle.FromIntToken(rawToken));
+                StackTraceData data = _stacktraceDatas[index];
+                return MethodNameFormatter.FormatMethodName(_metadataReader, data.OwningType, data.Name, data.Signature, data.GenericArguments);
+            }
+
+            private struct StackTraceData : IComparable<StackTraceData>
+            {
+                public int Rva { get; init; }
+                public Handle OwningType { get; init; }
+                public ConstantStringValueHandle Name { get; init; }
+                public MethodSignatureHandle Signature { get; init; }
+                public ConstantStringArrayHandle GenericArguments { get; init; }
+
+                public int CompareTo(StackTraceData other) => Rva.CompareTo(other.Rva);
             }
         }
     }
index f88e491..cc44031 100644 (file)
     <Compile Include="Internal\Runtime\CompilerHelpers\LibraryInitializer.cs" />
     <Compile Include="Internal\StackTraceMetadata\StackTraceMetadata.cs" />
     <Compile Include="Internal\StackTraceMetadata\MethodNameFormatter.cs" />
+    <Compile Include="$(CompilerCommonPath)\Internal\NativeFormat\NativeFormatReader.Primitives.cs">
+      <Link>Internal\NativeFormat\NativeFormatReader.Primitives.cs</Link>
+    </Compile>
     <Compile Include="$(CompilerCommonPath)\Internal\Runtime\MetadataBlob.cs">
       <Link>Internal\Runtime\MetadataBlob.cs</Link>
     </Compile>
+    <Compile Include="$(CompilerCommonPath)\Internal\Runtime\StackTraceData.cs">
+      <Link>Internal\Runtime\StackTraceData.cs</Link>
+    </Compile>
   </ItemGroup>
 </Project>
diff --git a/src/coreclr/tools/Common/Internal/Runtime/StackTraceData.cs b/src/coreclr/tools/Common/Internal/Runtime/StackTraceData.cs
new file mode 100644 (file)
index 0000000..2db0523
--- /dev/null
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Internal.Runtime
+{
+    internal static class StackTraceDataCommand
+    {
+        public const byte UpdateOwningType = 0x01;
+        public const byte UpdateName = 0x02;
+        public const byte UpdateSignature = 0x04;
+        public const byte UpdateGenericSignature = 0x08; // Just a shortcut - sig metadata has the info
+    }
+}
index 40e210d..b748cc2 100644 (file)
@@ -136,7 +136,7 @@ namespace ILCompiler
             out List<MetadataMapping<MetadataType>> typeMappings,
             out List<MetadataMapping<MethodDesc>> methodMappings,
             out List<MetadataMapping<FieldDesc>> fieldMappings,
-            out List<MetadataMapping<MethodDesc>> stackTraceMapping)
+            out List<StackTraceMapping> stackTraceMapping)
         {
             ComputeMetadata(new Policy(_blockingPolicy, this), factory,
                 out metadataBlob,
index 2a7fb40..7845ac2 100644 (file)
@@ -2,8 +2,13 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System;
+using System.Collections.Generic;
 
+using Internal.Runtime;
 using Internal.Text;
+using Internal.TypeSystem;
+
+using Debug = System.Diagnostics.Debug;
 
 namespace ILCompiler.DependencyAnalysis
 {
@@ -47,10 +52,77 @@ namespace ILCompiler.DependencyAnalysis
             objData.RequireInitialPointerAlignment();
             objData.AddSymbol(this);
 
-            foreach (var mappingEntry in factory.MetadataManager.GetStackTraceMapping(factory))
+            var mapping = new List<StackTraceMapping>(factory.MetadataManager.GetStackTraceMapping(factory));
+
+            // The information is encoded as a set of commands: set current owning type, set current method name, etc.
+            // Sort things so that we don't thrash the current entity too much.
+            mapping.Sort((x, y) =>
+            {
+                // Group methods on the same generic type definition together
+                int result = x.OwningTypeHandle.CompareTo(y.OwningTypeHandle);
+                if (result != 0)
+                    return result;
+
+                // Overloads get grouped together too
+                result = x.MethodNameHandle.CompareTo(y.MethodNameHandle);
+                if (result != 0)
+                    return result;
+
+                // Methods that only differ in something generic get grouped too
+                result = x.MethodSignatureHandle.CompareTo(y.MethodSignatureHandle);
+                if (result != 0)
+                    return result;
+
+                // At this point the genericness should be the same
+                Debug.Assert(x.MethodInstantiationArgumentCollectionHandle == y.MethodInstantiationArgumentCollectionHandle);
+
+                // Compare by the method as a tie breaker to get stable sort
+                return TypeSystemComparer.Instance.Compare(x.Method, y.Method);
+            });
+
+            int currentOwningType = 0;
+            int currentSignature = 0;
+            int currentName = 0;
+
+            // The first int contains the number of entries
+            objData.EmitInt(mapping.Count);
+
+            foreach (var entry in mapping)
             {
-                objData.EmitReloc(factory.MethodEntrypoint(mappingEntry.Entity), RelocType.IMAGE_REL_BASED_RELPTR32);
-                objData.EmitInt(mappingEntry.MetadataHandle);
+                var commandReservation = objData.ReserveByte();
+
+                byte command = 0;
+                if (currentOwningType != entry.OwningTypeHandle)
+                {
+                    currentOwningType = entry.OwningTypeHandle;
+                    command |= StackTraceDataCommand.UpdateOwningType;
+                    objData.EmitInt(currentOwningType);
+                }
+
+                if (currentName != entry.MethodNameHandle)
+                {
+                    currentName = entry.MethodNameHandle;
+                    command |= StackTraceDataCommand.UpdateName;
+                    objData.EmitCompressedUInt((uint)(currentName & MetadataManager.MetadataOffsetMask));
+                }
+
+                if (currentSignature != entry.MethodSignatureHandle)
+                {
+                    currentSignature = entry.MethodSignatureHandle;
+                    objData.EmitCompressedUInt((uint)(currentSignature & MetadataManager.MetadataOffsetMask));
+
+                    if (entry.MethodInstantiationArgumentCollectionHandle != 0)
+                    {
+                        command |= StackTraceDataCommand.UpdateGenericSignature;
+                        objData.EmitCompressedUInt((uint)(entry.MethodInstantiationArgumentCollectionHandle & MetadataManager.MetadataOffsetMask));
+                    }
+                    else
+                    {
+                        command |= StackTraceDataCommand.UpdateSignature;
+                    }
+                }
+                objData.EmitByte(commandReservation, command);
+                objData.EmitReloc(factory.MethodEntrypoint(entry.Method), RelocType.IMAGE_REL_BASED_RELPTR32);
             }
 
             _size = objData.CountBytes;
index 3d30bcd..ee984bb 100644 (file)
@@ -49,7 +49,7 @@ namespace ILCompiler
             out List<MetadataMapping<MetadataType>> typeMappings,
             out List<MetadataMapping<MethodDesc>> methodMappings,
             out List<MetadataMapping<FieldDesc>> fieldMappings,
-            out List<MetadataMapping<MethodDesc>> stackTraceMapping) where TPolicy : struct, IMetadataPolicy
+            out List<StackTraceMapping> stackTraceMapping) where TPolicy : struct, IMetadataPolicy
         {
             var transformed = MetadataTransform.Run(policy, GetCompilationModulesWithMetadata());
             MetadataTransform transform = transformed.Transform;
@@ -59,7 +59,7 @@ namespace ILCompiler
             writer.ScopeDefinitions.AddRange(transformed.Scopes);
 
             // Generate entries in the blob for methods that will be necessary for stack trace purposes.
-            var stackTraceRecords = new List<KeyValuePair<MethodDesc, MetadataRecord>>();
+            var stackTraceRecords = new List<StackTraceRecordData>();
             foreach (var methodBody in GetCompiledMethodBodies())
             {
                 MethodDesc method = methodBody.Method;
@@ -76,13 +76,14 @@ namespace ILCompiler
                 if (!_stackTraceEmissionPolicy.ShouldIncludeMethod(method))
                     continue;
 
-                MetadataRecord record = CreateStackTraceRecord(transform, method);
+                StackTraceRecordData record = CreateStackTraceRecord(transform, method);
 
-                stackTraceRecords.Add(new KeyValuePair<MethodDesc, MetadataRecord>(
-                    method,
-                    record));
+                stackTraceRecords.Add(record);
 
-                writer.AdditionalRootRecords.Add(record);
+                writer.AdditionalRootRecords.Add(record.OwningType);
+                writer.AdditionalRootRecords.Add(record.MethodName);
+                writer.AdditionalRootRecords.Add(record.MethodSignature);
+                writer.AdditionalRootRecords.Add(record.MethodInstantiationArgumentCollection);
             }
 
             var ms = new MemoryStream();
@@ -108,7 +109,7 @@ namespace ILCompiler
             typeMappings = new List<MetadataMapping<MetadataType>>();
             methodMappings = new List<MetadataMapping<MethodDesc>>();
             fieldMappings = new List<MetadataMapping<FieldDesc>>();
-            stackTraceMapping = new List<MetadataMapping<MethodDesc>>();
+            stackTraceMapping = new List<StackTraceMapping>();
 
             // Generate type definition mappings
             foreach (var type in factory.MetadataManager.GetTypesWithEETypes())
@@ -177,7 +178,13 @@ namespace ILCompiler
             // Generate stack trace metadata mapping
             foreach (var stackTraceRecord in stackTraceRecords)
             {
-                stackTraceMapping.Add(new MetadataMapping<MethodDesc>(stackTraceRecord.Key, writer.GetRecordHandle(stackTraceRecord.Value)));
+                StackTraceMapping mapping = new StackTraceMapping(
+                    stackTraceRecord.Method,
+                    writer.GetRecordHandle(stackTraceRecord.OwningType),
+                    writer.GetRecordHandle(stackTraceRecord.MethodSignature),
+                    writer.GetRecordHandle(stackTraceRecord.MethodName),
+                    stackTraceRecord.MethodInstantiationArgumentCollection != null ? writer.GetRecordHandle(stackTraceRecord.MethodInstantiationArgumentCollection) : 0);
+                stackTraceMapping.Add(mapping);
             }
         }
 
index fcbf125..8658068 100644 (file)
@@ -19,12 +19,11 @@ using MethodIL = Internal.IL.MethodIL;
 using CustomAttributeValue = System.Reflection.Metadata.CustomAttributeValue<Internal.TypeSystem.TypeDesc>;
 
 using MetadataRecord = Internal.Metadata.NativeFormat.Writer.MetadataRecord;
-using MemberReference = Internal.Metadata.NativeFormat.Writer.MemberReference;
 using TypeReference = Internal.Metadata.NativeFormat.Writer.TypeReference;
 using TypeSpecification = Internal.Metadata.NativeFormat.Writer.TypeSpecification;
 using ConstantStringValue = Internal.Metadata.NativeFormat.Writer.ConstantStringValue;
 using TypeInstantiationSignature = Internal.Metadata.NativeFormat.Writer.TypeInstantiationSignature;
-using MethodInstantiation = Internal.Metadata.NativeFormat.Writer.MethodInstantiation;
+using ConstantStringArray = Internal.Metadata.NativeFormat.Writer.ConstantStringArray;
 
 namespace ILCompiler
 {
@@ -43,7 +42,7 @@ namespace ILCompiler
         private List<MetadataMapping<MetadataType>> _typeMappings;
         private List<MetadataMapping<FieldDesc>> _fieldMappings;
         private List<MetadataMapping<MethodDesc>> _methodMappings;
-        private List<MetadataMapping<MethodDesc>> _stackTraceMappings;
+        private List<StackTraceMapping> _stackTraceMappings;
 
         protected readonly CompilerTypeSystemContext _typeSystemContext;
         protected readonly MetadataBlockingPolicy _blockingPolicy;
@@ -618,20 +617,22 @@ namespace ILCompiler
                                                 out List<MetadataMapping<MetadataType>> typeMappings,
                                                 out List<MetadataMapping<MethodDesc>> methodMappings,
                                                 out List<MetadataMapping<FieldDesc>> fieldMappings,
-                                                out List<MetadataMapping<MethodDesc>> stackTraceMapping);
+                                                out List<StackTraceMapping> stackTraceMapping);
 
-        protected MetadataRecord CreateStackTraceRecord(Metadata.MetadataTransform transform, MethodDesc method)
+        protected StackTraceRecordData CreateStackTraceRecord(Metadata.MetadataTransform transform, MethodDesc method)
         {
             // In the metadata, we only represent the generic definition
             MethodDesc methodToGenerateMetadataFor = method.GetTypicalMethodDefinition();
-            MetadataRecord record = transform.HandleQualifiedMethod(methodToGenerateMetadataFor);
 
-            // If we're generating a MemberReference to a method on a generic type, the owning type
+            ConstantStringValue name = (ConstantStringValue)methodToGenerateMetadataFor.Name;
+            MetadataRecord signature = transform.HandleMethodSignature(methodToGenerateMetadataFor.Signature);
+            MetadataRecord owningType = transform.HandleType(methodToGenerateMetadataFor.OwningType);
+
+            // If we're generating record for a method on a generic type, the owning type
             // should appear as if instantiated over its formals
             TypeDesc owningTypeToGenerateMetadataFor = methodToGenerateMetadataFor.OwningType;
             if (owningTypeToGenerateMetadataFor.HasInstantiation
-                && record is MemberReference memberRefRecord
-                && memberRefRecord.Parent is TypeReference)
+                && owningType is TypeReference)
             {
                 List<MetadataRecord> genericArgs = new List<MetadataRecord>();
                 foreach (Internal.TypeSystem.Ecma.EcmaGenericParameter genericParam in owningTypeToGenerateMetadataFor.Instantiation)
@@ -642,36 +643,32 @@ namespace ILCompiler
                     });
                 }
 
-                memberRefRecord.Parent = new TypeSpecification
+                owningType = new TypeSpecification
                 {
                     Signature = new TypeInstantiationSignature
                     {
-                        GenericType = memberRefRecord.Parent,
+                        GenericType = owningType,
                         GenericTypeArguments = genericArgs,
                     }
                 };
             }
 
-            // As a twist, instantiated generic methods appear as if instantiated over their formals.
+            // Generate metadata for the method instantiation arguments
+            ConstantStringArray methodInst;
             if (methodToGenerateMetadataFor.HasInstantiation)
             {
-                var methodInst = new MethodInstantiation
-                {
-                    Method = record,
-                };
-                methodInst.GenericTypeArguments.Capacity = methodToGenerateMetadataFor.Instantiation.Length;
+                methodInst = new ConstantStringArray();
                 foreach (Internal.TypeSystem.Ecma.EcmaGenericParameter typeArgument in methodToGenerateMetadataFor.Instantiation)
                 {
-                    var genericParam = new TypeReference
-                    {
-                        TypeName = (ConstantStringValue)typeArgument.Name,
-                    };
-                    methodInst.GenericTypeArguments.Add(genericParam);
+                    methodInst.Value.Add((ConstantStringValue)typeArgument.Name);
                 }
-                record = methodInst;
+            }
+            else
+            {
+                methodInst = null;
             }
 
-            return record;
+            return new StackTraceRecordData(method, owningType, signature, name, methodInst);
         }
 
         /// <summary>
@@ -703,7 +700,7 @@ namespace ILCompiler
             return _fieldMappings;
         }
 
-        public IEnumerable<MetadataMapping<MethodDesc>> GetStackTraceMapping(NodeFactory factory)
+        public IEnumerable<StackTraceMapping> GetStackTraceMapping(NodeFactory factory)
         {
             EnsureMetadataGenerated(factory);
             return _stackTraceMappings;
@@ -937,16 +934,39 @@ namespace ILCompiler
         }
     }
 
-    public struct MetadataMapping<TEntity>
+    public readonly struct MetadataMapping<TEntity>
     {
         public readonly TEntity Entity;
         public readonly int MetadataHandle;
 
         public MetadataMapping(TEntity entity, int metadataHandle)
-        {
-            Entity = entity;
-            MetadataHandle = metadataHandle;
-        }
+            => (Entity, MetadataHandle) = (entity, metadataHandle);
+    }
+
+    public readonly struct StackTraceMapping
+    {
+        public readonly MethodDesc Method;
+        public readonly int OwningTypeHandle;
+        public readonly int MethodSignatureHandle;
+        public readonly int MethodNameHandle;
+        public readonly int MethodInstantiationArgumentCollectionHandle;
+
+        public StackTraceMapping(MethodDesc method, int owningTypeHandle, int methodSignatureHandle, int methodNameHandle, int methodInstantiationArgumentCollectionHandle)
+            => (Method, OwningTypeHandle, MethodSignatureHandle, MethodNameHandle, MethodInstantiationArgumentCollectionHandle)
+            = (method, owningTypeHandle, methodSignatureHandle, methodNameHandle, methodInstantiationArgumentCollectionHandle);
+    }
+
+    public readonly struct StackTraceRecordData
+    {
+        public readonly MethodDesc Method;
+        public readonly MetadataRecord OwningType;
+        public readonly MetadataRecord MethodSignature;
+        public readonly MetadataRecord MethodName;
+        public readonly MetadataRecord MethodInstantiationArgumentCollection;
+
+        public StackTraceRecordData(MethodDesc method, MetadataRecord owningType, MetadataRecord methodSignature, MetadataRecord methodName, MetadataRecord methodInstantiationArgumentCollection)
+            => (Method, OwningType, MethodSignature, MethodName, MethodInstantiationArgumentCollection)
+            = (method, owningType, methodSignature, methodName, methodInstantiationArgumentCollection);
     }
 
     [Flags]
index 39d06a4..34c8c43 100644 (file)
@@ -225,7 +225,7 @@ namespace ILCompiler
             out List<MetadataMapping<MetadataType>> typeMappings,
             out List<MetadataMapping<MethodDesc>> methodMappings,
             out List<MetadataMapping<FieldDesc>> fieldMappings,
-            out List<MetadataMapping<MethodDesc>> stackTraceMapping)
+            out List<StackTraceMapping> stackTraceMapping)
         {
             ComputeMetadata(new GeneratedTypesAndCodeMetadataPolicy(_blockingPolicy, factory),
                 factory, out metadataBlob, out typeMappings, out methodMappings, out fieldMappings, out stackTraceMapping);
index 9d537f7..8a73f9d 100644 (file)
     <Compile Include="..\..\Common\Internal\Runtime\RuntimeConstants.cs">
       <Link>Common\RuntimeConstants.cs</Link>
     </Compile>
+    <Compile Include="..\..\Common\Internal\Runtime\StackTraceData.cs">
+      <Link>Common\StackTraceData.cs</Link>
+    </Compile>
     <Compile Include="..\..\Common\Internal\Runtime\UniversalGenericParameterLayout.cs">
       <Link>Common\UniversalGenericParameterLayout.cs</Link>
     </Compile>