1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System;
6 using System.IO;
7 using System.Collections.Generic;
8 using System.Text;
9 
10 using Internal.IL.Stubs;
11 using Internal.TypeSystem;
12 using Internal.TypeSystem.Ecma;
13 using Internal.Metadata.NativeFormat.Writer;
14 
15 using ILCompiler.Metadata;
16 using ILCompiler.DependencyAnalysis;
17 
18 using Debug = System.Diagnostics.Debug;
19 
20 namespace ILCompiler
21 {
22     /// <summary>
23     /// Base class for metadata managers that generate metadata blobs.
24     /// </summary>
25     public abstract class GeneratingMetadataManager : MetadataManager
26     {
27         protected readonly string _metadataLogFile;
28         protected readonly StackTraceEmissionPolicy _stackTraceEmissionPolicy;
29         private readonly Dictionary<DynamicInvokeMethodSignature, MethodDesc> _dynamicInvokeThunks;
30         private readonly ModuleDesc _generatedAssembly;
31 
GeneratingMetadataManager(ModuleDesc generatedAssembly, CompilerTypeSystemContext typeSystemContext, MetadataBlockingPolicy blockingPolicy, string logFile, StackTraceEmissionPolicy stackTracePolicy)32         public GeneratingMetadataManager(ModuleDesc generatedAssembly, CompilerTypeSystemContext typeSystemContext, MetadataBlockingPolicy blockingPolicy, string logFile, StackTraceEmissionPolicy stackTracePolicy)
33             : base(typeSystemContext, blockingPolicy)
34         {
35             _metadataLogFile = logFile;
36             _stackTraceEmissionPolicy = stackTracePolicy;
37             _generatedAssembly = generatedAssembly;
38 
39             if (DynamicInvokeMethodThunk.SupportsThunks(typeSystemContext))
40             {
41                 _dynamicInvokeThunks = new Dictionary<DynamicInvokeMethodSignature, MethodDesc>();
42             }
43         }
44 
WillUseMetadataTokenToReferenceMethod(MethodDesc method)45         public sealed override bool WillUseMetadataTokenToReferenceMethod(MethodDesc method)
46         {
47             return (GetMetadataCategory(method) & MetadataCategory.Description) != 0;
48         }
49 
WillUseMetadataTokenToReferenceField(FieldDesc field)50         public sealed override bool WillUseMetadataTokenToReferenceField(FieldDesc field)
51         {
52             return (GetMetadataCategory(field) & MetadataCategory.Description) != 0;
53         }
54 
55         protected void ComputeMetadata<TPolicy>(
56             TPolicy policy,
57             NodeFactory factory,
58             out byte[] metadataBlob,
59             out List<MetadataMapping<MetadataType>> typeMappings,
60             out List<MetadataMapping<MethodDesc>> methodMappings,
61             out List<MetadataMapping<FieldDesc>> fieldMappings,
62             out List<MetadataMapping<MethodDesc>> stackTraceMapping) where TPolicy : struct, IMetadataPolicy
63         {
64             var transformed = MetadataTransform.Run(policy, GetCompilationModulesWithMetadata());
65             MetadataTransform transform = transformed.Transform;
66 
67             // TODO: DeveloperExperienceMode: Use transformed.Transform.HandleType() to generate
68             //       TypeReference records for _typeDefinitionsGenerated that don't have metadata.
69             //       (To be used in MissingMetadataException messages)
70 
71             // Generate metadata blob
72             var writer = new MetadataWriter();
writer.ScopeDefinitions.AddRangeILCompiler.GeneratingMetadataManager.IMetadataPolicy73             writer.ScopeDefinitions.AddRange(transformed.Scopes);
74 
75             // Generate entries in the blob for methods that will be necessary for stack trace purposes.
76             var stackTraceRecords = new List<KeyValuePair<MethodDesc, MetadataRecord>>();
77             foreach (var methodBody in GetCompiledMethodBodies())
78             {
79                 MethodDesc method = methodBody.Method;
80 
81                 MethodDesc typicalMethod = method.GetTypicalMethodDefinition();
82 
83                 // Methods that will end up in the reflection invoke table should not have an entry in stack trace table
84                 // We'll try looking them up in reflection data at runtime.
85                 if (transformed.GetTransformedMethodDefinition(typicalMethod) != null &&
86                     ShouldMethodBeInInvokeMap(method) &&
87                     (GetMetadataCategory(method) & MetadataCategory.RuntimeMapping) != 0)
88                     continue;
89 
90                 if (!_stackTraceEmissionPolicy.ShouldIncludeMethod(method))
91                     continue;
92 
93                 MetadataRecord record = transform.HandleQualifiedMethod(typicalMethod);
94 
95                 // As a twist, instantiated generic methods appear as if instantiated over their formals.
96                 if (typicalMethod.HasInstantiation)
97                 {
98                     var methodInst = new MethodInstantiation
99                     {
100                         Method = record,
101                     };
102                     methodInst.GenericTypeArguments.Capacity = typicalMethod.Instantiation.Length;
103                     foreach (EcmaGenericParameter typeArgument in typicalMethod.Instantiation)
104                     {
105                         var genericParam = new TypeReference
106                         {
107                             TypeName = (ConstantStringValue)typeArgument.Name,
108                         };
109                         methodInst.GenericTypeArguments.Add(genericParam);
110                     }
111                     record = methodInst;
112                 }
113 
114                 stackTraceRecords.Add(new KeyValuePair<MethodDesc, MetadataRecord>(
115                     method,
116                     record));
117 
118                 writer.AdditionalRootRecords.Add(record);
119             }
120 
121             var ms = new MemoryStream();
122 
123             // .NET metadata is UTF-16 and UTF-16 contains code points that don't translate to UTF-8.
124             var noThrowUtf8Encoding = new UTF8Encoding(false, false);
125 
126             using (var logWriter = _metadataLogFile != null ? new StreamWriter(File.Open(_metadataLogFile, FileMode.Create, FileAccess.Write, FileShare.Read), noThrowUtf8Encoding) : null)
127             {
128                 writer.LogWriter = logWriter;
129                 writer.Write(ms);
130             }
131 
132             metadataBlob = ms.ToArray();
133 
134             typeMappings = new List<MetadataMapping<MetadataType>>();
135             methodMappings = new List<MetadataMapping<MethodDesc>>();
136             fieldMappings = new List<MetadataMapping<FieldDesc>>();
137             stackTraceMapping = new List<MetadataMapping<MethodDesc>>();
138 
139             // Generate type definition mappings
140             foreach (var type in factory.MetadataManager.GetTypesWithEETypes())
141             {
142                 MetadataType definition = type.IsTypeDefinition ? type as MetadataType : null;
143                 if (definition == null)
144                     continue;
145 
146                 MetadataRecord record = transformed.GetTransformedTypeDefinition(definition);
147 
148                 // Reflection requires that we maintain type identity. Even if we only generated a TypeReference record,
149                 // if there is an EEType for it, we also need a mapping table entry for it.
150                 if (record == null)
151                     record = transformed.GetTransformedTypeReference(definition);
152 
153                 if (record != null)
154                     typeMappings.Add(new MetadataMapping<MetadataType>(definition, writer.GetRecordHandle(record)));
155             }
156 
157             foreach (var method in GetCompiledMethods())
158             {
159                 if (method.IsCanonicalMethod(CanonicalFormKind.Specific))
160                 {
161                     // Canonical methods are not interesting.
162                     continue;
163                 }
164 
165                 if (IsReflectionBlocked(method.Instantiation) || IsReflectionBlocked(method.OwningType.Instantiation))
166                     continue;
167 
168                 if ((GetMetadataCategory(method) & MetadataCategory.RuntimeMapping) == 0)
169                     continue;
170 
171                 MetadataRecord record = transformed.GetTransformedMethodDefinition(method.GetTypicalMethodDefinition());
172 
173                 if (record != null)
174                     methodMappings.Add(new MetadataMapping<MethodDesc>(method, writer.GetRecordHandle(record)));
175             }
176 
177             foreach (var field in GetFieldsWithRuntimeMapping())
178             {
179                 Field record = transformed.GetTransformedFieldDefinition(field.GetTypicalFieldDefinition());
180                 if (record != null)
181                     fieldMappings.Add(new MetadataMapping<FieldDesc>(field, writer.GetRecordHandle(record)));
182             }
183 
184             // Generate stack trace metadata mapping
185             foreach (var stackTraceRecord in stackTraceRecords)
186             {
187                 stackTraceMapping.Add(new MetadataMapping<MethodDesc>(stackTraceRecord.Key, writer.GetRecordHandle(stackTraceRecord.Value)));
188             }
189         }
190 
191         /// <summary>
192         /// Gets a list of fields that got "compiled" and are eligible for a runtime mapping.
193         /// </summary>
194         /// <returns></returns>
GetFieldsWithRuntimeMapping()195         protected abstract IEnumerable<FieldDesc> GetFieldsWithRuntimeMapping();
196 
197         /// <summary>
198         /// Is there a reflection invoke stub for a method that is invokable?
199         /// </summary>
HasReflectionInvokeStubForInvokableMethod(MethodDesc method)200         public sealed override bool HasReflectionInvokeStubForInvokableMethod(MethodDesc method)
201         {
202             Debug.Assert(IsReflectionInvokable(method));
203 
204             if (_dynamicInvokeThunks == null)
205                 return false;
206 
207             // Place an upper limit on how many parameters a method can have to still get a static stub.
208             // From the past experience, methods taking 8000+ parameters get a stub that can hit various limitations
209             // in the codegen. On Project N, we were limited to 256 parameters because of MDIL limitations.
210             // We don't have such limitations here, but it's a nice round number.
211             // Reflection invoke will still work, but will go through the calling convention converter.
212 
213             return method.Signature.Length <= 256;
214         }
215 
216         /// <summary>
217         /// Gets a stub that can be used to reflection-invoke a method with a given signature.
218         /// </summary>
GetCanonicalReflectionInvokeStub(MethodDesc method)219         public sealed override MethodDesc GetCanonicalReflectionInvokeStub(MethodDesc method)
220         {
221             TypeSystemContext context = method.Context;
222             var sig = method.Signature;
223 
224             // Get a generic method that can be used to invoke method with this shape.
225             MethodDesc thunk;
226             var lookupSig = new DynamicInvokeMethodSignature(sig);
227             if (!_dynamicInvokeThunks.TryGetValue(lookupSig, out thunk))
228             {
229                 thunk = new DynamicInvokeMethodThunk(_generatedAssembly.GetGlobalModuleType(), lookupSig);
230                 _dynamicInvokeThunks.Add(lookupSig, thunk);
231             }
232 
233             return InstantiateCanonicalDynamicInvokeMethodForMethod(thunk, method);
234         }
235     }
236 }
237