diff --git a/src/mono/mono.proj b/src/mono/mono.proj index 9e90b9134fb103..7cd03731fcd232 100644 --- a/src/mono/mono.proj +++ b/src/mono/mono.proj @@ -40,6 +40,7 @@ true true true + true true true $([MSBuild]::NormalizeDirectory('$(MonoObjDir)', 'cross')) diff --git a/src/mono/sample/HelloWorld/HelloWorld.csproj b/src/mono/sample/HelloWorld/HelloWorld.csproj index f727ffe343f4d1..9ae9581798b9c9 100644 --- a/src/mono/sample/HelloWorld/HelloWorld.csproj +++ b/src/mono/sample/HelloWorld/HelloWorld.csproj @@ -3,4 +3,34 @@ Exe $(NetCoreAppCurrent) + + + + + + <_AotOutputType>Library + <_AotLibraryFormat>Dylib + false + + + + + + + + + + diff --git a/src/mono/sample/HelloWorld/Makefile b/src/mono/sample/HelloWorld/Makefile index 28df19d738f9d6..10a1a52f5d1324 100644 --- a/src/mono/sample/HelloWorld/Makefile +++ b/src/mono/sample/HelloWorld/Makefile @@ -4,6 +4,11 @@ DOTNET_Q_ARGS=--nologo -v:q -consoleloggerparameters:NoSummary MONO_CONFIG ?=Debug MONO_ARCH=x64 +AOT?=false + +#NET_TRACE_PATH= +#PGO_BINARY_PATH= +#MIBC_PROFILE_PATH= OS := $(shell uname -s) ifeq ($(OS),Darwin) @@ -12,10 +17,16 @@ else TARGET_OS=linux endif -MONO_ENV_OPTIONS ?=--llvm +MONO_ENV_OPTIONS ?= publish: - $(DOTNET) publish -c $(MONO_CONFIG) -r $(TARGET_OS)-$(MONO_ARCH) + $(DOTNET) publish \ + -c $(MONO_CONFIG) \ + -r $(TARGET_OS)-$(MONO_ARCH) \ + /p:RunAOTCompilation=$(AOT) \ + '/p:NetTracePath="$(NET_TRACE_PATH)"' \ + '/p:PgoBinaryPath="$(PGO_BINARY_PATH)"' \ + '/p:MibcProfilePath="$(MIBC_PROFILE_PATH)"' run: publish COMPlus_DebugWriteToStdErr=1 \ diff --git a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs index fcc24b6111b807..6d639794d3289f 100644 --- a/src/tasks/AotCompilerTask/MonoAOTCompiler.cs +++ b/src/tasks/AotCompilerTask/MonoAOTCompiler.cs @@ -111,6 +111,16 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task /// public bool UseDwarfDebug { get; set; } + /// + /// Path to Dotnet PGO binary (dotnet-pgo) + /// + public string? PgoBinaryPath { get; set; } + + /// + /// NetTrace file to use when invoking dotnet-pgo for + /// + public string? NetTracePath { get; set; } + /// /// File to use for profile-guided optimization, *only* the methods described in the file will be AOT compiled. /// @@ -119,7 +129,7 @@ public class MonoAOTCompiler : Microsoft.Build.Utilities.Task /// /// Mibc file to use for profile-guided optimization, *only* the methods described in the file will be AOT compiled. /// - public string[]? MibcProfilePath { get; set; } + public string[] MibcProfilePath { get; set; } = Array.Empty(); /// /// List of profilers to use. @@ -271,6 +281,20 @@ private bool ProcessAndValidateArguments() if (!Directory.Exists(IntermediateOutputPath)) Directory.CreateDirectory(IntermediateOutputPath); + if (!string.IsNullOrEmpty(NetTracePath)) + { + if (!File.Exists(NetTracePath)) + { + Log.LogError($"NetTracePath={nameof(NetTracePath)} doesn't exist"); + return false; + } + if (!File.Exists(PgoBinaryPath)) + { + Log.LogError($"NetTracePath was provided, but {nameof(PgoBinaryPath)}='{PgoBinaryPath}' doesn't exist"); + return false; + } + } + if (AotProfilePath != null) { foreach (var path in AotProfilePath) @@ -283,15 +307,12 @@ private bool ProcessAndValidateArguments() } } - if (MibcProfilePath != null) + foreach (var path in MibcProfilePath) { - foreach (var path in MibcProfilePath) + if (!File.Exists(path)) { - if (!File.Exists(path)) - { - Log.LogError($"MibcProfilePath '{path}' doesn't exist."); - return false; - } + Log.LogError($"MibcProfilePath '{path}' doesn't exist."); + return false; } } @@ -400,6 +421,39 @@ public override bool Execute() } } + private bool ProcessNettrace(string netTraceFile) + { + var outputMibcPath = Path.Combine(OutputDir, Path.ChangeExtension(Path.GetFileName(netTraceFile), ".mibc")); + + if (_cache!.Enabled) + { + string hash = Utils.ComputeHash(netTraceFile); + if (!_cache!.UpdateAndCheckHasFileChanged($"-mibc-source-file-{Path.GetFileName(netTraceFile)}", hash)) + { + Log.LogMessage(MessageImportance.Low, $"Skipping generating {outputMibcPath} from {netTraceFile} because source file hasn't changed"); + return true; + } + else + { + Log.LogMessage(MessageImportance.Low, $"Generating {outputMibcPath} from {netTraceFile} because the source file's hash has changed."); + } + } + + (int exitCode, string output) = Utils.TryRunProcess(Log, + PgoBinaryPath!, + $"create-mibc --trace {netTraceFile} --output {outputMibcPath}"); + + if (exitCode != 0) + { + Log.LogError($"dotnet-pgo({PgoBinaryPath}) failed for {netTraceFile}:{output}"); + return false; + } + + MibcProfilePath = MibcProfilePath.Append(outputMibcPath).ToArray(); + Log.LogMessage(MessageImportance.Low, $"Generated {outputMibcPath} from {PgoBinaryPath}"); + return true; + } + private bool ExecuteInternal() { if (!ProcessAndValidateArguments()) @@ -417,6 +471,9 @@ private bool ExecuteInternal() _cache = new FileCache(CacheFilePath, Log); + if (!string.IsNullOrEmpty(NetTracePath) && !ProcessNettrace(NetTracePath)) + return false; + List argsList = new(); foreach (var assemblyItem in _assembliesToCompile) argsList.Add(GetPrecompileArgumentsFor(assemblyItem, monoPaths)); @@ -756,7 +813,7 @@ private PrecompileArguments GetPrecompileArgumentsFor(ITaskItem assemblyItem, st } } - if (MibcProfilePath?.Length > 0) + if (MibcProfilePath.Length > 0) { aotArgs.Add("profile-only"); foreach (var path in MibcProfilePath) @@ -833,12 +890,13 @@ private PrecompileArguments GetPrecompileArgumentsFor(ITaskItem assemblyItem, st private bool PrecompileLibrary(PrecompileArguments args) { string assembly = args.AOTAssembly.GetMetadata("FullPath"); + string output; try { string msgPrefix = $"[{Path.GetFileName(assembly)}] "; // run the AOT compiler - (int exitCode, string output) = Utils.TryRunProcess(Log, + (int exitCode, output) = Utils.TryRunProcess(Log, CompilerBinaryPath, $"--response=\"{args.ResponseFilePath}\"", args.EnvironmentVariables, @@ -878,6 +936,12 @@ private bool PrecompileLibrary(PrecompileArguments args) bool copied = false; foreach (var proxyFile in args.ProxyFiles) { + if (!File.Exists(proxyFile.TempFile)) + { + Log.LogError($"Precompile command succeeded, but can't find the expected temporary output file - {proxyFile.TempFile} for {assembly}.{Environment.NewLine}{output}"); + return false; + } + copied |= proxyFile.CopyOutputFileIfChanged(); _fileWrites.Add(proxyFile.TargetFile); } @@ -1092,8 +1156,20 @@ public FileCache(string? cacheFilePath, TaskLoggingHelper log) _newCache = new(_oldCache.FileHashes); } + public bool UpdateAndCheckHasFileChanged(string filePath, string newHash) + { + if (!Enabled) + throw new InvalidOperationException("Cache is not enabled. Make sure the cache file path is set"); + + _newCache!.FileHashes[filePath] = newHash; + return !_oldCache!.FileHashes.TryGetValue(filePath, out string? oldHash) || oldHash != newHash; + } + public bool ShouldCopy(ProxyFile proxyFile, [NotNullWhen(true)] out string? cause) { + if (!Enabled) + throw new InvalidOperationException("Cache is not enabled. Make sure the cache file path is set"); + cause = null; string newHash = Utils.ComputeHash(proxyFile.TempFile); @@ -1151,6 +1227,9 @@ public bool CopyOutputFileIfChanged() try { + if (!File.Exists(TempFile)) + throw new LogAsErrorException($"Could not find the temporary file {TempFile} for target file {TargetFile}. Look for any errors/warnings generated earlier in the build."); + if (!_cache.ShouldCopy(this, out string? cause)) { _cache.Log.LogMessage(MessageImportance.Low, $"Skipping copying over {TargetFile} as the contents are unchanged");