diff --git a/Disasmo/Utils/DisassemblyPrettifier.cs b/Disasmo/Utils/DisassemblyPrettifier.cs index b63612b..7a9b185 100644 --- a/Disasmo/Utils/DisassemblyPrettifier.cs +++ b/Disasmo/Utils/DisassemblyPrettifier.cs @@ -24,16 +24,19 @@ public static class DisassemblyPrettifier /// G_M42249_IG03: /// C3 ret /// - /// ; Total bytes of code 76, prolog size 5 for method Program:SelectBucketIndex_old(int):int + /// ; Total bytes of code 76, prolog size 5, PerfScore 41.52, instruction count 3, bla-bla for method Program:MyMethod():int (Tier0) /// ; ============================================================ /// - public static string Prettify(string rawAsm, bool minimalComments) + public static string Prettify(string rawAsm, bool minimalComments, bool isInRunMode) { + const string MethodStartedMarker = "; Assembly listing for method "; + if (!minimalComments) return rawAsm; + try - { - var lines = rawAsm.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries); + { + var lines = rawAsm.Split(["\r\n", "\n", "\t"], StringSplitOptions.RemoveEmptyEntries); var blocks = new List(); var prevBlock = BlockType.Unknown; @@ -41,20 +44,26 @@ public static string Prettify(string rawAsm, bool minimalComments) foreach (var line in lines) { - if (line.Contains("; Assembly listing for method ")) - currentMethod = line.Remove(0, "; Assembly listing for method ".Length); - else if (currentMethod == "") - return rawAsm; // in case if format is changed + if (line.StartsWith(MethodStartedMarker)) + { + currentMethod = line.Remove(0, MethodStartedMarker.Length); + } + else if (currentMethod == "") // In case disasm's output format has changed + { + Log($"Wrong disasm's output format was detected. isInRunMode: {isInRunMode}"); + return rawAsm; + } var currentBlock = BlockType.Unknown; - if (line.StartsWith(";")) + { currentBlock = BlockType.Comments; + } else if (string.IsNullOrWhiteSpace(line)) { continue; } - else + else { currentBlock = BlockType.Code; if (Regex.IsMatch(line, @"^\w+:")) @@ -65,11 +74,19 @@ public static string Prettify(string rawAsm, bool minimalComments) if (currentBlock != prevBlock) { - blocks.Add(new Block { MethodName = currentMethod, Type = currentBlock, Data = $"\n{line}\n" }); + var block = new Block(methodName: currentMethod, type: currentBlock); + var data = block.MutableData; + data.AppendLine().Append(line).AppendLine(); + + blocks.Add(block); prevBlock = currentBlock; } else - blocks[blocks.Count - 1].Data += line + "\n"; + { + var block = blocks[blocks.Count - 1]; + var data = block.MutableData; + data.Append(line).AppendLine(); + } } var blocksByMethods = blocks.GroupBy(b => b.MethodName); @@ -77,47 +94,62 @@ public static string Prettify(string rawAsm, bool minimalComments) foreach (var method in blocksByMethods) { - List methodBlocks = method.ToList(); + var methodBlocks = (IEnumerable)method; - int size = ParseMethodTotalSizes(methodBlocks); + var size = ParseMethodTotalSizes(methodBlocks); - if (minimalComments) - { - methodBlocks = methodBlocks.Where(m => m.Type != BlockType.Comments).ToList(); - output.Append($"; Method {method.Key}"); - } + methodBlocks = methodBlocks.Where(m => m.Type != BlockType.Comments); + output.AppendLine($"; Method {method.Key}"); + + output.Append("; Total bytes of code: ") + .Append(size) + .AppendLine(); foreach (var block in methodBlocks) - output.Append(block.Data); + output.Append(block.ImmutableData); - if (minimalComments) - { - output.Append("; Total bytes of code: ") - .Append(size) - .AppendLine() - .AppendLine(); - } + output.AppendLine() + .AppendLine(); } return output.ToString(); } - catch + catch (Exception ex) when (ex is not MemberAccessException) // In case disasm's output format has changed { - return rawAsm; // format is changed - leave it as is + Log($"Exception. Disasm's output format may have changed. isInRunMode: {isInRunMode}"); } - } + catch { } - private static int ParseMethodTotalSizes(List methodBlocks) - { - const string marker = "; Total bytes of code "; - string lineToParse = methodBlocks.First(b => b.Data.Contains(marker)).Data; - int comma = lineToParse.IndexOf(','); - string size = comma == -1 ? - lineToParse.Substring(marker.Length) : - lineToParse.Substring(marker.Length, lineToParse.IndexOf(',') - marker.Length); - return int.Parse(size); + return rawAsm; + + static int ParseMethodTotalSizes(IEnumerable methodBlocks) + { + const string Marker = "; Total bytes of code "; + + var reversedMethodBlocks = methodBlocks.Reverse(); + foreach (var methodBlock in reversedMethodBlocks) + { + var lineToParse = methodBlock.ImmutableData; + var markerStartIndex = lineToParse.IndexOf(Marker); + if (markerStartIndex == -1) + continue; + + var sizePartStartIndex = markerStartIndex + Marker.Length; + var commaIndex = lineToParse.IndexOf(',', sizePartStartIndex); + + var sizePartString = commaIndex == -1 ? + lineToParse.Substring(sizePartStartIndex) : + lineToParse.Substring(sizePartStartIndex, commaIndex - sizePartStartIndex); + + return int.Parse(sizePartString); + } + + throw new Exception(); + } } + private static void Log(string message) => UserLogger.Log($"[{nameof(DisassemblyPrettifier)}] {message}"); + private enum BlockType { Unknown, @@ -127,8 +159,52 @@ private enum BlockType private class Block { - public string MethodName { get; set; } - public BlockType Type { get; set; } - public string Data { get; set; } + public Block(string methodName, BlockType type) + { + MethodName = methodName; + Type = type; + + _mutableData = new StringBuilder(64); + } + + private StringBuilder _mutableData; + private string _immutableData; + + public string MethodName { get; private set; } + public BlockType Type { get; private set; } + + public StringBuilder MutableData + { + get + { + if (_mutableData is null) + { + var message = "Undefined behavior was detected. An attempt was made to access cleared data."; + + Log($"Exception. {message}"); + throw new MemberAccessException(message); + } + + return _mutableData; + } + } + + public string ImmutableData + { + get + { + if (_immutableData is null) + { + _immutableData = _mutableData.ToString(); + + // Clear the mutable string field after accessing the immutable string field for the first time + _mutableData = null; + } + + return _immutableData; + } + } + + private static void Log(string message) => UserLogger.Log($"[{typeof(DisassemblyPrettifier)}.{typeof(Block)}] {message}"); } } \ No newline at end of file diff --git a/Disasmo/Utils/IntrinsicsSourcesService.cs b/Disasmo/Utils/IntrinsicsSourcesService.cs index 5da98ef..0584315 100644 --- a/Disasmo/Utils/IntrinsicsSourcesService.cs +++ b/Disasmo/Utils/IntrinsicsSourcesService.cs @@ -1,125 +1,206 @@ -using System; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Text; -namespace Disasmo.Utils +namespace Disasmo.Utils; + +public static class IntrinsicsSourcesService { - public static class IntrinsicsSourcesService + public static async Task> ParseIntrinsics(Action progressReporter) { - public static async Task> ParseIntrinsics(Action progressReporter) + progressReporter("Fetching data from Github..."); + + string[] files = + [ + "X86/Aes.cs", + "X86/Avx.cs", + "X86/Avx2.cs", + "X86/Bmi1.cs", + "X86/Bmi2.cs", + "X86/Fma.cs", + "X86/Lzcnt.cs", + "X86/Pclmulqdq.cs", + "X86/Popcnt.cs", + "X86/Sse.cs", + "X86/Sse2.cs", + "X86/Sse3.cs", + "X86/Sse41.cs", + "X86/Sse42.cs", + "X86/Ssse3.cs", + "X86/X86Base.cs", + "X86/X86Serialize.cs", + "X86/AvxVnni.cs", + + "X86/Avx512BW.cs", + "X86/Avx512CD.cs", + "X86/Avx512DQ.cs", + "X86/Avx512F.cs", + "X86/Avx512Vbmi.cs", + + "Arm/AdvSimd.cs", + "Arm/Aes.cs", + "Arm/ArmBase.cs", + "Arm/Crc32.cs", + "Arm/Dp.cs", + "Arm/Rdm.cs", + "Arm/Sha1.cs", + "Arm/Sha256.cs", + + "Vector64.cs", + "Vector64_1.cs", + "Vector128.cs", + "Vector128_1.cs", + "Vector256.cs", + "Vector256_1.cs", + "Vector512.cs", + "Vector512_1.cs", + ]; + + var contents = await DownloadContents(progressReporter, files); + + var progressReport = $"Compiling source code..."; + progressReporter(progressReport); + + var result = await ParseSourceFile(contents); + + return result; + } + + private static async Task DownloadContents(Action progressReporter, string[] files) + { + using var client = new HttpClient(); + var contents = new string[files.Length]; + var tasks = new Task[files.Length]; + + var taskIndex = 0; + var counter = new ProgressCounter(); + foreach (var file in files) { - List result = new List(600); - const string baseUrl = - "https://raw.githubusercontent.com/dotnet/runtime/main/src/libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/"; - string[] files = { - "X86/Aes.cs", - "X86/Avx.cs", - "X86/Avx2.cs", - "X86/Bmi1.cs", - "X86/Bmi2.cs", - "X86/Fma.cs", - "X86/Lzcnt.cs", - "X86/Pclmulqdq.cs", - "X86/Popcnt.cs", - "X86/Sse.cs", - "X86/Sse2.cs", - "X86/Sse3.cs", - "X86/Sse41.cs", - "X86/Sse42.cs", - "X86/Ssse3.cs", - "X86/X86Base.cs", - "X86/X86Serialize.cs", - "X86/AvxVnni.cs", - - "X86/Avx512BW.cs", - "X86/Avx512CD.cs", - "X86/Avx512DQ.cs", - "X86/Avx512F.cs", - "X86/Avx512Vbmi.cs", - - "Arm/AdvSimd.cs", - "Arm/Aes.cs", - "Arm/ArmBase.cs", - "Arm/Crc32.cs", - "Arm/Dp.cs", - "Arm/Rdm.cs", - "Arm/Sha1.cs", - "Arm/Sha256.cs", - - "Vector64.cs", - "Vector64_1.cs", - "Vector128.cs", - "Vector128_1.cs", - "Vector256.cs", - "Vector256_1.cs", - "Vector512.cs", - "Vector512_1.cs", - }; - foreach (var file in files) - { - progressReporter(file); - result.AddRange(await ParseSourceFile(baseUrl + file)); - } - return result; + tasks[taskIndex] = DownloadContent( + progressReporter, + client, + file, + contents, + taskIndex++, + counter, + files.Length); } - public static async Task> ParseSourceFile(string url) + await Task.WhenAll(tasks); + + return contents; + + static async Task DownloadContent( + Action progressReporter, + HttpClient client, + string file, + string[] contents, + int contentIndex, + ProgressCounter counter, + int totalFiles) { - var client = new HttpClient(); - string content = await client.GetStringAsync(url); - var result = new List(); - using (var workspace = new AdhocWorkspace()) - { - Project proj = workspace.AddProject("ParseIntrinsics", LanguageNames.CSharp) - .WithMetadataReferences(new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location) }); - Document doc = proj.AddDocument("foo", SourceText.From(content)); - Compilation compilation = await doc.Project.GetCompilationAsync(); - SyntaxNode root = await doc.GetSyntaxRootAsync(); - var model = compilation.GetSemanticModel(root.SyntaxTree); - var methods = root.DescendantNodes().OfType().ToList(); - - foreach (var method in methods) - { - var tokens = method.ChildTokens().ToArray(); - if (tokens.Length > 0) - { - var trivia = tokens.FirstOrDefault().LeadingTrivia; - string comments = string.Join("\n", - trivia.ToString().Split('\n').Select(i => i.Trim(' ', '\r', '\t')) - .Where(i => !string.IsNullOrWhiteSpace(i))); - var symbol = model.GetDeclaredSymbol(method); - var methodName = symbol.ToString() - .Replace("System.Runtime.Intrinsics.X86.", "") - .Replace("System.Runtime.Intrinsics.Arm.", "") - .Replace("System.Runtime.Intrinsics.", ""); - - var returnType = method.ReturnType.ToString(); - result.Add(new IntrinsicsInfo { Method = returnType + " " + methodName, Comments = comments }); - } - } - } + const string RuntimeUrl = "https://raw.githubusercontent.com/dotnet/runtime/main/src/"; + const string IntrinsicsBaseUrl = RuntimeUrl + "libraries/System.Private.CoreLib/src/System/Runtime/Intrinsics/"; + + var fileUrl = IntrinsicsBaseUrl + file; + contents[contentIndex] = await client.GetStringAsync(fileUrl); - return result; + var progressReport = $"Fetching data from Github...\nFetched {++counter.Count} of {totalFiles} files."; + progressReporter(progressReport); } } - - public class IntrinsicsInfo + private static async Task> ParseSourceFile(string[] contents) { - public string Comments { get; set; } - public string Method { get; set; } + var intrinsicsInfos = new List(8192); + using var workspace = new AdhocWorkspace(); + + var project = + workspace + .AddProject("ParseIntrinsicsProject", LanguageNames.CSharp) + .WithMetadataReferences([MetadataReference.CreateFromFile(typeof(object).Assembly.Location)]); + + foreach (var content in contents) + { + var document = project.AddDocument(name: Guid.NewGuid().ToString(), content); + project = document.Project; + } - public bool Contains(string str) + var compilation = await project.GetCompilationAsync(); + foreach (var document in project.Documents) { - return Comments.ToLowerInvariant().Contains(str.ToLowerInvariant()) || - Method.ToLowerInvariant().Contains(str.ToLowerInvariant()); + var documentSyntaxRoot = await document.GetSyntaxRootAsync(); + var model = compilation.GetSemanticModel(documentSyntaxRoot.SyntaxTree); + var methods = documentSyntaxRoot.DescendantNodes().OfType(); + + foreach (var method in methods) + { + var tokens = method.ChildTokens(); + if (!tokens.Any()) + continue; + + var rootToken = tokens.FirstOrDefault(); + + var triviaToken = rootToken.LeadingTrivia; + var commentsParts = + triviaToken.ToString() + .Split('\n') + .Select(i => i.Trim(' ', '\r', '\t')) + .Where(i => !string.IsNullOrWhiteSpace(i)); + + var comments = string.Join("\n", commentsParts); + var symbol = model.GetDeclaredSymbol(method); + var methodName = + symbol.ToString() + .Replace("System.Runtime.Intrinsics.X86.", "") + .Replace("System.Runtime.Intrinsics.Arm.", "") + .Replace("System.Runtime.Intrinsics.", ""); + + var returnType = method.ReturnType.ToString(); + + var intrinsicsInfo = new IntrinsicsInfo(method: returnType + " " + methodName, comments); + intrinsicsInfos.Add(intrinsicsInfo); + } } - public override string ToString() => Method; + return intrinsicsInfos; + } + + private class ProgressCounter + { + private int count; + + public int Count + { + get => count; + set => Interlocked.Exchange(ref count, value); + } } } + +public class IntrinsicsInfo +{ + public IntrinsicsInfo(string method, string comments) + { + _method = method; + _comments = comments; + + _cachedDataToCompare = method.ToLower() + '.' + comments.ToLower(); + } + + private readonly string _method; + private readonly string _comments; + private readonly string _cachedDataToCompare; + + public string Method { get => _method; set => throw new NotImplementedException(); } + public string Comments { get => _comments; set => throw new NotImplementedException(); } + + public bool Contains(string content) => _cachedDataToCompare.Contains(content.ToLowerInvariant()); + + public override string ToString() => Method; +} \ No newline at end of file diff --git a/Disasmo/Utils/LoaderAppManager.cs b/Disasmo/Utils/LoaderAppManager.cs index 99ea4e4..f2dfe22 100644 --- a/Disasmo/Utils/LoaderAppManager.cs +++ b/Disasmo/Utils/LoaderAppManager.cs @@ -12,82 +12,90 @@ public static class LoaderAppManager { public static readonly string DisasmoLoaderName = "DisasmoLoader4"; - private static async Task GetPathToLoader(string tf, Version addinVersion, CancellationToken ct) + private static async Task GetPathToLoader( + string targetFramework, + Version disasmoVersion, + CancellationToken cancellationToken) { - ProcessResult dotnetVersion = await ProcessUtils.RunProcess("dotnet", "--version", cancellationToken: ct); + var dotnetVersion = await ProcessUtils.RunProcess("dotnet", "--version", cancellationToken: cancellationToken); UserLogger.Log($"dotnet --version: {dotnetVersion.Output} ({dotnetVersion.Error})"); - string version = dotnetVersion.Output.Trim(); + var version = dotnetVersion.Output.Trim(); if (!char.IsDigit(version[0])) { // Something went wrong, use a random to proceed version = Guid.NewGuid().ToString("N"); } - string folderName = $"{addinVersion}_{tf}_{version}"; + var folderName = $"{disasmoVersion}_{targetFramework}_{version}"; UserLogger.Log($"LoaderAppManager.GetPathToLoader: {folderName}"); return Path.Combine(Path.GetTempPath(), DisasmoLoaderName, folderName); } - public static async Task InitLoaderAndCopyTo(string tf, string dest, Action logger, Version addinVersion, CancellationToken ct) + public static async Task InitLoaderAndCopyTo( + string targetFramework, + string destination, + Action logger, + Version disasmoVersion, + CancellationToken cancellationToken) { - if (!Directory.Exists(dest)) - throw new InvalidOperationException($"ERROR: dest dir was not found: {dest}"); + if (!Directory.Exists(destination)) + throw new InvalidOperationException($"ERROR: destination directory was not found: {destination}"); - string dir; + string directory; try { logger("Getting SDK version..."); - dir = await GetPathToLoader(tf, addinVersion, ct); + directory = await GetPathToLoader(targetFramework, disasmoVersion, cancellationToken); } - catch (Exception exc) + catch (Exception ex) { - throw new InvalidOperationException("ERROR in LoaderAppManager.GetPathToLoader: " + exc); + throw new InvalidOperationException("ERROR in LoaderAppManager.GetPathToLoader: " + ex); } - string csproj = Path.Combine(dir, $"{DisasmoLoaderName}.csproj"); - string csfile = Path.Combine(dir, $"{DisasmoLoaderName}.cs"); - string outDll = Path.Combine(dir, "out", $"{DisasmoLoaderName}.dll"); - string outJson = Path.Combine(dir, "out", $"{DisasmoLoaderName}.runtimeconfig.json"); - string outDllDest = Path.Combine(dest, DisasmoLoaderName + ".dll"); - string outJsonDest = Path.Combine(dest, DisasmoLoaderName + ".runtimeconfig.json"); + var csproj = Path.Combine(directory, $"{DisasmoLoaderName}.csproj"); + var csfile = Path.Combine(directory, $"{DisasmoLoaderName}.cs"); + var outDll = Path.Combine(directory, "out", $"{DisasmoLoaderName}.dll"); + var outJson = Path.Combine(directory, "out", $"{DisasmoLoaderName}.runtimeconfig.json"); + var outDllDestination = Path.Combine(destination, DisasmoLoaderName + ".dll"); + var outJsonDestination = Path.Combine(destination, DisasmoLoaderName + ".runtimeconfig.json"); - if (File.Exists(outDllDest) && File.Exists(outJsonDest)) - { + if (File.Exists(outDllDestination) && File.Exists(outJsonDestination)) return; - } - if (!Directory.Exists(dir)) + if (!Directory.Exists(directory)) { - Directory.CreateDirectory(dir); + Directory.CreateDirectory(directory); } else if (File.Exists(outDll) && File.Exists(outJson)) { - File.Copy(outDll, outDllDest, true); - File.Copy(outJson, outJsonDest, true); + File.Copy(outDll, outDllDestination, overwrite: true); + File.Copy(outJson, outJsonDestination, overwrite: true); return; } logger($"Building '{DisasmoLoaderName}' project..."); - ct.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); if (!File.Exists(csfile)) - TextUtils.SaveEmbeddedResourceTo($"{DisasmoLoaderName}.cs_template", dir); + TextUtils.SaveEmbeddedResourceTo($"{DisasmoLoaderName}.cs_template", directory); if (!File.Exists(csproj)) - TextUtils.SaveEmbeddedResourceTo($"{DisasmoLoaderName}.csproj_template", dir, content => content.Replace("%tfm%", tf)); + TextUtils.SaveEmbeddedResourceTo($"{DisasmoLoaderName}.csproj_template", directory, content => content.Replace("%tfm%", targetFramework)); Debug.Assert(File.Exists(csfile)); Debug.Assert(File.Exists(csproj)); - ct.ThrowIfCancellationRequested(); + cancellationToken.ThrowIfCancellationRequested(); - var msg = await ProcessUtils.RunProcess("dotnet", "build -c Release", workingDir: dir, cancellationToken: ct); + var message = await ProcessUtils.RunProcess("dotnet", "build -c Release", workingDirectory: directory, cancellationToken: cancellationToken); if (!File.Exists(outDll) || !File.Exists(outJson)) - throw new InvalidOperationException($"ERROR: 'dotnet build' did not produce expected binaries ('{outDll}'" + - $" and '{outJson}'):\n{msg.Output}\n\n{msg.Error}"); + { + var errorMessage = $"ERROR: 'dotnet build' did not produce expected binaries ('{outDll}' and '{outJson}'):\n{message.Output}\n\n{message.Error}"; + throw new InvalidOperationException(errorMessage); + } - ct.ThrowIfCancellationRequested(); - File.Copy(outDll, outDllDest, true); - File.Copy(outJson, outJsonDest, true); + cancellationToken.ThrowIfCancellationRequested(); + File.Copy(outDll, outDllDestination, overwrite: true); + File.Copy(outJson, outJsonDestination, overwrite: true); } } \ No newline at end of file diff --git a/Disasmo/Utils/ProcessUtils.cs b/Disasmo/Utils/ProcessUtils.cs index e7d8708..323b669 100644 --- a/Disasmo/Utils/ProcessUtils.cs +++ b/Disasmo/Utils/ProcessUtils.cs @@ -10,14 +10,14 @@ namespace Disasmo; public static class ProcessUtils { public static async Task RunProcess( - string path, + string path, string args = "", Dictionary envVars = null, - string workingDir = null, + string workingDirectory = null, Action outputLogger = null, CancellationToken cancellationToken = default) { - UserLogger.Log($"\nExecuting a command in directory \"{workingDir}\":\n\t{path} {args}\nEnv.vars:\n{DumpEnvVars(envVars)}"); + UserLogger.Log($"\nExecuting a command in directory \"{workingDirectory}\":\n\t{path} {args}\nEnv.vars:\n{DumpEnvVars(envVars)}"); var logger = new StringBuilder(); var loggerForErrors = new StringBuilder(); @@ -34,10 +34,10 @@ public static async Task RunProcess( Arguments = args, }; - if (workingDir != null) - processStartInfo.WorkingDirectory = workingDir; + if (workingDirectory is not null) + processStartInfo.WorkingDirectory = workingDirectory; - if (envVars != null) + if (envVars is not null) { foreach (var envVar in envVars) processStartInfo.EnvironmentVariables[envVar.Key] = envVar.Value; @@ -53,11 +53,13 @@ public static async Task RunProcess( logger.AppendLine(e.Data); loggerForErrors.AppendLine(e.Data); }; + process.OutputDataReceived += (sender, e) => { outputLogger?.Invoke(false, e.Data + "\n"); logger.AppendLine(e.Data); }; + process.BeginOutputReadLine(); process.BeginErrorReadLine(); @@ -67,9 +69,12 @@ public static async Task RunProcess( return new ProcessResult { Error = loggerForErrors.ToString().Trim('\r', '\n'), Output = logger.ToString().Trim('\r', '\n') }; } - catch (Exception e) + catch (Exception ex) { - return new ProcessResult { Error = $"RunProcess failed:{e.Message}.\npath={path}\nargs={args}\nworkingdir={workingDir ?? Environment.CurrentDirectory}\n{loggerForErrors}" }; + workingDirectory ??= Environment.CurrentDirectory; + var errorMessage = $"RunProcess failed:{ex.Message}.\npath={path}\nargs={args}\nworkingdir={workingDirectory}\n{loggerForErrors}"; + + return new ProcessResult { Error = errorMessage }; } finally { @@ -78,42 +83,46 @@ public static async Task RunProcess( } } - public static Task WaitForExitAsync(this Process process, - CancellationToken cancellationToken = default(CancellationToken)) + public static Task WaitForExitAsync(this Process process, CancellationToken cancellationToken = default) { if (process.HasExited) return Task.CompletedTask; - var tcs = new TaskCompletionSource(); + + var completionSource = new TaskCompletionSource(); process.EnableRaisingEvents = true; - process.Exited += (sender, args) => tcs.TrySetResult(null); - if (cancellationToken != default(CancellationToken)) - cancellationToken.Register(() => tcs.TrySetCanceled()); - return process.HasExited ? Task.CompletedTask : tcs.Task; + process.Exited += (sender, args) => completionSource.TrySetResult(null); + + if (cancellationToken != default) + cancellationToken.Register(() => completionSource.TrySetCanceled()); + + return process.HasExited ? Task.CompletedTask : completionSource.Task; } private static void KillProccessSafe(this Process process) { - if (process == null) + if (process is null) return; + try { if (!process.HasExited) process.Kill(); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } } private static string DumpEnvVars(Dictionary envVars) { - if (envVars == null) + if (envVars is null) return ""; - string envVar = ""; + var envVar = ""; foreach (var ev in envVars) envVar += ev.Key + "=" + ev.Value + "\n"; + return envVar; } } diff --git a/Disasmo/Utils/SymbolUtils.cs b/Disasmo/Utils/SymbolUtils.cs index be9f2f2..b823d81 100644 --- a/Disasmo/Utils/SymbolUtils.cs +++ b/Disasmo/Utils/SymbolUtils.cs @@ -6,24 +6,25 @@ public static class SymbolUtils { public static DisasmoSymbolInfo FromSymbol(ISymbol symbol) { - string target; string hostType; + string target; string methodName; - string prefix = ""; - ISymbol containingType = symbol as ITypeSymbol ?? symbol.ContainingType; + var prefix = ""; + var containingType = symbol as ITypeSymbol ?? symbol.ContainingType; - // match all for nested types - if (containingType.ContainingType is { }) + // Match all for nested types + if (containingType.ContainingType is not null) + { prefix = "*"; - + } else { - INamespaceSymbol ns = containingType.ContainingNamespace; - while (ns?.Name is { Length: > 0 } containingNamespace) + var @namespace = containingType.ContainingNamespace; + while (@namespace?.Name is { Length: > 0 } containingNamespace) { prefix = containingNamespace + "." + prefix; - ns = ns.ContainingNamespace; + @namespace = @namespace.ContainingNamespace; } } @@ -32,41 +33,40 @@ public static DisasmoSymbolInfo FromSymbol(ISymbol symbol) if (containingType is INamedTypeSymbol { IsGenericType: true }) prefix += "*"; - if (symbol is IMethodSymbol ms) + if (symbol is IMethodSymbol methodSymbol) { - if (ms.MethodKind == MethodKind.LocalFunction) + hostType = symbol.ContainingType.ToString(); + if (methodSymbol.MethodKind == MethodKind.LocalFunction) { - // hack for mangled names + // Hack for mangled names target = prefix + ":*" + symbol.MetadataName + "*"; - hostType = symbol.ContainingType.ToString(); methodName = "*"; } - else if (ms.MethodKind == MethodKind.Constructor) + else if (methodSymbol.MethodKind == MethodKind.Constructor) { target = prefix + ":.ctor"; - hostType = symbol.ContainingType.ToString(); methodName = "*"; } else { target = prefix + ":" + symbol.MetadataName; - hostType = symbol.ContainingType.ToString(); methodName = symbol.MetadataName; } } else if (symbol is IPropertySymbol) { - target = prefix + ":get_" + symbol.MetadataName + " " + prefix + ":set_" + symbol.MetadataName; hostType = symbol.ContainingType.ToString(); + target = prefix + ":get_" + symbol.MetadataName + " " + prefix + ":set_" + symbol.MetadataName; methodName = symbol.MetadataName; } else { - // the whole class - target = prefix + ":*"; + // The whole class hostType = symbol.ToString(); + target = prefix + ":*"; methodName = "*"; } + return new DisasmoSymbolInfo(target, hostType, methodName); } } diff --git a/Disasmo/Utils/TextUtils.cs b/Disasmo/Utils/TextUtils.cs index e9221d0..99e4fc2 100644 --- a/Disasmo/Utils/TextUtils.cs +++ b/Disasmo/Utils/TextUtils.cs @@ -5,22 +5,23 @@ namespace Disasmo; public static class TextUtils { - public static void SaveEmbeddedResourceTo(string resource, string folder, + public static void SaveEmbeddedResourceTo( + string resource, + string folder, Func contentProcessor = null) { - string filePath = Path.Combine(folder, resource.Replace("_template", "")); + var filePath = Path.Combine(folder, resource.Replace("_template", "")); if (File.Exists(filePath)) return; - using Stream stream = typeof(TextUtils).Assembly - .GetManifestResourceStream("Disasmo.Resources." + resource); - using StreamReader reader = new StreamReader(stream); + using var stream = typeof(TextUtils).Assembly.GetManifestResourceStream("Disasmo.Resources." + resource); + using var reader = new StreamReader(stream); var content = reader.ReadToEnd(); - File.WriteAllText(filePath, contentProcessor is { } ? contentProcessor(content) : content); + File.WriteAllText(filePath, contentProcessor != null ? contentProcessor(content) : content); } public static string NormalizeLineEndings(this string text) => - // normalize endings (DiffTool constantly complains) + // Normalize endings (DiffTool constantly complains) text.Replace(Environment.NewLine, "\n") .Replace("\n", Environment.NewLine) + Environment.NewLine; } \ No newline at end of file diff --git a/images/screenshot.png b/images/screenshot.png index 1a5728b..eee3ea4 100644 Binary files a/images/screenshot.png and b/images/screenshot.png differ diff --git a/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs b/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs index f30057a..766b47c 100644 --- a/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs +++ b/src/Vsix/Analyzers/Base/BaseSuggestedAction.cs @@ -13,7 +13,7 @@ namespace Disasmo; -internal abstract class BaseSuggestedAction : ISuggestedAction +public abstract class BaseSuggestedAction : ISuggestedAction { protected readonly CommonSuggestedActionsSource _actionsSource; @@ -28,12 +28,12 @@ public async Task ValidateAsync(CancellationToken cancellationToken) try { LastDocument = null; - LastTokenPos = 0; + LastTokenPosition = 0; var document = SnapshotSpan.Snapshot.TextBuffer.GetRelatedDocuments().FirstOrDefault(); - if (document != null && await IsValidSymbol(document, CaretPosition, cancellationToken)) + if (document is not null && await IsValidSymbolAsync(document, CaretPosition, cancellationToken)) { LastDocument = document; - LastTokenPos = CaretPosition; + LastTokenPosition = CaretPosition; return true; } @@ -45,19 +45,33 @@ public async Task ValidateAsync(CancellationToken cancellationToken) } } - public int LastTokenPos { get; set; } + public int LastTokenPosition { get; set; } + public Document LastDocument { get; set; } - protected abstract Task IsValidSymbol(Document document, int tokenPosition, CancellationToken cancellationToken); + public abstract string DisplayText { get; } + public string IconAutomationText => "Disamo"; - ImageMoniker ISuggestedAction.IconMoniker => KnownMonikers.CSLightswitch; + public string InputGestureText => null; + public bool HasActionSets => false; - public Task> GetActionSetsAsync(CancellationToken cancellationToken) => null; + public bool HasPreview => false; + + ImageMoniker ISuggestedAction.IconMoniker => KnownMonikers.CSLightswitch; + + protected abstract Task IsValidSymbolAsync(Document document, int tokenPosition, CancellationToken cancellationToken); + + public Task> GetActionSetsAsync(CancellationToken cancellationToken) => + Task.FromResult((IEnumerable)null); + public Task GetPreviewAsync(CancellationToken cancellationToken) => Task.FromResult(null); + public void Dispose() { } + public abstract void Invoke(CancellationToken cancellationToken); + public bool TryGetTelemetryId(out Guid telemetryId) { telemetryId = Guid.Empty; diff --git a/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs b/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs index 004e367..0896849 100644 --- a/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs +++ b/src/Vsix/Analyzers/Base/CommonSuggestedActionsSource.cs @@ -11,7 +11,7 @@ namespace Disasmo; -internal class CommonSuggestedActionsSource : ISuggestedActionsSource +public class CommonSuggestedActionsSource : ISuggestedActionsSource { private BaseSuggestedAction[] _baseActions; @@ -27,10 +27,7 @@ public CommonSuggestedActionsSource(CommonSuggestedActionsSourceProvider sourceP SourceProvider = sourceProvider; TextView = textView; TextBuffer = textBuffer; - _baseActions = new BaseSuggestedAction[] - { - new DisasmMethodOrClassAction(this), - }; + _baseActions = [new DisasmMethodOrClassAction(this)]; } public event EventHandler SuggestedActionsChanged; @@ -38,18 +35,19 @@ public CommonSuggestedActionsSource(CommonSuggestedActionsSourceProvider sourceP public void Dispose() { } public IEnumerable GetSuggestedActions( - ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, CancellationToken cancellationToken) { try { return _baseActions - .Where(a => a.LastDocument != null) - .Select(a => + .Where(action => action.LastDocument is not null) + .Select(action => { - a.SnapshotSpan = range; - a.CaretPosition = GetCaretPosition(); - return new SuggestedActionSet(PredefinedSuggestedActionCategoryNames.Any, new[] { a }); + action.SnapshotSpan = range; + action.CaretPosition = GetCaretPosition(); + return new SuggestedActionSet(PredefinedSuggestedActionCategoryNames.Any, [action]); }).ToArray(); } catch @@ -58,19 +56,20 @@ public IEnumerable GetSuggestedActions( } } - public async Task HasSuggestedActionsAsync(ISuggestedActionCategorySet requestedActionCategories, - SnapshotSpan range, CancellationToken cancellationToken) + public async Task HasSuggestedActionsAsync( + ISuggestedActionCategorySet requestedActionCategories, + SnapshotSpan range, + CancellationToken cancellationToken) { try { - foreach (var t in _baseActions) + foreach (var action in _baseActions) { - t.SnapshotSpan = range; - t.CaretPosition = GetCaretPosition(); - if (await t.ValidateAsync(default)) - { + action.SnapshotSpan = range; + action.CaretPosition = GetCaretPosition(); + + if (await action.ValidateAsync(default)) return true; - } } } catch (Exception ex) diff --git a/src/Vsix/Analyzers/Base/CommonSuggestedActionsSourceProvider.cs b/src/Vsix/Analyzers/Base/CommonSuggestedActionsSourceProvider.cs index 5c6dd0b..e501032 100644 --- a/src/Vsix/Analyzers/Base/CommonSuggestedActionsSourceProvider.cs +++ b/src/Vsix/Analyzers/Base/CommonSuggestedActionsSourceProvider.cs @@ -9,11 +9,11 @@ namespace Disasmo.Analyzers; [Export(typeof(ISuggestedActionsSourceProvider))] [Name("Disasmo Suggested Actions")] [ContentType("text")] -internal class CommonSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider +public class CommonSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider { public ISuggestedActionsSource CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) { - if (textBuffer == null && textView == null) + if (textBuffer is null && textView is null) return null; return new CommonSuggestedActionsSource(this, textView, textBuffer); diff --git a/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs b/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs index b890a62..1875a8a 100644 --- a/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs +++ b/src/Vsix/Analyzers/DisasmMethodOrClassAction.cs @@ -9,7 +9,7 @@ namespace Disasmo; -internal class DisasmMethodOrClassAction : BaseSuggestedAction +public class DisasmMethodOrClassAction : BaseSuggestedAction { public DisasmMethodOrClassAction(CommonSuggestedActionsSource actionsSource) : base(actionsSource) {} @@ -17,127 +17,119 @@ public override async void Invoke(CancellationToken cancellationToken) { try { - if (LastDocument != null) + if (LastDocument is null) + return; + + var window = await IdeUtils.ShowWindowAsync(true, cancellationToken); + if (window?.ViewModel is {} viewModel) { - var window = await IdeUtils.ShowWindowAsync(true, cancellationToken); - if (window?.ViewModel is {} viewModel) - { - viewModel.RunOperationAsync(await GetSymbol(LastDocument, LastTokenPos, cancellationToken), LastDocument.Project); - } + var symbol = await GetSymbolAsync(LastDocument, LastTokenPosition, cancellationToken); + var project = LastDocument.Project; + viewModel.RunOperationAsync(symbol, project); } } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } } - protected override async Task IsValidSymbol(Document document, int tokenPosition, CancellationToken cancellationToken) + protected override async Task IsValidSymbolAsync(Document document, int tokenPosition, CancellationToken cancellationToken) { try { if (Settings.Default.DisableLightBulb) return false; - SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken); - if (semanticModel == null) + var semanticModel = await document.GetSemanticModelAsync(cancellationToken); + if (semanticModel is null) return false; var syntaxTree = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken); var token = syntaxTree.FindToken(tokenPosition); - if (token.Parent is MethodDeclarationSyntax) - return true; - if (token.Parent is ClassDeclarationSyntax) - return true; - if (token.Parent is StructDeclarationSyntax) - return true; - if (token.Parent is LocalFunctionStatementSyntax) - return true; - if (token.Parent is ConstructorDeclarationSyntax) - return true; - if (token.Parent is PropertyDeclarationSyntax) - return true; - if (token.Parent is OperatorDeclarationSyntax) - return true; + var isValidSymbol = token.Parent + is MethodDeclarationSyntax + or ClassDeclarationSyntax + or StructDeclarationSyntax + or LocalFunctionStatementSyntax + or ConstructorDeclarationSyntax + or PropertyDeclarationSyntax + or OperatorDeclarationSyntax; } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } return false; } - static ISymbol FindRelatedSymbol(SemanticModel semanticModel, SyntaxNode node, bool allowClassesAndStructs, CancellationToken ct) + static ISymbol FindRelatedSymbol( + SemanticModel semanticModel, + SyntaxNode node, + bool allowClassesAndStructs, + CancellationToken cancellationToken) { - if (node is LocalFunctionStatementSyntax lf) - return semanticModel.GetDeclaredSymbol(lf, ct); - - if (node is MethodDeclarationSyntax m) - return semanticModel.GetDeclaredSymbol(m, ct); - - if (node is PropertyDeclarationSyntax p) - return semanticModel.GetDeclaredSymbol(p, ct); - - if (node is OperatorDeclarationSyntax o) - return semanticModel.GetDeclaredSymbol(o, ct); - - if (node is ConstructorDeclarationSyntax ctor) - return semanticModel.GetDeclaredSymbol(ctor, ct); - - if (!allowClassesAndStructs) - return null; - - if (node is ClassDeclarationSyntax c) - return semanticModel.GetDeclaredSymbol(c, ct); - - if (node is StructDeclarationSyntax s) - return semanticModel.GetDeclaredSymbol(s, ct); + if ((node + is LocalFunctionStatementSyntax + or MethodDeclarationSyntax + or PropertyDeclarationSyntax + or OperatorDeclarationSyntax + or ConstructorDeclarationSyntax) + || (allowClassesAndStructs && node + is ClassDeclarationSyntax + or StructDeclarationSyntax)) + { + return semanticModel.GetDeclaredSymbol(node, cancellationToken); + } return null; } - public static async Task GetSymbolStatic(Document doc, int tok, CancellationToken ct, bool recursive = false) + public static async Task GetSymbolStaticAsync( + Document document, + int tokenPosition, + CancellationToken cancellationToken, + bool recursive = false) { try { - SemanticModel semanticModel = await doc.GetSemanticModelAsync(ct); - if (semanticModel == null) + var semanticModel = await document.GetSemanticModelAsync(cancellationToken); + if (semanticModel is null) return null; - SyntaxNode syntaxTree = await semanticModel.SyntaxTree.GetRootAsync(ct); - SyntaxToken token = syntaxTree.FindToken(tok); - SyntaxNode parent = token.Parent; - if (parent == null) + var syntaxTree = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken); + var token = syntaxTree.FindToken(tokenPosition); + var parent = token.Parent; + if (parent is null) return null; - var symbol = FindRelatedSymbol(semanticModel, parent, true, ct); - if (symbol == null && recursive) + var symbol = FindRelatedSymbol(semanticModel, parent, true, cancellationToken); + if (symbol is null && recursive) { while (true) { parent = parent?.Parent; - if (parent == null) + if (parent is null) return null; - symbol = FindRelatedSymbol(semanticModel, parent, false, ct); - if (symbol != null) - { + symbol = FindRelatedSymbol(semanticModel, parent, false, cancellationToken); + if (symbol is not null) return symbol; - } } } + return symbol; } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); return null; } } - protected virtual Task GetSymbol(Document doc, int pos, CancellationToken ct) => - GetSymbolStatic(doc, pos, ct); + protected virtual Task GetSymbolAsync(Document document, int tokenPosition, CancellationToken cancellationToken) => + GetSymbolStaticAsync(document, tokenPosition, cancellationToken); public override string DisplayText { @@ -145,6 +137,7 @@ public override string DisplayText { if (string.IsNullOrWhiteSpace(DisasmoPackage.HotKey)) return "Disasm this"; + return $"Disasm this ({DisasmoPackage.HotKey})"; } } diff --git a/src/Vsix/Disasmo.Vsix.csproj b/src/Vsix/Disasmo.Vsix.csproj index 9cf00bd..9bb15f2 100644 --- a/src/Vsix/Disasmo.Vsix.csproj +++ b/src/Vsix/Disasmo.Vsix.csproj @@ -53,6 +53,7 @@ + diff --git a/src/Vsix/DisasmoPackage.cs b/src/Vsix/DisasmoPackage.cs index 1fd1c23..4930f96 100644 --- a/src/Vsix/DisasmoPackage.cs +++ b/src/Vsix/DisasmoPackage.cs @@ -1,21 +1,22 @@ -using System; -using System.ComponentModel.Composition; -using System.Diagnostics; -using System.Linq; -using System.Net.Http; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; -using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Commanding; using Microsoft.VisualStudio.Editor.Commanding; +using Microsoft.VisualStudio.ExtensionManager; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.Commanding; using Microsoft.VisualStudio.Utilities; +using System; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Linq; +using System.Net.Http; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; namespace Disasmo; @@ -28,36 +29,38 @@ namespace Disasmo; public sealed class DisasmoPackage : AsyncPackage { public const string PackageGuidString = "6d23b8d8-92f1-4f92-947a-b9021f6ab3dc"; + public const string PackageProductIdString = "Disasmo.39513ef5-c3ee-4547-b7be-f29c752b591d"; protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { try { Current = this; - await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); + + var disasmoCommand = IdeUtils.DTE().Commands.Item("Tools.Disasmo", 0); + if (disasmoCommand is null) + return; - var disasmoCmd = IdeUtils.DTE().Commands.Item("Tools.Disasmo", 0); - if (disasmoCmd != null) + var binding = ""; + if (disasmoCommand.Bindings is object[] bindingArray) { - string binding = ""; - if (disasmoCmd.Bindings is object[] bindingArray) + var hotkeys = bindingArray.Select(b => b.ToString()).ToArray(); + // Prefer Text Editor over Global + var bindingPair = hotkeys.FirstOrDefault(h => h.StartsWith("Text Editor::")) ?? hotkeys.FirstOrDefault(); + if (bindingPair is not null && bindingPair.Contains("::")) { - var hotkeys = bindingArray.Select(b => b.ToString()).ToArray(); - // prefer Text Editor over Global - var bindingPair = hotkeys.FirstOrDefault(h => h.StartsWith("Text Editor::")) ?? hotkeys.FirstOrDefault(); - if (bindingPair != null && bindingPair.Contains("::")) - binding = bindingPair.Substring(bindingPair.IndexOf("::", StringComparison.Ordinal) + 2); + binding = bindingPair.Substring(bindingPair.IndexOf("::", StringComparison.Ordinal) + 2); } - else + } + else + { + if (disasmoCommand.Bindings is string bindingString && bindingString.Contains("::")) { - if (disasmoCmd.Bindings is string bindingStr) - { - if (bindingStr.Contains("::")) - binding = bindingStr.Substring(bindingStr.IndexOf("::", StringComparison.Ordinal) + 2); - } + binding = bindingString.Substring(bindingString.IndexOf("::", StringComparison.Ordinal) + 2); } - HotKey = binding; } + HotKey = binding; } catch { @@ -68,59 +71,63 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke public static DisasmoPackage Current { get; set; } - public static async Task GetLatestVersionOnline() + public async Task GetLatestVersionOnlineAsync() { try { - await Task.Delay(3000); - - // is there an API to do it? - var client = new HttpClient(); - string str = await client.GetStringAsync("https://marketplace.visualstudio.com/items?itemName=EgorBogatov.Disasmo"); - string marker = "extensions/egorbogatov/disasmo/"; - int index = str.IndexOf(marker); - return Version.Parse(str.Substring(index + marker.Length, str.IndexOf('/', index + marker.Length) - index - marker.Length)); + // Question: Is there an API to do it? + // Answer: + // Visual Studio has its own functionality for fetching current package version. + // For example: Microsoft.VisualStudio.Setup.Services.MarketplaceExtensionService (internal) + // NuGet.PackageManagement.UI.Utility.NuGetSearchServiceReconnector (internal) + // Microsoft.VisualStudio.ExtensionManager.Implementation.ExtensionRepositoryService + // (public, BUT vssdk.extensionmanager DOES NOT PROVIDE GetSearchQueryExtensions METHOD FOR IVsExtensionRepository) + // Therefore, the easiest method is to directly request the website. + + using var client = new HttpClient(); + var content = await client.GetStringAsync("https://marketplace.visualstudio.com/items?itemName=EgorBogatov.Disasmo"); + var marker = "extensions/egorbogatov/disasmo/"; + var index = content.IndexOf(marker); + return Version.Parse(content.Substring(index + marker.Length, content.IndexOf('/', index + marker.Length) - index - marker.Length)); + } + catch + { + return new Version(0, 0); } - catch { return new Version(0, 0); } } public Version GetCurrentVersion() { - //TODO: fix - return new Version(5, 9, 2); - - //try - //{ - // // get ExtensionManager - // IVsExtensionManager manager = GetService(typeof(SVsExtensionManager)) as IVsExtensionManager; - // // get your extension by Product Id - // IInstalledExtension myExtension = manager.GetInstalledExtension("Disasmo.39513ef5-c3ee-4547-b7be-f29c752b591d"); - // // get current version - // Version currentVersion = myExtension.Header.Version; - // return currentVersion; - //} - //catch {return new Version(0, 0); } - } -} - -public class DisasmoCommandArgs : EditorCommandArgs -{ - public DisasmoCommandArgs(ITextView textView, ITextBuffer textBuffer) - : base(textView, textBuffer) - { + try + { + var extensionManager = GetService(typeof(SVsExtensionManager)) as IVsExtensionManager; + var ourExtension = extensionManager.GetInstalledExtension(PackageProductIdString); + var currentVersion = ourExtension.Header.Version; + return currentVersion; + } + catch + { + return new Version(0, 0); + } } } public class DisasmoCommandBinding { private const int DisasmoCommandId = 0x0100; + private const int DisasmoWindowCommandId = 0x0200; private const string DisasmoCommandSet = "4fd0ea18-9f33-43da-ace0-e387656e584c"; [Export] [CommandBinding(DisasmoCommandSet, DisasmoCommandId, typeof(DisasmoCommandArgs))] internal CommandBindingDefinition disasmoCommandBinding; + + [Export] + [CommandBinding(DisasmoCommandSet, DisasmoWindowCommandId, typeof(DisasmoWindowCommandArgs))] + internal CommandBindingDefinition disasmoWindowCommandBinding; } +public class DisasmoCommandArgs(ITextView textView, ITextBuffer textBuffer) : EditorCommandArgs(textView, textBuffer); [Export(typeof(ICommandHandler))] [ContentType("text")] @@ -129,10 +136,7 @@ public class DisasmoCommandHandler : ICommandHandler { public string DisplayName => "Disasmo this"; - public CommandState GetCommandState(DisasmoCommandArgs args) - { - return CommandState.Available; - } + public CommandState GetCommandState(DisasmoCommandArgs args) => CommandState.Available; public int GetCaretPosition(ITextView view) { @@ -152,36 +156,78 @@ public bool ExecuteCommand(DisasmoCommandArgs args, CommandExecutionContext cont IdeUtils.DTE().SaveActiveDocument(); var document = args.TextView?.TextBuffer?.GetRelatedDocuments()?.FirstOrDefault(); - if (document != null) + if (document is null) + return true; + + var position = GetCaretPosition(args.TextView); + if (position == -1) + return true; + + ThreadPool.QueueUserWorkItem(CallBack); + + return true; + + async void CallBack(object _) { - var pos = GetCaretPosition(args.TextView); - if (pos != -1) + try { - async void CallBack(object _) + if (DisasmoPackage.Current is null) { - try - { - if (DisasmoPackage.Current == null) - { - MessageBox.Show("Disasmo is still loading... (sometimes it takes a while for add-ins to fully load - it makes VS faster to start)."); - return; - } - await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); - var symbol = await DisasmMethodOrClassAction.GetSymbolStatic(document, pos, default, true); - var window = await IdeUtils.ShowWindowAsync(true, default); - if (window?.ViewModel is {} viewModel) - { - viewModel.RunOperationAsync(symbol, document.Project); - } - } - catch (Exception exc) - { - Debug.WriteLine(exc); - } + MessageBox.Show("Disasmo is still loading... (sometimes it takes a while for add-ins to fully load - it makes VS faster to start)."); + return; } - ThreadPool.QueueUserWorkItem(CallBack); + + await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); + + var symbol = await DisasmMethodOrClassAction.GetSymbolStaticAsync(document, position, default, true); + var window = await IdeUtils.ShowWindowAsync(true, default); + if (window?.ViewModel is { } viewModel) + { + viewModel.RunOperationAsync(symbol, document.Project); + } + } + catch (Exception ex) + { + Debug.WriteLine(ex); } } + } +} + +public class DisasmoWindowCommandArgs(ITextView textView, ITextBuffer textBuffer) : EditorCommandArgs(textView, textBuffer); + +[Export(typeof(ICommandHandler))] +[ContentType("text")] +[Name(nameof(DisasmoWindowCommandHandler))] +public class DisasmoWindowCommandHandler : ICommandHandler +{ + public string DisplayName => "Open DisasmWindow"; + + public CommandState GetCommandState(DisasmoWindowCommandArgs args) => CommandState.Available; + + public bool ExecuteCommand(DisasmoWindowCommandArgs args, CommandExecutionContext context) + { + ThreadPool.QueueUserWorkItem(CallBack); + return true; + + async void CallBack(object _) + { + try + { + if (DisasmoPackage.Current is null) + { + MessageBox.Show("Disasmo is still loading... (sometimes it takes a while for add-ins to fully load - it makes VS faster to start)."); + return; + } + + await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); + await IdeUtils.ShowWindowAsync(true, default); + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } } } \ No newline at end of file diff --git a/src/Vsix/DisasmoPackage.vsct b/src/Vsix/DisasmoPackage.vsct index 88fc9f3..4235ef8 100644 --- a/src/Vsix/DisasmoPackage.vsct +++ b/src/Vsix/DisasmoPackage.vsct @@ -30,8 +30,6 @@ - - - - + + diff --git a/src/Vsix/Properties/Settings.Designer.cs b/src/Vsix/Properties/Settings.Designer.cs index 1b14e82..b9e448e 100644 --- a/src/Vsix/Properties/Settings.Designer.cs +++ b/src/Vsix/Properties/Settings.Designer.cs @@ -147,12 +147,12 @@ public string GraphvisDotPath { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("False")] - public bool FgEnable { + public bool FlowgraphEnable { get { - return ((bool)(this["FgEnable"])); + return ((bool)(this["FlowgraphEnable"])); } set { - this["FgEnable"] = value; + this["FlowgraphEnable"] = value; } } @@ -244,24 +244,24 @@ public bool DisableLightBulb { [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("False")] - public bool DontGuessTFM { + public bool DontGuessTargetFramework { get { - return ((bool)(this["DontGuessTFM"])); + return ((bool)(this["DontGuessTargetFramework"])); } set { - this["DontGuessTFM"] = value; + this["DontGuessTargetFramework"] = value; } } [global::System.Configuration.UserScopedSettingAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Configuration.DefaultSettingValueAttribute("")] - public string OverridenTFM { + public string OverridenTargetFramework { get { - return ((string)(this["OverridenTFM"])); + return ((string)(this["OverridenTargetFramework"])); } set { - this["OverridenTFM"] = value; + this["OverridenTargetFramework"] = value; } } diff --git a/src/Vsix/Resources/AsmSyntax.xshd b/src/Vsix/Resources/AsmSyntax.xshd index fdfd6d1..e92582c 100644 --- a/src/Vsix/Resources/AsmSyntax.xshd +++ b/src/Vsix/Resources/AsmSyntax.xshd @@ -1,23 +1,29 @@  - + + - + + + - + Total bytes of code Method - - ; - - + // + + ;; + + + ; + ' ' @@ -25,8 +31,24 @@ + + + : + + + + \( + + + + nop + + + + 66 nop + - + .break .breakif .continue @@ -328,9 +350,6 @@ insertq insl insw - int - int03 - int3 into invd invept @@ -339,45 +358,6 @@ invoke invpcid invvpid - iret - iretd - iretl - iretq - iretw - ja - jae - jb - jbe - jc - jcxz - je - jecxz - jg - jge - jl - jle - jmp - jna - jnae - jnb - jnbe - jnc - jne - jng - jnge - jnl - jnle - jno - jnp - jns - jnz - jo - jp - jpe - jpo - jrcxz - js - jz kandnw kandw kmovw @@ -406,7 +386,6 @@ lgdt lgs lidt - ljmp lldt lmsw loadall @@ -418,15 +397,6 @@ lodsl lodsq lodsw - loop - loope - loopne - loopnz - loopz - lret - lretl - lretq - lretw lsl lss ltr @@ -729,12 +699,6 @@ repne repnz repz - ret - retf - retl - retn - retq - retw rex_jmp rol ror @@ -851,7 +815,6 @@ swapgs syscall sysenter - sysexit t1mskc test tzcnt @@ -1466,14 +1429,10 @@ adr adrl asr - b bfc bfi bic bkpt - bl - blx - bx bxj cbz cbnz @@ -1488,7 +1447,6 @@ dmb dsb eor - eret hvc isb it @@ -1589,8 +1547,6 @@ sxtb16 sxth sys - tbb - tbh teq tst uadd8 @@ -1633,36 +1589,18 @@ movs movw lsls - adds - beq - ble - bne - bgt + adds stp movz movk blr sxtw ldp - brk adcs adrp ands asrv at - bcs - bhs - bcc - blo - bmi - bpl - bvs - bvc - bhi - bls - bge - blt - bal bnv b.eq b.ne @@ -2045,8 +1983,103 @@ xtn2 zip1 zip2 - int3 align + + + + jmp + jcxz + jecxz + ja + jae + jb + jbe + jc + je + jg + jge + jl + jle + jna + jnae + jnb + jnbe + jnc + jne + jng + jnge + jnl + jnle + jno + jnp + jns + jnz + jo + jp + jpe + jpo + jrcxz + js + jz + ret + retf + retl + retn + retq + retw + iret + iretd + iretl + iretq + iretw + sysexit + sysret + eret + lret + lretl + lretq + lretw + ljmp + loop + loope + loopne + loopnz + loopz + + b + bl + bx + blx + beq + ble + bne + bgt + bge + blt + bcs + bhs + bcc + blo + bmi + bpl + bvs + bvc + bhi + bls + bal + tbb + tbh + + + + + syscall + int + int3 + int03 + + brk + \ No newline at end of file diff --git a/src/Vsix/Utils/IdeUtils.cs b/src/Vsix/Utils/IdeUtils.cs index 517bfd5..b1e96c2 100644 --- a/src/Vsix/Utils/IdeUtils.cs +++ b/src/Vsix/Utils/IdeUtils.cs @@ -20,8 +20,8 @@ public static class IdeUtils public static Project GetActiveProject(this DTE dte, string filePath) { - // find project by full name - if (dte.Solution != null) + // Find project by full name + if (dte.Solution is not null) { foreach (var projectObject in dte.Solution.Projects) { @@ -33,19 +33,21 @@ public static Project GetActiveProject(this DTE dte, string filePath) } var activeSolutionProjects = dte.ActiveSolutionProjects as Array; - if (activeSolutionProjects != null && activeSolutionProjects.Length > 0) + if (activeSolutionProjects is not null && activeSolutionProjects.Length > 0) return activeSolutionProjects.GetValue(0) as Project; + return null; } + public static void SaveActiveDocument(this DTE dte) { try { dte.ActiveDocument?.Save(); } - catch (Exception e) + catch (Exception ex) { - Debug.WriteLine(e); + Debug.WriteLine(ex); } } @@ -56,9 +58,9 @@ public static void SaveAllDocuments(this DTE dte) foreach (Document document in dte.Documents) document?.Save(); } - catch (Exception e) + catch (Exception ex) { - Debug.WriteLine(e); + Debug.WriteLine(ex); } } @@ -66,21 +68,23 @@ public static async Task ShowWindowAsync(bool tryTwice, CancellationToken { try { - if (DisasmoPackage.Current == null) + if (DisasmoPackage.Current is null) { MessageBox.Show("DisasmoPackage is still loading... (sometimes it takes a while for add-ins to fully load - it makes VS faster to start)."); return null; } + await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); var window = await DisasmoPackage.Current.ShowToolWindowAsync(typeof(DisasmWindow), 0, create: true, cancellationToken: cancellationToken); + if (tryTwice) { await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - // no idea why I have to call it twice, it doesn't work if I do it only once on the first usage - window = await DisasmoPackage.Current.ShowToolWindowAsync(typeof(T), 0, create: true, - cancellationToken: cancellationToken); + // No idea why I have to call it twice, it doesn't work if I do it only once on the first usage + window = await DisasmoPackage.Current.ShowToolWindowAsync(typeof(T), 0, create: true, cancellationToken: cancellationToken); await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); } + return window as T; } catch @@ -95,29 +99,29 @@ public static void RunDiffTools(string contentLeft, string contentRight) contentRight = string.IsNullOrEmpty(contentRight) ? " " : contentRight; var tempPath = Path.GetTempPath(); - var diffDir = Path.Combine(tempPath, "Disasmo_diffs_" + Guid.NewGuid().ToString("N").Substring(0, 10)); - Directory.CreateDirectory(diffDir); + var diffDirectory = Path.Combine(tempPath, "Disasmo_diffs_" + Guid.NewGuid().ToString("N").Substring(0, 10)); + Directory.CreateDirectory(diffDirectory); - string tmpFileLeft = Path.Combine(diffDir, "previous.asm"); - string tmpFileRight = Path.Combine(diffDir, "current.asm"); + var tempFileLeft = Path.Combine(diffDirectory, "previous.asm"); + var tempFileRight = Path.Combine(diffDirectory, "current.asm"); - File.WriteAllText(tmpFileLeft, contentLeft.NormalizeLineEndings()); - File.WriteAllText(tmpFileRight, contentRight.NormalizeLineEndings()); + File.WriteAllText(tempFileLeft, contentLeft.NormalizeLineEndings()); + File.WriteAllText(tempFileRight, contentRight.NormalizeLineEndings()); try { - // Copied from https://github.com/madskristensen/FileDiffer/blob/main/src/Commands/DiffFilesCommand.cs#L48-L56 (c) madskristensen - object args = $"\"{tmpFileLeft}\" \"{tmpFileRight}\""; - ((DTE)Package.GetGlobalService(typeof(SDTE))).Commands.Raise("5D4C0442-C0A2-4BE8-9B4D-AB1C28450942", 256, ref args, ref args); + // Copied from https://github.com/madskristensen/FileDiffer/blob/master/src/Commands/SelectedFilesCommand.cs#L62-L71 (c) madskristensen + object args = $"\"{tempFileLeft}\" \"{tempFileRight}\""; + ((DTE)Package.GetGlobalService(typeof(SDTE))).Commands.Raise("5D4C0442-C0A2-4BE8-9B4D-AB1C28450942", ID: 256, ref args, ref args); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } finally { - File.Delete(tmpFileLeft); - File.Delete(tmpFileRight); + File.Delete(tempFileLeft); + File.Delete(tempFileRight); } } @@ -138,27 +142,27 @@ public static string GetConfigurationDimension(ProjectConfiguration projectConfi public static TfmVersion GetTargetFrameworkVersionDimension(ProjectConfiguration projectConfiguration) { var targetFramework = GetTargetFrameworkDimension(projectConfiguration); - return targetFramework != null ? TfmVersion.Parse(targetFramework) : null; + return targetFramework is not null ? TfmVersion.Parse(targetFramework) : null; } - public static async Task GetProjectProperties(UnconfiguredProject unconfiguredProject, ProjectConfiguration projectConfiguration) + public static async Task GetProjectPropertiesAsync(UnconfiguredProject unconfiguredProject, ProjectConfiguration projectConfiguration) { try { - // it will throw "Release config was not found" to the Output if there is no such config in the project + // It will throw "Release config was not found" to the Output if there is no such config in the project projectConfiguration ??= await unconfiguredProject.Services.ProjectConfigurationsService.GetProjectConfigurationAsync("Release"); - ConfiguredProject configuredProject = await unconfiguredProject.LoadConfiguredProjectAsync(projectConfiguration); + var configuredProject = await unconfiguredProject.LoadConfiguredProjectAsync(projectConfiguration); return configuredProject.Services.ProjectPropertiesProvider.GetCommonProperties(); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); // VS was not able to find the given config (but it still might exist) return null; } } - public static async Task> GetProjectConfigurations(UnconfiguredProject unconfiguredProject) + public static async Task> GetProjectConfigurationsAsync(UnconfiguredProject unconfiguredProject) { try { @@ -166,32 +170,32 @@ public static async Task> GetProjectConfigurat .ProjectConfigurationsService .GetKnownProjectConfigurationsAsync(); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); // VS was not able to find the given config (but it still might exist) return []; } } - public static async void OpenInVSCode(string output) + public static void OpenInVSCode(string output) { if (string.IsNullOrWhiteSpace(output)) return; try { - string file = Path.GetTempFileName() + ".txt"; + var file = Path.GetTempFileName() + ".txt"; File.WriteAllText(file, output.NormalizeLineEndings()); - ProcessStartInfo psi = new ProcessStartInfo(file); - psi.Verb = "open"; - psi.UseShellExecute = true; - Process.Start(psi); + var processStartInfo = new ProcessStartInfo(file); + processStartInfo.Verb = "open"; + processStartInfo.UseShellExecute = true; + Process.Start(processStartInfo); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } } @@ -204,14 +208,14 @@ public static void OpenInVS(string output) { // Let's try .asm file and hope VS will be able to apply some highlighting // even for JitDump... - string file = Path.GetTempFileName() + ".asm"; + var file = Path.GetTempFileName() + ".asm"; File.WriteAllText(file, output.NormalizeLineEndings()); DTE().ItemOperations.OpenFile(file); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } } } \ No newline at end of file diff --git a/src/Vsix/Utils/TfmVersion.cs b/src/Vsix/Utils/TfmVersion.cs index cbe2d49..297a671 100644 --- a/src/Vsix/Utils/TfmVersion.cs +++ b/src/Vsix/Utils/TfmVersion.cs @@ -16,24 +16,21 @@ public class TfmVersion : IComparable public int CompareTo(TfmVersion other) { - if (other == null) + if (other is null) return 1; - var majorCmp = Nullable.Compare(Major, other.Major); - if (majorCmp != 0) - return majorCmp; + var majorCompare = Nullable.Compare(Major, other.Major); + if (majorCompare != 0) + return majorCompare; - var minorCmp = Nullable.Compare(Minor, other.Minor); - if (minorCmp != 0) - return minorCmp; + var minorCompare = Nullable.Compare(Minor, other.Minor); + if (minorCompare != 0) + return minorCompare; return Nullable.Compare(Patch, other.Patch); } - public override string ToString() - { - return $"{Moniker} {Major}.{Minor}.{Patch}"; - } + public override string ToString() => $"{Moniker} {Major}.{Minor}.{Patch}"; public static TfmVersion Parse(string tfm) { diff --git a/src/Vsix/Utils/ValidationRuleMinMaxInt.cs b/src/Vsix/Utils/ValidationRuleMinMaxInt.cs index e892255..dce0990 100644 --- a/src/Vsix/Utils/ValidationRuleMinMaxInt.cs +++ b/src/Vsix/Utils/ValidationRuleMinMaxInt.cs @@ -1,30 +1,25 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Globalization; using System.Windows.Controls; -namespace Disasmo.Utils -{ - internal class ValidationRuleMinMaxInt : ValidationRuleStringAsInt - { - public int Min { get; set; } - public int Max { get; set; } +namespace Disasmo.Utils; - public override ValidationResult Validate(object value, CultureInfo cultureInfo) - { - ValidationResult result = ValidateInternal(value, cultureInfo, out int parsedValue); +public class ValidationRuleMinMaxInt : ValidationRuleStringAsInt +{ + public int Min { get; set; } + public int Max { get; set; } - if (result.IsValid) { - if (parsedValue < Min) - result = new ValidationResult(false, $"Please enter a value greater than {Min}!"); - else if (parsedValue > Max) - result = new ValidationResult(false, $"Please enter a value less than {Max}!"); - } + public override ValidationResult Validate(object value, CultureInfo cultureInfo) + { + var result = ValidateInternal(value, cultureInfo, out int parsedValue); - return result; + if (result.IsValid) + { + if (parsedValue < Min) + result = new ValidationResult(false, $"Please enter a value greater than {Min}!"); + else if (parsedValue > Max) + result = new ValidationResult(false, $"Please enter a value less than {Max}!"); } + + return result; } -} +} \ No newline at end of file diff --git a/src/Vsix/Utils/ValidationRuleStringAsInt.cs b/src/Vsix/Utils/ValidationRuleStringAsInt.cs index b6a7f31..6e1b918 100644 --- a/src/Vsix/Utils/ValidationRuleStringAsInt.cs +++ b/src/Vsix/Utils/ValidationRuleStringAsInt.cs @@ -1,31 +1,26 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Globalization; using System.Windows.Controls; -namespace Disasmo.Utils +namespace Disasmo.Utils; + +public class ValidationRuleStringAsInt : ValidationRule { - internal class ValidationRuleStringAsInt : ValidationRule - { - public override ValidationResult Validate(object value, CultureInfo cultureInfo) - { - return ValidateInternal(value, cultureInfo, out _); - } + public override ValidationResult Validate(object value, CultureInfo cultureInfo) => + ValidateInternal(value, cultureInfo, out _); - protected ValidationResult ValidateInternal(object value, CultureInfo cultureInfo, out int parsedValue) - { - parsedValue = 0; + protected ValidationResult ValidateInternal(object value, CultureInfo cultureInfo, out int parsedValue) + { + parsedValue = 0; - if (value is int) - return ValidationResult.ValidResult; + if (value is int) + return ValidationResult.ValidResult; - if (value is string valueAsString) - return int.TryParse(valueAsString, NumberStyles.Integer, cultureInfo, out parsedValue) ? ValidationResult.ValidResult : new ValidationResult(false, "Please enter a valid number!"); + if (value is string valueAsString) + { + if (int.TryParse(valueAsString, NumberStyles.Integer, cultureInfo, out parsedValue)) + return ValidationResult.ValidResult; + } - return new ValidationResult(false, "Please enter a valid number!"); - } + return new ValidationResult(false, "Please enter a valid number!"); } -} +} \ No newline at end of file diff --git a/src/Vsix/ViewModels/FlowgraphItemViewModel.cs b/src/Vsix/ViewModels/FlowgraphItemViewModel.cs index 04fc8b0..8c2fe05 100644 --- a/src/Vsix/ViewModels/FlowgraphItemViewModel.cs +++ b/src/Vsix/ViewModels/FlowgraphItemViewModel.cs @@ -9,15 +9,36 @@ namespace Disasmo; public class FlowgraphItemViewModel : ViewModelBase { - private readonly SettingsViewModel _settingsView; + private readonly MainViewModel _mainViewModel; + private readonly SettingsViewModel _settingsViewModel; private string _imageUrl; private string _dotFileUrl; private string _name; + private string _ordinal; private bool _isBusy; + private bool _isInitialized; + private Task _currentLoadImageTask; - public FlowgraphItemViewModel(SettingsViewModel settingsView) + public FlowgraphItemViewModel( + MainViewModel mainViewModel, + SettingsViewModel settingsViewModel, + string ordinal, + string name, + string dotFileUrl, + string imageUrl) { - _settingsView = settingsView; + _mainViewModel = mainViewModel; + _settingsViewModel = settingsViewModel; + _ordinal = ordinal; + _name = name; + _dotFileUrl = dotFileUrl; + _imageUrl = imageUrl; + } + + public string Ordinal + { + get => _ordinal; + set => Set(ref _ordinal, value); } public string Name @@ -46,26 +67,43 @@ public bool IsBusy set => Set(ref _isBusy, value); } - public async Task LoadImageAsync(CancellationToken ct) + public bool IsInitialized + { + get => _isInitialized && File.Exists(DotFileUrl + ".png"); + set => Set(ref _isInitialized, value); + } + + public async Task LoadImageAsync(CancellationToken cancellationToken) + { + if (!IsBusy) + _currentLoadImageTask = InternalLoadImageAsync(cancellationToken); + + await _currentLoadImageTask; + } + + async Task InternalLoadImageAsync(CancellationToken cancellationToken) { - if (File.Exists(DotFileUrl + ".png")) + var dotImageUrl = DotFileUrl + ".png"; + if (IsInitialized) { - ImageUrl = DotFileUrl + ".png"; + ImageUrl = dotImageUrl; } else { IsBusy = true; + _mainViewModel.NotifyFlowgraphPhaseLoadingStarted(); try { - var img = DotFileUrl + ".png"; - string dotExeArgs = $"-Tpng -o\"{img}\" -Kdot \"{DotFileUrl}\""; - await ProcessUtils.RunProcess(_settingsView.GraphvisDotPath, dotExeArgs, cancellationToken: ct); - ImageUrl = img; + var dotExeArgs = $"-Tpng -o\"{dotImageUrl}\" -Kdot \"{DotFileUrl}\""; + await ProcessUtils.RunProcess(_settingsViewModel.GraphvisDotPath, dotExeArgs, cancellationToken: cancellationToken); + ImageUrl = dotImageUrl; } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } + IsInitialized = true; + _mainViewModel.NotifyFlowgraphPhaseLoadingFinished(); IsBusy = false; } } diff --git a/src/Vsix/ViewModels/IntrinsicsViewModel.cs b/src/Vsix/ViewModels/IntrinsicsViewModel.cs index b1345c1..6c1a162 100644 --- a/src/Vsix/ViewModels/IntrinsicsViewModel.cs +++ b/src/Vsix/ViewModels/IntrinsicsViewModel.cs @@ -1,9 +1,10 @@ -using System; +using Disasmo.Utils; +using GalaSoft.MvvmLight; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using Disasmo.Utils; -using GalaSoft.MvvmLight; +using System.Threading.Tasks; namespace Disasmo.ViewModels; @@ -22,7 +23,7 @@ public bool IsBusy set => Set(ref _isBusy, value); } - private async void StartDownloadSources() + private async Task FetchSourcesAsync() { if (IsInDesignMode || _isDownloading || _intrinsics?.Any() == true) return; @@ -31,19 +32,17 @@ private async void StartDownloadSources() _isDownloading = true; try { - _intrinsics = await IntrinsicsSourcesService.ParseIntrinsics(file => - { - LoadingStatus = "Loading data from Github...\nParsing " + file; - }); - + _intrinsics = await IntrinsicsSourcesService.ParseIntrinsics(statusMessage => LoadingStatus = statusMessage); + UpdateSuggestions(); } - catch (Exception exc) + catch (Exception ex) { - Debug.WriteLine(exc); + Debug.WriteLine(ex); } IsBusy = false; _isDownloading = false; + return; } public string Input @@ -52,14 +51,22 @@ public string Input set { Set(ref _input, value); - StartDownloadSources(); - if (_intrinsics == null || string.IsNullOrWhiteSpace(value) || value.Length < 3) - Suggestions = null; - else - Suggestions = _intrinsics.Where(i => i.Contains(value)).Take(15).ToList(); + FetchSourcesAsync(); + UpdateSuggestions(); } } + private void UpdateSuggestions() + { + if (_intrinsics is null || _input.Length < 3) + { + Suggestions = null; + return; + } + + Suggestions = _intrinsics.Where(i => i.Contains(_input)).Take(15).ToList(); + } + public string LoadingStatus { get => _loadingStatus; diff --git a/src/Vsix/ViewModels/MainViewModel.cs b/src/Vsix/ViewModels/MainViewModel.cs index c42b49f..76a6df6 100644 --- a/src/Vsix/ViewModels/MainViewModel.cs +++ b/src/Vsix/ViewModels/MainViewModel.cs @@ -1,942 +1,1093 @@ -using EnvDTE; +using Disasmo.ViewModels; using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.CommandWpf; using Microsoft.CodeAnalysis; +using Microsoft.VisualStudio.ProjectSystem; +using Microsoft.VisualStudio.ProjectSystem.Properties; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Windows.Input; +using CAProject = Microsoft.CodeAnalysis.Project; using Project = EnvDTE.Project; using Task = System.Threading.Tasks.Task; -using Disasmo.ViewModels; -using Microsoft.VisualStudio.ProjectSystem; -using Microsoft.VisualStudio.ProjectSystem.Properties; -using System.Collections.ObjectModel; -using CAProject = Microsoft.CodeAnalysis.Project; -namespace Disasmo +namespace Disasmo; + +public class MainViewModel : ViewModelBase { - public class MainViewModel : ViewModelBase + private static readonly int MaxCountOfLoadingPhases = Math.Max(2, Environment.ProcessorCount / 2); + // dot.exe eats exactly one virtual core. Assuming that the number of + // virtual cores (Environment.ProcessorCount) equals to the number of + // physical cores multiplied to 2. + // By the expression above, we allow dot.exe to use only 50% of the cpu resources. + // If don't take the limitation into account, dot.exe will eat up all the cpu resources, + // leading to at least system interruptions, and at worst to a system crash. + + private string _output; + private string _previousOutput; + private string _loadingStatus; + private string _stopwatchStatus; + private string[] _jitDumpPhases; + private bool _isLoading; + private bool _isPhasesLoading; + private bool _lastJitDumpStatus; + private ISymbol _currentSymbol; + private CAProject _currentProject; + private bool _success; + private string _currentProjectPath; + private string _currentTargetFramework; + private string _flowgraphPngPath; + private bool _updateIsAvailable; + private string DisasmoOutputDirectory = ""; + private ObservableCollection _flowgraphPhases = new(); + private FlowgraphItemViewModel _selectedPhase; + private int _countOfLoadingPhases; + private string _phaseLoadingPhrase; + private Version _currentVersion; + private Version _availableVersion; + + // Let's use new name for the temp folder each version to avoid possible issues (e.g. changes in the Disasmo.Loader) + private string DisasmoFolder => "Disasmo-v" + DisasmoPackage.Current?.GetCurrentVersion(); + + public SettingsViewModel SettingsViewModel { get; } = new(); + public IntrinsicsViewModel IntrinsicsViewModel { get; } = new(); + + public event Action MainPageRequested; + + public string[] JitDumpPhases { - private string _output; - private string _previousOutput; - private string _loadingStatus; - private string _stopwatchStatus; - private string[] _jitDumpPhases; - private bool _isLoading; - private ISymbol _currentSymbol; - private CAProject _currentProject; - private bool _success; - private string _currentProjectPath; - private string _currentTf; - private string _fgPngPath; - private string DisasmoOutDir = ""; - private ObservableCollection _fgPhases = new(); - private FlowgraphItemViewModel _selectedPhase; - - // let's use new name for the temp folder each version to avoid possible issues (e.g. changes in the Disasmo.Loader) - private string DisasmoFolder => "Disasmo-v" + DisasmoPackage.Current?.GetCurrentVersion(); - - public SettingsViewModel SettingsVm { get; } = new(); - public IntrinsicsViewModel IntrinsicsVm { get; } = new(); - - public event Action MainPageRequested; - - public string[] JitDumpPhases - { - get => _jitDumpPhases; - set => Set(ref _jitDumpPhases, value); - } + get => _jitDumpPhases; + set => Set(ref _jitDumpPhases, value); + } - public string Output + public string Output + { + get => _output; + set { - get => _output; - set - { - if (!string.IsNullOrWhiteSpace(_output)) - PreviousOutput = _output; - Set(ref _output, value); + if (!string.IsNullOrWhiteSpace(_output)) + PreviousOutput = _output; + Set(ref _output, value); - const string phasePrefix = "*************** Starting PHASE "; - JitDumpPhases = (Output ?? "") - .Split('\n') - .Where(l => l.StartsWith(phasePrefix)) - .Select(i => i.Replace(phasePrefix, "")) - .ToArray(); + const string phasePrefix = "*************** Starting PHASE "; + + if (_output is not null) + { + JitDumpPhases = Output + .Split('\n') + .Where(l => l.StartsWith(phasePrefix)) + .Select(i => i.Replace(phasePrefix, "")) + .ToArray(); + } + else + { + JitDumpPhases = []; } } + } - public string PreviousOutput - { - get => _previousOutput; - set => Set(ref _previousOutput, value); - } + public string PreviousOutput + { + get => _previousOutput; + set => Set(ref _previousOutput, value); + } - public string LoadingStatus - { - get => _loadingStatus; - set => Set(ref _loadingStatus, value); - } + public string LoadingStatus + { + get => _loadingStatus; + set => Set(ref _loadingStatus, value); + } - public CancellationTokenSource UserCts { get; set; } + public CancellationTokenSource UserCancellationTokens { get; set; } - public CancellationToken UserCt => UserCts?.Token ?? default; + public CancellationToken UserCancellationToken => UserCancellationTokens?.Token ?? default; - public void ThrowIfCanceled() - { - if (UserCts?.IsCancellationRequested == true) - throw new OperationCanceledException(); - } + public void ThrowIfCanceled() + { + if (UserCancellationTokens?.IsCancellationRequested == true) + throw new OperationCanceledException(); + } - public ICommand CancelCommand => new RelayCommand(() => - { - try { UserCts?.Cancel(); } catch { } - }); + public ICommand CancelCommand => new RelayCommand(() => + { + try { UserCancellationTokens?.Cancel(); } catch { } + }); - public string DefaultHotKey => DisasmoPackage.HotKey; + public string DefaultHotKey => DisasmoPackage.HotKey; - public bool Success - { - get => _success; - set => Set(ref _success, value); - } + public bool UpdateIsAvailable + { + get => _updateIsAvailable; + set { Set(ref _updateIsAvailable, value); } + } + + public bool Success + { + get => _success; + set => Set(ref _success, value); + } - public bool IsLoading + public bool IsLoading + { + get => _isLoading; + set { - get => _isLoading; - set + if (!_isLoading && value) { - if (!_isLoading && value) - { - UserCts = new CancellationTokenSource(); - } - Set(ref _isLoading, value); + UserCancellationTokens = new CancellationTokenSource(); } + Set(ref _isLoading, value); } + } - public string StopwatchStatus + public bool IsPhasesLoading + { + get => _isPhasesLoading; + set { - get => _stopwatchStatus; - set => Set(ref _stopwatchStatus, value); + Set(ref _isPhasesLoading, value); } + } - public string FgPngPath - { - get => _fgPngPath; - set => Set(ref _fgPngPath, value); - } + public string StopwatchStatus + { + get => _stopwatchStatus; + set => Set(ref _stopwatchStatus, value); + } - public ICommand RefreshCommand => new RelayCommand(() => RunOperationAsync(_currentSymbol, _currentProject)); + public string FlowgraphPngPath + { + get => _flowgraphPngPath; + set => Set(ref _flowgraphPngPath, value); + } - public ICommand RunDiffWithPrevious => new RelayCommand(() => IdeUtils.RunDiffTools(PreviousOutput, Output)); + public ICommand RefreshCommand => new RelayCommand(() => RunOperationAsync(_currentSymbol, _currentProject)); - public ICommand OpenInVSCode => new RelayCommand(() => IdeUtils.OpenInVSCode(Output)); + public ICommand RunDiffWithPrevious => new RelayCommand(() => IdeUtils.RunDiffTools(PreviousOutput, Output)); - public ICommand OpenInVS => new RelayCommand(() => IdeUtils.OpenInVS(Output)); + public ICommand OpenInVSCode => new RelayCommand(() => IdeUtils.OpenInVSCode(Output)); - public ObservableCollection FgPhases - { - get => _fgPhases; - set => Set(ref _fgPhases, value); - } + public ICommand OpenInVS => new RelayCommand(() => IdeUtils.OpenInVS(Output)); + + public ObservableCollection FlowgraphPhases + { + get => _flowgraphPhases; + set => Set(ref _flowgraphPhases, value); + } - public FlowgraphItemViewModel SelectedPhase + public FlowgraphItemViewModel SelectedPhase + { + get => _selectedPhase; + set { - get => _selectedPhase; - set - { - Set(ref _selectedPhase, value); - _selectedPhase?.LoadImageAsync(UserCt); - } + Set(ref _selectedPhase, value); + + TryLoadSelectedPhaseImage(); } + } - public async Task RunFinalExe(DisasmoSymbolInfo symbolInfo, IProjectProperties projectProperties) + public bool LastContextIsAsm => Success && !_lastJitDumpStatus; + + public string PhaseLoadingPhrase + { + get => _phaseLoadingPhrase; + set => Set(ref _phaseLoadingPhrase, value); + } + + public Version CurrentVersion + { + get => _currentVersion; + set => Set(ref _currentVersion, value); + } + + public Version AvailableVersion + { + get => _availableVersion; + set => Set(ref _availableVersion, value); + } + + public async Task CheckUpdatesAsync() + { + CurrentVersion = DisasmoPackage.Current?.GetCurrentVersion(); + AvailableVersion = await DisasmoPackage.Current?.GetLatestVersionOnlineAsync(); + if (CurrentVersion != null && AvailableVersion != null && AvailableVersion > CurrentVersion) + UpdateIsAvailable = true; + } + + public async Task RunFinalExeAsync(DisasmoSymbolInfo symbolInfo, IProjectProperties projectProperties) + { + try { - try - { - if (_currentSymbol == null || string.IsNullOrWhiteSpace(_currentProjectPath)) - return; + if (_currentSymbol is null || string.IsNullOrWhiteSpace(_currentProjectPath)) + return; - await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); + await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); - Success = false; - IsLoading = true; - FgPngPath = null; - LoadingStatus = "Loading..."; + Success = false; + IsLoading = true; + FlowgraphPngPath = null; + LoadingStatus = "Loading..."; + _lastJitDumpStatus = SettingsViewModel.JitDumpInsteadOfDisasm; - string dstFolder = DisasmoOutDir; - if (!Path.IsPathRooted(dstFolder)) - dstFolder = Path.Combine(Path.GetDirectoryName(_currentProjectPath), DisasmoOutDir); + var destinationFolder = DisasmoOutputDirectory; + if (!Path.IsPathRooted(destinationFolder)) + destinationFolder = Path.Combine(Path.GetDirectoryName(_currentProjectPath), destinationFolder); - // TODO: respect AssemblyName property (if it doesn't match csproj name) - string fileName = Path.GetFileNameWithoutExtension(_currentProjectPath); + // TODO: Respect AssemblyName property (if it doesn't match csproj name) + var fileName = Path.GetFileNameWithoutExtension(_currentProjectPath); - try + try + { + if (projectProperties is not null) { - if (projectProperties != null) + var customAsmName = await projectProperties.GetEvaluatedPropertyValueAsync("AssemblyName"); + if (!string.IsNullOrWhiteSpace(customAsmName)) { - string customAsmName = await projectProperties.GetEvaluatedPropertyValueAsync("AssemblyName"); - if (!string.IsNullOrWhiteSpace(customAsmName)) - { - fileName = customAsmName; - } + fileName = customAsmName; } } - catch { } + } + catch { } - var envVars = new Dictionary(); + var envVars = new Dictionary(); - if (!SettingsVm.RunAppMode && !SettingsVm.CrossgenIsSelected && !SettingsVm.NativeAotIsSelected) - { - var addinVersion = DisasmoPackage.Current.GetCurrentVersion(); - await LoaderAppManager.InitLoaderAndCopyTo(_currentTf, dstFolder, log => { /*TODO: update UI*/ }, addinVersion, UserCt); - } + if (!SettingsViewModel.RunAppMode && !SettingsViewModel.CrossgenIsSelected && !SettingsViewModel.NativeAotIsSelected) + { + var disasmoVersion = DisasmoPackage.Current.GetCurrentVersion(); + await LoaderAppManager.InitLoaderAndCopyTo(_currentTargetFramework, destinationFolder, log => { /*TODO: Update UI*/ }, disasmoVersion, UserCancellationToken); + } - if (SettingsVm.JitDumpInsteadOfDisasm) - envVars["DOTNET_JitDump"] = symbolInfo.Target; - else if (SettingsVm.PrintInlinees) - envVars["DOTNET_JitPrintInlinedMethods"] = symbolInfo.Target; - else - envVars["DOTNET_JitDisasm"] = symbolInfo.Target; + if (SettingsViewModel.JitDumpInsteadOfDisasm) + { + envVars["DOTNET_JitDump"] = symbolInfo.Target; + } + else if (SettingsViewModel.PrintInlinees) + { + envVars["DOTNET_JitPrintInlinedMethods"] = symbolInfo.Target; + } + else + { + envVars["DOTNET_JitDisasm"] = symbolInfo.Target; + } - if (!string.IsNullOrWhiteSpace(SettingsVm.SelectedCustomJit) && !SettingsVm.CrossgenIsSelected && !SettingsVm.NativeAotIsSelected && - !SettingsVm.SelectedCustomJit.Equals(Constants.DefaultJit, StringComparison.InvariantCultureIgnoreCase) && SettingsVm.UseCustomRuntime) - { - envVars["DOTNET_AltJitName"] = SettingsVm.SelectedCustomJit; - envVars["DOTNET_AltJit"] = symbolInfo.Target; - } + if (!string.IsNullOrWhiteSpace(SettingsViewModel.SelectedCustomJit) && + !SettingsViewModel.CrossgenIsSelected && + !SettingsViewModel.NativeAotIsSelected && + !SettingsViewModel.SelectedCustomJit.Equals(Constants.DefaultJit, StringComparison.InvariantCultureIgnoreCase) && + SettingsViewModel.UseCustomRuntime) + { + envVars["DOTNET_AltJitName"] = SettingsViewModel.SelectedCustomJit; + envVars["DOTNET_AltJit"] = symbolInfo.Target; + } - envVars["DOTNET_TieredPGO"] = SettingsVm.UsePGO ? "1" : "0"; - envVars["DOTNET_JitDisasmDiffable"] = SettingsVm.Diffable ? "1" : "0"; + envVars["DOTNET_TieredPGO"] = SettingsViewModel.UsePGO ? "1" : "0"; + envVars["DOTNET_JitDisasmDiffable"] = SettingsViewModel.Diffable ? "1" : "0"; - if (!SettingsVm.UseDotnetPublishForReload && SettingsVm.UseCustomRuntime) - { - var (runtimePackPath, success) = GetPathToRuntimePack(); - if (!success) - return; + if (!SettingsViewModel.UseDotnetPublishForReload && SettingsViewModel.UseCustomRuntime) + { + var (runtimePackPath, success) = GetPathToRuntimePack(); + if (!success) + return; + + // Tell jit to look for BCL libs in the locally built runtime pack + envVars["CORE_LIBRARIES"] = runtimePackPath; + } + + envVars["DOTNET_TieredCompilation"] = SettingsViewModel.UseTieredJit ? "1" : "0"; - // tell jit to look for BCL libs in the locally built runtime pack - envVars["CORE_LIBRARIES"] = runtimePackPath; + // User is free to override any of those ^ + SettingsViewModel.FillWithUserVars(envVars); + + string currentFlowgraphFile = null; + if (SettingsViewModel.FlowgraphEnable) + { + if (symbolInfo.MethodName == "*") + { + Output = "Flowgraph for classes (all methods) is not supported yet."; + return; } - envVars["DOTNET_TieredCompilation"] = SettingsVm.UseTieredJit ? "1" : "0"; + currentFlowgraphFile = Path.GetTempFileName(); + envVars["DOTNET_JitDumpFg"] = symbolInfo.Target; + envVars["DOTNET_JitDumpFgDot"] = "1"; + envVars["DOTNET_JitDumpFgPhase"] = "*"; + envVars["DOTNET_JitDumpFgFile"] = currentFlowgraphFile; + } + + var command = $"\"{LoaderAppManager.DisasmoLoaderName}.dll\" \"{fileName}.dll\" \"{symbolInfo.ClassName}\" \"{symbolInfo.MethodName}\" {SettingsViewModel.UseUnloadableContext}"; + if (SettingsViewModel.RunAppMode) + { + command = $"\"{fileName}.dll\""; + } + + var executable = "dotnet"; + if (SettingsViewModel.CrossgenIsSelected && SettingsViewModel.UseCustomRuntime) + { + var (clrCheckedFilesDir, checkedFound) = GetPathToCoreClrChecked(); + if (!checkedFound) + return; - // User is free to override any of those ^ - SettingsVm.FillWithUserVars(envVars); + var (runtimePackPath, runtimePackFound) = GetPathToRuntimePack(); + if (!runtimePackFound) + return; + executable = Path.Combine(SettingsViewModel.PathToLocalCoreClr, "dotnet.cmd"); + command = $"{Path.Combine(clrCheckedFilesDir, "crossgen2", "crossgen2.dll")} --out aot "; - string currentFgFile = null; - if (SettingsVm.FgEnable) + foreach (var envVar in envVars) { - if (symbolInfo.MethodName == "*") + var keyLower = envVar.Key.ToLowerInvariant(); + if (keyLower?.StartsWith("dotnet_") == false && + keyLower?.StartsWith("complus_") == false) { - Output = "Flowgraph for classes (all methods) is not supported yet."; - return; + continue; } - currentFgFile = Path.GetTempFileName(); - envVars["DOTNET_JitDumpFg"] = symbolInfo.Target; - envVars["DOTNET_JitDumpFgDot"] = "1"; - envVars["DOTNET_JitDumpFgPhase"] = "*"; - envVars["DOTNET_JitDumpFgFile"] = currentFgFile; + keyLower = keyLower + .Replace("dotnet_jitdump", "--codegenopt:jitdump") + .Replace("dotnet_jitdisasm", "--codegenopt:jitdisasm") + .Replace("dotnet_", "--codegenopt:") + .Replace("complus_", "--codegenopt:"); + command += keyLower + "=\"" + envVar.Value + "\" "; } + envVars.Clear(); + + // These are needed for faster crossgen itself - they're not changing output codegen + envVars["DOTNET_TieredPGO"] = "0"; + envVars["DOTNET_ReadyToRun"] = "1"; + envVars["DOTNET_TC_QuickJitForLoops"] = "1"; + envVars["DOTNET_TC_CallCountingDelayMs"] = "0"; + envVars["DOTNET_TieredCompilation"] = "1"; + command += SettingsViewModel.Crossgen2Args.Replace("\r\n", " ").Replace("\n", " ") + $" \"{fileName}.dll\" "; - string command = $"\"{LoaderAppManager.DisasmoLoaderName}.dll\" \"{fileName}.dll\" \"{symbolInfo.ClassName}\" \"{symbolInfo.MethodName}\" {SettingsVm.UseUnloadableContext}"; - if (SettingsVm.RunAppMode) + if (SettingsViewModel.UseDotnetPublishForReload) { - command = $"\"{fileName}.dll\""; + // Reference everything in the publish dir + command += $" -r: \"{destinationFolder}\\*.dll\" "; } - - string executable = "dotnet"; - - if (SettingsVm.CrossgenIsSelected && SettingsVm.UseCustomRuntime) + else { - var (clrCheckedFilesDir, checkedFound) = GetPathToCoreClrChecked(); - if (!checkedFound) - return; - - var (runtimePackPath, runtimePackFound) = GetPathToRuntimePack(); - if (!runtimePackFound) - return; - - executable = Path.Combine(SettingsVm.PathToLocalCoreClr, "dotnet.cmd"); - command = $"{Path.Combine(clrCheckedFilesDir, "crossgen2", "crossgen2.dll")} --out aot "; + // The runtime pack we use doesn't contain corelib so let's use "checked" corelib + // TODO: Build proper core_root with release version of corelib + var corelib = Path.Combine(clrCheckedFilesDir, "System.Private.CoreLib.dll"); + command += $" -r: \"{runtimePackPath}\\*.dll\" -r: \"{corelib}\" "; + } - foreach (var envVar in envVars) - { - var keyLower = envVar.Key.ToLowerInvariant(); - if (keyLower?.StartsWith("dotnet_") == false && - keyLower?.StartsWith("complus_") == false) - { - continue; - } + LoadingStatus = $"Executing crossgen2..."; + } + else if (SettingsViewModel.NativeAotIsSelected && SettingsViewModel.UseCustomRuntime) + { + var (clrReleaseFolder, clrFound) = GetPathToCoreClrCheckedForNativeAot(); + if (!clrFound) + return; - keyLower = keyLower - .Replace("dotnet_jitdump", "--codegenopt:jitdump") - .Replace("dotnet_jitdisasm", "--codegenopt:jitdisasm") - .Replace("dotnet_", "--codegenopt:") - .Replace("complus_", "--codegenopt:"); - command += keyLower + "=\"" + envVar.Value + "\" "; - } - envVars.Clear(); + command = ""; + executable = Path.Combine(clrReleaseFolder, "ilc", "ilc.exe"); - // These are needed for faster crossgen itself - they're not changing output codegen - envVars["DOTNET_TieredPGO"] = "0"; - envVars["DOTNET_ReadyToRun"] = "1"; - envVars["DOTNET_TC_QuickJitForLoops"] = "1"; - envVars["DOTNET_TC_CallCountingDelayMs"] = "0"; - envVars["DOTNET_TieredCompilation"] = "1"; - command += SettingsVm.Crossgen2Args.Replace("\r\n", " ").Replace("\n", " ") + $" \"{fileName}.dll\" "; + command += $" \"{fileName}.dll\" "; - if (SettingsVm.UseDotnetPublishForReload) - { - // Reference everything in the publish dir - command += $" -r: \"{dstFolder}\\*.dll\" "; - } - else + foreach (var envVar in envVars) + { + var keyLower = envVar.Key.ToLowerInvariant(); + if (keyLower?.StartsWith("dotnet_") == false && + keyLower?.StartsWith("complus_") == false) { - // the runtime pack we use doesn't contain corelib so let's use "checked" corelib - // TODO: build proper core_root with release version of corelib - var corelib = Path.Combine(clrCheckedFilesDir, "System.Private.CoreLib.dll"); - command += $" -r: \"{runtimePackPath}\\*.dll\" -r: \"{corelib}\" "; + continue; } - LoadingStatus = $"Executing crossgen2..."; + keyLower = keyLower + .Replace("dotnet_jitdump", "--codegenopt:jitdump") + .Replace("dotnet_jitdisasm", "--codegenopt:jitdisasm") + .Replace("dotnet_", "--codegenopt:") + .Replace("complus_", "--codegenopt:"); + command += keyLower + "=\"" + envVar.Value + "\" "; } - else if (SettingsVm.NativeAotIsSelected && SettingsVm.UseCustomRuntime) + envVars.Clear(); + command += SettingsViewModel.IlcArgs.Replace("%DOTNET_REPO%", SettingsViewModel.PathToLocalCoreClr.TrimEnd('\\', '/')).Replace("\r\n", " ").Replace("\n", " "); + + if (SettingsViewModel.UseDotnetPublishForReload) { - var (clrReleaseFolder, clrFound) = GetPathToCoreClrCheckedForNativeAot(); - if (!clrFound) - return; + // Reference everything in the publish dir + command += $" -r: \"{destinationFolder}\\*.dll\" "; + } + else + { + // The runtime pack we use doesn't contain corelib so let's use "checked" corelib. + // TODO: Build proper core_root with release version of corelib + //var corelib = Path.Combine(clrCheckedFilesDir, "System.Private.CoreLib.dll"); + //command += $" -r: \"{runtimePackPath}\\*.dll\" -r: \"{corelib}\" "; + } - command = ""; - executable = Path.Combine(clrReleaseFolder, "ilc", "ilc.exe"); + LoadingStatus = "Executing ILC... Make sure your method is not inlined and is reachable as NativeAOT runs IL Link. It might take some time..."; + } + else if (SettingsViewModel.IsNonCustomNativeAOTMode()) + { + LoadingStatus = "Compiling for NativeAOT (.NET 8.0+ is required) ..."; - command += $" \"{fileName}.dll\" "; + // For non-custom NativeAOT we need to use dotnet publish + with custom IlcArgs. + // Namely, we need to re-direct jit's output to a file (JitStdOutFile) - foreach (var envVar in envVars) - { - var keyLower = envVar.Key.ToLowerInvariant(); - if (keyLower?.StartsWith("dotnet_") == false && - keyLower?.StartsWith("complus_") == false) - { - continue; - } + var tmpJitStdout = Path.GetTempFileName() + ".asm"; - keyLower = keyLower - .Replace("dotnet_jitdump", "--codegenopt:jitdump") - .Replace("dotnet_jitdisasm", "--codegenopt:jitdisasm") - .Replace("dotnet_", "--codegenopt:") - .Replace("complus_", "--codegenopt:"); - command += keyLower + "=\"" + envVar.Value + "\" "; - } - envVars.Clear(); - command += SettingsVm.IlcArgs.Replace("%DOTNET_REPO%", SettingsVm.PathToLocalCoreClr.TrimEnd('\\', '/')).Replace("\r\n", " ").Replace("\n", " "); + envVars["DOTNET_JitStdOutFile"] = tmpJitStdout; - if (SettingsVm.UseDotnetPublishForReload) - { - // Reference everything in the publish dir - command += $" -r: \"{dstFolder}\\*.dll\" "; - } - else + var customIlcArgs = ""; + foreach (var envVar in envVars) + { + var keyLower = envVar.Key.ToLowerInvariant(); + if (keyLower?.StartsWith("dotnet_") == false && + keyLower?.StartsWith("complus_") == false) { - // the runtime pack we use doesn't contain corelib so let's use "checked" corelib - // TODO: build proper core_root with release version of corelib - //var corelib = Path.Combine(clrCheckedFilesDir, "System.Private.CoreLib.dll"); - //command += $" -r: \"{runtimePackPath}\\*.dll\" -r: \"{corelib}\" "; + continue; } - LoadingStatus = "Executing ILC... Make sure your method is not inlined and is reachable as NativeAOT runs IL Link. It might take some time..."; + keyLower = keyLower + .Replace("dotnet_", "--codegenopt:") + .Replace("complus_", "--codegenopt:"); + customIlcArgs += $"\t\t\n"; } - else if (SettingsVm.IsNonCustomNativeAOTMode()) - { - LoadingStatus = "Compiling for NativeAOT (.NET 8.0+ is required) ..."; + envVars.Clear(); - // For non-custom NativeAOT we need to use dotnet publish + with custom IlcArgs - // namely, we need to re-direct jit's output to a file (JitStdOutFile). - - var tmpJitStdout = Path.GetTempFileName() + ".asm"; - - envVars["DOTNET_JitStdOutFile"] = tmpJitStdout; + var tmpProps = Path.GetTempFileName() + ".props"; + File.WriteAllText(tmpProps, $""" + + + + $(DefineConstants);DISASMO + + + {customIlcArgs} + + + """); + + var targetFrameworkPart = SettingsViewModel.DontGuessTargetFramework && string.IsNullOrWhiteSpace(SettingsViewModel.OverridenTargetFramework) ? "" : $"-f {_currentTargetFramework}"; + + // NOTE: CustomBeforeDirectoryBuildProps is probably not a good idea to overwrite, but we need to pass IlcArgs somehow + var dotnetPublishArgs = + $"publish {targetFrameworkPart} -r win-{SettingsViewModel.Arch} -c Release" + + $" /p:PublishAot=true /p:CustomBeforeDirectoryBuildProps=\"{tmpProps}\"" + + $" /p:WarningLevel=0 /p:TreatWarningsAsErrors=false -v:q"; + + var publishResult = await ProcessUtils.RunProcess("dotnet", dotnetPublishArgs, null, Path.GetDirectoryName(_currentProjectPath), cancellationToken: UserCancellationToken); - string customIlcArgs = ""; - foreach (var envVar in envVars) - { - var keyLower = envVar.Key.ToLowerInvariant(); - if (keyLower?.StartsWith("dotnet_") == false && - keyLower?.StartsWith("complus_") == false) - { - continue; - } + ThrowIfCanceled(); - keyLower = keyLower - .Replace("dotnet_", "--codegenopt:") - .Replace("complus_", "--codegenopt:"); - customIlcArgs += $"\t\t\n"; - } - envVars.Clear(); - - var tmpProps = Path.GetTempFileName() + ".props"; - File.WriteAllText(tmpProps, $""" - - - - $(DefineConstants);DISASMO - - - {customIlcArgs} - - - """); - - string tfmPart = SettingsVm.DontGuessTFM && string.IsNullOrWhiteSpace(SettingsVm.OverridenTFM) ? "" : $"-f {_currentTf}"; - - // NOTE: CustomBeforeDirectoryBuildProps is probably not a good idea to overwrite, but we need to pass IlcArgs somehow - string dotnetPublishArgs = - $"publish {tfmPart} -r win-{SettingsViewModel.Arch} -c Release" + - $" /p:PublishAot=true /p:CustomBeforeDirectoryBuildProps=\"{tmpProps}\"" + - $" /p:WarningLevel=0 /p:TreatWarningsAsErrors=false -v:q"; - - var publishResult = await ProcessUtils.RunProcess("dotnet", dotnetPublishArgs, null, Path.GetDirectoryName(_currentProjectPath), cancellationToken: UserCt); - - ThrowIfCanceled(); - - if (string.IsNullOrEmpty(publishResult.Error)) + if (string.IsNullOrEmpty(publishResult.Error)) + { + if (!File.Exists(tmpJitStdout)) { - if (!File.Exists(tmpJitStdout)) - { - Output = $""" - JitDisasm didn't produce any output :(. Make sure your method is not inlined by the code generator - (it's a good idea to mark it as [MethodImpl(MethodImplOptions.NoInlining)]) and is reachable from Main() as - NativeAOT may delete unused methods. Also, JitDisasm doesn't work well for Main() in NativeAOT mode." - + Output = $""" + JitDisasm didn't produce any output :(. Make sure your method is not inlined by the code generator + (it's a good idea to mark it as [MethodImpl(MethodImplOptions.NoInlining)]) and is reachable from Main() as + NativeAOT may delete unused methods. Also, JitDisasm doesn't work well for Main() in NativeAOT mode." - {publishResult.Output} - """; - } - else - { - Success = true; - Output = File.ReadAllText(tmpJitStdout); - // Keep the temp files around for debugging if it failed. - // and delete them if it succeeded. - File.Delete(tmpProps); - File.Delete(tmpJitStdout); - } + {publishResult.Output} + """; } else { - Output = publishResult.Error; + Success = true; + Output = File.ReadAllText(tmpJitStdout); + + // Keep the temp files around for debugging if it failed. + // And delete them if it succeeded. + File.Delete(tmpProps); + File.Delete(tmpJitStdout); } - return; } else { - LoadingStatus = $"Executing DisasmoLoader..."; + Output = publishResult.Error; } + return; + } + else + { + LoadingStatus = $"Executing DisasmoLoader..."; + } - if (!SettingsVm.UseDotnetPublishForReload && !SettingsVm.CrossgenIsSelected && !SettingsVm.NativeAotIsSelected && SettingsVm.UseCustomRuntime) - { - var (clrCheckedFilesDir, success) = GetPathToCoreClrChecked(); - if (!success) - return; - executable = Path.Combine(clrCheckedFilesDir, "CoreRun.exe"); - } + if (!SettingsViewModel.UseDotnetPublishForReload && + !SettingsViewModel.CrossgenIsSelected && + !SettingsViewModel.NativeAotIsSelected && + SettingsViewModel.UseCustomRuntime) + { + var (clrCheckedFilesDir, success) = GetPathToCoreClrChecked(); + if (!success) + return; - if ((SettingsVm.RunAppMode) && - !string.IsNullOrWhiteSpace(SettingsVm.OverridenJitDisasm)) - { - envVars["DOTNET_JitDisasm"] = SettingsVm.OverridenJitDisasm; - } + executable = Path.Combine(clrCheckedFilesDir, "CoreRun.exe"); + } - ProcessResult result = await ProcessUtils.RunProcess( - executable, command, envVars, dstFolder, cancellationToken: UserCt); + if (SettingsViewModel.RunAppMode && + !string.IsNullOrWhiteSpace(SettingsViewModel.OverridenJitDisasm)) + { + envVars["DOTNET_JitDisasm"] = SettingsViewModel.OverridenJitDisasm; + } - ThrowIfCanceled(); + var result = await ProcessUtils.RunProcess(executable, command, envVars, destinationFolder, cancellationToken: UserCancellationToken); + ThrowIfCanceled(); - if (string.IsNullOrEmpty(result.Error)) - { - Success = true; - Output = PreprocessOutput(result.Output); - } - else + if (string.IsNullOrEmpty(result.Error)) + { + Success = true; + Output = PreprocessOutput(result.Output); + } + else + { + Output = result.Output + "\nERROR:\n" + result.Error; + } + + if (SettingsViewModel.FlowgraphEnable && SettingsViewModel.JitDumpInsteadOfDisasm) + { + currentFlowgraphFile += ".dot"; + if (!File.Exists(currentFlowgraphFile)) { - Output = result.Output + "\nERROR:\n" + result.Error; + Output = $"Oops, JitDumpFgFile ('{currentFlowgraphFile}') doesn't exist :(\nInvalid Phase name?"; + return; } - if (SettingsVm.FgEnable && SettingsVm.JitDumpInsteadOfDisasm) + if (new FileInfo(currentFlowgraphFile).Length == 0) { - currentFgFile += ".dot"; - if (!File.Exists(currentFgFile)) - { - Output = $"Oops, JitDumpFgFile ('{currentFgFile}') doesn't exist :(\nInvalid Phase name?"; - return; - } - - if (new FileInfo(currentFgFile).Length == 0) - { - Output = $"Oops, JitDumpFgFile ('{currentFgFile}') file is empty :(\nInvalid Phase name?"; - return; - } + Output = $"Oops, JitDumpFgFile ('{currentFlowgraphFile}') file is empty :(\nInvalid Phase name?"; + return; + } - string fgLines = File.ReadAllText(currentFgFile); + var flowGraphLines = File.ReadAllText(currentFlowgraphFile); - FgPhases.Clear(); - var graphs = fgLines.Split(new [] {"digraph FlowGraph {"}, StringSplitOptions.RemoveEmptyEntries); - int graphIndex = 0; + FlowgraphPhases.Clear(); + var graphs = flowGraphLines.Split(["digraph FlowGraph {"], StringSplitOptions.RemoveEmptyEntries); + var graphIndex = 0; + var absoluteGraphIndex = 0; - var fgBaseDir = Path.Combine(Path.GetTempPath(), "Disasmo", "Flowgraphs", Guid.NewGuid().ToString("N")); - Directory.CreateDirectory(fgBaseDir); - foreach (var graph in graphs) + var flowgraphBaseDirectory = Path.Combine(Path.GetTempPath(), "Disasmo", "Flowgraphs", Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(flowgraphBaseDirectory); + foreach (var graph in graphs) + { + try { - try - { - var name = graph.Substring(graph.IndexOf("graph [label = ") + "graph [label = ".Length); - name = name.Substring(0, name.IndexOf("\"];")); - name = name.Replace("\\n", " "); - name = name.Substring(name.IndexOf(" after ") + " after ".Length).Trim(); + var name = graph.Substring(graph.IndexOf("graph [label = ") + "graph [label = ".Length); + name = name.Substring(0, name.IndexOf("\"];")); + name = name.Replace("\\n", " "); + name = name.Substring(name.IndexOf(" after ") + " after ".Length).Trim(); - // Reset counter if tier0 and tier1 are merged together - if (name == "Pre-import") - { - graphIndex = 0; - } + // Reset counter if tier0 and tier1 are merged together + if (name == "Pre-import") + { + graphIndex = 0; + } - name = (++graphIndex) + ". " + name; + graphIndex++; + absoluteGraphIndex++; - // Ignore invalid path chars - name = Path.GetInvalidFileNameChars().Aggregate(name, (current, ic) => current.Replace(ic, '_')); + // Ignore invalid path chars + name = Path.GetInvalidFileNameChars().Aggregate(name, (current, ic) => current.Replace(ic, '_')); - var dotPath = Path.Combine(fgBaseDir, $"{name}.dot"); - File.WriteAllText(dotPath, "digraph FlowGraph {\n" + graph); + var identifier = absoluteGraphIndex + ". " + name; + var dotPath = Path.Combine(flowgraphBaseDirectory, $"{identifier}.dot"); + File.WriteAllText(dotPath, "digraph FlowGraph {\n" + graph); - FgPhases.Add(new FlowgraphItemViewModel(SettingsVm) { Name = name, DotFileUrl = dotPath, ImageUrl = "" }); - } - catch (Exception exc) - { - Debug.WriteLine(exc); - } + var itemViewModel = new FlowgraphItemViewModel(this, SettingsViewModel, graphIndex.ToString(), name, dotPath, string.Empty); + FlowgraphPhases.Add(itemViewModel); + } + catch (Exception ex) + { + Debug.WriteLine(ex); } } - - } - catch (OperationCanceledException e) - { - Output = e.Message; - } - catch (Exception e) - { - Output = e.ToString(); - } - finally - { - IsLoading = false; } } - - private string PreprocessOutput(string output) + catch (OperationCanceledException ex) { - if (SettingsVm.JitDumpInsteadOfDisasm || SettingsVm.PrintInlinees) - return output; - return DisassemblyPrettifier.Prettify(output, !SettingsVm.ShowAsmComments && !SettingsVm.RunAppMode); + Output = ex.Message; } - - private UnconfiguredProject GetUnconfiguredProject(Project project) + catch (Exception ex) { - var context = project as IVsBrowseObjectContext; - if (context == null && project != null) - context = project.Object as IVsBrowseObjectContext; - - return context?.UnconfiguredProject; + Output = ex.ToString(); } + finally + { + IsLoading = false; + } + } + + private string PreprocessOutput(string output) + { + if (SettingsViewModel.JitDumpInsteadOfDisasm || SettingsViewModel.PrintInlinees) + return output; + + return DisassemblyPrettifier.Prettify(output, minimalComments: !SettingsViewModel.ShowAsmComments, isInRunMode: SettingsViewModel.RunAppMode); + } - private (string, bool) GetPathToRuntimePack() + private UnconfiguredProject GetUnconfiguredProject(Project project) + { + var context = project as IVsBrowseObjectContext; + if (context is null && project is not null) + context = project.Object as IVsBrowseObjectContext; + + return context?.UnconfiguredProject; + } + + private (string, bool) GetPathToRuntimePack() + { + var arch = SettingsViewModel.Arch; + var (_, success) = GetPathToCoreClrChecked(); + if (!success) + return (null, false); + + var runtimePacksPath = Path.Combine(SettingsViewModel.PathToLocalCoreClr, @"artifacts\bin\runtime"); + string runtimePackPath = null; + if (Directory.Exists(runtimePacksPath)) { - var arch = SettingsViewModel.Arch; - var (_, success) = GetPathToCoreClrChecked(); - if (!success) - return (null, false); + var packs = Directory.GetDirectories(runtimePacksPath, "*-windows-Release-" + arch); + runtimePackPath = packs.OrderByDescending(i => i).FirstOrDefault(); + } - string runtimePacksPath = Path.Combine(SettingsVm.PathToLocalCoreClr, @"artifacts\bin\runtime"); - string runtimePackPath = null; - if (Directory.Exists(runtimePacksPath)) - { - var packs = Directory.GetDirectories(runtimePacksPath, "*-windows-Release-" + arch); - runtimePackPath = packs.OrderByDescending(i => i).FirstOrDefault(); - } + if (!Directory.Exists(runtimePackPath)) + { + Output = "Please, build a runtime-pack in your local repo:\n\n" + + $"Run 'build.cmd Clr+Clr.Aot+Libs -c Release -a {arch}' in the repo root\n" + + "Don't worry, you won't have to re-build it every time you change something in jit, vm or corelib."; - if (!Directory.Exists(runtimePackPath)) - { - Output = "Please, build a runtime-pack in your local repo:\n\n" + - $"Run 'build.cmd Clr+Clr.Aot+Libs -c Release -a {arch}' in the repo root\n" + - "Don't worry, you won't have to re-build it every time you change something in jit, vm or corelib."; - return (null, false); - } - return (runtimePackPath, true); + return (null, false); } - private (string, bool) GetPathToCoreClrChecked() + return (runtimePackPath, true); + } + + private (string, bool) GetPathToCoreClrChecked() + { + var arch = SettingsViewModel.Arch; + var clrCheckedFilesDirectory = FindJitDirectory(SettingsViewModel.PathToLocalCoreClr, arch); + if (string.IsNullOrWhiteSpace(clrCheckedFilesDirectory)) { - var arch = SettingsViewModel.Arch; - var clrCheckedFilesDir = FindJitDirectory(SettingsVm.PathToLocalCoreClr, arch); - if (string.IsNullOrWhiteSpace(clrCheckedFilesDir)) - { - Output = $"Path to a local dotnet/runtime repository is either not set or it's not built for {arch} arch yet" + - (SettingsVm.CrossgenIsSelected ? "\n(When you use crossgen and target e.g. arm64 you need coreclr built for that arch)" : "") + - "\nPlease clone it and build it in `Checked` mode, e.g.:\n\n" + - "git clone git@github.com:dotnet/runtime.git\n" + - "cd runtime\n" + - $"build.cmd Clr+Clr.Aot+Libs -c Release -rc Checked -a {arch}\n\n"; - return (null, false); - } - return (clrCheckedFilesDir, true); + Output = $"Path to a local dotnet/runtime repository is either not set or it's not built for {arch} arch yet" + + (SettingsViewModel.CrossgenIsSelected ? "\n(When you use crossgen and target e.g. arm64 you need coreclr built for that arch)" : "") + + "\nPlease clone it and build it in `Checked` mode, e.g.:\n\n" + + "git clone git@github.com:dotnet/runtime.git\n" + + "cd runtime\n" + + $"build.cmd Clr+Clr.Aot+Libs -c Release -rc Checked -a {arch}\n\n"; + + return (null, false); } + return (clrCheckedFilesDirectory, true); + } + - private (string, bool) GetPathToCoreClrCheckedForNativeAot() + private (string, bool) GetPathToCoreClrCheckedForNativeAot() + { + var arch = SettingsViewModel.Arch; + var releaseFolder = Path.Combine(SettingsViewModel.PathToLocalCoreClr, "artifacts", "bin", "coreclr", $"windows.{arch}.Checked"); + if (!Directory.Exists(releaseFolder) || !Directory.Exists(Path.Combine(releaseFolder, "aotsdk")) || !Directory.Exists(Path.Combine(releaseFolder, "ilc"))) { - var arch = SettingsViewModel.Arch; - var releaseFolder = Path.Combine(SettingsVm.PathToLocalCoreClr, "artifacts", "bin", "coreclr", $"windows.{arch}.Checked"); - if (!Directory.Exists(releaseFolder) || !Directory.Exists(Path.Combine(releaseFolder, "aotsdk")) || !Directory.Exists(Path.Combine(releaseFolder, "ilc"))) - { - Output = $"Path to a local dotnet/runtime repository is either not set or it's not correctly built for {arch} arch yet for NativeAOT" + - "\nPlease clone it and build it using the following steps.:\n\n" + - "git clone git@github.com:dotnet/runtime.git\n" + - "cd runtime\n" + - $"build.cmd Clr+Clr.Aot+Libs -c Release -rc Checked -a {arch}\n\n"; - return (null, false); - } - return (releaseFolder, true); + Output = $"Path to a local dotnet/runtime repository is either not set or it's not correctly built for {arch} arch yet for NativeAOT" + + "\nPlease clone it and build it using the following steps.:\n\n" + + "git clone git@github.com:dotnet/runtime.git\n" + + "cd runtime\n" + + $"build.cmd Clr+Clr.Aot+Libs -c Release -rc Checked -a {arch}\n\n"; + + return (null, false); } - public async void RunOperationAsync(ISymbol symbol, CAProject project) + return (releaseFolder, true); + } + + public async Task RunOperationAsync(ISymbol symbol, CAProject project) + { + var stopwatch = Stopwatch.StartNew(); + var dte = IdeUtils.DTE(); + + // It's possible that the last modified C# document is not active (e.g. Disasmo itself is in the focus) + // So we have no choice but to run Save() for all the opened documents + dte.SaveAllDocuments(); + + try { - var stopwatch = Stopwatch.StartNew(); - DTE dte = IdeUtils.DTE(); + IsLoading = true; + FlowgraphPngPath = null; + MainPageRequested?.Invoke(); + Success = false; + _currentSymbol = symbol; + _currentProject = project; + Output = ""; + + if (symbol is null) + { + Output = "Symbol is not recognized, put cursor on a function/class name"; + return; + } + + string clrCheckedFilesDirectory = null; + if (SettingsViewModel.UseCustomRuntime) + { + var (dir, success) = GetPathToCoreClrChecked(); + if (!success) + return; - // It's possible that the last modified C# document is not active (e.g. Disasmo itself is in the focus) - // so we have no choice but to run Save() for all the opened documents - dte.SaveAllDocuments(); + clrCheckedFilesDirectory = dir; + } - try + if (symbol is IMethodSymbol { IsGenericMethod: true } && !SettingsViewModel.RunAppMode) { - IsLoading = true; - FgPngPath = null; - MainPageRequested?.Invoke(); - Success = false; - _currentSymbol = symbol; - _currentProject = project; - Output = ""; + // TODO: Ask user to specify type parameters + Output = "Generic methods are only supported in 'Run' mode"; + return; + } - if (symbol == null) - { - Output = "Symbol is not recognized, put cursor on a function/class name"; - return; - } - - string clrCheckedFilesDir = null; - if (SettingsVm.UseCustomRuntime) - { - var (dir, success) = GetPathToCoreClrChecked(); - if (!success) - return; - clrCheckedFilesDir = dir; - } + ThrowIfCanceled(); - if (symbol is IMethodSymbol { IsGenericMethod: true } && !SettingsVm.RunAppMode) - { - // TODO: ask user to specify type parameters - Output = "Generic methods are only supported in 'Run' mode"; - return; - } + // Find Release-{SettingsViewModel.Arch} configuration: + var currentProject = dte.GetActiveProject(project.FilePath); + if (currentProject is null) + { + Output = "There no active project. Please re-open solution."; + return; + } - ThrowIfCanceled(); + await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); + _currentProjectPath = currentProject.FileName; - // Find Release-{SettingsViewModel.Arch} configuration: - Project currentProject = dte.GetActiveProject(project.FilePath); - if (currentProject is null) - { - Output = "There no active project. Please re-open solution."; - return; - } + var unconfiguredProject = GetUnconfiguredProject(currentProject); + // Find all configurations, ordered by version descending + var projectConfigurations = (await IdeUtils.GetProjectConfigurationsAsync(unconfiguredProject)) + .OrderByDescending(IdeUtils.GetTargetFrameworkVersionDimension) + .AsEnumerable(); - await DisasmoPackage.Current.JoinableTaskFactory.SwitchToMainThreadAsync(); - _currentProjectPath = currentProject.FileName; - - UnconfiguredProject unconfiguredProject = GetUnconfiguredProject(currentProject); - // find all configurations, ordered by version descending - var projectConfigurations = (await IdeUtils.GetProjectConfigurations(unconfiguredProject)) - .OrderByDescending(IdeUtils.GetTargetFrameworkVersionDimension) - .ToList(); - // filter Release configurations - var releaseConfigurations = projectConfigurations - .Where(cfg => string.Equals(IdeUtils.GetConfigurationDimension(cfg), "Release", StringComparison.OrdinalIgnoreCase)) - .ToList(); - // Use Release configurations only if we have any - if (releaseConfigurations.Any()) - { - projectConfigurations = releaseConfigurations; - } + // Filter Release configurations + var releaseConfigurations = projectConfigurations + .Where(cfg => string.Equals(IdeUtils.GetConfigurationDimension(cfg), "Release", StringComparison.OrdinalIgnoreCase)); - ProjectConfiguration projectConfiguration; - if (string.IsNullOrWhiteSpace(SettingsVm.OverridenTFM)) + // Use Release configurations only if we have any + if (releaseConfigurations.Any()) + { + projectConfigurations = releaseConfigurations; + } + + ProjectConfiguration projectConfiguration; + if (string.IsNullOrWhiteSpace(SettingsViewModel.OverridenTargetFramework)) + { + // Choose first (highest) + projectConfiguration = projectConfigurations.FirstOrDefault(); + // Resolve later + _currentTargetFramework = null; + } + else + { + // No validation in this case + _currentTargetFramework = SettingsViewModel.OverridenTargetFramework.Trim(); + var currentTfmVersion = TfmVersion.Parse(_currentTargetFramework); + + // Find the best suitable project configuration + projectConfiguration = projectConfigurations + .FirstOrDefault(cfg => currentTfmVersion is not null && currentTfmVersion.CompareTo(IdeUtils.GetTargetFrameworkVersionDimension(cfg)) >= 0) + ?? projectConfigurations.FirstOrDefault(); + } + + var projectProperties = await IdeUtils.GetProjectPropertiesAsync(unconfiguredProject, projectConfiguration); + ThrowIfCanceled(); + + // Resolve target framework + if (_currentTargetFramework is null) + { + int? major; + if (projectProperties is not null) { - // choose first (highest) - projectConfiguration = projectConfigurations.FirstOrDefault(); - // resolve later - _currentTf = null; + _currentTargetFramework = await projectProperties.GetEvaluatedPropertyValueAsync("TargetFramework"); + major = TfmVersion.Parse(_currentTargetFramework)?.Major; } else { - // No validation in this case - _currentTf = SettingsVm.OverridenTFM.Trim(); - var currentTfmVersion = TfmVersion.Parse(_currentTf); - // find the best suitable project configuration - projectConfiguration = projectConfigurations - .FirstOrDefault(cfg => currentTfmVersion != null && currentTfmVersion.CompareTo(IdeUtils.GetTargetFrameworkVersionDimension(cfg)) >= 0) - ?? projectConfigurations.FirstOrDefault(); + // Fallback to net 7.0 + _currentTargetFramework = "net7.0"; + major = 7; } - IProjectProperties projectProperties = await IdeUtils.GetProjectProperties(unconfiguredProject, projectConfiguration); ThrowIfCanceled(); - // resolve target framework - if (_currentTf == null) + if (major >= 6) { - int? major; - if (projectProperties != null) - { - _currentTf = await projectProperties.GetEvaluatedPropertyValueAsync("TargetFramework"); - major = TfmVersion.Parse(_currentTf)?.Major; - } - else + if (!SettingsViewModel.UseCustomRuntime && major < 7) { - // fallback to net 7.0 - _currentTf = "net7.0"; - major = 7; - } + Output = + "Only net7.0 (and newer) apps are supported with non-locally built dotnet/runtime.\n" + + "Make sure net7.0 is set in your csproj."; - ThrowIfCanceled(); - - if (major >= 6) - { - if (!SettingsVm.UseCustomRuntime && major < 7) - { - Output = - "Only net7.0 (and newer) apps are supported with non-locally built dotnet/runtime.\nMake sure net7.0 is set in your csproj."; - return; - } - } - else - { - Output = - "Only net6.0 (and newer) apps are supported.\nMake sure net6.0 is set in your csproj."; return; } } - - ThrowIfCanceled(); - - if (SettingsVm.RunAppMode && SettingsVm.UseDotnetPublishForReload) + else { - // TODO: fix this - Output = "\"Run current app\" mode only works with \"dotnet build\" reload strategy, see Options tab."; + Output = + "Only net6.0 (and newer) apps are supported.\n" + + "Make sure net6.0 is set in your csproj."; + return; } + } - // Validation for Flowgraph tab - if (SettingsVm.FgEnable) + ThrowIfCanceled(); + + if (SettingsViewModel.RunAppMode && SettingsViewModel.UseDotnetPublishForReload) + { + // TODO: Fix this + Output = "\"Run current app\" mode only works with \"dotnet build\" reload strategy, see Options tab."; + return; + } + + // Validation for Flowgraph tab + if (SettingsViewModel.FlowgraphEnable) + { + if (string.IsNullOrWhiteSpace(SettingsViewModel.GraphvisDotPath) || + !File.Exists(SettingsViewModel.GraphvisDotPath)) { - if (string.IsNullOrWhiteSpace(SettingsVm.GraphvisDotPath) || - !File.Exists(SettingsVm.GraphvisDotPath)) - { - Output = "Graphvis is not installed or path to dot.exe is incorrect, see 'Settings' tab.\nGraphvis can be installed from https://graphviz.org/download/"; - return; - } + Output = + "Graphvis is not installed or path to dot.exe is incorrect, see 'Settings' tab.\n" + + "Graphvis can be installed from https://graphviz.org/download/"; - if (!SettingsVm.JitDumpInsteadOfDisasm) - { - Output = "Either disable flowgraphs in the 'Flowgraph' tab or enable JitDump."; - return; - } + return; } - if (SettingsVm.CrossgenIsSelected || SettingsVm.NativeAotIsSelected) + if (!SettingsViewModel.JitDumpInsteadOfDisasm) { - if (SettingsVm.UsePGO) - { - Output = "PGO has no effect on R2R'd/NativeAOT code."; - return; - } - - if (SettingsVm.RunAppMode) - { - Output = "Run mode is not supported for crossgen/NativeAOT"; - return; - } + Output = "Either disable flowgraphs in the 'Flowgraph' tab or enable JitDump."; + return; + } + } - if (SettingsVm.UseTieredJit) - { - Output = "TieredJIT has no effect on R2R'd/NativeAOT code."; - return; - } + if (SettingsViewModel.CrossgenIsSelected || SettingsViewModel.NativeAotIsSelected) + { + if (SettingsViewModel.UsePGO) + { + Output = "PGO has no effect on R2R'd/NativeAOT code."; + return; + } - if (SettingsVm.FgEnable) - { - Output = "Flowgraphs are not tested with crossgen2/NativeAOT yet (in Disasmo)"; - return; - } + if (SettingsViewModel.RunAppMode) + { + Output = "Run mode is not supported for crossgen/NativeAOT"; + return; } - string outputDir = projectProperties == null ? "bin" : await projectProperties.GetEvaluatedPropertyValueAsync("OutputPath"); - DisasmoOutDir = Path.Combine(outputDir, DisasmoFolder + (SettingsVm.UseDotnetPublishForReload ? "_published" : "")); - string currentProjectDirPath = Path.GetDirectoryName(_currentProjectPath); + if (SettingsViewModel.UseTieredJit) + { + Output = "TieredJIT has no effect on R2R'd/NativeAOT code."; + return; + } - if (SettingsVm.IsNonCustomDotnetAotMode()) + if (SettingsViewModel.FlowgraphEnable) { - ThrowIfCanceled(); - var symbolInfo = SymbolUtils.FromSymbol(_currentSymbol); - await RunFinalExe(symbolInfo, projectProperties); + Output = "Flowgraphs are not tested with crossgen2/NativeAOT yet (in Disasmo)"; return; } + } - string tfmPart = SettingsVm.DontGuessTFM && string.IsNullOrWhiteSpace(SettingsVm.OverridenTFM) ? "" : $"-f {_currentTf}"; + var outputDirectory = projectProperties is null ? "bin" : await projectProperties.GetEvaluatedPropertyValueAsync("OutputPath"); + DisasmoOutputDirectory = Path.Combine(outputDirectory, DisasmoFolder + (SettingsViewModel.UseDotnetPublishForReload ? "_published" : "")); + var currentProjectDirPath = Path.GetDirectoryName(_currentProjectPath); - // Some things can't be set in CLI e.g. appending to DefineConstants - var tmpProps = Path.GetTempFileName() + ".props"; - File.WriteAllText(tmpProps, $""" - - - - $(DefineConstants);DISASMO - - - """); - - ProcessResult publishResult; - if (SettingsVm.UseDotnetPublishForReload) - { - LoadingStatus = $"dotnet publish -r win-{SettingsViewModel.Arch} -c Release -o ..."; + if (SettingsViewModel.IsNonCustomDotnetAotMode()) + { + ThrowIfCanceled(); + var symbolInfo = SymbolUtils.FromSymbol(_currentSymbol); + await RunFinalExeAsync(symbolInfo, projectProperties); + return; + } - string dotnetPublishArgs = - $"publish {tfmPart} -r win-{SettingsViewModel.Arch} -c Release -o {DisasmoOutDir} --self-contained true /p:PublishTrimmed=false /p:PublishSingleFile=false /p:CustomBeforeDirectoryBuildProps=\"{tmpProps}\" /p:WarningLevel=0 /p:TreatWarningsAsErrors=false -v:q"; + var targetFrameworkPart = SettingsViewModel.DontGuessTargetFramework && string.IsNullOrWhiteSpace(SettingsViewModel.OverridenTargetFramework) + ? "" + : $"-f {_currentTargetFramework}"; + + // Some things can't be set in CLI e.g. appending to DefineConstants + var tempProperties = Path.GetTempFileName() + ".props"; + File.WriteAllText(tempProperties, $""" + + + + $(DefineConstants);DISASMO + + + """); + + ProcessResult publishResult; + if (SettingsViewModel.UseDotnetPublishForReload) + { + LoadingStatus = $"dotnet publish -r win-{SettingsViewModel.Arch} -c Release -o ..."; + + var dotnetPublishArgs = $"publish {targetFrameworkPart} -r win-{SettingsViewModel.Arch} -c Release -o {DisasmoOutputDirectory} --self-contained true /p:PublishTrimmed=false /p:PublishSingleFile=false /p:CustomBeforeDirectoryBuildProps=\"{tempProperties}\" /p:WarningLevel=0 /p:TreatWarningsAsErrors=false -v:q"; - publishResult = await ProcessUtils.RunProcess("dotnet", dotnetPublishArgs, null, currentProjectDirPath, cancellationToken: UserCt); + publishResult = await ProcessUtils.RunProcess("dotnet", dotnetPublishArgs, null, currentProjectDirPath, cancellationToken: UserCancellationToken); + } + else + { + if (SettingsViewModel.UseCustomRuntime) + { + var (_, rpSuccess) = GetPathToRuntimePack(); + if (!rpSuccess) + return; } - else + + LoadingStatus = "dotnet build -c Release -o ..."; + + var dotnetBuildArgs = $"build {targetFrameworkPart} -c Release -o {DisasmoOutputDirectory} --no-self-contained " + + "/p:RuntimeIdentifier=\"\" " + + "/p:RuntimeIdentifiers=\"\" " + + "/p:WarningLevel=0 " + + $"/p:CustomBeforeDirectoryBuildProps=\"{tempProperties}\" " + + $"/p:TreatWarningsAsErrors=false \"{_currentProjectPath}\""; + + var fasterBuildEnvVars = new Dictionary { - if (SettingsVm.UseCustomRuntime) - { - var (_, rpSuccess) = GetPathToRuntimePack(); - if (!rpSuccess) - return; - } + ["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "1", + ["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1" + }; - LoadingStatus = "dotnet build -c Release -o ..."; + if (SettingsViewModel.UseNoRestoreFlag) + { + dotnetBuildArgs += " --no-restore --no-dependencies --nologo"; + fasterBuildEnvVars["DOTNET_MULTILEVEL_LOOKUP"] = "0"; + } - string dotnetBuildArgs = $"build {tfmPart} -c Release -o {DisasmoOutDir} --no-self-contained " + - "/p:RuntimeIdentifier=\"\" " + - "/p:RuntimeIdentifiers=\"\" " + - "/p:WarningLevel=0 " + - $"/p:CustomBeforeDirectoryBuildProps=\"{tmpProps}\" " + - $"/p:TreatWarningsAsErrors=false \"{_currentProjectPath}\""; + publishResult = await ProcessUtils.RunProcess( + "dotnet", + dotnetBuildArgs, + fasterBuildEnvVars, + currentProjectDirPath, + cancellationToken: UserCancellationToken); + } - Dictionary fasterBuildEnvVars = new Dictionary - { - ["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "1", - ["DOTNET_CLI_TELEMETRY_OPTOUT"] = "1" - }; + File.Delete(tempProperties); + ThrowIfCanceled(); - if (SettingsVm.UseNoRestoreFlag) - { - dotnetBuildArgs += " --no-restore --no-dependencies --nologo"; - fasterBuildEnvVars["DOTNET_MULTILEVEL_LOOKUP"] = "0"; - } + if (!string.IsNullOrEmpty(publishResult.Error)) + { + Output = publishResult.Error; + return; + } - publishResult = await ProcessUtils.RunProcess("dotnet", dotnetBuildArgs, fasterBuildEnvVars, - currentProjectDirPath, - cancellationToken: UserCt); - } + // In case if there are compilation errors: + if (publishResult.Output.Contains(": error")) + { + Output = publishResult.Output; + return; + } - File.Delete(tmpProps); - ThrowIfCanceled(); + if (SettingsViewModel.UseDotnetPublishForReload && SettingsViewModel.UseCustomRuntime) + { + LoadingStatus = "Copying files from locally built CoreCLR"; - if (!string.IsNullOrEmpty(publishResult.Error)) + var destinationFolder = DisasmoOutputDirectory; + if (!Path.IsPathRooted(destinationFolder)) { - Output = publishResult.Error; - return; + destinationFolder = Path.Combine(currentProjectDirPath, destinationFolder); } - // in case if there are compilation errors: - if (publishResult.Output.Contains(": error")) + if (!Directory.Exists(destinationFolder)) { - Output = publishResult.Output; + Output = $"Something went wrong, {destinationFolder} doesn't exist after 'dotnet publish -r win-{SettingsViewModel.Arch} -c Release' step"; return; } - if (SettingsVm.UseDotnetPublishForReload && SettingsVm.UseCustomRuntime) - { - LoadingStatus = "Copying files from locally built CoreCLR"; + var copyClrFilesResult = await ProcessUtils.RunProcess("robocopy", $"/e \"{clrCheckedFilesDirectory}\" \"{destinationFolder}", null, cancellationToken: UserCancellationToken); - string dstFolder = DisasmoOutDir; - if (!Path.IsPathRooted(dstFolder)) - dstFolder = Path.Combine(currentProjectDirPath, DisasmoOutDir); - if (!Directory.Exists(dstFolder)) - { - Output = - $"Something went wrong, {dstFolder} doesn't exist after 'dotnet publish -r win-{SettingsViewModel.Arch} -c Release' step"; - return; - } + if (!string.IsNullOrEmpty(copyClrFilesResult.Error)) + { + Output = copyClrFilesResult.Error; + return; + } + } - var copyClrFilesResult = await ProcessUtils.RunProcess("robocopy", - $"/e \"{clrCheckedFilesDir}\" \"{dstFolder}", null, cancellationToken: UserCt); + ThrowIfCanceled(); + var finalSymbolInfo = SymbolUtils.FromSymbol(_currentSymbol); + await RunFinalExeAsync(finalSymbolInfo, projectProperties); + } + catch (OperationCanceledException ex) + { + Output = ex.Message; + } + catch (Exception ex) + { + Output = ex.ToString(); + } + finally + { + IsLoading = false; + stopwatch.Stop(); + StopwatchStatus = $"Disasm took {stopwatch.Elapsed.TotalSeconds:F1}s"; + } + } - if (!string.IsNullOrEmpty(copyClrFilesResult.Error)) - { - Output = copyClrFilesResult.Error; - return; - } - } + public bool CanLoadUninitializedPhase => MaxCountOfLoadingPhases > _countOfLoadingPhases; - ThrowIfCanceled(); - var finalSymbolInfo = SymbolUtils.FromSymbol(_currentSymbol); - await RunFinalExe(finalSymbolInfo, projectProperties); - } - catch (OperationCanceledException e) - { - Output = e.Message; - } - catch (Exception e) + private void TryLoadSelectedPhaseImage() + { + if (!_selectedPhase.IsInitialized) + { + if (CanLoadUninitializedPhase) { - Output = e.ToString(); + _ = _selectedPhase.LoadImageAsync(UserCancellationToken); } - finally + else { - IsLoading = false; - stopwatch.Stop(); - StopwatchStatus = $"Disasm took {stopwatch.Elapsed.TotalSeconds:F1} s."; + UpdatePhaseLoadingStatus(hasReachedTheLimit: true); + return; } } - private static string FindJitDirectory(string basePath, string arch) + UpdatePhaseLoadingStatus(); + } + + public void NotifyFlowgraphPhaseLoadingStarted() + { + Interlocked.Increment(ref _countOfLoadingPhases); + UpdatePhaseLoadingStatus(); + } + + public void NotifyFlowgraphPhaseLoadingFinished() + { + Interlocked.Decrement(ref _countOfLoadingPhases); + UpdatePhaseLoadingStatus(); + TryLoadSelectedPhaseImage(); // In case selected phase image load was abandoned due to the limit + } + + private void UpdatePhaseLoadingStatus(bool hasReachedTheLimit = false) + { + IsPhasesLoading = !_selectedPhase.IsInitialized; + + if (!IsPhasesLoading) + return; + + if (!_selectedPhase.IsBusy && hasReachedTheLimit) { - string jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{arch}.Checked"); - if (Directory.Exists(jitDir)) - { - return jitDir; - } + PhaseLoadingPhrase = + $"Cannot load this phase, because the limit" + "\n" + + $"in {MaxCountOfLoadingPhases} simultaneously loaded phases" + "\n" + + $"has been reached." + "\n\n" + + $"Wait for the previous phases to load."; - jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{arch}.Debug"); - if (Directory.Exists(jitDir)) - { - return jitDir; - } - return null; + return; } + + PhaseLoadingPhrase = $"Loading...\nQueue: {_countOfLoadingPhases} of {MaxCountOfLoadingPhases} phases"; + } + + private static string FindJitDirectory(string basePath, string arch) + { + var jitDirectory = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{arch}.Checked"); + if (Directory.Exists(jitDirectory)) + return jitDirectory; + + jitDirectory = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{arch}.Debug"); + if (Directory.Exists(jitDirectory)) + return jitDirectory; + + return null; } } \ No newline at end of file diff --git a/src/Vsix/ViewModels/SettingsViewModel.cs b/src/Vsix/ViewModels/SettingsViewModel.cs index 073b4c6..49fc1a3 100644 --- a/src/Vsix/ViewModels/SettingsViewModel.cs +++ b/src/Vsix/ViewModels/SettingsViewModel.cs @@ -3,507 +3,472 @@ using System.Collections.ObjectModel; using System.IO; using System.Linq; +using System.Threading; using System.Windows.Forms; using System.Windows.Input; using Disasmo.Properties; using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.CommandWpf; -namespace Disasmo +namespace Disasmo; + +public class SettingsViewModel : ViewModelBase { - public class SettingsViewModel : ViewModelBase + private string _pathToLocalCoreClr; + private bool _jitDumpInsteadOfDisasm; + private string _customEnvVars; + private string _crossgen2Args; + private string _ilcArgs; + private bool _showAsmComments; + private bool _useDotnetPublishForReload; + private bool _useDotnetBuildForReload; + private bool _runAppMode; + private bool _printInlinees; + private double _fontSize; + private bool _useNoRestoreFlag; + private bool _disableLightBulb; + private bool _useTieredJit; + private bool _useUnloadableContext; + private bool _usePGO; + private bool _diffable; + private bool _dontGuessTargetFramework; + private bool _useCustomRuntime; + private ObservableCollection _customJits; + private string _selectedCustomJit; + private string _graphvisDot; + private string _overridenJitDisasm; + private bool _flowgraphEnable; + private string _overridenTargetFramework; + + public SettingsViewModel() { - private string _pathToLocalCoreClr; - private bool _jitDumpInsteadOfDisasm; - private string _customEnvVars; - private string _crossgen2Args; - private string _ilcArgs; - private bool _showAsmComments; - private bool _updateIsAvailable; - private Version _currentVersion; - private Version _availableVersion; - private bool _useDotnetPublishForReload; - private bool _useDotnetBuildForReload; - private bool _runAppMode; - private bool _printInlinees; - private double _fontSize; - private bool _useNoRestoreFlag; - private bool _disableLightBulb; - private bool _useTieredJit; - private bool _useUnloadableContext; - private bool _usePGO; - private bool _diffable; - private bool _dontGuessTFM; - private bool _useCustomRuntime; - private ObservableCollection _customJits; - private string _selectedCustomJit; - private string _graphvisDot; - private string _overridenJitDisasm; - private bool _fgEnable; - private string _overridenTfm; - - public SettingsViewModel() - { - PathToLocalCoreClr = Settings.Default.PathToCoreCLR_V9; - ShowAsmComments = Settings.Default.ShowAsmComments_V9; - CustomEnvVars = Settings.Default.CustomEnvVars3_V15.Replace(";;", Environment.NewLine); - Crossgen2Args = Settings.Default.CrossgenArgs_V6; - IlcArgs = Settings.Default.IlcArgs_V9.Replace(";;", Environment.NewLine); - JitDumpInsteadOfDisasm = Settings.Default.JitDumpInsteadOfDisasm_V9; - UseDotnetBuildForReload = Settings.Default.UseDotnetBuildForReload_V9; - RunAppMode = Settings.Default.RunAppMode_V9; - UseNoRestoreFlag = Settings.Default.UseNoRestoreFlag_V9; - FontSize = Settings.Default.FontSize; - UpdateIsAvailable = false; - UseTieredJit = Settings.Default.UseTieredJit_V4; - UseCustomRuntime = Settings.Default.UseCustomRuntime_V4; - GraphvisDotPath = Settings.Default.GraphvisDotPath; - FgEnable = Settings.Default.FgEnable; - PrintInlinees = Settings.Default.PrintInlinees_V3; - UsePGO = Settings.Default.UsePGO; - Diffable = Settings.Default.Diffable; - UseUnloadableContext = Settings.Default.UseUnloadableContext; - DisableLightBulb = Settings.Default.DisableLightBulb; - DontGuessTFM = Settings.Default.DontGuessTFM; - OverridenTFM = Settings.Default.OverridenTFM; - CheckUpdates(); - } - - private async void CheckUpdates() - { - CurrentVersion = DisasmoPackage.Current?.GetCurrentVersion(); - AvailableVersion = await DisasmoPackage.GetLatestVersionOnline(); - if (CurrentVersion != null && AvailableVersion > CurrentVersion) - UpdateIsAvailable = true; - } + PathToLocalCoreClr = Settings.Default.PathToCoreCLR_V9; + ShowAsmComments = Settings.Default.ShowAsmComments_V9; + CustomEnvVars = Settings.Default.CustomEnvVars3_V15.Replace(";;", Environment.NewLine); + Crossgen2Args = Settings.Default.CrossgenArgs_V6; + IlcArgs = Settings.Default.IlcArgs_V9.Replace(";;", Environment.NewLine); + JitDumpInsteadOfDisasm = Settings.Default.JitDumpInsteadOfDisasm_V9; + UseDotnetBuildForReload = Settings.Default.UseDotnetBuildForReload_V9; + RunAppMode = Settings.Default.RunAppMode_V9; + UseNoRestoreFlag = Settings.Default.UseNoRestoreFlag_V9; + FontSize = Settings.Default.FontSize; + UseTieredJit = Settings.Default.UseTieredJit_V4; + UseCustomRuntime = Settings.Default.UseCustomRuntime_V4; + GraphvisDotPath = Settings.Default.GraphvisDotPath; + FlowgraphEnable = Settings.Default.FlowgraphEnable; + PrintInlinees = Settings.Default.PrintInlinees_V3; + UsePGO = Settings.Default.UsePGO; + Diffable = Settings.Default.Diffable; + UseUnloadableContext = Settings.Default.UseUnloadableContext; + DisableLightBulb = Settings.Default.DisableLightBulb; + DontGuessTargetFramework = Settings.Default.DontGuessTargetFramework; + OverridenTargetFramework = Settings.Default.OverridenTargetFramework; + } - public static string Arch { get; set; } = "x64"; + public static string Arch { get; set; } = "x64"; - public bool FgEnable + public bool FlowgraphEnable + { + get => _flowgraphEnable; + set { - get => _fgEnable; - set + Set(ref _flowgraphEnable, value); + Settings.Default.FlowgraphEnable = value; + Settings.Default.Save(); + if (value) { - Set(ref _fgEnable, value); - Settings.Default.FgEnable = value; - Settings.Default.Save(); - if (value) - { - JitDumpInsteadOfDisasm = true; - } + JitDumpInsteadOfDisasm = true; } } + } - public string GraphvisDotPath + public string GraphvisDotPath + { + get => _graphvisDot; + set { - get => _graphvisDot; - set - { - Set(ref _graphvisDot, value); - Settings.Default.GraphvisDotPath = value; - Settings.Default.Save(); - } + Set(ref _graphvisDot, value); + Settings.Default.GraphvisDotPath = value; + Settings.Default.Save(); } + } - public string OverridenJitDisasm - { - // No need to save it - get => _overridenJitDisasm; - set => Set(ref _overridenJitDisasm, value); - } + public string OverridenJitDisasm + { + // No need to save it + get => _overridenJitDisasm; + set => Set(ref _overridenJitDisasm, value); + } - public string PathToLocalCoreClr + public string PathToLocalCoreClr + { + get => _pathToLocalCoreClr; + set { - get => _pathToLocalCoreClr; - set - { - Set(ref _pathToLocalCoreClr, value); - Settings.Default.PathToCoreCLR_V9 = value; - Settings.Default.Save(); + Set(ref _pathToLocalCoreClr, value); + Settings.Default.PathToCoreCLR_V9 = value; + Settings.Default.Save(); - if (PopulateCustomJits()) - { - return; - } + if (PopulateCustomJits()) + return; - SelectedCustomJit = null; - CustomJits?.Clear(); - } + SelectedCustomJit = null; + CustomJits?.Clear(); } + } - public bool IsNonCustomDotnetAotMode() - { - return !UseCustomRuntime && - (SelectedCustomJit == Crossgen || SelectedCustomJit == Ilc); - } + public bool IsNonCustomDotnetAotMode() => + !UseCustomRuntime && (SelectedCustomJit == Crossgen || SelectedCustomJit == Ilc); - public bool IsNonCustomNativeAOTMode() - { - return !UseCustomRuntime && SelectedCustomJit == Ilc; - } + public bool IsNonCustomNativeAOTMode() => + !UseCustomRuntime && SelectedCustomJit == Ilc; - public const string DefaultJit = "clrjit.dll"; - public const string Crossgen = "crossgen2.dll (R2R)"; - public const string Ilc = "ilc (NativeAOT)"; + public const string DefaultJit = "clrjit.dll"; + public const string Crossgen = "crossgen2.dll (R2R)"; + public const string Ilc = "ilc (NativeAOT)"; - public bool PopulateCustomJits() + public bool PopulateCustomJits() + { + if (!UseCustomRuntime) { - if (!UseCustomRuntime) - { - CustomJits = new ObservableCollection(); - CustomJits.Add(DefaultJit); + CustomJits = new ObservableCollection(); + CustomJits.Add(DefaultJit); - // TODO: - //CustomJits.Add(Crossgen); - CustomJits.Add(Ilc); - SelectedCustomJit = CustomJits[0]; - return true; - } + // TODO: + //CustomJits.Add(Crossgen); + CustomJits.Add(Ilc); + SelectedCustomJit = CustomJits[0]; + return true; + } - if (!string.IsNullOrWhiteSpace(_pathToLocalCoreClr)) + if (!string.IsNullOrWhiteSpace(_pathToLocalCoreClr)) + { + var jitDirectory = FindJitDirectory(_pathToLocalCoreClr); + if (jitDirectory is not null) { - string jitDir = FindJitDirectory(_pathToLocalCoreClr); - if (jitDir != null) + string[] jits = Directory.GetFiles(jitDirectory, "clrjit*.dll"); + CustomJits = new ObservableCollection(jits.Select(Path.GetFileName)); + SelectedCustomJit = CustomJits.FirstOrDefault(j => j == DefaultJit); + if (SelectedCustomJit is not null) { - string[] jits = Directory.GetFiles(jitDir, "clrjit*.dll"); - CustomJits = new ObservableCollection(jits.Select(Path.GetFileName)); - SelectedCustomJit = CustomJits.FirstOrDefault(j => j == DefaultJit); - if (SelectedCustomJit != null) - { - CustomJits.Add(Crossgen); - CustomJits.Add(Ilc); - } - return true; + CustomJits.Add(Crossgen); + CustomJits.Add(Ilc); } + return true; } - return false; } + return false; + } - public ObservableCollection CustomJits - { - get => _customJits; - set => Set(ref _customJits, value); - } + public ObservableCollection CustomJits + { + get => _customJits; + set => Set(ref _customJits, value); + } - public string SelectedCustomJit + public string SelectedCustomJit + { + get => _selectedCustomJit; + set { - get => _selectedCustomJit; - set + if (value?.StartsWith("crossgen") == true || value?.StartsWith("ilc") == true) { - if (value?.StartsWith("crossgen") == true || value?.StartsWith("ilc") == true) - { - RunAppMode = false; - UseTieredJit = false; - UsePGO = false; - JitDumpInsteadOfDisasm = false; - } - Set(ref _selectedCustomJit, value); + RunAppMode = false; + UseTieredJit = false; + UsePGO = false; + JitDumpInsteadOfDisasm = false; } + Set(ref _selectedCustomJit, value); } + } - public bool CrossgenIsSelected => SelectedCustomJit?.StartsWith("crossgen") == true; + public bool CrossgenIsSelected => SelectedCustomJit?.StartsWith("crossgen") == true; - public bool NativeAotIsSelected => SelectedCustomJit?.StartsWith("ilc") == true; + public bool NativeAotIsSelected => SelectedCustomJit?.StartsWith("ilc") == true; - public bool RunAppMode + public bool RunAppMode + { + get => _runAppMode; + set { - get => _runAppMode; - set + Set(ref _runAppMode, value); + Settings.Default.RunAppMode_V9 = value; + Settings.Default.Save(); + if (value) { - Set(ref _runAppMode, value); - Settings.Default.RunAppMode_V9 = value; - Settings.Default.Save(); - if (value) - { - UseUnloadableContext = false; - } + UseUnloadableContext = false; } } + } - public string OverridenTFM + public string OverridenTargetFramework + { + get => _overridenTargetFramework; + set { - get => _overridenTfm; - set - { - Set("OverridenTFM", ref _overridenTfm, value); - Settings.Default.OverridenTFM = value; - Settings.Default.Save(); - } + Set("OverridenTargetFramework", ref _overridenTargetFramework, value); + Settings.Default.OverridenTargetFramework = value; + Settings.Default.Save(); } + } - public bool PrintInlinees + public bool PrintInlinees + { + get => _printInlinees; + set { - get => _printInlinees; - set + Set(ref _printInlinees, value); + if (value) { - Set(ref _printInlinees, value); - if (value) - { - // Reset "Use JitDump" flag which should also reset FlowGraph flag - JitDumpInsteadOfDisasm = false; - } - - Settings.Default.PrintInlinees_V3 = value; - Settings.Default.Save(); + // Reset "Use JitDump" flag which should also reset FlowGraph flag + JitDumpInsteadOfDisasm = false; } + + Settings.Default.PrintInlinees_V3 = value; + Settings.Default.Save(); } + } - public bool UsePGO + public bool UsePGO + { + get => _usePGO; + set { - get => _usePGO; - set - { - Set(ref _usePGO, value); - if (value) - this.UseTieredJit = true; + Set(ref _usePGO, value); - Settings.Default.UsePGO = value; - Settings.Default.Save(); + if (value) + { + UseTieredJit = true; } + + Settings.Default.UsePGO = value; + Settings.Default.Save(); } + } - public bool Diffable + public bool Diffable + { + get => _diffable; + set { - get => _diffable; - set - { - Set(ref _diffable, value); - Settings.Default.Diffable = value; - Settings.Default.Save(); - } + Set(ref _diffable, value); + Settings.Default.Diffable = value; + Settings.Default.Save(); } + } - public bool UseNoRestoreFlag + public bool UseNoRestoreFlag + { + get => _useNoRestoreFlag; + set { - get => _useNoRestoreFlag; - set - { - Set(ref _useNoRestoreFlag, value); - Settings.Default.UseNoRestoreFlag_V9 = value; - Settings.Default.Save(); - } + Set(ref _useNoRestoreFlag, value); + Settings.Default.UseNoRestoreFlag_V9 = value; + Settings.Default.Save(); } + } - public bool DisableLightBulb + public bool DisableLightBulb + { + get => _disableLightBulb; + set { - get => _disableLightBulb; - set - { - Set(ref _disableLightBulb, value); - Settings.Default.DisableLightBulb = value; - Settings.Default.Save(); - } + Set(ref _disableLightBulb, value); + Settings.Default.DisableLightBulb = value; + Settings.Default.Save(); } + } - public double FontSize + public double FontSize + { + get => _fontSize; + set { - get => _fontSize; - set - { - Set(ref _fontSize, value); - Settings.Default.FontSize = value; - Settings.Default.Save(); - } + Set(ref _fontSize, value); + Settings.Default.FontSize = value; + Settings.Default.Save(); } + } public bool UseCustomRuntime + { + get => _useCustomRuntime; + set { - get => _useCustomRuntime; - set - { - Set(ref _useCustomRuntime, value); - Settings.Default.UseCustomRuntime_V4 = value; - Settings.Default.Save(); - if (!value) - { - JitDumpInsteadOfDisasm = false; - UseDotnetPublishForReload = false; - UseDotnetBuildForReload = true; - PrintInlinees = false; - } - PopulateCustomJits(); - } - } + Set(ref _useCustomRuntime, value); + Settings.Default.UseCustomRuntime_V4 = value; + Settings.Default.Save(); - public bool UseDotnetPublishForReload - { - get => _useDotnetPublishForReload; - set + if (!value) { - Set(ref _useDotnetPublishForReload, value); - Set(ref _useDotnetBuildForReload, !value); - Settings.Default.UseDotnetBuildForReload_V9 = !value; - Settings.Default.Save(); + JitDumpInsteadOfDisasm = false; + UseDotnetPublishForReload = false; + UseDotnetBuildForReload = true; + PrintInlinees = false; } - } - public bool UseDotnetBuildForReload - { - get => _useDotnetBuildForReload; - set - { - Set(ref _useDotnetBuildForReload, value); - Set(ref _useDotnetPublishForReload, !value); - Settings.Default.UseDotnetBuildForReload_V9 = value; - Settings.Default.Save(); - } + PopulateCustomJits(); } + } - public bool JitDumpInsteadOfDisasm + public bool UseDotnetPublishForReload + { + get => _useDotnetPublishForReload; + set { - get => _jitDumpInsteadOfDisasm; - set - { - Set(ref _jitDumpInsteadOfDisasm, value); - Settings.Default.JitDumpInsteadOfDisasm_V9 = value; - Settings.Default.Save(); - if (!value) - { - FgEnable = false; - } - else - { - PrintInlinees = false; - } - } + Set(ref _useDotnetPublishForReload, value); + Set(ref _useDotnetBuildForReload, !value); + Settings.Default.UseDotnetBuildForReload_V9 = !value; + Settings.Default.Save(); } + } - public bool UseTieredJit + public bool UseDotnetBuildForReload + { + get => _useDotnetBuildForReload; + set { - get => _useTieredJit; - set - { - Set(ref _useTieredJit, value); - Settings.Default.UseTieredJit_V4 = value; - Settings.Default.Save(); - } + Set(ref _useDotnetBuildForReload, value); + Set(ref _useDotnetPublishForReload, !value); + Settings.Default.UseDotnetBuildForReload_V9 = value; + Settings.Default.Save(); } + } - public bool DontGuessTFM + public bool JitDumpInsteadOfDisasm + { + get => _jitDumpInsteadOfDisasm; + set { - get => _dontGuessTFM; - set + Set(ref _jitDumpInsteadOfDisasm, value); + Settings.Default.JitDumpInsteadOfDisasm_V9 = value; + Settings.Default.Save(); + + if (!value) { - Set(ref _dontGuessTFM, value); - Settings.Default.DontGuessTFM = value; - Settings.Default.Save(); + FlowgraphEnable = false; } - } - - public bool UseUnloadableContext - { - get => _useUnloadableContext; - set + else { - Set(ref _useUnloadableContext, value); - Settings.Default.UseUnloadableContext = value; - Settings.Default.Save(); - if (value) - { - RunAppMode = false; - } + PrintInlinees = false; } } + } - public bool ShowAsmComments + public bool UseTieredJit + { + get => _useTieredJit; + set { - get => _showAsmComments; - set - { - Set(ref _showAsmComments, value); - Settings.Default.ShowAsmComments_V9 = value; - Settings.Default.Save(); - } + Set(ref _useTieredJit, value); + Settings.Default.UseTieredJit_V4 = value; + Settings.Default.Save(); } + } - public string CustomEnvVars + public bool DontGuessTargetFramework + { + get => _dontGuessTargetFramework; + set { - get => _customEnvVars; - set - { - Set(ref _customEnvVars, value); - Settings.Default.CustomEnvVars3_V15 = value; - Settings.Default.Save(); - } + Set(ref _dontGuessTargetFramework, value); + Settings.Default.DontGuessTargetFramework = value; + Settings.Default.Save(); } + } - public string Crossgen2Args + public bool UseUnloadableContext + { + get => _useUnloadableContext; + set { - get => _crossgen2Args; - set + Set(ref _useUnloadableContext, value); + Settings.Default.UseUnloadableContext = value; + Settings.Default.Save(); + + if (value) { - Set(ref _crossgen2Args, value); - Settings.Default.CrossgenArgs_V6 = value; - Settings.Default.Save(); + RunAppMode = false; } } + } - public string IlcArgs + public bool ShowAsmComments + { + get => _showAsmComments; + set { - get => _ilcArgs; - set - { - Set(ref _ilcArgs, value); - Settings.Default.IlcArgs_V9 = value; - Settings.Default.Save(); - } + Set(ref _showAsmComments, value); + Settings.Default.ShowAsmComments_V9 = value; + Settings.Default.Save(); } + } - public bool UpdateIsAvailable + public string CustomEnvVars + { + get => _customEnvVars; + set { - get => _updateIsAvailable; - set { Set(ref _updateIsAvailable, value); } + Set(ref _customEnvVars, value); + Settings.Default.CustomEnvVars3_V15 = value; + Settings.Default.Save(); } + } - public Version CurrentVersion + public string Crossgen2Args + { + get => _crossgen2Args; + set { - get => _currentVersion; - set => Set(ref _currentVersion, value); + Set(ref _crossgen2Args, value); + Settings.Default.CrossgenArgs_V6 = value; + Settings.Default.Save(); } + } - public Version AvailableVersion + public string IlcArgs + { + get => _ilcArgs; + set { - get => _availableVersion; - set => Set(ref _availableVersion, value); + Set(ref _ilcArgs, value); + Settings.Default.IlcArgs_V9 = value; + Settings.Default.Save(); } + } - public ICommand BrowseCommand => new RelayCommand(() => - { - var dialog = new FolderBrowserDialog(); - var result = dialog.ShowDialog(); - if (result == DialogResult.OK) - PathToLocalCoreClr = dialog.SelectedPath; - }); + public ICommand BrowseCommand => new RelayCommand(() => + { + var dialog = new FolderBrowserDialog(); + var result = dialog.ShowDialog(); + if (result == DialogResult.OK) + PathToLocalCoreClr = dialog.SelectedPath; + }); - public void FillWithUserVars(Dictionary dictionary) - { - if (string.IsNullOrWhiteSpace(CustomEnvVars)) - return; + public void FillWithUserVars(Dictionary dictionary) + { + if (string.IsNullOrWhiteSpace(CustomEnvVars)) + return; - var pairs = CustomEnvVars.Split(new [] {"\r\n", "\n"}, StringSplitOptions.RemoveEmptyEntries); - foreach (var pair in pairs) - { - var parts = pair.Split('='); - if (parts.Length == 2) - dictionary[parts[0].Trim()] = parts[1].Trim(); - } + var pairs = CustomEnvVars.Split(new [] {"\r\n", "\n"}, StringSplitOptions.RemoveEmptyEntries); + foreach (var pair in pairs) + { + var parts = pair.Split('='); + if (parts.Length == 2) + dictionary[parts[0].Trim()] = parts[1].Trim(); } + } - private static string FindJitDirectory(string basePath) - { - string jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{Arch}.Checked"); - if (Directory.Exists(jitDir)) - { - return jitDir; - } + private static string FindJitDirectory(string basePath) + { + var jitDirectory = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{Arch}.Checked"); + if (Directory.Exists(jitDirectory)) + return jitDirectory; - jitDir = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{Arch}.Debug"); - if (Directory.Exists(jitDir)) - { - return jitDir; - } + jitDirectory = Path.Combine(basePath, $@"artifacts\bin\coreclr\windows.{Arch}.Debug"); + if (Directory.Exists(jitDirectory)) + return jitDirectory; - return null; - } + return null; } -} +} \ No newline at end of file diff --git a/src/Vsix/Views/Converters/BoolToFontUnderlineConverter.cs b/src/Vsix/Views/Converters/BoolToFontUnderlineConverter.cs new file mode 100644 index 0000000..6e3fcb2 --- /dev/null +++ b/src/Vsix/Views/Converters/BoolToFontUnderlineConverter.cs @@ -0,0 +1,14 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Disasmo.Utils; + +public class BoolToFontUnderlineConverter : DependencyObject, IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => + value is not null && (bool)value ? TextDecorations.Underline : null; + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => Binding.DoNothing; +} \ No newline at end of file diff --git a/src/Vsix/Views/Converters/BoolToFontWeightConverter.cs b/src/Vsix/Views/Converters/BoolToFontWeightConverter.cs index 8e02eee..7f871dd 100644 --- a/src/Vsix/Views/Converters/BoolToFontWeightConverter.cs +++ b/src/Vsix/Views/Converters/BoolToFontWeightConverter.cs @@ -8,7 +8,7 @@ namespace Disasmo.Utils; public class BoolToFontWeightConverter : DependencyObject, IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => - value != null && (bool)value ? FontWeights.Bold : FontWeights.Normal; + value is not null && (bool)value ? FontWeights.Bold : FontWeights.Normal; public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => Binding.DoNothing; } \ No newline at end of file diff --git a/src/Vsix/Views/Converters/InversedBooleanToVisibilityConverter.cs b/src/Vsix/Views/Converters/InversedBooleanToVisibilityConverter.cs index de72944..3e2d952 100644 --- a/src/Vsix/Views/Converters/InversedBooleanToVisibilityConverter.cs +++ b/src/Vsix/Views/Converters/InversedBooleanToVisibilityConverter.cs @@ -3,14 +3,13 @@ using System.Windows; using System.Windows.Data; -namespace Disasmo.Utils +namespace Disasmo.Utils; + +public class InversedBooleanToVisibilityConverter : IValueConverter { - public class InversedBooleanToVisibilityConverter : IValueConverter - { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => - value is true ? Visibility.Collapsed : Visibility.Visible; + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => + value is true ? Visibility.Collapsed : Visibility.Visible; - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => - throw new NotImplementedException(); - } -} + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => + throw new NotImplementedException(); +} \ No newline at end of file diff --git a/src/Vsix/Views/DisasmWindow.cs b/src/Vsix/Views/DisasmWindow.cs index ea594f0..a994ff2 100644 --- a/src/Vsix/Views/DisasmWindow.cs +++ b/src/Vsix/Views/DisasmWindow.cs @@ -6,11 +6,11 @@ namespace Disasmo; [Guid("97cd0cd6-1d77-4848-8b6e-dc82cdccc6d7")] public class DisasmWindow : ToolWindowPane { - public MainViewModel ViewModel => (MainViewModel) ((DisasmWindowControl) Content).DataContext; + public MainViewModel ViewModel => (MainViewModel)((DisasmWindowControl)Content).DataContext; public DisasmWindow() : base(null) { Caption = "Disasmo"; Content = new DisasmWindowControl(); } -} +} \ No newline at end of file diff --git a/src/Vsix/Views/DisasmWindowControl.xaml b/src/Vsix/Views/DisasmWindowControl.xaml index 1f00e99..3516200 100644 --- a/src/Vsix/Views/DisasmWindowControl.xaml +++ b/src/Vsix/Views/DisasmWindowControl.xaml @@ -6,113 +6,221 @@ xmlns:avalonedit="http://icsharpcode.net/sharpdevelop/avalonedit" xmlns:disasmo="clr-namespace:Disasmo" xmlns:utils="clr-namespace:Disasmo.Utils" - mc:Ignorable="d" TextElement.Foreground="Black" - d:DesignHeight="1000" d:DesignWidth="800"> + mc:Ignorable="d" + d:DesignHeight="1000" d:DesignWidth="800" + Loaded="UserControl_Loaded"> + + - - - - - + + + + + + + + - - - + + + + + - - - - - - - - - - - -