Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9d52c5c
Cleanup code (1)
3fbaea00 Nov 9, 2025
930b09c
Correct the description of DisassemblyPrettifier.Prettify.
3fbaea00 Nov 9, 2025
df3b8e7
Refactor DisassemblyPrettifier.
3fbaea00 Nov 11, 2025
247c25c
Switch null-checks from the equality operator to the extended version…
3fbaea00 Nov 11, 2025
5df1555
Cleanup code (2)
3fbaea00 Nov 13, 2025
2f8abe1
Add cache to IntrinsicsSourcesService
3fbaea00 Nov 13, 2025
2018f83
Fix a bug and optimize IntrinsicsSourcesService
3fbaea00 Nov 13, 2025
fb6c551
Themized the disasmo tool window
3fbaea00 Nov 16, 2025
70b505f
Redesign tool window
3fbaea00 Nov 23, 2025
30fd5de
Fix link to FileDiffer (c) madskristensen
3fbaea00 Nov 23, 2025
be0f9b7
Switch from string.Empty to ""
3fbaea00 Dec 7, 2025
5056b18
Fix bug related to applying asm syntax on plain text
3fbaea00 Dec 7, 2025
ba669d3
Colorize asm code: no-operation instructions, control instrutions, br…
3fbaea00 Dec 7, 2025
b16606c
Perfect alignment of the Disasmo loading progress bar
3fbaea00 Dec 7, 2025
7a8f9d7
Allow minimal comments in RunApp mode. Change the order of the method…
3fbaea00 Dec 8, 2025
485e3b5
Remake package version obtaining
3fbaea00 Dec 8, 2025
cdd681c
Fix a deadlock in designer's contructor
3fbaea00 Dec 8, 2025
8b5ba95
Add a queue to load flowgraph phases to avoing crashing the entire sy…
3fbaea00 Dec 8, 2025
aa1339c
Add functionality to DisasmWindow
3fbaea00 Dec 9, 2025
fdf232d
Update the Disasmo screenshot file
3fbaea00 Dec 9, 2025
ff3fb1e
Various UI changes
3fbaea00 Dec 9, 2025
85023c1
Fix bug related to ignoring projects within solution folders when DTE…
3fbaea00 May 19, 2026
089aa62
Fix lack of quotes in dotnet build args, which led to fail when targe…
3fbaea00 May 19, 2026
3d11979
Fix bug then it was impossible to analyze implementation of interface…
3fbaea00 May 20, 2026
c2c19e6
Change GetRuntimeBaseDefinition to GetBaseDefinition and add a commen…
3fbaea00 May 21, 2026
1c19a7f
Bump version to 6.0.0 due to previous loader changes. Remove "2020 Ad…
3fbaea00 May 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 50 additions & 56 deletions Disasmo/Resources/DisasmoLoader4.cs_template
Original file line number Diff line number Diff line change
Expand Up @@ -10,87 +10,81 @@ using System.Runtime.Loader;

public class DisasmoLoader
{
public static void Main(string[] args)
{
PrecompileAllMethodsInType(args);
}
const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;

private static void PrecompileAllMethodsInType(string[] args)
static void Main(string[] args)
{
string assemblyName = args[0];
string typeName = args[1];
string methodName = args[2];
string unloadable = args[3];
var (assemblyName, typeName, methodName, unloadable) = (args[0], args[1], args[2], args[3]);

// Another ugly workaround for mangled names till I figure out a proper solution
if (typeName.Contains('_') && typeName.Contains('.'))
typeName = typeName.Substring(typeName.LastIndexOf('.') + 1);

var alc = new AssemblyLoadContext("DisasmoALC", unloadable == "True");
Assembly asm = alc.LoadFromAssemblyPath(Path.Combine(Environment.CurrentDirectory, assemblyName));
Type fastType = asm.GetType(typeName);
if (fastType != null)
var asm = alc.LoadFromAssemblyPath(Path.Combine(Environment.CurrentDirectory, assemblyName));
if (asm.GetType(typeName) is { } fastType)
{
PrecompileMethods(fastType, methodName);
PrecompileProperties(fastType, methodName);
return;
}

foreach (Type type in asm.GetTypes())
else
{
// We replace pluses with dots because 'typeName' is a C#'y name of the type
// Unfortunately, Roslyn doesn't have a display option to output the runtime name of the type
// And we do not want to complicate things by formatting the type's name ourselves
// This is the easiest solution to that problem
if (type.FullName?.Replace('+', '.').Contains(typeName) == true)
foreach (var type in asm.GetTypes())
{
PrecompileMethods(type, methodName);
PrecompileProperties(type, methodName);
// We replace pluses with dots because 'typeName' is a C#'y name of the type
// Unfortunately, Roslyn doesn't have a display option to output the runtime name of the type
// And we do not want to complicate things by formatting the type's name ourselves
// This is the easiest solution to that problem
if (type.FullName?.Replace('+', '.').Contains(typeName) is true)
{
PrecompileMethods(type, methodName);
break;
}
}
}
}

private static void PrecompileProperties(Type type, string propertyName)
static void PrecompileMethods(Type type, string methodName)
{
foreach (PropertyInfo propInfo in type.GetProperties((BindingFlags)60))
foreach (var method in type.GetMethods(bindingFlags))
{
if (propInfo.Name == "*" || propInfo.Name == propertyName)
{
if (propInfo.GetMethod != null)
RuntimeHelpers.PrepareMethod(propInfo.GetMethod.MethodHandle);
if (propInfo.SetMethod != null)
RuntimeHelpers.PrepareMethod(propInfo.SetMethod.MethodHandle);
}
if (method.IsGenericMethod)
continue;

if (method.DeclaringType != type && method.DeclaringType is not null)
continue;

if (methodName == "*" || method.Name == methodName)
CompileMethod(method);
else if (method.Name.Contains(">g__" + methodName)) // Local functions
CompileMethod(method);
}
}

private static void PrecompileMethods(Type type, string methodName)
{
foreach (MethodBase method in
type.GetMethods((BindingFlags)60).Concat(
type.GetConstructors((BindingFlags)60).Select(c => (MethodBase)c)))
foreach (var constructor in type.GetConstructors(bindingFlags))
{
if (method.IsGenericMethod)
if (methodName == "*" || constructor.Name == methodName)
CompileMethod(constructor);
}

foreach (var property in type.GetProperties(bindingFlags))
{
if (property.DeclaringType != type)
continue;

try
{
if (method.DeclaringType == type || method.DeclaringType == null)
{
if (methodName == "*" || method.Name == methodName)
{
RuntimeHelpers.PrepareMethod(method.MethodHandle);
}
else if (method.Name.Contains(">g__" + methodName))
{
// Special case for local functions
RuntimeHelpers.PrepareMethod(method.MethodHandle);
}
}
}
catch
if (methodName == "*" || property.Name == methodName)
{
if (property.GetMethod is { } getMethod)
CompileMethod(getMethod);

if (property.SetMethod is { } setMethod)
CompileMethod(setMethod);
}
}
}
}

static void CompileMethod(MethodBase methodBase)
{
if (methodBase is MethodInfo methodInfo)
methodInfo.GetBaseDefinition(); // Magic. Forcing the runtime to trigger virtual slots so PrepareMethod also account for interface method implementations
RuntimeHelpers.PrepareMethod(methodBase.MethodHandle);
}
}
2 changes: 1 addition & 1 deletion Disasmo/Resources/DisasmoLoader4.csproj_template
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<OutputPath>out</OutputPath>
<AssemblyName>DisasmoLoader4</AssemblyName>
<AssemblyName>DisasmoLoader4</AssemblyName>
</PropertyGroup>
</Project>
160 changes: 118 additions & 42 deletions Disasmo/Utils/DisassemblyPrettifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,37 +24,46 @@ 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)
/// ; ============================================================
/// </summary>
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<Block>();

var prevBlock = BlockType.Unknown;
var currentMethod = "";

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+:"))
Expand All @@ -65,59 +74,82 @@ 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);
var output = new StringBuilder();

foreach (var method in blocksByMethods)
{
List<Block> methodBlocks = method.ToList();
var methodBlocks = (IEnumerable<Block>)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<Block> 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<Block> 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,
Expand All @@ -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}");
}
}
Loading