diff --git a/NuGet.Config b/NuGet.Config
index 2c2296c4..1c5a35f1 100644
--- a/NuGet.Config
+++ b/NuGet.Config
@@ -5,6 +5,7 @@
+
diff --git a/build.cmd b/build.cmd
index 927a9d70..3d227b80 100644
--- a/build.cmd
+++ b/build.cmd
@@ -1,5 +1,5 @@
@echo off
-setlocal
+setlocal EnableDelayedExpansion
REM Build and optionally publish sub projects
REM
@@ -49,7 +49,12 @@ set projects=jit-diff jit-dasm jit-analyze jit-format pmi jit-dasm-pmi jit-decis
REM Build each project
for %%p in (%projects%) do (
if %publish%==true (
- dotnet publish -c %buildType% -o %appInstallDir% .\src\%%p
+ REM Publish src/pmi project without single-file, so it can be executed with a custom build of the runtime/JIT
+ if "%%p"=="pmi" (
+ dotnet publish -c %buildType% -o %appInstallDir% .\src\%%p
+ ) else (
+ dotnet publish -c %buildType% -o %appInstallDir% .\src\%%p -p:PublishSingleFile=true
+ )
if errorlevel 1 echo ERROR: dotnet publish failed for .\src\%%p.&set __ExitCode=1
) else (
dotnet build -c %buildType% .\src\%%p
@@ -62,7 +67,7 @@ exit /b %__ExitCode%
:usage
echo.
-echo build.cmd [-b ^] [-f] [-h] [-p]
+echo build.cmd [-b ^] [-h] [-p]
echo.
echo -b ^ : Build type, can be Debug or Release.
echo -h : Show this message.
diff --git a/build.sh b/build.sh
index 9932aa58..7fb326d8 100755
--- a/build.sh
+++ b/build.sh
@@ -9,7 +9,7 @@
function usage
{
echo ""
- echo "build.sh [-b ] [-f] [-h] [-p]"
+ echo "build.sh [-b ] [-h] [-p]"
echo ""
echo " -b : Build type, can be Debug or Release."
echo " -h : Show this message."
@@ -21,17 +21,13 @@ function usage
__ErrMsgPrefix="ERROR: "
final_exit_code=0
buildType="Release"
-publish=false
-workingDir="$PWD"
-cd "`dirname \"$0\"`"
-scriptDir="$PWD"
-cd $workingDir
-platform="`dotnet --info | awk '/RID/ {print $2}'`"
+publish=0
+scriptDir="$(cd "$(dirname "$0")" || exit; pwd -P)"
# default install in 'bin' dir at script location
appInstallDir="$scriptDir/bin"
# process for '-h', '-p', '-b '
-while getopts "hpfb:" opt; do
+while getopts "hpb:" opt; do
case "$opt" in
h)
usage
@@ -41,7 +37,10 @@ while getopts "hpfb:" opt; do
buildType=$OPTARG
;;
p)
- publish=true
+ publish=1
+ ;;
+ *) echo "ERROR: unknown argument $opt"
+ exit 1
;;
esac
done
@@ -52,15 +51,19 @@ declare -a projects=(jit-dasm jit-diff jit-analyze jit-format pmi jit-dasm-pmi j
# for each project either build or publish
for proj in "${projects[@]}"
do
- if [ "$publish" == true ]; then
- dotnet publish -c $buildType -o $appInstallDir ./src/$proj
+ if [ "$publish" = 1 ]; then
+ case "$proj" in
+ # Publish src/pmi project without single-file, so it can be executed with a custom build of the runtime/JIT
+ pmi) dotnet publish -c "$buildType" -o "$appInstallDir" ./src/"$proj" ;;
+ *) dotnet publish -c "$buildType" -o "$appInstallDir" ./src/"$proj" -p:PublishSingleFile=true ;;
+ esac
exit_code=$?
if [ $exit_code != 0 ]; then
echo "${__ErrMsgPrefix}dotnet publish of ./src/${proj} failed."
final_exit_code=1
fi
else
- dotnet build -c $buildType ./src/$proj
+ dotnet build -c "$buildType" ./src/"$proj"
exit_code=$?
if [ $exit_code != 0 ]; then
echo "${__ErrMsgPrefix}dotnet build of ./src/${proj} failed."
@@ -69,4 +72,4 @@ do
fi
done
-exit $final_exit_code
+exit "$final_exit_code"
diff --git a/doc/diffs.md b/doc/diffs.md
index d14d3c2b..b52c8e42 100644
--- a/doc/diffs.md
+++ b/doc/diffs.md
@@ -66,11 +66,11 @@ build.sh [-b ] [-f] [-h] [-p] [-t ]
By default, assembly code output (aka, "dasm") is generated by running crossgen
with a specified JIT to compile a specified set of assemblies, by setting the
following JIT environment variables to generate the output:
-* `DOTNET_NgenDisasm`
-* `DOTNET_NgenUnwindDump`
-* `DOTNET_NgenEHDump`
+* `DOTNET_JitDisasm`
+* `DOTNET_JitUnwindDump`
+* `DOTNET_JitEHDump`
* `DOTNET_JitDiffableDasm`
-* optionally, `DOTNET_NgenGCDump`
+* optionally, `DOTNET_JitGCDump`
Generating "diffs" involves generating assembly code output for both a baseline
and a "diff" JIT, and comparing the results.
diff --git a/src/cijobs/CIClient.cs b/src/cijobs/CIClient.cs
new file mode 100644
index 00000000..b7133a3e
--- /dev/null
+++ b/src/cijobs/CIClient.cs
@@ -0,0 +1,180 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ManagedCodeGen
+{
+ // Wrap CI httpClient with focused APIs for product, job, and build.
+ // This logic is seperate from listing/copying and just extracts data.
+ internal sealed class CIClient
+ {
+ private HttpClient _client;
+
+ public CIClient(string server)
+ {
+ _client = new HttpClient();
+ _client.BaseAddress = new Uri(server);
+ _client.DefaultRequestHeaders.Accept.Clear();
+ _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+ _client.Timeout = Timeout.InfiniteTimeSpan;
+ }
+
+ public async Task DownloadProduct(string messageString, string outputPath, string contentPath)
+ {
+ Console.WriteLine("Downloading: {0}", messageString);
+
+ HttpResponseMessage response = await _client.GetAsync(messageString);
+
+ bool downloaded = false;
+
+ if (response.IsSuccessStatusCode)
+ {
+ var zipPath = Path.Combine(outputPath, Path.GetFileName(contentPath));
+ using (var outputStream = System.IO.File.Create(zipPath))
+ {
+ Stream inputStream = await response.Content.ReadAsStreamAsync();
+ inputStream.CopyTo(outputStream);
+ }
+ downloaded = true;
+ }
+ else
+ {
+ Console.Error.WriteLine("Zip not found!");
+ }
+
+ return downloaded;
+ }
+
+ public async Task> GetProductJobs(string productName, string branchName)
+ {
+ string productString = $"job/{productName}/job/{branchName}/api/json?&tree=jobs[name,url]";
+
+ try
+ {
+ using HttpResponseMessage response = await _client.GetAsync(productString);
+
+ if (response.IsSuccessStatusCode)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+ var productJobs = JsonSerializer.Deserialize(json);
+ return productJobs.jobs;
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.Error.WriteLine("Error enumerating jobs: {0} {1}", ex.Message, ex.InnerException.Message);
+ }
+
+ return Enumerable.Empty();
+ }
+
+ public async Task> GetJobBuilds(string productName, string branchName,
+ string jobName, bool lastSuccessfulBuild, int number, string commit)
+ {
+ var jobString
+ = String.Format(@"job/{0}/job/{1}/job/{2}", productName, branchName, jobName);
+ var messageString
+ = String.Format("{0}/api/json?&tree=builds[number,url],lastSuccessfulBuild[number,url]",
+ jobString);
+ HttpResponseMessage response = await _client.GetAsync(messageString);
+
+ if (response.IsSuccessStatusCode)
+ {
+ var json = await response.Content.ReadAsStringAsync();
+ var jobBuilds = JsonSerializer.Deserialize(json);
+
+ if (lastSuccessfulBuild)
+ {
+ var lastSuccessfulNumber = jobBuilds.lastSuccessfulBuild.number;
+ jobBuilds.lastSuccessfulBuild.info = await GetJobBuildInfo(productName, branchName, jobName, lastSuccessfulNumber);
+ return Enumerable.Repeat(jobBuilds.lastSuccessfulBuild, 1);
+ }
+ else if (number != 0)
+ {
+ var builds = jobBuilds.builds;
+
+ var count = builds.Count();
+ for (int i = 0; i < count; i++)
+ {
+ var build = builds[i];
+ if (build.number == number)
+ {
+ build.info = await GetJobBuildInfo(productName, branchName, jobName, build.number);
+ return Enumerable.Repeat(build, 1);
+ }
+ }
+ return Enumerable.Empty();
+ }
+ else if (commit != null)
+ {
+ var builds = jobBuilds.builds;
+
+ var count = builds.Count();
+ for (int i = 0; i < count; i++)
+ {
+ var build = builds[i];
+ build.info = await GetJobBuildInfo(productName, branchName, jobName, build.number);
+ var actions = build.info.actions.Where(x => x.lastBuiltRevision.SHA1 != null);
+ foreach (var action in actions)
+ {
+ if (action.lastBuiltRevision.SHA1.Equals(commit, StringComparison.OrdinalIgnoreCase))
+ {
+ return Enumerable.Repeat(build, 1);
+ }
+ }
+ }
+ return Enumerable.Empty();
+ }
+ else
+ {
+ var builds = jobBuilds.builds;
+
+ var count = builds.Count();
+ for (int i = 0; i < count; i++)
+ {
+ var build = builds[i];
+ // fill in build info
+ build.info = await GetJobBuildInfo(productName, branchName, jobName, build.number);
+ builds[i] = build;
+ }
+
+ return jobBuilds.builds;
+ }
+ }
+ else
+ {
+ return Enumerable.Empty();
+ }
+ }
+
+ public async Task GetJobBuildInfo(string repoName, string branchName, string jobName, int number)
+ {
+ string buildString = String.Format("job/{0}/job/{1}/job/{2}/{3}",
+ repoName, branchName, jobName, number);
+ string buildMessage = String.Format("{0}/{1}", buildString,
+ "api/json?&tree=actions[lastBuiltRevision[SHA1]],artifacts[fileName,relativePath],result");
+ HttpResponseMessage response = await _client.GetAsync(buildMessage);
+
+ if (response.IsSuccessStatusCode)
+ {
+ var buildInfoJson = await response.Content.ReadAsStringAsync();
+ var info = JsonSerializer.Deserialize(buildInfoJson);
+ return info;
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+}
diff --git a/src/cijobs/CIJobsRootCommand.cs b/src/cijobs/CIJobsRootCommand.cs
new file mode 100644
index 00000000..25a18ab7
--- /dev/null
+++ b/src/cijobs/CIJobsRootCommand.cs
@@ -0,0 +1,188 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.Threading.Tasks;
+
+namespace ManagedCodeGen
+{
+ internal sealed class CIJobsRootCommand : RootCommand
+ {
+ public Option Server { get; } =
+ new(new[] { "--server", "-s" }, "Url of the server. Defaults to http://ci.dot.net/");
+ public Option JobName { get; } =
+ new(new[] { "--job", "-j" }, "Name of the job.");
+ public Option BranchName { get; } =
+ new(new[] { "--branch", "-b" }, () => "master", "Name of the branch.");
+ public Option RepoName { get; } =
+ new(new[] { "--repo", "-r" }, () => "dotnet_coreclr", "Name of the repo (e.g. dotnet_corefx or dotnet_coreclr).");
+ public Option MatchPattern { get; } =
+ new(new[] { "--match", "-m" }, "Regex pattern used to select jobs output.");
+ public Option JobNumber { get; } =
+ new(new[] { "--number", "-n" }, "Job number.");
+ public Option ShowLastSuccessful { get; } =
+ new(new[] { "--last-successful", "-l", }, "Show last successful build.");
+ public Option Commit { get; } =
+ new(new[] { "--commit", "-c", }, "List build at this commit.");
+ public Option ShowArtifacts { get; } =
+ new(new[] { "--artifacts", "-a" }, "Show job artifacts on server.");
+ public Option OutputPath { get; } =
+ new(new[] { "--output", "-o" }, "The path where output will be placed.");
+ public Option OutputRoot { get; } =
+ new("--output-root", "The root directory where output will be placed. A subdirectory named by job and build number will be created within this to store the output.");
+ public Option Unzip { get; } =
+ new(new[] { "--unzip", "-u" }, "Unzip copied artifacts");
+ public Option ContentPath { get; } =
+ new(new[] { "--ContentPath", "-p" }, "Relative product zip path. Default is artifact/bin/Product/*zip*/Product.zip");
+
+ public ParseResult Result;
+
+ public CIJobsRootCommand(string[] args) : base("Continuous integration build jobs tool")
+ {
+ List errors = new();
+
+ Command listCommand = new("list", "List jobs on dotnet-ci.cloudapp.net for the repo.")
+ {
+ Server,
+ JobName,
+ BranchName,
+ RepoName,
+ MatchPattern,
+ JobNumber,
+ ShowLastSuccessful,
+ Commit,
+ ShowArtifacts
+ };
+
+ listCommand.SetHandler(context => TryExecuteWithContextAsync(context, "list", result =>
+ {
+ int jobNumber = result.GetValue(JobNumber);
+ bool showLastSuccessful = result.GetValue(ShowLastSuccessful);
+ string commit = result.GetValue(Commit);
+
+ if (result.FindResultFor(JobNumber) == null)
+ {
+ if (jobNumber != 0)
+ {
+ errors.Add("Must select --job to specify --number .");
+ }
+
+ if (showLastSuccessful)
+ {
+ errors.Add("Must select --job to specify --last_successful.");
+ }
+
+ if (commit != null)
+ {
+ errors.Add("Must select --job to specify --commit .");
+ }
+
+ if (result.GetValue(ShowArtifacts))
+ {
+ errors.Add("Must select --job to specify --artifacts.");
+ }
+ }
+ else
+ {
+ if (Convert.ToInt32(jobNumber != 0) + Convert.ToInt32(showLastSuccessful) + Convert.ToInt32(commit != null) > 1)
+ {
+ errors.Add("Must have at most one of --number , --last_successful, and --commit for list.");
+ }
+
+ if (!string.IsNullOrEmpty(result.GetValue(MatchPattern)))
+ {
+ errors.Add("Match pattern not valid with --job");
+ }
+ }
+ }));
+
+ AddCommand(listCommand);
+
+ Command copyCommand = new("copy", @"Copies job artifacts from dotnet-ci.cloudapp.net. This
+command copies a zip of artifacts from a repo (defaulted to
+dotnet_coreclr). The default location of the zips is the
+Product sub-directory, though that can be changed using the
+ContentPath(p) parameter")
+ {
+ Server,
+ JobName,
+ BranchName,
+ RepoName,
+ JobNumber,
+ ShowLastSuccessful,
+ Commit,
+ ShowArtifacts,
+ OutputPath,
+ OutputRoot,
+ Unzip,
+ ContentPath
+ };
+
+ copyCommand.SetHandler(context => TryExecuteWithContextAsync(context, "copy", result =>
+ {
+ if (result.GetValue(JobName) == null)
+ {
+ errors.Add("Must have --job for copy.");
+ }
+
+ int jobNumber = result.GetValue(JobNumber);
+ bool shwoLastSuccessful = result.GetValue(ShowLastSuccessful);
+ string commit = result.GetValue(Commit);
+ if (jobNumber == 0 && !shwoLastSuccessful && commit == null)
+ {
+ errors.Add("Must have --number , --last_successful, or --commit for copy.");
+ }
+
+ if (Convert.ToInt32(jobNumber != 0) + Convert.ToInt32(shwoLastSuccessful) + Convert.ToInt32(commit != null) > 1)
+ {
+ errors.Add("Must have only one of --number , --last_successful, and --commit for copy.");
+ }
+
+ string outputPath = result.GetValue(OutputPath);
+ string outputRoot = result.GetValue(OutputRoot);
+ if (outputPath == null && outputRoot == null)
+ {
+ errors.Add("Must specify either --output or --output_root for copy.");
+ }
+
+ if (outputPath != null && outputRoot != null)
+ {
+ errors.Add("Must specify only one of --output or --output_root .");
+ }
+ }));
+
+ AddCommand(copyCommand);
+
+ async Task TryExecuteWithContextAsync(InvocationContext context, string name, Action validate)
+ {
+ Result = context.ParseResult;
+ try
+ {
+ validate(Result);
+ if (errors.Count > 0)
+ {
+ throw new Exception(string.Join(Environment.NewLine, errors));
+ }
+
+ context.ExitCode = await new Program(this).RunAsync(name);
+ }
+ catch (Exception e)
+ {
+ Console.ResetColor();
+ Console.ForegroundColor = ConsoleColor.Red;
+
+ Console.Error.WriteLine("Error: " + e.Message);
+ Console.Error.WriteLine(e.ToString());
+
+ Console.ResetColor();
+
+ context.ExitCode = 1;
+ }
+ }
+ }
+ }
+}
diff --git a/src/cijobs/Models.cs b/src/cijobs/Models.cs
new file mode 100644
index 00000000..1ccac116
--- /dev/null
+++ b/src/cijobs/Models.cs
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+
+namespace ManagedCodeGen
+{
+ internal class Artifact
+ {
+ public string fileName { get; set; }
+ public string relativePath { get; set; }
+ }
+
+ internal class Revision
+ {
+ public string SHA1 { get; set; }
+ }
+
+ internal class Action
+ {
+ public Revision lastBuiltRevision { get; set; }
+ }
+
+ internal class BuildInfo
+ {
+ public List actions { get; set; }
+ public List artifacts { get; set; }
+ public string result { get; set; }
+ }
+
+ internal class Job
+ {
+ public string name { get; set; }
+ public string url { get; set; }
+ }
+
+ internal class ProductJobs
+ {
+ public List jobs { get; set; }
+ }
+
+ internal class Build
+ {
+ public int number { get; set; }
+ public string url { get; set; }
+ public BuildInfo info { get; set; }
+ }
+
+ internal class JobBuilds
+ {
+ public List builds { get; set; }
+ public Build lastSuccessfulBuild { get; set; }
+ }
+}
diff --git a/src/cijobs/Program.cs b/src/cijobs/Program.cs
new file mode 100755
index 00000000..8410bf6a
--- /dev/null
+++ b/src/cijobs/Program.cs
@@ -0,0 +1,245 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// cijobs - Continuous integration build jobs tool enables the listing of
+// jobs built in the CI system as well as downloading their artifacts. This
+// functionality allows for the speed up of some common dev tasks by taking
+// advantage of work being done in the cloud.
+//
+// Scenario 1: Start new work. When beginning a new set of changes, listing
+// job status can help you find a commit to start your work from. The tool
+// answers questions like "are the CentOS build jobs passing?" and "what was
+// the commit hash for the last successful Tizen arm32 build?"
+//
+// Scenario 2: Copy artifacts to speed up development flow. The tool enables
+// developers to download builds from the cloud so that developers can avoid
+// rebuilding baseline tools on a local machine. Need the crossgen tool for
+// the baseline commit for OSX diffs? Cijobs makes this easy to copy to your
+// system.
+//
+
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.CommandLine.Parsing;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace ManagedCodeGen
+{
+ internal sealed class Program
+ {
+ private readonly CIJobsRootCommand _command;
+ private string _repoName;
+ private string _branchName;
+ private string _commit;
+ private string _jobName;
+ private int _jobNumber;
+ private string _contentPath;
+ private bool _showLastSuccessful;
+
+ public Program(CIJobsRootCommand command)
+ {
+ _command = command;
+
+ _repoName = Get(command.RepoName);
+ _branchName = Get(command.BranchName);
+ _commit = Get(_command.Commit);
+ _jobName = Get(command.JobName);
+ _jobNumber = Get(command.JobNumber);
+ _showLastSuccessful = Get(command.ShowLastSuccessful);
+ _contentPath = Get(command.ContentPath) ?? "artifact/bin/Product/*zip*/Product.zip";
+ }
+
+ // Main entry point. Simply set up a httpClient to access the CI
+ // and switch on the command to invoke underlying logic.
+ public async Task RunAsync(string name)
+ {
+ CIClient cic = new(Get(_command.Server));
+
+ if (name == "list")
+ return await ListAsync(cic);
+
+ return await CopyAsync(cic);
+ }
+
+ private T Get(Option option) => _command.Result.GetValue(option);
+
+ private static Task Main(string[] args) =>
+ new CommandLineBuilder(new CIJobsRootCommand(args))
+ .UseVersionOption("--version", "-v")
+ .UseHelp()
+ .UseParseErrorReporting()
+ .Build()
+ .InvokeAsync(args);
+
+ // List jobs and their details from the given project on .NETCI Jenkins instance.
+ // List functionality:
+ // if --job is not specified, ListOption.Jobs, list jobs under branch.
+ // (default is "master" set in root command).
+ // if --job is specified, ListOption.Builds, list job builds with details.
+ // --number, --last_successful, or -commit can be used to specify specific job
+ //
+ private async Task ListAsync(CIClient cic)
+ {
+ if (_jobName == null)
+ {
+ var jobs = await cic.GetProductJobs(_repoName, _branchName);
+ string matchPattern = Get(_command.MatchPattern);
+
+ if (matchPattern != null)
+ {
+ var pattern = new Regex(matchPattern);
+ PrettyJobs(jobs.Where(x => pattern.IsMatch(x.name)));
+ }
+ else
+ {
+ PrettyJobs(jobs);
+ }
+ }
+ else
+ {
+ var builds = await cic.GetJobBuilds(_repoName, _branchName,
+ _jobName, _showLastSuccessful,
+ _jobNumber, _commit);
+
+ if (_showLastSuccessful && builds.Any())
+ {
+ Console.WriteLine("Last successful build:");
+ }
+
+ PrettyBuilds(builds, Get(_command.ShowArtifacts));
+ }
+
+ return 0;
+ }
+
+ private static void PrettyJobs(IEnumerable jobs)
+ {
+ foreach (var job in jobs)
+ {
+ Console.WriteLine("job {0}", job.name);
+ }
+ }
+
+ private static void PrettyBuilds(IEnumerable buildList, bool artifacts = false)
+ {
+ foreach (var build in buildList)
+ {
+ var result = build.info.result;
+ if (result != null)
+ {
+ Console.Write("build {0} - {1} : ", build.number, result);
+ PrettyBuildInfo(build.info, artifacts);
+ }
+ }
+ }
+
+ private static void PrettyBuildInfo(BuildInfo info, bool artifacts = false)
+ {
+ var actions = info.actions.Where(x => x.lastBuiltRevision.SHA1 != null);
+
+ if (actions.Any())
+ {
+ var action = actions.First();
+ Console.WriteLine("commit {0}", action.lastBuiltRevision.SHA1);
+ }
+ else
+ {
+ Console.WriteLine("");
+ }
+
+ if (artifacts)
+ {
+ Console.WriteLine(" artifacts:");
+ foreach (var artifact in info.artifacts)
+ {
+ Console.WriteLine(" {0}", artifact.relativePath);
+ }
+ }
+ }
+
+ // Based on the config, copy down the artifacts for the referenced job.
+ // Today this is just the product bits. This code also knows how to install
+ // the bits into the asmdiff.json config file.
+ public async Task CopyAsync(CIClient cic)
+ {
+ if (_showLastSuccessful)
+ {
+ // Query last successful build and extract the number.
+ var builds = await cic.GetJobBuilds(_repoName, _branchName, _jobName, true, 0, null);
+
+ if (!builds.Any())
+ {
+ Console.WriteLine("Last successful not found on server.");
+ return -1;
+ }
+
+ Build lastSuccess = builds.First();
+ _jobNumber = lastSuccess.number;
+ }
+ else if (_commit != null)
+ {
+ var builds = await cic.GetJobBuilds(_repoName, _branchName, _jobName, false, 0, _commit);
+
+ if (!builds.Any())
+ {
+ Console.WriteLine("Commit not found on server.");
+ return -1;
+ }
+
+ Build commitBuild = builds.First();
+ _jobNumber = commitBuild.number;
+ }
+
+ string outputPath;
+ string outputRoot = Get(_command.OutputRoot);
+
+ if (outputRoot != null)
+ {
+ outputPath = Get(_command.OutputPath);
+ }
+ else
+ {
+ string tag = $"{_jobName}-{_jobNumber}";
+ outputPath = Path.Combine(outputRoot, tag);
+ }
+
+ if (Directory.Exists(outputPath))
+ {
+ Console.WriteLine("Warning: directory {0} already exists.", outputPath);
+ }
+
+ // Create directory if it doesn't exist.
+ Directory.CreateDirectory(outputPath);
+
+ // Pull down the zip file.
+ await DownloadZip(cic, outputPath);
+
+ return 0;
+ }
+
+ // Download zip file. It's arguable that this should be in the
+ private async Task DownloadZip(CIClient cic, string outputPath)
+ {
+ string messageString = $"job/{_repoName}/job/{_branchName}/job/{_jobName}/{_jobNumber}/{_contentPath}";
+
+ // Copy product tools to output location.
+ bool success = await cic.DownloadProduct(messageString, outputPath, _contentPath);
+
+ if (success && Get(_command.Unzip))
+ {
+ // unzip archive in place.
+ var zipPath = Path.Combine(outputPath, Path.GetFileName(_contentPath));
+ Console.WriteLine("Unzipping: {0}", zipPath);
+ ZipFile.ExtractToDirectory(zipPath, outputPath);
+ }
+ }
+ }
+}
diff --git a/src/cijobs/cijobs.cs b/src/cijobs/cijobs.cs
deleted file mode 100755
index 81918adf..00000000
--- a/src/cijobs/cijobs.cs
+++ /dev/null
@@ -1,678 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-///////////////////////////////////////////////////////////////////////////////
-//
-// cijobs - Continuous integration build jobs tool enables the listing of
-// jobs built in the CI system as well as downloading their artifacts. This
-// functionality allows for the speed up of some common dev tasks by taking
-// advantage of work being done in the cloud.
-//
-// Scenario 1: Start new work. When beginning a new set of changes, listing
-// job status can help you find a commit to start your work from. The tool
-// answers questions like "are the CentOS build jobs passing?" and "what was
-// the commit hash for the last successful Tizen arm32 build?"
-//
-// Scenario 2: Copy artifacts to speed up development flow. The tool enables
-// developers to download builds from the cloud so that developers can avoid
-// rebuilding baseline tools on a local machine. Need the crossgen tool for
-// the baseline commit for OSX diffs? Cijobs makes this easy to copy to your
-// system.
-//
-
-
-using System;
-using System.Collections.Generic;
-using System.CommandLine;
-using System.IO;
-using System.IO.Compression;
-using System.Linq;
-using System.Net.Http;
-using System.Net.Http.Headers;
-using System.Text.Json;
-using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace ManagedCodeGen
-{
- internal class cijobs
- {
- // Supported commands. List to view information from the CI system, and Copy to download artifacts.
- public enum Command
- {
- List,
- Copy
- }
-
- // List options control what level and level of detail to put out.
- // Jobs lists jobs under a product.
- // Builds lists build instances under a job.
- // Number lists a particular builds info.
- public enum ListOption
- {
- Invalid,
- Jobs,
- Builds
- }
-
- // Define options to be parsed
- public class Config
- {
- private ArgumentSyntax _syntaxResult;
- private Command _command = Command.List;
- private ListOption _listOption = ListOption.Invalid;
- private string _server = "http://ci.dot.net/";
- private string _jobName;
- private string _contentPath;
- private string _repoName = "dotnet_coreclr";
- private int _number = 0;
- private string _matchPattern = String.Empty;
- private string _branchName = "master";
- private bool _lastSuccessful = false;
- private string _commit;
- private bool _unzip = false;
- private string _outputPath;
- private string _outputRoot;
- private bool _artifacts = false;
-
- public Config(string[] args)
- {
- _syntaxResult = ArgumentSyntax.Parse(args, syntax =>
- {
- // NOTE!!! - Commands and their options are ordered. Moving an option out of line
- // could move it to another command. Take a careful look at how they're organized
- // before changing.
- syntax.DefineCommand("list", ref _command, Command.List,
- "List jobs on dotnet-ci.cloudapp.net for the repo.");
- syntax.DefineOption("s|server", ref _server, "Url of the server. Defaults to http://ci.dot.net/");
- syntax.DefineOption("j|job", ref _jobName, "Name of the job.");
- syntax.DefineOption("b|branch", ref _branchName,
- "Name of the branch (default is master).");
- syntax.DefineOption("r|repo", ref _repoName,
- "Name of the repo (e.g. dotnet_corefx or dotnet_coreclr). Default is dotnet_coreclr.");
- syntax.DefineOption("m|match", ref _matchPattern,
- "Regex pattern used to select jobs output.");
- syntax.DefineOption("n|number", ref _number, "Job number.");
- syntax.DefineOption("l|last_successful", ref _lastSuccessful,
- "List last successful build.");
- syntax.DefineOption("c|commit", ref _commit, "List build at this commit.");
- syntax.DefineOption("a|artifacts", ref _artifacts, "List job artifacts on server.");
-
- syntax.DefineCommand("copy", ref _command, Command.Copy,
- "Copies job artifacts from dotnet-ci.cloudapp.net. "
- + "This command copies a zip of artifacts from a repo (defaulted to dotnet_coreclr)."
- + " The default location of the zips is the Product sub-directory, though "
- + "that can be changed using the ContentPath(p) parameter");
- syntax.DefineOption("s|server", ref _server, "Url of the server. Defaults to http://ci.dot.net/");
- syntax.DefineOption("j|job", ref _jobName, "Name of the job.");
- syntax.DefineOption("n|number", ref _number, "Job number.");
- syntax.DefineOption("l|last_successful", ref _lastSuccessful,
- "Copy last successful build.");
- syntax.DefineOption("c|commit", ref _commit, "Copy this commit.");
- syntax.DefineOption("b|branch", ref _branchName,
- "Name of the branch (default is master).");
- syntax.DefineOption("r|repo", ref _repoName,
- "Name of the repo (e.g. dotnet_corefx or dotnet_coreclr). Default is dotnet_coreclr.");
- syntax.DefineOption("o|output", ref _outputPath, "The path where output will be placed.");
- syntax.DefineOption("or|output_root", ref _outputRoot,
- "The root directory where output will be placed. A subdirectory named by job and build number will be created within this to store the output.");
- syntax.DefineOption("u|unzip", ref _unzip, "Unzip copied artifacts");
- syntax.DefineOption("p|ContentPath", ref _contentPath,
- "Relative product zip path. Default is artifact/bin/Product/*zip*/Product.zip");
- });
-
- // Run validation code on parsed input to ensure we have a sensible scenario.
- validate();
- }
-
- private void validate()
- {
- if (!Uri.IsWellFormedUriString(_server, UriKind.Absolute))
- {
- _syntaxResult.ReportError($"Invalid uri: {_server}.");
- }
- switch (_command)
- {
- case Command.List:
- {
- validateList();
- }
- break;
- case Command.Copy:
- {
- validateCopy();
- }
- break;
- }
- }
-
- private void validateCopy()
- {
- if (_jobName == null)
- {
- _syntaxResult.ReportError("Must have --job for copy.");
- }
-
- if (_number == 0 && !_lastSuccessful && _commit == null)
- {
- _syntaxResult.ReportError("Must have --number , --last_successful, or --commit for copy.");
- }
-
- if (Convert.ToInt32(_number != 0) + Convert.ToInt32(_lastSuccessful) + Convert.ToInt32(_commit != null) > 1)
- {
- _syntaxResult.ReportError("Must have only one of --number , --last_successful, and --commit for copy.");
- }
-
- if ((_outputPath == null) && (_outputRoot == null))
- {
- _syntaxResult.ReportError("Must specify either --output or --output_root for copy.");
- }
-
- if ((_outputPath != null) && (_outputRoot != null))
- {
- _syntaxResult.ReportError("Must specify only one of --output or --output_root .");
- }
-
- if (_contentPath == null)
- {
- _contentPath = "artifact/bin/Product/*zip*/Product.zip";
- }
- }
-
- private void validateList()
- {
- if (_jobName != null)
- {
- _listOption = ListOption.Builds;
-
- if (Convert.ToInt32(_number != 0) + Convert.ToInt32(_lastSuccessful) + Convert.ToInt32(_commit != null) > 1)
- {
- _syntaxResult.ReportError("Must have at most one of --number , --last_successful, and --commit for list.");
- }
-
- if (_matchPattern != String.Empty)
- {
- _syntaxResult.ReportError("Match pattern not valid with --job");
- }
- }
- else
- {
- _listOption = ListOption.Jobs;
-
- if (_number != 0)
- {
- _syntaxResult.ReportError("Must select --job to specify --number .");
- }
-
- if (_lastSuccessful)
- {
- _syntaxResult.ReportError("Must select --job to specify --last_successful.");
- }
-
- if (_commit != null)
- {
- _syntaxResult.ReportError("Must select --job to specify --commit .");
- }
-
- if (_artifacts)
- {
- _syntaxResult.ReportError("Must select --job to specify --artifacts.");
- }
- }
- }
-
- public Command DoCommand { get { return _command; } }
- public ListOption DoListOption { get { return _listOption; } }
- public string JobName { get { return _jobName; } }
- public string Server { get { return _server; } }
- public string ContentPath { get { return _contentPath; } }
- public int Number { get { return _number; } set { this._number = value; } }
- public string MatchPattern { get { return _matchPattern; } }
- public string BranchName { get { return _branchName; } }
- public string RepoName { get { return _repoName; } }
- public bool LastSuccessful { get { return _lastSuccessful; } }
- public string Commit { get { return _commit; } }
- public bool DoUnzip { get { return _unzip; } }
- public string OutputPath { get { return _outputPath; } }
- public string OutputRoot { get { return _outputRoot; } }
- public bool Artifacts { get { return _artifacts; } }
- }
-
- // The following block of simple structs maps to the data extracted from the CI system as json.
- // This allows to map it directly into C# and access it.
-
- // fields are assigned to by json deserializer
- #pragma warning disable 0649
-
- private struct Artifact
- {
- public string fileName;
- public string relativePath;
- }
-
- private struct Revision
- {
- public string SHA1;
- }
-
- private class Action
- {
- public Revision lastBuiltRevision;
- }
-
- private class BuildInfo
- {
- public List actions;
- public List artifacts;
- public string result;
- }
-
- private struct Job
- {
- public string name;
- public string url;
- }
-
- private struct ProductJobs
- {
- public List jobs;
- }
-
- private struct Build
- {
- public int number;
- public string url;
- public BuildInfo info;
- }
-
- private struct JobBuilds
- {
- public List builds;
- public Build lastSuccessfulBuild;
- }
-
- #pragma warning restore 0649
-
- // Main entry point. Simply set up a httpClient to access the CI
- // and switch on the command to invoke underlying logic.
- private static int Main(string[] args)
- {
- Config config = new Config(args);
- int error = 0;
-
- CIClient cic = new CIClient(config);
-
- Command currentCommand = config.DoCommand;
- switch (currentCommand)
- {
- case Command.List:
- {
- ListCommand.List(cic, config).Wait();
- break;
- }
- case Command.Copy:
- {
- CopyCommand.Copy(cic, config).Wait();
- break;
- }
- default:
- {
- Console.Error.WriteLine("super bad! why no command!");
- error = 1;
- break;
- }
- }
-
- return error;
- }
-
- // Wrap CI httpClient with focused APIs for product, job, and build.
- // This logic is seperate from listing/copying and just extracts data.
- private class CIClient
- {
- private HttpClient _client;
-
- public CIClient(Config config)
- {
- _client = new HttpClient();
- _client.BaseAddress = new Uri(config.Server);
- _client.DefaultRequestHeaders.Accept.Clear();
- _client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
- _client.Timeout = Timeout.InfiniteTimeSpan;
- }
-
- public async Task DownloadProduct(Config config, string outputPath, string contentPath)
- {
- string messageString
- = String.Format("job/{0}/job/{1}/job/{2}/{3}/{4}",
- config.RepoName, config.BranchName, config.JobName, config.Number, contentPath);
-
- Console.WriteLine("Downloading: {0}", messageString);
-
- HttpResponseMessage response = await _client.GetAsync(messageString);
-
- bool downloaded = false;
-
- if (response.IsSuccessStatusCode)
- {
- var zipPath = Path.Combine(outputPath, Path.GetFileName(contentPath));
- using (var outputStream = System.IO.File.Create(zipPath))
- {
- Stream inputStream = await response.Content.ReadAsStreamAsync();
- inputStream.CopyTo(outputStream);
- }
- downloaded = true;
- }
- else
- {
- Console.Error.WriteLine("Zip not found!");
- }
-
- return downloaded;
- }
-
- public async Task> GetProductJobs(string productName, string branchName)
- {
- string productString
- = String.Format("job/{0}/job/{1}/api/json?&tree=jobs[name,url]",
- productName, branchName);
-
- try
- {
- HttpResponseMessage response = await _client.GetAsync(productString);
-
- if (response.IsSuccessStatusCode)
- {
- var json = await response.Content.ReadAsStringAsync();
- var productJobs = JsonSerializer.Deserialize(json);
- return productJobs.jobs;
- }
- else
- {
- return Enumerable.Empty();
- }
- }
- catch (Exception ex)
- {
- Console.Error.WriteLine("Error enumerating jobs: {0} {1}", ex.Message, ex.InnerException.Message);
- return Enumerable.Empty();
- }
- }
-
- public async Task> GetJobBuilds(string productName, string branchName,
- string jobName, bool lastSuccessfulBuild, int number, string commit)
- {
- var jobString
- = String.Format(@"job/{0}/job/{1}/job/{2}", productName, branchName, jobName);
- var messageString
- = String.Format("{0}/api/json?&tree=builds[number,url],lastSuccessfulBuild[number,url]",
- jobString);
- HttpResponseMessage response = await _client.GetAsync(messageString);
-
- if (response.IsSuccessStatusCode)
- {
- var json = await response.Content.ReadAsStringAsync();
- var jobBuilds = JsonSerializer.Deserialize(json);
-
- if (lastSuccessfulBuild)
- {
- var lastSuccessfulNumber = jobBuilds.lastSuccessfulBuild.number;
- jobBuilds.lastSuccessfulBuild.info
- = await GetJobBuildInfo(productName, branchName, jobName, lastSuccessfulNumber);
- return Enumerable.Repeat(jobBuilds.lastSuccessfulBuild, 1);
- }
- else if (number != 0)
- {
- var builds = jobBuilds.builds;
-
- var count = builds.Count();
- for (int i = 0; i < count; i++)
- {
- var build = builds[i];
- if (build.number == number)
- {
- build.info = await GetJobBuildInfo(productName, branchName, jobName, build.number);
- return Enumerable.Repeat(build, 1);
- }
- }
- return Enumerable.Empty();
- }
- else if (commit != null)
- {
- var builds = jobBuilds.builds;
-
- var count = builds.Count();
- for (int i = 0; i < count; i++)
- {
- var build = builds[i];
- build.info = await GetJobBuildInfo(productName, branchName, jobName, build.number);
- var actions = build.info.actions.Where(x => x.lastBuiltRevision.SHA1 != null);
- foreach (var action in actions)
- {
- if (action.lastBuiltRevision.SHA1.Equals(commit, StringComparison.OrdinalIgnoreCase))
- {
- return Enumerable.Repeat(build, 1);
- }
- }
- }
- return Enumerable.Empty();
- }
- else
- {
- var builds = jobBuilds.builds;
-
- var count = builds.Count();
- for (int i = 0; i < count; i++)
- {
- var build = builds[i];
- // fill in build info
- build.info = await GetJobBuildInfo(productName, branchName, jobName, build.number);
- builds[i] = build;
- }
-
- return jobBuilds.builds;
- }
- }
- else
- {
- return Enumerable.Empty();
- }
- }
-
- public async Task GetJobBuildInfo(string repoName, string branchName, string jobName, int number)
- {
- string buildString = String.Format("job/{0}/job/{1}/job/{2}/{3}",
- repoName, branchName, jobName, number);
- string buildMessage = String.Format("{0}/{1}", buildString,
- "api/json?&tree=actions[lastBuiltRevision[SHA1]],artifacts[fileName,relativePath],result");
- HttpResponseMessage response = await _client.GetAsync(buildMessage);
-
- if (response.IsSuccessStatusCode)
- {
- var buildInfoJson = await response.Content.ReadAsStringAsync();
- var info = JsonSerializer.Deserialize(buildInfoJson);
- return info;
- }
- else
- {
- return null;
- }
- }
- }
-
- // Implementation of the list command.
- private class ListCommand
- {
- // List jobs and their details from the given project on .NETCI Jenkins instance.
- // List functionality:
- // if --job is not specified, ListOption.Jobs, list jobs under branch.
- // (default is "master" set in Config).
- // if --job is specified, ListOption.Builds, list job builds with details.
- // --number, --last_successful, or -commit can be used to specify specific job
- //
- public static async Task List(CIClient cic, Config config)
- {
- switch (config.DoListOption)
- {
- case ListOption.Jobs:
- {
- var jobs = await cic.GetProductJobs(config.RepoName, config.BranchName);
-
- if (config.MatchPattern != null)
- {
- var pattern = new Regex(config.MatchPattern);
- PrettyJobs(jobs.Where(x => pattern.IsMatch(x.name)));
- }
- else
- {
- PrettyJobs(jobs);
- }
- }
- break;
- case ListOption.Builds:
- {
- var builds = await cic.GetJobBuilds(config.RepoName, config.BranchName,
- config.JobName, config.LastSuccessful,
- config.Number, config.Commit);
-
- if (config.LastSuccessful && builds.Any())
- {
- Console.WriteLine("Last successful build:");
- }
-
- PrettyBuilds(builds, config.Artifacts);
- }
- break;
- default:
- {
- Console.Error.WriteLine("Unknown list option!");
- }
- break;
- }
- }
-
- private static void PrettyJobs(IEnumerable jobs)
- {
- foreach (var job in jobs)
- {
- Console.WriteLine("job {0}", job.name);
- }
- }
-
- private static void PrettyBuilds(IEnumerable buildList, bool artifacts = false)
- {
- foreach (var build in buildList)
- {
- var result = build.info.result;
- if (result != null)
- {
- Console.Write("build {0} - {1} : ", build.number, result);
- PrettyBuildInfo(build.info, artifacts);
- }
- }
- }
-
- private static void PrettyBuildInfo(BuildInfo info, bool artifacts = false)
- {
- var actions = info.actions.Where(x => x.lastBuiltRevision.SHA1 != null);
-
- if (actions.Any())
- {
- var action = actions.First();
- Console.WriteLine("commit {0}", action.lastBuiltRevision.SHA1);
- }
- else
- {
- Console.WriteLine("");
- }
-
- if (artifacts)
- {
- Console.WriteLine(" artifacts:");
- foreach (var artifact in info.artifacts)
- {
- Console.WriteLine(" {0}", artifact.relativePath);
- }
- }
- }
- }
-
- // Implementation of the copy command.
- private class CopyCommand
- {
- // Based on the config, copy down the artifacts for the referenced job.
- // Today this is just the product bits. This code also knows how to install
- // the bits into the asmdiff.json config file.
- public static async Task Copy(CIClient cic, Config config)
- {
- if (config.LastSuccessful)
- {
- // Query last successful build and extract the number.
- var builds = await cic.GetJobBuilds(config.RepoName, config.BranchName, config.JobName, true, 0, null);
-
- if (!builds.Any())
- {
- Console.WriteLine("Last successful not found on server.");
- return;
- }
-
- Build lastSuccess = builds.First();
- config.Number = lastSuccess.number;
- }
- else if (config.Commit != null)
- {
- var builds = await cic.GetJobBuilds(config.RepoName, config.BranchName, config.JobName, false, 0, config.Commit);
-
- if (!builds.Any())
- {
- Console.WriteLine("Commit not found on server.");
- return;
- }
-
- Build commitBuild = builds.First();
- config.Number = commitBuild.number;
- }
-
- string outputPath;
-
- if (config.OutputRoot == null)
- {
- outputPath = config.OutputPath;
- }
- else
- {
- string tag = String.Format("{0}-{1}", config.JobName, config.Number);
- outputPath = Path.Combine(config.OutputRoot, tag);
- }
-
- if (Directory.Exists(outputPath))
- {
- Console.WriteLine("Warning: directory {0} already exists.", outputPath);
- }
-
- // Create directory if it doesn't exist.
- Directory.CreateDirectory(outputPath);
-
- // Pull down the zip file.
- await DownloadZip(cic, config, outputPath, config.ContentPath);
- }
-
- // Download zip file. It's arguable that this should be in the
- private static async Task DownloadZip(CIClient cic, Config config, string outputPath, string contentPath)
- {
- // Copy product tools to output location.
- bool success = await cic.DownloadProduct(config, outputPath, contentPath);
-
- if (success && config.DoUnzip)
- {
- // unzip archive in place.
- var zipPath = Path.Combine(outputPath, Path.GetFileName(contentPath));
- Console.WriteLine("Unzipping: {0}", zipPath);
- ZipFile.ExtractToDirectory(zipPath, outputPath);
- }
- }
- }
- }
-}
diff --git a/src/jit-analyze/IEnumerableExtensions.cs b/src/jit-analyze/IEnumerableExtensions.cs
new file mode 100644
index 00000000..bbc4d26f
--- /dev/null
+++ b/src/jit-analyze/IEnumerableExtensions.cs
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace ManagedCodeGen
+{
+ // Allow Linq to be able to sum up MetricCollections
+ public static class IEnumerableExtensions
+ {
+ public static MetricCollection Sum(this IEnumerable source)
+ {
+ MetricCollection result = new MetricCollection();
+
+ foreach (MetricCollection s in source)
+ {
+ result.Add(s);
+ }
+
+ return result;
+ }
+
+ public static MetricCollection Sum(this IEnumerable source, Func selector)
+ {
+ return source.Select(x => selector(x)).Sum();
+ }
+ }
+}
diff --git a/src/jit-analyze/JitAnalyzeRootCommand.cs b/src/jit-analyze/JitAnalyzeRootCommand.cs
new file mode 100644
index 00000000..88e73411
--- /dev/null
+++ b/src/jit-analyze/JitAnalyzeRootCommand.cs
@@ -0,0 +1,145 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.Globalization;
+
+namespace ManagedCodeGen
+{
+ internal sealed class JitAnalyzeRootCommand : RootCommand
+ {
+ public Option BasePath { get; } =
+ new(new[] { "--base", "-b" }, "Base file or directory");
+ public Option DiffPath { get; } =
+ new(new[] { "--diff", "-d" }, "Diff file or directory");
+ public Option Recursive { get; } =
+ new(new[] { "--recursive", "-r" }, "Search directories recursively");
+ public Option FileExtension { get; } =
+ new("--file-extension", () => ".dasm", "File extension to look for");
+ public Option Count { get; } =
+ new(new[] { "--count", "-c" }, () => 20, "Count of files and methods (at most) to output in the summary. (count) improvements and (count) regressions of each will be included");
+ public Option Warn { get; } =
+ new(new[] { "--warn", "-w" }, "Generate warning output for files/methods that only exists in one dataset or the other (only in base or only in diff)");
+ public Option> Metrics { get; } =
+ new(new[] { "--metrics", "-m" }, () => new List { "CodeSize" }, $"Metrics to use for diff computations. Available metrics: {MetricCollection.ListMetrics()}");
+ public Option Note { get; } =
+ new("--note", "Descriptive note to add to summary output");
+ public Option NoReconcile { get; } =
+ new("--no-reconcile", "Do not reconcile unique methods in base/diff");
+ public Option Json { get; } =
+ new("--json", "Dump analysis data to specified file in JSON format");
+ public Option Tsv { get; } =
+ new("--tsv", "Dump analysis data to specified file in tab-separated format");
+ public Option MD { get; } =
+ new("--md", "Dump analysis data to specified file in markdown format");
+ public Option Filter { get; } =
+ new("--filter", "Only consider assembly files whose names match the filter");
+ public Option SkipTextDiff { get; } =
+ new("--skip-text-diff", "Skip analysis that checks for files that have textual diffs but no metric diffs");
+ public Option RetainOnlyTopFiles { get; } =
+ new("--retain-only-top-files", "Retain only the top 'count' improvements/regressions .dasm files. Delete other files. Useful in CI scenario to reduce the upload size");
+ public Option OverrideTotalBaseMetric { get; } =
+ new("--override-total-base-metric", result =>
+ {
+ string optionValue = result.Tokens[0].Value;
+ if (double.TryParse(optionValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var parsedValue))
+ return parsedValue;
+
+ result.ErrorMessage = $"Cannot parse argument '{optionValue}' for option '--override-total-base-metric' as expected type '{typeof(double).FullName}'.";
+ return 0;
+ }, false, "Override the total base metric shown in the output with this value. Useful when only changed .dasm files are present and these values are known");
+ public Option OverrideTotalDiffMetric { get; } =
+ new("--override-total-diff-metric", result =>
+ {
+ string optionValue = result.Tokens[0].Value;
+ if (double.TryParse(optionValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var parsedValue))
+ return parsedValue;
+
+ result.ErrorMessage = $"Cannot parse argument '{optionValue}' for option '--override-total-diff-metric' as expected type '{typeof(double).FullName}'.";
+ return 0;
+ }, false, "Override the total diff metric shown in the output with this value. Useful when only changed .dasm files are present and these values are known");
+ public Option IsDiffsOnly { get; } =
+ new("--is-diffs-only", "Specify that the disassembly files are only produced for contexts with diffs, so avoid producing output making assumptions about the number of contexts");
+ public Option IsSubsetOfDiffs { get; } =
+ new("--is-subset-of-diffs", "Specify that the disassembly files are only a subset of the contexts with diffs, so avoid producing output making assumptions about the remaining diffs");
+
+ public ParseResult Result;
+
+ public JitAnalyzeRootCommand(string[] args) : base("Compare and analyze `*.dasm` files from baseline/diff")
+ {
+ AddOption(BasePath);
+ AddOption(DiffPath);
+ AddOption(Recursive);
+ AddOption(FileExtension);
+ AddOption(Count);
+ AddOption(Warn);
+ AddOption(Metrics);
+ AddOption(Note);
+ AddOption(NoReconcile);
+ AddOption(Json);
+ AddOption(Tsv);
+ AddOption(MD);
+ AddOption(Filter);
+ AddOption(SkipTextDiff);
+ AddOption(RetainOnlyTopFiles);
+ AddOption(OverrideTotalBaseMetric);
+ AddOption(OverrideTotalDiffMetric);
+ AddOption(IsDiffsOnly);
+ AddOption(IsSubsetOfDiffs);
+
+ this.SetHandler(context =>
+ {
+ Result = context.ParseResult;
+
+ try
+ {
+ List errors = new();
+ if (Result.GetValue(BasePath) == null)
+ {
+ errors.Add("Base path (--base) is required");
+ }
+
+ if (Result.GetValue(DiffPath) == null)
+ {
+ errors.Add("Diff path (--diff) is required");
+ }
+
+ foreach (string metricName in Result.GetValue(Metrics))
+ {
+ if (!MetricCollection.ValidateMetric(metricName))
+ {
+ errors.Add($"Unknown metric '{metricName}'. Available metrics: {MetricCollection.ListMetrics()}");
+ }
+ }
+
+ if ((Result.FindResultFor(OverrideTotalBaseMetric) == null) != (Result.FindResultFor(OverrideTotalDiffMetric) == null))
+ {
+ errors.Add("override-total-base-metric and override-total-diff-metric must either both be specified or both not be specified");
+ }
+
+ if (errors.Count > 0)
+ {
+ throw new Exception(string.Join(Environment.NewLine, errors));
+ }
+
+ context.ExitCode = new Program(this).Run();
+ }
+ catch (Exception e)
+ {
+ Console.ResetColor();
+ Console.ForegroundColor = ConsoleColor.Red;
+
+ Console.Error.WriteLine("Error: " + e.Message);
+ Console.Error.WriteLine(e.ToString());
+
+ Console.ResetColor();
+
+ context.ExitCode = 1;
+ }
+ });
+ }
+ }
+}
diff --git a/src/jit-analyze/MetricCollection.cs b/src/jit-analyze/MetricCollection.cs
new file mode 100644
index 00000000..35307b2c
--- /dev/null
+++ b/src/jit-analyze/MetricCollection.cs
@@ -0,0 +1,152 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Text.Json.Serialization;
+
+namespace ManagedCodeGen
+{
+ public class MetricCollection
+ {
+ private static Dictionary s_metricNameToIndex;
+ private static Metric[] s_metrics;
+
+ static MetricCollection()
+ {
+ var derivedType = typeof(Metric);
+ var currentAssembly = Assembly.GetAssembly(derivedType);
+ s_metrics = currentAssembly.GetTypes()
+ .Where(t => t != derivedType && derivedType.IsAssignableFrom(t))
+ .Select(t => currentAssembly.CreateInstance(t.FullName)).Cast().ToArray();
+
+ s_metricNameToIndex = new Dictionary(s_metrics.Length);
+
+ for (int i = 0; i < s_metrics.Length; i++)
+ {
+ Metric m = s_metrics[i];
+ s_metricNameToIndex[m.Name] = i;
+ }
+ }
+
+ [JsonInclude]
+ private Metric[] metrics;
+
+ public MetricCollection()
+ {
+ metrics = new Metric[s_metrics.Length];
+ for (int i = 0; i < s_metrics.Length; i++)
+ {
+ metrics[i] = s_metrics[i].Clone();
+ }
+ }
+
+ public MetricCollection(MetricCollection other) : this()
+ {
+ this.SetValueFrom(other);
+ }
+
+ public static IEnumerable AllMetrics => s_metrics;
+
+ public Metric GetMetric(string metricName)
+ {
+ int index;
+ if (s_metricNameToIndex.TryGetValue(metricName, out index))
+ {
+ return metrics[index];
+ }
+ return null;
+ }
+
+ public static bool ValidateMetric(string name)
+ {
+ return s_metricNameToIndex.TryGetValue(name, out _);
+ }
+
+ public static string DisplayName(string metricName)
+ {
+ int index;
+ if (s_metricNameToIndex.TryGetValue(metricName, out index))
+ {
+ return s_metrics[index].DisplayName;
+ }
+ return "Unknown metric";
+ }
+
+ public static string ListMetrics()
+ {
+ StringBuilder sb = new StringBuilder();
+ bool isFirst = true;
+ foreach (string s in s_metricNameToIndex.Keys)
+ {
+ if (!isFirst) sb.Append(", ");
+ sb.Append(s);
+ isFirst = false;
+ }
+ return sb.ToString();
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ bool isFirst = true;
+ foreach (Metric m in metrics)
+ {
+ if (!isFirst) sb.Append(", ");
+ sb.Append($"{m.Name} {m.Unit} {m.ValueString}");
+ isFirst = false;
+ }
+ return sb.ToString();
+ }
+
+ public void Add(MetricCollection other)
+ {
+ for (int i = 0; i < metrics.Length; i++)
+ {
+ metrics[i].Add(other.metrics[i]);
+ }
+ }
+
+ public void Add(string metricName, double value)
+ {
+ Metric m = GetMetric(metricName);
+ m.Value += value;
+ }
+
+ public void Sub(MetricCollection other)
+ {
+ for (int i = 0; i < metrics.Length; i++)
+ {
+ metrics[i].Sub(other.metrics[i]);
+ }
+ }
+
+ public void Rel(MetricCollection other)
+ {
+ for (int i = 0; i < metrics.Length; i++)
+ {
+ metrics[i].Rel(other.metrics[i]);
+ }
+ }
+
+ public void SetValueFrom(MetricCollection other)
+ {
+ for (int i = 0; i < metrics.Length; i++)
+ {
+ metrics[i].SetValueFrom(other.metrics[i]);
+ }
+ }
+
+ public bool IsZero()
+ {
+ for (int i = 0; i < metrics.Length; i++)
+ {
+ if (metrics[i].Value != 0) return false;
+ }
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jit-analyze/Metrics.cs b/src/jit-analyze/Metrics.cs
new file mode 100644
index 00000000..fbf62929
--- /dev/null
+++ b/src/jit-analyze/Metrics.cs
@@ -0,0 +1,162 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace ManagedCodeGen
+{
+ public abstract class Metric
+ {
+ public virtual string Name { get; }
+ public virtual string DisplayName { get; }
+ public virtual string Unit { get; }
+ public virtual bool LowerIsBetter { get; }
+ public abstract Metric Clone();
+ public abstract string ValueString { get; }
+ public double Value { get; set; }
+
+ public void Add(Metric m)
+ {
+ Value += m.Value;
+ }
+
+ public void Sub(Metric m)
+ {
+ Value -= m.Value;
+ }
+
+ public void Rel(Metric m)
+ {
+ Value = (Value - m.Value) / m.Value;
+ }
+
+ public void SetValueFrom(Metric m)
+ {
+ Value = m.Value;
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+ }
+
+ public class CodeSizeMetric : Metric
+ {
+ public override string Name => "CodeSize";
+ public override string DisplayName => "Code Size";
+ public override string Unit => "byte";
+ public override bool LowerIsBetter => true;
+ public override Metric Clone() => new CodeSizeMetric();
+ public override string ValueString => $"{Value}";
+ }
+
+ public class PrologSizeMetric : Metric
+ {
+ public override string Name => "PrologSize";
+ public override string DisplayName => "Prolog Size";
+ public override string Unit => "byte";
+ public override bool LowerIsBetter => true;
+ public override Metric Clone() => new PrologSizeMetric();
+ public override string ValueString => $"{Value}";
+ }
+
+ public class PerfScoreMetric : Metric
+ {
+ public override string Name => "PerfScore";
+ public override string DisplayName => "Perf Score";
+ public override string Unit => "PerfScoreUnit";
+ public override bool LowerIsBetter => true;
+ public override Metric Clone() => new PerfScoreMetric();
+ public override string ValueString => $"{Value:F2}";
+ }
+
+ public class InstrCountMetric : Metric
+ {
+ public override string Name => "InstrCount";
+ public override string DisplayName => "Instruction Count";
+ public override string Unit => "Instruction";
+ public override bool LowerIsBetter => true;
+ public override Metric Clone() => new InstrCountMetric();
+ public override string ValueString => $"{Value}";
+ }
+
+ public class AllocSizeMetric : Metric
+ {
+ public override string Name => "AllocSize";
+ public override string DisplayName => "Allocation Size";
+ public override string Unit => "byte";
+ public override bool LowerIsBetter => true;
+ public override Metric Clone() => new AllocSizeMetric();
+ public override string ValueString => $"{Value}";
+ }
+
+ public class ExtraAllocBytesMetric : Metric
+ {
+ public override string Name => "ExtraAllocBytes";
+ public override string DisplayName => "Extra Allocation Size";
+ public override string Unit => "byte";
+ public override bool LowerIsBetter => true;
+ public override Metric Clone() => new ExtraAllocBytesMetric();
+ public override string ValueString => $"{Value}";
+ }
+ public class DebugClauseMetric : Metric
+ {
+ public override string Name => "DebugClauseCount";
+ public override string DisplayName => "Debug Clause Count";
+ public override string Unit => "Clause";
+ public override bool LowerIsBetter => true;
+ public override Metric Clone() => new DebugClauseMetric();
+ public override string ValueString => $"{Value}";
+ }
+
+ public class DebugVarMetric : Metric
+ {
+ public override string Name => "DebugVarCount";
+ public override string DisplayName => "Debug Variable Count";
+ public override string Unit => "Variable";
+ public override bool LowerIsBetter => true;
+ public override Metric Clone() => new DebugVarMetric();
+ public override string ValueString => $"{Value}";
+ }
+
+ /* LSRA specific */
+ public class SpillCountMetric : Metric
+ {
+ public override string Name => "SpillCount";
+ public override string DisplayName => "Spill Count";
+ public override string Unit => "Count";
+ public override bool LowerIsBetter => true;
+ public override Metric Clone() => new SpillCountMetric();
+ public override string ValueString => $"{Value}";
+ }
+
+ public class SpillWeightMetric : Metric
+ {
+ public override string Name => "SpillWeight";
+ public override string DisplayName => "Spill Weighted";
+ public override string Unit => "Count";
+ public override bool LowerIsBetter => true;
+ public override Metric Clone() => new SpillWeightMetric();
+ public override string ValueString => $"{Value}";
+ }
+
+ public class ResolutionCountMetric : Metric
+ {
+ public override string Name => "ResolutionCount";
+ public override string DisplayName => "Resolution Count";
+ public override string Unit => "Count";
+ public override bool LowerIsBetter => true;
+ public override Metric Clone() => new ResolutionCountMetric();
+ public override string ValueString => $"{Value}";
+ }
+
+ public class ResolutionWeightMetric : Metric
+ {
+ public override string Name => "ResolutionWeight";
+ public override string DisplayName => "Resolution Weighted";
+ public override string Unit => "Count";
+ public override bool LowerIsBetter => true;
+ public override Metric Clone() => new ResolutionWeightMetric();
+ public override string ValueString => $"{Value}";
+ }
+}
diff --git a/src/jit-analyze/jit-analyze.cs b/src/jit-analyze/Program.cs
similarity index 66%
rename from src/jit-analyze/jit-analyze.cs
rename to src/jit-analyze/Program.cs
index 66f008a7..e6b02841 100644
--- a/src/jit-analyze/jit-analyze.cs
+++ b/src/jit-analyze/Program.cs
@@ -5,47 +5,32 @@
using System;
using System.Collections.Generic;
using System.CommandLine;
+using System.CommandLine.Parsing;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
-using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Xml;
namespace ManagedCodeGen
{
- // Allow Linq to be able to sum up MetricCollections
- public static class jitanalyzeExtensions
+ internal sealed class Program
{
- public static jitanalyze.MetricCollection Sum(this IEnumerable source)
- {
- jitanalyze.MetricCollection result = new jitanalyze.MetricCollection();
-
- foreach (jitanalyze.MetricCollection s in source)
- {
- result.Add(s);
- }
-
- return result;
- }
-
- public static jitanalyze.MetricCollection Sum(this IEnumerable source, Func selector)
- {
- return source.Select(x => selector(x)).Sum();
- }
- }
+ private readonly JitAnalyzeRootCommand _command;
+ private readonly bool _reconcile;
+ private readonly string _filter;
+ private readonly double _overrideTotalBaseMetric;
+ private readonly double _overrideTotalDiffMetric;
+ private readonly int _count;
+ private readonly string _basePath;
+ private readonly string _diffPath;
- public class jitanalyze
- {
+ private static string METRIC_SEP = new string('-', 80);
private const string DETAILS_MARKER = "SUMMARY_MARKER";
- private static string METRIC_SEP = new string('-', 80);
private const string DETAILS_TEXT =
@"```
@@ -55,140 +40,17 @@ public class jitanalyze
```
";
- public class Config
+ public Program(JitAnalyzeRootCommand command)
{
- private ArgumentSyntax _syntaxResult;
- private string _basePath = null;
- private string _diffPath = null;
- private bool _recursive = false;
- private string _fileExtension = ".dasm";
- private bool _full = false;
- private bool _warn = false;
- private int _count = 20;
- private string _json;
- private string _tsv;
- private string _md;
- private bool _noreconcile = false;
- private string _note;
- private string _filter;
- private List _metrics;
- private bool _skipTextDiff = false;
- private bool _retainOnlyTopFiles = false;
- private double? _overrideTotalBaseMetric;
- private double? _overrideTotalDiffMetric;
- private bool _isDiffsOnly = false;
- private bool _isSubsetOfDiffs = false;
-
- public Config(string[] args)
- {
- static double? ParseDouble(string val)
- {
- if (double.TryParse(val, NumberStyles.Float, CultureInfo.InvariantCulture, out double dblVal))
- return dblVal;
-
- return null;
- }
-
- _syntaxResult = ArgumentSyntax.Parse(args, syntax =>
- {
- syntax.DefineOption("b|base", ref _basePath, "Base file or directory.");
- syntax.DefineOption("d|diff", ref _diffPath, "Diff file or directory.");
- syntax.DefineOption("r|recursive", ref _recursive, "Search directories recursively.");
- syntax.DefineOption("ext|fileExtension", ref _fileExtension, "File extension to look for. By default, .dasm");
- syntax.DefineOption("c|count", ref _count,
- "Count of files and methods (at most) to output in the summary."
- + " (count) improvements and (count) regressions of each will be included."
- + " (default 20)");
- syntax.DefineOption("w|warn", ref _warn,
- "Generate warning output for files/methods that only "
- + "exists in one dataset or the other (only in base or only in diff).");
- syntax.DefineOption("m|metrics", ref _metrics, (value) => value.Split(",").ToList(), $"Comma-separated metric to use for diff computations. Available metrics: {MetricCollection.ListMetrics()}.");
- syntax.DefineOption("note", ref _note,
- "Descriptive note to add to summary output");
- syntax.DefineOption("noreconcile", ref _noreconcile,
- "Do not reconcile unique methods in base/diff");
- syntax.DefineOption("json", ref _json,
- "Dump analysis data to specified file in JSON format.");
- syntax.DefineOption("tsv", ref _tsv,
- "Dump analysis data to specified file in tab-separated format.");
- syntax.DefineOption("md", ref _md,
- "Dump analysis data to specified file in markdown format.");
- syntax.DefineOption("filter", ref _filter,
- "Only consider assembly files whose names match the filter");
- syntax.DefineOption("skiptextdiff", ref _skipTextDiff,
- "Skip analysis that checks for files that have textual diffs but no metric diffs.");
- syntax.DefineOption("retainOnlyTopFiles ", ref _retainOnlyTopFiles,
- "Retain only the top 'count' improvements/regressions .dasm files. Delete other files. Useful in CI scenario to reduce the upload size.");
- syntax.DefineOption("override-total-base-metric", ref _overrideTotalBaseMetric, ParseDouble,
- "Override the total base metric shown in the output with this value. Useful when only changed .dasm files are present and these values are known.");
- syntax.DefineOption("override-total-diff-metric", ref _overrideTotalDiffMetric, ParseDouble,
- "Override the total diff metric shown in the output with this value. Useful when only changed .dasm files are present and these values are known.");
- syntax.DefineOption("is-diffs-only", ref _isDiffsOnly,
- "Specify that the disassembly files are only produced for contexts with diffs, so avoid producing output making assumptions about the number of contexts.");
- syntax.DefineOption("is-subset-of-diffs", ref _isSubsetOfDiffs,
- "Specify that the disassembly files are only a subset of the contexts with diffs, so avoid producing output making assumptions about the remaining diffs.");
- });
-
- // Run validation code on parsed input to ensure we have a sensible scenario.
- Validate();
- }
+ _command = command;
- private void Validate()
- {
- if (_basePath == null)
- {
- _syntaxResult.ReportError("Base path (--base) is required.");
- }
-
- if (_diffPath == null)
- {
- _syntaxResult.ReportError("Diff path (--diff) is required.");
- }
-
- if (_metrics == null)
- {
- _metrics = new List { "CodeSize" };
- }
-
- foreach (string metricName in _metrics)
- {
- if (!MetricCollection.ValidateMetric(metricName))
- {
- _syntaxResult.ReportError($"Unknown metric '{metricName}'. Available metrics: {MetricCollection.ListMetrics()}");
- }
- }
-
- if (OverrideTotalBaseMetric.HasValue != OverrideTotalDiffMetric.HasValue)
- {
- _syntaxResult.ReportError("override-total-base-metric and override-total-diff-metric must either both be specified or both not be specified");
- }
- }
-
- public string BasePath { get { return _basePath; } }
- public string DiffPath { get { return _diffPath; } }
- public bool Recursive { get { return _recursive; } }
- public string FileExtension { get { return _fileExtension; } }
- public bool Full { get { return _full; } }
- public bool Warn { get { return _warn; } }
- public int Count { get { return _count; } }
- public string TSVFileName { get { return _tsv; } }
- public string JsonFileName { get { return _json; } }
- public string MarkdownFileName { get { return _md; } }
- public bool DoGenerateJson { get { return _json != null; } }
- public bool DoGenerateTSV { get { return _tsv != null; } }
- public bool DoGenerateMarkdown { get { return _md != null; } }
- public bool Reconcile { get { return !_noreconcile; } }
- public string Note { get { return _note; } }
- public double? OverrideTotalBaseMetric => _overrideTotalBaseMetric;
- public double? OverrideTotalDiffMetric => _overrideTotalDiffMetric;
- public bool IsDiffsOnly => _isDiffsOnly;
- public bool IsSubsetOfDiffs => _isSubsetOfDiffs;
-
- public string Filter { get { return _filter; } }
-
- public List Metrics { get { return _metrics; } }
- public bool SkipTextDiff { get { return _skipTextDiff; } }
- public bool RetainOnlyTopFiles { get { return _retainOnlyTopFiles; } }
+ _reconcile = !Get(command.NoReconcile);
+ _filter = Get(command.Filter);
+ _overrideTotalBaseMetric = Get(command.OverrideTotalBaseMetric);
+ _overrideTotalDiffMetric = Get(command.OverrideTotalDiffMetric);
+ _count = Get(command.Count);
+ _basePath = Get(command.BasePath);
+ _diffPath = Get(command.DiffPath);
}
public class FileInfo
@@ -222,302 +84,6 @@ public int GetHashCode(FileInfo fi)
}
}
- public abstract class Metric
- {
- public virtual string Name { get; }
- public virtual string DisplayName { get; }
- public virtual string Unit { get; }
- public virtual bool LowerIsBetter { get; }
- public abstract Metric Clone();
- public abstract string ValueString { get; }
- public double Value { get; set; }
-
- public void Add(Metric m)
- {
- Value += m.Value;
- }
-
- public void Sub(Metric m)
- {
- Value -= m.Value;
- }
-
- public void Rel(Metric m)
- {
- Value = (Value - m.Value) / m.Value;
- }
-
- public void SetValueFrom(Metric m)
- {
- Value = m.Value;
- }
-
- public override string ToString()
- {
- return Name;
- }
- }
-
- public class CodeSizeMetric : Metric
- {
- public override string Name => "CodeSize";
- public override string DisplayName => "Code Size";
- public override string Unit => "byte";
- public override bool LowerIsBetter => true;
- public override Metric Clone() => new CodeSizeMetric();
- public override string ValueString => $"{Value}";
- }
-
- public class PrologSizeMetric : Metric
- {
- public override string Name => "PrologSize";
- public override string DisplayName => "Prolog Size";
- public override string Unit => "byte";
- public override bool LowerIsBetter => true;
- public override Metric Clone() => new PrologSizeMetric();
- public override string ValueString => $"{Value}";
- }
-
- public class PerfScoreMetric : Metric
- {
- public override string Name => "PerfScore";
- public override string DisplayName => "Perf Score";
- public override string Unit => "PerfScoreUnit";
- public override bool LowerIsBetter => true;
- public override Metric Clone() => new PerfScoreMetric();
- public override string ValueString => $"{Value:F2}";
- }
-
- public class InstrCountMetric : Metric
- {
- public override string Name => "InstrCount";
- public override string DisplayName => "Instruction Count";
- public override string Unit => "Instruction";
- public override bool LowerIsBetter => true;
- public override Metric Clone() => new InstrCountMetric();
- public override string ValueString => $"{Value}";
- }
-
- public class AllocSizeMetric : Metric
- {
- public override string Name => "AllocSize";
- public override string DisplayName => "Allocation Size";
- public override string Unit => "byte";
- public override bool LowerIsBetter => true;
- public override Metric Clone() => new AllocSizeMetric();
- public override string ValueString => $"{Value}";
- }
-
- public class ExtraAllocBytesMetric : Metric
- {
- public override string Name => "ExtraAllocBytes";
- public override string DisplayName => "Extra Allocation Size";
- public override string Unit => "byte";
- public override bool LowerIsBetter => true;
- public override Metric Clone() => new ExtraAllocBytesMetric();
- public override string ValueString => $"{Value}";
- }
- public class DebugClauseMetric : Metric
- {
- public override string Name => "DebugClauseCount";
- public override string DisplayName => "Debug Clause Count";
- public override string Unit => "Clause";
- public override bool LowerIsBetter => true;
- public override Metric Clone() => new DebugClauseMetric();
- public override string ValueString => $"{Value}";
- }
-
- public class DebugVarMetric : Metric
- {
- public override string Name => "DebugVarCount";
- public override string DisplayName => "Debug Variable Count";
- public override string Unit => "Variable";
- public override bool LowerIsBetter => true;
- public override Metric Clone() => new DebugVarMetric();
- public override string ValueString => $"{Value}";
- }
-
- /* LSRA specific */
- public class SpillCountMetric : Metric
- {
- public override string Name => "SpillCount";
- public override string DisplayName => "Spill Count";
- public override string Unit => "Count";
- public override bool LowerIsBetter => true;
- public override Metric Clone() => new SpillCountMetric();
- public override string ValueString => $"{Value}";
- }
-
- public class SpillWeightMetric : Metric
- {
- public override string Name => "SpillWeight";
- public override string DisplayName => "Spill Weighted";
- public override string Unit => "Count";
- public override bool LowerIsBetter => true;
- public override Metric Clone() => new SpillWeightMetric();
- public override string ValueString => $"{Value}";
- }
-
- public class ResolutionCountMetric : Metric
- {
- public override string Name => "ResolutionCount";
- public override string DisplayName => "Resolution Count";
- public override string Unit => "Count";
- public override bool LowerIsBetter => true;
- public override Metric Clone() => new ResolutionCountMetric();
- public override string ValueString => $"{Value}";
- }
-
- public class ResolutionWeightMetric : Metric
- {
- public override string Name => "ResolutionWeight";
- public override string DisplayName => "Resolution Weighted";
- public override string Unit => "Count";
- public override bool LowerIsBetter => true;
- public override Metric Clone() => new ResolutionWeightMetric();
- public override string ValueString => $"{Value}";
- }
-
- public class MetricCollection
- {
- private static Dictionary s_metricNameToIndex;
- private static Metric[] s_metrics;
-
- static MetricCollection()
- {
- var derivedType = typeof(Metric);
- var currentAssembly = Assembly.GetAssembly(derivedType);
- s_metrics = currentAssembly.GetTypes()
- .Where(t => t != derivedType && derivedType.IsAssignableFrom(t))
- .Select(t => currentAssembly.CreateInstance(t.FullName)).Cast().ToArray();
-
- s_metricNameToIndex = new Dictionary(s_metrics.Length);
-
- for (int i = 0; i < s_metrics.Length; i++)
- {
- Metric m = s_metrics[i];
- s_metricNameToIndex[m.Name] = i;
- }
- }
-
- [JsonInclude]
- private Metric[] metrics;
-
- public MetricCollection()
- {
- metrics = new Metric[s_metrics.Length];
- for (int i = 0; i < s_metrics.Length; i++)
- {
- metrics[i] = s_metrics[i].Clone();
- }
- }
-
- public MetricCollection(MetricCollection other) : this()
- {
- this.SetValueFrom(other);
- }
-
- public static IEnumerable AllMetrics => s_metrics;
-
- public Metric GetMetric(string metricName)
- {
- int index;
- if (s_metricNameToIndex.TryGetValue(metricName, out index))
- {
- return metrics[index];
- }
- return null;
- }
-
- public static bool ValidateMetric(string name)
- {
- return s_metricNameToIndex.TryGetValue(name, out _);
- }
-
- public static string DisplayName(string metricName)
- {
- int index;
- if (s_metricNameToIndex.TryGetValue(metricName, out index))
- {
- return s_metrics[index].DisplayName;
- }
- return "Unknown metric";
- }
-
- public static string ListMetrics()
- {
- StringBuilder sb = new StringBuilder();
- bool isFirst = true;
- foreach (string s in s_metricNameToIndex.Keys)
- {
- if (!isFirst) sb.Append(", ");
- sb.Append(s);
- isFirst = false;
- }
- return sb.ToString();
- }
-
- public override string ToString()
- {
- StringBuilder sb = new StringBuilder();
- bool isFirst = true;
- foreach (Metric m in metrics)
- {
- if (!isFirst) sb.Append(", ");
- sb.Append($"{m.Name} {m.Unit} {m.ValueString}");
- isFirst = false;
- }
- return sb.ToString();
- }
-
- public void Add(MetricCollection other)
- {
- for (int i = 0; i < metrics.Length; i++)
- {
- metrics[i].Add(other.metrics[i]);
- }
- }
-
- public void Add(string metricName, double value)
- {
- Metric m = GetMetric(metricName);
- m.Value += value;
- }
-
- public void Sub(MetricCollection other)
- {
- for (int i = 0; i < metrics.Length; i++)
- {
- metrics[i].Sub(other.metrics[i]);
- }
- }
-
- public void Rel(MetricCollection other)
- {
- for (int i = 0; i < metrics.Length; i++)
- {
- metrics[i].Rel(other.metrics[i]);
- }
- }
-
- public void SetValueFrom(MetricCollection other)
- {
- for (int i = 0; i < metrics.Length; i++)
- {
- metrics[i].SetValueFrom(other.metrics[i]);
- }
- }
-
- public bool IsZero()
- {
- for (int i = 0; i < metrics.Length; i++)
- {
- if (metrics[i].Value != 0) return false;
- }
- return true;
- }
- }
-
public class MethodInfo
{
private MetricCollection metrics;
@@ -533,9 +99,9 @@ public MethodInfo()
public override string ToString()
{
- return String.Format(@"name {0}, {1}, function count {2}, offsets {3}",
+ return string.Format(@"name {0}, {1}, function count {2}, offsets {3}",
name, metrics, functionCount,
- String.Join(", ", functionOffsets.ToArray()));
+ string.Join(", ", functionOffsets.ToArray()));
}
}
@@ -806,8 +372,8 @@ public static IEnumerable ExtractMethodInfo(string filePath)
// numbers are regressions. (lower is better)
//
// Todo: handle metrics where "higher is better"
- public static IEnumerable Comparator(IEnumerable baseInfo,
- IEnumerable diffInfo, Config config, string metricName)
+ public IEnumerable Comparator(IEnumerable baseInfo,
+ IEnumerable diffInfo, string metricName)
{
MethodInfoComparer methodInfoComparer = new MethodInfoComparer();
return baseInfo.Join(diffInfo, b => b.path, d => d.path, (b, d) =>
@@ -837,7 +403,7 @@ public static IEnumerable Comparator(IEnumerable baseInfo,
methodDeltaList = jointList.Where(x => x.deltaMetrics.GetMetric(metricName).Value != 0)
};
- if (config.Reconcile)
+ if (_reconcile)
{
f.Reconcile();
}
@@ -853,7 +419,7 @@ public static IEnumerable Comparator(IEnumerable baseInfo,
// Top diffs by size across all files
// Top diffs by percentage size across all files
//
- public static (int, string) Summarize(IEnumerable fileDeltaList, Config config, string metricName)
+ public (int, string) Summarize(IEnumerable fileDeltaList, string metricName)
{
StringBuilder summaryContents = new StringBuilder();
@@ -862,9 +428,10 @@ public static (int, string) Summarize(IEnumerable fileDeltaList, Conf
var totalBaseMetrics = fileDeltaList.Sum(x => x.baseMetrics);
var totalDiffMetrics = fileDeltaList.Sum(x => x.diffMetrics);
- if (config.Note != null)
+ string note = Get(_command.Note);
+ if (note != null)
{
- summaryContents.AppendLine($"\n{config.Note}");
+ summaryContents.AppendLine($"\n{note}");
}
Metric totalBaseMetric = totalBaseMetrics.GetMetric(metricName);
@@ -875,19 +442,19 @@ public static (int, string) Summarize(IEnumerable fileDeltaList, Conf
string metricDisplayName = totalBaseMetrics.GetMetric(metricName).DisplayName;
summaryContents.Append($"\nSummary of {metricDisplayName} diffs:");
- if (config.Filter != null)
+ if (_filter != null)
{
- summaryContents.Append($" (using filter '{config.Filter}')");
+ summaryContents.Append($" (using filter '{_filter}')");
}
summaryContents.AppendLine(string.Format("\n({0} is better)\n", totalBaseMetric.LowerIsBetter ? "Lower" : "Higher"));
- if (config.OverrideTotalBaseMetric.HasValue)
+ if (_command.Result.FindResultFor(_command.OverrideTotalBaseMetric) != null)
{
- Debug.Assert(config.OverrideTotalDiffMetric.HasValue);
- summaryContents.AppendLine(string.Format(CultureInfo.InvariantCulture, "Total {0}s of base: {1} (overridden on cmd)", unitName, config.OverrideTotalBaseMetric.Value));
- summaryContents.AppendLine(string.Format(CultureInfo.InvariantCulture, "Total {0}s of diff: {1} (overridden on cmd)", unitName, config.OverrideTotalDiffMetric.Value));
- double delta = config.OverrideTotalDiffMetric.Value - config.OverrideTotalBaseMetric.Value;
- summaryContents.AppendLine(string.Format(CultureInfo.InvariantCulture, "Total {0}s of delta: {1} ({2:P} of base)", unitName, delta, delta / config.OverrideTotalBaseMetric.Value));
+ Debug.Assert(_command.Result.FindResultFor(_command.OverrideTotalDiffMetric) != null);
+ summaryContents.AppendLine(string.Format(CultureInfo.InvariantCulture, "Total {0}s of base: {1} (overridden on cmd)", unitName, _overrideTotalBaseMetric));
+ summaryContents.AppendLine(string.Format(CultureInfo.InvariantCulture, "Total {0}s of diff: {1} (overridden on cmd)", unitName, _overrideTotalDiffMetric));
+ double delta = _overrideTotalDiffMetric - _overrideTotalBaseMetric;
+ summaryContents.AppendLine(string.Format(CultureInfo.InvariantCulture, "Total {0}s of delta: {1} ({2:P} of base)", unitName, delta, delta / _overrideTotalBaseMetric));
}
else if (totalBaseMetric.Value != 0)
{
@@ -915,7 +482,7 @@ public static (int, string) Summarize(IEnumerable fileDeltaList, Conf
summaryContents.AppendLine(DETAILS_MARKER);
- if (config.Reconcile)
+ if (_reconcile)
{
// See if base or diff had any unique methods
var uniqueToBase = fileDeltaList.Sum(x => x.reconciledCountBase);
@@ -936,8 +503,7 @@ public static (int, string) Summarize(IEnumerable fileDeltaList, Conf
}
}
- int requestedCount = config.Count;
- if (requestedCount == 0)
+ if (_count == 0)
{
return (Math.Abs(totalDeltaMetric.Value) == 0 ? 0 : -1, summaryContents.ToString());
}
@@ -955,19 +521,20 @@ public static (int, string) Summarize(IEnumerable fileDeltaList, Conf
int fileRegressionCount = sortedFileRegressions.Count();
int sortedFileCount = fileImprovementCount + fileRegressionCount;
int unchangedFileCount = fileDeltaList.Count() - sortedFileCount;
+ bool retainOnlyTopFiles = Get(_command.RetainOnlyTopFiles);
void DisplayFileMetric(string headerText, int metricCount, IEnumerable list)
{
if (metricCount > 0)
{
summaryContents.AppendLine($"\n{headerText} ({unitName}s):");
- foreach (var fileDelta in list.Take(Math.Min(metricCount, requestedCount)))
+ foreach (var fileDelta in list.Take(Math.Min(metricCount, _count)))
{
summaryContents.AppendLine(string.Format(" {1,8} : {0} ({2:P} of base)", fileDelta.basePath,
fileDelta.deltaMetrics.GetMetric(metricName).ValueString,
fileDelta.deltaMetrics.GetMetric(metricName).Value / fileDelta.baseMetrics.GetMetric(metricName).Value));
- fileDelta.RetainFile = config.RetainOnlyTopFiles;
+ fileDelta.RetainFile = retainOnlyTopFiles;
}
}
}
@@ -1019,7 +586,7 @@ void DisplayMethodMetric(string headerText, string subtext, int methodCount, dyn
if (methodCount > 0)
{
summaryContents.AppendLine($"\n{headerText} ({subtext}s):");
- foreach (var method in list.GetRange(0, Math.Min(methodCount, requestedCount)))
+ foreach (var method in list.GetRange(0, Math.Min(methodCount, _count)))
{
summaryContents.Append(string.Format(" {2,8} ({3,6:P} of base) : {0} - {1}", method.path, method.name, method.deltaMetric.ValueString,
method.deltaMetric.Value / method.baseMetric.Value));
@@ -1045,9 +612,9 @@ void DisplayMethodMetric(string headerText, string subtext, int methodCount, dyn
DisplayMethodMetric("Top method regressions", "percentage", methodRegressionCount, sortedMethodRegressionsByPercentage);
DisplayMethodMetric("Top method improvements", "percentage", methodImprovementCount, sortedMethodImprovementsByPercentage);
- if (!config.IsSubsetOfDiffs)
+ if (!Get(_command.IsSubsetOfDiffs))
{
- if (config.IsDiffsOnly)
+ if (Get(_command.IsDiffsOnly))
{
summaryContents.AppendLine($"\n{sortedMethodCount} total methods with {metricDisplayName} differences ({methodImprovementCount} improved, {methodRegressionCount} regressed).");
}
@@ -1057,11 +624,11 @@ void DisplayMethodMetric(string headerText, string subtext, int methodCount, dyn
}
}
- if (!config.SkipTextDiff)
+ if (!Get(_command.SkipTextDiff))
{
// Show files with text diffs but no metric diffs.
- Dictionary diffCounts = DiffInText(config.DiffPath, config.BasePath);
+ Dictionary diffCounts = DiffInText(_diffPath, _basePath);
// TODO: resolve diffs to particular methods in the files.
var zeroDiffFilesWithDiffs = fileDeltaList.Where(x => diffCounts.ContainsKey(x.diffPath) && (x.deltaMetrics.IsZero()))
@@ -1071,14 +638,14 @@ void DisplayMethodMetric(string headerText, string subtext, int methodCount, dyn
if (zeroDiffFilesWithDiffCount > 0)
{
summaryContents.AppendLine($"\n{zeroDiffFilesWithDiffCount} files had text diffs but no metric diffs.");
- foreach (var zerofile in zeroDiffFilesWithDiffs.Take(config.Count))
+ foreach (var zerofile in zeroDiffFilesWithDiffs.Take(_count))
{
summaryContents.AppendLine($"{zerofile.basePath} had {diffCounts[zerofile.basePath]} diffs");
}
}
}
- if (config.RetainOnlyTopFiles)
+ if (retainOnlyTopFiles)
{
if ((fileDeltaList.Count() > 0))
{
@@ -1096,8 +663,8 @@ void DeleteFile(string path)
{
if (!fileToDelete.RetainFile)
{
- DeleteFile(Path.Combine(config.BasePath, fileToDelete.basePath));
- DeleteFile(Path.Combine(config.DiffPath, fileToDelete.diffPath));
+ DeleteFile(Path.Combine(_basePath, fileToDelete.basePath));
+ DeleteFile(Path.Combine(_diffPath, fileToDelete.diffPath));
filesDeleted += 2;
}
}
@@ -1317,16 +884,26 @@ public static Dictionary DiffInText(string diffPath, string basePat
return fileToTextDiffCount;
}
- public static int Main(string[] args)
+ private T Get(Option option) => _command.Result.GetValue(option);
+
+ private static int Main(string[] args) =>
+ new CommandLineBuilder(new JitAnalyzeRootCommand(args))
+ .UseVersionOption("--version", "-v")
+ .UseHelp()
+ .UseParseErrorReporting()
+ .Build()
+ .Invoke(args);
+
+ public int Run()
{
- // Parse incoming arguments
- Config config = new Config(args);
int retCode = 0;
try
{
// Extract method info from base and diff directory or file.
- var baseList = ExtractFileInfo(config.BasePath, config.Filter, config.FileExtension, config.Recursive);
- var diffList = ExtractFileInfo(config.DiffPath, config.Filter, config.FileExtension, config.Recursive);
+ bool recursive = Get(_command.Recursive);
+ string extension = Get(_command.FileExtension);
+ var baseList = ExtractFileInfo(_basePath, _filter, extension, recursive);
+ var diffList = ExtractFileInfo(_diffPath, _filter, extension, recursive);
// Compare the method info for each file and generate a list of
// non-zero deltas. The lists that include files in one but not
@@ -1338,23 +915,26 @@ public static int Main(string[] args)
StringBuilder jsonContents = new StringBuilder();
StringBuilder markdownContents = new StringBuilder();
- foreach (var metricName in config.Metrics)
+ string json = Get(_command.Json);
+ string tsv = Get(_command.Tsv);
+ string md = Get(_command.MD);
+ foreach (var metricName in Get(_command.Metrics))
{
- compareList = Comparator(baseList, diffList, config, metricName);
+ compareList = Comparator(baseList, diffList, metricName);
- if (config.DoGenerateTSV)
+ if (tsv != null)
{
tsvContents.Append(GenerateTSV(compareList));
}
- if (config.DoGenerateJson)
+ if (json != null)
{
jsonContents.Append(GenerateJson(compareList));
}
- var summarizedReport = Summarize(compareList, config, metricName);
+ var summarizedReport = Summarize(compareList, metricName);
- if (config.DoGenerateMarkdown)
+ if (md != null)
{
markdownContents.Append(GenerateMarkdown(summarizedReport.Item2));
markdownContents.AppendLine();
@@ -1369,7 +949,7 @@ public static int Main(string[] args)
// Generate warning lists if requested.
- if (config.Warn)
+ if (Get(_command.Warn))
{
WarnFiles(diffList, baseList);
WarnMethods(compareList);
@@ -1377,17 +957,17 @@ public static int Main(string[] args)
if (tsvContents.Length > 0)
{
- File.WriteAllText(config.TSVFileName, tsvContents.ToString());
+ File.WriteAllText(tsv, tsvContents.ToString());
}
if (jsonContents.Length > 0)
{
- File.WriteAllText(config.JsonFileName, jsonContents.ToString());
+ File.WriteAllText(json, jsonContents.ToString());
}
if (markdownContents.Length > 0)
{
- File.WriteAllText(config.MarkdownFileName, markdownContents.ToString());
+ File.WriteAllText(md, markdownContents.ToString());
}
return retCode;
diff --git a/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs b/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs
new file mode 100644
index 00000000..d7270e36
--- /dev/null
+++ b/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs
@@ -0,0 +1,124 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.IO;
+
+namespace ManagedCodeGen
+{
+ internal sealed class JitDasmPmiRootCommand : RootCommand
+ {
+ public Option AltJit { get; } =
+ new("--altjit", "If set, the name of the altjit to use (e.g., clrjit_win_arm64_x64.dll)");
+ public Option CorerunPath { get; } =
+ new(new[] { "--corerun", "-c" }, result => result.Tokens.Count > 0 ? Path.GetFullPath(result.Tokens[0].Value) : null, true, "The corerun compiler exe");
+ public Option JitPath { get; } =
+ new(new[] { "--jit", "-j" }, result => result.Tokens.Count > 0 ? Path.GetFullPath(result.Tokens[0].Value) : null, true, "The full path to the jit library");
+ public Option OutputPath { get; } =
+ new(new[] { "--output", "-o" }, "The output path");
+ public Option Filename { get; } =
+ new(new[] { "--file", "-f" }, "Name of file to take list of assemblies from. Both a file and assembly list can be used");
+ public Option DumpGCInfo { get; } =
+ new("--gcinfo", "Add GC info to the disasm output");
+ public Option DumpDebugInfo { get; } =
+ new("--debuginfo", "Add Debug info to the disasm output");
+ public Option Verbose { get; } =
+ new("--verbose", "Enable verbose output");
+ public Option NoDiffable { get; } =
+ new("--nodiffable", "Generate non-diffable asm (pointer values will be left in output)");
+ public Option Tier0 { get; } =
+ new("--tier0", "Generate tier0 code");
+ public Option Cctors { get; } =
+ new("--cctors", "Jit and run cctors before jitting other methods");
+ public Option Recursive { get; } =
+ new(new[] { "--recursive", "-r" }, "Search directories recursively");
+ public Option> PlatformPaths { get; } =
+ new(new[] { "--platform", "-p" }, "Path to platform assemblies");
+ public Option> Methods { get; } =
+ new(new[] { "--methods", "-m" }, "List of methods to disasm");
+ public Argument> AssemblyList { get; } =
+ new("--assembly", "The list of assemblies or directories to scan for assemblies");
+ public Option WaitForDebugger { get; } =
+ new(new[] { "--wait", "-w" }, "Wait for debugger to attach");
+ public Option NoCopyJit { get; } =
+ new("--nocopy", "Correct jit has already been copied into the corerun directory");
+
+ public ParseResult Result;
+
+ public JitDasmPmiRootCommand(string[] args) : base("Managed code gen diff tool")
+ {
+ AddOption(AltJit);
+ AddOption(CorerunPath);
+ AddOption(JitPath);
+ AddOption(OutputPath);
+ AddOption(Filename);
+ AddOption(DumpGCInfo);
+ AddOption(DumpDebugInfo);
+ AddOption(Verbose);
+ AddOption(NoDiffable);
+ AddOption(Tier0);
+ AddOption(Cctors);
+ AddOption(Recursive);
+ AddOption(PlatformPaths);
+ AddOption(Methods);
+ AddOption(WaitForDebugger);
+ AddOption(NoCopyJit);
+
+ AddArgument(AssemblyList);
+
+ this.SetHandler(context =>
+ {
+ Result = context.ParseResult;
+
+ try
+ {
+ List errors = new();
+ string corerun = Result.GetValue(CorerunPath);
+ if (corerun == null || !File.Exists(corerun))
+ {
+ errors.Add("Can't find --corerun tool.");
+ }
+
+ if (Result.FindResultFor(Filename) == null && Result.GetValue(AssemblyList).Count == 0)
+ {
+ errors.Add("No input: Specify --file or list input assemblies.");
+ }
+
+ string jitPath = Result.GetValue(JitPath);
+ if (jitPath != null && !File.Exists(jitPath))
+ {
+ errors.Add("Can't find --jit library.");
+ }
+
+ string filename = Result.GetValue(Filename);
+ if (filename != null && !File.Exists(filename))
+ {
+ errors.Add($"Error reading input file {filename}, file not found.");
+ }
+
+ if (errors.Count > 0)
+ {
+ throw new Exception(string.Join(Environment.NewLine, errors));
+ }
+
+ context.ExitCode = new Program(this).Run();
+ }
+ catch (Exception e)
+ {
+ Console.ResetColor();
+ Console.ForegroundColor = ConsoleColor.Red;
+
+ Console.Error.WriteLine("Error: " + e.Message);
+ Console.Error.WriteLine(e.ToString());
+
+ Console.ResetColor();
+
+ context.ExitCode = 1;
+ }
+ });
+ }
+ }
+}
diff --git a/src/jit-dasm-pmi/Program.cs b/src/jit-dasm-pmi/Program.cs
new file mode 100644
index 00000000..511d9365
--- /dev/null
+++ b/src/jit-dasm-pmi/Program.cs
@@ -0,0 +1,475 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// jit-dasm-pmi - The managed code gen diff tool scripts the generation of
+// diffable assembly code output from the the runtime. This enables quickly
+// generating A/B comparisons of .Net codegen tools to validate ongoing
+// development.
+//
+// The related jit-dasm tool is complementary, and does something similar for
+// prejitted code.
+//
+
+using System;
+using System.Diagnostics;
+using System.CommandLine;
+using System.CommandLine.Parsing;
+using System.IO;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Linq;
+using System.Text;
+
+namespace ManagedCodeGen
+{
+ public class AssemblyInfo
+ {
+ public string Name { get; set; }
+ // Contains path to assembly.
+ public string Path { get; set; }
+ // Contains relative path within output directory for given assembly.
+ // This allows for different output directories per tool.
+ public string OutputPath { get; set; }
+ }
+
+ internal sealed class Program
+ {
+ private readonly JitDasmPmiRootCommand _command;
+ private readonly string _corerunPath;
+ private readonly bool _verbose;
+
+ public Program(JitDasmPmiRootCommand command)
+ {
+ _command = command;
+ _corerunPath = Get(command.CorerunPath);
+ _verbose = Get(command.Verbose);
+ }
+
+ public int Run()
+ {
+ // Stop to attach a debugger if desired.
+ if (Get(_command.WaitForDebugger))
+ {
+ Console.WriteLine("Wait for a debugger to attach. Press ENTER to continue");
+ Console.WriteLine($"Process ID: {Process.GetCurrentProcess().Id}");
+ Console.ReadLine();
+ }
+
+ // Builds assemblyInfoList on jitdasm
+ List assemblyWorkList = GenerateAssemblyWorklist();
+
+ // Produces a set of disasm outputs for a given code generator and assembly list/
+ int errorCount = GenerateAsm(assemblyWorkList);
+ if (errorCount > 0)
+ {
+ Console.Error.WriteLine("{0} errors compiling set.", errorCount);
+ }
+
+ return errorCount;
+ }
+
+ private T Get(Option option) => _command.Result.GetValue(option);
+ private T Get(Argument arg) => _command.Result.GetValue(arg);
+
+ private static int Main(string[] args) =>
+ new CommandLineBuilder(new JitDasmPmiRootCommand(args))
+ .UseVersionOption("--version", "-v")
+ .UseHelp()
+ .UseParseErrorReporting()
+ .Build()
+ .Invoke(args);
+
+ public List GenerateAssemblyWorklist()
+ {
+ List assemblyList = new List();
+ List assemblyInfoList = new List();
+
+ string filename = Get(_command.Filename);
+ if (filename != null)
+ {
+ assemblyList = new List();
+ string inputFile = filename;
+
+ // Open file, read assemblies one per line, and add them to the assembly list.
+ using (var inputStream = File.Open(inputFile, FileMode.Open))
+ using (var inputStreamReader = new StreamReader(inputStream))
+ {
+ string line;
+ while ((line = inputStreamReader.ReadLine()) != null)
+ {
+ // Each line is a path to an assembly.
+ if (!File.Exists(line))
+ {
+ Console.WriteLine("Can't find {0} skipping...", line);
+ continue;
+ }
+
+ assemblyList.Add(line);
+ }
+ }
+ }
+
+ List userAssemblyList = Get(_command.AssemblyList);
+ if (userAssemblyList.Count > 0)
+ {
+ // Append command line assemblies
+ assemblyList.AddRange(userAssemblyList);
+ }
+
+ // Process worklist and produce the info needed for the disasm engines.
+ foreach (var path in assemblyList)
+ {
+ FileAttributes attr;
+
+ if (File.Exists(path) || Directory.Exists(path))
+ {
+ attr = File.GetAttributes(path);
+ }
+ else
+ {
+ Console.WriteLine("Can't find assembly or directory at {0}", path);
+ continue;
+ }
+
+ if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
+ {
+ if (_verbose)
+ {
+ Console.WriteLine("Processing directory: {0}", path);
+ }
+
+ // For the directory case create a stack and recursively find any
+ // assemblies for compilation.
+ List directoryAssemblyInfoList = IdentifyAssemblies(path);
+
+ // Add info generated at this directory
+ assemblyInfoList.AddRange(directoryAssemblyInfoList);
+ }
+ else
+ {
+ // This is the file case.
+
+ AssemblyInfo info = new AssemblyInfo
+ {
+ Name = Path.GetFileName(path),
+ Path = Path.GetDirectoryName(path),
+ OutputPath = ""
+ };
+
+ assemblyInfoList.Add(info);
+ }
+ }
+
+ return assemblyInfoList;
+ }
+
+ // Recursivly search for assemblies from a root path.
+ private List IdentifyAssemblies(string rootPath)
+ {
+ List assemblyInfoList = new List();
+ string fullRootPath = Path.GetFullPath(rootPath);
+ SearchOption searchOption = (Get(_command.Recursive)) ?
+ SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
+
+ // Get files that could be assemblies, but discard currently
+ // ngen'd assemblies.
+ var subFiles = Directory.EnumerateFiles(rootPath, "*", searchOption)
+ .Where(s => (s.EndsWith(".exe") || s.EndsWith(".dll")) && !s.Contains(".ni."));
+
+ foreach (var filePath in subFiles)
+ {
+ if (_verbose)
+ {
+ Console.WriteLine("Scanning: {0}", filePath);
+ }
+
+ // skip if not an assembly
+ if (!Utility.IsAssembly(filePath))
+ {
+ continue;
+ }
+
+ string fileName = Path.GetFileName(filePath);
+ string directoryName = Path.GetDirectoryName(filePath);
+ string fullDirectoryName = Path.GetFullPath(directoryName);
+ string outputPath = fullDirectoryName.Substring(fullRootPath.Length).TrimStart(Path.DirectorySeparatorChar);
+
+ AssemblyInfo info = new AssemblyInfo
+ {
+ Name = fileName,
+ Path = directoryName,
+ OutputPath = outputPath
+ };
+
+ assemblyInfoList.Add(info);
+ }
+
+ return assemblyInfoList;
+ }
+
+ public int GenerateAsm(List assemblyInfoList)
+ {
+ string testOverlayDir = Path.GetDirectoryName(_corerunPath);
+ string jitPath = Get(_command.JitPath);
+ string jitDir = Path.GetDirectoryName(jitPath);
+ string realJitPath = Path.Combine(testOverlayDir, GetPmiJitLibraryName(""));
+ string tempJitPath = Path.Combine(testOverlayDir, GetPmiJitLibraryName("-backup"));
+
+ int errorCount = 0;
+ bool copyjit = !Get(_command.NoCopyJit);
+
+ try
+ {
+ if (copyjit)
+ {
+ if (_verbose)
+ {
+ Console.WriteLine($"Copying default jit: {realJitPath} ==> {tempJitPath}");
+ }
+ File.Copy(realJitPath, tempJitPath, true);
+ if (_verbose)
+ {
+ Console.WriteLine($"Copying in the test jit: {jitPath} ==> {realJitPath}");
+ }
+ // May need chmod +x for non-windows ??
+ File.Copy(jitPath, realJitPath, true);
+ }
+
+ GenerateAsmInternal(assemblyInfoList, ref errorCount);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"JIT DASM PMI failed: {e.Message}");
+ errorCount++;
+ }
+ finally
+ {
+ if (copyjit)
+ {
+ if (_verbose)
+ {
+ Console.WriteLine($"Restoring default jit: {tempJitPath} ==> {realJitPath}");
+ }
+ File.Copy(tempJitPath, realJitPath, true);
+ }
+ }
+
+ return errorCount;
+
+ string GetPmiJitLibraryName(string suffix)
+ {
+ string jitName = Path.GetFileNameWithoutExtension(jitPath);
+ string pmiJitName = jitName + suffix + Path.GetExtension(jitPath);
+ return pmiJitName;
+ }
+ }
+ void GenerateAsmInternal(List assemblyInfoList, ref int errorCount)
+ {
+ // Build a command per assembly to generate the asm output.
+ foreach (var assembly in assemblyInfoList)
+ {
+ if (_verbose)
+ {
+ Console.WriteLine("assembly name: " + assembly.Name);
+ }
+ string fullPathAssembly = Path.Combine(assembly.Path, assembly.Name);
+
+ if (!File.Exists(fullPathAssembly))
+ {
+ // Assembly not found. Produce a warning and skip this input.
+ Console.WriteLine("Skipping. Assembly not found: {0}", fullPathAssembly);
+ continue;
+ }
+
+ string binDir = Path.GetDirectoryName(System.AppContext.BaseDirectory);
+ string command = "DRIVEALL-QUIET";
+ if (Get(_command.Cctors))
+ {
+ command += "-CCTORS";
+ }
+ List commandArgs = new List() { Path.Combine(binDir, "pmi.dll"), command, fullPathAssembly };
+
+ Dictionary _environmentVariables = new Dictionary();
+ // Add environment variables to the environment of the command we are going to execute, and
+ // display them to the user in verbose mode.
+ void AddEnvironmentVariable(string varName, string varValue)
+ {
+ _environmentVariables[varName] = varValue;
+ if (_verbose)
+ {
+ Console.WriteLine("set {0}={1}", varName, varValue);
+ }
+ }
+
+ StringBuilder pmiEnv = new StringBuilder();
+ // Append environment variable to the string that will be used as a value of PMIENV environment
+ // variable.
+ void AppendEnvironmentVariableToPmiEnv(string varName, string varValue)
+ {
+ if (pmiEnv.Length > 0)
+ {
+ pmiEnv.Append(";");
+ }
+ pmiEnv.Append(varName + "=" + varValue);
+ if (_verbose)
+ {
+ Console.WriteLine("Appending: {0}={1} to PMIENV", varName, varValue);
+ }
+ }
+
+ // Pick up ambient DOTNET settings.
+ foreach (string envVar in Environment.GetEnvironmentVariables().Keys)
+ {
+ if (envVar.IndexOf("DOTNET_") == 0)
+ {
+ string value = Environment.GetEnvironmentVariable(envVar);
+ AppendEnvironmentVariableToPmiEnv(envVar, value);
+ }
+ }
+
+ // Set up environment do PMI based disasm.
+ AppendEnvironmentVariableToPmiEnv("DOTNET_JitDisasm", "*");
+ AppendEnvironmentVariableToPmiEnv("DOTNET_JitDisasmAssemblies", Path.GetFileNameWithoutExtension(assembly.Name));
+ AppendEnvironmentVariableToPmiEnv("DOTNET_JitUnwindDump", "*");
+ AppendEnvironmentVariableToPmiEnv("DOTNET_JitEHDump", "*");
+ if (!Get(_command.NoDiffable))
+ {
+ AppendEnvironmentVariableToPmiEnv("DOTNET_JitDiffableDasm", "1");
+ }
+ AppendEnvironmentVariableToPmiEnv("DOTNET_ReadyToRun", "0");
+ AppendEnvironmentVariableToPmiEnv("DOTNET_ZapDisable", "1");
+ AppendEnvironmentVariableToPmiEnv("DOTNET_JitEnableNoWayAssert", "1"); // Force noway_assert to generate assert (not fall back to MinOpts).
+ AppendEnvironmentVariableToPmiEnv("DOTNET_JitNoForceFallback", "1"); // Don't stress noway fallback path.
+ AppendEnvironmentVariableToPmiEnv("DOTNET_JitRequired", "1"); // Force NO_WAY to generate assert. Also generates assert for BADCODE/BADCODE3.
+
+ bool tier0 = Get(_command.Tier0);
+ // We likely don't want tiering enabled, but allow it, if user wants tier0 codegen
+ AppendEnvironmentVariableToPmiEnv("DOTNET_TieredCompilation", tier0 ? "1" : "0");
+
+ if (tier0)
+ {
+ // jit all methods at tier0
+ AppendEnvironmentVariableToPmiEnv("DOTNET_TC_QuickJitForLoops", "1");
+ // don't promote any method to tier1
+ AppendEnvironmentVariableToPmiEnv("DOTNET_TC_CallCounting", "0");
+ }
+
+ if (Get(_command.DumpGCInfo ))
+ {
+ AppendEnvironmentVariableToPmiEnv("DOTNET_JitGCDump", "*");
+ }
+
+ if (Get(_command.DumpDebugInfo))
+ {
+ AppendEnvironmentVariableToPmiEnv("DOTNET_JitDebugDump", "*");
+ }
+
+ string altJit = Get(_command.AltJit);
+ if (altJit != null)
+ {
+ AppendEnvironmentVariableToPmiEnv("DOTNET_AltJit", "*");
+ AppendEnvironmentVariableToPmiEnv("DOTNET_AltJitName", altJit);
+
+ const string arm64AsTarget = "_arm64_";
+ int targetArm64 = altJit.IndexOf(arm64AsTarget);
+ if (targetArm64 > 0)
+ {
+ bool isHostArm64 = (altJit.IndexOf("arm64", targetArm64 + arm64AsTarget.Length) > 0);
+ if (!isHostArm64)
+ {
+ // If this looks like a cross-targeting altjit with a arm64 target and a different host
+ // then fix the SIMD size.
+ AppendEnvironmentVariableToPmiEnv("DOTNET_SIMD16ByteOnly", "1");
+ }
+ }
+ }
+
+ // Set up PMI path...
+ AddEnvironmentVariable("PMIPATH", assembly.Path);
+
+ if (_verbose)
+ {
+ Console.WriteLine("Running: {0} {1}", _corerunPath, string.Join(" ", commandArgs));
+ }
+
+ ProcessResult result;
+
+ string outputPath = Get(_command.OutputPath);
+ if (outputPath != null)
+ {
+ // Generate path to the output file
+ var assemblyFileName = Path.ChangeExtension(assembly.Name, ".dasm");
+ var dasmPath = Path.Combine(outputPath, assembly.OutputPath, assemblyFileName);
+ var logPath = Path.ChangeExtension(dasmPath, ".log");
+
+ Utility.EnsureParentDirectoryExists(dasmPath);
+
+ AppendEnvironmentVariableToPmiEnv("DOTNET_JitStdOutFile", dasmPath);
+
+ AddEnvironmentVariable("PMIENV", pmiEnv.ToString());
+
+ result = Utility.ExecuteProcess(_corerunPath, commandArgs, true, environmentVariables: _environmentVariables);
+
+ // Redirect stdout/stderr to log file and run command.
+ StringBuilder output = new StringBuilder();
+ if (!string.IsNullOrEmpty(result.StdOut))
+ {
+ output.AppendLine(result.StdOut);
+ }
+ if (!string.IsNullOrEmpty(result.StdErr) && (result.StdOut != result.StdErr))
+ {
+ output.AppendLine(result.StdErr);
+ }
+ if (output.Length > 0)
+ {
+ File.WriteAllText(logPath, output.ToString());
+ }
+
+ bool hasOutput = true;
+
+ if (result.ExitCode != 0)
+ {
+ errorCount++;
+
+ if (result.ExitCode == -2146234344)
+ {
+ Console.Error.WriteLine("{0} is not a managed assembly", fullPathAssembly);
+
+ // Discard output if the assembly is not managed
+ File.Delete(dasmPath);
+ File.Delete(logPath);
+
+ hasOutput = false;
+ }
+ else
+ {
+ Console.Error.WriteLine("Error running {0} on {1}", _corerunPath, fullPathAssembly);
+ }
+ }
+
+ if (hasOutput && File.Exists(logPath) && !File.Exists(dasmPath))
+ {
+ // Looks like the JIT does not support DOTNET_JitStdOutFile so
+ // the assembly output must be in the log file.
+ File.Move(logPath, dasmPath);
+ }
+ }
+ else
+ {
+ AddEnvironmentVariable("PMIENV", pmiEnv.ToString());
+
+ // By default forward to output to stdout/stderr.
+ result = Utility.ExecuteProcess(_corerunPath, commandArgs, environmentVariables: _environmentVariables);
+
+ if (result.ExitCode != 0)
+ {
+ errorCount ++;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/jit-dasm-pmi/jit-dasm-pmi.cs b/src/jit-dasm-pmi/jit-dasm-pmi.cs
deleted file mode 100644
index 00d6e9c5..00000000
--- a/src/jit-dasm-pmi/jit-dasm-pmi.cs
+++ /dev/null
@@ -1,634 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-///////////////////////////////////////////////////////////////////////////////
-//
-// jit-dasm-pmi - The managed code gen diff tool scripts the generation of
-// diffable assembly code output from the the runtime. This enables quickly
-// generating A/B comparisons of .Net codegen tools to validate ongoing
-// development.
-//
-// The related jit-dasm tool is complementary, and does something similar for
-// prejitted code.
-//
-
-using System;
-using System.Diagnostics;
-using System.CommandLine;
-using System.IO;
-using System.Collections.Generic;
-using System.Reflection;
-using System.Linq;
-using System.Text;
-
-namespace ManagedCodeGen
-{
- // Define options to be parsed
- public class Config
- {
- private ArgumentSyntax _syntaxResult;
- private string _altjit = null;
- private string _corerunExe = null;
- private string _jitPath = null;
- private string _rootPath = null;
- private string _fileName = null;
- private IReadOnlyList _assemblyList = Array.Empty();
- private bool _wait = false;
- private bool _recursive = false;
- private IReadOnlyList _methods = Array.Empty();
- private IReadOnlyList _platformPaths = Array.Empty();
- private bool _dumpGCInfo = false;
- private bool _dumpDebugInfo = false;
- private bool _noCopyJit = false;
- private bool _verbose = false;
- private bool _noDiffable = false;
- private bool _tier0 = false;
- private bool _cctors = false;
-
- public Config(string[] args)
- {
- _syntaxResult = ArgumentSyntax.Parse(args, syntax =>
- {
- syntax.DefineOption("altjit", ref _altjit, "If set, the name of the altjit to use (e.g., clrjit_win_arm64_x64.dll).");
- syntax.DefineOption("c|corerun", ref _corerunExe, "The corerun compiler exe.");
- syntax.DefineOption("j|jit", ref _jitPath, "The full path to the jit library.");
- syntax.DefineOption("o|output", ref _rootPath, "The output path.");
- syntax.DefineOption("f|file", ref _fileName, "Name of file to take list of assemblies from. Both a file and assembly list can be used.");
- syntax.DefineOption("gcinfo", ref _dumpGCInfo, "Add GC info to the disasm output.");
- syntax.DefineOption("debuginfo", ref _dumpDebugInfo, "Add Debug info to the disasm output.");
- syntax.DefineOption("v|verbose", ref _verbose, "Enable verbose output.");
- syntax.DefineOption("nodiffable", ref _noDiffable, "Generate non-diffable asm (pointer values will be left in output).");
- syntax.DefineOption("tier0", ref this._tier0, "Generate tier0 code.");
- syntax.DefineOption("cctors", ref _cctors, "Jit and run cctors before jitting other methods");
- syntax.DefineOption("r|recursive", ref _recursive, "Scan directories recursively.");
- syntax.DefineOptionList("p|platform", ref _platformPaths, "Path to platform assemblies");
-
- var waitArg = syntax.DefineOption("w|wait", ref _wait, "Wait for debugger to attach.");
- waitArg.IsHidden = true;
-
- var methodsArg = syntax.DefineOptionList("m|methods", ref _methods, "List of methods to disasm.");
- methodsArg.IsHidden = true;
-
- var noCopyArg = syntax.DefineOption("nocopy", ref _noCopyJit, "Correct jit has already been copied into the corerun directory");
- noCopyArg.IsHidden = true;
-
- // Warning!! - Parameters must occur after options to preserve parsing semantics.
-
- syntax.DefineParameterList("assembly", ref _assemblyList, "The list of assemblies or directories to scan for assemblies.");
- });
-
- // Run validation code on parsed input to ensure we have a sensible scenario.
-
- Validate();
- }
-
- // Validate arguments
- //
- // Pass a single tool as --corerun. Optionally specify a jit for corerun to use.
- //
- private void Validate()
- {
- if (_corerunExe == null)
- {
- _syntaxResult.ReportError("Specify --corerun.");
- }
-
- if ((_fileName == null) && (_assemblyList.Count == 0))
- {
- _syntaxResult.ReportError("No input: Specify --file or list input assemblies.");
- }
-
- // Check that we can find the corerunExe
- if (_corerunExe != null)
- {
- if (!File.Exists(_corerunExe))
- {
- _syntaxResult.ReportError("Can't find --corerun tool.");
- }
- else
- {
- // Set to full path for command resolution logic.
- string fullCorerunPath = Path.GetFullPath(_corerunExe);
- _corerunExe = fullCorerunPath;
- }
- }
-
- // Check that we can find the jit library.
- if (_jitPath != null)
- {
- if (!File.Exists(_jitPath))
- {
- _syntaxResult.ReportError("Can't find --jit library.");
- }
- else
- {
- // Set to full path for command resolution logic.
- string fullJitPath = Path.GetFullPath(_jitPath);
- _jitPath = fullJitPath;
- }
- }
-
- if (_fileName != null)
- {
- if (!File.Exists(_fileName))
- {
- var message = String.Format("Error reading input file {0}, file not found.", _fileName);
- _syntaxResult.ReportError(message);
- }
- }
- }
-
- public bool HasUserAssemblies { get { return AssemblyList.Count > 0; } }
- public bool WaitForDebugger { get { return _wait; } }
- public bool UseJitPath { get { return (_jitPath != null); } }
- public bool Recursive { get { return _recursive; } }
- public bool UseFileName { get { return (_fileName != null); } }
- public bool DumpGCInfo { get { return _dumpGCInfo; } }
- public bool DumpDebugInfo { get { return _dumpDebugInfo; } }
- public bool DoVerboseOutput { get { return _verbose; } }
- public bool NoDiffable { get { return _noDiffable; } }
- public bool CopyJit { get { return !_noCopyJit; } }
- public string CorerunExecutable { get { return _corerunExe; } }
- public string JitPath { get { return _jitPath; } }
- public string AltJit { get { return _altjit; } }
- public string RootPath { get { return _rootPath; } }
- public IReadOnlyList PlatformPaths { get { return _platformPaths; } }
- public string FileName { get { return _fileName; } }
- public IReadOnlyList AssemblyList { get { return _assemblyList; } }
- public bool Tier0 => _tier0;
- public bool Cctors => _cctors;
- }
-
- public class AssemblyInfo
- {
- public string Name { get; set; }
- // Contains path to assembly.
- public string Path { get; set; }
- // Contains relative path within output directory for given assembly.
- // This allows for different output directories per tool.
- public string OutputPath { get; set; }
- }
-
- public class jitdasmpmi
- {
- public static int Main(string[] args)
- {
- // Error count will be returned. Start at 0 - this will be incremented
- // based on the error counts derived from the DisasmEngine executions.
- int errorCount = 0;
-
- // Parse and store comand line options.
- var config = new Config(args);
-
- // Stop to attach a debugger if desired.
- if (config.WaitForDebugger)
- {
- WaitForDebugger();
- }
-
- // Builds assemblyInfoList on jitdasm
-
- List assemblyWorkList = GenerateAssemblyWorklist(config);
-
- // The disasm engine encapsulates a particular set of diffs. An engine is
- // produced with a given code generator and assembly list, which then produces
- // a set of disasm outputs.
-
- DisasmEnginePmi corerunDisasm = new DisasmEnginePmi(config.CorerunExecutable, config, config.RootPath, assemblyWorkList);
- corerunDisasm.GenerateAsm();
-
- if (corerunDisasm.ErrorCount > 0)
- {
- Console.Error.WriteLine("{0} errors compiling set.", corerunDisasm.ErrorCount);
- errorCount += corerunDisasm.ErrorCount;
- }
-
- return errorCount;
- }
-
- private static void WaitForDebugger()
- {
- Console.WriteLine("Wait for a debugger to attach. Press ENTER to continue");
- Console.WriteLine($"Process ID: {Process.GetCurrentProcess().Id}");
- Console.ReadLine();
- }
-
- public static List GenerateAssemblyWorklist(Config config)
- {
- bool verbose = config.DoVerboseOutput;
- List assemblyList = new List();
- List assemblyInfoList = new List();
-
- if (config.UseFileName)
- {
- assemblyList = new List();
- string inputFile = config.FileName;
-
- // Open file, read assemblies one per line, and add them to the assembly list.
- using (var inputStream = System.IO.File.Open(inputFile, FileMode.Open))
- {
- using (var inputStreamReader = new StreamReader(inputStream))
- {
- string line;
- while ((line = inputStreamReader.ReadLine()) != null)
- {
- // Each line is a path to an assembly.
- if (!File.Exists(line))
- {
- Console.WriteLine("Can't find {0} skipping...", line);
- continue;
- }
-
- assemblyList.Add(line);
- }
- }
- }
- }
-
- if (config.HasUserAssemblies)
- {
- // Append command line assemblies
- assemblyList.AddRange(config.AssemblyList);
- }
-
- // Process worklist and produce the info needed for the disasm engines.
- foreach (var path in assemblyList)
- {
- FileAttributes attr;
-
- if (File.Exists(path) || Directory.Exists(path))
- {
- attr = File.GetAttributes(path);
- }
- else
- {
- Console.WriteLine("Can't find assembly or directory at {0}", path);
- continue;
- }
-
- if ((attr & FileAttributes.Directory) == FileAttributes.Directory)
- {
- if (verbose)
- {
- Console.WriteLine("Processing directory: {0}", path);
- }
-
- // For the directory case create a stack and recursively find any
- // assemblies for compilation.
- List directoryAssemblyInfoList = IdentifyAssemblies(path,
- config);
-
- // Add info generated at this directory
- assemblyInfoList.AddRange(directoryAssemblyInfoList);
- }
- else
- {
- // This is the file case.
-
- AssemblyInfo info = new AssemblyInfo
- {
- Name = Path.GetFileName(path),
- Path = Path.GetDirectoryName(path),
- OutputPath = ""
- };
-
- assemblyInfoList.Add(info);
- }
- }
-
- return assemblyInfoList;
- }
-
- // Recursivly search for assemblies from a root path.
- private static List IdentifyAssemblies(string rootPath, Config config)
- {
- List assemblyInfoList = new List();
- string fullRootPath = Path.GetFullPath(rootPath);
- SearchOption searchOption = (config.Recursive) ?
- SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
-
- // Get files that could be assemblies, but discard currently
- // ngen'd assemblies.
- var subFiles = Directory.EnumerateFiles(rootPath, "*", searchOption)
- .Where(s => (s.EndsWith(".exe") || s.EndsWith(".dll")) && !s.Contains(".ni."));
-
- foreach (var filePath in subFiles)
- {
- if (config.DoVerboseOutput)
- {
- Console.WriteLine("Scanning: {0}", filePath);
- }
-
- // skip if not an assembly
- if (!Utility.IsAssembly(filePath))
- {
- continue;
- }
-
- string fileName = Path.GetFileName(filePath);
- string directoryName = Path.GetDirectoryName(filePath);
- string fullDirectoryName = Path.GetFullPath(directoryName);
- string outputPath = fullDirectoryName.Substring(fullRootPath.Length).TrimStart(Path.DirectorySeparatorChar);
-
- AssemblyInfo info = new AssemblyInfo
- {
- Name = fileName,
- Path = directoryName,
- OutputPath = outputPath
- };
-
- assemblyInfoList.Add(info);
- }
-
- return assemblyInfoList;
- }
-
- private class DisasmEnginePmi
- {
- private string _executablePath;
- private Config _config;
- private string _rootPath = null;
- private IReadOnlyList _platformPaths;
- private string _jitPath = null;
- private string _altjit = null;
- private List _assemblyInfoList;
- public bool doGCDump = false;
- public bool doDebugDump = false;
- public bool verbose = false;
- private int _errorCount = 0;
-
- public int ErrorCount { get { return _errorCount; } }
-
- private string GetPmiJitLibraryName(string suffix = "")
- {
- string jitName = Path.GetFileNameWithoutExtension(_jitPath);
- string pmiJitName = jitName + suffix + Path.GetExtension(_jitPath);
- return pmiJitName;
- }
-
- public DisasmEnginePmi(string executable, Config config, string outputPath,
- List assemblyInfoList)
- {
- _config = config;
- _executablePath = executable;
- _rootPath = outputPath;
- _platformPaths = config.PlatformPaths;
- _jitPath = config.JitPath;
- _altjit = config.AltJit;
- _assemblyInfoList = assemblyInfoList;
-
- this.doGCDump = config.DumpGCInfo;
- this.doDebugDump = config.DumpDebugInfo;
- this.verbose = config.DoVerboseOutput;
- }
-
- public void GenerateAsm()
- {
- string testOverlayDir = Path.GetDirectoryName(_config.CorerunExecutable);
- string jitDir = Path.GetDirectoryName(_jitPath);
- string realJitPath = Path.Combine(testOverlayDir, GetPmiJitLibraryName());
- string tempJitPath = Path.Combine(testOverlayDir, GetPmiJitLibraryName("-backup"));
-
- try
- {
- if (_config.CopyJit)
- {
- if (this.verbose)
- {
- Console.WriteLine($"Copying default jit: {realJitPath} ==> {tempJitPath}");
- }
- File.Copy(realJitPath, tempJitPath, true);
- if (this.verbose)
- {
- Console.WriteLine($"Copying in the test jit: {_jitPath} ==> {realJitPath}");
- }
- // May need chmod +x for non-windows ??
- File.Copy(_jitPath, realJitPath, true);
- }
-
- GenerateAsmInternal();
- }
- catch (Exception e)
- {
- Console.WriteLine($"JIT DASM PMI failed: {e.Message}");
- _errorCount++;
- }
- finally
- {
- if (_config.CopyJit)
- {
- if (this.verbose)
- {
- Console.WriteLine($"Restoring default jit: {tempJitPath} ==> {realJitPath}");
- }
- File.Copy(tempJitPath, realJitPath, true);
- }
- }
- }
- void GenerateAsmInternal()
- {
- // Build a command per assembly to generate the asm output.
- foreach (var assembly in _assemblyInfoList)
- {
- if (_config.DoVerboseOutput)
- {
- Console.WriteLine("assembly name: " + assembly.Name);
- }
- string fullPathAssembly = Path.Combine(assembly.Path, assembly.Name);
-
- if (!File.Exists(fullPathAssembly))
- {
- // Assembly not found. Produce a warning and skip this input.
- Console.WriteLine("Skipping. Assembly not found: {0}", fullPathAssembly);
- continue;
- }
-
- Assembly thisAssembly = typeof(DisasmEnginePmi).Assembly;
- string binDir = Path.GetDirectoryName(thisAssembly.Location);
- string command = "DRIVEALL-QUIET";
- if (_config.Cctors)
- {
- command += "-CCTORS";
- }
- List commandArgs = new List() { Path.Combine(binDir, "pmi.dll"), command, fullPathAssembly };
-
- Dictionary _environmentVariables = new Dictionary();
- // Add environment variables to the environment of the command we are going to execute, and
- // display them to the user in verbose mode.
- void AddEnvironmentVariable(string varName, string varValue)
- {
- _environmentVariables[varName] = varValue;
- if (this.verbose)
- {
- Console.WriteLine("set {0}={1}", varName, varValue);
- }
- }
-
- StringBuilder pmiEnv = new StringBuilder();
- // Append environment variable to the string that will be used as a value of PMIENV environment
- // variable.
- void AppendEnvironmentVariableToPmiEnv(string varName, string varValue)
- {
- if (pmiEnv.Length > 0)
- {
- pmiEnv.Append(";");
- }
- pmiEnv.Append(varName + "=" + varValue);
- if (this.verbose)
- {
- Console.WriteLine("Appending: {0}={1} to PMIENV", varName, varValue);
- }
- }
-
- // Pick up ambient DOTNET settings.
- foreach (string envVar in Environment.GetEnvironmentVariables().Keys)
- {
- if (envVar.IndexOf("DOTNET_") == 0)
- {
- string value = Environment.GetEnvironmentVariable(envVar);
- AppendEnvironmentVariableToPmiEnv(envVar, value);
- }
- }
-
- // Set up environment do PMI based disasm.
- AppendEnvironmentVariableToPmiEnv("DOTNET_JitDisasm", "*");
- AppendEnvironmentVariableToPmiEnv("DOTNET_JitDisasmAssemblies", Path.GetFileNameWithoutExtension(assembly.Name));
- AppendEnvironmentVariableToPmiEnv("DOTNET_JitUnwindDump", "*");
- AppendEnvironmentVariableToPmiEnv("DOTNET_JitEHDump", "*");
- if (!this._config.NoDiffable)
- {
- AppendEnvironmentVariableToPmiEnv("DOTNET_JitDiffableDasm", "1");
- }
- AppendEnvironmentVariableToPmiEnv("DOTNET_ReadyToRun", "0");
- AppendEnvironmentVariableToPmiEnv("DOTNET_ZapDisable", "1");
- AppendEnvironmentVariableToPmiEnv("DOTNET_JitEnableNoWayAssert", "1"); // Force noway_assert to generate assert (not fall back to MinOpts).
- AppendEnvironmentVariableToPmiEnv("DOTNET_JitNoForceFallback", "1"); // Don't stress noway fallback path.
- AppendEnvironmentVariableToPmiEnv("DOTNET_JitRequired", "1"); // Force NO_WAY to generate assert. Also generates assert for BADCODE/BADCODE3.
-
- // We likely don't want tiering enabled, but allow it, if user wants tier0 codegen
- AppendEnvironmentVariableToPmiEnv("DOTNET_TieredCompilation", _config.Tier0 ? "1" : "0");
-
- if (_config.Tier0)
- {
- // jit all methods at tier0
- AppendEnvironmentVariableToPmiEnv("DOTNET_TC_QuickJitForLoops", "1");
- // don't promote any method to tier1
- AppendEnvironmentVariableToPmiEnv("DOTNET_TC_CallCounting", "0");
- }
-
- if (this.doGCDump)
- {
- AppendEnvironmentVariableToPmiEnv("DOTNET_JitGCDump", "*");
- }
-
- if (this.doDebugDump)
- {
- AppendEnvironmentVariableToPmiEnv("DOTNET_JitDebugDump", "*");
- }
-
- if (this._altjit != null)
- {
- AppendEnvironmentVariableToPmiEnv("DOTNET_AltJit", "*");
- AppendEnvironmentVariableToPmiEnv("DOTNET_AltJitName", _altjit);
-
- const string arm64AsTarget = "_arm64_";
- int targetArm64 = _altjit.IndexOf(arm64AsTarget);
- if (targetArm64 > 0)
- {
- bool isHostArm64 = (_altjit.IndexOf("arm64", targetArm64 + arm64AsTarget.Length) > 0);
- if (!isHostArm64)
- {
- // If this looks like a cross-targeting altjit with a arm64 target and a different host
- // then fix the SIMD size.
- AppendEnvironmentVariableToPmiEnv("DOTNET_SIMD16ByteOnly", "1");
- }
- }
- }
-
- // Set up PMI path...
- AddEnvironmentVariable("PMIPATH", assembly.Path);
-
- if (this.verbose)
- {
- Console.WriteLine("Running: {0} {1}", _executablePath, String.Join(" ", commandArgs));
- }
-
- ProcessResult result;
-
- if (_rootPath != null)
- {
- // Generate path to the output file
- var assemblyFileName = Path.ChangeExtension(assembly.Name, ".dasm");
- var dasmPath = Path.Combine(_rootPath, assembly.OutputPath, assemblyFileName);
- var logPath = Path.ChangeExtension(dasmPath, ".log");
-
- Utility.EnsureParentDirectoryExists(dasmPath);
-
- AppendEnvironmentVariableToPmiEnv("DOTNET_JitStdOutFile", dasmPath);
-
- AddEnvironmentVariable("PMIENV", pmiEnv.ToString());
-
- result = Utility.ExecuteProcess(_executablePath, commandArgs, true, environmentVariables: _environmentVariables);
-
- // Redirect stdout/stderr to log file and run command.
- StringBuilder output = new StringBuilder();
- if (!string.IsNullOrEmpty(result.StdOut))
- {
- output.AppendLine(result.StdOut);
- }
- if (!string.IsNullOrEmpty(result.StdErr) && (result.StdOut != result.StdErr))
- {
- output.AppendLine(result.StdErr);
- }
- if (output.Length > 0)
- {
- File.WriteAllText(logPath, output.ToString());
- }
-
- bool hasOutput = true;
-
- if (result.ExitCode != 0)
- {
- _errorCount++;
-
- if (result.ExitCode == -2146234344)
- {
- Console.Error.WriteLine("{0} is not a managed assembly", fullPathAssembly);
-
- // Discard output if the assembly is not managed
- File.Delete(dasmPath);
- File.Delete(logPath);
-
- hasOutput = false;
- }
- else
- {
- Console.Error.WriteLine("Error running {0} on {1}", _executablePath, fullPathAssembly);
- }
- }
-
- if (hasOutput && File.Exists(logPath) && !File.Exists(dasmPath))
- {
- // Looks like the JIT does not support DOTNET_JitStdOutFile so
- // the assembly output must be in the log file.
- File.Move(logPath, dasmPath);
- }
- }
- else
- {
- AddEnvironmentVariable("PMIENV", pmiEnv.ToString());
-
- // By default forward to output to stdout/stderr.
- result = Utility.ExecuteProcess(_executablePath, commandArgs, environmentVariables: _environmentVariables);
-
- if (result.ExitCode != 0)
- {
- _errorCount++;
- }
- }
- }
- }
- }
- }
-}
diff --git a/src/jit-dasm/JitDasmRootCommand.cs b/src/jit-dasm/JitDasmRootCommand.cs
new file mode 100644
index 00000000..8e6f3051
--- /dev/null
+++ b/src/jit-dasm/JitDasmRootCommand.cs
@@ -0,0 +1,128 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.IO;
+
+namespace ManagedCodeGen
+{
+ internal sealed class JitDasmRootCommand : RootCommand
+ {
+ public Option AltJit { get; } =
+ new("--altjit", "If set, the name of the altjit to use (e.g., clrjit_win_arm64_x64.dll)");
+ public Option CrossgenPath { get; } =
+ new(new[] { "--crossgen", "-c" }, result => result.Tokens.Count > 0 ? Path.GetFullPath(result.Tokens[0].Value) : null, true, "The crossgen or crossgen2 compiler exe.");
+ public Option JitPath { get; } =
+ new(new[] { "--jit", "-j" }, result => result.Tokens.Count > 0 ? Path.GetFullPath(result.Tokens[0].Value) : null, true, "The full path to the jit library");
+ public Option OutputPath { get; } =
+ new(new[] { "--output", "-o" }, "The output path");
+ public Option Filename { get; } =
+ new(new[] { "--file", "-f" }, "Name of file to take list of assemblies from. Both a file and assembly list can be used");
+ public Option DumpGCInfo { get; } =
+ new("--gcinfo", "Add GC info to the disasm output");
+ public Option DumpDebugInfo { get; } =
+ new("--debuginfo", "Add Debug info to the disasm output");
+ public Option Verbose { get; } =
+ new("--verbose", "Enable verbose output");
+ public Option NoDiffable { get; } =
+ new("--nodiffable", "Generate non-diffable asm (pointer values will be left in output)");
+ public Option Recursive { get; } =
+ new(new[] { "--recursive", "-r" }, "Search directories recursively");
+ public Option> PlatformPaths { get; } =
+ new(new[] { "--platform", "-p" }, "Path to platform assemblies");
+ public Option> Methods { get; } =
+ new(new[] { "--methods", "-m" }, "List of methods to disasm");
+ public Argument> AssemblyList { get; } =
+ new("--assembly", "The list of assemblies or directories to scan for assemblies");
+ public Option WaitForDebugger { get; } =
+ new(new[] { "--wait", "-w" }, "Wait for debugger to attach");
+
+ public ParseResult Result;
+ public bool CodeGeneratorV1 { get; private set; }
+
+ public JitDasmRootCommand(string[] args) : base("Managed codegen diff tool (crossgen/AOT)")
+ {
+ AddOption(AltJit);
+ AddOption(CrossgenPath);
+ AddOption(JitPath);
+ AddOption(OutputPath);
+ AddOption(Filename);
+ AddOption(DumpGCInfo);
+ AddOption(DumpDebugInfo);
+ AddOption(Verbose);
+ AddOption(NoDiffable);
+ AddOption(Recursive);
+ AddOption(PlatformPaths);
+ AddOption(Methods);
+ AddOption(WaitForDebugger);
+
+ AddArgument(AssemblyList);
+
+ this.SetHandler(context =>
+ {
+ Result = context.ParseResult;
+
+ try
+ {
+ List errors = new();
+ string crossgen = Result.GetValue(CrossgenPath);
+ if (crossgen == null || !File.Exists(crossgen))
+ {
+ errors.Add("Can't find --crossgen tool.");
+ }
+ else
+ {
+ string crossgenFilename = Path.GetFileNameWithoutExtension(crossgen).ToLower();
+ if (crossgenFilename == "crossgen")
+ {
+ CodeGeneratorV1 = true;
+ }
+ else if (crossgenFilename != "crossgen2")
+ {
+ errors.Add("--crossgen tool should be crossgen or crossgen2.");
+ }
+ }
+
+ if (Result.FindResultFor(Filename) == null && Result.GetValue(AssemblyList).Count == 0)
+ {
+ errors.Add("No input: Specify --file or list input assemblies.");
+ }
+
+ string jitPath = Result.GetValue(JitPath);
+ if (jitPath != null && !File.Exists(jitPath))
+ {
+ errors.Add("Can't find --jit library.");
+ }
+
+ string filename = Result.GetValue(Filename);
+ if (filename != null && !File.Exists(filename))
+ {
+ errors.Add($"Error reading input file {filename}, file not found.");
+ }
+
+ if (errors.Count > 0)
+ {
+ throw new Exception(string.Join(Environment.NewLine, errors));
+ }
+
+ context.ExitCode = new Program(this).Run();
+ }
+ catch (Exception e)
+ {
+ Console.ResetColor();
+ Console.ForegroundColor = ConsoleColor.Red;
+
+ Console.Error.WriteLine("Error: " + e.Message);
+ Console.Error.WriteLine(e.ToString());
+
+ Console.ResetColor();
+
+ context.ExitCode = 1;
+ }
+ });
+ }
+ }
+}
diff --git a/src/jit-dasm/Program.cs b/src/jit-dasm/Program.cs
new file mode 100644
index 00000000..acd54a52
--- /dev/null
+++ b/src/jit-dasm/Program.cs
@@ -0,0 +1,473 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+///////////////////////////////////////////////////////////////////////////////
+//
+// jitdasm - The managed code gen diff tool scripts the generation of
+// diffable assembly code output from the crossgen ahead of time compilation
+// tool. This enables quickly generating A/B comparisons of .Net codegen
+// tools to validate ongoing development.
+//
+// Scenario 1: Pass A and B compilers to jitdasm. Using the --base and --diff
+// arguments pass two seperate compilers and a passed set of assemblies. This
+// is the most common scenario.
+//
+// Scenario 2: Iterativly call jitdasm with a series of compilers tagging
+// each run. Allows for producing a broader set of results like 'base',
+// 'experiment1', 'experiment2', and 'experiment3'. This tagging is only
+// allowed in the case where a single compiler is passed to avoid any
+// confusion in the generated results.
+//
+
+using System;
+using System.Diagnostics;
+using System.CommandLine;
+using System.CommandLine.Parsing;
+using System.IO;
+using System.Collections.Generic;
+using System.Runtime.Loader;
+using System.Linq;
+using System.Text;
+
+namespace ManagedCodeGen
+{
+ public class AssemblyInfo
+ {
+ public string Name { get; set; }
+ // Contains path to assembly.
+ public string Path { get; set; }
+ // Contains relative path within output directory for given assembly.
+ // This allows for different output directories per tool.
+ public string OutputPath { get; set; }
+ }
+
+
+ internal sealed class Program
+ {
+ private readonly JitDasmRootCommand _command;
+ private readonly string _crossgenPath;
+ private readonly bool _verbose;
+ private readonly Dictionary _environmentVariables = new();
+
+ public Program(JitDasmRootCommand command)
+ {
+ _command = command;
+ _crossgenPath = Get(command.CrossgenPath);
+ _verbose = Get(command.Verbose);
+ }
+
+ public int Run()
+ {
+ // Stop to attach a debugger if desired.
+ if (Get(_command.WaitForDebugger))
+ {
+ Console.WriteLine("Wait for a debugger to attach. Press ENTER to continue");
+ Console.WriteLine($"Process ID: {Process.GetCurrentProcess().Id}");
+ Console.ReadLine();
+ }
+
+ // Builds assemblyInfoList on jitdasm
+ List assemblyWorkList = GenerateAssemblyWorklist();
+
+ // Produces a set of disasm outputs for a given code generator and assembly list/
+ int errorCount = GenerateAsm(assemblyWorkList);
+ if (errorCount > 0)
+ {
+ Console.Error.WriteLine("{0} errors compiling set.", errorCount);
+ }
+
+ return errorCount;
+ }
+
+ private T Get(Option option) => _command.Result.GetValue(option);
+ private T Get(Argument