Merge pull request #51191 from neikeq/mono-ios-aot-cache

C#+iOS: Cache AOT compilater output
This commit is contained in:
Rémi Verschelde 2021-08-02 17:57:37 +02:00 committed by GitHub
commit 7a7de3ce12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 199 additions and 38 deletions

View File

@ -249,55 +249,72 @@ namespace GodotTools.Export
var aotObjFilePaths = new List<string>(assemblies.Count);
foreach (var assembly in assemblies)
string compilerDirPath = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers",
$"{OS.Platforms.iOS}-arm64");
string crossCompiler = FindCrossCompiler(compilerDirPath);
string aotCacheDir = Path.Combine(ProjectSettings.GlobalizePath(GodotSharpDirs.ResTempDir),
"obj", isDebug ? "ExportDebug" : "ExportRelease", "godot-aot-cache");
if (!Directory.Exists(aotCacheDir))
Directory.CreateDirectory(aotCacheDir);
var aotCache = new AotCache(Path.Combine(aotCacheDir, "cache.json"));
try
{
string assemblyName = assembly.Key;
string assemblyPath = assembly.Value;
string asmFileName = assemblyName + ".dll.S";
string objFileName = assemblyName + ".dll.o";
foreach (var assembly in assemblies)
{
string asmFilePath = Path.Combine(aotTempDir, asmFileName);
string assemblyName = assembly.Key;
string assemblyPath = assembly.Value;
var compilerArgs = GetAotCompilerArgs(OS.Platforms.iOS, isDebug, "arm64", aotOpts, assemblyPath, asmFilePath);
string asmFilePath = Path.Combine(aotCacheDir, assemblyName + ".dll.S");
string objFilePath = Path.Combine(aotCacheDir, assemblyName + ".dll.o");
string compilerDirPath = Path.Combine(GodotSharpDirs.DataEditorToolsDir, "aot-compilers", $"{OS.Platforms.iOS}-arm64");
ExecuteCompiler(FindCrossCompiler(compilerDirPath), compilerArgs, bclDir);
// Assembling
const string iOSPlatformName = "iPhoneOS";
const string versionMin = "10.0"; // TODO: Turn this hard-coded version into an exporter setting
string iOSSdkPath = Path.Combine(XcodeHelper.XcodePath,
$"Contents/Developer/Platforms/{iOSPlatformName}.platform/Developer/SDKs/{iOSPlatformName}.sdk");
string objFilePath = Path.Combine(aotTempDir, objFileName);
var clangArgs = new List<string>()
aotCache.RunCached(name: assemblyName, input: assemblyPath, output: objFilePath, () =>
{
"-isysroot", iOSSdkPath,
"-Qunused-arguments",
$"-miphoneos-version-min={versionMin}",
"-arch", "arm64",
"-c",
"-o", objFilePath,
"-x", "assembler"
};
Console.WriteLine($"AOT compiler: Compiling '{assemblyName}'...");
if (isDebug)
clangArgs.Add("-DDEBUG");
var compilerArgs = GetAotCompilerArgs(OS.Platforms.iOS, isDebug,
"arm64", aotOpts, assemblyPath, asmFilePath);
clangArgs.Add(asmFilePath);
ExecuteCompiler(crossCompiler, compilerArgs, bclDir);
int clangExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("clang"), clangArgs);
if (clangExitCode != 0)
throw new Exception($"Command 'clang' exited with code: {clangExitCode}");
// Assembling
const string iOSPlatformName = "iPhoneOS";
const string versionMin = "10.0"; // TODO: Turn this hard-coded version into an exporter setting
string iOSSdkPath = Path.Combine(XcodeHelper.XcodePath,
$"Contents/Developer/Platforms/{iOSPlatformName}.platform/Developer/SDKs/{iOSPlatformName}.sdk");
var clangArgs = new List<string>()
{
"-isysroot", iOSSdkPath,
"-Qunused-arguments",
$"-miphoneos-version-min={versionMin}",
"-arch", "arm64",
"-c",
"-o", objFilePath,
"-x", "assembler"
};
if (isDebug)
clangArgs.Add("-DDEBUG");
clangArgs.Add(asmFilePath);
int clangExitCode = OS.ExecuteCommand(XcodeHelper.FindXcodeTool("clang"), clangArgs);
if (clangExitCode != 0)
throw new Exception($"Command 'clang' exited with code: {clangExitCode}");
});
aotObjFilePaths.Add(objFilePath);
}
aotModuleInfoSymbols.Add($"mono_aot_module_{AssemblyNameToAotSymbol(assemblyName)}_info");
aotModuleInfoSymbols.Add($"mono_aot_module_{AssemblyNameToAotSymbol(assemblyName)}_info");
}
}
finally
{
aotCache.SaveCache();
}
RunAr(aotObjFilePaths, libAotFilePath);

View File

@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using Newtonsoft.Json;
namespace GodotTools.Export
{
public class AotCache
{
private readonly string _cacheFilePath;
private readonly Cache _cache = new Cache();
private bool _hasUnsavedChanges = false;
public AotCache(string cacheFilePath)
{
_cacheFilePath = cacheFilePath;
if (File.Exists(_cacheFilePath))
LoadCache(_cacheFilePath, out _cache);
}
private static byte[] ComputeSha256Checksum(string filePath)
{
using (var sha256 = SHA256.Create())
{
using (var streamReader = File.OpenRead(filePath))
return sha256.ComputeHash(streamReader);
}
}
private static bool CompareHashes(byte[] a, byte[] b)
{
if (a.Length != b.Length)
return false;
for (int i = 0; i < a.Length; i++)
{
if (a[i] != b[i])
return false;
}
return true;
}
private class Cache
{
[JsonProperty("assemblies")]
public Dictionary<string, CachedChecksums> Assemblies { get; set; } =
new Dictionary<string, CachedChecksums>();
}
private struct CachedChecksums
{
[JsonProperty("input_checksum")] public string InputChecksumBase64 { get; set; }
[JsonProperty("output_checksum")] public string OutputChecksumBase64 { get; set; }
}
private static void LoadCache(string cacheFilePath, out Cache cache)
{
using (var streamReader = new StreamReader(cacheFilePath, Encoding.UTF8))
using (var jsonReader = new JsonTextReader(streamReader))
{
cache = new JsonSerializer().Deserialize<Cache>(jsonReader);
}
}
private static void SaveCache(string cacheFilePath, Cache cache)
{
using (var streamWriter = new StreamWriter(cacheFilePath, append: false, Encoding.UTF8))
using (var jsonWriter = new JsonTextWriter(streamWriter))
{
new JsonSerializer().Serialize(jsonWriter, cache);
}
}
private bool TryGetCachedChecksums(string name, out CachedChecksums cachedChecksums)
=> _cache.Assemblies.TryGetValue(name, out cachedChecksums);
private void ChangeCache(string name, byte[] inputChecksum, byte[] outputChecksum)
{
_cache.Assemblies[name] = new CachedChecksums()
{
InputChecksumBase64 = Convert.ToBase64String(inputChecksum),
OutputChecksumBase64 = Convert.ToBase64String(outputChecksum)
};
_hasUnsavedChanges = true;
}
public void SaveCache()
{
if (!_hasUnsavedChanges)
return;
SaveCache(_cacheFilePath, _cache);
_hasUnsavedChanges = false;
}
private bool IsCached(string name, byte[] inputChecksum, string output)
{
if (!File.Exists(output))
{
return false;
}
if (!TryGetCachedChecksums(name, out var cachedChecksums))
return false;
if (string.IsNullOrEmpty(cachedChecksums.InputChecksumBase64) ||
string.IsNullOrEmpty(cachedChecksums.OutputChecksumBase64))
return false;
var cachedInputChecksum = Convert.FromBase64String(cachedChecksums.InputChecksumBase64);
if (!CompareHashes(inputChecksum, cachedInputChecksum))
return false;
var outputChecksum = ComputeSha256Checksum(output);
var cachedOutputChecksum = Convert.FromBase64String(cachedChecksums.OutputChecksumBase64);
if (!CompareHashes(outputChecksum, cachedOutputChecksum))
return false;
return true;
}
public void RunCached(string name, string input, string output, Action action)
{
var inputChecksum = ComputeSha256Checksum(input);
if (IsCached(name, inputChecksum, output))
{
Console.WriteLine($"AOT compiler cache: '{name}' already compiled.");
return;
}
action();
var outputChecksum = ComputeSha256Checksum(output);
ChangeCache(name, inputChecksum, outputChecksum);
}
}
}