From 8c9f7bf05012c17b79f2938f8ace60e9535e1fc7 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Sat, 29 Oct 2022 15:04:02 +0300 Subject: [PATCH 01/22] Use System.CommandLine v2 in cijobs --- src/cijobs/CIClient.cs | 180 +++++++++ src/cijobs/CIJobsRootCommand.cs | 188 +++++++++ src/cijobs/Models.cs | 55 +++ src/cijobs/Program.cs | 245 ++++++++++++ src/cijobs/cijobs.cs | 678 -------------------------------- 5 files changed, 668 insertions(+), 678 deletions(-) create mode 100644 src/cijobs/CIClient.cs create mode 100644 src/cijobs/CIJobsRootCommand.cs create mode 100644 src/cijobs/Models.cs create mode 100755 src/cijobs/Program.cs delete mode 100755 src/cijobs/cijobs.cs 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..9e7b1c48 --- /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", true, "Name of the branch."); + public Option RepoName { get; } = + new(new[] { "--repo", "-r" }, _ => "dotnet_coreclr", true, "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.GetValueForOption(JobNumber); + bool showLastSuccessful = result.GetValueForOption(ShowLastSuccessful); + string commit = result.GetValueForOption(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.GetValueForOption(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.GetValueForOption(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.GetValueForOption(JobName) == null) + { + errors.Add("Must have --job for copy."); + } + + int jobNumber = result.GetValueForOption(JobNumber); + bool shwoLastSuccessful = result.GetValueForOption(ShowLastSuccessful); + string commit = result.GetValueForOption(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.GetValueForOption(OutputPath); + string outputRoot = result.GetValueForOption(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..73d899e6 --- /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.GetValueForOption(option); + + private static Task Main(string[] args) => + new CommandLineBuilder(new CIJobsRootCommand(args)) + .UseVersionOption("-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); - } - } - } - } -} From c83640e9d900c63cf9f3655193ad48f80e861b5a Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Sat, 29 Oct 2022 16:09:16 +0300 Subject: [PATCH 02/22] Use System.CommandLine v2 in jit-analyze --- src/jit-analyze/IEnumerableExtensions.cs | 31 + src/jit-analyze/JitAnalyzeRootCommand.cs | 128 ++++ src/jit-analyze/MetricCollection.cs | 152 +++++ src/jit-analyze/Metrics.cs | 162 +++++ .../{jit-analyze.cs => Program.cs} | 578 +++--------------- 5 files changed, 552 insertions(+), 499 deletions(-) create mode 100644 src/jit-analyze/IEnumerableExtensions.cs create mode 100644 src/jit-analyze/JitAnalyzeRootCommand.cs create mode 100644 src/jit-analyze/MetricCollection.cs create mode 100644 src/jit-analyze/Metrics.cs rename src/jit-analyze/{jit-analyze.cs => Program.cs} (66%) 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..7ea11222 --- /dev/null +++ b/src/jit-analyze/JitAnalyzeRootCommand.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; + +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", true, "File extension to look for"); + public Option Count { get; } = + new(new[] { "--count", "-c" }, _ => 20, true, "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" }, true, $"Comma-separated metric 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", "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", "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.GetValueForOption(BasePath) == null) + { + errors.Add("Base path (--base) is required"); + } + + if (Result.GetValueForOption(DiffPath) == null) + { + errors.Add("Diff path (--diff) is required"); + } + + foreach (string metricName in Result.GetValueForOption(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..7c1717ea 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.GetValueForOption(option); + + private static int Main(string[] args) => + new CommandLineBuilder(new JitAnalyzeRootCommand(args)) + .UseVersionOption("-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; From 817444ba7a8f4326abb4e8109bdd28439059e47d Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Sun, 30 Oct 2022 11:08:52 +0200 Subject: [PATCH 03/22] Use System.CommandLine v2 in jit-dasm-pmi --- src/jit-dasm-pmi/JitDasmPmiRootCommand.cs | 123 +++++ src/jit-dasm-pmi/Program.cs | 475 ++++++++++++++++ src/jit-dasm-pmi/jit-dasm-pmi.cs | 634 ---------------------- 3 files changed, 598 insertions(+), 634 deletions(-) create mode 100644 src/jit-dasm-pmi/JitDasmPmiRootCommand.cs create mode 100644 src/jit-dasm-pmi/Program.cs delete mode 100644 src/jit-dasm-pmi/jit-dasm-pmi.cs diff --git a/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs b/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs new file mode 100644 index 00000000..e6c0036a --- /dev/null +++ b/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs @@ -0,0 +1,123 @@ +// 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(new[] { "--verbose", "-v" }, "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 Option> 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(AssemblyList); + AddOption(WaitForDebugger); + AddOption(NoCopyJit); + + this.SetHandler(context => + { + Result = context.ParseResult; + + try + { + List errors = new(); + string corerun = Result.GetValueForOption(CorerunPath); + if (corerun == null || !File.Exists(corerun)) + { + errors.Add("Can't find --corerun tool."); + } + + if (Result.FindResultFor(Filename) == null && Result.GetValueForOption(AssemblyList).Count == 0) + { + errors.Add("No input: Specify --file or list input assemblies."); + } + + string jitPath = Result.GetValueForOption(JitPath); + if (jitPath != null && !File.Exists(jitPath)) + { + errors.Add("Can't find --jit library."); + } + + string filename = Result.GetValueForOption(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..e527d25a --- /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.GetValueForOption(option); + + private static int Main(string[] args) => + new CommandLineBuilder(new JitDasmPmiRootCommand(args)) + .UseVersionOption("-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; + } + + Assembly thisAssembly = typeof(Program).Assembly; + string binDir = Path.GetDirectoryName(thisAssembly.Location); + 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++; - } - } - } - } - } - } -} From f93be71b897a26bb96f557a1b2bf6dc523ef9856 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Sun, 30 Oct 2022 11:55:44 +0200 Subject: [PATCH 04/22] Use System.CommandLine v2 in jit-dasm --- src/jit-dasm/JitDasmRootCommand.cs | 125 +++++ src/jit-dasm/Program.cs | 473 +++++++++++++++++++ src/jit-dasm/jit-dasm.cs | 724 ----------------------------- 3 files changed, 598 insertions(+), 724 deletions(-) create mode 100644 src/jit-dasm/JitDasmRootCommand.cs create mode 100644 src/jit-dasm/Program.cs delete mode 100644 src/jit-dasm/jit-dasm.cs diff --git a/src/jit-dasm/JitDasmRootCommand.cs b/src/jit-dasm/JitDasmRootCommand.cs new file mode 100644 index 00000000..02e23447 --- /dev/null +++ b/src/jit-dasm/JitDasmRootCommand.cs @@ -0,0 +1,125 @@ +// 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(new[] { "--verbose", "-v" }, "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 Option> 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(AssemblyList); + AddOption(WaitForDebugger); + + this.SetHandler(context => + { + Result = context.ParseResult; + + try + { + List errors = new(); + string crossgen = Result.GetValueForOption(CrossgenPath); + if (crossgen == null || !File.Exists(crossgen)) + { + errors.Add("Can't find --crossgen tool."); + } + + 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.GetValueForOption(AssemblyList).Count == 0) + { + errors.Add("No input: Specify --file or list input assemblies."); + } + + string jitPath = Result.GetValueForOption(JitPath); + if (jitPath != null && !File.Exists(jitPath)) + { + errors.Add("Can't find --jit library."); + } + + string filename = Result.GetValueForOption(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..1482db37 --- /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.GetValueForOption(option); + + private static int Main(string[] args) => + new CommandLineBuilder(new JitDasmRootCommand(args)) + .UseVersionOption("-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) + { + int errorCount = 0; + + // 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; + } + + List commandArgs = new List(); + + // Tell crossgen not to output a success/failure message at the end; that message + // includes a full path to the generated .ni.dll file, which makes all base/diff + // asm files appear to have diffs. + if (_command.CodeGeneratorV1) + { + commandArgs.Add("/silent"); + } + // Also pass /nologo to avoid spurious diffs that sometimes appear when errors + // occur (sometimes the logo lines and error lines are interleaved). + if (_command.CodeGeneratorV1) + { + commandArgs.Add("/nologo"); + } + + string jitPath = Get(_command.JitPath); + // Set jit path if it's defined. + if (jitPath != null) + { + commandArgs.Add(_command.CodeGeneratorV1 ? "/JitPath" : "--jitpath"); + commandArgs.Add(jitPath); + } + + // Set platform assembly path if it's defined. + List platformPaths = Get(_command.PlatformPaths); + if (platformPaths.Count > 0) + { + if (_command.CodeGeneratorV1) + { + commandArgs.Add("/Platform_Assemblies_Paths"); + commandArgs.Add(string.Join(" ", platformPaths)); + } + else + { + commandArgs.Add("--reference"); + commandArgs.Add(string.Join(" ", platformPaths.Select(str => Path.Combine(str, "*.dll")))); + } + } + + string extension = Path.GetExtension(fullPathAssembly); + string nativeOutput = Path.ChangeExtension(fullPathAssembly, "ni" + extension); + string mapOutput = Path.ChangeExtension(fullPathAssembly, "ni.map"); + + string outputPath = Get(_command.OutputPath); + if (outputPath != null) + { + string assemblyNativeFileName = Path.ChangeExtension(assembly.Name, "ni" + extension); + string assemblyMapFileName = Path.ChangeExtension(assembly.Name, "ni.map"); + nativeOutput = Path.Combine(outputPath, assembly.OutputPath, assemblyNativeFileName); + mapOutput = Path.Combine(outputPath, assembly.OutputPath, assemblyMapFileName); + + Utility.EnsureParentDirectoryExists(nativeOutput); + + commandArgs.Add(_command.CodeGeneratorV1 ? "/out" : "--outputfilepath"); + commandArgs.Add(nativeOutput); + } + + if (!_command.CodeGeneratorV1) + { + commandArgs.Add("--optimize"); + } + + commandArgs.Add(fullPathAssembly); + + // Pick up ambient DOTNET_ settings. + foreach (string envVar in Environment.GetEnvironmentVariables().Keys) + { + if (envVar.IndexOf("DOTNET_") == 0) + { + string value = Environment.GetEnvironmentVariable(envVar); + AddEnvironmentVariable(envVar, value); + } + } + + // Set up environment do disasm. + AddEnvironmentVariable("DOTNET_NgenDisasm", "*"); + AddEnvironmentVariable("DOTNET_NgenUnwindDump", "*"); + AddEnvironmentVariable("DOTNET_NgenEHDump", "*"); + if (!Get(_command.NoDiffable)) + { + AddEnvironmentVariable("DOTNET_JitDiffableDasm", "1"); + } + AddEnvironmentVariable("DOTNET_JitEnableNoWayAssert", "1"); // Force noway_assert to generate assert (not fall back to MinOpts). + AddEnvironmentVariable("DOTNET_JitNoForceFallback", "1"); // Don't stress noway fallback path. + AddEnvironmentVariable("DOTNET_JitRequired", "1"); // Force NO_WAY to generate assert. Also generates assert for BADCODE/BADCODE3. + + if (Get(_command.DumpGCInfo )) + { + AddEnvironmentVariable("DOTNET_NgenGCDump", "*"); + } + + if (Get(_command.DumpDebugInfo)) + { + AddEnvironmentVariable("DOTNET_NgenDebugDump", "*"); + } + + string altJit = Get(_command.AltJit); + if (altJit != null) + { + AddEnvironmentVariable("DOTNET_AltJit", "*"); + AddEnvironmentVariable("DOTNET_AltJitNgen", "*"); + AddEnvironmentVariable("DOTNET_AltJitName", altJit); + } + + string dasmPath = null; + if (outputPath != null) + { + // Generate path to the output file + var assemblyFileName = Path.ChangeExtension(assembly.Name, ".dasm"); + dasmPath = Path.Combine(outputPath, assembly.OutputPath, assemblyFileName); + + Utility.EnsureParentDirectoryExists(dasmPath); + + AddEnvironmentVariable("DOTNET_JitStdOutFile", dasmPath); + } + + if (_verbose) + { + Console.WriteLine("Running: {0} {1}", _crossgenPath, string.Join(" ", commandArgs)); + } + + ProcessResult result; + + if (outputPath != null) + { + var logPath = Path.ChangeExtension(dasmPath, ".log"); + result = ExecuteProcess(commandArgs, true); + + // Write stdout/stderr to log file. + StringBuilder output = new StringBuilder(); + if (!string.IsNullOrEmpty(result.StdOut)) + { + output.Append(result.StdOut); + } + if (!string.IsNullOrEmpty(result.StdErr) && (result.StdOut != result.StdErr)) + { + output.Append(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}", _crossgenPath, 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 + { + // By default forward to output to stdout/stderr. + result = ExecuteProcess(commandArgs, false); + + if (result.ExitCode != 0) + { + errorCount++; + } + } + + // Remove the generated .ni.exe/dll/map file; typical use case is generating dasm for + // assemblies in the test tree, and leaving the .ni.dll around would mean that + // subsequent test passes would re-use that code instead of jitting with the + // compiler that's supposed to be tested. + if (File.Exists(nativeOutput)) + { + File.Delete(nativeOutput); + } + if (File.Exists(mapOutput)) + { + File.Delete(mapOutput); + } + } + + return errorCount; + } + + private ProcessResult ExecuteProcess(List commandArgs, bool capture) + { + if (_command.CodeGeneratorV1) + { + return Utility.ExecuteProcess(_crossgenPath, commandArgs, capture, environmentVariables: _environmentVariables); + } + else + { + commandArgs.Add("--parallelism 1"); + foreach (var envVar in _environmentVariables) + { + commandArgs.Add("--codegenopt"); + string dotnetPrefix = "DOTNET_"; + commandArgs.Add(string.Format("{0}={1}", envVar.Key.Substring(dotnetPrefix.Length), envVar.Value)); + } + return Utility.ExecuteProcess(_crossgenPath, commandArgs, capture); + } + } + + // 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); + } + } + } +} diff --git a/src/jit-dasm/jit-dasm.cs b/src/jit-dasm/jit-dasm.cs deleted file mode 100644 index dd258be5..00000000 --- a/src/jit-dasm/jit-dasm.cs +++ /dev/null @@ -1,724 +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. - -/////////////////////////////////////////////////////////////////////////////// -// -// 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.IO; -using System.Collections.Generic; -using System.Runtime.Loader; -using System.Linq; -using System.Text; - -namespace ManagedCodeGen -{ - public enum CodeGenerator - { - Crossgen, - Crossgen2 - } - - // Define options to be parsed - public class Config - { - private ArgumentSyntax _syntaxResult; - private string _altjit = null; - private string _crossgenExe = 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 _verbose = false; - private bool _noDiffable = false; - private CodeGenerator _codeGenerator; - - 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|crossgen", ref _crossgenExe, "The crossgen or crossgen2 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)."); - var waitArg = syntax.DefineOption("w|wait", ref _wait, "Wait for debugger to attach."); - waitArg.IsHidden = true; - - syntax.DefineOption("r|recursive", ref _recursive, "Scan directories recursively."); - syntax.DefineOptionList("p|platform", ref _platformPaths, "Path to platform assemblies"); - var methodsArg = syntax.DefineOptionList("m|methods", ref _methods, - "List of methods to disasm."); - methodsArg.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 --crossgen. Optionally specify a jit for crossgen to use. - // - private void Validate() - { - if (_crossgenExe == null) - { - _syntaxResult.ReportError("Specify --crossgen."); - } - - if ((_fileName == null) && (_assemblyList.Count == 0)) - { - _syntaxResult.ReportError("No input: Specify --file or list input assemblies."); - } - - // Check that we can find the crossgenExe - if (_crossgenExe != null) - { - if (!File.Exists(_crossgenExe)) - { - _syntaxResult.ReportError("Can't find --crossgen tool."); - } - else - { - // Set to full path for command resolution logic. - string fullCrossgenPath = Path.GetFullPath(_crossgenExe); - _crossgenExe = fullCrossgenPath; - - switch (Path.GetFileNameWithoutExtension(fullCrossgenPath).ToLower()) - { - case "crossgen": - _codeGenerator = CodeGenerator.Crossgen; - break; - case "crossgen2": - _codeGenerator = CodeGenerator.Crossgen2; - break; - default: - _syntaxResult.ReportError("--crossgen tool should be crossgen or crossgen2."); - break; - } - } - } - - // 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 string CrossgenExecutable { get { return _crossgenExe; } } - 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 CodeGenerator CodeGenerator { get { return _codeGenerator; } } - } - - 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 jitdasm - { - 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. - - - DisasmEngine crossgenDisasm; - - if (config.CodeGenerator == CodeGenerator.Crossgen) - { - crossgenDisasm = new CrossgenDisasmEngine(config.CrossgenExecutable, config, config.RootPath, assemblyWorkList); - } - else - { - crossgenDisasm = new Crossgen2DisasmEngine(config.CrossgenExecutable, config, config.RootPath, assemblyWorkList); - } - crossgenDisasm.GenerateAsm(); - - if (crossgenDisasm.ErrorCount > 0) - { - Console.Error.WriteLine("{0} errors compiling set.", crossgenDisasm.ErrorCount); - errorCount += crossgenDisasm.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 abstract class DisasmEngine - { - protected string _executablePath; - private Config _config; - private string _rootPath = null; - protected IReadOnlyList _platformPaths; - protected 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; - protected Dictionary _environmentVariables; - - public int ErrorCount { get { return _errorCount; } } - - public DisasmEngine(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; - _environmentVariables = new Dictionary(); - - this.doGCDump = config.DumpGCInfo; - this.doDebugDump = config.DumpDebugInfo; - this.verbose = config.DoVerboseOutput; - } - - public void GenerateAsm() - { - // 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; - } - - List commandArgs = new List(); - - // Tell crossgen not to output a success/failure message at the end; that message - // includes a full path to the generated .ni.dll file, which makes all base/diff - // asm files appear to have diffs. - AddSilentOption(commandArgs); - // Also pass /nologo to avoid spurious diffs that sometimes appear when errors - // occur (sometimes the logo lines and error lines are interleaved). - AddNoLogoOption(commandArgs); - - // Set jit path if it's defined. - if (_jitPath != null) - { - AddJitPathOption(commandArgs); - } - - // Set platform assembly path if it's defined. - if (_platformPaths.Count > 0) - { - AddAssembliesPathsOption(commandArgs); - } - - string extension = Path.GetExtension(fullPathAssembly); - string nativeOutput = Path.ChangeExtension(fullPathAssembly, "ni" + extension); - string mapOutput = Path.ChangeExtension(fullPathAssembly, "ni.map"); - - if (_rootPath != null) - { - string assemblyNativeFileName = Path.ChangeExtension(assembly.Name, "ni" + extension); - string assemblyMapFileName = Path.ChangeExtension(assembly.Name, "ni.map"); - nativeOutput = Path.Combine(_rootPath, assembly.OutputPath, assemblyNativeFileName); - mapOutput = Path.Combine(_rootPath, assembly.OutputPath, assemblyMapFileName); - - Utility.EnsureParentDirectoryExists(nativeOutput); - - AddOutputPathOption(commandArgs, nativeOutput); - } - - AddOptimizationOption(commandArgs); - - commandArgs.Add(fullPathAssembly); - - // Pick up ambient DOTNET_ settings. - foreach (string envVar in Environment.GetEnvironmentVariables().Keys) - { - if (envVar.IndexOf("DOTNET_") == 0) - { - string value = Environment.GetEnvironmentVariable(envVar); - AddEnvironmentVariable(envVar, value); - } - } - - // Set up environment do disasm. - AddEnvironmentVariable("DOTNET_NgenDisasm", "*"); - AddEnvironmentVariable("DOTNET_NgenUnwindDump", "*"); - AddEnvironmentVariable("DOTNET_NgenEHDump", "*"); - if (!this._config.NoDiffable) - { - AddEnvironmentVariable("DOTNET_JitDiffableDasm", "1"); - } - AddEnvironmentVariable("DOTNET_JitEnableNoWayAssert", "1"); // Force noway_assert to generate assert (not fall back to MinOpts). - AddEnvironmentVariable("DOTNET_JitNoForceFallback", "1"); // Don't stress noway fallback path. - AddEnvironmentVariable("DOTNET_JitRequired", "1"); // Force NO_WAY to generate assert. Also generates assert for BADCODE/BADCODE3. - - if (this.doGCDump) - { - AddEnvironmentVariable("DOTNET_NgenGCDump", "*"); - } - - if (this.doDebugDump) - { - AddEnvironmentVariable("DOTNET_NgenDebugDump", "*"); - } - - if (this._altjit != null) - { - AddEnvironmentVariable("DOTNET_AltJit", "*"); - AddEnvironmentVariable("DOTNET_AltJitNgen", "*"); - AddEnvironmentVariable("DOTNET_AltJitName", _altjit); - } - - string dasmPath = null; - if (_rootPath != null) - { - // Generate path to the output file - var assemblyFileName = Path.ChangeExtension(assembly.Name, ".dasm"); - dasmPath = Path.Combine(_rootPath, assembly.OutputPath, assemblyFileName); - - Utility.EnsureParentDirectoryExists(dasmPath); - - AddEnvironmentVariable("DOTNET_JitStdOutFile", dasmPath); - } - - if (this.verbose) - { - Console.WriteLine("Running: {0} {1}", _executablePath, String.Join(" ", commandArgs)); - } - - ProcessResult result; - - if (_rootPath != null) - { - var logPath = Path.ChangeExtension(dasmPath, ".log"); - result = ExecuteProcess(commandArgs, true); - - // Write stdout/stderr to log file. - StringBuilder output = new StringBuilder(); - if (!string.IsNullOrEmpty(result.StdOut)) - { - output.Append(result.StdOut); - } - if (!string.IsNullOrEmpty(result.StdErr) && (result.StdOut != result.StdErr)) - { - output.Append(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 - { - // By default forward to output to stdout/stderr. - result = ExecuteProcess(commandArgs); - - if (result.ExitCode != 0) - { - _errorCount++; - } - } - - // Remove the generated .ni.exe/dll/map file; typical use case is generating dasm for - // assemblies in the test tree, and leaving the .ni.dll around would mean that - // subsequent test passes would re-use that code instead of jitting with the - // compiler that's supposed to be tested. - if (File.Exists(nativeOutput)) - { - File.Delete(nativeOutput); - } - if (File.Exists(mapOutput)) - { - File.Delete(mapOutput); - } - } - } - - // 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); - } - } - - abstract protected void AddSilentOption(List commandArgs); - - abstract protected void AddNoLogoOption(List commandArgs); - - abstract protected void AddJitPathOption(List commandArgs); - - abstract protected void AddAssembliesPathsOption(List commandArgs); - - abstract protected void AddOutputPathOption(List commandArgs, string outputPath); - - abstract protected void AddOptimizationOption(List commandArgs); - - abstract protected ProcessResult ExecuteProcess(List commandArgs, bool capture = false); - } - - sealed private class CrossgenDisasmEngine : DisasmEngine - { - public CrossgenDisasmEngine(string executable, Config config, string outputPath, - List assemblyInfoList) : base(executable, config, outputPath, assemblyInfoList) - { - } - - override protected void AddSilentOption(List commandArgs) - { - commandArgs.Add("/silent"); - } - - override protected void AddNoLogoOption(List commandArgs) - { - commandArgs.Add("/nologo"); - } - - override protected void AddJitPathOption(List commandArgs) - { - commandArgs.Add("/JitPath"); - commandArgs.Add(_jitPath); - } - - override protected void AddAssembliesPathsOption(List commandArgs) - { - commandArgs.Add("/Platform_Assemblies_Paths"); - commandArgs.Add(String.Join(" ", _platformPaths)); - } - - override protected void AddOutputPathOption(List commandArgs, string outputPath) - { - commandArgs.Add("/out"); - commandArgs.Add(outputPath); - } - - override protected void AddOptimizationOption(List commandArgs) - { - } - - override protected ProcessResult ExecuteProcess(List commandArgs, bool capture) - { - return Utility.ExecuteProcess(_executablePath, commandArgs, capture, environmentVariables: _environmentVariables); - } - } - - sealed private class Crossgen2DisasmEngine : DisasmEngine - { - public Crossgen2DisasmEngine(string executable, Config config, string outputPath, - List assemblyInfoList) : base(executable, config, outputPath, assemblyInfoList) - { - } - - override protected void AddSilentOption(List commandArgs) - { - } - - override protected void AddNoLogoOption(List commandArgs) - { - } - - override protected void AddJitPathOption(List commandArgs) - { - commandArgs.Add("--jitpath"); - commandArgs.Add(_jitPath); - } - - override protected void AddAssembliesPathsOption(List commandArgs) - { - commandArgs.Add("--reference"); - commandArgs.Add(String.Join(" ", _platformPaths.Select(str => Path.Combine(str, "*.dll")))); - } - - override protected void AddOutputPathOption(List commandArgs, string outputPath) - { - commandArgs.Add("--outputfilepath"); - commandArgs.Add(outputPath); - } - - override protected void AddOptimizationOption(List commandArgs) - { - commandArgs.Add("--optimize"); - } - - override protected ProcessResult ExecuteProcess(List commandArgs, bool capture) - { - commandArgs.Add("--parallelism 1"); - foreach (var envVar in _environmentVariables) - { - commandArgs.Add("--codegenopt"); - string dotnetPrefix = "DOTNET_"; - commandArgs.Add(String.Format("{0}={1}", envVar.Key.Substring(dotnetPrefix.Length), envVar.Value)); - } - return Utility.ExecuteProcess(_executablePath, commandArgs, capture); - } - } - } -} From 66b27ce34049a4dbf6cc781196f787fc4e18b6ff Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Wed, 9 Nov 2022 15:17:20 +0200 Subject: [PATCH 05/22] Update configs --- NuGet.Config | 1 + src/jit-diff/jit-diff.csproj | 3 +-- src/jit-format/jit-format.csproj | 3 +-- src/jit-include.props | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) 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/src/jit-diff/jit-diff.csproj b/src/jit-diff/jit-diff.csproj index f1e61d62..7c7b258e 100644 --- a/src/jit-diff/jit-diff.csproj +++ b/src/jit-diff/jit-diff.csproj @@ -1,8 +1,6 @@  - - Exe @@ -10,6 +8,7 @@ + diff --git a/src/jit-format/jit-format.csproj b/src/jit-format/jit-format.csproj index f1e61d62..7c7b258e 100644 --- a/src/jit-format/jit-format.csproj +++ b/src/jit-format/jit-format.csproj @@ -1,8 +1,6 @@  - - Exe @@ -10,6 +8,7 @@ + diff --git a/src/jit-include.props b/src/jit-include.props index ed35d9fd..509377be 100644 --- a/src/jit-include.props +++ b/src/jit-include.props @@ -1,7 +1,7 @@ - + From 32c3deb166cc28392c9c1717716caab40431fe04 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Wed, 9 Nov 2022 16:51:02 +0200 Subject: [PATCH 06/22] Fix custom parsing and defaults --- src/cijobs/CIJobsRootCommand.cs | 4 ++-- src/jit-analyze/JitAnalyzeRootCommand.cs | 25 +++++++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/cijobs/CIJobsRootCommand.cs b/src/cijobs/CIJobsRootCommand.cs index 9e7b1c48..250295ab 100644 --- a/src/cijobs/CIJobsRootCommand.cs +++ b/src/cijobs/CIJobsRootCommand.cs @@ -17,9 +17,9 @@ internal sealed class CIJobsRootCommand : RootCommand public Option JobName { get; } = new(new[] { "--job", "-j" }, "Name of the job."); public Option BranchName { get; } = - new(new[] { "--branch", "-b" }, _ => "master", true, "Name of the branch."); + new(new[] { "--branch", "-b" }, () => "master", "Name of the branch."); public Option RepoName { get; } = - new(new[] { "--repo", "-r" }, _ => "dotnet_coreclr", true, "Name of the repo (e.g. dotnet_corefx or dotnet_coreclr)."); + 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; } = diff --git a/src/jit-analyze/JitAnalyzeRootCommand.cs b/src/jit-analyze/JitAnalyzeRootCommand.cs index 7ea11222..40800208 100644 --- a/src/jit-analyze/JitAnalyzeRootCommand.cs +++ b/src/jit-analyze/JitAnalyzeRootCommand.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.CommandLine; +using System.Globalization; namespace ManagedCodeGen { @@ -17,13 +18,13 @@ internal sealed class JitAnalyzeRootCommand : RootCommand public Option Recursive { get; } = new(new[] { "--recursive", "-r" }, "Search directories recursively"); public Option FileExtension { get; } = - new("--file-extension", _ => ".dasm", true, "File extension to look for"); + new("--file-extension", () => ".dasm", "File extension to look for"); public Option Count { get; } = - new(new[] { "--count", "-c" }, _ => 20, true, "Count of files and methods (at most) to output in the summary. (count) improvements and (count) regressions of each will be included"); + 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" }, true, $"Comma-separated metric to use for diff computations. Available metrics: {MetricCollection.ListMetrics()}"); + 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; } = @@ -41,9 +42,23 @@ internal sealed class JitAnalyzeRootCommand : RootCommand 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", "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"); + new("--override-total-base-metric", result => + { + if (double.TryParse(result.Tokens[0].Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) + return val; + + result.ErrorMessage = $"Cannot parse argument '{result.Tokens[0].Value}' for option '--override-total-base-metric' as expected type 'System.Double'."; + 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", "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"); + new("--override-total-diff-metric", result => + { + if (double.TryParse(result.Tokens[0].Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) + return val; + + result.ErrorMessage = $"Cannot parse argument '{result.Tokens[0].Value}' for option '--override-total-diff-metric' as expected type 'System.Double'."; + 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; } = From 1b61f17b3282acf30010e78685a306f33a223a65 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Wed, 9 Nov 2022 16:53:33 +0200 Subject: [PATCH 07/22] Import common props in mutate-test --- src/mutate-test/mutate-test.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mutate-test/mutate-test.csproj b/src/mutate-test/mutate-test.csproj index 5533bbaa..bd600f3a 100644 --- a/src/mutate-test/mutate-test.csproj +++ b/src/mutate-test/mutate-test.csproj @@ -1,5 +1,7 @@  + + Exe net6.0 @@ -11,7 +13,6 @@ - From 2b316e9af0bfd515ca562b94d0592474d0dedce0 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Wed, 9 Nov 2022 17:06:29 +0200 Subject: [PATCH 08/22] Nitpicks --- src/jit-analyze/JitAnalyzeRootCommand.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/jit-analyze/JitAnalyzeRootCommand.cs b/src/jit-analyze/JitAnalyzeRootCommand.cs index 40800208..5fda9490 100644 --- a/src/jit-analyze/JitAnalyzeRootCommand.cs +++ b/src/jit-analyze/JitAnalyzeRootCommand.cs @@ -44,19 +44,21 @@ internal sealed class JitAnalyzeRootCommand : RootCommand public Option OverrideTotalBaseMetric { get; } = new("--override-total-base-metric", result => { - if (double.TryParse(result.Tokens[0].Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) - return val; + string optionValue = result.Tokens[0].Value; + if (double.TryParse(optionValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var parsedValue)) + return parsedValue; - result.ErrorMessage = $"Cannot parse argument '{result.Tokens[0].Value}' for option '--override-total-base-metric' as expected type 'System.Double'."; + 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 => { - if (double.TryParse(result.Tokens[0].Value, NumberStyles.Any, CultureInfo.InvariantCulture, out var val)) - return val; + string optionValue = result.Tokens[0].Value; + if (double.TryParse(optionValue, NumberStyles.Any, CultureInfo.InvariantCulture, out var parsedValue)) + return parsedValue; - result.ErrorMessage = $"Cannot parse argument '{result.Tokens[0].Value}' for option '--override-total-diff-metric' as expected type 'System.Double'."; + 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; } = From bf68548963deb2cedccf1903346a911e260bf85c Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Mon, 21 Nov 2022 22:32:55 +0200 Subject: [PATCH 09/22] Bump version: 2.0.0-beta4.22564.1 --- src/cijobs/CIJobsRootCommand.cs | 22 +++++++++++----------- src/cijobs/Program.cs | 2 +- src/jit-analyze/JitAnalyzeRootCommand.cs | 6 +++--- src/jit-analyze/Program.cs | 2 +- src/jit-dasm-pmi/JitDasmPmiRootCommand.cs | 8 ++++---- src/jit-dasm-pmi/Program.cs | 2 +- src/jit-dasm/JitDasmRootCommand.cs | 8 ++++---- src/jit-dasm/Program.cs | 2 +- src/jit-include.props | 2 +- src/mutate-test/Program.cs | 4 ++-- 10 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/cijobs/CIJobsRootCommand.cs b/src/cijobs/CIJobsRootCommand.cs index 250295ab..25a18ab7 100644 --- a/src/cijobs/CIJobsRootCommand.cs +++ b/src/cijobs/CIJobsRootCommand.cs @@ -60,9 +60,9 @@ public CIJobsRootCommand(string[] args) : base("Continuous integration build job listCommand.SetHandler(context => TryExecuteWithContextAsync(context, "list", result => { - int jobNumber = result.GetValueForOption(JobNumber); - bool showLastSuccessful = result.GetValueForOption(ShowLastSuccessful); - string commit = result.GetValueForOption(Commit); + int jobNumber = result.GetValue(JobNumber); + bool showLastSuccessful = result.GetValue(ShowLastSuccessful); + string commit = result.GetValue(Commit); if (result.FindResultFor(JobNumber) == null) { @@ -81,7 +81,7 @@ public CIJobsRootCommand(string[] args) : base("Continuous integration build job errors.Add("Must select --job to specify --commit ."); } - if (result.GetValueForOption(ShowArtifacts)) + if (result.GetValue(ShowArtifacts)) { errors.Add("Must select --job to specify --artifacts."); } @@ -93,7 +93,7 @@ public CIJobsRootCommand(string[] args) : base("Continuous integration build job errors.Add("Must have at most one of --number , --last_successful, and --commit for list."); } - if (!string.IsNullOrEmpty(result.GetValueForOption(MatchPattern))) + if (!string.IsNullOrEmpty(result.GetValue(MatchPattern))) { errors.Add("Match pattern not valid with --job"); } @@ -124,14 +124,14 @@ command copies a zip of artifacts from a repo (defaulted to copyCommand.SetHandler(context => TryExecuteWithContextAsync(context, "copy", result => { - if (result.GetValueForOption(JobName) == null) + if (result.GetValue(JobName) == null) { errors.Add("Must have --job for copy."); } - int jobNumber = result.GetValueForOption(JobNumber); - bool shwoLastSuccessful = result.GetValueForOption(ShowLastSuccessful); - string commit = result.GetValueForOption(Commit); + 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."); @@ -142,8 +142,8 @@ command copies a zip of artifacts from a repo (defaulted to errors.Add("Must have only one of --number , --last_successful, and --commit for copy."); } - string outputPath = result.GetValueForOption(OutputPath); - string outputRoot = result.GetValueForOption(OutputRoot); + 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."); diff --git a/src/cijobs/Program.cs b/src/cijobs/Program.cs index 73d899e6..f7aa89c4 100755 --- a/src/cijobs/Program.cs +++ b/src/cijobs/Program.cs @@ -69,7 +69,7 @@ public async Task RunAsync(string name) return await CopyAsync(cic); } - private T Get(Option option) => _command.Result.GetValueForOption(option); + private T Get(Option option) => _command.Result.GetValue(option); private static Task Main(string[] args) => new CommandLineBuilder(new CIJobsRootCommand(args)) diff --git a/src/jit-analyze/JitAnalyzeRootCommand.cs b/src/jit-analyze/JitAnalyzeRootCommand.cs index 5fda9490..88e73411 100644 --- a/src/jit-analyze/JitAnalyzeRootCommand.cs +++ b/src/jit-analyze/JitAnalyzeRootCommand.cs @@ -97,17 +97,17 @@ public JitAnalyzeRootCommand(string[] args) : base("Compare and analyze `*.dasm` try { List errors = new(); - if (Result.GetValueForOption(BasePath) == null) + if (Result.GetValue(BasePath) == null) { errors.Add("Base path (--base) is required"); } - if (Result.GetValueForOption(DiffPath) == null) + if (Result.GetValue(DiffPath) == null) { errors.Add("Diff path (--diff) is required"); } - foreach (string metricName in Result.GetValueForOption(Metrics)) + foreach (string metricName in Result.GetValue(Metrics)) { if (!MetricCollection.ValidateMetric(metricName)) { diff --git a/src/jit-analyze/Program.cs b/src/jit-analyze/Program.cs index 7c1717ea..36a1d364 100644 --- a/src/jit-analyze/Program.cs +++ b/src/jit-analyze/Program.cs @@ -884,7 +884,7 @@ public static Dictionary DiffInText(string diffPath, string basePat return fileToTextDiffCount; } - private T Get(Option option) => _command.Result.GetValueForOption(option); + private T Get(Option option) => _command.Result.GetValue(option); private static int Main(string[] args) => new CommandLineBuilder(new JitAnalyzeRootCommand(args)) diff --git a/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs b/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs index e6c0036a..7901d0ce 100644 --- a/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs +++ b/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs @@ -75,24 +75,24 @@ public JitDasmPmiRootCommand(string[] args) : base("Managed code gen diff tool") try { List errors = new(); - string corerun = Result.GetValueForOption(CorerunPath); + string corerun = Result.GetValue(CorerunPath); if (corerun == null || !File.Exists(corerun)) { errors.Add("Can't find --corerun tool."); } - if (Result.FindResultFor(Filename) == null && Result.GetValueForOption(AssemblyList).Count == 0) + if (Result.FindResultFor(Filename) == null && Result.GetValue(AssemblyList).Count == 0) { errors.Add("No input: Specify --file or list input assemblies."); } - string jitPath = Result.GetValueForOption(JitPath); + string jitPath = Result.GetValue(JitPath); if (jitPath != null && !File.Exists(jitPath)) { errors.Add("Can't find --jit library."); } - string filename = Result.GetValueForOption(Filename); + string filename = Result.GetValue(Filename); if (filename != null && !File.Exists(filename)) { errors.Add($"Error reading input file {filename}, file not found."); diff --git a/src/jit-dasm-pmi/Program.cs b/src/jit-dasm-pmi/Program.cs index e527d25a..2af07350 100644 --- a/src/jit-dasm-pmi/Program.cs +++ b/src/jit-dasm-pmi/Program.cs @@ -71,7 +71,7 @@ public int Run() return errorCount; } - private T Get(Option option) => _command.Result.GetValueForOption(option); + private T Get(Option option) => _command.Result.GetValue(option); private static int Main(string[] args) => new CommandLineBuilder(new JitDasmPmiRootCommand(args)) diff --git a/src/jit-dasm/JitDasmRootCommand.cs b/src/jit-dasm/JitDasmRootCommand.cs index 02e23447..4791f40c 100644 --- a/src/jit-dasm/JitDasmRootCommand.cs +++ b/src/jit-dasm/JitDasmRootCommand.cs @@ -67,7 +67,7 @@ public JitDasmRootCommand(string[] args) : base("Managed codegen diff tool (cros try { List errors = new(); - string crossgen = Result.GetValueForOption(CrossgenPath); + string crossgen = Result.GetValue(CrossgenPath); if (crossgen == null || !File.Exists(crossgen)) { errors.Add("Can't find --crossgen tool."); @@ -83,18 +83,18 @@ public JitDasmRootCommand(string[] args) : base("Managed codegen diff tool (cros errors.Add("--crossgen tool should be crossgen or crossgen2."); } - if (Result.FindResultFor(Filename) == null && Result.GetValueForOption(AssemblyList).Count == 0) + if (Result.FindResultFor(Filename) == null && Result.GetValue(AssemblyList).Count == 0) { errors.Add("No input: Specify --file or list input assemblies."); } - string jitPath = Result.GetValueForOption(JitPath); + string jitPath = Result.GetValue(JitPath); if (jitPath != null && !File.Exists(jitPath)) { errors.Add("Can't find --jit library."); } - string filename = Result.GetValueForOption(Filename); + string filename = Result.GetValue(Filename); if (filename != null && !File.Exists(filename)) { errors.Add($"Error reading input file {filename}, file not found."); diff --git a/src/jit-dasm/Program.cs b/src/jit-dasm/Program.cs index a46eba02..4be076f0 100644 --- a/src/jit-dasm/Program.cs +++ b/src/jit-dasm/Program.cs @@ -80,7 +80,7 @@ public int Run() return errorCount; } - private T Get(Option option) => _command.Result.GetValueForOption(option); + private T Get(Option option) => _command.Result.GetValue(option); private static int Main(string[] args) => new CommandLineBuilder(new JitDasmRootCommand(args)) diff --git a/src/jit-include.props b/src/jit-include.props index 509377be..d88199a9 100644 --- a/src/jit-include.props +++ b/src/jit-include.props @@ -1,7 +1,7 @@ - + diff --git a/src/mutate-test/Program.cs b/src/mutate-test/Program.cs index 94197bee..95a678ff 100644 --- a/src/mutate-test/Program.cs +++ b/src/mutate-test/Program.cs @@ -232,7 +232,7 @@ public Program(MutateTestRootCommand command) private static bool isFirstRun = true; - private T Get(Option option) => _command.Result.GetValueForOption(option); + private T Get(Option option) => _command.Result.GetValue(option); private static int Main(string[] args) => new CommandLineBuilder(new MutateTestRootCommand(args)) @@ -257,7 +257,7 @@ public int Run() MSBuildLocator.RegisterDefaults(); } - string inputFilePath = _command.Result.GetValueForArgument(_command.InputFilePath); + string inputFilePath = _command.Result.GetValue(_command.InputFilePath); if (Get(_command.Recursive)) { if (!Directory.Exists(inputFilePath)) From 95056b2ab08f977e7ed6e9f0ed836bcc98542cc7 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Fri, 25 Nov 2022 01:37:37 +0200 Subject: [PATCH 10/22] Publish tools as single-file --- build.cmd | 2 +- build.sh | 2 +- src/jit-dasm-pmi/Program.cs | 3 +-- src/pmi/pmi.cs | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/build.cmd b/build.cmd index 927a9d70..85cd01ec 100644 --- a/build.cmd +++ b/build.cmd @@ -49,7 +49,7 @@ 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 + 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 diff --git a/build.sh b/build.sh index 9932aa58..f5b4771f 100755 --- a/build.sh +++ b/build.sh @@ -53,7 +53,7 @@ declare -a projects=(jit-dasm jit-diff jit-analyze jit-format pmi jit-dasm-pmi j for proj in "${projects[@]}" do if [ "$publish" == true ]; then - dotnet publish -c $buildType -o $appInstallDir ./src/$proj + dotnet publish -c $buildType -o $appInstallDir ./src/$proj -p:PublishSingleFile=true exit_code=$? if [ $exit_code != 0 ]; then echo "${__ErrMsgPrefix}dotnet publish of ./src/${proj} failed." diff --git a/src/jit-dasm-pmi/Program.cs b/src/jit-dasm-pmi/Program.cs index 2af07350..59e66ddb 100644 --- a/src/jit-dasm-pmi/Program.cs +++ b/src/jit-dasm-pmi/Program.cs @@ -283,8 +283,7 @@ void GenerateAsmInternal(List assemblyInfoList, ref int errorCount continue; } - Assembly thisAssembly = typeof(Program).Assembly; - string binDir = Path.GetDirectoryName(thisAssembly.Location); + string binDir = Path.GetDirectoryName(System.AppContext.BaseDirectory); string command = "DRIVEALL-QUIET"; if (Get(_command.Cctors)) { diff --git a/src/pmi/pmi.cs b/src/pmi/pmi.cs index 1af064fe..8e71ba62 100644 --- a/src/pmi/pmi.cs +++ b/src/pmi/pmi.cs @@ -174,7 +174,7 @@ public CustomLoadContext(string assemblyPath) // Add in current assembly path and framework path pmiPath += Path.GetDirectoryName(assemblyPath); pmiPath += ";"; - pmiPath += Path.GetDirectoryName(typeof(object).Assembly.Location); + pmiPath += Path.GetDirectoryName(System.AppContext.BaseDirectory); PmiPath = pmiPath; } From 6e010097fcab0779423a41de763f2bcb47944df3 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Fri, 25 Nov 2022 22:02:29 +0200 Subject: [PATCH 11/22] Skip pmi projects and fix shell-check warnings --- build.cmd | 12 +++++++++--- build.sh | 28 +++++++++++++++------------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/build.cmd b/build.cmd index 85cd01ec..8bf21a9e 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,13 @@ 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 -p:PublishSingleFile=true + REM Publish *PMI* projects without single-file + set "string=%%p" + if not "!string:pmi=!"=="!string!" ( + 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 +68,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 f5b4771f..2bccebde 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 %1" + exit 1 ;; esac done @@ -52,15 +51,18 @@ 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 -p:PublishSingleFile=true + if [ "$publish" = 1 ]; then + case "$proj" in + **[Pp][Mm][Ii]**) 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 +71,4 @@ do fi done -exit $final_exit_code +exit "$final_exit_code" From 4a347baf5256ddac2afa5b123fb5716a52a9e14d Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Fri, 25 Nov 2022 22:08:38 +0200 Subject: [PATCH 12/22] Fix an error message --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 2bccebde..40932213 100755 --- a/build.sh +++ b/build.sh @@ -39,7 +39,7 @@ while getopts "hpb:" opt; do p) publish=1 ;; - *) echo "ERROR: unknown argument %1" + *) echo "ERROR: unknown argument %opt" exit 1 ;; esac From eb01579d458a6bf852c7b3ff33c1780c0db12d76 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Thu, 1 Dec 2022 06:48:41 +0200 Subject: [PATCH 13/22] Align version option in System.CommandLine usages --- src/cijobs/Program.cs | 2 +- src/jit-analyze/Program.cs | 2 +- src/jit-dasm-pmi/JitDasmPmiRootCommand.cs | 2 +- src/jit-dasm-pmi/Program.cs | 2 +- src/jit-dasm/JitDasmRootCommand.cs | 2 +- src/jit-dasm/Program.cs | 2 +- src/mutate-test/Program.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cijobs/Program.cs b/src/cijobs/Program.cs index f7aa89c4..8410bf6a 100755 --- a/src/cijobs/Program.cs +++ b/src/cijobs/Program.cs @@ -73,7 +73,7 @@ public async Task RunAsync(string name) private static Task Main(string[] args) => new CommandLineBuilder(new CIJobsRootCommand(args)) - .UseVersionOption("-v") + .UseVersionOption("--version", "-v") .UseHelp() .UseParseErrorReporting() .Build() diff --git a/src/jit-analyze/Program.cs b/src/jit-analyze/Program.cs index 36a1d364..e6b02841 100644 --- a/src/jit-analyze/Program.cs +++ b/src/jit-analyze/Program.cs @@ -888,7 +888,7 @@ public static Dictionary DiffInText(string diffPath, string basePat private static int Main(string[] args) => new CommandLineBuilder(new JitAnalyzeRootCommand(args)) - .UseVersionOption("-v") + .UseVersionOption("--version", "-v") .UseHelp() .UseParseErrorReporting() .Build() diff --git a/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs b/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs index 7901d0ce..dd301174 100644 --- a/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs +++ b/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs @@ -26,7 +26,7 @@ internal sealed class JitDasmPmiRootCommand : RootCommand public Option DumpDebugInfo { get; } = new("--debuginfo", "Add Debug info to the disasm output"); public Option Verbose { get; } = - new(new[] { "--verbose", "-v" }, "Enable verbose output"); + 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; } = diff --git a/src/jit-dasm-pmi/Program.cs b/src/jit-dasm-pmi/Program.cs index 59e66ddb..b3d6ec85 100644 --- a/src/jit-dasm-pmi/Program.cs +++ b/src/jit-dasm-pmi/Program.cs @@ -75,7 +75,7 @@ public int Run() private static int Main(string[] args) => new CommandLineBuilder(new JitDasmPmiRootCommand(args)) - .UseVersionOption("-v") + .UseVersionOption("--version", "-v") .UseHelp() .UseParseErrorReporting() .Build() diff --git a/src/jit-dasm/JitDasmRootCommand.cs b/src/jit-dasm/JitDasmRootCommand.cs index 4791f40c..f4ce8d12 100644 --- a/src/jit-dasm/JitDasmRootCommand.cs +++ b/src/jit-dasm/JitDasmRootCommand.cs @@ -26,7 +26,7 @@ internal sealed class JitDasmRootCommand : RootCommand public Option DumpDebugInfo { get; } = new("--debuginfo", "Add Debug info to the disasm output"); public Option Verbose { get; } = - new(new[] { "--verbose", "-v" }, "Enable verbose output"); + 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; } = diff --git a/src/jit-dasm/Program.cs b/src/jit-dasm/Program.cs index 4be076f0..6b655e7d 100644 --- a/src/jit-dasm/Program.cs +++ b/src/jit-dasm/Program.cs @@ -84,7 +84,7 @@ public int Run() private static int Main(string[] args) => new CommandLineBuilder(new JitDasmRootCommand(args)) - .UseVersionOption("-v") + .UseVersionOption("--version", "-v") .UseHelp() .UseParseErrorReporting() .Build() diff --git a/src/mutate-test/Program.cs b/src/mutate-test/Program.cs index 95a678ff..56bc9d82 100644 --- a/src/mutate-test/Program.cs +++ b/src/mutate-test/Program.cs @@ -236,7 +236,7 @@ public Program(MutateTestRootCommand command) private static int Main(string[] args) => new CommandLineBuilder(new MutateTestRootCommand(args)) - .UseVersionOption("-v") + .UseVersionOption("--version", "-v") .UseHelp() .UseParseErrorReporting() .Build() From 9840926d9adbf94487b0f1feaa82e3cc4452e1a3 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Sun, 11 Dec 2022 17:45:51 +0200 Subject: [PATCH 14/22] Only exclude PMI project from singlefile publishing --- build.cmd | 7 +++---- build.sh | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/build.cmd b/build.cmd index 8bf21a9e..b1c4f564 100644 --- a/build.cmd +++ b/build.cmd @@ -50,11 +50,10 @@ REM Build each project for %%p in (%projects%) do ( if %publish%==true ( REM Publish *PMI* projects without single-file - set "string=%%p" - if not "!string:pmi=!"=="!string!" ( - dotnet publish -c %buildType% -o %appInstallDir% .\src\%%p - ) else ( + if "%%p"=="pmi" ( dotnet publish -c %buildType% -o %appInstallDir% .\src\%%p -p:PublishSingleFile=true + ) else ( + dotnet publish -c %buildType% -o %appInstallDir% .\src\%%p ) if errorlevel 1 echo ERROR: dotnet publish failed for .\src\%%p.&set __ExitCode=1 ) else ( diff --git a/build.sh b/build.sh index 40932213..88e3e19f 100755 --- a/build.sh +++ b/build.sh @@ -53,8 +53,8 @@ for proj in "${projects[@]}" do if [ "$publish" = 1 ]; then case "$proj" in - **[Pp][Mm][Ii]**) dotnet publish -c "$buildType" -o "$appInstallDir" ./src/"$proj" ;; - *) dotnet publish -c "$buildType" -o "$appInstallDir" ./src/"$proj" -p:PublishSingleFile=true ;; + 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 From 592011279ab27ebb0e15e8713f213d4c274e2273 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Sun, 11 Dec 2022 17:57:31 +0200 Subject: [PATCH 15/22] Flip condition --- build.cmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.cmd b/build.cmd index b1c4f564..76ff3f51 100644 --- a/build.cmd +++ b/build.cmd @@ -51,9 +51,9 @@ for %%p in (%projects%) do ( if %publish%==true ( REM Publish *PMI* projects without single-file if "%%p"=="pmi" ( - dotnet publish -c %buildType% -o %appInstallDir% .\src\%%p -p:PublishSingleFile=true - ) else ( 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 ( From a9b0dd1b40fb6a3c29e24a71f43cac3cf14b9375 Mon Sep 17 00:00:00 2001 From: Adeel <3840695+am11@users.noreply.github.com> Date: Sun, 11 Dec 2022 18:01:42 +0200 Subject: [PATCH 16/22] Updat comments --- build.cmd | 2 +- build.sh | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build.cmd b/build.cmd index 76ff3f51..3d227b80 100644 --- a/build.cmd +++ b/build.cmd @@ -49,7 +49,7 @@ 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 ( - REM Publish *PMI* projects without single-file + 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 ( diff --git a/build.sh b/build.sh index 88e3e19f..19c953bf 100755 --- a/build.sh +++ b/build.sh @@ -53,6 +53,7 @@ for proj in "${projects[@]}" do 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 From 08eb48dabf035a9fcae5e3688f3df66e1d241634 Mon Sep 17 00:00:00 2001 From: Adeel Mujahid <3840695+am11@users.noreply.github.com> Date: Tue, 3 Jan 2023 12:17:23 +0200 Subject: [PATCH 17/22] Revert src/pmi/pmi.cs --- src/pmi/pmi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pmi/pmi.cs b/src/pmi/pmi.cs index 8e71ba62..1af064fe 100644 --- a/src/pmi/pmi.cs +++ b/src/pmi/pmi.cs @@ -174,7 +174,7 @@ public CustomLoadContext(string assemblyPath) // Add in current assembly path and framework path pmiPath += Path.GetDirectoryName(assemblyPath); pmiPath += ";"; - pmiPath += Path.GetDirectoryName(System.AppContext.BaseDirectory); + pmiPath += Path.GetDirectoryName(typeof(object).Assembly.Location); PmiPath = pmiPath; } From e4a81665ea46654f618b72dc4e6e7e9094bc244a Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 10 Jan 2023 10:16:47 +0100 Subject: [PATCH 18/22] Make AssemblyList an argument to the command to preserve existing behavior --- src/jit-dasm/JitDasmRootCommand.cs | 5 +++-- src/jit-dasm/Program.cs | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/jit-dasm/JitDasmRootCommand.cs b/src/jit-dasm/JitDasmRootCommand.cs index f4ce8d12..e5b68b25 100644 --- a/src/jit-dasm/JitDasmRootCommand.cs +++ b/src/jit-dasm/JitDasmRootCommand.cs @@ -35,7 +35,7 @@ internal sealed class JitDasmRootCommand : RootCommand new(new[] { "--platform", "-p" }, "Path to platform assemblies"); public Option> Methods { get; } = new(new[] { "--methods", "-m" }, "List of methods to disasm"); - public Option> AssemblyList { get; } = + 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"); @@ -57,9 +57,10 @@ public JitDasmRootCommand(string[] args) : base("Managed codegen diff tool (cros AddOption(Recursive); AddOption(PlatformPaths); AddOption(Methods); - AddOption(AssemblyList); AddOption(WaitForDebugger); + AddArgument(AssemblyList); + this.SetHandler(context => { Result = context.ParseResult; diff --git a/src/jit-dasm/Program.cs b/src/jit-dasm/Program.cs index 6b655e7d..926bbf15 100644 --- a/src/jit-dasm/Program.cs +++ b/src/jit-dasm/Program.cs @@ -81,6 +81,7 @@ public int Run() } 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 JitDasmRootCommand(args)) From b163b08c2d1b0760b09cdd9d8c19dc8ec745d3f6 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 10 Jan 2023 10:33:26 +0100 Subject: [PATCH 19/22] Fix some formatting --- src/jit-dasm/Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jit-dasm/Program.cs b/src/jit-dasm/Program.cs index 926bbf15..acd54a52 100644 --- a/src/jit-dasm/Program.cs +++ b/src/jit-dasm/Program.cs @@ -327,7 +327,7 @@ public int GenerateAsm(List assemblyInfoList) AddEnvironmentVariable("DOTNET_JitNoForceFallback", "1"); // Don't stress noway fallback path. AddEnvironmentVariable("DOTNET_JitRequired", "1"); // Force NO_WAY to generate assert. Also generates assert for BADCODE/BADCODE3. - if (Get(_command.DumpGCInfo )) + if (Get(_command.DumpGCInfo)) { AddEnvironmentVariable("DOTNET_JitGCDump", "*"); } @@ -453,7 +453,7 @@ private ProcessResult ExecuteProcess(List commandArgs, bool capture) { commandArgs.Add("--codegenopt"); string dotnetPrefix = "DOTNET_"; - commandArgs.Add(string.Format("{0}={1}", envVar.Key.Substring(dotnetPrefix.Length), envVar.Value)); + commandArgs.Add(string.Format("{0}={1}", envVar.Key.Substring(dotnetPrefix.Length), envVar.Value)); } return Utility.ExecuteProcess(_crossgenPath, commandArgs, capture); } From 7efb5309bbd9b60a9b2cc54080cf3d7e7ea4bfd0 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 10 Jan 2023 10:33:45 +0100 Subject: [PATCH 20/22] Fix NullReferenceException when not supplying any args to jit-dasm --- src/jit-dasm/JitDasmRootCommand.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/jit-dasm/JitDasmRootCommand.cs b/src/jit-dasm/JitDasmRootCommand.cs index e5b68b25..8e6f3051 100644 --- a/src/jit-dasm/JitDasmRootCommand.cs +++ b/src/jit-dasm/JitDasmRootCommand.cs @@ -73,15 +73,17 @@ public JitDasmRootCommand(string[] args) : base("Managed codegen diff tool (cros { errors.Add("Can't find --crossgen tool."); } - - string crossgenFilename = Path.GetFileNameWithoutExtension(crossgen).ToLower(); - if (crossgenFilename == "crossgen") - { - CodeGeneratorV1 = true; - } - else if (crossgenFilename != "crossgen2") + else { - errors.Add("--crossgen tool should be crossgen or crossgen2."); + 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) From e9443b027a0aa5a6a4782b9ef0aa8a5dccf99311 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 10 Jan 2023 10:52:27 +0100 Subject: [PATCH 21/22] Also fix jit-dasm-pmi --- src/jit-dasm-pmi/JitDasmPmiRootCommand.cs | 5 +++-- src/jit-dasm-pmi/Program.cs | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs b/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs index dd301174..d7270e36 100644 --- a/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs +++ b/src/jit-dasm-pmi/JitDasmPmiRootCommand.cs @@ -39,7 +39,7 @@ internal sealed class JitDasmPmiRootCommand : RootCommand new(new[] { "--platform", "-p" }, "Path to platform assemblies"); public Option> Methods { get; } = new(new[] { "--methods", "-m" }, "List of methods to disasm"); - public Option> AssemblyList { get; } = + 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"); @@ -64,10 +64,11 @@ public JitDasmPmiRootCommand(string[] args) : base("Managed code gen diff tool") AddOption(Recursive); AddOption(PlatformPaths); AddOption(Methods); - AddOption(AssemblyList); AddOption(WaitForDebugger); AddOption(NoCopyJit); + AddArgument(AssemblyList); + this.SetHandler(context => { Result = context.ParseResult; diff --git a/src/jit-dasm-pmi/Program.cs b/src/jit-dasm-pmi/Program.cs index b3d6ec85..511d9365 100644 --- a/src/jit-dasm-pmi/Program.cs +++ b/src/jit-dasm-pmi/Program.cs @@ -72,6 +72,7 @@ public int Run() } 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)) From a51a231f292e89d7d87a506e69958bc71257169f Mon Sep 17 00:00:00 2001 From: Adeel Mujahid <3840695+am11@users.noreply.github.com> Date: Wed, 11 Jan 2023 11:27:31 +0200 Subject: [PATCH 22/22] Fix a typo --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 19c953bf..7fb326d8 100755 --- a/build.sh +++ b/build.sh @@ -39,7 +39,7 @@ while getopts "hpb:" opt; do p) publish=1 ;; - *) echo "ERROR: unknown argument %opt" + *) echo "ERROR: unknown argument $opt" exit 1 ;; esac