mirror of
https://github.com/open-goal/jak-project
synced 2026-08-19 22:12:24 -04:00
[decompiler] cleanup to get atomic ops working on jak2 inputs (#1426)
* [decompiler] cleanup to get atomic ops working on jak2 inputs * clang format
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/versions.h"
|
||||
|
||||
/*!
|
||||
* @file symbols.h
|
||||
* The location of fixed symbols in the GOAL symbol table.
|
||||
*/
|
||||
|
||||
namespace jak1_symbols {
|
||||
constexpr int FIX_SYM_EMPTY_CAR = -0xc;
|
||||
constexpr int FIX_SYM_EMPTY_PAIR = -0xa;
|
||||
constexpr int FIX_SYM_EMPTY_CDR = -0x8;
|
||||
@@ -76,3 +79,34 @@ constexpr int FIX_SYM_SOUND = 0x1b8; // ??
|
||||
constexpr int FIX_SYM_DGO = 0x1c0; // ??
|
||||
constexpr int FIX_SYM_TOP_LEVEL = 0x1c8; // ??
|
||||
constexpr int FIX_FIXED_SYM_END_OFFSET = 0x1d0;
|
||||
} // namespace jak1_symbols
|
||||
|
||||
namespace jak2_symbols {
|
||||
constexpr int FIX_SYM_EMPTY_CAR = -0x8;
|
||||
constexpr int FIX_SYM_EMPTY_PAIR = -0x7;
|
||||
constexpr int FIX_SYM_EMPTY_CDR = -0x4;
|
||||
constexpr int FIX_SYM_FALSE = 0x0; // GOAL boolean #f (note that this is equal to the $s7 register)
|
||||
constexpr int FIX_SYM_TRUE = 0x4; // GOAL boolean #t
|
||||
} // namespace jak2_symbols
|
||||
|
||||
constexpr int false_symbol_offset() {
|
||||
return jak1_symbols::FIX_SYM_FALSE;
|
||||
}
|
||||
|
||||
constexpr int true_symbol_offset(GameVersion version) {
|
||||
switch (version) {
|
||||
case GameVersion::Jak1:
|
||||
return jak1_symbols::FIX_SYM_TRUE;
|
||||
case GameVersion::Jak2:
|
||||
return jak2_symbols::FIX_SYM_TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int empty_pair_offset(GameVersion version) {
|
||||
switch (version) {
|
||||
case GameVersion::Jak1:
|
||||
return jak1_symbols::FIX_SYM_EMPTY_PAIR;
|
||||
case GameVersion::Jak2:
|
||||
return jak2_symbols::FIX_SYM_EMPTY_PAIR;
|
||||
}
|
||||
}
|
||||
@@ -2612,7 +2612,7 @@ std::shared_ptr<ControlFlowGraph> build_cfg(
|
||||
Function& func,
|
||||
const CondWithElseLengthHack& cond_with_else_hack,
|
||||
const std::unordered_set<int>& blocks_ending_in_asm_br) {
|
||||
// fmt::print("START {}\n", func.guessed_name.to_string());
|
||||
// fmt::print("START {}\n", func.guessed_name.to_string());
|
||||
auto cfg = std::make_shared<ControlFlowGraph>();
|
||||
|
||||
const auto& blocks = cfg->create_blocks(func.basic_blocks.size());
|
||||
|
||||
@@ -508,27 +508,47 @@ void LinkedObjectFile::process_fp_relative_links() {
|
||||
case InstructionKind::DADDU:
|
||||
case InstructionKind::ADDU: {
|
||||
ASSERT(prev_instr);
|
||||
if (prev_instr->kind != InstructionKind::ORI) {
|
||||
if (prev_instr->kind == InstructionKind::ORI) {
|
||||
ASSERT(prev_instr->kind == InstructionKind::ORI);
|
||||
int offset_reg_src_id = instr.kind == InstructionKind::DADDU ? 0 : 1;
|
||||
auto offset_reg = instr.get_src(offset_reg_src_id).get_reg();
|
||||
ASSERT(offset_reg == prev_instr->get_dst(0).get_reg());
|
||||
ASSERT(offset_reg == prev_instr->get_src(0).get_reg());
|
||||
auto& atom = prev_instr->get_imm_src();
|
||||
int additional_offset = 0;
|
||||
if (pprev_instr && pprev_instr->kind == InstructionKind::LUI) {
|
||||
ASSERT(pprev_instr->get_dst(0).get_reg() == offset_reg);
|
||||
additional_offset = (1 << 16) * pprev_instr->get_imm_src().get_imm();
|
||||
pprev_instr->get_imm_src().set_label(
|
||||
get_label_id_for(seg, current_fp + atom.get_imm() + additional_offset));
|
||||
}
|
||||
atom.set_label(
|
||||
get_label_id_for(seg, current_fp + atom.get_imm() + additional_offset));
|
||||
stats.n_fp_reg_use_resolved++;
|
||||
} else if (prev_instr->kind == InstructionKind::DADDIU) {
|
||||
/*
|
||||
* Jak 2 has a new use of fp to access elements of a static array that looks like
|
||||
* this:
|
||||
* (set! v1 (* idx stride))
|
||||
* daddiu v1, v1, 8128
|
||||
* daddu v1, v1, fp
|
||||
*/
|
||||
auto val_plus_off_reg = prev_instr->get_dst(0).get_reg();
|
||||
|
||||
// it's possible that this isn't always the case, but works for all of jak 2
|
||||
ASSERT(val_plus_off_reg == prev_instr->get_src(0).get_reg());
|
||||
ASSERT(val_plus_off_reg == instr.get_src(0).get_reg());
|
||||
ASSERT(val_plus_off_reg == instr.get_dst(0).get_reg());
|
||||
auto& atom = prev_instr->get_imm_src();
|
||||
atom.set_label(get_label_id_for(seg, current_fp + atom.get_imm()));
|
||||
|
||||
stats.n_fp_reg_use_resolved++;
|
||||
} else {
|
||||
lg::error("Failed to process fp relative links for (d)addu preceded by: {}",
|
||||
prev_instr->to_string(labels));
|
||||
return;
|
||||
}
|
||||
ASSERT(prev_instr->kind == InstructionKind::ORI);
|
||||
int offset_reg_src_id = instr.kind == InstructionKind::DADDU ? 0 : 1;
|
||||
auto offset_reg = instr.get_src(offset_reg_src_id).get_reg();
|
||||
ASSERT(offset_reg == prev_instr->get_dst(0).get_reg());
|
||||
ASSERT(offset_reg == prev_instr->get_src(0).get_reg());
|
||||
auto& atom = prev_instr->get_imm_src();
|
||||
int additional_offset = 0;
|
||||
if (pprev_instr && pprev_instr->kind == InstructionKind::LUI) {
|
||||
ASSERT(pprev_instr->get_dst(0).get_reg() == offset_reg);
|
||||
additional_offset = (1 << 16) * pprev_instr->get_imm_src().get_imm();
|
||||
pprev_instr->get_imm_src().set_label(
|
||||
get_label_id_for(seg, current_fp + atom.get_imm() + additional_offset));
|
||||
}
|
||||
atom.set_label(
|
||||
get_label_id_for(seg, current_fp + atom.get_imm() + additional_offset));
|
||||
stats.n_fp_reg_use_resolved++;
|
||||
|
||||
} break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -39,12 +39,6 @@ void ObjectFileDB::analyze_functions_ir2(
|
||||
const Config& config,
|
||||
const std::unordered_set<std::string>& skip_functions,
|
||||
const std::unordered_map<std::string, std::unordered_set<std::string>>& skip_states) {
|
||||
// First, do basic analysis on the top level:
|
||||
lg::info("Using IR2 analysis...");
|
||||
|
||||
lg::info("Processing top-level functions...");
|
||||
ir2_top_level_pass(config);
|
||||
|
||||
int total_file_count = 0;
|
||||
for (auto& f : obj_files_by_name) {
|
||||
total_file_count += f.second.size();
|
||||
@@ -178,12 +172,12 @@ void ObjectFileDB::ir2_run_mips2c(const Config& config, ObjectFileData& data) {
|
||||
for_each_function_def_order_in_obj(data, [&](Function& func, int) {
|
||||
if (config.hacks.mips2c_functions_by_name.count(func.name())) {
|
||||
lg::info("MIPS2C on {}", func.name());
|
||||
run_mips2c(&func);
|
||||
run_mips2c(&func, config.game_version);
|
||||
}
|
||||
|
||||
auto it = config.hacks.mips2c_jump_table_functions.find(func.name());
|
||||
if (it != config.hacks.mips2c_jump_table_functions.end()) {
|
||||
run_mips2c_jump_table(&func, it->second);
|
||||
run_mips2c_jump_table(&func, it->second, config.game_version);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -390,8 +384,9 @@ void ObjectFileDB::ir2_atomic_op_pass(int seg, const Config& config, ObjectFileD
|
||||
blocks_ending_in_asm_branch = asm_branch_it->second;
|
||||
}
|
||||
|
||||
auto ops = convert_function_to_atomic_ops(func, data.linked_data.labels, func.warnings,
|
||||
inline_asm, blocks_ending_in_asm_branch);
|
||||
auto ops =
|
||||
convert_function_to_atomic_ops(func, data.linked_data.labels, func.warnings, inline_asm,
|
||||
blocks_ending_in_asm_branch, config.game_version);
|
||||
func.ir2.atomic_ops = std::make_shared<FunctionAtomicOps>(std::move(ops));
|
||||
func.ir2.atomic_ops_succeeded = true;
|
||||
func.ir2.env.set_end_var(func.ir2.atomic_ops->end_op().return_var());
|
||||
@@ -877,7 +872,12 @@ std::string ObjectFileDB::ir2_function_to_string(ObjectFileData& data, Function&
|
||||
init_types = &func.ir2.env.get_types_at_block_entry(block_id);
|
||||
}
|
||||
|
||||
for (int instr_id = block.start_word; instr_id < block.end_word; instr_id++) {
|
||||
int start_word = block.start_word;
|
||||
// if we have no prologue, skip the type tag.
|
||||
if (start_word == 0) {
|
||||
start_word = 1;
|
||||
}
|
||||
for (int instr_id = start_word; instr_id < block.end_word; instr_id++) {
|
||||
print_instr_start(instr_id);
|
||||
bool printed_comment = false;
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ namespace {
|
||||
std::unique_ptr<AtomicOp> convert_1(const Instruction& i0,
|
||||
int idx,
|
||||
bool hint_inline_asm,
|
||||
bool force_asm_branch);
|
||||
bool force_asm_branch,
|
||||
GameVersion version);
|
||||
|
||||
//////////////////////
|
||||
// Register Helpers
|
||||
@@ -368,10 +369,10 @@ std::unique_ptr<AtomicOp> make_asm_op(const Instruction& i0, int idx) {
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AtomicOp> convert_1_allow_asm(const Instruction& i0, int idx) {
|
||||
std::unique_ptr<AtomicOp> convert_1_allow_asm(const Instruction& i0, int idx, GameVersion version) {
|
||||
// only used for delay slots, so fine to assume that this can never be an asm branch itself
|
||||
// as there are no branches in delay slots anywhere.
|
||||
auto as_normal = convert_1(i0, idx, false, false);
|
||||
auto as_normal = convert_1(i0, idx, false, false, version);
|
||||
if (as_normal) {
|
||||
return as_normal;
|
||||
}
|
||||
@@ -382,7 +383,7 @@ std::unique_ptr<AtomicOp> convert_1_allow_asm(const Instruction& i0, int idx) {
|
||||
// Branch Helpers
|
||||
////////////////////////
|
||||
|
||||
IR2_BranchDelay get_branch_delay(const Instruction& i0, int idx) {
|
||||
IR2_BranchDelay get_branch_delay(const Instruction& i0, int idx, GameVersion version) {
|
||||
if (is_nop(i0)) {
|
||||
return IR2_BranchDelay(IR2_BranchDelay::Kind::NOP);
|
||||
} else if (is_gpr_3(i0, InstructionKind::OR, {}, rs7(), rr0())) {
|
||||
@@ -391,7 +392,7 @@ IR2_BranchDelay get_branch_delay(const Instruction& i0, int idx) {
|
||||
return IR2_BranchDelay(IR2_BranchDelay::Kind::SET_REG_REG, make_dst_var(i0, idx),
|
||||
make_src_var(i0.get_src(0).get_reg(), idx));
|
||||
} else if (i0.kind == InstructionKind::DADDIU && i0.get_src(0).is_reg(rs7()) &&
|
||||
i0.get_src(1).is_imm(FIX_SYM_TRUE)) {
|
||||
i0.get_src(1).is_imm(true_symbol_offset(version))) {
|
||||
return IR2_BranchDelay(IR2_BranchDelay::Kind::SET_REG_TRUE, make_dst_var(i0, idx));
|
||||
} else if (i0.kind == InstructionKind::LW && i0.get_src(1).is_reg(rs7()) &&
|
||||
i0.get_src(0).is_sym()) {
|
||||
@@ -419,13 +420,14 @@ std::unique_ptr<AtomicOp> make_branch(const IR2_Condition& condition,
|
||||
bool likely,
|
||||
int dest_label,
|
||||
int my_idx,
|
||||
bool force_asm) {
|
||||
bool force_asm,
|
||||
GameVersion version) {
|
||||
ASSERT(!likely);
|
||||
auto branch_delay = get_branch_delay(delay, my_idx);
|
||||
auto branch_delay = get_branch_delay(delay, my_idx, version);
|
||||
if (!force_asm && branch_delay.is_known()) {
|
||||
return std::make_unique<BranchOp>(likely, condition, dest_label, branch_delay, my_idx);
|
||||
} else {
|
||||
auto delay_op = std::shared_ptr<AtomicOp>(convert_1_allow_asm(delay, my_idx));
|
||||
auto delay_op = std::shared_ptr<AtomicOp>(convert_1_allow_asm(delay, my_idx, version));
|
||||
if (!delay_op) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Failed to convert branch delay slot instruction for branch at {}", my_idx));
|
||||
@@ -459,9 +461,10 @@ std::unique_ptr<AtomicOp> make_asm_branch(const IR2_Condition& condition,
|
||||
const Instruction& delay,
|
||||
bool likely,
|
||||
int dest_label,
|
||||
int my_idx) {
|
||||
int my_idx,
|
||||
GameVersion version) {
|
||||
ASSERT(!likely);
|
||||
auto delay_op = std::shared_ptr<AtomicOp>(convert_1_allow_asm(delay, my_idx));
|
||||
auto delay_op = std::shared_ptr<AtomicOp>(convert_1_allow_asm(delay, my_idx, version));
|
||||
if (!delay_op) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("Failed to convert branch delay slot instruction for branch at {}", my_idx));
|
||||
@@ -561,12 +564,12 @@ std::unique_ptr<AtomicOp> convert_lw_1(const Instruction& i0, int idx) {
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<AtomicOp> convert_daddiu_1(const Instruction& i0, int idx) {
|
||||
std::unique_ptr<AtomicOp> convert_daddiu_1(const Instruction& i0, int idx, GameVersion version) {
|
||||
if (i0.get_src(0).is_reg(rs7()) && i0.get_src(1).is_sym()) {
|
||||
// get symbol pointer
|
||||
return std::make_unique<SetVarOp>(
|
||||
make_dst_var(i0, idx), SimpleAtom::make_sym_ptr(i0.get_src(1).get_sym()).as_expr(), idx);
|
||||
} else if (i0.get_src(0).is_reg(rs7()) && i0.get_src(1).is_imm(FIX_SYM_EMPTY_PAIR)) {
|
||||
} else if (i0.get_src(0).is_reg(rs7()) && i0.get_src(1).is_imm(empty_pair_offset(version))) {
|
||||
// get empty pair
|
||||
return std::make_unique<SetVarOp>(make_dst_var(i0, idx),
|
||||
SimpleAtom::make_empty_list().as_expr(), idx);
|
||||
@@ -574,8 +577,7 @@ std::unique_ptr<AtomicOp> convert_daddiu_1(const Instruction& i0, int idx) {
|
||||
// get pointer to beginning of symbol table (this is a bit of a hack)
|
||||
return std::make_unique<SetVarOp>(
|
||||
make_dst_var(i0, idx), SimpleAtom::make_sym_val("__START-OF-TABLE__").as_expr(), idx);
|
||||
} else if (i0.get_src(0).is_reg(rs7()) && i0.get_src(1).is_imm(FIX_SYM_TRUE)) {
|
||||
// get pointer to beginning of symbol table (this is a bit of a hack)
|
||||
} else if (i0.get_src(0).is_reg(rs7()) && i0.get_src(1).is_imm(true_symbol_offset(version))) {
|
||||
return std::make_unique<SetVarOp>(make_dst_var(i0, idx),
|
||||
SimpleAtom::make_sym_ptr("#t").as_expr(), idx);
|
||||
} else if (i0.get_src(0).is_reg(rfp()) && i0.get_src(1).is_label()) {
|
||||
@@ -805,7 +807,8 @@ std::unique_ptr<AtomicOp> convert_subu_1(const Instruction& i0, int idx) {
|
||||
std::unique_ptr<AtomicOp> convert_1(const Instruction& i0,
|
||||
int idx,
|
||||
bool hint_inline_asm,
|
||||
bool force_asm_branch) {
|
||||
bool force_asm_branch,
|
||||
GameVersion version) {
|
||||
switch (i0.kind) {
|
||||
case InstructionKind::OR:
|
||||
return convert_or_1(i0, idx);
|
||||
@@ -870,7 +873,7 @@ std::unique_ptr<AtomicOp> convert_1(const Instruction& i0,
|
||||
case InstructionKind::MAXS:
|
||||
return make_3reg_op(i0, SimpleExpression::Kind::MAX_S, idx);
|
||||
case InstructionKind::DADDIU:
|
||||
return convert_daddiu_1(i0, idx);
|
||||
return convert_daddiu_1(i0, idx, version);
|
||||
case InstructionKind::DADDU:
|
||||
return convert_daddu_1(i0, idx);
|
||||
case InstructionKind::DSUBU:
|
||||
@@ -1003,7 +1006,8 @@ std::unique_ptr<AtomicOp> convert_bne_2(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
int idx,
|
||||
bool likely,
|
||||
bool force_asm) {
|
||||
bool force_asm,
|
||||
GameVersion version) {
|
||||
auto s0 = i0.get_src(0).get_reg();
|
||||
auto s1 = i0.get_src(1).get_reg();
|
||||
auto dest = i0.get_src(2).get_label();
|
||||
@@ -1021,14 +1025,15 @@ std::unique_ptr<AtomicOp> convert_bne_2(const Instruction& i0,
|
||||
make_src_atom(s1, idx));
|
||||
condition.make_flipped();
|
||||
}
|
||||
return make_branch(condition, i1, likely, dest, idx, force_asm);
|
||||
return make_branch(condition, i1, likely, dest, idx, force_asm, version);
|
||||
}
|
||||
|
||||
std::unique_ptr<AtomicOp> convert_beq_2(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
int idx,
|
||||
bool likely,
|
||||
bool force_asm) {
|
||||
bool force_asm,
|
||||
GameVersion version) {
|
||||
auto s0 = i0.get_src(0).get_reg();
|
||||
auto s1 = i0.get_src(1).get_reg();
|
||||
auto dest = i0.get_src(2).get_label();
|
||||
@@ -1053,10 +1058,13 @@ std::unique_ptr<AtomicOp> convert_beq_2(const Instruction& i0,
|
||||
IR2_Condition(IR2_Condition::Kind::EQUAL, make_src_atom(s0, idx), make_src_atom(s1, idx));
|
||||
condition.make_flipped();
|
||||
}
|
||||
return make_branch(condition, i1, likely, dest, idx, force_asm);
|
||||
return make_branch(condition, i1, likely, dest, idx, force_asm, version);
|
||||
}
|
||||
|
||||
std::unique_ptr<AtomicOp> convert_daddiu_2(const Instruction& i0, const Instruction& i1, int idx) {
|
||||
std::unique_ptr<AtomicOp> convert_daddiu_2(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
int idx,
|
||||
GameVersion version) {
|
||||
// daddiu dest, s7, 8
|
||||
// mov{n,z} dest, s7, src
|
||||
if (i1.kind == InstructionKind::MOVN || i1.kind == InstructionKind::MOVZ) {
|
||||
@@ -1066,7 +1074,7 @@ std::unique_ptr<AtomicOp> convert_daddiu_2(const Instruction& i0, const Instruct
|
||||
return nullptr;
|
||||
}
|
||||
ASSERT(i0.get_src(0).is_reg(rs7()));
|
||||
ASSERT(i0.get_src(1).is_imm(8));
|
||||
ASSERT(i0.get_src(1).is_imm(true_symbol_offset(version)));
|
||||
ASSERT(i1.get_dst(0).is_reg(dest));
|
||||
ASSERT(i1.get_src(0).is_reg(rs7()));
|
||||
auto kind =
|
||||
@@ -1143,42 +1151,55 @@ std::unique_ptr<AtomicOp> convert_slt_2(const Instruction& i0,
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<AtomicOp> convert_bltz_2(const Instruction& i0, const Instruction& i1, int idx) {
|
||||
std::unique_ptr<AtomicOp> convert_bltz_2(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
int idx,
|
||||
GameVersion version) {
|
||||
// bltz is never emitted outside of inline asm.
|
||||
auto dest = i0.get_src(1).get_label();
|
||||
return make_asm_branch(IR2_Condition(IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED,
|
||||
make_src_atom(i0.get_src(0).get_reg(), idx)),
|
||||
i1, false, dest, idx);
|
||||
i1, false, dest, idx, version);
|
||||
}
|
||||
|
||||
std::unique_ptr<AtomicOp> convert_bgez_2(const Instruction& i0, const Instruction& i1, int idx) {
|
||||
std::unique_ptr<AtomicOp> convert_bgez_2(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
int idx,
|
||||
GameVersion version) {
|
||||
// bgez is never emitted outside of inline asm.
|
||||
auto dest = i0.get_src(1).get_label();
|
||||
return make_asm_branch(IR2_Condition(IR2_Condition::Kind::GEQ_ZERO_SIGNED,
|
||||
make_src_atom(i0.get_src(0).get_reg(), idx)),
|
||||
i1, false, dest, idx);
|
||||
i1, false, dest, idx, version);
|
||||
}
|
||||
|
||||
std::unique_ptr<AtomicOp> convert_blez_2(const Instruction& i0, const Instruction& i1, int idx) {
|
||||
std::unique_ptr<AtomicOp> convert_blez_2(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
int idx,
|
||||
GameVersion version) {
|
||||
// blez is never emitted outside of inline asm.
|
||||
auto dest = i0.get_src(1).get_label();
|
||||
return make_asm_branch(IR2_Condition(IR2_Condition::Kind::LEQ_ZERO_SIGNED,
|
||||
make_src_atom(i0.get_src(0).get_reg(), idx)),
|
||||
i1, false, dest, idx);
|
||||
i1, false, dest, idx, version);
|
||||
}
|
||||
|
||||
std::unique_ptr<AtomicOp> convert_bgtz_2(const Instruction& i0, const Instruction& i1, int idx) {
|
||||
std::unique_ptr<AtomicOp> convert_bgtz_2(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
int idx,
|
||||
GameVersion version) {
|
||||
// bgtz is never emitted outside of inline asm.
|
||||
auto dest = i0.get_src(1).get_label();
|
||||
return make_asm_branch(IR2_Condition(IR2_Condition::Kind::GREATER_THAN_ZERO_SIGNED,
|
||||
make_src_atom(i0.get_src(0).get_reg(), idx)),
|
||||
i1, false, dest, idx);
|
||||
i1, false, dest, idx, version);
|
||||
}
|
||||
|
||||
std::unique_ptr<AtomicOp> convert_2(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
int idx,
|
||||
bool force_asm_branch) {
|
||||
bool force_asm_branch,
|
||||
GameVersion version) {
|
||||
switch (i0.kind) {
|
||||
case InstructionKind::DIV:
|
||||
return convert_division_2(i0, i1, idx, true);
|
||||
@@ -1187,11 +1208,11 @@ std::unique_ptr<AtomicOp> convert_2(const Instruction& i0,
|
||||
case InstructionKind::JALR:
|
||||
return convert_jalr_2(i0, i1, idx);
|
||||
case InstructionKind::BNE:
|
||||
return convert_bne_2(i0, i1, idx, false, force_asm_branch);
|
||||
return convert_bne_2(i0, i1, idx, false, force_asm_branch, version);
|
||||
case InstructionKind::BEQ:
|
||||
return convert_beq_2(i0, i1, idx, false, force_asm_branch);
|
||||
return convert_beq_2(i0, i1, idx, false, force_asm_branch, version);
|
||||
case InstructionKind::DADDIU:
|
||||
return convert_daddiu_2(i0, i1, idx);
|
||||
return convert_daddiu_2(i0, i1, idx, version);
|
||||
case InstructionKind::LUI:
|
||||
return convert_lui_2(i0, i1, idx);
|
||||
case InstructionKind::SLT:
|
||||
@@ -1201,13 +1222,13 @@ std::unique_ptr<AtomicOp> convert_2(const Instruction& i0,
|
||||
case InstructionKind::CLTS:
|
||||
return convert_fp_branch_asm(i0, i1, IR2_Condition::Kind::FLOAT_LESS_THAN, idx);
|
||||
case InstructionKind::BLTZ:
|
||||
return convert_bltz_2(i0, i1, idx);
|
||||
return convert_bltz_2(i0, i1, idx, version);
|
||||
case InstructionKind::BGEZ:
|
||||
return convert_bgez_2(i0, i1, idx);
|
||||
return convert_bgez_2(i0, i1, idx, version);
|
||||
case InstructionKind::BGTZ:
|
||||
return convert_bgtz_2(i0, i1, idx);
|
||||
return convert_bgtz_2(i0, i1, idx, version);
|
||||
case InstructionKind::BLEZ:
|
||||
return convert_blez_2(i0, i1, idx);
|
||||
return convert_blez_2(i0, i1, idx, version);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1264,7 +1285,8 @@ std::unique_ptr<AtomicOp> convert_lui_3(const Instruction& i0,
|
||||
std::unique_ptr<AtomicOp> convert_dsubu_3(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
const Instruction& i2,
|
||||
int idx) {
|
||||
int idx,
|
||||
GameVersion version) {
|
||||
if (i1.kind == InstructionKind::DADDIU &&
|
||||
(i2.kind == InstructionKind::MOVN || i2.kind == InstructionKind::MOVZ)) {
|
||||
// dsubu temp, a, b
|
||||
@@ -1275,7 +1297,7 @@ std::unique_ptr<AtomicOp> convert_dsubu_3(const Instruction& i0,
|
||||
auto b = i0.get_src(1).get_reg();
|
||||
auto dest = i1.get_dst(0).get_reg();
|
||||
ASSERT(i1.get_src(0).is_reg(rs7()));
|
||||
ASSERT(i1.get_src(1).is_imm(FIX_SYM_TRUE));
|
||||
ASSERT(i1.get_src(1).is_imm(true_symbol_offset(version)));
|
||||
ASSERT(i2.get_dst(0).get_reg() == dest);
|
||||
ASSERT(i2.get_src(0).is_reg(rs7()));
|
||||
ASSERT(i2.get_src(1).get_reg() == temp);
|
||||
@@ -1321,7 +1343,8 @@ std::unique_ptr<AtomicOp> convert_slt_3(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
const Instruction& i2,
|
||||
bool is_signed,
|
||||
int idx) {
|
||||
int idx,
|
||||
GameVersion version) {
|
||||
auto s0 = i0.get_src(0).get_reg();
|
||||
auto s1 = i0.get_src(1).get_reg();
|
||||
std::unique_ptr<AtomicOp> result;
|
||||
@@ -1354,7 +1377,7 @@ std::unique_ptr<AtomicOp> convert_slt_3(const Instruction& i0,
|
||||
if (i1.kind == InstructionKind::BEQ) {
|
||||
condition.invert();
|
||||
}
|
||||
result = make_branch(condition, i2, false, dest, idx, false);
|
||||
result = make_branch(condition, i2, false, dest, idx, false, version);
|
||||
add_clobber_if_unwritten(*result, temp);
|
||||
return result;
|
||||
} else if (i1.kind == InstructionKind::DADDIU &&
|
||||
@@ -1366,7 +1389,7 @@ std::unique_ptr<AtomicOp> convert_slt_3(const Instruction& i0,
|
||||
auto temp = i0.get_dst(0).get_reg();
|
||||
auto dest = i1.get_dst(0).get_reg();
|
||||
ASSERT(i1.get_src(0).is_reg(rs7()));
|
||||
ASSERT(i1.get_src(1).is_imm(FIX_SYM_TRUE));
|
||||
ASSERT(i1.get_src(1).is_imm(true_symbol_offset(version)));
|
||||
ASSERT(i2.get_dst(0).get_reg() == dest);
|
||||
ASSERT(i2.get_src(0).is_reg(rs7()));
|
||||
ASSERT(i2.get_src(1).get_reg() == temp);
|
||||
@@ -1400,7 +1423,8 @@ std::unique_ptr<AtomicOp> convert_slti_3(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
const Instruction& i2,
|
||||
bool is_signed,
|
||||
int idx) {
|
||||
int idx,
|
||||
GameVersion version) {
|
||||
auto s0 = i0.get_src(0).get_reg();
|
||||
auto s1 = SimpleAtom::make_int_constant(i0.get_src(1).get_imm());
|
||||
std::unique_ptr<AtomicOp> result;
|
||||
@@ -1419,7 +1443,7 @@ std::unique_ptr<AtomicOp> convert_slti_3(const Instruction& i0,
|
||||
if (i1.kind == InstructionKind::BEQ) {
|
||||
condition.invert();
|
||||
}
|
||||
result = make_branch(condition, i2, false, dest, idx, false);
|
||||
result = make_branch(condition, i2, false, dest, idx, false, version);
|
||||
add_clobber_if_unwritten(*result, temp);
|
||||
return result;
|
||||
} else if (i1.kind == InstructionKind::DADDIU &&
|
||||
@@ -1431,7 +1455,7 @@ std::unique_ptr<AtomicOp> convert_slti_3(const Instruction& i0,
|
||||
auto temp = i0.get_dst(0).get_reg();
|
||||
auto dest = i1.get_dst(0).get_reg();
|
||||
ASSERT(i1.get_src(0).is_reg(rs7()));
|
||||
ASSERT(i1.get_src(1).is_imm(FIX_SYM_TRUE));
|
||||
ASSERT(i1.get_src(1).is_imm(true_symbol_offset(version)));
|
||||
ASSERT(i2.get_dst(0).get_reg() == dest);
|
||||
ASSERT(i2.get_src(0).is_reg(rs7()));
|
||||
ASSERT(i2.get_src(1).get_reg() == temp);
|
||||
@@ -1455,14 +1479,15 @@ std::unique_ptr<AtomicOp> convert_fp_branch(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
const Instruction& i2,
|
||||
IR2_Condition::Kind kind,
|
||||
int idx) {
|
||||
int idx,
|
||||
GameVersion version) {
|
||||
if (i1.kind == InstructionKind::BC1T || i1.kind == InstructionKind::BC1F) {
|
||||
IR2_Condition condition(kind, make_src_atom(i0.get_src(0).get_reg(), idx),
|
||||
make_src_atom(i0.get_src(1).get_reg(), idx));
|
||||
if (i1.kind == InstructionKind::BC1F) {
|
||||
condition.invert();
|
||||
}
|
||||
return make_branch(condition, i2, false, i1.get_src(0).get_label(), idx, false);
|
||||
return make_branch(condition, i2, false, i1.get_src(0).get_label(), idx, false, version);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1470,26 +1495,27 @@ std::unique_ptr<AtomicOp> convert_fp_branch(const Instruction& i0,
|
||||
std::unique_ptr<AtomicOp> convert_3(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
const Instruction& i2,
|
||||
int idx) {
|
||||
int idx,
|
||||
GameVersion version) {
|
||||
switch (i0.kind) {
|
||||
case InstructionKind::LUI:
|
||||
return convert_lui_3(i0, i1, i2, idx);
|
||||
case InstructionKind::DSUBU:
|
||||
return convert_dsubu_3(i0, i1, i2, idx);
|
||||
return convert_dsubu_3(i0, i1, i2, idx, version);
|
||||
case InstructionKind::SLT:
|
||||
return convert_slt_3(i0, i1, i2, true, idx);
|
||||
return convert_slt_3(i0, i1, i2, true, idx, version);
|
||||
case InstructionKind::SLTU:
|
||||
return convert_slt_3(i0, i1, i2, false, idx);
|
||||
return convert_slt_3(i0, i1, i2, false, idx, version);
|
||||
case InstructionKind::SLTI:
|
||||
return convert_slti_3(i0, i1, i2, true, idx);
|
||||
return convert_slti_3(i0, i1, i2, true, idx, version);
|
||||
case InstructionKind::SLTIU:
|
||||
return convert_slti_3(i0, i1, i2, false, idx);
|
||||
return convert_slti_3(i0, i1, i2, false, idx, version);
|
||||
case InstructionKind::CEQS:
|
||||
return convert_fp_branch(i0, i1, i2, IR2_Condition::Kind::FLOAT_EQUAL, idx);
|
||||
return convert_fp_branch(i0, i1, i2, IR2_Condition::Kind::FLOAT_EQUAL, idx, version);
|
||||
case InstructionKind::CLTS:
|
||||
return convert_fp_branch(i0, i1, i2, IR2_Condition::Kind::FLOAT_LESS_THAN, idx);
|
||||
return convert_fp_branch(i0, i1, i2, IR2_Condition::Kind::FLOAT_LESS_THAN, idx, version);
|
||||
case InstructionKind::CLES:
|
||||
return convert_fp_branch(i0, i1, i2, IR2_Condition::Kind::FLOAT_LEQ, idx);
|
||||
return convert_fp_branch(i0, i1, i2, IR2_Condition::Kind::FLOAT_LEQ, idx, version);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1503,7 +1529,8 @@ std::unique_ptr<AtomicOp> convert_dsll32_4(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
const Instruction& i2,
|
||||
const Instruction& i3,
|
||||
int idx) {
|
||||
int idx,
|
||||
GameVersion version) {
|
||||
if (i1.kind == InstructionKind::SLT && i2.kind == InstructionKind::BEQ) {
|
||||
// dsll32 temp, a0, 30
|
||||
// slt temp, temp, r0
|
||||
@@ -1524,7 +1551,7 @@ std::unique_ptr<AtomicOp> convert_dsll32_4(const Instruction& i0,
|
||||
ASSERT(i2.get_src(1).is_reg(rr0()));
|
||||
|
||||
IR2_Condition condition(IR2_Condition::Kind::IS_NOT_PAIR, make_src_atom(arg, idx));
|
||||
auto result = make_branch(condition, i3, false, i2.get_src(2).get_label(), idx, false);
|
||||
auto result = make_branch(condition, i3, false, i2.get_src(2).get_label(), idx, false, version);
|
||||
result->add_clobber_reg(temp);
|
||||
return result;
|
||||
}
|
||||
@@ -1536,7 +1563,8 @@ std::unique_ptr<AtomicOp> convert_fp_branch_with_nop(const Instruction& i0,
|
||||
const Instruction& i2,
|
||||
const Instruction& i3,
|
||||
IR2_Condition::Kind kind,
|
||||
int idx) {
|
||||
int idx,
|
||||
GameVersion version) {
|
||||
if (i1.kind != InstructionKind::VNOP) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1546,7 +1574,7 @@ std::unique_ptr<AtomicOp> convert_fp_branch_with_nop(const Instruction& i0,
|
||||
if (i2.kind == InstructionKind::BC1F) {
|
||||
condition.invert();
|
||||
}
|
||||
return make_branch(condition, i3, false, i2.get_src(0).get_label(), idx, false);
|
||||
return make_branch(condition, i3, false, i2.get_src(0).get_label(), idx, false, version);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1555,12 +1583,14 @@ std::unique_ptr<AtomicOp> convert_4(const Instruction& i0,
|
||||
const Instruction& i1,
|
||||
const Instruction& i2,
|
||||
const Instruction& i3,
|
||||
int idx) {
|
||||
int idx,
|
||||
GameVersion version) {
|
||||
switch (i0.kind) {
|
||||
case InstructionKind::DSLL32:
|
||||
return convert_dsll32_4(i0, i1, i2, i3, idx);
|
||||
return convert_dsll32_4(i0, i1, i2, i3, idx, version);
|
||||
case InstructionKind::CEQS:
|
||||
return convert_fp_branch_with_nop(i0, i1, i2, i3, IR2_Condition::Kind::FLOAT_EQUAL, idx);
|
||||
return convert_fp_branch_with_nop(i0, i1, i2, i3, IR2_Condition::Kind::FLOAT_EQUAL, idx,
|
||||
version);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
@@ -2035,6 +2065,7 @@ int convert_block_to_atomic_ops(int begin_idx,
|
||||
const std::vector<DecompilerLabel>& labels,
|
||||
FunctionAtomicOps* container,
|
||||
DecompWarnings& warnings,
|
||||
GameVersion version,
|
||||
bool hint_inline_asm,
|
||||
bool block_ends_in_asm_branch) {
|
||||
container->block_id_to_first_atomic_op.push_back(container->ops.size());
|
||||
@@ -2089,7 +2120,7 @@ int convert_block_to_atomic_ops(int begin_idx,
|
||||
|
||||
if (!converted && n_instr >= 4) {
|
||||
// try 4 instructions
|
||||
op = convert_4(instr[0], instr[1], instr[2], instr[3], op_idx);
|
||||
op = convert_4(instr[0], instr[1], instr[2], instr[3], op_idx, version);
|
||||
if (op) {
|
||||
converted = true;
|
||||
length = 4;
|
||||
@@ -2098,7 +2129,7 @@ int convert_block_to_atomic_ops(int begin_idx,
|
||||
|
||||
if (!converted && n_instr >= 3) {
|
||||
// try 3 instructions
|
||||
op = convert_3(instr[0], instr[1], instr[2], op_idx);
|
||||
op = convert_3(instr[0], instr[1], instr[2], op_idx, version);
|
||||
if (op) {
|
||||
converted = true;
|
||||
length = 3;
|
||||
@@ -2107,7 +2138,7 @@ int convert_block_to_atomic_ops(int begin_idx,
|
||||
|
||||
if (!converted && n_instr >= 2) {
|
||||
// try 2 instructions
|
||||
op = convert_2(instr[0], instr[1], op_idx, block_ends_in_asm_branch);
|
||||
op = convert_2(instr[0], instr[1], op_idx, block_ends_in_asm_branch, version);
|
||||
if (op) {
|
||||
converted = true;
|
||||
length = 2;
|
||||
@@ -2117,7 +2148,7 @@ int convert_block_to_atomic_ops(int begin_idx,
|
||||
if (!converted) {
|
||||
// try 1 instruction
|
||||
bool force_asm_branch = n_instr == 1 && block_ends_in_asm_branch;
|
||||
op = convert_1(*instr, op_idx, hint_inline_asm, force_asm_branch);
|
||||
op = convert_1(*instr, op_idx, hint_inline_asm, force_asm_branch, version);
|
||||
if (op) {
|
||||
converted = true;
|
||||
length = 1;
|
||||
@@ -2162,7 +2193,8 @@ FunctionAtomicOps convert_function_to_atomic_ops(
|
||||
const std::vector<DecompilerLabel>& labels,
|
||||
DecompWarnings& warnings,
|
||||
bool hint_inline_asm,
|
||||
const std::unordered_set<int>& blocks_ending_in_asm_branches) {
|
||||
const std::unordered_set<int>& blocks_ending_in_asm_branches,
|
||||
GameVersion version) {
|
||||
FunctionAtomicOps result;
|
||||
|
||||
int last_op = 0;
|
||||
@@ -2173,7 +2205,7 @@ FunctionAtomicOps convert_function_to_atomic_ops(
|
||||
auto begin = func.instructions.begin() + block.start_word;
|
||||
auto end = func.instructions.begin() + block.end_word;
|
||||
last_op = convert_block_to_atomic_ops(
|
||||
block.start_word, begin, end, labels, &result, warnings, hint_inline_asm,
|
||||
block.start_word, begin, end, labels, &result, warnings, version, hint_inline_asm,
|
||||
blocks_ending_in_asm_branches.find(i) != blocks_ending_in_asm_branches.end());
|
||||
if (i == int(func.basic_blocks.size()) - 1) {
|
||||
// we're the last block. insert the function end op.
|
||||
|
||||
@@ -49,6 +49,7 @@ int convert_block_to_atomic_ops(int begin_idx,
|
||||
const std::vector<DecompilerLabel>& labels,
|
||||
FunctionAtomicOps* container,
|
||||
DecompWarnings& warnings,
|
||||
GameVersion version,
|
||||
bool inline_asm_hint = false,
|
||||
bool block_ends_in_asm_branch = false);
|
||||
|
||||
@@ -60,5 +61,6 @@ FunctionAtomicOps convert_function_to_atomic_ops(
|
||||
const std::vector<DecompilerLabel>& labels,
|
||||
DecompWarnings& warnings,
|
||||
bool hint_inline_asm,
|
||||
const std::unordered_set<int>& blocks_ending_in_asm_branches);
|
||||
const std::unordered_set<int>& blocks_ending_in_asm_branches,
|
||||
GameVersion version);
|
||||
} // namespace decompiler
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "cfg_builder.h"
|
||||
#include "decompiler/Function/Function.h"
|
||||
#include "decompiler/IR2/Form.h"
|
||||
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
||||
#include "decompiler/util/MatchParam.h"
|
||||
|
||||
namespace decompiler {
|
||||
@@ -107,6 +108,13 @@ void clean_up_cond_with_else(FormPool& pool, FormElement* ir, const Env& env) {
|
||||
void clean_up_until_loop(FormPool& pool, UntilElement* ir, const Env& env) {
|
||||
auto condition_branch = get_condition_branch(ir->condition);
|
||||
ASSERT(condition_branch.first);
|
||||
if (condition_branch.first->op()->branch_delay().kind() != IR2_BranchDelay::Kind::NOP) {
|
||||
ASSERT_MSG(
|
||||
false,
|
||||
fmt::format(
|
||||
"bad delay slot in until loop: {} in {}\n", env.func->name(),
|
||||
condition_branch.first->op()->branch_delay().to_form(env.file->labels, env).print()));
|
||||
}
|
||||
ASSERT(condition_branch.first->op()->branch_delay().kind() == IR2_BranchDelay::Kind::NOP);
|
||||
auto replacement = condition_branch.first->op()->get_condition_as_form(pool, env);
|
||||
replacement->invert();
|
||||
@@ -708,6 +716,7 @@ void clean_up_cond_no_else_final(Function& func, CondNoElseElement* cne) {
|
||||
ASSERT(fr.has_value());
|
||||
cne->final_destination = *fr;
|
||||
} else {
|
||||
fmt::print("failed to clean up cond_no_else_final: {}\n", func.name());
|
||||
ASSERT(false);
|
||||
}
|
||||
}
|
||||
@@ -1634,6 +1643,10 @@ Form* cfg_to_ir_helper(FormPool& pool, Function& f, const CfgVtx* vtx) {
|
||||
ASSERT(delay_end - delay_start == 1);
|
||||
auto& op = f.ir2.atomic_ops->ops.at(delay_start);
|
||||
auto op_as_expr = dynamic_cast<SetVarOp*>(op.get());
|
||||
if (!op_as_expr) {
|
||||
fmt::print("bad in {}\n", f.name());
|
||||
fmt::print("{}\n", op->to_string(f.ir2.env));
|
||||
}
|
||||
ASSERT(op_as_expr);
|
||||
e.branch_delay = *op_as_expr;
|
||||
}
|
||||
|
||||
@@ -591,12 +591,15 @@ Mips2C_Line handle_generic_op2_u16(const Instruction& i0, const std::string& ins
|
||||
instr_str};
|
||||
}
|
||||
|
||||
Mips2C_Line handle_daddiu(Mips2C_Output& out, const Instruction& i0, const std::string& instr_str) {
|
||||
Mips2C_Line handle_daddiu(Mips2C_Output& out,
|
||||
const Instruction& i0,
|
||||
const std::string& instr_str,
|
||||
GameVersion version) {
|
||||
if (i0.get_src(1).is_label()) {
|
||||
return {instr_str, instr_str};
|
||||
} else if (i0.get_src(0).is_reg(rs7()) && i0.get_src(1).is_sym("#t")) {
|
||||
return {fmt::format("c->{}({}, {}, {});", i0.op_name_to_string(), reg_to_name(i0.get_dst(0)),
|
||||
reg_to_name(i0.get_src(0)), FIX_SYM_TRUE),
|
||||
reg_to_name(i0.get_src(0)), true_symbol_offset(version)),
|
||||
instr_str};
|
||||
} else if (i0.get_src(0).is_reg(rs7()) && i0.get_src(1).is_sym()) {
|
||||
out.require_symbol(i0.get_src(1).get_sym());
|
||||
@@ -931,7 +934,8 @@ Mips2C_Line handle_normal_instr(Mips2C_Output& output,
|
||||
const Instruction& i0,
|
||||
const std::string& instr_str,
|
||||
int& unknown_count,
|
||||
const LinkedObjectFile* file) {
|
||||
const LinkedObjectFile* file,
|
||||
GameVersion version) {
|
||||
switch (i0.kind) {
|
||||
case InstructionKind::CTC2:
|
||||
return handle_ctc2(i0, instr_str);
|
||||
@@ -1073,7 +1077,7 @@ Mips2C_Line handle_normal_instr(Mips2C_Output& output,
|
||||
case InstructionKind::AND:
|
||||
return handle_generic_op3(i0, instr_str, "and_"); // and isn't allowed in C++
|
||||
case InstructionKind::DADDIU:
|
||||
return handle_daddiu(output, i0, instr_str);
|
||||
return handle_daddiu(output, i0, instr_str, version);
|
||||
case InstructionKind::ADDIU:
|
||||
return handle_generic_op2_u16(i0, instr_str);
|
||||
case InstructionKind::QMTC2:
|
||||
@@ -1172,7 +1176,9 @@ struct JumpTableBlock {
|
||||
bool branch_always = false; // that branch is always taken?
|
||||
};
|
||||
|
||||
void run_mips2c_jump_table(Function* f, const std::vector<int>& jump_table_locations) {
|
||||
void run_mips2c_jump_table(Function* f,
|
||||
const std::vector<int>& jump_table_locations,
|
||||
GameVersion version) {
|
||||
fmt::print("mips2c-jump on {}\n", f->name());
|
||||
u32 magic_code = std::hash<std::string>()(f->name());
|
||||
std::unordered_map<int, int> loc_to_block;
|
||||
@@ -1212,8 +1218,8 @@ void run_mips2c_jump_table(Function* f, const std::vector<int>& jump_table_locat
|
||||
ASSERT(delay_block.end_instr - delay_block.start_instr == 1); // only 1 instr.
|
||||
auto& delay_instr = f->instructions.at(delay_block.start_instr);
|
||||
auto delay_instr_str = delay_instr.to_string(file->labels);
|
||||
auto delay_instr_line =
|
||||
handle_normal_instr(output, delay_instr, delay_instr_str, unknown_count, file);
|
||||
auto delay_instr_line = handle_normal_instr(output, delay_instr, delay_instr_str,
|
||||
unknown_count, file, version);
|
||||
output.lines.emplace_back(fmt::format(" {}", delay_instr_line.code),
|
||||
delay_instr_line.comment);
|
||||
ASSERT(delay_block.succ_ft == -1);
|
||||
@@ -1230,7 +1236,7 @@ void run_mips2c_jump_table(Function* f, const std::vector<int>& jump_table_locat
|
||||
auto& delay_i = f->instructions.at(i);
|
||||
auto delay_i_str = delay_i.to_string(file->labels);
|
||||
output.lines.push_back(
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file));
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file, version));
|
||||
ASSERT(i + 1 == block.end_instr);
|
||||
// then the goto
|
||||
output.lines.emplace_back(fmt::format("next_block = {};", block.succ_branch),
|
||||
@@ -1246,7 +1252,7 @@ void run_mips2c_jump_table(Function* f, const std::vector<int>& jump_table_locat
|
||||
auto& delay_i = f->instructions.at(i);
|
||||
auto delay_i_str = delay_i.to_string(file->labels);
|
||||
output.lines.push_back(
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file));
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file, version));
|
||||
ASSERT(i + 1 == block.end_instr);
|
||||
// then the goto
|
||||
output.lines.emplace_back(
|
||||
@@ -1264,7 +1270,7 @@ void run_mips2c_jump_table(Function* f, const std::vector<int>& jump_table_locat
|
||||
auto& delay_i = f->instructions.at(i);
|
||||
auto delay_i_str = delay_i.to_string(file->labels);
|
||||
output.lines.push_back(
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file));
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file, version));
|
||||
|
||||
// then the goto
|
||||
output.lines.emplace_back(fmt::format("goto end_of_function;", block.succ_branch),
|
||||
@@ -1279,7 +1285,7 @@ void run_mips2c_jump_table(Function* f, const std::vector<int>& jump_table_locat
|
||||
auto& delay_i = f->instructions.at(i);
|
||||
auto delay_i_str = delay_i.to_string(file->labels);
|
||||
output.lines.push_back(
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file));
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file, version));
|
||||
output.lines.emplace_back("c->jalr(call_addr);", instr_str);
|
||||
} else if (instr.kind == InstructionKind::JR) {
|
||||
// special case for jr's to handle the jump tableing.
|
||||
@@ -1290,7 +1296,8 @@ void run_mips2c_jump_table(Function* f, const std::vector<int>& jump_table_locat
|
||||
output.lines.emplace_back("break;");
|
||||
|
||||
} else {
|
||||
output.lines.push_back(handle_normal_instr(output, instr, instr_str, unknown_count, file));
|
||||
output.lines.push_back(
|
||||
handle_normal_instr(output, instr, instr_str, unknown_count, file, version));
|
||||
}
|
||||
|
||||
ASSERT(output.lines.size() > old_line_count);
|
||||
@@ -1312,7 +1319,7 @@ void run_mips2c_jump_table(Function* f, const std::vector<int>& jump_table_locat
|
||||
}
|
||||
}
|
||||
|
||||
void run_mips2c(Function* f) {
|
||||
void run_mips2c(Function* f, GameVersion version) {
|
||||
g_unknown = 0;
|
||||
auto* file = f->ir2.env.file;
|
||||
std::unordered_set<int> likely_delay_blocks;
|
||||
@@ -1347,8 +1354,8 @@ void run_mips2c(Function* f) {
|
||||
ASSERT(delay_block.end_instr - delay_block.start_instr == 1); // only 1 instr.
|
||||
auto& delay_instr = f->instructions.at(delay_block.start_instr);
|
||||
auto delay_instr_str = delay_instr.to_string(file->labels);
|
||||
auto delay_instr_line =
|
||||
handle_normal_instr(output, delay_instr, delay_instr_str, unknown_count, file);
|
||||
auto delay_instr_line = handle_normal_instr(output, delay_instr, delay_instr_str,
|
||||
unknown_count, file, version);
|
||||
output.lines.emplace_back(fmt::format(" {}", delay_instr_line.code),
|
||||
delay_instr_line.comment);
|
||||
ASSERT(delay_block.succ_ft == -1);
|
||||
@@ -1364,7 +1371,7 @@ void run_mips2c(Function* f) {
|
||||
auto& delay_i = f->instructions.at(i);
|
||||
auto delay_i_str = delay_i.to_string(file->labels);
|
||||
output.lines.push_back(
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file));
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file, version));
|
||||
ASSERT(i + 1 == block.end_instr);
|
||||
// then the goto
|
||||
output.lines.emplace_back(fmt::format("goto block_{};", block.succ_branch),
|
||||
@@ -1382,7 +1389,7 @@ void run_mips2c(Function* f) {
|
||||
auto& delay_i = f->instructions.at(i);
|
||||
auto delay_i_str = delay_i.to_string(file->labels);
|
||||
output.lines.push_back(
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file));
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file, version));
|
||||
// ASSERT(i + 1 == block.end_instr);
|
||||
// then the goto
|
||||
output.lines.emplace_back(fmt::format("if (bc) {{goto block_{};}}", block.succ_branch),
|
||||
@@ -1398,7 +1405,7 @@ void run_mips2c(Function* f) {
|
||||
auto& delay_i = f->instructions.at(i);
|
||||
auto delay_i_str = delay_i.to_string(file->labels);
|
||||
output.lines.push_back(
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file));
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file, version));
|
||||
|
||||
// then the goto
|
||||
output.lines.emplace_back(fmt::format("goto end_of_function;", block.succ_branch),
|
||||
@@ -1413,10 +1420,11 @@ void run_mips2c(Function* f) {
|
||||
auto& delay_i = f->instructions.at(i);
|
||||
auto delay_i_str = delay_i.to_string(file->labels);
|
||||
output.lines.push_back(
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file));
|
||||
handle_normal_instr(output, delay_i, delay_i_str, unknown_count, file, version));
|
||||
output.lines.emplace_back("c->jalr(call_addr);", instr_str);
|
||||
} else {
|
||||
output.lines.push_back(handle_normal_instr(output, instr, instr_str, unknown_count, file));
|
||||
output.lines.push_back(
|
||||
handle_normal_instr(output, instr, instr_str, unknown_count, file, version));
|
||||
}
|
||||
|
||||
ASSERT(output.lines.size() > old_line_count);
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "common/versions.h"
|
||||
|
||||
namespace decompiler {
|
||||
class Function;
|
||||
|
||||
void run_mips2c(Function* f);
|
||||
void run_mips2c_jump_table(Function* f, const std::vector<int>& jump_table_locations);
|
||||
void run_mips2c(Function* f, GameVersion version);
|
||||
void run_mips2c_jump_table(Function* f,
|
||||
const std::vector<int>& jump_table_locations,
|
||||
GameVersion version);
|
||||
} // namespace decompiler
|
||||
|
||||
@@ -66,6 +66,7 @@ Config read_config_file(const std::string& path_to_config_file,
|
||||
config.process_art_groups = cfg.at("process_art_groups").get<bool>();
|
||||
config.hexdump_code = cfg.at("hexdump_code").get<bool>();
|
||||
config.hexdump_data = cfg.at("hexdump_data").get<bool>();
|
||||
config.find_functions = cfg.at("find_functions").get<bool>();
|
||||
config.dump_objs = cfg.at("dump_objs").get<bool>();
|
||||
config.print_cfgs = cfg.at("print_cfgs").get<bool>();
|
||||
config.generate_symbol_definition_map = cfg.at("generate_symbol_definition_map").get<bool>();
|
||||
|
||||
@@ -104,6 +104,7 @@ struct Config {
|
||||
bool process_art_groups = false;
|
||||
bool rip_levels = false;
|
||||
bool extract_collision = false;
|
||||
bool find_functions = false;
|
||||
|
||||
bool write_hex_near_instructions = false;
|
||||
bool hexdump_code = false;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
;; type system setup
|
||||
(define-extern object type)
|
||||
(define-extern type type)
|
||||
(define-extern structure type)
|
||||
(define-extern uint128 type)
|
||||
(define-extern basic type)
|
||||
(define-extern pair type)
|
||||
(define-extern array type)
|
||||
|
||||
|
||||
|
||||
;; some types we need.
|
||||
(declare-type sparticle-launch-group basic)
|
||||
(declare-type lightning-spec basic)
|
||||
(declare-type sparticle-launcher basic)
|
||||
(declare-type state basic)
|
||||
@@ -20,6 +20,9 @@
|
||||
// Run the decompiler
|
||||
"decompile_code": false,
|
||||
|
||||
// run the first pass of the decompiler
|
||||
"find_functions": true,
|
||||
|
||||
////////////////////////////
|
||||
// DATA ANALYSIS OPTIONS
|
||||
////////////////////////////
|
||||
|
||||
@@ -27,7 +27,35 @@
|
||||
"hint_inline_assembly_functions": [],
|
||||
|
||||
"asm_functions_by_name": [
|
||||
// checking boxed type is different now - these make the cfg stuff sad
|
||||
"type?", "print", "printl", "inspect", "valid?", "name=", "(method 8 res-lump)", "joint-control-remap!", "(method 21 game-info)", "(method 12 level)",
|
||||
"bg", "(anon-function 2 cam-combiner)", "cspace-inspect-tree", "command-get-process", "command-get-trans", "(method 10 script-context)",
|
||||
"(method 11 script-context)", "(method 9 script-context)", "(anon-function 64 script)", "(anon-function 61 script)", "(anon-function 60 script)",
|
||||
"(anon-function 54 script)", "(anon-function 52 script)", "(anon-function 49 script)", "(anon-function 33 script)", "debug-menu-func-decode",
|
||||
"scene-player-init", "(method 77 spyder)", "(method 77 flamer)", "(method 77 grenadier)", "(method 224 bot)", "(method 77 rapid-gunner)",
|
||||
// until loop without nop:
|
||||
"rand-vu-int-count-excluding", "rand-vu-int-range-exclude", "(method 9 history)", "history-print", "history-draw",
|
||||
"(method 9 sparticle-launcher)", "(method 18 tracking-spline)", "cam-string-find-position-rel!", "cam-layout-entity-volume-info-create",
|
||||
"process-drawable-shock-skel-effect", "target-history-print", "display-list-control", "anim-test-anim-list-handler",
|
||||
"anim-test-sequence-list-handler", "anim-tester-get-playing-item", "(method 9 mysql-nav-graph)", "(method 58 nav-graph-editor)",
|
||||
"(method 120 enemy)", "start-pilot-recorder", "(anon-function 10 pilot-recorder)", "(method 0 hover-nav-control)",
|
||||
"(method 24 nav-network)", "(method 11 predator-manager)", "(method 9 bot-speech-list)", "(method 9 bot-speech-list-shuffle)",
|
||||
"(anon-function 10 sig-recorder)", "(method 14 trail-graph)", "(method 12 trail-graph)", "(method 11 trail-graph)",
|
||||
// actual asm
|
||||
"symlink2", "blerc-a-fragment", "blerc-execute", "foreground-check-longest-edge-asm", "generic-light-proc",
|
||||
"shadow-add-single-edges","shadow-add-facing-single-tris", "shadow-add-double-tris", "shadow-add-double-edges",
|
||||
"(method 12 collide-mesh)", "(method 17 collide-edge-work)", "(method 42 collide-shape)", "(method 12 collide-shape-prim-sphere)",
|
||||
"(method 12 collide-shape-prim-mesh)", "(method 18 collide-shape-prim-mesh)", "(method 10 collide-cache-prim)",
|
||||
"(method 17 collide-cache)", "(method 16 ocean)",
|
||||
|
||||
// unknown bug
|
||||
"joint-mod-polar-look-at-guts", "(method 13 external-art-control)", "update-mood-copy-ctywide", "update-mood-copy-stadium",
|
||||
"update-mood-drillb", "reset-target-tracking", "(anon-function 1 target)", "find-nearest-entity", "target-land-effect",
|
||||
"(method 12 effect-control)", "(method 11 effect-control)", "(method 10 effect-control)",
|
||||
"(anon-function 2 scene)", "progress-trans", "(method 10 bigmap)", "(method 9 editable-region)", "(method 57 enemy)",
|
||||
"(anon-function 10 meet-brutter)", "(method 154 vehicle-racer)", "(method 188 predator)", "(anon-function 13 sig0-course)",
|
||||
"(method 228 hal-sewer)", "(method 154 vehicle-city-racer)", "(method 53 squid)", "(anon-function 11 fort-floor-spike)",
|
||||
"(method 29 gun-dummy)", "vehicle-explode-post", "(method 158 vehicle-guard)", "(method 207 metalhead-predator)"
|
||||
],
|
||||
|
||||
// these functions use pairs and the decompiler
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
// if you want to filter to only some object names.
|
||||
// it will make the decompiler much faster.
|
||||
"allowed_objects": [],
|
||||
"banned_objects": [],
|
||||
"banned_objects": ["effect-control"],
|
||||
|
||||
////////////////////////////
|
||||
// CODE ANALYSIS OPTIONS
|
||||
@@ -19,7 +19,9 @@
|
||||
"disassemble_code": true,
|
||||
|
||||
// Run the decompiler
|
||||
"decompile_code": false,
|
||||
"decompile_code": true,
|
||||
|
||||
"find_functions": true,
|
||||
|
||||
////////////////////////////
|
||||
// DATA ANALYSIS OPTIONS
|
||||
|
||||
@@ -151,6 +151,11 @@ int main(int argc, char** argv) {
|
||||
db.process_labels();
|
||||
fmt::print("[Mem] After code: {} MB\n", get_peak_rss() / (1024 * 1024));
|
||||
|
||||
// top level decompile (do this before printing asm so we get function names)
|
||||
if (config.find_functions) {
|
||||
db.ir2_top_level_pass(config);
|
||||
}
|
||||
|
||||
// print disassembly
|
||||
if (config.disassemble_code || config.disassemble_data) {
|
||||
db.write_disassembly(out_folder, config.disassemble_data, config.disassemble_code,
|
||||
|
||||
@@ -241,7 +241,7 @@ void set_fullscreen(DisplayMode mode, int screen) {
|
||||
}
|
||||
|
||||
void input_mode_set(u32 enable) {
|
||||
if (enable == s7.offset + FIX_SYM_TRUE) { // #t
|
||||
if (enable == s7.offset + jak1_symbols::FIX_SYM_TRUE) { // #t
|
||||
Pad::g_input_mode_mapping = g_settings.pad_mapping_info;
|
||||
Pad::EnterInputMode();
|
||||
} else {
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "common/util/Assert.h"
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
using namespace jak1_symbols;
|
||||
|
||||
namespace {
|
||||
// turn on printf's for debugging linking issues.
|
||||
constexpr bool link_debug_printfs = false;
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include "kscheme.h"
|
||||
#include "common/symbols.h"
|
||||
|
||||
using namespace jak1_symbols;
|
||||
|
||||
Ptr<Symbol> ListenerLinkBlock;
|
||||
Ptr<Symbol> ListenerFunction;
|
||||
Ptr<Symbol> kernel_dispatcher;
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
|
||||
#include "svnrev.h"
|
||||
|
||||
using namespace jak1_symbols;
|
||||
using namespace ee;
|
||||
|
||||
/*!
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "common/symbols.h"
|
||||
#include "common/util/Assert.h"
|
||||
|
||||
using namespace jak1_symbols;
|
||||
|
||||
///////////
|
||||
// SDATA
|
||||
///////////
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
#include "game/mips2c/mips2c_table.h"
|
||||
#include "common/util/Assert.h"
|
||||
|
||||
using namespace jak1_symbols;
|
||||
|
||||
//! Controls link mode when EnableMethodSet = 0, MasterDebug = 1, DiskBoot = 0. Will enable a
|
||||
//! warning message if EnableMethodSet = 1
|
||||
u32 FastLink;
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "game/kernel/kscheme.h"
|
||||
#include "common/symbols.h"
|
||||
|
||||
using namespace jak1_symbols;
|
||||
|
||||
extern "C" {
|
||||
void _mips2c_call_linux();
|
||||
void _mips2c_call_windows();
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "common/symbols.h"
|
||||
|
||||
using namespace emitter;
|
||||
|
||||
using namespace jak1_symbols; // TODO jak 1 symbols
|
||||
namespace {
|
||||
Register get_reg(const RegVal* rv, const AllocationResult& allocs, emitter::IR_Record irec) {
|
||||
if (rv->rlet_constraint().has_value()) {
|
||||
|
||||
@@ -513,6 +513,8 @@ bool Debugger::write_memory(const u8* src_buffer, int size, u32 goal_addr) {
|
||||
* Read the GOAL Symbol table from an attached and halted target.
|
||||
*/
|
||||
void Debugger::read_symbol_table() {
|
||||
// todo: this assumes many things specific to jak 1.
|
||||
using namespace jak1_symbols;
|
||||
ASSERT(is_valid() && is_attached() && is_halted());
|
||||
u32 bytes_read = 0;
|
||||
u32 reads = 0;
|
||||
|
||||
@@ -173,7 +173,8 @@ std::unique_ptr<FormRegressionTest::TestData> FormRegressionTest::make_function(
|
||||
|
||||
// convert instruction to atomic ops
|
||||
DecompWarnings warnings;
|
||||
auto ops = convert_function_to_atomic_ops(test->func, program.labels, warnings, false, {});
|
||||
auto ops = convert_function_to_atomic_ops(test->func, program.labels, warnings, false, {},
|
||||
GameVersion::Jak1);
|
||||
test->func.ir2.atomic_ops = std::make_shared<FunctionAtomicOps>(std::move(ops));
|
||||
test->func.ir2.atomic_ops_succeeded = true;
|
||||
test->func.ir2.env.set_end_var(test->func.ir2.atomic_ops->end_op().return_var());
|
||||
|
||||
@@ -24,6 +24,7 @@ std::string assembly_from_list(std::vector<std::string> assemblyLines) {
|
||||
return str;
|
||||
}
|
||||
|
||||
// jak 1 specific
|
||||
void test_case(std::string assembly_lines,
|
||||
std::vector<std::string> output_lines,
|
||||
std::vector<std::vector<std::string>> write_regs,
|
||||
@@ -45,7 +46,7 @@ void test_case(std::string assembly_lines,
|
||||
// treat the entire program as a single basic block, and convert!
|
||||
DecompWarnings warnings;
|
||||
convert_block_to_atomic_ops(0, prg.instructions.begin(), prg.instructions.end(), prg.labels,
|
||||
&container, warnings);
|
||||
&container, warnings, GameVersion::Jak1);
|
||||
|
||||
// count operations
|
||||
EXPECT_EQ(container.ops.size(), output_lines.size());
|
||||
@@ -130,7 +131,7 @@ TEST(DecompilerAtomicOpBuilder, RegUseDuplication) {
|
||||
FunctionAtomicOps container;
|
||||
DecompWarnings warnings;
|
||||
convert_block_to_atomic_ops(0, prg.instructions.begin(), prg.instructions.end(), prg.labels,
|
||||
&container, warnings);
|
||||
&container, warnings, GameVersion::Jak1);
|
||||
ASSERT_EQ(1, container.ops.size());
|
||||
auto& op = container.ops.at(0);
|
||||
for (const auto& reg_group : {op->read_regs(), op->write_regs(), op->clobber_regs()}) {
|
||||
|
||||
@@ -307,6 +307,7 @@ void disassemble(Decompiler& dc) {
|
||||
|
||||
void decompile(Decompiler& dc, const OfflineTestConfig& config) {
|
||||
dc.db->extract_art_info();
|
||||
dc.db->ir2_top_level_pass(*dc.config);
|
||||
dc.db->analyze_functions_ir2({}, *dc.config, config.skip_compile_functions,
|
||||
config.skip_compile_states);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "game/kernel/kscheme.h"
|
||||
#include "all_jak1_symbols.h"
|
||||
|
||||
using namespace jak1_symbols;
|
||||
|
||||
TEST(Kernel, strend) {
|
||||
char test[] = "test";
|
||||
char* end = strend(test);
|
||||
|
||||
@@ -114,6 +114,7 @@ struct SymbolMap {
|
||||
};
|
||||
|
||||
SymbolMap build_symbol_map(const Ram& ram, u32 s7) {
|
||||
// TODO jak 1 specific
|
||||
fmt::print("finding symbols...\n");
|
||||
SymbolMap map;
|
||||
/*
|
||||
@@ -150,9 +151,10 @@ SymbolMap build_symbol_map(const Ram& ram, u32 s7) {
|
||||
std::unordered_map<u32, std::string> build_type_map(const Ram& ram,
|
||||
const SymbolMap& symbols,
|
||||
u32 s7) {
|
||||
// TODO jak 1 specific
|
||||
std::unordered_map<u32, std::string> result;
|
||||
fmt::print("finding types...\n");
|
||||
u32 type_of_type = ram.word(s7 + FIX_SYM_TYPE_TYPE);
|
||||
u32 type_of_type = ram.word(s7 + jak1_symbols::FIX_SYM_TYPE_TYPE);
|
||||
ASSERT(type_of_type == ram.word(symbols.name_to_addr.at("type")));
|
||||
|
||||
for (const auto& [name, addr] : symbols.name_to_addr) {
|
||||
|
||||
Reference in New Issue
Block a user