#include "gtest/gtest.h" #include "decompiler/IR2/AtomicOp.h" #include "decompiler/IR2/AtomicOpBuilder.h" #include "decompiler/Disasm/InstructionParser.h" #include "third-party/fmt/core.h" #include "third-party/fmt/format.h" #include using namespace decompiler; std::regex labelRegex("^L\\d+:\\s*$"); // Auto indents / adds new-lines to a list of assembly lines std::string assembly_from_list(std::vector assemblyLines) { std::string str = ""; for (std::string line : assemblyLines) { if (std::regex_match(line, labelRegex)) { str += fmt::format("{}\n", line); } else { str += fmt::format(" {}\n", line); } } return str; } void test_case(std::string assembly_lines, std::vector output_lines, std::vector> write_regs, std::vector> read_regs, std::vector> clobbered_regs) { InstructionParser parser; // convert to Instructions: // some MIPS instructions. Can be a sequence of instructions, possibly with labels. ParsedProgram prg = parser.parse_program(assembly_lines); // this verifies we can convert from a string to an instruction, and back to a string again. // the instruction printer will add two leading spaces and a newline. EXPECT_EQ(prg.print(), assembly_lines); // next, set up a test environment for the conversion. The FunctionAtomicOps will hold // the result of the conversion FunctionAtomicOps container; // treat the entire program as a single basic block, and convert! convert_block_to_atomic_ops(0, prg.instructions.begin(), prg.instructions.end(), prg.labels, &container); // count operations EXPECT_EQ(container.ops.size(), output_lines.size()); // for now, we create an empty environment. The environment will be used in the future to // rename register to variables, but for now, we just leave it empty and the printing will // use register names Env env; // check the we get the right result: for (size_t i = 0; i < container.ops.size(); i++) { const auto& op = container.ops.at(i); EXPECT_EQ(op->to_string(prg.labels, &env), output_lines.at(i)); // check that the registers read/written are identified for the operation // check write registers EXPECT_EQ(op->write_regs().size(), write_regs.at(i).size()); for (size_t j = 0; j < op->write_regs().size(); j++) { const std::string expected_reg = op->write_regs().at(j).to_string(); // the ordering of the registers doesn't matter. It could happen to be in the same order // as the opcode here, but it may not always be the case. bool found = false; for (const std::string reg : write_regs.at(i)) { // TODO - is there a potential bug here in the event that either list has duplicate // registers? if (reg == expected_reg) { found = true; break; } } EXPECT_TRUE(found) << fmt::format("Unable to find expected WRITE register - {}", expected_reg); } // check read registers EXPECT_EQ(op->read_regs().size(), read_regs.at(i).size()); for (size_t j = 0; j < op->read_regs().size(); j++) { const std::string expected_reg = op->read_regs().at(j).to_string(); // the ordering of the registers doesn't matter. It could happen to be in the same order // as the opcode here, but it may not always be the case. bool found = false; for (const std::string reg : read_regs.at(i)) { // TODO - is there a potential bug here in the event that either list has duplicate // registers? if (reg == expected_reg) { found = true; break; } } EXPECT_TRUE(found) << fmt::format("Unable to find expected READ register - {}", expected_reg); } // check clobbered registers EXPECT_EQ(op->clobber_regs().size(), clobbered_regs.at(i).size()); for (size_t j = 0; j < op->clobber_regs().size(); j++) { const std::string expected_reg = op->clobber_regs().at(j).to_string(); // the ordering of the registers doesn't matter. It could happen to be in the same order // as the opcode here, but it may not always be the case. bool found = false; for (const std::string reg : clobbered_regs.at(i)) { // TODO - is there a potential bug here in the event that either list has duplicate // registers? if (reg == expected_reg) { found = true; break; } } EXPECT_TRUE(found) << fmt::format("Unable to find expected CLOBBERED register - {}", expected_reg); } } } TEST(DecompilerAtomicOpBuilder, Example) { test_case(assembly_from_list({"and v0, v1, a3", "and a1, a2, a2"}), {"(set! v0 (logand v1 a3))", "(set! a1 (logand a2 a2))"}, {{"v0"}, {"a1"}}, {{"v1", "a3"}, {"a2", "a2"}}, {{}, {}}); } TEST(DecompilerAtomicOpBuilder, ABS_S) { test_case(assembly_from_list({"abs.s f1, f2"}), {"(set! f1 (abs.s f2))"}, {{"f1"}}, {{"f2"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, ADDIU) { test_case(assembly_from_list({"addiu a1, r0, 12"}), {"(set! a1 12)"}, {{"a1"}}, {{}}, {{}}); } TEST(DecompilerAtomicOpBuilder, ADD_S) { test_case(assembly_from_list({"add.s f1, f2, f3"}), {"(set! f1 (+.s f2 f3))"}, {{"f1"}}, {{"f2", "f3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, AND) { test_case(assembly_from_list({"and a1, a2, a3"}), {"(set! a1 (logand a2 a3))"}, {{"a1"}}, {{"a2", "a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, ANDI) { test_case(assembly_from_list({"andi a1, a2, 1234"}), {"(set! a1 (logand a2 1234))"}, {{"a1"}}, {{"a2"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, CVT_S_W) { test_case(assembly_from_list({"cvt.s.w f1, f2"}), {"(set! f1 (i2f f2))"}, {{"f1"}}, {{"f2"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, CVT_W_S) { test_case(assembly_from_list({"cvt.w.s f1, f2"}), {"(set! f1 (f2i f2))"}, {{"f1"}}, {{"f2"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, DADDIU) { test_case(assembly_from_list({"daddiu a1, s7, test"}), {"(set! a1 'test)"}, {{"a1"}}, {{}}, {{}}); test_case(assembly_from_list({"daddiu a1, s7, -10"}), {"(set! a1 '())"}, {{"a1"}}, {{}}, {{}}); test_case(assembly_from_list({"daddiu a1, s7, -32768"}), {"(set! a1 __START-OF-TABLE__)"}, {{"a1"}}, {{}}, {{}}); test_case(assembly_from_list({"daddiu a1, s7, 8"}), {"(set! a1 '#t)"}, {{"a1"}}, {{}}, {{}}); test_case(assembly_from_list({"L123:", "daddiu a1, fp, L123"}), {"(set! a1 L123)"}, {{"a1"}}, {{}}, {{}}); test_case(assembly_from_list({"daddiu a1, a2, 1234"}), {"(set! a1 (+ a2 1234))"}, {{"a1"}}, {{"a2"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, DADDU) { test_case(assembly_from_list({"daddu a1, a2, a3"}), {"(set! a1 (+ a2 a3))"}, {{"a1"}}, {{"a2", "a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, DIV_S) { test_case(assembly_from_list({"div.s f1, f2, f3"}), {"(set! f1 (/.s f2 f3))"}, {{"f1"}}, {{"f2", "f3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, DSLL) { test_case(assembly_from_list({"dsll a2, a3, 3"}), {"(set! a2 (shl a3 3))"}, {{"a2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, DSLL32) { test_case(assembly_from_list({"dsll32 a2, a3, 3"}), {"(set! a2 (shl a3 35))"}, {{"a2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, DSLLV) { test_case(assembly_from_list({"dsllv a1, a2, a3"}), {"(set! a1 (shl a2 a3))"}, {{"a1"}}, {{"a2", "a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, DSRA) { test_case(assembly_from_list({"dsra a2, a3, 3"}), {"(set! a2 (sra a3 3))"}, {{"a2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, DSRA32) { test_case(assembly_from_list({"dsra32 a2, a3, 3"}), {"(set! a2 (sra a3 35))"}, {{"a2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, DSRAV) { test_case(assembly_from_list({"dsrav a1, a2, a3"}), {"(set! a1 (sra a2 a3))"}, {{"a1"}}, {{"a2", "a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, DSRL) { test_case(assembly_from_list({"dsrl a2, a3, 3"}), {"(set! a2 (srl a3 3))"}, {{"a2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, DSRL32) { test_case(assembly_from_list({"dsrl32 a2, a3, 3"}), {"(set! a2 (srl a3 35))"}, {{"a2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, DSRLV) { test_case(assembly_from_list({"dsrlv a1, a2, a3"}), {"(set! a1 (srl a2 a3))"}, {{"a1"}}, {{"a2", "a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, DSUBU) { test_case(assembly_from_list({"dsubu a1, a2, a3"}), {"(set! a1 (- a2 a3))"}, {{"a1"}}, {{"a2", "a3"}}, {{}}); test_case(assembly_from_list({"dsubu a1, r0, a3"}), {"(set! a1 (- a3))"}, {{"a1"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, LB) { test_case(assembly_from_list({"L123:", "lb a3, L123(fp)"}), {"(set! a3 (l.b L123))"}, {{"a3"}}, {{}}, {{}}); test_case(assembly_from_list({"lb a2, 0(a3)"}), {"(set! a2 (l.b a3))"}, {{"a2"}}, {{"a3"}}, {{}}); test_case(assembly_from_list({"lb a2, 12(a3)"}), {"(set! a2 (l.b (+ a3 12)))"}, {{"a2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, LBU) { test_case(assembly_from_list({"L123:", "lbu a3, L123(fp)"}), {"(set! a3 (l.bu L123))"}, {{"a3"}}, {{}}, {{}}); test_case(assembly_from_list({"lbu a2, 0(a3)"}), {"(set! a2 (l.bu a3))"}, {{"a2"}}, {{"a3"}}, {{}}); test_case(assembly_from_list({"lbu a2, 12(a3)"}), {"(set! a2 (l.bu (+ a3 12)))"}, {{"a2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, LD) { test_case(assembly_from_list({"L123:", "ld a3, L123(fp)"}), {"(set! a3 (l.d L123))"}, {{"a3"}}, {{}}, {{}}); test_case(assembly_from_list({"ld a2, 0(a3)"}), {"(set! a2 (l.d a3))"}, {{"a2"}}, {{"a3"}}, {{}}); test_case(assembly_from_list({"ld a2, 12(a3)"}), {"(set! a2 (l.d (+ a3 12)))"}, {{"a2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, LH) { test_case(assembly_from_list({"L123:", "lh a3, L123(fp)"}), {"(set! a3 (l.h L123))"}, {{"a3"}}, {{}}, {{}}); test_case(assembly_from_list({"lh a2, 0(a3)"}), {"(set! a2 (l.h a3))"}, {{"a2"}}, {{"a3"}}, {{}}); test_case(assembly_from_list({"lh a2, 12(a3)"}), {"(set! a2 (l.h (+ a3 12)))"}, {{"a2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, LHU) { test_case(assembly_from_list({"L123:", "lhu a3, L123(fp)"}), {"(set! a3 (l.hu L123))"}, {{"a3"}}, {{}}, {{}}); test_case(assembly_from_list({"lhu a2, 0(a3)"}), {"(set! a2 (l.hu a3))"}, {{"a2"}}, {{"a3"}}, {{}}); test_case(assembly_from_list({"lhu a2, 12(a3)"}), {"(set! a2 (l.hu (+ a3 12)))"}, {{"a2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, LUI) { test_case(assembly_from_list({"lui a3, 2"}), {"(set! a3 131072)"}, {{"a3"}}, {{}}, {{}}); } TEST(DecompilerAtomicOpBuilder, LW) { test_case(assembly_from_list({"lw r0, 2(r0)"}), {"(break!)"}, {{}}, {{}}, {{}}); test_case(assembly_from_list({"lw a2, test(s7)"}), {"(set! a2 test)"}, {{"a2"}}, {{}}, {{}}); test_case(assembly_from_list({"L123:", "lw a3, L123(fp)"}), {"(set! a3 (l.w L123))"}, {{"a3"}}, {{}}, {{}}); test_case(assembly_from_list({"lw a2, 0(a3)"}), {"(set! a2 (l.w a3))"}, {{"a2"}}, {{"a3"}}, {{}}); test_case(assembly_from_list({"lw a2, 12(a3)"}), {"(set! a2 (l.w (+ a3 12)))"}, {{"a2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, LWC1) { test_case(assembly_from_list({"L123:", "lwc1 f3, L123(fp)"}), {"(set! f3 (l.f L123))"}, {{"f3"}}, {{}}, {{}}); test_case(assembly_from_list({"lwc1 f2, 0(a3)"}), {"(set! f2 (l.f a3))"}, {{"f2"}}, {{"a3"}}, {{}}); test_case(assembly_from_list({"lwc1 f2, 12(a3)"}), {"(set! f2 (l.f (+ a3 12)))"}, {{"f2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, LWU) { test_case(assembly_from_list({"L123:", "lwu a3, L123(fp)"}), {"(set! a3 (l.wu L123))"}, {{"a3"}}, {{}}, {{}}); test_case(assembly_from_list({"lwu a2, 0(a3)"}), {"(set! a2 (l.wu a3))"}, {{"a2"}}, {{"a3"}}, {{}}); test_case(assembly_from_list({"lwu a2, 12(a3)"}), {"(set! a2 (l.wu (+ a3 12)))"}, {{"a2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, MAX_S) { test_case(assembly_from_list({"max.s f1, f2, f3"}), {"(set! f1 (max.s f2 f3))"}, {{"f1"}}, {{"f2", "f3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, MFC1) { test_case(assembly_from_list({"mfc1 a1, f3"}), {"(set! a1 (fpr->gpr f3))"}, {{"a1"}}, {{"f3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, MIN_S) { test_case(assembly_from_list({"min.s f1, f2, f3"}), {"(set! f1 (min.s f2 f3))"}, {{"f1"}}, {{"f2", "f3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, MOVN) { test_case(assembly_from_list({"movn a1, s7, a2"}), {"(cmove-#f-nonzero a1 a2)"}, {{"a1"}}, {{"a2"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, MOVZ) { test_case(assembly_from_list({"movz a1, s7, a2"}), {"(cmove-#f-zero a1 a2)"}, {{"a1"}}, {{"a2"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, MOV_S) { test_case(assembly_from_list({"mov.s f1, f2"}), {"(set! f1 f2)"}, {{"f1"}}, {{"f2"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, MTC1) { test_case(assembly_from_list({"mtc1 f3, a1"}), {"(set! f3 (gpr->fpr a1))"}, {{"f3"}}, {{"a1"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, MULT3) { test_case(assembly_from_list({"mult3 a1, a2, a3"}), {"(set! a1 (*.si a2 a3))"}, {{"a1"}}, {{"a2", "a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, MULTU3) { test_case(assembly_from_list({"multu3 a1, a2, a3"}), {"(set! a1 (*.ui a2 a3))"}, {{"a1"}}, {{"a2", "a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, MUL_S) { test_case(assembly_from_list({"mul.s f1, f2, f3"}), {"(set! f1 (*.s f2 f3))"}, {{"f1"}}, {{"f2", "f3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, NEG_S) { test_case(assembly_from_list({"neg.s f1, f2"}), {"(set! f1 (neg.s f2))"}, {{"f1"}}, {{"f2"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, NOR) { test_case(assembly_from_list({"nor a1, a2, r0"}), {"(set! a1 (lognot a2))"}, {{"a1"}}, {{"a2"}}, {{}}); test_case(assembly_from_list({"nor a1, a2, a3"}), {"(set! a1 (lognor a2 a3))"}, {{"a1"}}, {{"a2", "a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, OR) { test_case(assembly_from_list({"or a1, a2, a3"}), {"(set! a1 (logior a2 a3))"}, {{"a1"}}, {{"a2", "a3"}}, {{}}); test_case(assembly_from_list({"or a2, r0, r0"}), {"(set! a2 0)"}, {{"a2"}}, {{}}, {{}}); test_case(assembly_from_list({"or a1, s7, r0"}), {"(set! a1 '#f)"}, {{"a1"}}, {{}}, {{}}); } TEST(DecompilerAtomicOpBuilder, ORI) { test_case(assembly_from_list({"ori a2, r0, 1234"}), {"(set! a2 1234)"}, {{"a2"}}, {{}}, {{}}); test_case(assembly_from_list({"ori a2, r0, -1234"}), {"(set! a2 -1234)"}, {{"a2"}}, {{}}, {{}}); test_case(assembly_from_list({"ori a2, a3, -1234"}), {"(set! a2 (logior a3 -1234))"}, {{"a2"}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, SB) { test_case(assembly_from_list({"sb a1, 2(a3)"}), {"(s.b! (+ a3 2) a1)"}, {{}}, {{"a1", "a3"}}, {{}}); test_case(assembly_from_list({"sb a1, 0(a3)"}), {"(s.b! a3 a1)"}, {{}}, {{"a1", "a3"}}, {{}}); test_case(assembly_from_list({"sb s7, 2(a3)"}), {"(s.b! (+ a3 2) '#f)"}, {{}}, {{"a3"}}, {{}}); test_case(assembly_from_list({"sb s7, 0(a3)"}), {"(s.b! a3 '#f)"}, {{}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, SD) { test_case(assembly_from_list({"sd a1, 2(a3)"}), {"(s.d! (+ a3 2) a1)"}, {{}}, {{"a1", "a3"}}, {{}}); test_case(assembly_from_list({"sd a1, 0(a3)"}), {"(s.d! a3 a1)"}, {{}}, {{"a1", "a3"}}, {{}}); test_case(assembly_from_list({"sd s7, 2(a3)"}), {"(s.d! (+ a3 2) '#f)"}, {{}}, {{"a3"}}, {{}}); test_case(assembly_from_list({"sd s7, 0(a3)"}), {"(s.d! a3 '#f)"}, {{}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, SH) { test_case(assembly_from_list({"sh a1, 2(a3)"}), {"(s.h! (+ a3 2) a1)"}, {{}}, {{"a1", "a3"}}, {{}}); test_case(assembly_from_list({"sh a1, 0(a3)"}), {"(s.h! a3 a1)"}, {{}}, {{"a1", "a3"}}, {{}}); test_case(assembly_from_list({"sh s7, 2(a3)"}), {"(s.h! (+ a3 2) '#f)"}, {{}}, {{"a3"}}, {{}}); test_case(assembly_from_list({"sh s7, 0(a3)"}), {"(s.h! a3 '#f)"}, {{}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, SLL) { test_case(assembly_from_list({"sll r0, r0, 0"}), {"(nop!)"}, {{}}, {{}}, {{}}); } TEST(DecompilerAtomicOpBuilder, SQRT_S) { test_case(assembly_from_list({"sqrt.s f1, f2"}), {"(set! f1 (sqrt.s f2))"}, {{"f1"}}, {{"f2"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, SUB_S) { test_case(assembly_from_list({"sub.s f1, f2, f3"}), {"(set! f1 (-.s f2 f3))"}, {{"f1"}}, {{"f2", "f3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, SW) { test_case(assembly_from_list({"sw a1, test(s7)"}), {"(s.w! test a1)"}, {{}}, {{"a1"}}, {{}}); test_case(assembly_from_list({"sw s7, test(s7)"}), {"(s.w! test '#f)"}, {{}}, {{}}, {{}}); test_case(assembly_from_list({"sw a1, 2(a3)"}), {"(s.w! (+ a3 2) a1)"}, {{}}, {{"a1", "a3"}}, {{}}); test_case(assembly_from_list({"sw a1, 0(a3)"}), {"(s.w! a3 a1)"}, {{}}, {{"a1", "a3"}}, {{}}); test_case(assembly_from_list({"sw s7, 2(a3)"}), {"(s.w! (+ a3 2) '#f)"}, {{}}, {{"a3"}}, {{}}); test_case(assembly_from_list({"sw s7, 0(a3)"}), {"(s.w! a3 '#f)"}, {{}}, {{"a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, SWC1) { test_case(assembly_from_list({"swc1 f2, 2(a3)"}), {"(s.f! (+ a3 2) f2)"}, {{}}, {{"f2", "a3"}}, {{}}); test_case(assembly_from_list({"swc1 f2, 0(a3)"}), {"(s.f! a3 f2)"}, {{}}, {{"f2", "a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, XOR) { test_case(assembly_from_list({"xor a1, a2, a3"}), {"(set! a1 (logxor a2 a3))"}, {{"a1"}}, {{"a2", "a3"}}, {{}}); } TEST(DecompilerAtomicOpBuilder, XORI) { test_case(assembly_from_list({"xori a1, a2, 1234"}), {"(set! a1 (logxor a2 1234))"}, {{"a1"}}, {{"a2"}}, {{}}); }