Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
51 changes: 51 additions & 0 deletions Cpp2IL.Core.Tests/Isil/IsilFormattingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
namespace Cpp2IL.Core.Tests.Isil;

using Cpp2IL.Core.ISIL;

public class IsilFormattingTests
{
[Test]
public void ToString_FormatsJumpWithHexTarget()
{
var instruction = new Instruction(12, OpCode.Jump, (ulong)0x1Au);

Assert.That(instruction.ToString(), Is.EqualTo("12 Jump 001A"));
}

[Test]
public void ToString_FormatsConditionalJumpWithHexTargetAndCondition()
{
var condition = new Register(null, "ZF");
var instruction = new Instruction(5, OpCode.ConditionalJump, (ulong)0x2Bu, condition);

Assert.That(instruction.ToString(), Is.EqualTo("5 ConditionalJump 002B, ZF"));
}

[TestCase(OpCode.CallVoid)]
[TestCase(OpCode.Call)]
public void ToString_FormatsCallLikeOpCodesWithHexTargetAndRemainingOperands(OpCode opcode)
{
var arg0 = new Register(null, "rcx");
var instruction = new Instruction(42, opcode, (ulong)0x1234u, arg0, "hello");

Assert.That(instruction.ToString(), Is.EqualTo($"42 {opcode} 1234, rcx, \"hello\""));
}

[Test]
public void ToString_UsesDefaultPathForNonSpecialOpcode()
{
var destination = new Register(null, "rax");
var source = new Register(null, "rbx");
var instruction = new Instruction(3, OpCode.Move, destination, source);

Assert.That(instruction.ToString(), Is.EqualTo("3 Move rax, rbx"));
}

[Test]
public void ToString_ReturnWithoutOperands_DoesNotHaveTrailingSpace()
{
var instruction = new Instruction(17, OpCode.Return);

Assert.That(instruction.ToString(), Is.EqualTo("17 Return"));
}
}
83 changes: 83 additions & 0 deletions Cpp2IL.Core.Tests/Isil/X86IsilTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using Cpp2IL.Core.ISIL;
using System.Collections.Generic;

namespace Cpp2IL.Core.Tests.Isil;

public class X86IsilTests
{
[SetUp]
public void Setup()
{
Cpp2IlApi.ResetInternalState();
TestGameLoader.LoadSimple2019Game();
}

[Test]
public void X86IsilConversionTestSimpleIf()
{
var appContext = Cpp2IlApi.CurrentAppContext!;
var mscorlib = appContext.AssembliesByName["mscorlib"];
var appDomain = mscorlib.GetTypeByFullName("System.AppDomain");

Assert.That(appDomain, Is.Not.Null, "expected to find System.AppDomain in mscorlib");

var method = appDomain!.GetMethod("DoDomainUnload");
var isil = appContext.InstructionSet.GetIsilFromMethod(method);

Assert.That(isil, Is.Not.Null.And.Not.Empty, "expected ISIL conversion to produce instructions");

var rax = new Register(null, "rax");
var rcx = new Register(null, "rcx");
var rdx = new Register(null, "rdx");
var r8 = new Register(null, "r8");
var r9 = new Register(null, "r9");
var CF = new Register(null, "CF");
var OF = new Register(null, "OF");
var SF = new Register(null, "SF");
var ZF = new Register(null, "ZF");
var PF = new Register(null, "PF");
var TEMP1 = new Register(null, "TEMP1");
var TEMP2 = new Register(null, "TEMP2");
var TEMP3 = new Register(null, "TEMP3");
var TEMP4 = new Register(null, "TEMP4");
var TEMP5 = new Register(null, "TEMP5");

var instructions = new List<Instruction>();

void Add(int index, OpCode opCode, params object[] operands) =>
instructions.Add(new Instruction(index, opCode, operands));

Add(0, OpCode.Move, rax, new MemoryOperand(rcx, null, 0x48));
Add(1, OpCode.CheckLess, CF, rax, 0);
Add(2, OpCode.Subtract, TEMP1, rax, 0);
Add(3, OpCode.Xor, TEMP2, rax, 0);
Add(4, OpCode.Xor, TEMP3, rax, TEMP1);
Add(5, OpCode.And, TEMP4, TEMP2, TEMP3);
Add(6, OpCode.CheckLess, OF, TEMP4, 0);
Add(7, OpCode.CheckLess, SF, TEMP1, 0);
Add(8, OpCode.CheckEqual, ZF, TEMP1, 0);
Add(9, OpCode.And, TEMP5, TEMP2, 1);
Add(10, OpCode.CheckEqual, PF, TEMP5, 0);
Add(11, OpCode.ConditionalJump, 18, ZF);
Add(12, OpCode.Move, rdx, rcx);
Add(13, OpCode.Move, r9, 0);
Add(14, OpCode.Move, rcx, rax);
Add(15, OpCode.Move, r8, 0);
Add(16, OpCode.CallVoid, (ulong)0x180267A70, rcx, rdx, r8);
Add(17, OpCode.Return);
Add(18, OpCode.Return);

Assert.That(isil.Count == instructions.Count,
$"expected instruction count to be {instructions.Count}, but got {isil.Count}");

for (var i = 0; i < instructions.Count; i++)
{
var instruction = instructions[i];
if (instruction.OpCode is OpCode.Jump or OpCode.ConditionalJump)
instruction.Operands[0] = instructions[(int)instruction.Operands[0]];

Assert.True(instruction == isil[i], $"expected: {instruction}, but got {isil[i]}");
}
}
}

71 changes: 68 additions & 3 deletions Cpp2IL.Core/ISIL/Instruction.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
Expand All @@ -6,7 +7,7 @@

namespace Cpp2IL.Core.ISIL;

public class Instruction(int index, OpCode opcode, params object[] operands)
public class Instruction(int index, OpCode opcode, params object[] operands) : IEquatable<Instruction>
{
public int Index = index;

Expand Down Expand Up @@ -118,9 +119,17 @@ public override string ToString()
return $"{Index} {OpCode} {jumpTarget2:X4}, {FormatOperand(Operands[1])}";

if ((OpCode is OpCode.CallVoid or OpCode.Call) && Operands[0] is ulong callTarget)
return $"{Index} {OpCode} {callTarget:X4}, {string.Join(", ", Operands.Skip(1).Select(FormatOperand))}";
{
var remainingOperands = string.Join(", ", Operands.Skip(1).Select(FormatOperand));
return string.IsNullOrEmpty(remainingOperands)
? $"{Index} {OpCode} {callTarget:X4}"
: $"{Index} {OpCode} {callTarget:X4}, {remainingOperands}";
}

return $"{Index} {OpCode} {string.Join(", ", Operands.Select(FormatOperand))}";
var formattedOperands = string.Join(", ", Operands.Select(FormatOperand));
return string.IsNullOrEmpty(formattedOperands)
? $"{Index} {OpCode}"
: $"{Index} {OpCode} {formattedOperands}";
}

private static string FormatOperand(object operand)
Expand All @@ -147,4 +156,60 @@ public static bool IsConstantValue(object operand) =>
MemoryOperand memory => memory.IsConstant,
_ => true
};

public static bool operator ==(Instruction? left, Instruction? right)
{
if (left is null && right is null)
return true;
if (left is null || right is null)
return false;

return left.Equals(right);
}

public static bool operator !=(Instruction? left, Instruction? right) => !(left == right);

public bool Equals(Instruction? other)
{
if (ReferenceEquals(this, other))
return true;

if (other is null)
return false;

if (OpCode != other.OpCode)
return false;

if (Index != other.Index)
return false;

if (Operands.Count != other.Operands.Count)
return false;

for (int i = 0; i < Operands.Count; i++)
{
var thisOperand = Operands[i];
var otherOperand = other.Operands[i];

if (!thisOperand.Equals(otherOperand))
return false;
}

return true;
}

public override bool Equals(object? obj) => obj is Instruction other && Equals(other);

public override int GetHashCode()
{
unchecked
{
var hashCode = Index;
hashCode = (hashCode * 397) ^ OpCode.GetHashCode();
foreach (var operand in Operands)
hashCode = (hashCode * 397) ^ operand.GetHashCode();
return hashCode;
}
}

}
Loading