A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using Mono.Cecil;
4using Mono.Cecil.Cil;
5using Mono.Cecil.Rocks;
6using Unity.CompilationPipeline.Common.ILPostProcessing;
7using Unity.Jobs.LowLevel.Unsafe;
8using MethodAttributes = Mono.Cecil.MethodAttributes;
9using MethodBody = Mono.Cecil.Cil.MethodBody;
10using TypeAttributes = Mono.Cecil.TypeAttributes;
11
12namespace Unity.Jobs.CodeGen
13{
14 internal partial class JobsILPostProcessor : ILPostProcessor
15 {
16 private static readonly string ProducerAttributeName = typeof(JobProducerTypeAttribute).FullName;
17 private static readonly string RegisterGenericJobTypeAttributeName = typeof(RegisterGenericJobTypeAttribute).FullName;
18
19 public static MethodReference AttributeConstructorReferenceFor(Type attributeType, ModuleDefinition module)
20 {
21 return module.ImportReference(attributeType.GetConstructor(Array.Empty<Type>()));
22 }
23
24 private TypeReference LaunderTypeRef(TypeReference r_)
25 {
26 ModuleDefinition mod = AssemblyDefinition.MainModule;
27
28 TypeDefinition def = r_.Resolve();
29
30 TypeReference result;
31
32 if (r_ is GenericInstanceType git)
33 {
34 var gt = new GenericInstanceType(LaunderTypeRef(def));
35
36 foreach (var gp in git.GenericParameters)
37 {
38 gt.GenericParameters.Add(gp);
39 }
40
41 foreach (var ga in git.GenericArguments)
42 {
43 gt.GenericArguments.Add(LaunderTypeRef(ga));
44 }
45
46 result = gt;
47
48 }
49 else
50 {
51 result = new TypeReference(def.Namespace, def.Name, def.Module, def.Scope, def.IsValueType);
52
53 if (def.DeclaringType != null)
54 {
55 result.DeclaringType = LaunderTypeRef(def.DeclaringType);
56 }
57 }
58
59 return mod.ImportReference(result);
60 }
61
62 // http://www.isthe.com/chongo/src/fnv/hash_64a.c
63 static ulong StableHash_FNV1A64(string text)
64 {
65 ulong result = 14695981039346656037;
66 foreach (var c in text)
67 {
68 result = 1099511628211 * (result ^ (byte)(c & 255));
69 result = 1099511628211 * (result ^ (byte)(c >> 8));
70 }
71 return result;
72 }
73
74 bool PostProcessImpl()
75 {
76 bool anythingChanged = false;
77
78 var asmDef = AssemblyDefinition;
79 var funcDef = new MethodDefinition("CreateJobReflectionData",
80 MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig,
81 asmDef.MainModule.ImportReference(typeof(void)));
82
83 // This must use a stable hash code function (do not using string.GetHashCode)
84 var autoClassName = $"__JobReflectionRegistrationOutput__{StableHash_FNV1A64(asmDef.FullName)}";
85
86 funcDef.Body.InitLocals = false;
87
88 var classDef = new TypeDefinition("", autoClassName, TypeAttributes.Class, asmDef.MainModule.ImportReference(typeof(object)));
89 classDef.IsBeforeFieldInit = false;
90 classDef.CustomAttributes.Add(new CustomAttribute(AttributeConstructorReferenceFor(typeof(DOTSCompilerGeneratedAttribute), asmDef.MainModule)));
91 classDef.Methods.Add(funcDef);
92
93 var body = funcDef.Body;
94 var processor = body.GetILProcessor();
95
96 // Setup instructions used for try/catch wrapping all earlyinit calls
97 // for this assembly's job types
98 var workStartOp = processor.Create(OpCodes.Nop);
99 var workDoneOp = Instruction.Create(OpCodes.Nop);
100 var handler = Instruction.Create(OpCodes.Nop);
101 var landingPad = Instruction.Create(OpCodes.Nop);
102
103 processor.Append(workStartOp);
104
105 var genericJobs = new List<TypeReference>();
106 var visited = new HashSet<string>();
107
108 foreach (var attr in asmDef.CustomAttributes)
109 {
110 if (attr.AttributeType.FullName != RegisterGenericJobTypeAttributeName)
111 continue;
112
113 var typeRef = (TypeReference)attr.ConstructorArguments[0].Value;
114 var openType = typeRef.Resolve();
115
116 if (!typeRef.IsGenericInstance || !openType.IsValueType)
117 {
118 DiagnosticMessages.Add(UserError.DC3001(openType));
119 continue;
120 }
121
122 genericJobs.Add(typeRef);
123 visited.Add(typeRef.FullName);
124 }
125
126 CollectGenericTypeInstances(AssemblyDefinition, genericJobs, visited);
127
128 foreach (var t in asmDef.MainModule.Types)
129 {
130 anythingChanged |= VisitJobStructs(t, processor, body);
131 }
132
133 foreach (var t in genericJobs)
134 {
135 anythingChanged |= VisitJobStructs(t, processor, body);
136 }
137
138 // Now that we have generated all reflection info
139 // finish wrapping the ops in a try catch now
140 var lastWorkOp = processor.Body.Instructions[processor.Body.Instructions.Count-1];
141 processor.Append(handler);
142
143 var earlyInitHelpersDef = asmDef.MainModule.ImportReference(typeof(EarlyInitHelpers)).Resolve();
144 MethodDefinition jobReflectionDataCreationFailedDef = null;
145 foreach (var method in earlyInitHelpersDef.Methods)
146 {
147 if (method.Name == nameof(EarlyInitHelpers.JobReflectionDataCreationFailed))
148 {
149 jobReflectionDataCreationFailedDef = method;
150 break;
151 }
152 }
153
154 var errorHandler = asmDef.MainModule.ImportReference(jobReflectionDataCreationFailedDef);
155 processor.Append(Instruction.Create(OpCodes.Call, errorHandler));
156 processor.Append(landingPad);
157
158 var leaveSuccess = Instruction.Create(OpCodes.Leave, landingPad);
159 var leaveFail = Instruction.Create(OpCodes.Leave, landingPad);
160 processor.InsertAfter(lastWorkOp, leaveSuccess);
161 processor.InsertBefore(landingPad, leaveFail);
162
163 var exc = new ExceptionHandler(ExceptionHandlerType.Catch);
164 exc.TryStart = workStartOp;
165 exc.TryEnd = leaveSuccess.Next;
166 exc.HandlerStart = handler;
167 exc.HandlerEnd = leaveFail.Next;
168 exc.CatchType = asmDef.MainModule.ImportReference(typeof(Exception));
169 body.ExceptionHandlers.Add(exc);
170
171 processor.Emit(OpCodes.Ret);
172
173 if (anythingChanged)
174 {
175 var ctorFuncDef = new MethodDefinition("EarlyInit", MethodAttributes.Static | MethodAttributes.Public | MethodAttributes.HideBySig, asmDef.MainModule.ImportReference(typeof(void)));
176
177 if (!Defines.Contains("UNITY_EDITOR"))
178 {
179 // Needs to run automatically in the player, but we need to
180 // exclude this attribute when building for the editor, or
181 // it will re-run the registration for every enter play mode.
182 var loadTypeEnumType = asmDef.MainModule.ImportReference(typeof(UnityEngine.RuntimeInitializeLoadType));
183 var attributeCtor = asmDef.MainModule.ImportReference(typeof(UnityEngine.RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new[] { typeof(UnityEngine.RuntimeInitializeLoadType) }));
184 var attribute = new CustomAttribute(attributeCtor);
185 attribute.ConstructorArguments.Add(new CustomAttributeArgument(loadTypeEnumType, UnityEngine.RuntimeInitializeLoadType.AfterAssembliesLoaded));
186 ctorFuncDef.CustomAttributes.Add(attribute);
187 }
188 else
189 {
190 // Needs to run automatically in the editor.
191 var attributeCtor2 = asmDef.MainModule.ImportReference(typeof(UnityEditor.InitializeOnLoadMethodAttribute).GetConstructor(Type.EmptyTypes));
192 ctorFuncDef.CustomAttributes.Add(new CustomAttribute(attributeCtor2));
193 }
194
195 ctorFuncDef.Body.InitLocals = false;
196
197 var p = ctorFuncDef.Body.GetILProcessor();
198
199 p.Emit(OpCodes.Call, funcDef);
200 p.Emit(OpCodes.Ret);
201
202 classDef.Methods.Add(ctorFuncDef);
203
204 asmDef.MainModule.Types.Add(classDef);
205 }
206
207 return anythingChanged;
208 }
209
210 private bool VisitJobStructInterfaces(TypeReference jobTypeRef, TypeDefinition jobType, TypeDefinition currentType, ILProcessor processor, MethodBody body)
211 {
212 bool didAnything = false;
213
214 if (currentType.HasInterfaces && jobType.IsValueType)
215 {
216 foreach (var iface in currentType.Interfaces)
217 {
218 var idef = iface.InterfaceType.CheckedResolve();
219
220 foreach (var attr in idef.CustomAttributes)
221 {
222 if (attr.AttributeType.FullName == ProducerAttributeName)
223 {
224 var producerRef = (TypeReference)attr.ConstructorArguments[0].Value;
225 var launderedType = LaunderTypeRef(jobTypeRef);
226 didAnything |= GenerateCalls(producerRef, launderedType, body, processor);
227 }
228
229 if (currentType.IsInterface)
230 {
231 // Generic jobs need to be either reference in fully closed form, or registered explicitly with an attribute.
232 if (iface.InterfaceType.GenericParameters.Count == 0)
233 didAnything |= VisitJobStructInterfaces(jobTypeRef, jobType, idef, processor, body);
234 }
235 }
236 }
237 }
238
239 foreach (var nestedType in currentType.NestedTypes)
240 {
241 didAnything |= VisitJobStructs(nestedType, processor, body);
242 }
243
244 return didAnything;
245 }
246
247 private bool VisitJobStructs(TypeReference t, ILProcessor processor, MethodBody body)
248 {
249 if (t.GenericParameters.Count > 0)
250 {
251 // Generic jobs need to be either reference in fully closed form, or registered explicitly with an attribute.
252 return false;
253 }
254
255 var rt = t.CheckedResolve();
256
257 return VisitJobStructInterfaces(t, rt, rt, processor, body);
258 }
259
260 private bool GenerateCalls(TypeReference producerRef, TypeReference jobStructType, MethodBody body, ILProcessor processor)
261 {
262 try
263 {
264 var carrierType = producerRef.CheckedResolve();
265 MethodDefinition methodToCall = null;
266 while (carrierType != null)
267 {
268 methodToCall = null;
269 foreach (var method in carrierType.GetMethods())
270 {
271 if(method.IsStatic && method.IsPublic && method.Parameters.Count == 0 && method.Name == "EarlyJobInit")
272 {
273 methodToCall = method;
274 break;
275 }
276 }
277
278 if (methodToCall != null)
279 break;
280
281 carrierType = carrierType.DeclaringType;
282 }
283
284 // Legacy jobs lazy initialize.
285 if (methodToCall == null)
286 return false;
287
288 var asm = AssemblyDefinition.MainModule;
289 var mref = asm.ImportReference(asm.ImportReference(methodToCall).MakeGenericInstanceMethod(jobStructType));
290 processor.Append(Instruction.Create(OpCodes.Call, mref));
291
292 return true;
293 }
294 catch (Exception ex)
295 {
296 DiagnosticMessages.Add(InternalCompilerError.DCICE300(producerRef, jobStructType, ex));
297 }
298
299 return false;
300 }
301
302 private static void CollectGenericTypeInstances(AssemblyDefinition assembly, List<TypeReference> types, HashSet<string> visited)
303 {
304 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
305 // WARNING: THIS CODE HAS TO BE MAINTAINED IN SYNC WITH BurstReflection.cs in Unity.Burst package
306 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
307
308 // From: https://gist.github.com/xoofx/710aaf86e0e8c81649d1261b1ef9590e
309 if (assembly == null) throw new ArgumentNullException(nameof(assembly));
310 const int mdMaxCount = 1 << 24;
311 foreach (var module in assembly.Modules)
312 {
313 for (int i = 1; i < mdMaxCount; i++)
314 {
315 // Token base id for TypeSpec
316 const int mdTypeSpec = 0x1B000000;
317 var token = module.LookupToken(mdTypeSpec | i);
318 if (token is GenericInstanceType type)
319 {
320 if (type.IsGenericInstance && !type.ContainsGenericParameter)
321 {
322 CollectGenericTypeInstances(type, types, visited);
323 }
324 } else if (token == null) break;
325 }
326
327 for (int i = 1; i < mdMaxCount; i++)
328 {
329 // Token base id for MethodSpec
330 const int mdMethodSpec = 0x2B000000;
331 var token = module.LookupToken(mdMethodSpec | i);
332 if (token is GenericInstanceMethod method)
333 {
334 foreach (var argType in method.GenericArguments)
335 {
336 if (argType.IsGenericInstance && !argType.ContainsGenericParameter)
337 {
338 CollectGenericTypeInstances(argType, types, visited);
339 }
340 }
341 }
342 else if (token == null) break;
343 }
344
345 for (int i = 1; i < mdMaxCount; i++)
346 {
347 // Token base id for Field
348 const int mdField = 0x04000000;
349 var token = module.LookupToken(mdField | i);
350 if (token is FieldReference field)
351 {
352 var fieldType = field.FieldType;
353 if (fieldType.IsGenericInstance && !fieldType.ContainsGenericParameter)
354 {
355 CollectGenericTypeInstances(fieldType, types, visited);
356 }
357 }
358 else if (token == null) break;
359 }
360 }
361 }
362
363 private static void CollectGenericTypeInstances(TypeReference type, List<TypeReference> types, HashSet<string> visited)
364 {
365 if (type.IsPrimitive) return;
366 if (!visited.Add(type.FullName)) return;
367
368 // Add only concrete types
369 if (type.IsGenericInstance && !type.ContainsGenericParameter)
370 {
371 types.Add(type);
372 }
373
374 // Collect recursively generic type arguments
375 var genericInstanceType = type as GenericInstanceType;
376 if (genericInstanceType != null)
377 {
378 foreach (var genericTypeArgument in genericInstanceType.GenericArguments)
379 {
380 if (!genericTypeArgument.IsPrimitive)
381 {
382 CollectGenericTypeInstances(genericTypeArgument, types, visited);
383 }
384 }
385 }
386 }
387 }
388}
389