mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 11:26:18 -04:00
add register usage pass (#194)
This commit is contained in:
parent
fe693b5da2
commit
8f86f0f00e
|
@ -32,6 +32,7 @@ add_library(
|
|||
IR2/AtomicOpBuilder.cpp
|
||||
IR2/AtomicOpTypeAnalysis.cpp
|
||||
IR2/Env.cpp
|
||||
IR2/reg_usage.cpp
|
||||
|
||||
ObjectFile/LinkedObjectFile.cpp
|
||||
ObjectFile/LinkedObjectFileCreation.cpp
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
#include "CfgVtx.h"
|
||||
#include "decompiler/util/DecompilerTypeSystem.h"
|
||||
#include "decompiler/util/TP_Type.h"
|
||||
// for RegSet:
|
||||
#include "decompiler/IR2/reg_usage.h"
|
||||
|
||||
namespace decompiler {
|
||||
class LinkedObjectFile;
|
||||
class Function;
|
||||
|
||||
using RegSet = std::unordered_set<Register, Register::hash>;
|
||||
|
||||
struct BasicBlock {
|
||||
int start_word;
|
||||
int end_word;
|
||||
|
|
|
@ -165,6 +165,9 @@ class Function {
|
|||
bool atomic_ops_attempted = false;
|
||||
bool atomic_ops_succeeded = false;
|
||||
std::shared_ptr<FunctionAtomicOps> atomic_ops = nullptr;
|
||||
bool has_reg_use = false;
|
||||
RegUsageInfo reg_use;
|
||||
bool has_type_info = false;
|
||||
Env env;
|
||||
} ir2;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#include "TypeAnalysis.h"
|
||||
#include "decompiler/Function/Function.h"
|
||||
#include "decompiler/IR/IR.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
#include "decompiler/config.h"
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
#pragma once
|
||||
#include "Function.h"
|
|
@ -54,18 +54,17 @@ std::string AtomicOp::reg_type_info_as_string(const TypeState& init_types,
|
|||
|
||||
auto read_mask = regs_to_gpr_mask(m_read_regs);
|
||||
auto write_mask = regs_to_gpr_mask(m_write_regs);
|
||||
auto clobber_mask = regs_to_gpr_mask(m_clobber_regs);
|
||||
|
||||
result += fmt::format("[{}] -> [{}]", init_types.print_gpr_masked(read_mask),
|
||||
end_types.print_gpr_masked(write_mask));
|
||||
|
||||
if (clobber_mask) {
|
||||
result += "cl: ";
|
||||
for (auto& reg : m_clobber_regs) {
|
||||
result += reg.to_string();
|
||||
result += ' ';
|
||||
}
|
||||
}
|
||||
// auto clobber_mask = regs_to_gpr_mask(m_clobber_regs);
|
||||
// if (clobber_mask) {
|
||||
// result += "cl: ";
|
||||
// for (auto& reg : m_clobber_regs) {
|
||||
// result += reg.to_string();
|
||||
// result += ' ';
|
||||
// }
|
||||
// }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
192
decompiler/IR2/reg_usage.cpp
Normal file
192
decompiler/IR2/reg_usage.cpp
Normal file
|
@ -0,0 +1,192 @@
|
|||
#include "reg_usage.h"
|
||||
#include "decompiler/Function/Function.h"
|
||||
|
||||
namespace decompiler {
|
||||
RegUsageInfo::RegUsageInfo(int n_blocks, int n_ops) {
|
||||
block.resize(n_blocks);
|
||||
op.resize(n_ops);
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool in_set(RegSet& set, const Register& obj) {
|
||||
return set.find(obj) != set.end();
|
||||
}
|
||||
|
||||
void phase1(const FunctionAtomicOps& ops, int block_id, RegUsageInfo* out) {
|
||||
int end_op = ops.block_id_to_end_atomic_op.at(block_id);
|
||||
int start_op = ops.block_id_to_first_atomic_op.at(block_id);
|
||||
|
||||
for (int i = end_op; i-- > start_op;) {
|
||||
const auto& instr = ops.ops.at(i);
|
||||
auto& lv = out->op.at(i).live;
|
||||
auto& dd = out->op.at(i).dead;
|
||||
auto& block = out->block.at(block_id);
|
||||
|
||||
// make all read live out
|
||||
auto read = instr->read_regs();
|
||||
lv.clear();
|
||||
for (auto& x : read) {
|
||||
lv.insert(x);
|
||||
}
|
||||
|
||||
// kill things which are overwritten
|
||||
dd.clear();
|
||||
auto write = instr->write_regs();
|
||||
for (auto& x : write) {
|
||||
if (!in_set(lv, x)) {
|
||||
dd.insert(x);
|
||||
}
|
||||
}
|
||||
|
||||
// b.use = i.liveout
|
||||
RegSet use_old = block.use;
|
||||
block.use.clear();
|
||||
for (auto& x : lv) {
|
||||
block.use.insert(x);
|
||||
}
|
||||
// | (bu.use & !i.dead)
|
||||
for (auto& x : use_old) {
|
||||
if (!in_set(dd, x)) {
|
||||
block.use.insert(x);
|
||||
}
|
||||
}
|
||||
|
||||
// b.defs = i.dead
|
||||
RegSet defs_old = block.defs;
|
||||
block.defs.clear();
|
||||
for (auto& x : dd) {
|
||||
block.defs.insert(x);
|
||||
}
|
||||
// | b.defs & !i.lv
|
||||
for (auto& x : defs_old) {
|
||||
if (!in_set(lv, x)) {
|
||||
block.defs.insert(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool phase2(const std::vector<BasicBlock>& blocks, int block_id, RegUsageInfo* info) {
|
||||
bool changed = false;
|
||||
auto& block_info = info->block.at(block_id);
|
||||
const auto& block_obj = blocks.at(block_id);
|
||||
auto out = block_info.defs; // copy
|
||||
|
||||
for (auto s : {block_obj.succ_branch, block_obj.succ_ft}) {
|
||||
if (s == -1) {
|
||||
continue;
|
||||
}
|
||||
for (auto in : info->block.at(s).input) {
|
||||
out.insert(in);
|
||||
}
|
||||
}
|
||||
|
||||
RegSet in = block_info.use;
|
||||
for (auto x : out) {
|
||||
if (!in_set(block_info.defs, x)) {
|
||||
in.insert(x);
|
||||
}
|
||||
}
|
||||
|
||||
if (in != block_info.input || out != block_info.output) {
|
||||
changed = true;
|
||||
block_info.input = in;
|
||||
block_info.output = out;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
void phase3(const FunctionAtomicOps& ops,
|
||||
const std::vector<BasicBlock>& blocks,
|
||||
int block_id,
|
||||
RegUsageInfo* info) {
|
||||
RegSet live_local;
|
||||
const auto& block_obj = blocks.at(block_id);
|
||||
for (auto s : {block_obj.succ_branch, block_obj.succ_ft}) {
|
||||
if (s == -1) {
|
||||
continue;
|
||||
}
|
||||
for (auto i : info->block.at(s).input) {
|
||||
live_local.insert(i);
|
||||
}
|
||||
}
|
||||
|
||||
int end_op = ops.block_id_to_end_atomic_op.at(block_id);
|
||||
int start_op = ops.block_id_to_first_atomic_op.at(block_id);
|
||||
|
||||
for (int i = end_op; i-- > start_op;) {
|
||||
auto& lv = info->op.at(i).live;
|
||||
auto& dd = info->op.at(i).dead;
|
||||
|
||||
RegSet new_live = lv;
|
||||
for (auto x : live_local) {
|
||||
if (!in_set(dd, x)) {
|
||||
new_live.insert(x);
|
||||
}
|
||||
}
|
||||
lv = live_local;
|
||||
live_local = new_live;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RegUsageInfo analyze_ir2_register_usage(const Function& function) {
|
||||
const auto& blocks = function.basic_blocks;
|
||||
const auto& ops = function.ir2.atomic_ops;
|
||||
RegUsageInfo result(blocks.size(), ops->ops.size());
|
||||
|
||||
for (int i = 0; i < int(blocks.size()); i++) {
|
||||
phase1(*ops, i, &result);
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
do {
|
||||
changed = false;
|
||||
for (int i = 0; i < int(blocks.size()); i++) {
|
||||
if (phase2(blocks, i, &result)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
} while (changed);
|
||||
|
||||
for (int i = 0; i < int(blocks.size()); i++) {
|
||||
phase3(*ops, blocks, i, &result);
|
||||
}
|
||||
|
||||
// we want to know if an op "consumes" a register.
|
||||
// this means the value of the register coming in is:
|
||||
// A. read by the operation
|
||||
// B. dead after the operation.
|
||||
// loop over blocks, then
|
||||
for (int i = 0; i < int(ops->ops.size()); i++) {
|
||||
const auto& op = ops->ops.at(i);
|
||||
auto& op_info = result.op.at(i);
|
||||
|
||||
// look at each register we read from:
|
||||
for (auto reg : op->read_regs()) {
|
||||
if (op_info.live.find(reg) == op_info.live.end()) {
|
||||
// not live out, this means we must consume it.
|
||||
op_info.consumes.insert(reg);
|
||||
} else {
|
||||
// the register has a live value, but is it a new value?
|
||||
for (auto wr : op->write_regs()) {
|
||||
if (wr == reg) {
|
||||
op_info.consumes.insert(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// also useful to know, written and unused.
|
||||
for (auto reg : op->write_regs()) {
|
||||
if (op_info.live.find(reg) == op_info.live.end()) {
|
||||
op_info.written_and_unused.insert(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace decompiler
|
30
decompiler/IR2/reg_usage.h
Normal file
30
decompiler/IR2/reg_usage.h
Normal file
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include "decompiler/Disasm/Register.h"
|
||||
|
||||
namespace decompiler {
|
||||
|
||||
class Function;
|
||||
|
||||
using RegSet = std::unordered_set<Register, Register::hash>;
|
||||
|
||||
struct RegUsageInfo {
|
||||
struct PerBlock {
|
||||
RegSet use, defs, input, output;
|
||||
};
|
||||
|
||||
struct PerOp {
|
||||
RegSet live, dead, consumes, written_and_unused;
|
||||
};
|
||||
|
||||
std::vector<PerBlock> block;
|
||||
std::vector<PerOp> op;
|
||||
|
||||
RegUsageInfo() = default;
|
||||
RegUsageInfo(int n_blocks, int n_ops);
|
||||
};
|
||||
|
||||
RegUsageInfo analyze_ir2_register_usage(const Function& function);
|
||||
} // namespace decompiler
|
|
@ -71,6 +71,7 @@ class ObjectFileDB {
|
|||
void ir2_basic_block_pass();
|
||||
void ir2_atomic_op_pass();
|
||||
void ir2_type_analysis_pass();
|
||||
void ir2_register_usage_pass();
|
||||
void ir2_write_results(const std::string& output_dir);
|
||||
std::string ir2_to_file(ObjectFileData& data);
|
||||
std::string ir2_function_to_string(ObjectFileData& data, Function& function, int seg);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "common/util/Timer.h"
|
||||
#include "common/util/FileUtil.h"
|
||||
#include "decompiler/Function/TypeInspector.h"
|
||||
#include "decompiler/IR2/reg_usage.h"
|
||||
|
||||
namespace decompiler {
|
||||
|
||||
|
@ -26,6 +27,8 @@ void ObjectFileDB::analyze_functions_ir2(const std::string& output_dir) {
|
|||
ir2_atomic_op_pass();
|
||||
lg::info("Running type analysis...");
|
||||
ir2_type_analysis_pass();
|
||||
lg::info("Register usage analysis...");
|
||||
ir2_register_usage_pass();
|
||||
lg::info("Writing results...");
|
||||
ir2_write_results(output_dir);
|
||||
}
|
||||
|
@ -239,6 +242,7 @@ void ObjectFileDB::ir2_atomic_op_pass() {
|
|||
* Analyze registers and determine the type in each register at each instruction.
|
||||
* - Figure out the type of each function, from configs.
|
||||
* - Propagate types.
|
||||
* - NOTE: this will update register info usage more accurately for functions.
|
||||
*/
|
||||
void ObjectFileDB::ir2_type_analysis_pass() {
|
||||
Timer timer;
|
||||
|
@ -259,6 +263,7 @@ void ObjectFileDB::ir2_type_analysis_pass() {
|
|||
auto hints = get_config().type_hints_by_function_by_idx[func.guessed_name.to_string()];
|
||||
if (func.run_type_analysis_ir2(ts, dts, data.linked_data, hints)) {
|
||||
successful_functions++;
|
||||
func.ir2.has_type_info = true;
|
||||
} else {
|
||||
func.warnings.append(";; Type analysis failed\n");
|
||||
}
|
||||
|
@ -273,6 +278,25 @@ void ObjectFileDB::ir2_type_analysis_pass() {
|
|||
attempted_functions, non_asm_functions, total_functions, timer.getMs());
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_register_usage_pass() {
|
||||
Timer timer;
|
||||
|
||||
int total_funcs = 0, analyzed_funcs = 0;
|
||||
for_each_function_def_order([&](Function& func, int segment_id, ObjectFileData& data) {
|
||||
(void)segment_id;
|
||||
(void)data;
|
||||
total_funcs++;
|
||||
if (!func.suspected_asm && func.ir2.atomic_ops_succeeded) {
|
||||
analyzed_funcs++;
|
||||
func.ir2.reg_use = analyze_ir2_register_usage(func);
|
||||
func.ir2.has_reg_use = true;
|
||||
}
|
||||
});
|
||||
|
||||
lg::info("{}/{} functions had register usage analyzed in {:.2f} ms", analyzed_funcs, total_funcs,
|
||||
timer.getMs());
|
||||
}
|
||||
|
||||
void ObjectFileDB::ir2_write_results(const std::string& output_dir) {
|
||||
Timer timer;
|
||||
lg::info("Writing IR2 results to file...");
|
||||
|
@ -457,6 +481,17 @@ std::string ObjectFileDB::ir2_function_to_string(ObjectFileData& data, Function&
|
|||
line, printed_comment,
|
||||
op.reg_type_info_as_string(*init_types, func.ir2.env.get_types_after_op(op_id)), 50);
|
||||
}
|
||||
|
||||
if (func.ir2.has_reg_use) {
|
||||
std::string regs;
|
||||
for (auto r : func.ir2.reg_use.op.at(op_id).consumes) {
|
||||
regs += r.to_charp();
|
||||
regs += ' ';
|
||||
}
|
||||
if (!regs.empty()) {
|
||||
append_commented(line, printed_comment, "cs: " + regs, 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto& instr = func.instructions.at(instr_id);
|
||||
// print linked strings
|
||||
|
|
Loading…
Reference in a new issue