mirror of
https://github.com/open-goal/jak-project.git
synced 2024-11-23 14:20:07 +00:00
4d751af38e
Favors the `lg` namespace over `fmt` directly, as this will output the logs to a file / has log levels. I also made assertion errors go to a file, this unfortunately means importing `lg` and hence `fmt` which was attempted to be avoided before. But I'm not sure how else to do this aspect without re-inventing the file logging. We have a lot of commented out prints as well that we should probably cleanup at some point / switch them to trace level and default to `info` level. I noticed the pattern of disabling debug logs behind some boolean, something to consider cleaning up in the future -- if our logs were more structured (knowing where they are coming from) then a lot this boilerplate could be eliminated. Closes #1358
226 lines
6.0 KiB
C++
226 lines
6.0 KiB
C++
#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 read = instr->read_regs();
|
|
auto write = instr->write_regs();
|
|
|
|
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
|
|
lv.clear();
|
|
for (auto& x : read) {
|
|
lv.insert(x);
|
|
}
|
|
|
|
// kill things which are overwritten
|
|
dd.clear();
|
|
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() + 1);
|
|
|
|
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);
|
|
}
|
|
|
|
// compute live in
|
|
for (int block_id = 0; block_id < int(blocks.size()); block_id++) {
|
|
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 instr_id = start_op + 1; instr_id < end_op; instr_id++) {
|
|
result.op.at(instr_id).live_in = result.op.at(instr_id - 1).live;
|
|
}
|
|
|
|
if (end_op > start_op) {
|
|
auto& last_live_out = result.op.at(end_op - 1).live;
|
|
for (auto succ : {blocks.at(block_id).succ_branch, blocks.at(block_id).succ_ft}) {
|
|
if (succ != -1) {
|
|
auto succ_id = ops->block_id_to_first_atomic_op.at(succ); // todo?
|
|
result.op.at(succ_id).live_in.insert(last_live_out.begin(), last_live_out.end());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// special case for the very first op
|
|
auto& first_op_live_out = result.op.at(0).live;
|
|
RegSet first_op_live_in;
|
|
first_op_live_in.insert(first_op_live_out.begin(), first_op_live_out.end());
|
|
for (auto reg : ops->ops.at(0)->write_regs()) {
|
|
first_op_live_in.erase(reg);
|
|
}
|
|
first_op_live_in.insert(ops->ops.at(0)->read_regs().begin(), ops->ops.at(0)->read_regs().end());
|
|
result.op.at(0).live_in = first_op_live_in;
|
|
|
|
// 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()) {
|
|
// lg::print("op {} wau {}\n", op->to_string(function.ir2.env), reg.to_string());
|
|
op_info.written_and_unused.insert(reg);
|
|
}
|
|
}
|
|
}
|
|
|
|
result.op.pop_back();
|
|
ASSERT(result.op.size() == ops->ops.size());
|
|
return result;
|
|
}
|
|
} // namespace decompiler
|