mirror of
https://github.com/AssetRipper/Cpp2IL.git
synced 2026-11-13 17:07:41 -10:00
132 lines
5.8 KiB
C#
132 lines
5.8 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using Cpp2IL.Core.Analysis;
|
|
using Cpp2IL.Core.Graphs;
|
|
using Cpp2IL.Core.ISIL;
|
|
using Cpp2IL.Core.Model.Contexts;
|
|
|
|
namespace Cpp2IL.Core.Tests.Analysis;
|
|
|
|
public class SimplifierTests
|
|
{
|
|
private static MethodAnalysisContext CreateMethod(ISILControlFlowGraph graph, params LocalVariable[] locals)
|
|
{
|
|
var method = (MethodAnalysisContext)RuntimeHelpers.GetUninitializedObject(typeof(MethodAnalysisContext));
|
|
method.ControlFlowGraph = graph;
|
|
method.Locals = locals.ToList();
|
|
method.ParameterLocals = [];
|
|
return method;
|
|
}
|
|
|
|
[Test]
|
|
public void DoesNotInlineAcrossJoinWhenLocalHasMultipleDefinitions()
|
|
{
|
|
var x = new LocalVariable("x", new Register(null, "x"));
|
|
var cond = new LocalVariable("cond", new Register(null, "cond"));
|
|
var selected = new LocalVariable("selected", new Register(null, "selected"));
|
|
var oddText = new LocalVariable("oddText", new Register(null, "oddText"));
|
|
var evenText = new LocalVariable("evenText", new Register(null, "evenText"));
|
|
|
|
var instructions = new List<Instruction>
|
|
{
|
|
new(0, OpCode.CheckNotEqual, cond, x, Imm(1)),
|
|
new(1, OpCode.ConditionalJump, Imm(5), cond),
|
|
new(2, OpCode.Move, oddText, Str("Odd second")),
|
|
new(3, OpCode.Move, selected, oddText),
|
|
new(4, OpCode.Jump, Imm(8)),
|
|
new(5, OpCode.Move, evenText, Str("Even second")),
|
|
new(6, OpCode.Move, selected, evenText),
|
|
new(7, OpCode.Jump, Imm(8)),
|
|
new(8, OpCode.CallVoid, Str("Console.WriteLine"), selected, Imm(0)),
|
|
new(9, OpCode.Return),
|
|
};
|
|
|
|
foreach (var instruction in instructions)
|
|
{
|
|
if (instruction.OpCode is not (OpCode.Jump or OpCode.ConditionalJump))
|
|
continue;
|
|
|
|
instruction.SetOperand(0, instructions[(int)((Immediate)instruction.Operands[0]).Value]);
|
|
}
|
|
|
|
var graph = new ISILControlFlowGraph(instructions);
|
|
var method = CreateMethod(graph, cond, selected, oddText, evenText);
|
|
|
|
Simplifier.Simplify(method);
|
|
|
|
var live = graph.Blocks.SelectMany(b => b.Instructions).ToList();
|
|
var selectedDefinitions = live.Where(i => i.OpCode == OpCode.Move && ReferenceEquals(i.Destination, selected)).ToList();
|
|
Assert.That(selectedDefinitions.Count, Is.EqualTo(2), "both branch assignments to selected must remain");
|
|
|
|
var writeLineCall = live.Single(i => i.OpCode == OpCode.CallVoid && i.Operands[0] is StringLiteral { Value: "Console.WriteLine" });
|
|
Assert.That(ReferenceEquals(writeLineCall.Operands[1], selected), Is.True,
|
|
"join-point call must keep the selected local, not a branch-specific constant");
|
|
}
|
|
|
|
[Test]
|
|
public void DoesNotCorruptUnrelatedConstantMemoryAddends()
|
|
{
|
|
var aLocal = new LocalVariable("a", new Register(null, "a"));
|
|
var bLocal = new LocalVariable("b", new Register(null, "b"));
|
|
|
|
// a := [0xAAAA]; f(a); b := [0xBBBB]; g(b). Inlining a's constant load must not touch the
|
|
// unrelated constant address [0xBBBB] - they are distinct absolute addresses.
|
|
var instructions = new List<Instruction>
|
|
{
|
|
new(0, OpCode.Move, aLocal, new MemoryOperand(null, null, 0xAAAA, 0)),
|
|
new(1, OpCode.CallVoid, Str("f"), aLocal, Imm(0)),
|
|
new(2, OpCode.Move, bLocal, new MemoryOperand(null, null, 0xBBBB, 0)),
|
|
new(3, OpCode.CallVoid, Str("g"), bLocal, Imm(0)),
|
|
new(4, OpCode.Return),
|
|
};
|
|
|
|
var graph = new ISILControlFlowGraph(instructions);
|
|
var method = CreateMethod(graph, aLocal, bLocal);
|
|
|
|
Simplifier.Simplify(method);
|
|
|
|
var live = graph.Blocks.SelectMany(b => b.Instructions).ToList();
|
|
|
|
var gCall = live.Single(i => i.OpCode == OpCode.CallVoid && i.Operands[0] is StringLiteral { Value: "g" });
|
|
Assert.That(gCall.Operands[1], Is.InstanceOf<MemoryOperand>());
|
|
Assert.That(((MemoryOperand)gCall.Operands[1]).Addend, Is.EqualTo(0xBBBBL),
|
|
"an unrelated constant address must not be rewritten by another inline");
|
|
|
|
// The intended inline still happens.
|
|
var fCall = live.Single(i => i.OpCode == OpCode.CallVoid && i.Operands[0] is StringLiteral { Value: "f" });
|
|
Assert.That(fCall.Operands[1], Is.InstanceOf<MemoryOperand>());
|
|
Assert.That(((MemoryOperand)fCall.Operands[1]).Addend, Is.EqualTo(0xAAAAL));
|
|
}
|
|
|
|
[Test]
|
|
public void PropagatesCopyThroughMemoryBase()
|
|
{
|
|
var x = new LocalVariable("x", new Register(null, "x"));
|
|
var y = new LocalVariable("y", new Register(null, "y"));
|
|
var z = new LocalVariable("z", new Register(null, "z"));
|
|
|
|
// x := y; z := [x]; f(z). Copy propagation must rewrite the load's base x -> y (a base must
|
|
// stay a local), so the surviving load reads [y].
|
|
var instructions = new List<Instruction>
|
|
{
|
|
new(0, OpCode.Move, x, y),
|
|
new(1, OpCode.Move, z, new MemoryOperand(x, null, 0, 0)),
|
|
new(2, OpCode.CallVoid, Str("f"), z, Imm(0)),
|
|
new(3, OpCode.Return),
|
|
};
|
|
|
|
var graph = new ISILControlFlowGraph(instructions);
|
|
var method = CreateMethod(graph, x, y, z);
|
|
|
|
Simplifier.Simplify(method);
|
|
|
|
var live = graph.Blocks.SelectMany(b => b.Instructions).ToList();
|
|
var load = live.Single(i => i.Operands.Any(o => o is MemoryOperand));
|
|
var memory = (MemoryOperand)load.Operands.First(o => o is MemoryOperand);
|
|
Assert.That(ReferenceEquals(memory.Base, y), Is.True, "the copy's source must be propagated into the memory base");
|
|
Assert.That(live.Any(i => i.OpCode == OpCode.Move && ReferenceEquals(i.Operands[0], x)), Is.False,
|
|
"the now-dead copy is removed");
|
|
}
|
|
}
|