first commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1b75159fe183205bba1d83e5e75a734
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ddf71e25119f3861b687943b050c9971
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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("]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0f1d1ee0d88384188ad6cdcffc5e9d8
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95e00b4ae3493f2bb9d61143777414fc
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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<float>)
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35f3382bcc423b60acc9af62d10d008b
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8dd47a9e5283ced8ded83ce38cc78aa
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f204499a5833eb3ac929d6b00ec26e8
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -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:
|
||||
Binary file not shown.
@@ -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:
|
||||
Binary file not shown.
@@ -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:
|
||||
Binary file not shown.
@@ -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:
|
||||
@@ -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": []
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c6dda6c7922c624cb7ea9a7f1c7d54a
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user