Skip to content

Commit 9d9038e

Browse files
committed
Add disassembler
1 parent a3c56bb commit 9d9038e

33 files changed

+1981
-1389
lines changed

src/AsmArm64.CodeGen/Arm64Processor.WriterAssembler.cs

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,7 +1735,6 @@ private static (string Default, string? IndexRegisterW) GetMemoryOperandType(Mem
17351735
case Arm64MemoryEncodingKind.BaseRegisterAndFixedImmediateOptional:
17361736
case Arm64MemoryEncodingKind.BaseRegisterAndImmediateOptional:
17371737
return ("Arm64ImmediateMemoryAccessor", null);
1738-
break;
17391738
case Arm64MemoryEncodingKind.BaseRegisterAndIndexXmAndLslAmount:
17401739
return ("Arm64RegisterXExtendMemoryAccessor", null);
17411740
case Arm64MemoryEncodingKind.BaseRegisterAndIndexWmOrXmAndExtend:
@@ -1791,7 +1790,6 @@ private static void GetRegisterOperandVariation(int operandIndex, Instruction in
17911790
string? baseType;
17921791

17931792
var operandName = GetNormalizedOperandName(register.Name);
1794-
17951793
List<WriteEncodingDelegate> encodings = new();
17961794

17971795
var testArguments = new List<RegisterTestArgument>();
@@ -1891,9 +1889,7 @@ private static void GetRegisterOperandVariation(int operandIndex, Instruction in
18911889
operandVariationX.TestArguments.Add(new RegisterTestArgument("X", 31));
18921890
}
18931891

1894-
var encodingX = GetRegisterIndexEncoding(instruction, register, operandVariationX);
1895-
Debug.Assert(encodingX is not null);
1896-
operandVariationX.WriteEncodings.Add(encodingX);
1892+
AddRegisterIndexEncoding(instruction, register, operandVariationX);
18971893

18981894
var operandVariationW = new OperandVariation()
18991895
{
@@ -1909,9 +1905,7 @@ private static void GetRegisterOperandVariation(int operandIndex, Instruction in
19091905
}
19101906
operandVariationW.AcceptedBitValues.AddRange(xOrWSelector.BitValues.Where(x => x.Text == "W"));
19111907

1912-
var encodingW = GetRegisterIndexEncoding(instruction, register, operandVariationW);
1913-
Debug.Assert(encodingW is not null);
1914-
operandVariationW.WriteEncodings.Add(encodingW);
1908+
AddRegisterIndexEncoding(instruction, register, operandVariationW);
19151909

19161910
operandVariations.Add(operandVariationX);
19171911
operandVariations.Add(operandVariationW);
@@ -2045,12 +2039,7 @@ private static void GetRegisterOperandVariation(int operandIndex, Instruction in
20452039
operandVariation.AcceptedBitValues.Add(bitValue);
20462040
operandVariation.TestArguments.AddRange(testArguments);
20472041

2048-
var indexEncoding = GetRegisterIndexEncoding(instruction, register, operandVariation);
2049-
if (indexEncoding != null)
2050-
{
2051-
operandVariation.WriteEncodings.Add(indexEncoding);
2052-
}
2053-
2042+
AddRegisterIndexEncoding(instruction, register, operandVariation);
20542043
GenerateEncodingForExtract(instruction, register.IndexerExtract, operandVariation, "ElementIndex", "element indexer");
20552044
operandVariations.Add(operandVariation);
20562045
}
@@ -2106,12 +2095,7 @@ private static void GetRegisterOperandVariation(int operandIndex, Instruction in
21062095
break;
21072096
}
21082097

2109-
var indexEncoding = GetRegisterIndexEncoding(instruction, register, operandVariation);
2110-
if (indexEncoding is not null)
2111-
{
2112-
operandVariation.WriteEncodings.Add(indexEncoding!);
2113-
}
2114-
2098+
AddRegisterIndexEncoding(instruction, register, operandVariation);
21152099
GenerateEncodingForExtract(instruction, register.IndexerExtract, operandVariation, "ElementIndex", "element indexer");
21162100
operandVariations.Add(operandVariation);
21172101
}
@@ -2174,11 +2158,8 @@ private static void GetRegisterOperandVariation(int operandIndex, Instruction in
21742158
opVar.TestArguments.AddRange(testArguments);
21752159
}
21762160

2177-
var directIndexEncoding = GetRegisterIndexEncoding(instruction, register, opVar);
2178-
if (directIndexEncoding is not null)
2179-
{
2180-
encodings.Add(directIndexEncoding);
2181-
}
2161+
opVar.WriteEncodings.AddRange(encodings);
2162+
AddRegisterIndexEncoding(instruction, register, opVar);
21822163

21832164
switch (instruction.Id)
21842165
{
@@ -2204,7 +2185,6 @@ private static void GetRegisterOperandVariation(int operandIndex, Instruction in
22042185
//break;
22052186
}
22062187

2207-
opVar.WriteEncodings.AddRange(encodings);
22082188
GenerateEncodingForExtract(instruction, register.IndexerExtract, opVar, "ElementIndex", "element indexer");
22092189
operandVariations.Add(opVar);
22102190
}
@@ -2393,52 +2373,69 @@ private static void GenerateSwitchCaseFromValue(string valuePath, List<(int Valu
23932373
return null;
23942374
}
23952375

2396-
private static WriteEncodingDelegate? GetRegisterIndexEncoding(Instruction instruction, RegisterOperandDescriptor register, OperandVariation operandVariation)
2376+
private static void AddRegisterIndexEncoding(Instruction instruction, RegisterOperandDescriptor register, OperandVariation operandVariation)
23972377
{
2378+
var operandVar = operandVariation.OperandName;
2379+
2380+
if (register.IsOptional)
2381+
{
2382+
var regName = instruction.Id == "RET_64r_branch_reg" ? "X30" : "XZR";
2383+
operandVariation.WriteEncodings.Add((w, variable, variation, operand, index) => w.WriteLine($"{operandVar} = ({operandVar}.Kind == Arm64RegisterKind.Invalid) ? Arm64RegisterX.{regName} : {operandVar};"));
2384+
}
2385+
23982386
switch (register.RegisterIndexEncodingKind)
23992387
{
24002388
case Arm64RegisterIndexEncodingKind.Std5:
24012389
{
24022390
if (register.LowBitIndexEncoding == 0)
24032391
{
2404-
return (w, variable, instr, op, opIndex) => w.WriteLine($"{variable} |= (uint){op.OperandName}.Index;");
2392+
operandVariation.WriteEncodings.Add((w, variable, instr, op, opIndex) => w.WriteLine($"{variable} |= (uint){operandVar}.Index;"));
24052393
}
24062394
else
24072395
{
2408-
return (w, variable, instr, op, opIndex) => w.WriteLine($"{variable} |= (uint){op.OperandName}.Index << {register.LowBitIndexEncoding};");
2396+
operandVariation.WriteEncodings.Add((w, variable, instr, op, opIndex) => w.WriteLine($"{variable} |= (uint){operandVar}.Index << {register.LowBitIndexEncoding};"));
24092397
}
2398+
2399+
break;
24102400
}
24112401
case Arm64RegisterIndexEncodingKind.Std4:
24122402
{
24132403
if (register.LowBitIndexEncoding == 0)
24142404
{
2415-
return (w, variable, instr, op, opIndex) => w.WriteLine($"{variable} |= (uint)({op.OperandName}.Index & 0xF);"); // 4 bits so we mask by security
2405+
operandVariation.WriteEncodings.Add((w, variable, instr, op, opIndex) => w.WriteLine($"{variable} |= (uint)({operandVar}.Index & 0xF);")); // 4 bits so we mask by security
24162406
}
24172407
else
24182408
{
2419-
return (w, variable, instr, op, opIndex) => w.WriteLine($"{variable} |= (uint)({op.OperandName}.Index & 0xF) << {register.LowBitIndexEncoding};");
2409+
operandVariation.WriteEncodings.Add((w, variable, instr, op, opIndex) => w.WriteLine($"{variable} |= (uint)({operandVar}.Index & 0xF) << {register.LowBitIndexEncoding};"));
24202410
}
2411+
2412+
break;
24212413
}
24222414
case Arm64RegisterIndexEncodingKind.Std5Plus1:
2423-
return (w, variable, instr, op, opIndex) =>
2424-
{
2425-
var previousOp = instr.Operands[opIndex - 1];
2426-
w.WriteLine($"if ({op.OperandName}.Index != (({previousOp.OperandName}.Index + 1) & 0x1F)) throw new {nameof(ArgumentOutOfRangeException)}(nameof({op.OperandName}), $\"Invalid Register. Index `{{{op.OperandName}.Index}}` must be + 1 from operand {previousOp.OperandName} with index `{{{previousOp.OperandName}.Index}}`\");");
2427-
};
2415+
operandVariation.WriteEncodings.Add(
2416+
(w, variable, instr, op, opIndex) =>
2417+
{
2418+
var previousOp = instr.Operands[opIndex - 1];
2419+
w.WriteLine(
2420+
$"if ({operandVar}.Index != (({previousOp.OperandName}.Index + 1) & 0x1F)) throw new {nameof(ArgumentOutOfRangeException)}(nameof({operandVar}), $\"Invalid Register. Index `{{{operandVar}.Index}}` must be + 1 from operand {previousOp.OperandName} with index `{{{previousOp.OperandName}.Index}}`\");");
2421+
}
2422+
);
2423+
break;
24282424
case Arm64RegisterIndexEncodingKind.BitMapExtract:
24292425
GenerateEncodingForExtract(instruction, register.RegisterIndexExtract, operandVariation, "Index", "register index");
24302426
break;
24312427
case Arm64RegisterIndexEncodingKind.Fixed:
2432-
return (w, variable, instr, op, opIndex) =>
2433-
{
2434-
w.WriteLine($"if ({operandVariation.OperandName}.Index != {register.FixedRegisterIndex}) throw new {nameof(ArgumentOutOfRangeException)}(nameof({op.OperandName}), $\"Invalid Register. Expecting the fixed index {register.FixedRegisterIndex} instead of {{{operandVariation.OperandName}}}\");");
2435-
};
2428+
operandVariation.WriteEncodings.Add((w, variable, instr, op, opIndex) =>
2429+
{
2430+
w.WriteLine(
2431+
$"if ({operandVariation.OperandName}.Index != {register.FixedRegisterIndex}) throw new {nameof(ArgumentOutOfRangeException)}(nameof({operandVar}), $\"Invalid Register. Expecting the fixed index {register.FixedRegisterIndex} instead of {{{operandVariation.OperandName}}}\");");
2432+
}
2433+
);
2434+
break;
24362435
default:
24372436
Debug.Assert(false,"register", $"RegisterIndexEncodingKind `{register.RegisterIndexEncodingKind}` is not supported");
24382437
break;
24392438
}
2440-
2441-
return null;
24422439
}
24432440

24442441
private static string GetNormalizedOperandName(string name)

src/AsmArm64.CodeGen/AsmArm64.CodeGen.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<Compile Include="..\AsmArm64\Arm64ExtendEncodingKind.cs" Link="Arm64ExtendEncodingKind.cs" />
1414
<Compile Include="..\AsmArm64\Arm64ImmediateEncodingKind.cs" Link="Model\Arm64ImmediateEncodingKind.cs" />
1515
<Compile Include="..\AsmArm64\Arm64ImmediateValueEncodingKind.cs" Link="Model\Arm64ImmediateValueEncodingKind.cs" />
16+
<Compile Include="..\AsmArm64\Arm64InstructionEncodingFlags.cs" Link="Arm64InstructionEncodingFlags.cs" />
1617
<Compile Include="..\AsmArm64\Arm64LabelEncodingKind.cs" Link="Model\Arm64LabelEncodingKind.cs" />
1718
<Compile Include="..\AsmArm64\Arm64MemoryEncodingKind.cs" Link="Model\Arm64MemoryEncodingKind.cs" />
1819
<Compile Include="..\AsmArm64\Arm64MemoryExtendEncodingKind.cs" Link="Model\Arm64MemoryExtendEncodingKind.cs" />

src/AsmArm64.CodeGen/Model/Instruction.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,17 @@ public void Encode(Span<byte> buffer)
121121
MemoryMarshal.Write(buffer.Slice(2), (ushort)MnemonicIndex);
122122
buffer[4] = (byte)InstructionClassIndex;
123123
buffer[5] = (byte)FeatureExpressionIdIndex;
124-
buffer[6] = (byte)(UseOperandEncoding8Bytes ? 1 : 0);
124+
var flags = UseOperandEncoding8Bytes ? Arm64InstructionEncodingFlags.Encoding8Bytes : Arm64InstructionEncodingFlags.None;
125+
if (Operands.Any(x => x.Descriptor is LabelOperandDescriptor))
126+
{
127+
flags |= Arm64InstructionEncodingFlags.HasLabel;
128+
}
129+
130+
if (DocVars.TryGetValue("cond-setting", out var str) && str.Equals("S", StringComparison.OrdinalIgnoreCase))
131+
{
132+
flags |= Arm64InstructionEncodingFlags.HasSetFlags;
133+
}
134+
buffer[6] = (byte)flags;
125135
buffer[7] = (byte)Operands.Count;
126136
}
127137

src/AsmArm64.Tests/AsmArm64.Tests.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
<ItemGroup>
1212
<PackageReference Include="MSTest" />
13+
<PackageReference Include="Verify.DiffPlex" />
14+
<PackageReference Include="Verify.MSTest" />
1315
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
1416
</ItemGroup>
1517

src/AsmArm64.Tests/TestAssembler.cs

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
// Licensed under the BSD-Clause 2 license.
33
// See license.txt file in the project root for full license information.
44

5+
using System.Runtime.InteropServices;
6+
57
namespace AsmArm64.Tests;
68

9+
using static Arm64Factory;
10+
711
[TestClass]
8-
public class TestAssembler
12+
public class TestAssembler : VerifyBase
913
{
1014
[TestMethod]
1115
public void TestSimple()
@@ -31,4 +35,90 @@ public void TestSimple()
3135
var labelOperand2 = (Arm64LabelOperand)operand2;
3236
Assert.AreEqual(4, labelOperand2.Offset);
3337
}
38+
39+
[TestMethod]
40+
public async Task TestDisassemblerSimple()
41+
{
42+
var instructionBuffer = AssembleInstructionsSample();
43+
var disassembler = new Arm64Disassembler();
44+
var textWriter = new StringWriter();
45+
disassembler.Disassemble(instructionBuffer, textWriter);
46+
47+
var disassembledText = textWriter.ToString();
48+
49+
await Verify(disassembledText);
50+
}
51+
52+
[TestMethod]
53+
public async Task TestDisassemblerDetailed()
54+
{
55+
var instructionBuffer = AssembleInstructionsSample();
56+
var disassembler = new Arm64Disassembler
57+
{
58+
Options =
59+
{
60+
PrintAddress = true,
61+
PrintAssemblyBytes = true
62+
}
63+
};
64+
65+
var textWriter = new StringWriter();
66+
67+
disassembler.Disassemble(instructionBuffer, textWriter);
68+
69+
var disassembledText = textWriter.ToString();
70+
await Verify(disassembledText);
71+
}
72+
73+
private static byte[] AssembleInstructionsSample()
74+
{
75+
var bufferList = new Arm64InstructionBufferByList();
76+
var asm = new Arm64Assembler(bufferList);
77+
78+
// // Main entry point
79+
// _start:
80+
var labelStart = asm.CreateLabelId("_start");
81+
asm.BindLabel(labelStart);
82+
// mov x0, #5 // Call sum_loop(5)
83+
asm.MOVZ(X0, 5);
84+
// bl sum_loop // Call the function, result in x0
85+
var labelSumLoop = asm.CreateLabelId("sum_loop");
86+
asm.BL(labelSumLoop);
87+
//
88+
// // Do something with result (for now, just returning)
89+
// ret // Return from _start (normally would return to a caller)
90+
asm.RET();
91+
//
92+
//
93+
// // Function: sum_loop(x0)
94+
// // - Takes x0 as the loop limit
95+
// // - Returns the sum in x0
96+
// sum_loop:
97+
asm.BindLabel(labelSumLoop);
98+
// mov x1, #0 // Accumulator (sum)
99+
asm.MOVZ(X1, 0);
100+
// mov x2, #0 // Counter
101+
asm.MOVZ(X2, 0);
102+
//
103+
// loop_start:
104+
var labelLoopStart = asm.CreateLabelId("loop_start");
105+
asm.BindLabel(labelLoopStart);
106+
// add x1, x1, x2 // Add counter (x2) to sum (x1)
107+
asm.ADD(X1, X1, X2);
108+
// add x2, x2, #1 // Increment counter
109+
asm.ADD(X2, X2, 1);
110+
//
111+
// cmp x2, x0 // Compare counter with limit
112+
asm.CMP(X2, X0);
113+
// blt loop_start // If counter < limit, continue loop
114+
asm.B(LT, labelLoopStart);
115+
//
116+
// mov x0, x1 // Store result in x0 (return value)
117+
asm.MOV(X0, X1);
118+
// ret // Return to caller
119+
asm.RET();
120+
asm.Assemble();
121+
122+
return MemoryMarshal.Cast<uint, byte>(CollectionsMarshal.AsSpan(bufferList.Instructions)).ToArray();
123+
}
34124
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Alexandre Mutel. All rights reserved.
2+
// Licensed under the BSD-Clause 2 license.
3+
// See license.txt file in the project root for full license information.
4+
5+
using System.Globalization;
6+
using System.Runtime.CompilerServices;
7+
using VerifyTests.DiffPlex;
8+
9+
namespace AsmArm64.Tests;
10+
11+
internal static class TestsInitializer
12+
{
13+
[ModuleInitializer]
14+
public static void Initialize()
15+
{
16+
VerifyDiffPlex.Initialize(OutputType.Compact);
17+
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
18+
Verifier.UseProjectRelativeDirectory("Verified");
19+
DiffEngine.DiffRunner.Disabled = true;
20+
VerifierSettings.DontScrubSolutionDirectory();
21+
}
22+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
LL_01:
2+
0000000000010000 A0 00 80 D2 mov x0, #5
3+
0000000000010004 02 00 00 94 bl LL_02
4+
5+
0000000000010008 C0 03 5F D6 ret
6+
7+
LL_02:
8+
000000000001000C 01 00 80 D2 mov x1, #0
9+
0000000000010010 02 00 80 D2 mov x2, #0
10+
11+
LL_03:
12+
0000000000010014 21 00 02 8B add x1, x1, x2
13+
0000000000010018 42 04 00 91 add x2, x2, #1
14+
000000000001001C 5F 00 00 EB cmp x2, x0
15+
0000000000010020 AB FF FF 54 b.lt LL_03
16+
17+
0000000000010024 E0 03 01 AA mov x0, x1
18+
0000000000010028 C0 03 5F D6 ret
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
LL_01:
2+
mov x0, #5
3+
bl LL_02
4+
5+
ret
6+
7+
LL_02:
8+
mov x1, #0
9+
mov x2, #0
10+
11+
LL_03:
12+
add x1, x1, x2
13+
add x2, x2, #1
14+
cmp x2, x0
15+
b.lt LL_03
16+
17+
mov x0, x1
18+
ret

0 commit comments

Comments
 (0)