Skip to content

Commit

Permalink
dual: arm: Add whole-function disassembly for IR
Browse files Browse the repository at this point in the history
  • Loading branch information
fleroviux committed Feb 1, 2025
1 parent 8e4a7af commit 914891a
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 80 deletions.
3 changes: 1 addition & 2 deletions src/dual/src/arm/dynarec/backend/arm64/arm64_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ void ARM64Backend::Execute(const ir::Function& function) {
code.MOVP2R(XReg_State, &m_cpu_state);

fmt::print("===========================================\n");
fmt::print("IR function:\n{}\n", ir::disassemble(function));

for(size_t bb_index = 0u; bb_index < function.basic_blocks.size(); bb_index++) {
const ir::BasicBlock& basic_block = function.basic_blocks[bb_index];
Expand Down Expand Up @@ -212,8 +213,6 @@ void ARM64Backend::Execute(const ir::Function& function) {
instruction = instruction->next;
}

fmt::print("bb_{}:\n{}\n", bb_index, ir::disassemble(basic_block));

if(!got_terminal_instruction) {
ATOM_PANIC("Basic block did not end in a terminal instruction!");
}
Expand Down
12 changes: 11 additions & 1 deletion src/dual/src/arm/dynarec/ir/basic_block.hpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@

#pragma once

#include <atom/non_copyable.hpp>
#include <algorithm>
#include <vector>

#include "instruction.hpp"
#include "value.hpp"

namespace dual::arm::jit::ir {

struct BasicBlock {
struct BasicBlock : atom::NonCopyable {
Instruction* head{};
Instruction* tail{};
std::vector<Value*> values{};

// Needed because the implicit move constructor is deleted due to atom::NonCopyable(?)
BasicBlock() = default;
BasicBlock(BasicBlock&& basic_block) {
std::swap(head, basic_block.head);
std::swap(tail, basic_block.tail);
std::swap(values, basic_block.values);
}
};

} // namespace dual::arm::jit::ir
192 changes: 118 additions & 74 deletions src/dual/src/arm/dynarec/ir/disassemble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,119 @@

namespace dual::arm::jit::ir {

static const char* get_instruction_mnemonic(Instruction::Type type);
static const char* get_cpu_mode_label(Mode cpu_mode);
static const char* get_condition_label(Condition condition);
static const char* get_data_type_label(Value::DataType data_type);

std::string disassemble(const Function& function) {
std::string disassembled_code{};
disassembled_code.reserve(4096); // 4 KiB should be enough to fit near all (if not all) cases

disassembled_code += fmt::format("fun_PLACEHOLDER() {{\n");

size_t bb_index = 0u;
for(const BasicBlock& basic_block : function.basic_blocks) {
disassembled_code += fmt::format("\tbb_{}:\n", bb_index++);
disassembled_code += disassemble(basic_block, "\t\t");
}

disassembled_code += "}";

return disassembled_code;
}

std::string disassemble(const BasicBlock& basic_block, const char* indent) {
std::string disassembled_code{};
disassembled_code.reserve(4096); // 4 KiB should be enough to fit near all (if not all) cases

if(!basic_block.values.empty()) {
for(const Value *value: basic_block.values) {
if(value->create_ref.instruction != nullptr) { // Skip orphaned values from instructions that no longer exist.
disassembled_code += indent + fmt::format("{} \tv{}\n", get_data_type_label(value->data_type), value->id);
}
}
disassembled_code += "\n";
}

const Instruction* instruction = basic_block.head;
while(instruction != nullptr) {
disassembled_code += indent + disassemble(*instruction) + "\n";
instruction = instruction->next;
}

return disassembled_code;
}

std::string disassemble(const Instruction& instruction) {
const size_t out_slot_count = instruction.out_slot_count;
const size_t arg_slot_count = instruction.arg_slot_count;

std::string disassembled_code{};
disassembled_code.reserve(100);

if(out_slot_count != 0u) {
for(size_t slot = 0; slot < out_slot_count; slot++) {
disassembled_code += fmt::format("v{}", instruction.out_slots[slot]);
if(slot != out_slot_count - 1) {
disassembled_code += ", ";
}
}

disassembled_code += "\t := ";
} else {
disassembled_code += "\t "; // indent to same level as other instructions
}

disassembled_code += get_instruction_mnemonic(instruction.type);
if(instruction.flags) {
disassembled_code += ".";
if(instruction.flags & Instruction::Flag::OutputHostFlags) disassembled_code += "s";
}
disassembled_code += " ";

for(size_t slot = 0; slot < arg_slot_count; slot++) {
const Input& arg = instruction.arg_slots[slot];

switch(arg.GetType()) {
case Input::Type::Null: {
disassembled_code += "(null)";
break;
}
case Input::Type::Value: {
disassembled_code += fmt::format("v{}", arg.AsValue());
break;
}
case Input::Type::GPR: {
disassembled_code += fmt::format("r{}", (int)arg.AsGPR());
break;
}
case Input::Type::Mode: {
disassembled_code += "%";
disassembled_code += get_cpu_mode_label(arg.AsMode());
break;
}
case Input::Type::ConstU32: {
disassembled_code += fmt::format("0x{:08X}_u32", arg.AsConstU32());
break;
}
case Input::Type::Condition: {
disassembled_code += get_condition_label(arg.AsCondition());
break;
}
default: {
ATOM_PANIC("unhandled input type: {}", (int)arg.GetType());
}
}

if(slot != arg_slot_count - 1) {
disassembled_code += ", ";
}
}

return disassembled_code;
}

static const char* get_instruction_mnemonic(Instruction::Type type) {
switch(type) {
case Instruction::Type::LDCONST: return "ldconst";
Expand Down Expand Up @@ -59,81 +172,12 @@ static const char* get_condition_label(Condition condition) {
}
}

std::string disassemble(const BasicBlock& basic_block) {
std::string disassembled_code{};
disassembled_code.reserve(4096); // 4 KiB should be enough to fit near all (if not all) cases

const Instruction* instruction = basic_block.head;

while(instruction != nullptr) {
const size_t out_slot_count = instruction->out_slot_count;
const size_t arg_slot_count = instruction->arg_slot_count;

if(out_slot_count != 0u) {
for(size_t slot = 0; slot < out_slot_count; slot++) {
disassembled_code += fmt::format("v{}", instruction->out_slots[slot]);
if(slot != out_slot_count - 1) {
disassembled_code += ", ";
}
}

disassembled_code += "\t := ";
} else {
disassembled_code += "\t "; // indent to same level as other instructions
}

disassembled_code += get_instruction_mnemonic(instruction->type);
if(instruction->flags) {
disassembled_code += ".";
if(instruction->flags & Instruction::Flag::OutputHostFlags) disassembled_code += "s";
}
disassembled_code += " ";

for(size_t slot = 0; slot < arg_slot_count; slot++) {
const Input& arg = instruction->arg_slots[slot];

switch(arg.GetType()) {
case Input::Type::Null: {
disassembled_code += "(null)";
break;
}
case Input::Type::Value: {
disassembled_code += fmt::format("v{}", arg.AsValue());
break;
}
case Input::Type::GPR: {
disassembled_code += fmt::format("r{}", (int)arg.AsGPR());
break;
}
case Input::Type::Mode: {
disassembled_code += "%";
disassembled_code += get_cpu_mode_label(arg.AsMode());
break;
}
case Input::Type::ConstU32: {
disassembled_code += fmt::format("0x{:08X}_u32", arg.AsConstU32());
break;
}
case Input::Type::Condition: {
disassembled_code += get_condition_label(arg.AsCondition());
break;
}
default: {
ATOM_PANIC("unhandled input type: {}", (int)arg.GetType());
}
}

if(slot != arg_slot_count - 1) {
disassembled_code += ", ";
}
}

disassembled_code += "\n";

instruction = instruction->next;
static const char* get_data_type_label(Value::DataType data_type) {
switch(data_type) {
case Value::DataType::U32: return "U32";
case Value::DataType::HostFlags: return "HFLAG";
default: ATOM_PANIC("unhandled value data type: {}", (int)data_type);
}

return disassembled_code;
}

} // namespace dual::arm::jit::ir
6 changes: 4 additions & 2 deletions src/dual/src/arm/dynarec/ir/disassemble.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@

#include <string>

#include "basic_block.hpp"
#include "function.hpp"

namespace dual::arm::jit::ir {

std::string disassemble(const BasicBlock& basic_block);
std::string disassemble(const Function& function);
std::string disassemble(const BasicBlock& basic_block, const char* indent = nullptr);
std::string disassemble(const Instruction& instruction);

} // namespace dual::arm::jit::ir
3 changes: 2 additions & 1 deletion src/dual/src/arm/dynarec/ir/function.hpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@

#pragma once

#include <atom/non_copyable.hpp>
#include <vector>

#include "basic_block.hpp"

namespace dual::arm::jit::ir {

struct Function {
struct Function : atom::NonCopyable {
std::vector<BasicBlock> basic_blocks{};
};

Expand Down

0 comments on commit 914891a

Please sign in to comment.