first commit

This commit is contained in:
lethanhsonvsp
2025-11-17 15:16:36 +07:00
commit a40d0921eb
17012 changed files with 2652386 additions and 0 deletions

View File

@@ -0,0 +1,374 @@
using System;
using System.IO;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Pdb;
namespace zzzUnity.Burst.CodeGen
{
/// <summary>
/// Provides an assembly resolver with deferred loading and a custom metadata resolver.
/// </summary>
/// <remarks>
/// This class is not thread safe. It needs to be protected outside.
/// </remarks>
#if BURST_COMPILER_SHARED
public
#else
internal
#endif
class AssemblyResolver : BaseAssemblyResolver
{
private readonly ReadingMode _readingMode;
public AssemblyResolver(ReadingMode readingMode = ReadingMode.Deferred)
{
_readingMode = readingMode;
// We remove all setup by Cecil by default (it adds '.' and 'bin')
ClearSearchDirectories();
LoadDebugSymbols = false; // We don't bother loading the symbols by default now, since we use SRM to handle symbols in a more thread safe manner
// this is to maintain compatibility with the patch-assemblies path (see BclApp.cs), used by dots runtime
}
public bool LoadDebugSymbols { get; set; }
protected void ClearSearchDirectories()
{
foreach (var dir in GetSearchDirectories())
{
RemoveSearchDirectory(dir);
}
}
public AssemblyDefinition LoadFromFile(string path)
{
return AssemblyDefinition.ReadAssembly(path, CreateReaderParameters());
}
public AssemblyDefinition LoadFromStream(Stream peStream, Stream pdbStream = null, ISymbolReaderProvider customSymbolReader=null)
{
peStream.Position = 0;
if (pdbStream != null)
{
pdbStream.Position = 0;
}
var readerParameters = CreateReaderParameters();
if (customSymbolReader != null)
{
readerParameters.ReadSymbols = true;
readerParameters.SymbolReaderProvider = customSymbolReader;
}
try
{
readerParameters.SymbolStream = pdbStream;
return AssemblyDefinition.ReadAssembly(peStream, readerParameters);
}
catch
{
readerParameters.ReadSymbols = false;
readerParameters.SymbolStream = null;
peStream.Position = 0;
if (pdbStream != null)
{
pdbStream.Position = 0;
}
return AssemblyDefinition.ReadAssembly(peStream, readerParameters);
}
}
public override AssemblyDefinition Resolve(AssemblyNameReference name)
{
var readerParameters = CreateReaderParameters();
AssemblyDefinition assemblyDefinition;
try
{
assemblyDefinition = Resolve(name, readerParameters);
}
catch (Exception ex)
{
if (readerParameters.ReadSymbols == true)
{
// Attempt to load without symbols
readerParameters.ReadSymbols = false;
assemblyDefinition = Resolve(name, readerParameters);
}
else
{
throw new AssemblyResolutionException(
name,
new Exception($"Failed to resolve assembly '{name}' in directories: {string.Join(Environment.NewLine, GetSearchDirectories())}", ex));
}
}
return assemblyDefinition;
}
public bool TryResolve(AssemblyNameReference name, out AssemblyDefinition assembly)
{
try
{
assembly = Resolve(name);
return true;
}
catch (AssemblyResolutionException)
{
assembly = null;
return false;
}
}
public new void AddSearchDirectory(string directory)
{
if (!GetSearchDirectories().Contains(directory))
{
base.AddSearchDirectory(directory);
}
}
private ReaderParameters CreateReaderParameters()
{
var readerParams = new ReaderParameters
{
InMemory = true,
AssemblyResolver = this,
MetadataResolver = new CustomMetadataResolver(this),
ReadSymbols = LoadDebugSymbols // We no longer use cecil to read symbol information, prefering SRM thread safe methods, so I`m being explicit here in case the default changes
};
if (LoadDebugSymbols)
{
readerParams.SymbolReaderProvider = new CustomSymbolReaderProvider(null);
}
readerParams.ReadingMode = _readingMode;
return readerParams;
}
internal static string NormalizeFilePath(string path)
{
try
{
return Path.GetFullPath(new Uri(path).LocalPath).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
}
catch (Exception ex)
{
throw new Exception($"Could not normalize file path: {path}", ex);
}
}
private class CustomMetadataResolver : MetadataResolver
{
public CustomMetadataResolver(IAssemblyResolver assemblyResolver) : base(assemblyResolver)
{
}
public override MethodDefinition Resolve(MethodReference method)
{
if (method is MethodDefinition methodDef)
{
return methodDef;
}
if (method.GetElementMethod() is MethodDefinition methodDef2)
{
return methodDef2;
}
var result = base.Resolve(method);
// There's a bug in Cecil where if we try to load a module again (usually when compiling for a different target)
// the modreqs on method definition parameters will be lost. Which means that method reference resolutions will
// fail. This checks tries to create a modreq-free version of the method reference (and if there were any modreqs)
// try to resolve that instead.
if (result == null)
{
var modreqStrippedMethod = new MethodReference(method.Name, method.ReturnType, method.DeclaringType);
modreqStrippedMethod.CallingConvention = method.CallingConvention;
modreqStrippedMethod.ExplicitThis = method.ExplicitThis;
modreqStrippedMethod.HasThis = method.HasThis;
foreach (var gparam in method.GenericParameters)
{
modreqStrippedMethod.GenericParameters.Add(gparam);
}
bool hasModreqs = false;
foreach (var param in method.Parameters)
{
if (param.ParameterType is RequiredModifierType modreqType)
{
hasModreqs = true;
var strippedParam = new ParameterDefinition(param.Name, param.Attributes, modreqType.ElementType);
strippedParam.MetadataToken = param.MetadataToken;
strippedParam.IsIn = param.IsIn || modreqType.ModifierType.FullName == "System.Runtime.InteropServices.InAttribute";
strippedParam.IsOut = param.IsOut || modreqType.ModifierType.FullName == "System.Runtime.InteropServices.OutAttribute";
strippedParam.IsOptional = param.IsOptional;
strippedParam.Constant = param.Constant;
strippedParam.HasDefault = param.HasDefault;
strippedParam.MarshalInfo = param.MarshalInfo;
modreqStrippedMethod.Parameters.Add(strippedParam);
}
else
{
modreqStrippedMethod.Parameters.Add(param);
}
}
if (hasModreqs)
{
if (method is GenericInstanceMethod genericMethod)
{
var genericModreqStrippedMethod = new GenericInstanceMethod(modreqStrippedMethod);
foreach (var garg in genericMethod.GenericArguments)
{
genericModreqStrippedMethod.GenericArguments.Add(garg);
}
modreqStrippedMethod = genericModreqStrippedMethod;
}
result = base.Resolve(modreqStrippedMethod);
}
}
return result;
}
}
/// <summary>
/// Custom implementation of <see cref="ISymbolReaderProvider"/> to:
/// - to load pdb/mdb through a MemoryStream to avoid locking the file on the disk
/// - catch any exceptions while loading the symbols and report them back
/// </summary>
private class CustomSymbolReaderProvider : ISymbolReaderProvider
{
private readonly Action<string, Exception> _logException;
public CustomSymbolReaderProvider(Action<string, Exception> logException)
{
_logException = logException;
}
public ISymbolReader GetSymbolReader(ModuleDefinition module, string fileName)
{
if (string.IsNullOrWhiteSpace(fileName)) return null;
string pdbFileName = fileName;
try
{
fileName = NormalizeFilePath(fileName);
pdbFileName = GetPdbFileName(fileName);
if (File.Exists(pdbFileName))
{
var pdbStream = ReadToMemoryStream(pdbFileName);
if (IsPortablePdb(pdbStream))
return new SafeDebugReaderProvider(new PortablePdbReaderProvider().GetSymbolReader(module, pdbStream));
return new SafeDebugReaderProvider(new NativePdbReaderProvider().GetSymbolReader(module, pdbStream));
}
}
catch (Exception ex) when (_logException != null)
{
_logException?.Invoke($"Unable to load symbol `{pdbFileName}`", ex);
return null;
}
return null;
}
private static MemoryStream ReadToMemoryStream(string filename)
{
return new MemoryStream(File.ReadAllBytes(filename));
}
public ISymbolReader GetSymbolReader(ModuleDefinition module, Stream symbolStream)
{
throw new NotSupportedException();
}
private static string GetPdbFileName(string assemblyFileName)
{
return Path.ChangeExtension(assemblyFileName, ".pdb");
}
private static bool IsPortablePdb(Stream stream)
{
if (stream.Length < 4L)
return false;
long position = stream.Position;
try
{
return (int)new BinaryReader(stream).ReadUInt32() == 1112167234;
}
finally
{
stream.Position = position;
}
}
/// <summary>
/// This class is a wrapper around <see cref="ISymbolReader"/> to protect
/// against failure while trying to read debug information in Mono.Cecil
/// </summary>
private class SafeDebugReaderProvider : ISymbolReader
{
private readonly ISymbolReader _reader;
public SafeDebugReaderProvider(ISymbolReader reader)
{
_reader = reader;
}
public void Dispose()
{
try
{
_reader.Dispose();
}
catch
{
// ignored
}
}
public ISymbolWriterProvider GetWriterProvider()
{
// We are not protecting here as we are not suppose to write to PDBs
return _reader.GetWriterProvider();
}
public bool ProcessDebugHeader(ImageDebugHeader header)
{
try
{
return _reader.ProcessDebugHeader(header);
}
catch
{
// ignored
}
return false;
}
public MethodDebugInformation Read(MethodDefinition method)
{
try
{
return _reader.Read(method);
}
catch
{
// ignored
}
return null;
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a1b75159fe183205bba1d83e5e75a734
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
// TODO: Once DOTS has the latest 2022.2 editor merged into their fork, this can be UNITY_2022_2_OR_NEWER!
#if UNITY_2023_1_OR_NEWER
using ILPostProcessorToUse = zzzUnity.Burst.CodeGen.ILPostProcessing;
#else
using ILPostProcessorToUse = zzzUnity.Burst.CodeGen.ILPostProcessingLegacy;
#endif
/// Deliberately named zzzUnity.Burst.CodeGen, as we need to ensure its last in the chain
namespace zzzUnity.Burst.CodeGen
{
/// <summary>
/// Postprocessor used to replace calls from C# to [BurstCompile] functions to direct calls to
/// Burst native code functions without having to go through a C# delegate.
/// </summary>
internal class BurstILPostProcessor : ILPostProcessor
{
private sealed class CachedAssemblyResolver : AssemblyResolver
{
private Dictionary<string, AssemblyDefinition> _cache = new Dictionary<string, AssemblyDefinition>();
private Dictionary<string, string> _knownLocations = new Dictionary<string, string>();
public void RegisterKnownLocation(string path)
{
var k = Path.GetFileNameWithoutExtension(path);
// If an assembly is referenced multiple times, resolve to the first one
if (!_knownLocations.ContainsKey(k))
{
_knownLocations.Add(k, path);
}
}
public override AssemblyDefinition Resolve(AssemblyNameReference name)
{
if (!_cache.TryGetValue(name.FullName, out var definition))
{
if (_knownLocations.TryGetValue(name.Name, out var path))
{
definition = LoadFromFile(path);
}
else
{
definition = base.Resolve(name);
}
_cache.Add(name.FullName, definition);
}
return definition;
}
}
public bool IsDebugging;
public int DebuggingLevel;
private void SetupDebugging()
{
// This can be setup to get more diagnostics
var debuggingStr = Environment.GetEnvironmentVariable("UNITY_BURST_DEBUG");
var debugLevel = 0;
IsDebugging = debuggingStr != null && int.TryParse(debuggingStr, out debugLevel) && debugLevel > 0;
if (IsDebugging)
{
Log("[com.unity.burst] Extra debugging is turned on.");
DebuggingLevel = debugLevel;
}
}
private static SequencePoint FindBestSequencePointFor(MethodDefinition method, Instruction instruction)
{
var sequencePoints = method.DebugInformation?.GetSequencePointMapping().Values.OrderBy(s => s.Offset).ToList();
if (sequencePoints == null || !sequencePoints.Any())
return null;
for (int i = 0; i != sequencePoints.Count-1; i++)
{
if (sequencePoints[i].Offset < instruction.Offset &&
sequencePoints[i + 1].Offset > instruction.Offset)
return sequencePoints[i];
}
return sequencePoints.FirstOrDefault();
}
private static DiagnosticMessage MakeDiagnosticError(MethodDefinition method, Instruction errorLocation, string message)
{
var m = new DiagnosticMessage { DiagnosticType = DiagnosticType.Error };
var sPoint = errorLocation != null ? FindBestSequencePointFor(method, errorLocation) : null;
if (sPoint!=null)
{
m.Column = sPoint.StartColumn;
m.Line = sPoint.StartLine;
m.File = sPoint.Document.Url;
}
m.MessageData = message;
return m;
}
public override unsafe ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
{
var diagnostics = new List<DiagnosticMessage>();
if (!WillProcess(compiledAssembly))
return new ILPostProcessResult(null, diagnostics);
bool wasModified = false;
SetupDebugging();
bool debugging = IsDebugging && DebuggingLevel >= 2;
var inMemoryAssembly = compiledAssembly.InMemoryAssembly;
var peData = inMemoryAssembly.PeData;
var pdbData = inMemoryAssembly.PdbData;
var loader = new CachedAssemblyResolver();
var folders = new HashSet<string>();
var isForEditor = compiledAssembly.Defines?.Contains("UNITY_EDITOR") ?? false;
foreach (var reference in compiledAssembly.References)
{
loader.RegisterKnownLocation(reference);
folders.Add(Path.Combine(Environment.CurrentDirectory, Path.GetDirectoryName(reference)));
}
var folderList = folders.OrderBy(x => x).ToList();
foreach (var folder in folderList)
{
loader.AddSearchDirectory(folder);
}
var clock = Stopwatch.StartNew();
if (debugging)
{
Log($"Start processing assembly {compiledAssembly.Name}, IsForEditor: {isForEditor}, Folders: {string.Join("\n", folderList)}");
}
var ilPostProcessing = new ILPostProcessorToUse(loader, isForEditor,
(m,i,s) => { diagnostics.Add(MakeDiagnosticError(m, i, s)); },
IsDebugging ? Log : (LogDelegate)null, DebuggingLevel);
var functionPointerProcessing = new FunctionPointerInvokeTransform(loader,
(m,i,s) => { diagnostics.Add(MakeDiagnosticError(m, i, s)); },
IsDebugging ? Log : (LogDelegate)null, DebuggingLevel);
try
{
// For IL Post Processing, use the builtin symbol reader provider
var assemblyDefinition = loader.LoadFromStream(new MemoryStream(peData), new MemoryStream(pdbData), new PortablePdbReaderProvider() );
wasModified |= ilPostProcessing.Run(assemblyDefinition);
wasModified |= functionPointerProcessing.Run(assemblyDefinition);
if (wasModified)
{
var peStream = new MemoryStream();
var pdbStream = new MemoryStream();
var writeParameters = new WriterParameters
{
SymbolWriterProvider = new PortablePdbWriterProvider(),
WriteSymbols = true,
SymbolStream = pdbStream
};
assemblyDefinition.Write(peStream, writeParameters);
peStream.Flush();
pdbStream.Flush();
peData = peStream.ToArray();
pdbData = pdbStream.ToArray();
}
}
catch (Exception ex)
{
throw new InvalidOperationException($"Internal compiler error for Burst ILPostProcessor on {compiledAssembly.Name}. Exception: {ex}");
}
if (debugging)
{
Log($"End processing assembly {compiledAssembly.Name} in {clock.Elapsed.TotalMilliseconds}ms.");
}
if (wasModified && !diagnostics.Any(d => d.DiagnosticType == DiagnosticType.Error))
{
return new ILPostProcessResult(new InMemoryAssembly(peData, pdbData), diagnostics);
}
return new ILPostProcessResult(null, diagnostics);
}
private static void Log(string message)
{
Console.WriteLine($"{nameof(BurstILPostProcessor)}: {message}");
}
public override ILPostProcessor GetInstance()
{
return this;
}
public override bool WillProcess(ICompiledAssembly compiledAssembly)
{
return compiledAssembly.References.Any(f => Path.GetFileName(f) == "Unity.Burst.dll");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ddf71e25119f3861b687943b050c9971
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Text;
using Mono.Cecil;
namespace zzzUnity.Burst.CodeGen
{
/// <summary>
/// Provides some Cecil Extensions.
/// </summary>
#if BURST_COMPILER_SHARED
public
#else
internal
#endif
static class CecilExtensions
{
public static void BuildAssemblyQualifiedName(this TypeReference type, StringBuilder builder)
{
if (type == null) throw new ArgumentNullException(nameof(type));
TypeReference elementType;
type.BuildReflectionFullName(builder, out elementType, assemblyQualified: true);
if (!(elementType is GenericParameter))
{
// Recover assembly reference from scope first (e.g for types imported), otherwise from Module.Assembly
var assemblyReference = elementType.Scope as AssemblyNameReference ?? elementType.Module?.Assembly?.Name;
if (assemblyReference != null)
{
builder.Append(", ").Append(assemblyReference);
}
}
}
public static void BuildReflectionFullName(this TypeReference type, StringBuilder builder, bool assemblyQualified)
{
BuildReflectionFullName(type, builder, out _, assemblyQualified);
}
public static void BuildReflectionFullName(this TypeReference type, StringBuilder builder, out TypeReference elementType, bool assemblyQualified)
{
if (type == null) throw new ArgumentNullException(nameof(type));
if (type is PointerType pointerType)
{
pointerType.ElementType.BuildReflectionFullName(builder, out elementType, assemblyQualified);
builder.Append("*");
}
else if (type is PinnedType pinnedType)
{
pinnedType.ElementType.BuildReflectionFullName(builder, out elementType, assemblyQualified);
builder.Append(" pinned");
}
else if (type is ByReferenceType byReferenceType)
{
byReferenceType.ElementType.BuildReflectionFullName(builder, out elementType, assemblyQualified);
builder.Append("&");
}
else if (type is ArrayType arrayType)
{
arrayType.ElementType.BuildReflectionFullName(builder, out elementType, assemblyQualified);
builder.Append("[]");
}
else if (type is GenericParameter genericParameter)
{
elementType = type;
builder.Append(genericParameter.Type == GenericParameterType.Method ? "!!" : "!");
builder.Append(genericParameter.Position);
}
else if (type is FunctionPointerType functionPointerType)
{
elementType = type;
builder.Append("delegate* ");
builder.Append(functionPointerType.CallingConvention switch
{
MethodCallingConvention.Default => "managed",
MethodCallingConvention.Unmanaged => "unmanaged",
MethodCallingConvention.C => "unmanaged[Cdecl]",
MethodCallingConvention.FastCall => "unmanaged[Fastcall]",
MethodCallingConvention.ThisCall => "unmanaged[Thiscall]",
MethodCallingConvention.StdCall => "unmanaged[Stdcall]",
MethodCallingConvention.Generic => "generic",
MethodCallingConvention.VarArg => "vararg",
var conv => $"<unknown calling conv: {(int)conv}>",
});
builder.Append("<");
for (var i = 0; i < functionPointerType.Parameters.Count; i++)
{
var param = functionPointerType.Parameters[i];
param.ParameterType.BuildAssemblyQualifiedName(builder);
builder.Append(", ");
}
functionPointerType.MethodReturnType.ReturnType.BuildAssemblyQualifiedName(builder);
builder.Append(">");
}
else
{
elementType = type;
var types = new List<TypeReference>();
var declaringType = type;
while (declaringType != null)
{
types.Add(declaringType);
declaringType = declaringType.DeclaringType;
}
var baseType = types[types.Count - 1];
if (!string.IsNullOrEmpty(baseType.Namespace))
{
builder.Append(baseType.Namespace);
builder.Append(".");
}
builder.Append(baseType.Name);
for (int i = types.Count - 2; i >= 0; i--)
{
var nestedType = types[i];
builder.Append("+").Append(nestedType.Name);
}
if (elementType is GenericInstanceType genericInstanceType && genericInstanceType.HasGenericArguments)
{
builder.Append("[");
for (var i = 0; i < genericInstanceType.GenericArguments.Count; i++)
{
var genericArgument = genericInstanceType.GenericArguments[i];
if (i > 0)
{
builder.Append(",");
}
if (assemblyQualified)
{
builder.Append("[");
genericArgument.BuildAssemblyQualifiedName(builder);
builder.Append("]");
}
else
{
genericArgument.BuildReflectionFullName(builder, out var _, assemblyQualified: true);
}
}
builder.Append("]");
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f0f1d1ee0d88384188ad6cdcffc5e9d8
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,534 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Burst.Compiler.IL.Syntax;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;
namespace zzzUnity.Burst.CodeGen
{
/// <summary>
/// Transforms a direct invoke on a burst function pointer into an calli, avoiding the need to marshal the delegate back.
/// </summary>
internal class FunctionPointerInvokeTransform
{
private struct CaptureInformation
{
public MethodReference Operand;
public List<Instruction> Captured;
}
private Dictionary<TypeReference, (MethodDefinition method, Instruction instruction)> _needsNativeFunctionPointer;
private Dictionary<MethodDefinition, TypeReference> _needsIl2cppInvoke;
private Dictionary<MethodDefinition, List<CaptureInformation>> _capturedSets;
private MethodDefinition _monoPInvokeAttributeCtorDef;
private MethodDefinition _unmanagedFunctionPointerAttributeCtorDef;
private TypeReference _burstFunctionPointerType;
private TypeReference _burstCompilerType;
private TypeReference _systemType;
private TypeReference _callingConventionType;
private LogDelegate _debugLog;
private int _logLevel;
private AssemblyResolver _loader;
private ErrorDiagnosticDelegate _errorReport;
public readonly static bool enableInvokeAttribute = true;
public readonly static bool enableCalliOptimisation = false; // For now only run the pass on dots player/tiny
public readonly static bool enableUnmangedFunctionPointerInject = true;
public FunctionPointerInvokeTransform(AssemblyResolver loader,ErrorDiagnosticDelegate error, LogDelegate log = null, int logLevel = 0)
{
_loader = loader;
_needsNativeFunctionPointer = new Dictionary<TypeReference, (MethodDefinition, Instruction)>();
_needsIl2cppInvoke = new Dictionary<MethodDefinition, TypeReference>();
_capturedSets = new Dictionary<MethodDefinition, List<CaptureInformation>>();
_monoPInvokeAttributeCtorDef = null;
_unmanagedFunctionPointerAttributeCtorDef = null;
_burstFunctionPointerType = null;
_burstCompilerType = null;
_systemType = null;
_callingConventionType = null;
_debugLog = log;
_logLevel = logLevel;
_errorReport = error;
}
private AssemblyDefinition GetAsmDefinitionFromFile(AssemblyResolver loader, string assemblyName)
{
if (loader.TryResolve(AssemblyNameReference.Parse(assemblyName), out var result))
{
return result;
}
return null;
}
public void Initialize(AssemblyResolver loader, AssemblyDefinition assemblyDefinition, TypeSystem typeSystem)
{
if (_monoPInvokeAttributeCtorDef == null)
{
var burstAssembly = GetAsmDefinitionFromFile(loader, "Unity.Burst");
_burstFunctionPointerType = burstAssembly.MainModule.GetType("Unity.Burst.FunctionPointer`1");
_burstCompilerType = burstAssembly.MainModule.GetType("Unity.Burst.BurstCompiler");
var corLibrary = loader.Resolve(typeSystem.CoreLibrary as AssemblyNameReference);
// If the corLibrary is a redirecting assembly, then the type isn't present in Types
// and GetType() will therefore not find it, so instead we'll have to look it up in ExportedTypes
Func<string, TypeDefinition> getCorLibTy = (name) =>
{
return corLibrary.MainModule.GetType(name) ??
corLibrary.MainModule.ExportedTypes.FirstOrDefault(x => x.FullName == name)?.Resolve();
};
_systemType = getCorLibTy("System.Type"); // Only needed for MonoPInvokeCallback constructor in Unity
if (enableUnmangedFunctionPointerInject)
{
var unmanagedFunctionPointerAttribute = getCorLibTy("System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute");
_callingConventionType = getCorLibTy("System.Runtime.InteropServices.CallingConvention");
_unmanagedFunctionPointerAttributeCtorDef = unmanagedFunctionPointerAttribute.GetConstructors().Single(c => c.Parameters.Count == 1 && c.Parameters[0].ParameterType.MetadataType == _callingConventionType.MetadataType);
}
var asmDef = GetAsmDefinitionFromFile(loader, "UnityEngine.CoreModule");
// bail if we can't find a reference, handled gracefully later
if (asmDef == null)
return;
var monoPInvokeAttribute = asmDef.MainModule.GetType("AOT.MonoPInvokeCallbackAttribute");
_monoPInvokeAttributeCtorDef = monoPInvokeAttribute.GetConstructors().First();
}
}
public bool Run(AssemblyDefinition assemblyDefinition)
{
Initialize(_loader, assemblyDefinition, assemblyDefinition.MainModule.TypeSystem);
var types = assemblyDefinition.MainModule.GetTypes().ToArray();
foreach (var type in types)
{
CollectDelegateInvokesFromType(type);
}
return Finish();
}
public void CollectDelegateInvokesFromType(TypeDefinition type)
{
foreach (var m in type.Methods)
{
if (m.HasBody)
{
CollectDelegateInvokes(m);
}
}
}
private bool ProcessUnmanagedAttributeFixups()
{
if (_unmanagedFunctionPointerAttributeCtorDef == null)
return false;
bool modified = false;
foreach (var kp in _needsNativeFunctionPointer)
{
var delegateType = kp.Key;
var instruction = kp.Value.instruction;
var method = kp.Value.method;
var delegateDef = delegateType.Resolve();
var hasAttributeAlready = delegateDef.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == _unmanagedFunctionPointerAttributeCtorDef.DeclaringType.FullName);
// If there is already an an attribute present
if (hasAttributeAlready!=null)
{
if (hasAttributeAlready.ConstructorArguments.Count==1)
{
var cc = (System.Runtime.InteropServices.CallingConvention)hasAttributeAlready.ConstructorArguments[0].Value;
if (cc == System.Runtime.InteropServices.CallingConvention.Cdecl)
{
if (_logLevel > 2) _debugLog?.Invoke($"UnmanagedAttributeFixups Skipping appending unmanagedFunctionPointerAttribute as already present aand calling convention matches");
}
else
{
// constructor with non cdecl calling convention
_errorReport(method, instruction, $"BurstCompiler.CompileFunctionPointer is only compatible with cdecl calling convention, this delegate type already has `[UnmanagedFunctionPointer(CallingConvention.{ Enum.GetName(typeof(System.Runtime.InteropServices.CallingConvention), cc) })]` please remove the attribute if you wish to use this function with Burst.");
}
}
else
{
// Empty constructor which defaults to Winapi which is incompatable
_errorReport(method, instruction, $"BurstCompiler.CompileFunctionPointer is only compatible with cdecl calling convention, this delegate type already has `[UnmanagedFunctionPointer]` please remove the attribute if you wish to use this function with Burst.");
}
continue;
}
var attribute = new CustomAttribute(delegateType.Module.ImportReference(_unmanagedFunctionPointerAttributeCtorDef));
attribute.ConstructorArguments.Add(new CustomAttributeArgument(delegateType.Module.ImportReference(_callingConventionType), System.Runtime.InteropServices.CallingConvention.Cdecl));
delegateDef.CustomAttributes.Add(attribute);
modified = true;
}
return modified;
}
private bool ProcessIl2cppInvokeFixups()
{
if (_monoPInvokeAttributeCtorDef == null)
return false;
bool modified = false;
foreach (var invokeNeeded in _needsIl2cppInvoke)
{
var declaringType = invokeNeeded.Value;
var implementationMethod = invokeNeeded.Key;
// Unity requires a type parameter for the attributecallback
if (declaringType == null)
{
_debugLog?.Invoke($"FunctionPtrInvoke.LocateFunctionPointerTCreation: Unable to automatically append CallbackAttribute due to missing declaringType for {implementationMethod}");
continue;
}
var attribute = new CustomAttribute(implementationMethod.Module.ImportReference(_monoPInvokeAttributeCtorDef));
attribute.ConstructorArguments.Add(new CustomAttributeArgument(implementationMethod.Module.ImportReference(_systemType), implementationMethod.Module.ImportReference(declaringType)));
implementationMethod.CustomAttributes.Add(attribute);
modified = true;
if (_logLevel > 1) _debugLog?.Invoke($"FunctionPtrInvoke.LocateFunctionPointerTCreation: Added InvokeCallbackAttribute to {implementationMethod}");
}
return modified;
}
private bool ProcessFunctionPointerInvokes()
{
var madeChange = false;
foreach (var capturedData in _capturedSets)
{
var latePatchMethod = capturedData.Key;
var capturedList = capturedData.Value;
latePatchMethod.Body.SimplifyMacros(); // De-optimise short branches, since we will end up inserting instructions
foreach(var capturedInfo in capturedList)
{
var captured = capturedInfo.Captured;
var operand = capturedInfo.Operand;
if (captured.Count!=2)
{
_debugLog?.Invoke($"FunctionPtrInvoke.Finish: expected 2 instructions - Unable to optimise this reference");
continue;
}
if (_logLevel > 1) _debugLog?.Invoke($"FunctionPtrInvoke.Finish:{Environment.NewLine} latePatchMethod:{latePatchMethod}{Environment.NewLine} captureList:{capturedList}{Environment.NewLine} capture0:{captured[0]}{Environment.NewLine} operand:{operand}");
var processor = latePatchMethod.Body.GetILProcessor();
var genericContext = GenericContext.From(operand, operand.DeclaringType);
CallSite callsite;
try
{
callsite = new CallSite(genericContext.Resolve(operand.ReturnType))
{
CallingConvention = MethodCallingConvention.C
};
for (int oo = 0; oo < operand.Parameters.Count; oo++)
{
var param = operand.Parameters[oo];
var ty = genericContext.Resolve(param.ParameterType);
callsite.Parameters.Add(new ParameterDefinition(param.Name, param.Attributes, ty));
}
}
catch (NullReferenceException)
{
_debugLog?.Invoke($"FunctionPtrInvoke.Finish: Failed to resolve the generic context of `{operand}`");
continue;
}
// Make sure everything is in order before we make a change
var originalGetInvoke = captured[0];
if (originalGetInvoke.Operand is MethodReference mmr)
{
var genericMethodDef = mmr.Resolve();
var genericInstanceType = mmr.DeclaringType as GenericInstanceType;
var genericInstanceDef = genericInstanceType.Resolve();
// Locate the correct instance method - we know already at this point we have an instance of Function
MethodReference mr = default;
bool failed = true;
foreach (var m in genericInstanceDef.Methods)
{
if (m.FullName.Contains("get_Value"))
{
mr = m;
failed = false;
break;
}
}
if (failed)
{
_debugLog?.Invoke($"FunctionPtrInvoke.Finish: failed to locate get_Value method on {genericInstanceDef} - Unable to optimise this reference");
continue;
}
var newGenericRef = new MethodReference(mr.Name, mr.ReturnType, genericInstanceType)
{
HasThis = mr.HasThis,
ExplicitThis = mr.ExplicitThis,
CallingConvention = mr.CallingConvention
};
foreach (var param in mr.Parameters)
newGenericRef.Parameters.Add(new ParameterDefinition(param.ParameterType));
foreach (var gparam in mr.GenericParameters)
newGenericRef.GenericParameters.Add(new GenericParameter(gparam.Name, newGenericRef));
var importRef = latePatchMethod.Module.ImportReference(newGenericRef);
var newMethodCall = processor.Create(OpCodes.Call, importRef);
// Replace get_invoke with get_Value - Don't use replace though as if the original call is target of a branch
//the branch doesn't get updated.
originalGetInvoke.OpCode = newMethodCall.OpCode;
originalGetInvoke.Operand = newMethodCall.Operand;
// Add local to capture result
var newLocal = new VariableDefinition(mr.ReturnType);
latePatchMethod.Body.Variables.Add(newLocal);
// Store result of get_Value
var storeInst = processor.Create(OpCodes.Stloc, newLocal);
processor.InsertAfter(originalGetInvoke, storeInst);
// Swap invoke with calli
var calli = processor.Create(OpCodes.Calli, callsite);
// We can use replace here, since we already checked this is in the same Basic Block, and thus can't be target of a branch
processor.Replace(captured[1], calli);
// Insert load local prior to calli
var loadValue = processor.Create(OpCodes.Ldloc, newLocal);
processor.InsertBefore(calli, loadValue);
if (_logLevel > 1) _debugLog?.Invoke($"FunctionPtrInvoke.Finish: Optimised {originalGetInvoke} with {newMethodCall}");
madeChange = true;
}
}
latePatchMethod.Body.OptimizeMacros(); // Re-optimise branches
}
return madeChange;
}
public bool Finish()
{
bool madeChange = false;
if (enableInvokeAttribute)
{
madeChange |= ProcessIl2cppInvokeFixups();
}
if (enableUnmangedFunctionPointerInject)
{
madeChange |= ProcessUnmanagedAttributeFixups();
}
if (enableCalliOptimisation)
{
madeChange |= ProcessFunctionPointerInvokes();
}
return madeChange;
}
private bool IsBurstFunctionPointerMethod(MethodReference methodRef, string method, out GenericInstanceType methodInstance)
{
methodInstance = methodRef?.DeclaringType as GenericInstanceType;
return (methodInstance != null && methodInstance.ElementType.FullName == _burstFunctionPointerType.FullName && methodRef.Name == method);
}
private bool IsBurstCompilerMethod(GenericInstanceMethod methodRef, string method)
{
var methodInstance = methodRef?.DeclaringType as TypeReference;
return (methodInstance != null && methodInstance.FullName == _burstCompilerType.FullName && methodRef.Name == method);
}
private void LocateFunctionPointerTCreation(MethodDefinition m, Instruction i)
{
if (i.OpCode == OpCodes.Call)
{
var genInstMethod = i.Operand as GenericInstanceMethod;
var isBurstCompilerCompileFunctionPointer = IsBurstCompilerMethod(genInstMethod, "CompileFunctionPointer");
var isBurstFunctionPointerGetInvoke = IsBurstFunctionPointerMethod(i.Operand as MethodReference, "get_Invoke", out var instanceType);
if (!(isBurstCompilerCompileFunctionPointer || isBurstFunctionPointerGetInvoke)) return;
if (enableUnmangedFunctionPointerInject)
{
var delegateType = isBurstCompilerCompileFunctionPointer ? genInstMethod.GenericArguments[0].Resolve() : instanceType.GenericArguments[0].Resolve();
// We check for null, since unfortunately it is possible that the call is wrapped inside
//another open delegate and we cannot determine the delegate type
if (delegateType != null && !_needsNativeFunctionPointer.ContainsKey(delegateType))
{
_needsNativeFunctionPointer.Add(delegateType, (m, i));
}
}
// No need to process further if its not a CompileFunctionPointer method
if (!isBurstCompilerCompileFunctionPointer) return;
if (enableInvokeAttribute)
{
// Currently only handles the following pre-pattern (which should cover most common uses)
// ldftn ...
// newobj ...
if (i.Previous?.OpCode != OpCodes.Newobj)
{
_debugLog?.Invoke($"FunctionPtrInvoke.LocateFunctionPointerTCreation: Unable to automatically append CallbackAttribute due to not finding NewObj {i.Previous}");
return;
}
var newObj = i.Previous;
if (newObj.Previous?.OpCode != OpCodes.Ldftn)
{
_debugLog?.Invoke($"FunctionPtrInvoke.LocateFunctionPointerTCreation: Unable to automatically append CallbackAttribute due to not finding LdFtn {newObj.Previous}");
return;
}
var ldFtn = newObj.Previous;
// Determine the delegate type
var methodDefinition = newObj.Operand as MethodDefinition;
var declaringType = methodDefinition?.DeclaringType;
// Fetch the implementation method
var implementationMethod = ldFtn.Operand as MethodDefinition;
var hasInvokeAlready = implementationMethod?.CustomAttributes.FirstOrDefault(x =>
x.AttributeType.Name == _monoPInvokeAttributeCtorDef.DeclaringType.Name);
if (hasInvokeAlready != null)
{
if (_logLevel > 2) _debugLog?.Invoke($"FunctionPtrInvoke.LocateFunctionPointerTCreation: Skipping appending Callback Attribute as already present {hasInvokeAlready}");
return;
}
if (implementationMethod == null)
{
_debugLog?.Invoke($"FunctionPtrInvoke.LocateFunctionPointerTCreation: Unable to automatically append CallbackAttribute due to missing method from {ldFtn} {ldFtn.Operand}");
return;
}
if (implementationMethod.CustomAttributes.FirstOrDefault(x => x.Constructor.DeclaringType.Name == "BurstCompileAttribute") == null)
{
_debugLog?.Invoke($"FunctionPtrInvoke.LocateFunctionPointerTCreation: Unable to automatically append CallbackAttribute due to missing burst attribute from {implementationMethod}");
return;
}
// Need to add the custom attribute
if (!_needsIl2cppInvoke.ContainsKey(implementationMethod))
{
_needsIl2cppInvoke.Add(implementationMethod, declaringType);
}
}
}
}
[Obsolete("Will be removed in a future Burst verison")]
public bool IsInstructionForFunctionPointerInvoke(MethodDefinition m, Instruction i)
{
throw new NotImplementedException();
}
private void CollectDelegateInvokes(MethodDefinition m)
{
if (!(enableCalliOptimisation || enableInvokeAttribute || enableUnmangedFunctionPointerInject))
return;
bool hitGetInvoke = false;
TypeDefinition delegateType = null;
List<Instruction> captured = null;
foreach (var inst in m.Body.Instructions)
{
if (_logLevel > 2) _debugLog?.Invoke($"FunctionPtrInvoke.CollectDelegateInvokes: CurrentInstruction {inst} {inst.Operand}");
// Check for a FunctionPointerT creation
if (enableUnmangedFunctionPointerInject || enableInvokeAttribute)
{
LocateFunctionPointerTCreation(m, inst);
}
if (enableCalliOptimisation)
{
if (!hitGetInvoke)
{
if (inst.OpCode != OpCodes.Call) continue;
if (!IsBurstFunctionPointerMethod(inst.Operand as MethodReference, "get_Invoke", out var methodInstance)) continue;
// At this point we have a call to a FunctionPointer.Invoke
hitGetInvoke = true;
delegateType = methodInstance.GenericArguments[0].Resolve();
captured = new List<Instruction>();
captured.Add(inst); // Capture the get_invoke, we will swap this for get_value and a store to local
}
else
{
if (!(inst.OpCode.FlowControl == FlowControl.Next || inst.OpCode.FlowControl == FlowControl.Call))
{
// Don't perform transform across blocks
hitGetInvoke = false;
}
else
{
if (inst.OpCode == OpCodes.Callvirt)
{
if (inst.Operand is MethodReference mref)
{
var method = mref.Resolve();
if (method.DeclaringType == delegateType)
{
hitGetInvoke = false;
List<CaptureInformation> storage = null;
if (!_capturedSets.TryGetValue(m, out storage))
{
storage = new List<CaptureInformation>();
_capturedSets.Add(m, storage);
}
// Capture the invoke - which we will swap for a load local (stored from the get_value) and a calli
captured.Add(inst);
var captureInfo = new CaptureInformation { Captured = captured, Operand = mref };
if (_logLevel > 1) _debugLog?.Invoke($"FunctionPtrInvoke.CollectDelegateInvokes: captureInfo:{captureInfo}{Environment.NewLine}capture0{captured[0]}");
storage.Add(captureInfo);
}
}
else
{
hitGetInvoke = false;
}
}
}
}
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 95e00b4ae3493f2bb9d61143777414fc
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,301 @@
using System.Diagnostics;
using Mono.Cecil;
using Mono.Cecil.Rocks;
namespace Burst.Compiler.IL.Syntax
{
/// <summary>
/// A generic context contains a mapping between GenericParameter ({T}) and resolved TypeReference (int, float, MyStruct&lt;float&gt;)
/// </summary>
#if BURST_INTERNAL || BURST_COMPILER_SHARED
public
#else
internal
#endif
readonly struct GenericContext
{
private readonly GenericInstanceType _typeContext;
private readonly GenericInstanceMethod _methodContext;
/// <summary>
/// An empty <see cref="GenericContext"/>
/// </summary>
public static readonly GenericContext None = new GenericContext();
/// <summary>
/// Initializes a new instance of the <see cref="GenericContext"/> class.
/// </summary>
/// <param name="genericMethod">The generic method instance.</param>
private GenericContext(GenericInstanceMethod genericMethod, GenericInstanceType genericType)
{
_methodContext = genericMethod;
_typeContext = genericType;
}
/// <summary>
/// Is there no generics in this context?
/// </summary>
/// <returns></returns>
public bool IsEmpty()
{
return _typeContext == null && _methodContext == null;
}
/// <summary>
/// Resolve the generics of the given <see cref="T:Mono.Cecil.MethodReference"/>
/// </summary>
/// <param name="unresolvedMethod"></param>
/// <returns></returns>
public MethodReference Resolve(MethodReference unresolvedMethod)
{
Debug.Assert(unresolvedMethod != null);
// The following code was originally derived from IL2CPP.
var resolvedMethod = unresolvedMethod;
if (IsEmpty())
{
return resolvedMethod;
}
var declaringType = Resolve(unresolvedMethod.DeclaringType);
if (unresolvedMethod is GenericInstanceMethod genericInstanceMethod)
{
resolvedMethod = new MethodReference(unresolvedMethod.Name, unresolvedMethod.ReturnType, declaringType);
foreach (var p in unresolvedMethod.Parameters)
{
resolvedMethod.Parameters.Add(new ParameterDefinition(p.Name, p.Attributes, p.ParameterType));
}
foreach (var gp in genericInstanceMethod.ElementMethod.GenericParameters)
{
resolvedMethod.GenericParameters.Add(new GenericParameter(gp.Name, resolvedMethod));
}
resolvedMethod.HasThis = unresolvedMethod.HasThis;
var m = new GenericInstanceMethod(resolvedMethod);
foreach (var ga in genericInstanceMethod.GenericArguments)
{
m.GenericArguments.Add(Resolve(ga));
}
resolvedMethod = m;
}
else
{
if (unresolvedMethod.HasGenericParameters)
{
var newGenericInstanceMethod = new GenericInstanceMethod(unresolvedMethod);
foreach (var gp in unresolvedMethod.GenericParameters)
{
newGenericInstanceMethod.GenericArguments.Add(Resolve(gp));
}
resolvedMethod = newGenericInstanceMethod;
}
else
{
resolvedMethod = new MethodReference(unresolvedMethod.Name, unresolvedMethod.ReturnType, declaringType);
foreach (var p in unresolvedMethod.Parameters)
{
resolvedMethod.Parameters.Add(new ParameterDefinition(p.Name, p.Attributes, p.ParameterType));
}
resolvedMethod.HasThis = unresolvedMethod.HasThis;
resolvedMethod.MetadataToken = unresolvedMethod.MetadataToken;
}
}
return resolvedMethod;
}
/// <summary>
/// Expands the specified <see cref="T:Mono.Cecil.TypeReference"/> if it is either a <see cref="T:Mono.Cecil.GenericParameter"/> or a partially expanded <see cref="T:Mono.Cecil.GenericInstanceType"/>
/// </summary>
/// <param name="typeReference">The type reference.</param>
/// <returns>TypeReference.</returns>
public TypeReference Resolve(TypeReference typeReference)
{
Debug.Assert(typeReference != null);
if (IsEmpty())
{
return typeReference;
}
switch (typeReference)
{
case GenericParameter genericParam:
Debug.Assert(genericParam.Owner != null);
if (genericParam.Owner.GenericParameterType == GenericParameterType.Type)
{
Debug.Assert(_typeContext != null);
return _typeContext.GenericArguments[genericParam.Position];
}
else
{
Debug.Assert(_methodContext != null);
return _methodContext.GenericArguments[genericParam.Position];
}
case ArrayType arrayType:
return new ArrayType(Resolve(arrayType.ElementType), arrayType.Rank);
case PointerType pointerType:
return Resolve(pointerType.ElementType).MakePointerType();
case PinnedType pinnedType:
return Resolve(pinnedType.ElementType).MakePointerType();
case ByReferenceType byRefType:
return Resolve(byRefType.ElementType).MakeByReferenceType();
case RequiredModifierType requiredModType:
return new RequiredModifierType(requiredModType.ModifierType, Resolve(requiredModType.ElementType));
case OptionalModifierType optionalModType:
return Resolve(optionalModType.ElementType);
}
if (ContainsGenericParameters(typeReference))
{
if (typeReference is GenericInstanceType partialGenericInstance)
{
// TODO: Ideally, we should cache this GenericInstanceType once it has been resolved
var genericInstance = new GenericInstanceType(partialGenericInstance.ElementType);
foreach (var genericArgument in partialGenericInstance.GenericArguments)
{
genericInstance.GenericArguments.Add(Resolve(genericArgument));
}
return genericInstance;
}
else
{
// Sometimes we can have a TypeDefinition with HasGenericParameters false, but GenericParameters.Count > 0
var typeDefinition = typeReference as TypeDefinition;
if (typeDefinition?.GenericParameters.Count > 0)
{
var genericInstance = new GenericInstanceType(typeDefinition);
foreach (var genericArgument in typeDefinition.GenericParameters)
{
genericInstance.GenericArguments.Add(Resolve(genericArgument));
}
return genericInstance;
}
}
}
return typeReference;
}
/// <summary>
/// If the given type is a reference or pointer type, the underlying type is returned
/// </summary>
/// <param name="typeReference"></param>
/// <returns></returns>
public static TypeReference GetTypeReferenceForPointerOrReference(TypeReference typeReference)
{
while (true)
{
switch (typeReference)
{
case PointerType pointerType:
typeReference = pointerType.ElementType;
break;
case ByReferenceType byRefType:
typeReference = byRefType.ElementType;
break;
default:
return typeReference;
}
}
}
/// <summary>
/// Create <see cref="GenericContext"/> from a <see cref="T:Mono.Cecil.TypeReference"/>
/// </summary>
/// <param name="typeReference"></param>
/// <returns></returns>
public static GenericContext From(TypeReference typeReference)
{
Debug.Assert(typeReference != null);
if (typeReference is PinnedType pinnedType)
{
typeReference = pinnedType.ElementType;
}
typeReference = GetTypeReferenceForPointerOrReference(typeReference);
if (typeReference is ArrayType arrayType)
{
typeReference = arrayType.ElementType;
}
return new GenericContext(null, typeReference as GenericInstanceType);
}
/// <summary>
/// Create <see cref="GenericContext"/> from a <see cref="T:Mono.Cecil.MethodReference"/> and a <see cref="T:Mono.Cecil.TypeReference"/>
/// </summary>
/// <param name="methodReference"></param>
/// <param name="typeReference"></param>
/// <returns></returns>
public static GenericContext From(MethodReference methodReference, TypeReference typeReference)
{
Debug.Assert(methodReference != null);
Debug.Assert(typeReference != null);
typeReference = GetTypeReferenceForPointerOrReference(typeReference);
return new GenericContext(methodReference as GenericInstanceMethod, typeReference as GenericInstanceType);
}
/// <summary>
/// Checks if the specified TypeReference contains generic parameters that need type expansion
/// </summary>
/// <param name="typeReference">The type reference.</param>
/// <returns><c>true</c> if the specified TypeReference contains generic arguments that need type expansion, <c>false</c> otherwise.</returns>
public static bool ContainsGenericParameters(TypeReference typeReference)
{
switch (typeReference)
{
case GenericParameter genericParam:
return true;
case ArrayType arrayType:
return ContainsGenericParameters(arrayType.ElementType);
case PointerType pointerType:
return ContainsGenericParameters(pointerType.ElementType);
case PinnedType pinnedType:
return ContainsGenericParameters(pinnedType.ElementType);
case ByReferenceType byRefType:
return ContainsGenericParameters(byRefType.ElementType);
case RequiredModifierType requiredModType:
return ContainsGenericParameters(requiredModType.ModifierType);
case OptionalModifierType optionalModType:
return ContainsGenericParameters(optionalModType.ElementType);
case GenericInstanceType partialGenericInstance:
{
foreach (var genericArgument in partialGenericInstance.GenericArguments)
{
if (ContainsGenericParameters(genericArgument))
{
return true;
}
}
break;
}
case TypeDefinition typeDefinition:
{
// Sometimes we can have a TypeDefinition with HasGenericParameters false, but GenericParameters.Count > 0
return typeDefinition.GenericParameters.Count > 0;
}
}
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 35f3382bcc423b60acc9af62d10d008b
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,942 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace zzzUnity.Burst.CodeGen
{
/// <summary>
/// Main class for post processing assemblies. The post processing is currently performing:
/// - Replace C# call from C# to Burst functions with attributes [BurstCompile] to a call to the compiled Burst function
/// In both editor and standalone scenarios. For DOTS Runtime, this is done differently at BclApp level by patching
/// DllImport.
/// - Replace calls to `SharedStatic.GetOrCreate` with `SharedStatic.GetOrCreateUnsafe`, and calculate the hashes during ILPP time
/// rather than in static constructors at runtime.
/// </summary>
internal class ILPostProcessing
{
private AssemblyDefinition _burstAssembly;
private MethodReference _burstCompilerIsEnabledMethodDefinition;
private MethodReference _burstCompilerCompileFunctionPointer;
private FieldReference _burstCompilerOptionsField;
private TypeReference _burstCompilerOptionsType;
private TypeReference _functionPointerType;
private MethodReference _functionPointerGetValue;
private MethodReference _burstDiscardAttributeConstructor;
private TypeSystem _typeSystem;
private TypeReference _systemDelegateType;
private TypeReference _systemASyncCallbackType;
private TypeReference _systemIASyncResultType;
private AssemblyDefinition _assemblyDefinition;
private bool _modified;
#if !UNITY_DOTSPLAYER
private bool _containsDirectCall;
#endif
private readonly StringBuilder _builder = new StringBuilder(1024);
private readonly List<Instruction> _instructionsToReplace = new List<Instruction>(4);
public const string PostfixManaged = "$BurstManaged";
private const string PostfixBurstDirectCall = "$BurstDirectCall";
private const string PostfixBurstDelegate = "$PostfixBurstDelegate";
private const string GetFunctionPointerName = "GetFunctionPointer";
private const string GetFunctionPointerDiscardName = "GetFunctionPointerDiscard";
private const string InvokeName = "Invoke";
public ILPostProcessing(AssemblyResolver loader, bool isForEditor, ErrorDiagnosticDelegate error, LogDelegate log = null, int logLevel = 0, bool skipInitializeOnLoad = false)
{
_skipInitializeOnLoad = skipInitializeOnLoad;
Loader = loader;
IsForEditor = isForEditor;
}
public bool _skipInitializeOnLoad;
public bool IsForEditor { get; private set; }
private AssemblyResolver Loader { get; }
public bool Run(AssemblyDefinition assemblyDefinition)
{
_assemblyDefinition = assemblyDefinition;
_typeSystem = assemblyDefinition.MainModule.TypeSystem;
_modified = false;
var types = assemblyDefinition.MainModule.GetTypes().ToArray();
foreach (var type in types)
{
ProcessType(type);
}
#if !UNITY_DOTSPLAYER
if (_containsDirectCall)
{
GenerateInitializeOnLoadMethod();
}
#endif
return _modified;
}
private void GenerateInitializeOnLoadMethod()
{
// This method is needed to ensure that BurstCompiler.Options is initialized on the main thread,
// before any direct call methods are called on a background thread.
// [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.AfterAssembliesLoaded)]
// [UnityEditor.InitializeOnLoadMethod] // When its an editor assembly
// private static void Initialize()
// {
// var _ = BurstCompiler.Options;
// }
const string initializeOnLoadClassName = "$BurstDirectCallInitializer";
var initializeOnLoadClass = _assemblyDefinition.MainModule.Types.FirstOrDefault(x => x.Name == initializeOnLoadClassName);
if (initializeOnLoadClass != null)
{
// If there's already a class with this name, remove it,
// This would mean that we're postprocessing an already-postprocessed assembly;
// I don't think that ever happens, but no sense in breaking if it does.
_assemblyDefinition.MainModule.Types.Remove(initializeOnLoadClass);
}
initializeOnLoadClass = new TypeDefinition(
"",
initializeOnLoadClassName,
TypeAttributes.NotPublic |
TypeAttributes.AutoLayout |
TypeAttributes.AnsiClass |
TypeAttributes.Abstract |
TypeAttributes.Sealed |
TypeAttributes.BeforeFieldInit)
{
BaseType = _typeSystem.Object
};
_assemblyDefinition.MainModule.Types.Add(initializeOnLoadClass);
var initializeOnLoadMethod = new MethodDefinition("Initialize", MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, _typeSystem.Void)
{
ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
DeclaringType = initializeOnLoadClass
};
initializeOnLoadMethod.Body.Variables.Add(new VariableDefinition(_burstCompilerOptionsType));
var processor = initializeOnLoadMethod.Body.GetILProcessor();
processor.Emit(OpCodes.Ldsfld, _burstCompilerOptionsField);
processor.Emit(OpCodes.Stloc_0);
processor.Emit(OpCodes.Ret);
initializeOnLoadClass.Methods.Add(FixDebugInformation(initializeOnLoadMethod));
var attribute = new CustomAttribute(_unityEngineInitializeOnLoadAttributeCtor);
attribute.ConstructorArguments.Add(new CustomAttributeArgument(_unityEngineRuntimeInitializeLoadType, _unityEngineRuntimeInitializeLoadAfterAssemblies.Constant));
initializeOnLoadMethod.CustomAttributes.Add(attribute);
if (IsForEditor && !_skipInitializeOnLoad)
{
// Need to ensure the editor tag for initialize on load is present, otherwise edit mode tests will not call Initialize
attribute = new CustomAttribute(_unityEditorInitilizeOnLoadAttributeCtor);
initializeOnLoadMethod.CustomAttributes.Add(attribute);
}
}
private static bool CanComputeCompileTimeHash(TypeReference typeRef)
{
if (typeRef.ContainsGenericParameter)
{
return false;
}
var assemblyNameReference = typeRef.Scope as AssemblyNameReference ?? typeRef.Module.Assembly?.Name;
if (assemblyNameReference == null)
{
return false;
}
switch (assemblyNameReference.Name)
{
case "netstandard":
case "mscorlib":
return false;
}
return true;
}
private void ProcessType(TypeDefinition type)
{
if (!type.HasGenericParameters && TryGetBurstCompileAttribute(type, out _))
{
// Make a copy because we are going to modify it
var methodCount = type.Methods.Count;
for (var j = 0; j < methodCount; j++)
{
var method = type.Methods[j];
if (!method.IsStatic || method.HasGenericParameters || !TryGetBurstCompileAttribute(method, out var methodBurstCompileAttribute)) continue;
bool isDirectCallDisabled = false;
bool foundProperty = false;
if (methodBurstCompileAttribute.HasProperties)
{
foreach (var property in methodBurstCompileAttribute.Properties)
{
if (property.Name == "DisableDirectCall")
{
isDirectCallDisabled = (bool)property.Argument.Value;
foundProperty = true;
break;
}
}
}
// If the method doesn't have a direct call specified, try the assembly level, do one last check for any assembly level [BurstCompile] instead.
if (foundProperty == false && TryGetBurstCompileAttribute(method.Module.Assembly, out var assemblyBurstCompileAttribute))
{
if (assemblyBurstCompileAttribute.HasProperties)
{
foreach (var property in assemblyBurstCompileAttribute.Properties)
{
if (property.Name == "DisableDirectCall")
{
isDirectCallDisabled = (bool)property.Argument.Value;
break;
}
}
}
}
foreach (var customAttribute in method.CustomAttributes)
{
if (customAttribute.AttributeType.FullName == "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute")
{
// Can't / shouldn't enable direct call for [UnmanagedCallersOnly] methods -
// these can't be called from managed code.
isDirectCallDisabled = true;
break;
}
}
#if !UNITY_DOTSPLAYER // Direct call is not Supported for dots runtime via this pre-processor, its handled elsewhere, this code assumes a Unity Editor based burst
if (!isDirectCallDisabled)
{
if (_burstAssembly == null)
{
var resolved = methodBurstCompileAttribute.Constructor.DeclaringType.Resolve();
InitializeBurstAssembly(resolved.Module.Assembly);
}
ProcessMethodForDirectCall(method);
_modified = true;
_containsDirectCall = true;
}
#endif
}
}
if (TypeHasSharedStaticInIt(type))
{
foreach (var method in type.Methods)
{
// Skip anything that isn't the static constructor.
if (method.Name != ".cctor")
{
continue;
}
try
{
#if DEBUG
if (_instructionsToReplace.Count != 0)
{
throw new InvalidOperationException("Instructions to replace wasn't cleared properly!");
}
#endif
foreach (var instruction in method.Body.Instructions)
{
// Skip anything that isn't a call.
if (instruction.OpCode != OpCodes.Call)
{
continue;
}
var calledMethod = (MethodReference)instruction.Operand;
if (calledMethod.Name != "GetOrCreate")
{
continue;
}
// Skip anything that isn't member of the `SharedStatic` class.
if (!TypeIsSharedStatic(calledMethod.DeclaringType))
{
continue;
}
// We only handle the `GetOrCreate` calls with a single parameter (the alignment).
if (calledMethod.Parameters.Count != 1)
{
continue;
}
// We only post-process the generic versions of `GetOrCreate`.
if (!(calledMethod is GenericInstanceMethod genericInstanceMethod))
{
continue;
}
var atLeastOneArgumentCanBeComputed = false;
foreach (var genericArgument in genericInstanceMethod.GenericArguments)
{
if (CanComputeCompileTimeHash(genericArgument))
{
atLeastOneArgumentCanBeComputed = true;
}
}
// We cannot post-process a shared static with all arguments being open generic.
// We cannot post-process a shared static where all of its types are in core libraries.
if (!atLeastOneArgumentCanBeComputed)
{
continue;
}
_instructionsToReplace.Add(instruction);
}
if (_instructionsToReplace.Count > 0)
{
_modified = true;
}
foreach (var instruction in _instructionsToReplace)
{
var calledMethod = (GenericInstanceMethod)instruction.Operand;
var hashCode64 = CalculateHashCode64(calledMethod.GenericArguments[0]);
long subHashCode64 = 0;
var useCalculatedHashCode = true;
var useCalculatedSubHashCode = true;
if (calledMethod.GenericArguments.Count == 2)
{
subHashCode64 = CalculateHashCode64(calledMethod.GenericArguments[1]);
useCalculatedHashCode = CanComputeCompileTimeHash(calledMethod.GenericArguments[0]);
useCalculatedSubHashCode = CanComputeCompileTimeHash(calledMethod.GenericArguments[1]);
}
#if DEBUG
if (!useCalculatedHashCode && !useCalculatedSubHashCode)
{
throw new InvalidOperationException("Cannot replace when both hashes are invalid!");
}
#endif
var methodToCall = "GetOrCreateUnsafe";
TypeReference genericArgument = null;
if (!useCalculatedHashCode)
{
methodToCall = "GetOrCreatePartiallyUnsafeWithSubHashCode";
genericArgument = calledMethod.GenericArguments[0];
}
else if (!useCalculatedSubHashCode)
{
methodToCall = "GetOrCreatePartiallyUnsafeWithHashCode";
genericArgument = calledMethod.GenericArguments[1];
}
var getOrCreateUnsafe = _assemblyDefinition.MainModule.ImportReference(
calledMethod.DeclaringType.Resolve().Methods.First(m => m.Name == methodToCall));
getOrCreateUnsafe.DeclaringType = calledMethod.DeclaringType;
if (genericArgument != null)
{
var genericInstanceMethod = new GenericInstanceMethod(getOrCreateUnsafe);
genericInstanceMethod.GenericArguments.Add(genericArgument);
getOrCreateUnsafe = genericInstanceMethod;
}
var processor = method.Body.GetILProcessor();
if (useCalculatedHashCode)
{
processor.InsertBefore(instruction, processor.Create(OpCodes.Ldc_I8, hashCode64));
}
if (useCalculatedSubHashCode)
{
processor.InsertBefore(instruction, processor.Create(OpCodes.Ldc_I8, subHashCode64));
}
processor.Replace(instruction, processor.Create(OpCodes.Call, getOrCreateUnsafe));
}
}
finally
{
_instructionsToReplace.Clear();
}
}
}
}
// WARNING: This **must** be kept in sync with the definition in BurstRuntime.cs!
private static long HashStringWithFNV1A64(string text)
{
// Using http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-1a
// with basis and prime:
const ulong offsetBasis = 14695981039346656037;
const ulong prime = 1099511628211;
ulong result = offsetBasis;
foreach (var c in text)
{
result = prime * (result ^ (byte)(c & 255));
result = prime * (result ^ (byte)(c >> 8));
}
return (long)result;
}
private long CalculateHashCode64(TypeReference type)
{
try
{
#if DEBUG
if (_builder.Length != 0)
{
throw new InvalidOperationException("StringBuilder wasn't cleared properly!");
}
#endif
type.BuildAssemblyQualifiedName(_builder);
return HashStringWithFNV1A64(_builder.ToString());
}
finally
{
_builder.Clear();
}
}
private static bool TypeIsSharedStatic(TypeReference typeRef)
{
if (typeRef.Namespace != "Unity.Burst")
{
return false;
}
if (typeRef.Name != "SharedStatic`1")
{
return false;
}
return true;
}
private static bool TypeHasSharedStaticInIt(TypeDefinition typeDef)
{
foreach (var field in typeDef.Fields)
{
if (TypeIsSharedStatic(field.FieldType))
{
return true;
}
}
return false;
}
private TypeDefinition InjectDelegate(TypeDefinition declaringType, string originalName, MethodDefinition managed, string uniqueSuffix)
{
var injectedDelegateType = new TypeDefinition(declaringType.Namespace, $"{originalName}{uniqueSuffix}{PostfixBurstDelegate}",
TypeAttributes.NestedPublic |
TypeAttributes.AutoLayout |
TypeAttributes.AnsiClass |
TypeAttributes.Sealed
)
{
DeclaringType = declaringType,
BaseType = _systemDelegateType
};
declaringType.NestedTypes.Add(injectedDelegateType);
{
var constructor = new MethodDefinition(".ctor",
MethodAttributes.Public |
MethodAttributes.HideBySig |
MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName,
_typeSystem.Void)
{
HasThis = true,
IsManaged = true,
IsRuntime = true,
DeclaringType = injectedDelegateType
};
constructor.Parameters.Add(new ParameterDefinition(_typeSystem.Object));
constructor.Parameters.Add(new ParameterDefinition(_typeSystem.IntPtr));
injectedDelegateType.Methods.Add(constructor);
}
{
var invoke = new MethodDefinition("Invoke",
MethodAttributes.Public |
MethodAttributes.HideBySig |
MethodAttributes.NewSlot |
MethodAttributes.Virtual,
managed.ReturnType)
{
HasThis = true,
IsManaged = true,
IsRuntime = true,
DeclaringType = injectedDelegateType
};
foreach (var parameter in managed.Parameters)
{
invoke.Parameters.Add(parameter);
}
injectedDelegateType.Methods.Add(invoke);
}
{
var beginInvoke = new MethodDefinition("BeginInvoke",
MethodAttributes.Public |
MethodAttributes.HideBySig |
MethodAttributes.NewSlot |
MethodAttributes.Virtual,
_systemIASyncResultType)
{
HasThis = true,
IsManaged = true,
IsRuntime = true,
DeclaringType = injectedDelegateType
};
foreach (var parameter in managed.Parameters)
{
beginInvoke.Parameters.Add(parameter);
}
beginInvoke.Parameters.Add(new ParameterDefinition(_systemASyncCallbackType));
beginInvoke.Parameters.Add(new ParameterDefinition(_typeSystem.Object));
injectedDelegateType.Methods.Add(beginInvoke);
}
{
var endInvoke = new MethodDefinition("EndInvoke",
MethodAttributes.Public |
MethodAttributes.HideBySig |
MethodAttributes.NewSlot |
MethodAttributes.Virtual,
managed.ReturnType)
{
HasThis = true,
IsManaged = true,
IsRuntime = true,
DeclaringType = injectedDelegateType
};
endInvoke.Parameters.Add(new ParameterDefinition(_systemIASyncResultType));
injectedDelegateType.Methods.Add(endInvoke);
}
return injectedDelegateType;
}
private MethodDefinition CreateGetFunctionPointerDiscardMethod(TypeDefinition cls, FieldDefinition pointerField, MethodDefinition targetMethod, TypeDefinition injectedDelegate)
{
var genericCompileFunctionPointer = new GenericInstanceMethod(_burstCompilerCompileFunctionPointer);
genericCompileFunctionPointer.GenericArguments.Add(injectedDelegate);
var genericFunctionPointerType = new GenericInstanceType(_functionPointerType);
genericFunctionPointerType.GenericArguments.Add(injectedDelegate);
var genericGetValue = new MethodReference(_functionPointerGetValue.Name, _functionPointerGetValue.ReturnType, genericFunctionPointerType);
foreach (var p in _functionPointerGetValue.Parameters)
{
genericGetValue.Parameters.Add(new ParameterDefinition(p.Name, p.Attributes, p.ParameterType));
}
genericGetValue.HasThis = _functionPointerGetValue.HasThis;
genericGetValue.MetadataToken = _functionPointerGetValue.MetadataToken;
/*var genericGetValue = new Mono.Cecil.GenericInstanceMethod(_functionPointerGetValue)
{
DeclaringType = genericFunctionPointerType
};*/
// Create GetFunctionPointerDiscard method:
//
// [BurstDiscard]
// public static void GetFunctionPointerDiscard(ref IntPtr ptr) {
// if (Pointer == null) {
// Pointer = BurstCompiler.CompileFunctionPointer<InjectedDelegate>(d);
// }
//
// ptr = Pointer
// }
var getFunctionPointerDiscardMethod = new MethodDefinition(GetFunctionPointerDiscardName, MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, _typeSystem.Void)
{
ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
DeclaringType = cls
};
getFunctionPointerDiscardMethod.Body.Variables.Add(new VariableDefinition(genericFunctionPointerType));
getFunctionPointerDiscardMethod.Parameters.Add(new ParameterDefinition(new ByReferenceType(_typeSystem.IntPtr)));
var processor = getFunctionPointerDiscardMethod.Body.GetILProcessor();
processor.Emit(OpCodes.Ldsfld, pointerField);
var branchPosition = processor.Body.Instructions[processor.Body.Instructions.Count - 1];
processor.Emit(OpCodes.Ldnull);
processor.Emit(OpCodes.Ldftn, targetMethod);
processor.Emit(OpCodes.Newobj, injectedDelegate.Methods.First(md => md.IsConstructor && md.Parameters.Count == 2));
processor.Emit(OpCodes.Call, genericCompileFunctionPointer);
processor.Emit(OpCodes.Stloc_0);
processor.Emit(OpCodes.Ldloca, 0);
processor.Emit(OpCodes.Call, genericGetValue);
processor.Emit(OpCodes.Stsfld, pointerField);
processor.Emit(OpCodes.Ldarg_0);
processor.InsertAfter(branchPosition, Instruction.Create(OpCodes.Brtrue, processor.Body.Instructions[processor.Body.Instructions.Count - 1]));
processor.Emit(OpCodes.Ldsfld, pointerField);
processor.Emit(OpCodes.Stind_I);
processor.Emit(OpCodes.Ret);
cls.Methods.Add(FixDebugInformation(getFunctionPointerDiscardMethod));
getFunctionPointerDiscardMethod.CustomAttributes.Add(new CustomAttribute(_burstDiscardAttributeConstructor));
return getFunctionPointerDiscardMethod;
}
private MethodDefinition CreateGetFunctionPointerMethod(TypeDefinition cls, MethodDefinition getFunctionPointerDiscardMethod)
{
// Create GetFunctionPointer method:
//
// public static IntPtr GetFunctionPointer() {
// var ptr;
// GetFunctionPointerDiscard(ref ptr);
// return ptr;
// }
var getFunctionPointerMethod = new MethodDefinition(GetFunctionPointerName, MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static, _typeSystem.IntPtr)
{
ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
DeclaringType = cls
};
getFunctionPointerMethod.Body.Variables.Add(new VariableDefinition(_typeSystem.IntPtr));
getFunctionPointerMethod.Body.InitLocals = true;
var processor = getFunctionPointerMethod.Body.GetILProcessor();
processor.Emit(OpCodes.Ldc_I4_0);
processor.Emit(OpCodes.Conv_I);
processor.Emit(OpCodes.Stloc_0);
processor.Emit(OpCodes.Ldloca_S, (byte)0);
processor.Emit(OpCodes.Call, getFunctionPointerDiscardMethod);
processor.Emit(OpCodes.Ldloc_0);
processor.Emit(OpCodes.Ret);
cls.Methods.Add(FixDebugInformation(getFunctionPointerMethod));
return getFunctionPointerMethod;
}
private void ProcessMethodForDirectCall(MethodDefinition burstCompileMethod)
{
var declaringType = burstCompileMethod.DeclaringType;
var uniqueSuffix = $"_{burstCompileMethod.MetadataToken.RID:X8}";
var injectedDelegate = InjectDelegate(declaringType, burstCompileMethod.Name, burstCompileMethod, uniqueSuffix);
// Create a copy of the original method that will be the actual managed method
// The original method is patched at the end of this method to call
// the dispatcher that will go to the Burst implementation or the managed method (if in the editor and Burst is disabled)
var managedFallbackMethod = new MethodDefinition($"{burstCompileMethod.Name}{PostfixManaged}", burstCompileMethod.Attributes, burstCompileMethod.ReturnType)
{
DeclaringType = declaringType,
ImplAttributes = burstCompileMethod.ImplAttributes,
MetadataToken = burstCompileMethod.MetadataToken,
};
// Ensure the CustomAttributes are the same
managedFallbackMethod.CustomAttributes.Clear();
foreach (var attr in burstCompileMethod.CustomAttributes)
{
managedFallbackMethod.CustomAttributes.Add(attr);
}
declaringType.Methods.Add(managedFallbackMethod);
foreach (var parameter in burstCompileMethod.Parameters)
{
managedFallbackMethod.Parameters.Add(parameter);
}
// Copy the body from the original burst method to the managed fallback, we'll replace the burstCompileMethod body later.
managedFallbackMethod.Body.InitLocals = burstCompileMethod.Body.InitLocals;
managedFallbackMethod.Body.LocalVarToken = burstCompileMethod.Body.LocalVarToken;
managedFallbackMethod.Body.MaxStackSize = burstCompileMethod.Body.MaxStackSize;
foreach (var variable in burstCompileMethod.Body.Variables)
{
managedFallbackMethod.Body.Variables.Add(variable);
}
foreach (var instruction in burstCompileMethod.Body.Instructions)
{
managedFallbackMethod.Body.Instructions.Add(instruction);
}
foreach (var exceptionHandler in burstCompileMethod.Body.ExceptionHandlers)
{
managedFallbackMethod.Body.ExceptionHandlers.Add(exceptionHandler);
}
managedFallbackMethod.ImplAttributes &= MethodImplAttributes.NoInlining;
// 0x0100 is AggressiveInlining
managedFallbackMethod.ImplAttributes |= (MethodImplAttributes)0x0100;
// The method needs to be public because we query for it in the ILPP code.
managedFallbackMethod.Attributes &= ~MethodAttributes.Private;
managedFallbackMethod.Attributes |= MethodAttributes.Public;
// private static class (Name_RID.$Postfix)
var cls = new TypeDefinition(declaringType.Namespace, $"{burstCompileMethod.Name}{uniqueSuffix}{PostfixBurstDirectCall}",
TypeAttributes.NestedAssembly |
TypeAttributes.AutoLayout |
TypeAttributes.AnsiClass |
TypeAttributes.Abstract |
TypeAttributes.Sealed |
TypeAttributes.BeforeFieldInit
)
{
DeclaringType = declaringType,
BaseType = _typeSystem.Object
};
declaringType.NestedTypes.Add(cls);
// Create Field:
//
// private static IntPtr Pointer;
var pointerField = new FieldDefinition("Pointer", FieldAttributes.Static | FieldAttributes.Private, _typeSystem.IntPtr)
{
DeclaringType = cls
};
cls.Fields.Add(pointerField);
var getFunctionPointerDiscardMethod = CreateGetFunctionPointerDiscardMethod(
cls, pointerField,
// In the player the function pointer is looked up in a registry by name
// so we can't request a `$BurstManaged` function (because it was never compiled, only the toplevel one)
// But, it's safe *in the player* to request the toplevel function
IsForEditor ? managedFallbackMethod : burstCompileMethod,
injectedDelegate);
var getFunctionPointerMethod = CreateGetFunctionPointerMethod(cls, getFunctionPointerDiscardMethod);
// Create the Invoke method based on the original method (same signature)
//
// public static XXX Invoke(...args) {
// if (BurstCompiler.IsEnabled)
// {
// var funcPtr = GetFunctionPointer();
// if (funcPtr != null) return funcPtr(...args);
// }
// return OriginalMethod(...args);
// }
var invokeAttributes = managedFallbackMethod.Attributes;
invokeAttributes &= ~MethodAttributes.Private;
invokeAttributes |= MethodAttributes.Public;
var invoke = new MethodDefinition(InvokeName, invokeAttributes, burstCompileMethod.ReturnType)
{
ImplAttributes = MethodImplAttributes.IL | MethodImplAttributes.Managed,
DeclaringType = cls
};
var signature = new CallSite(burstCompileMethod.ReturnType)
{
CallingConvention = MethodCallingConvention.C
};
foreach (var parameter in burstCompileMethod.Parameters)
{
invoke.Parameters.Add(parameter);
signature.Parameters.Add(parameter);
}
invoke.Body.Variables.Add(new VariableDefinition(_typeSystem.IntPtr));
invoke.Body.InitLocals = true;
var processor = invoke.Body.GetILProcessor();
processor.Emit(OpCodes.Call, _burstCompilerIsEnabledMethodDefinition);
var branchPosition0 = processor.Body.Instructions[processor.Body.Instructions.Count - 1];
processor.Emit(OpCodes.Call, getFunctionPointerMethod);
processor.Emit(OpCodes.Stloc_0);
processor.Emit(OpCodes.Ldloc_0);
var branchPosition1 = processor.Body.Instructions[processor.Body.Instructions.Count - 1];
EmitArguments(processor, invoke);
processor.Emit(OpCodes.Ldloc_0);
processor.Emit(OpCodes.Calli, signature);
processor.Emit(OpCodes.Ret);
var previousRet = processor.Body.Instructions[processor.Body.Instructions.Count - 1];
EmitArguments(processor, invoke);
processor.Emit(OpCodes.Call, managedFallbackMethod);
processor.Emit(OpCodes.Ret);
// Insert the branch once we have emitted the instructions
processor.InsertAfter(branchPosition0, Instruction.Create(OpCodes.Brfalse, previousRet.Next));
processor.InsertAfter(branchPosition1, Instruction.Create(OpCodes.Brfalse, previousRet.Next));
cls.Methods.Add(FixDebugInformation(invoke));
// Final patching of the original method
// public static XXX OriginalMethod(...args) {
// Name_RID.$Postfix.Invoke(...args);
// ret;
// }
burstCompileMethod.Body = new MethodBody(burstCompileMethod);
processor = burstCompileMethod.Body.GetILProcessor();
EmitArguments(processor, burstCompileMethod);
processor.Emit(OpCodes.Call, invoke);
processor.Emit(OpCodes.Ret);
FixDebugInformation(burstCompileMethod);
// Ensure that the original method does not have any sequence points
burstCompileMethod.DebugInformation.SequencePoints.Clear();
}
private static MethodDefinition FixDebugInformation(MethodDefinition method)
{
method.DebugInformation.Scope = new ScopeDebugInformation(method.Body.Instructions.First(), method.Body.Instructions.Last());
return method;
}
private AssemblyDefinition GetAsmDefinitionFromFile(AssemblyResolver loader, string assemblyName)
{
if (loader.TryResolve(AssemblyNameReference.Parse(assemblyName), out var result))
{
return result;
}
return null;
}
private MethodReference _unityEngineInitializeOnLoadAttributeCtor;
private TypeReference _unityEngineRuntimeInitializeLoadType;
private FieldDefinition _unityEngineRuntimeInitializeLoadAfterAssemblies;
private MethodReference _unityEditorInitilizeOnLoadAttributeCtor;
private void InitializeBurstAssembly(AssemblyDefinition burstAssembly)
{
_burstAssembly = burstAssembly;
var burstCompilerTypeDefinition = burstAssembly.MainModule.GetType("Unity.Burst", "BurstCompiler");
_burstCompilerIsEnabledMethodDefinition = _assemblyDefinition.MainModule.ImportReference(burstCompilerTypeDefinition.Methods.FirstOrDefault(x => x.Name == "get_IsEnabled"));
_burstCompilerCompileFunctionPointer = _assemblyDefinition.MainModule.ImportReference(burstCompilerTypeDefinition.Methods.FirstOrDefault(x => x.Name == "CompileFunctionPointer"));
_burstCompilerOptionsField = _assemblyDefinition.MainModule.ImportReference(burstCompilerTypeDefinition.Fields.FirstOrDefault(x => x.Name == "Options"));
_burstCompilerOptionsType = _assemblyDefinition.MainModule.ImportReference(burstAssembly.MainModule.GetType("Unity.Burst", "BurstCompilerOptions"));
var functionPointerTypeDefinition = burstAssembly.MainModule.GetType("Unity.Burst", "FunctionPointer`1");
_functionPointerType = _assemblyDefinition.MainModule.ImportReference(functionPointerTypeDefinition);
_functionPointerGetValue = _assemblyDefinition.MainModule.ImportReference(functionPointerTypeDefinition.Methods.FirstOrDefault(x => x.Name == "get_Value"));
var corLibrary = Loader.Resolve((AssemblyNameReference)_typeSystem.CoreLibrary);
_systemDelegateType = _assemblyDefinition.MainModule.ImportReference(corLibrary.MainModule.GetType("System.MulticastDelegate"));
_systemASyncCallbackType = _assemblyDefinition.MainModule.ImportReference(corLibrary.MainModule.GetType("System.AsyncCallback"));
_systemIASyncResultType = _assemblyDefinition.MainModule.ImportReference(corLibrary.MainModule.GetType("System.IAsyncResult"));
var asmDef = GetAsmDefinitionFromFile(Loader, "UnityEngine.CoreModule");
var runtimeInitializeOnLoadMethodAttribute = asmDef.MainModule.GetType("UnityEngine", "RuntimeInitializeOnLoadMethodAttribute");
var runtimeInitializeLoadType = asmDef.MainModule.GetType("UnityEngine", "RuntimeInitializeLoadType");
var burstDiscardType = asmDef.MainModule.GetType("Unity.Burst", "BurstDiscardAttribute");
_burstDiscardAttributeConstructor = _assemblyDefinition.MainModule.ImportReference(burstDiscardType.Methods.First(method => method.Name == ".ctor"));
_unityEngineInitializeOnLoadAttributeCtor = _assemblyDefinition.MainModule.ImportReference(runtimeInitializeOnLoadMethodAttribute.Methods.FirstOrDefault(x => x.Name == ".ctor" && x.HasParameters));
_unityEngineRuntimeInitializeLoadType = _assemblyDefinition.MainModule.ImportReference(runtimeInitializeLoadType);
_unityEngineRuntimeInitializeLoadAfterAssemblies = runtimeInitializeLoadType.Fields.FirstOrDefault(x => x.Name == "AfterAssembliesLoaded");
if (IsForEditor && !_skipInitializeOnLoad)
{
asmDef = GetAsmDefinitionFromFile(Loader, "UnityEditor.CoreModule");
if (asmDef == null)
asmDef = GetAsmDefinitionFromFile(Loader, "UnityEditor");
var initializeOnLoadMethodAttribute = asmDef.MainModule.GetType("UnityEditor", "InitializeOnLoadMethodAttribute");
_unityEditorInitilizeOnLoadAttributeCtor = _assemblyDefinition.MainModule.ImportReference(initializeOnLoadMethodAttribute.Methods.FirstOrDefault(x => x.Name == ".ctor" && !x.HasParameters));
}
}
private static void EmitArguments(ILProcessor processor, MethodDefinition method)
{
for (var i = 0; i < method.Parameters.Count; i++)
{
switch (i)
{
case 0:
processor.Emit(OpCodes.Ldarg_0);
break;
case 1:
processor.Emit(OpCodes.Ldarg_1);
break;
case 2:
processor.Emit(OpCodes.Ldarg_2);
break;
case 3:
processor.Emit(OpCodes.Ldarg_3);
break;
default:
if (i <= 255)
{
processor.Emit(OpCodes.Ldarg_S, (byte)i);
}
else
{
processor.Emit(OpCodes.Ldarg, i);
}
break;
}
}
}
private static bool TryGetBurstCompileAttribute(ICustomAttributeProvider provider, out CustomAttribute customAttribute)
{
if (provider.HasCustomAttributes)
{
foreach (var customAttr in provider.CustomAttributes)
{
if (customAttr.Constructor.DeclaringType.Name == "BurstCompileAttribute")
{
customAttribute = customAttr;
return true;
}
}
}
customAttribute = null;
return false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e8dd47a9e5283ced8ded83ce38cc78aa
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0f204499a5833eb3ac929d6b00ec26e8
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,69 @@
fileFormatVersion: 2
guid: 74f442ba825df4d0483995eadabb1fd4
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 1
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Editor: 0
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,69 @@
fileFormatVersion: 2
guid: 61f68ecf9182840f7a8c8f0a7cbbbc47
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 1
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Editor: 0
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,69 @@
fileFormatVersion: 2
guid: fc89006ebb6404ae7b55bf130d290f91
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 1
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Editor: 0
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,69 @@
fileFormatVersion: 2
guid: c494744dbcf1c45e0b4d7431ab1a16d9
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 1
validateReferences: 1
platformData:
- first:
: Any
second:
enabled: 0
settings:
Exclude Editor: 0
Exclude Linux64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
- first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
- first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
{
"name": "Unity.Burst.CodeGen",
"references": [
"Unity.Burst"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": true,
"precompiledReferences": [
"Unity.Burst.Cecil.dll",
"Unity.Burst.Cecil.Rocks.dll",
"Unity.Burst.Cecil.Pdb.dll",
"Unity.Burst.Cecil.Mdb.dll"
],
"autoReferenced": false,
"versionDefines": []
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9c6dda6c7922c624cb7ea9a7f1c7d54a
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: