[decomp] add mips2c converter (#842)

* mips 2 c basic version, not yet tested

* calling works without crashing, but the function doesn't

* it works

* add test

* cleanup and actually add the test

* dont use mips2c by default for font

* clean up formatting
This commit is contained in:
water111
2021-09-11 20:52:35 -04:00
committed by GitHub
parent e47476983a
commit f9d8fcd6e4
61 changed files with 3463 additions and 153 deletions
+4 -5
View File
@@ -5,9 +5,6 @@
* Common Integer Types.
*/
#ifndef JAK1_COMMON_TYPES_H
#define JAK1_COMMON_TYPES_H
#include <cstdint>
using u8 = uint8_t;
@@ -22,12 +19,14 @@ using s64 = int64_t;
struct u128 {
union {
u64 du64[2];
s64 ds64[2];
u32 du32[4];
s32 ds32[4];
u16 du16[8];
s16 ds16[8];
u8 du8[16];
s8 ds8[16];
float f[4];
};
};
static_assert(sizeof(u128) == 16, "u128");
#endif // JAK1_COMMON_TYPES_H
+2
View File
@@ -18,6 +18,8 @@ enum LinkKind {
enum SegmentTypes { MAIN_SEGMENT = 0, DEBUG_SEGMENT = 1, TOP_LEVEL_SEGMENT = 2 };
constexpr const char* SEGMENT_NAMES[3] = {"main", "debug", "top-level"};
constexpr int N_SEG = 3;
/*!
-5
View File
@@ -5,9 +5,6 @@
* The location of fixed symbols in the GOAL symbol table.
*/
#ifndef JAK1_SYMBOLS_H
#define JAK1_SYMBOLS_H
constexpr int FIX_SYM_EMPTY_CAR = -0xc;
constexpr int FIX_SYM_EMPTY_PAIR = -0xa;
constexpr int FIX_SYM_EMPTY_CDR = -0x8;
@@ -79,5 +76,3 @@ 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;
#endif // JAK1_SYMBOLS_H
+1
View File
@@ -10,6 +10,7 @@ add_library(
analysis/inline_asm_rewrite.cpp
analysis/insert_lets.cpp
analysis/label_types.cpp
analysis/mips2c.cpp
analysis/reg_usage.cpp
analysis/stack_spill.cpp
analysis/static_refs.cpp
-4
View File
@@ -5,9 +5,6 @@
* Utilities for checking if an instruction matches some criteria.
*/
#ifndef JAK_DISASSEMBLER_INSTRUCTIONMATCHING_H
#define JAK_DISASSEMBLER_INSTRUCTIONMATCHING_H
#include "Instruction.h"
#include "decompiler/util/MatchParam.h"
@@ -58,4 +55,3 @@ Register make_fpr(int fpr);
bool is_branch(const Instruction& instr, MatchParam<bool> likely);
bool is_always_branch(const Instruction& instr);
} // namespace decompiler
#endif // JAK_DISASSEMBLER_INSTRUCTIONMATCHING_H
+9 -6
View File
@@ -78,7 +78,7 @@ void Function::analyze_prologue(const LinkedObjectFile& file) {
lg::warn(
"Function {} was flagged as asm due to this instruction: {}. Consider flagging as asm "
"in config!",
guessed_name.to_string(), instr.to_string(file.labels));
name(), instr.to_string(file.labels));
warnings.general_warning("Flagged as asm because of {}", instr.to_string(file.labels));
suspected_asm = true;
return;
@@ -103,7 +103,7 @@ void Function::analyze_prologue(const LinkedObjectFile& file) {
lg::warn(
"Function {} was flagged as asm due to this instruction: {}. Consider flagging as asm "
"in config!",
guessed_name.to_string(), instr.to_string(file.labels));
name(), instr.to_string(file.labels));
warnings.general_warning("Flagged as asm because of {}", instr.to_string(file.labels));
suspected_asm = true;
return;
@@ -158,7 +158,7 @@ void Function::analyze_prologue(const LinkedObjectFile& file) {
if (this_reg != get_expected_gpr_backup(i, n_gpr_backups)) {
suspected_asm = true;
lg::warn("Function {} stores on the stack in a strange way ({}), flagging as asm!",
instructions.at(idx + i).to_string(file.labels), guessed_name.to_string());
instructions.at(idx + i).to_string(file.labels), name());
warnings.general_warning("Flagged as asm due to strange stack store: {}",
instructions.at(idx + i).to_string(file.labels));
return;
@@ -187,7 +187,7 @@ void Function::analyze_prologue(const LinkedObjectFile& file) {
if (this_reg != get_expected_fpr_backup(i, n_fpr_backups)) {
suspected_asm = true;
lg::warn("Function {} stores on the stack in a strange way ({}), flagging as asm!",
instructions.at(idx + i).to_string(file.labels), guessed_name.to_string());
instructions.at(idx + i).to_string(file.labels), name());
warnings.general_warning("Flagged as asm due to strange stack store: {}",
instructions.at(idx + i).to_string(file.labels));
return;
@@ -378,8 +378,7 @@ void Function::check_epilogue(const LinkedObjectFile& file) {
idx--;
assert(is_jr_ra(instructions.at(idx)));
idx--;
lg::warn("Function {} has a double return and is being flagged as asm.",
guessed_name.to_string());
lg::warn("Function {} has a double return and is being flagged as asm.", name());
warnings.general_warning("Flagged as asm due to double return");
}
// delay slot should be daddiu sp, sp, offset
@@ -773,4 +772,8 @@ BlockTopologicalSort Function::bb_topo_sort() {
return result;
}
std::string Function::name() const {
return guessed_name.to_string();
}
} // namespace decompiler
+3
View File
@@ -113,6 +113,7 @@ class Function {
int get_basic_op_count();
int get_failed_basic_op_count();
BlockTopologicalSort bb_topo_sort();
std::string name() const;
TypeSpec type;
@@ -188,6 +189,8 @@ class Function {
bool expressions_succeeded = false;
} ir2;
std::optional<std::string> mips2c_output;
std::vector<std::string> types_defined;
private:
+2 -2
View File
@@ -86,7 +86,7 @@ TP_Type SimpleAtom::get_type(const TypeState& input,
return TP_Type::make_enter_state();
} else if (m_string == "run-function-in-process") {
return TP_Type::make_run_function_in_process_function();
} else if (m_string == "set-to-run" && env.func->guessed_name.to_string() != "enter-state") {
} else if (m_string == "set-to-run" && env.func->name() != "enter-state") {
return TP_Type::make_set_to_run_function();
}
@@ -1253,7 +1253,7 @@ TypeState CallOp::propagate_types_internal(const TypeState& input,
int arg_count = -1;
if (dynamic_string) {
arg_count = dts.get_dynamic_format_arg_count(env.func->guessed_name.to_string(), m_my_idx);
arg_count = dts.get_dynamic_format_arg_count(env.func->name(), m_my_idx);
} else if (arg_type.is_constant_string()) {
auto& str = arg_type.get_string();
arg_count = dts.get_format_arg_count(str);
+2 -3
View File
@@ -4956,8 +4956,7 @@ void ConditionalMoveFalseElement::push_to_stack(const Env& env, FormPool& pool,
// pop the value and the original
auto popped = pop_to_forms({old_value, source}, env, pool, stack, true);
if (!is_symbol_true(popped.at(0))) {
lg::warn("{}: Failed to ConditionalMoveFalseElement::push_to_stack",
env.func->guessed_name.to_string());
lg::warn("{}: Failed to ConditionalMoveFalseElement::push_to_stack", env.func->name());
stack.push_value_to_reg(source, popped.at(1), true, TypeSpec("symbol"));
stack.push_form_element(this, true);
return;
@@ -5139,7 +5138,7 @@ void VectorFloatLoadStoreElement::push_to_stack(const Env& env, FormPool& pool,
}
}
auto name = env.func->guessed_name.to_string();
auto name = env.func->name();
// don't find vector-! inside of vector-!.
if (!m_is_load && name != "vector-!" && name != "vector+!" && name != "vector-reset!") {
if (try_vector_reset_inline(env, pool, stack, this)) {
+1 -1
View File
@@ -553,7 +553,7 @@ std::string LinkedObjectFile::print_function_disassembly(Function& func,
const std::string& extra_name) {
std::string result;
result += ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n";
result += "; .function " + func.guessed_name.to_string() + " " + extra_name + "\n";
result += "; .function " + func.name() + " " + extra_name + "\n";
result += ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n";
result += func.prologue.to_string(2) + "\n";
if (func.warnings.has_warnings()) {
+2 -2
View File
@@ -681,7 +681,7 @@ void ObjectFileDB::analyze_functions_ir1(const Config& config) {
func.guessed_name.unique_id = uid++;
func.guessed_name.id_in_object = func_in_obj++;
func.guessed_name.object_name = data.to_unique_name();
auto name = func.guessed_name.to_string();
auto name = func.name();
if (unique_names.find(name) != unique_names.end()) {
duplicated_functions[name].insert(data.to_unique_name());
@@ -700,7 +700,7 @@ void ObjectFileDB::analyze_functions_ir1(const Config& config) {
for_each_function([&](Function& func, int segment_id, ObjectFileData& data) {
(void)segment_id;
auto name = func.guessed_name.to_string();
auto name = func.name();
if (duplicated_functions.find(name) != duplicated_functions.end()) {
duplicated_functions[name].insert(data.to_unique_name());
+1
View File
@@ -84,6 +84,7 @@ class ObjectFileDB {
void ir2_do_segment_analysis_phase1(int seg, const Config& config);
void ir2_do_segment_analysis_phase2(int seg, const Config& config);
void ir2_setup_labels(const Config& config);
void ir2_run_mips2c(const Config& config);
std::string ir2_to_file(ObjectFileData& data, const Config& config);
std::string ir2_function_to_string(ObjectFileData& data, Function& function, int seg);
std::string ir2_final_out(ObjectFileData& data,
+48 -35
View File
@@ -3,7 +3,7 @@
* This runs the IR2 analysis passes.
*/
#include <common/link_types.h>
#include "common/link_types.h"
#include "ObjectFileDB.h"
#include "common/log/log.h"
#include "common/util/Timer.h"
@@ -24,6 +24,7 @@
#include "decompiler/analysis/symbol_def_map.h"
#include "common/goos/PrettyPrinter.h"
#include "decompiler/IR2/Form.h"
#include "decompiler/analysis/mips2c.h"
namespace decompiler {
@@ -65,17 +66,15 @@ void ObjectFileDB::analyze_functions_ir2(const std::string& output_dir,
ir2_symbol_definition_map(output_dir);
}
// if (!skip_debug_output) {
// lg::info("Storing temporary form result...");
// ir2_store_current_forms();
// }
lg::info("Inserting anonymous function definitions...");
ir2_insert_anonymous_functions(DEBUG_SEGMENT);
ir2_insert_anonymous_functions(MAIN_SEGMENT);
ir2_insert_anonymous_functions(TOP_LEVEL_SEGMENT);
// doesn't really matter where we do this.
ir2_run_mips2c(config);
if (!output_dir.empty()) {
lg::info("Writing results...");
ir2_write_results(output_dir, config);
@@ -83,7 +82,7 @@ void ObjectFileDB::analyze_functions_ir2(const std::string& output_dir,
}
void ObjectFileDB::ir2_do_segment_analysis_phase1(int seg, const Config& config) {
lg::info("COMMON ANALYSIS 1 {}", seg);
lg::info("ASM analysis for {} segment", SEGMENT_NAMES[seg]);
lg::info("Processing basic blocks and control flow graph...");
ir2_basic_block_pass(seg, config);
@@ -94,7 +93,7 @@ void ObjectFileDB::ir2_do_segment_analysis_phase1(int seg, const Config& config)
}
void ObjectFileDB::ir2_do_segment_analysis_phase2(int seg, const Config& config) {
lg::info("COMMON ANALYSIS 2 {}", seg);
lg::info("GOAL analysis for {} segment", SEGMENT_NAMES[seg]);
lg::info("Running type analysis...");
ir2_type_analysis_pass(seg, config);
@@ -133,6 +132,15 @@ void ObjectFileDB::ir2_setup_labels(const Config& config) {
});
}
void ObjectFileDB::ir2_run_mips2c(const Config& config) {
for_each_function_def_order([&](Function& func, int, ObjectFileData&) {
if (config.hacks.mips2c_functions_by_name.count(func.name())) {
lg::info("MIPS2C on {}", func.name());
run_mips2c(&func);
}
});
}
/*!
* Analyze the top level function of each object.
* - Find global function definitions
@@ -174,7 +182,7 @@ void ObjectFileDB::ir2_top_level_pass(const Config& config) {
func.guessed_name.unique_id = uid++;
func.guessed_name.id_in_object = func_in_obj++;
func.guessed_name.object_name = data.to_unique_name();
auto name = func.guessed_name.to_string();
auto name = func.name();
switch (func.guessed_name.kind) {
case FunctionName::FunctionKind::METHOD:
@@ -212,6 +220,12 @@ void ObjectFileDB::ir2_top_level_pass(const Config& config) {
func.warnings.info("Flagged as asm by config");
func.suspected_asm = true;
}
if (config.hacks.mips2c_functions_by_name.find(name) !=
config.hacks.mips2c_functions_by_name.end()) {
func.warnings.info("Flagged as mips2c by config");
func.suspected_asm = true;
}
}
}
});
@@ -219,7 +233,7 @@ void ObjectFileDB::ir2_top_level_pass(const Config& config) {
// we remember duplicates like this so we can warn on all occurances of the duplicate name
for_each_function([&](Function& func, int segment_id, ObjectFileData& data) {
(void)segment_id;
auto name = func.guessed_name.to_string();
auto name = func.name();
if (duplicated_functions.find(name) != duplicated_functions.end()) {
duplicated_functions[name].insert(data.to_unique_name());
@@ -280,23 +294,21 @@ void ObjectFileDB::ir2_basic_block_pass(int seg, const Config& config) {
// build a control flow graph, just looking at branch instructions.
CondWithElseLengthHack hack;
auto lookup =
config.hacks.cond_with_else_len_by_func_name.find(func.guessed_name.to_string());
auto lookup = config.hacks.cond_with_else_len_by_func_name.find(func.name());
if (lookup != config.hacks.cond_with_else_len_by_func_name.end()) {
hack = lookup->second;
}
std::unordered_set<int> asm_br_blocks;
auto asm_lookup =
config.hacks.blocks_ending_in_asm_branch_by_func_name.find(func.guessed_name.to_string());
auto asm_lookup = config.hacks.blocks_ending_in_asm_branch_by_func_name.find(func.name());
if (asm_lookup != config.hacks.blocks_ending_in_asm_branch_by_func_name.end()) {
asm_br_blocks = asm_lookup->second;
}
func.cfg = build_cfg(data.linked_data, seg, func, hack, asm_br_blocks);
if (!func.cfg->is_fully_resolved()) {
lg::warn("Function {} from {} failed to build control flow graph!",
func.guessed_name.to_string(), data.to_unique_name());
lg::warn("Function {} from {} failed to build control flow graph!", func.name(),
data.to_unique_name());
failed_to_build_cfg++;
} else {
func.cfg_ok = true;
@@ -363,13 +375,12 @@ void ObjectFileDB::ir2_atomic_op_pass(int seg, const Config& config) {
func.ir2.atomic_ops_attempted = true;
attempted++;
try {
bool inline_asm =
config.hacks.hint_inline_assembly_functions.find(func.guessed_name.to_string()) !=
config.hacks.hint_inline_assembly_functions.end();
bool inline_asm = config.hacks.hint_inline_assembly_functions.find(func.name()) !=
config.hacks.hint_inline_assembly_functions.end();
std::unordered_set<int> blocks_ending_in_asm_branch;
auto asm_branch_it = config.hacks.blocks_ending_in_asm_branch_by_func_name.find(
func.guessed_name.to_string());
auto asm_branch_it =
config.hacks.blocks_ending_in_asm_branch_by_func_name.find(func.name());
if (asm_branch_it != config.hacks.blocks_ending_in_asm_branch_by_func_name.end()) {
blocks_ending_in_asm_branch = asm_branch_it->second;
@@ -382,8 +393,8 @@ void ObjectFileDB::ir2_atomic_op_pass(int seg, const Config& config) {
func.ir2.env.set_end_var(func.ir2.atomic_ops->end_op().return_var());
successful++;
} catch (std::exception& e) {
lg::warn("Function {} from {} could not be converted to atomic ops: {}",
func.guessed_name.to_string(), data.to_unique_name(), e.what());
lg::warn("Function {} from {} could not be converted to atomic ops: {}", func.name(),
data.to_unique_name(), e.what());
func.warnings.general_warning("Failed to convert to atomic ops: {}", e.what());
}
}
@@ -440,7 +451,7 @@ void ObjectFileDB::ir2_type_analysis_pass(int seg, const Config& config) {
func.type = ts;
attempted_functions++;
// try type analysis here.
auto func_name = func.guessed_name.to_string();
auto func_name = func.name();
auto register_casts =
try_lookup(config.register_type_casts_by_function_by_atomic_op_idx, func_name);
func.ir2.env.set_type_casts(register_casts);
@@ -465,9 +476,8 @@ void ObjectFileDB::ir2_type_analysis_pass(int seg, const Config& config) {
func.warnings.type_prop_warning("Type analysis failed");
}
} else {
lg::warn("Function {} didn't know its type", func.guessed_name.to_string());
func.warnings.type_prop_warning("Function {} has unknown type",
func.guessed_name.to_string());
lg::warn("Function {} didn't know its type", func.name());
func.warnings.type_prop_warning("Function {} has unknown type", func.name());
}
}
});
@@ -505,7 +515,7 @@ void ObjectFileDB::ir2_register_usage_pass(int seg) {
for (auto& x : dep_regs) {
if ((x.get_kind() == Reg::VF && x.get_vf() != 0) || x.get_kind() == Reg::SPECIAL) {
lg::error("Bad vf dependency on {} in {}", x.to_charp(), func.guessed_name.to_string());
lg::error("Bad vf dependency on {} in {}", x.to_charp(), func.name());
func.warnings.bad_vf_dependency("{}", x.to_string());
continue;
}
@@ -519,8 +529,7 @@ void ObjectFileDB::ir2_register_usage_pass(int seg) {
continue;
}
lg::error("Bad register dependency on {} in {}", x.to_charp(),
func.guessed_name.to_string());
lg::error("Bad register dependency on {} in {}", x.to_charp(), func.name());
func.warnings.general_warning("Function may read a register that is not set: {}",
x.to_string());
}
@@ -548,7 +557,7 @@ void ObjectFileDB::ir2_variable_pass(int seg) {
func.ir2.env.set_local_vars(*result);
}
} catch (const std::exception& e) {
lg::warn("variable pass failed on {}: {}", func.guessed_name.to_string(), e.what());
lg::warn("variable pass failed on {}: {}", func.name(), e.what());
}
}
});
@@ -610,7 +619,7 @@ void ObjectFileDB::ir2_build_expressions(int seg, const Config& config) {
if (func.ir2.top_form && func.ir2.env.has_type_analysis() && func.ir2.env.has_local_vars() &&
func.ir2.env.types_succeeded) {
attempted++;
auto name = func.guessed_name.to_string();
auto name = func.name();
auto arg_config = config.function_arg_names.find(name);
auto var_config = config.function_var_overrides.find(name);
if (convert_to_expressions(func.ir2.top_form, *func.ir2.form_pool, func,
@@ -685,7 +694,7 @@ void ObjectFileDB::ir2_insert_anonymous_functions(int seg) {
total += insert_static_refs(func.ir2.top_form, *func.ir2.form_pool, func, dts);
} catch (std::exception& e) {
func.warnings.general_warning("Failed static ref finding: {}\n", e.what());
lg::error("Function {} failed static ref: {}\n", func.guessed_name.to_string(), e.what());
lg::error("Function {} failed static ref: {}\n", func.name(), e.what());
}
}
});
@@ -733,7 +742,7 @@ std::string ObjectFileDB::ir2_to_file(ObjectFileData& data, const Config& config
result += ir2_function_to_string(data, func, seg);
} catch (std::exception& e) {
result += "Failed to write";
result += func.guessed_name.to_string();
result += func.name();
result += ": ";
result += e.what();
result += "\n";
@@ -856,7 +865,7 @@ void append_commented(std::string& line,
std::string ObjectFileDB::ir2_function_to_string(ObjectFileData& data, Function& func, int seg) {
std::string result;
result += ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n";
result += "; .function " + func.guessed_name.to_string() + "\n";
result += "; .function " + func.name() + "\n";
result += ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n";
result += func.prologue.to_string(2) + "\n";
if (func.warnings.has_warnings()) {
@@ -994,6 +1003,10 @@ std::string ObjectFileDB::ir2_function_to_string(ObjectFileData& data, Function&
}
}
if (func.mips2c_output) {
result += *func.mips2c_output;
}
result += "\n";
assert(total_instructions_printed == (func.end_word - func.start_word - 1));
+4
View File
@@ -0,0 +1,4 @@
---
BasedOnStyle: Chromium
ColumnLimit: 100
SortIncludes: true
+4 -3
View File
@@ -1,11 +1,12 @@
#include <memory>
#include "atomic_op_builder.h"
#include "common/log/log.h"
#include "common/symbols.h"
#include "atomic_op_builder.h"
#include "decompiler/Function/Function.h"
#include "decompiler/Disasm/InstructionMatching.h"
#include "decompiler/util/TP_Type.h"
#include "decompiler/Function/Function.h"
#include "decompiler/Function/Warnings.h"
#include "decompiler/util/TP_Type.h"
namespace decompiler {
+1
View File
@@ -1,5 +1,6 @@
#pragma once
#include <vector>
#include "decompiler/IR2/AtomicOp.h"
namespace decompiler {
+5 -6
View File
@@ -4,9 +4,9 @@
*/
#include "cfg_builder.h"
#include "decompiler/util/MatchParam.h"
#include "decompiler/Function/Function.h"
#include "decompiler/IR2/Form.h"
#include "decompiler/util/MatchParam.h"
namespace decompiler {
namespace {
@@ -395,8 +395,8 @@ bool try_clean_up_sc_as_and(FormPool& pool, Function& func, ShortCircuitElement*
bool this_live_out = (branch_info.written_and_unused.find(ir_dest.reg()) ==
branch_info.written_and_unused.end());
if (live_out_result != this_live_out) {
lg::error("Bad live out result on {}. At 0 was {} now at {} is {}",
func.guessed_name.to_string(), live_out_result, i, this_live_out);
lg::error("Bad live out result on {}. At 0 was {} now at {} is {}", func.name(),
live_out_result, i, this_live_out);
}
assert(live_out_result == this_live_out);
}
@@ -677,7 +677,7 @@ void convert_cond_no_else_to_compare(FormPool& pool,
if (condition_as_single) {
*ir_loc = replacement;
} else {
// lg::error("Weird case in {}", f.guessed_name.to_string());
// lg::error("Weird case in {}", f.name());
(void)f;
auto seq = cne->entries.front().condition;
seq->pop_back();
@@ -1785,8 +1785,7 @@ void build_initial_forms(Function& function) {
function.ir2.top_form = result;
} catch (std::runtime_error& e) {
function.warnings.general_warning(e.what());
lg::warn("Failed to build initial forms in {}: {}", function.guessed_name.to_string(),
e.what());
lg::warn("Failed to build initial forms in {}: {}", function.name(), e.what());
}
}
} // namespace decompiler
+4 -4
View File
@@ -1,9 +1,9 @@
#include "expression_build.h"
#include "common/goos/PrettyPrinter.h"
#include "decompiler/Function/Function.h"
#include "decompiler/IR2/Form.h"
#include "decompiler/IR2/FormStack.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "common/goos/PrettyPrinter.h"
namespace decompiler {
@@ -157,15 +157,15 @@ bool convert_to_expressions(
auto warn = fmt::format(
"Function {} has a return type of none, but the expression builder found a return "
"statement.",
f.guessed_name.to_string());
f.name());
f.warnings.expression_build_warning(warn);
lg::warn(warn);
}
}
} catch (std::exception& e) {
f.warnings.expression_build_warning("In {}: {}", f.guessed_name.to_string(), e.what());
lg::warn("In {}: {}", f.guessed_name.to_string(), e.what());
f.warnings.expression_build_warning("In {}: {}", f.name(), e.what());
lg::warn("In {}: {}", f.name(), e.what());
return false;
}
+1 -1
View File
@@ -1,8 +1,8 @@
#pragma once
#include <string>
#include <unordered_map>
#include <vector>
#include <string>
namespace decompiler {
class Form;
+11 -12
View File
@@ -1,17 +1,16 @@
#include "decompiler/IR2/GenericElementMatcher.h"
#include "final_output.h"
#include "decompiler/IR2/Form.h"
#include "common/goos/PrettyPrinter.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "decompiler/IR2/Form.h"
#include "decompiler/IR2/GenericElementMatcher.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
#include "decompiler/util/DecompilerTypeSystem.h"
namespace decompiler {
goos::Object get_arg_list_for_function(const Function& func, const Env& env) {
std::vector<goos::Object> argument_elts;
if (func.type.arg_count() < 1) {
throw std::runtime_error(
fmt::format("Function {} has unknown type.\n", func.guessed_name.to_string()));
throw std::runtime_error(fmt::format("Function {} has unknown type.\n", func.name()));
}
assert(func.type.arg_count() >= 1);
for (size_t i = 0; i < func.type.arg_count() - 1; i++) {
@@ -105,9 +104,9 @@ std::string final_defun_out(const Function& func,
top.push_back(pretty_print::to_symbol(def_name));
if (behavior) {
top.push_back(pretty_print::to_symbol(func.guessed_name.to_string() + " " + *behavior));
top.push_back(pretty_print::to_symbol(func.name() + " " + *behavior));
} else {
top.push_back(pretty_print::to_symbol(func.guessed_name.to_string()));
top.push_back(pretty_print::to_symbol(func.name()));
}
top.push_back(arguments);
auto top_form = pretty_print::build_list(top);
@@ -147,7 +146,7 @@ std::string final_defun_out(const Function& func,
assert(special_mode == FunctionDefSpecials::NONE);
std::vector<goos::Object> top;
top.push_back(pretty_print::to_symbol(def_name));
top.push_back(pretty_print::to_symbol(func.guessed_name.to_string()));
top.push_back(pretty_print::to_symbol(func.name()));
top.push_back(arguments);
auto top_form = pretty_print::build_list(top);
@@ -160,7 +159,7 @@ std::string final_defun_out(const Function& func,
std::vector<goos::Object> top;
top.push_back(pretty_print::to_symbol("state-handler"));
top.push_back(pretty_print::to_symbol(func.guessed_name.to_string()));
top.push_back(pretty_print::to_symbol(func.name()));
top.push_back(arguments);
auto top_form = pretty_print::build_list(top);
@@ -334,7 +333,7 @@ std::string write_from_top_level_form(Form* top_form,
something_matched = true;
result += fmt::format(";; definition for function {}\n",
global_match_result.maps.strings.at(func_name));
if (skip_functions.find(func->guessed_name.to_string()) == skip_functions.end()) {
if (skip_functions.find(func->name()) == skip_functions.end()) {
result += careful_function_to_string(func, dts);
} else {
result += ";; skipped.\n\n";
@@ -351,7 +350,7 @@ std::string write_from_top_level_form(Form* top_form,
result +=
fmt::format(";; definition for method {} of type {}\n", func->guessed_name.method_id,
method_match_result.maps.strings.at(type_name));
if (skip_functions.find(func->guessed_name.to_string()) == skip_functions.end()) {
if (skip_functions.find(func->name()) == skip_functions.end()) {
result += careful_function_to_string(func, dts);
} else {
result += ";; skipped.\n\n";
@@ -387,7 +386,7 @@ std::string write_from_top_level_form(Form* top_form,
something_matched = true;
result += fmt::format(";; definition (debug) for function {}\n",
debug_match_result.maps.strings.at(0));
if (skip_functions.find(func->guessed_name.to_string()) == skip_functions.end()) {
if (skip_functions.find(func->name()) == skip_functions.end()) {
result += careful_function_to_string(func, dts, FunctionDefSpecials::DEFUN_DEBUG);
} else {
result += ";; skipped.\n\n";
+1
View File
@@ -1,5 +1,6 @@
#pragma once
#include <string>
#include "decompiler/Function/Function.h"
namespace decompiler {
+4 -4
View File
@@ -1,11 +1,11 @@
#include "find_defstates.h"
#include "decompiler/IR2/Form.h"
#include "common/goos/PrettyPrinter.h"
#include "common/type_system/state.h"
#include "decompiler/IR2/Form.h"
#include "decompiler/IR2/GenericElementMatcher.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
#include "common/type_system/state.h"
namespace decompiler {
@@ -129,7 +129,7 @@ std::vector<DefstateElement::Entry> get_defstate_entries(
this_entry.is_behavior = true;
if (print_renames) {
fmt::print("RENAME: {} to ", handler_func->guessed_name.to_string());
fmt::print("RENAME: {} to ", handler_func->name());
}
if (virtual_child) {
@@ -138,7 +138,7 @@ std::vector<DefstateElement::Entry> get_defstate_entries(
handler_func->guessed_name.set_as_nv_state(state_name, handler_kind);
}
if (print_renames) {
fmt::print("{}\n", handler_func->guessed_name.to_string());
fmt::print("{}\n", handler_func->name());
}
// scary part - modify the function type!
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#include "decompiler/util/DecompilerTypeSystem.h"
#include "decompiler/Function/Function.h"
#include "decompiler/util/DecompilerTypeSystem.h"
namespace decompiler {
void run_defstate(Function& top_level_func);
+5 -6
View File
@@ -1,13 +1,12 @@
#include "inline_asm_rewrite.h"
#include "common/goos/PrettyPrinter.h"
#include "decompiler/Function/Function.h"
#include "decompiler/IR2/Form.h"
#include "decompiler/IR2/FormStack.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "common/goos/PrettyPrinter.h"
#include "decompiler/IR2/OpenGoalMapping.h"
#include "decompiler/analysis/reg_usage.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
#include "decompiler/analysis/reg_usage.h"
#include "decompiler/util/DecompilerTypeSystem.h"
namespace decompiler {
@@ -89,8 +88,8 @@ bool rewrite_inline_asm_instructions(Form* top_level_form,
top_level_form->push_back(rlet);
}
} catch (std::exception& e) {
std::string warning = fmt::format("ASM instruction re-writing failed in {}: {}",
f.guessed_name.to_string(), e.what());
std::string warning =
fmt::format("ASM instruction re-writing failed in {}: {}", f.name(), e.what());
lg::warn(warning);
f.warnings.general_warning(";; {}", warning);
return false;
+5 -6
View File
@@ -1,10 +1,10 @@
#include <algorithm>
#include <limits>
#include <array>
#include <limits>
#include "insert_lets.h"
#include "decompiler/IR2/GenericElementMatcher.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "insert_lets.h"
namespace decompiler {
@@ -79,8 +79,7 @@ Form* lca_form(Form* a, Form* b, const Env& env) {
bi--;
}
if (!result) {
fmt::print("{} bad form is {}\n\n{}\n", env.func->guessed_name.to_string(), a->to_string(env),
b->to_string(env));
fmt::print("{} bad form is {}\n\n{}\n", env.func->name(), a->to_string(env), b->to_string(env));
}
assert(result);
@@ -733,7 +732,7 @@ FormElement* rewrite_multi_let_as_vector_dot(LetElement* in, const Env& env, For
}
// don't inline in the actual function...
if (env.func->guessed_name.to_string() == "vector-dot") {
if (env.func->name() == "vector-dot") {
return nullptr;
}
@@ -804,7 +803,7 @@ bool register_can_hold_var(const Register& reg) {
LetStats insert_lets(const Function& func, Env& env, FormPool& pool, Form* top_level_form) {
(void)func;
// if (func.guessed_name.to_string() != "(method 4 pair)") {
// if (func.name() != "(method 4 pair)") {
// return {};
// }
LetStats stats;
+1 -1
View File
@@ -1,7 +1,7 @@
#pragma once
#include "decompiler/IR2/Env.h"
#include "decompiler/Function/Function.h"
#include "decompiler/IR2/Env.h"
#include "decompiler/IR2/Form.h"
namespace decompiler {
+1 -1
View File
@@ -56,7 +56,7 @@ void find_functions(LabelDB* db, LinkedObjectFile* file) {
auto idx_of_label = db->try_get_index_by_offset(seg, offset_of_function);
if (!idx_of_label) {
func.warnings.general_warning("Could not find any references to this function: {}",
func.guessed_name.to_string());
func.name());
} else {
auto old = db->set_and_get_previous(*idx_of_label, func.type, false, {});
if (old.known) {
+779
View File
@@ -0,0 +1,779 @@
#include <set>
#include "mips2c.h"
#include "decompiler/Disasm/InstructionMatching.h"
#include "decompiler/Function/Function.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
namespace decompiler {
//////////////////////
// Register Helpers
//////////////////////
Register rs7() {
return make_gpr(Reg::S7);
}
Register rr0() {
return make_gpr(Reg::R0);
}
Register rfp() {
return make_gpr(Reg::FP);
}
Register rra() {
return make_gpr(Reg::RA);
}
Register rt9() {
return make_gpr(Reg::T9);
}
Register rv0() {
return make_gpr(Reg::V0);
}
Register rsp() {
return make_gpr(Reg::SP);
}
Register make_vf(int idx) {
return Register(Reg::VF, idx);
}
std::string goal_to_c_name(const std::string& name) {
std::string result;
for (auto c : name) {
if (c == '!' || c == '?' || c == '*') {
continue;
}
if (c == '-') {
c = '_';
}
result.push_back(c);
}
return result;
}
std::string goal_to_c_function_name(const FunctionName& name) {
switch (name.kind) {
case FunctionName::FunctionKind::GLOBAL:
return goal_to_c_name(name.function_name);
default:
assert(false);
}
}
const char* reg_to_name(const InstructionAtom& atom) {
assert(atom.is_reg());
return atom.get_reg().to_charp();
}
struct Mips2C_Line {
std::string code;
std::string comment;
Mips2C_Line() = default;
Mips2C_Line(const std::string& _code) : code(_code) {}
Mips2C_Line(const std::string& _code, const std::string& _comment)
: code(_code), comment(_comment) {}
};
struct Mips2C_Output {
void output_label(int block_idx) { lines.push_back(fmt::format("\nblock_{}:", block_idx)); }
void output_line_comment(const std::string& text) { lines.emplace_back("// " + text); }
void output_instr(const std::string& instr, const std::string& comment) {
lines.emplace_back(instr, comment);
}
std::string write_to_string(const FunctionName& goal_func_name) const {
std::string name = goal_to_c_function_name(goal_func_name);
std::string result = "//--------------------------MIPS2C---------------------\n";
result += "#include \"game/mips2c/mips2c_private.h\"\n";
result += "#include \"game/kernel/kscheme.h\"\n";
// start of namespace for this function
result += "namespace Mips2C {\n";
result += fmt::format("namespace {} {{\n", name);
if (!symbol_cache.empty()) {
result += "struct Cache {\n";
for (auto& sym : symbol_cache) {
result += fmt::format(" void* {}; // {}\n", goal_to_c_name(sym), sym);
}
result += "} cache;\n\n";
}
result += "u64 execute(void* ctxt) {\n";
result += " auto* c = (ExecutionContext*)ctxt;\n";
result += " bool bc = false;";
for (auto& line : lines) {
result += " ";
result += line.code;
if (!line.comment.empty()) {
if (line.code.length() < 50) {
for (int i = 0; i < 50 - (int)line.code.length(); i++) {
result.push_back(' ');
}
}
result.append("// ");
result.append(line.comment);
}
result += '\n';
}
result += "end_of_function:\n return c->gprs[v0].du64[0];\n";
result += "}\n\n";
// link function:
result += "void link() {\n";
for (auto& sym : symbol_cache) {
result += fmt::format(" cache.{} = intern_from_c(\"{}\").c();\n", goal_to_c_name(sym), sym);
}
result +=
fmt::format(" gLinkedFunctionTable.reg(\"{}\", execute);\n", goal_func_name.to_string());
result += "}\n\n";
result += fmt::format("}} // namespace {}\n", name);
result += "} // namespace Mips2C\n";
result +=
fmt::format("// add {}::link to the link callback table for the object file.\n", name);
result += "// FWD DEC:\n";
result += fmt::format("namespace {} {{ extern void link(); }}\n", name);
return result;
}
void require_symbol(const std::string& name) { symbol_cache.insert(name); }
std::vector<Mips2C_Line> lines;
std::set<std::string> symbol_cache;
};
struct M2C_Block {
int idx = -1;
int succ_branch = -1;
int succ_ft = -1;
std::vector<int> pred;
int start_instr = -1;
int end_instr = -1;
bool has_branch = false;
bool branch_likely = false;
bool branch_always = false;
bool has_pred(int pidx) const {
for (auto p : pred) {
if (p == pidx) {
return true;
}
}
return false;
}
};
void link_fall_through(int first_idx, int second_idx, std::vector<M2C_Block>& blocks) {
auto& first = blocks.at(first_idx);
auto& second = blocks.at(second_idx);
assert(first.succ_ft == -1); // don't want to overwrite something by accident.
// can only fall through to the next code in memory.
assert(first_idx + 1 == second_idx);
first.succ_ft = second_idx;
if (!second.has_pred(first_idx)) {
// if a block can block fall through and branch to the same block, we want to avoid adding
// it as a pred twice. This is rare, but does happen and makes sense with likely branches
// which only run the delay slot when taken.
second.pred.push_back(first_idx);
}
}
void link_branch(int first_idx, int second_idx, std::vector<M2C_Block>& blocks) {
auto& first = blocks.at(first_idx);
auto& second = blocks.at(second_idx);
assert(first.succ_branch == -1);
first.succ_branch = second_idx;
if (!second.has_pred(first_idx)) {
// see comment in link_fall_through
second.pred.push_back(first_idx);
}
}
void link_fall_through_likely(int first_idx, int second_idx, std::vector<M2C_Block>& blocks) {
auto& first = blocks.at(first_idx);
auto& second = blocks.at(second_idx);
assert(first.succ_ft == -1); // don't want to overwrite something by accident.
// can only fall through to the next code in memory.
assert(first_idx + 2 == second_idx);
first.succ_ft = second_idx;
if (!second.has_pred(first_idx)) {
// see comment in link_fall_through
second.pred.push_back(first_idx);
}
}
/*!
* Compute predecessor and successor of each block
*
* NOTE: due to likely branch delays/etc, succ_ft and succ_branch behave strangely.
*
* The succ_ft destination is always taken if has_branch is false.
* The succ_branch destination is always taken if branch_always it true.
*
* Otherwise, has_branch is true, and the succ_branch is taken if the branch condition is true.
* The succ_ft and succ_branch may be _anywhere_. succ_ft may not always be the next destination.
*/
std::vector<M2C_Block> setup_preds_and_succs(const Function& func, const LinkedObjectFile& file) {
// create m2c blocks
std::vector<M2C_Block> blocks;
blocks.resize(func.basic_blocks.size());
for (size_t i = 0; i < blocks.size(); i++) {
blocks[i].idx = i;
blocks[i].start_instr = func.basic_blocks[i].start_word;
blocks[i].end_instr = func.basic_blocks[i].end_word;
}
// set up succ / pred
for (int i = 0; i < int(func.basic_blocks.size()); i++) {
auto& b = func.basic_blocks[i];
assert(!blocks.at(i).branch_always);
bool not_last = (i + 1) < int(func.basic_blocks.size());
if (b.end_word == b.start_word) {
// there's no room for a branch here, fall through to the end
if (not_last) {
link_fall_through(i, i + 1, blocks);
}
} else {
// room for at least a likely branch, try that first.
int likely_branch_idx = b.end_word - 1;
assert(likely_branch_idx >= b.start_word);
auto& likely_branch_candidate = func.instructions.at(likely_branch_idx);
if (is_branch(likely_branch_candidate, true)) {
// is a likely branch
blocks.at(i).has_branch = true;
blocks.at(i).branch_likely = true;
// blocks.at(i).kind = CfgVtx::DelaySlotKind::NO_DELAY;
bool branch_always = is_always_branch(likely_branch_candidate);
// need to find block target
int block_target = -1;
int label_target = likely_branch_candidate.get_label_target();
assert(label_target != -1);
const auto& label = file.labels.at(label_target);
// assert(label.target_segment == seg);
assert((label.offset % 4) == 0);
int offset = label.offset / 4 - func.start_word;
assert(offset >= 0);
for (int j = int(func.basic_blocks.size()); j-- > 0;) {
if (func.basic_blocks[j].start_word == offset) {
block_target = j;
break;
}
}
assert(block_target != -1);
// "branch" to delay slot, which then "falls through" to the destination.
link_branch(i, i + 1, blocks);
if (branch_always) {
// don't continue to the next one
blocks.at(i).branch_always = true;
} else {
// not an always branch
if (not_last) {
// "fall through" to after the delay slot block.
// don't take the delay slot.
link_fall_through_likely(i, i + 2, blocks);
}
}
auto& delay_block = blocks.at(i + 1);
delay_block.branch_likely = false;
delay_block.branch_always = true;
delay_block.has_branch = true;
// delay_block.kind = CfgVtx::DelaySlotKind::NO_DELAY;
link_branch(i + 1, block_target, blocks);
} else {
if (b.end_word - b.start_word < 2) {
// no room for a branch, just fall through
if (not_last) {
link_fall_through(i, i + 1, blocks);
}
} else {
// try as a normal branch.
int idx = b.end_word - 2;
assert(idx >= b.start_word);
auto& branch_candidate = func.instructions.at(idx);
// auto& delay_slot_candidate = func.instructions.at(idx + 1);
if (is_branch(branch_candidate, false)) {
blocks.at(i).has_branch = true;
blocks.at(i).branch_likely = false;
// blocks.at(i).kind = get_delay_slot(delay_slot_candidate);
bool branch_always = is_always_branch(branch_candidate);
// need to find block target
int block_target = -1;
int label_target = branch_candidate.get_label_target();
assert(label_target != -1);
const auto& label = file.labels.at(label_target);
// assert(label.target_segment == seg);
assert((label.offset % 4) == 0);
int offset = label.offset / 4 - func.start_word;
assert(offset >= 0);
// the order here matters when there are zero size blocks. Unclear what the best answer
// is.
// i think in end it doesn't actually matter??
// for (int j = 0; j < int(func.basic_blocks.size()); j++) {
for (int j = int(func.basic_blocks.size()); j-- > 0;) {
if (func.basic_blocks[j].start_word == offset) {
block_target = j;
break;
}
}
assert(block_target != -1);
link_branch(i, block_target, blocks);
if (branch_always) {
// don't continue to the next one
blocks.at(i).branch_always = true;
} else {
// not an always branch
if (not_last) {
link_fall_through(i, i + 1, blocks);
}
}
} else {
// not a branch.
if (not_last) {
link_fall_through(i, i + 1, blocks);
}
}
}
}
}
}
return blocks;
}
bool block_requires_label(const Function* f,
const std::vector<M2C_Block>& blocks,
size_t block_idx) {
const auto& block = blocks[block_idx];
if (block.pred.empty()) {
// no way to get to this block??
if (block_idx != 0) {
// don't warn on the first block.
lg::warn("Mips2C function {} block {} is unreachable.", f->name(), block_idx);
}
return false;
}
if (block.pred.size() == 1 && block_idx > 0 && block.pred.front() == (int)block_idx - 1 &&
blocks.at(block_idx - 1).succ_ft == (int)block_idx) {
// the only way to get to this block is to fall through.
return false;
}
return true;
}
int g_unknown = 0;
Mips2C_Line handle_unknown(const std::string& instr_str) {
g_unknown++;
return fmt::format("// Unknown instr: {}", instr_str);
}
Mips2C_Line handle_generic_load(const Instruction& i0, const std::string& instr_str) {
if (i0.get_src(1).is_reg(rsp())) {
return handle_unknown(instr_str);
} else {
return {fmt::format("c->{}({}, {}, {});", i0.op_name_to_string(), reg_to_name(i0.get_dst(0)),
i0.get_src(0).get_imm(), reg_to_name(i0.get_src(1))),
instr_str};
}
}
Mips2C_Line handle_lw(Mips2C_Output& out, const Instruction& i0, const std::string& instr_str) {
if (i0.get_src(1).is_reg(rs7()) && i0.get_src(0).is_sym()) {
// symbol load.
out.require_symbol(i0.get_src(0).get_sym());
return {fmt::format("c->load_symbol({}, cache.{});", reg_to_name(i0.get_dst(0)),
goal_to_c_name(i0.get_src(0).get_sym())),
instr_str};
} else {
// fall back to standard loads
return handle_generic_load(i0, instr_str);
}
}
Mips2C_Line handle_generic_store(Mips2C_Output& /*out*/,
const Instruction& i0,
const std::string& instr_str) {
if (i0.get_src(2).is_reg(Register(Reg::GPR, Reg::SP))) {
return handle_unknown(instr_str);
} else {
return {fmt::format("c->{}({}, {}, {});", i0.op_name_to_string(), reg_to_name(i0.get_src(0)),
i0.get_src(1).get_imm(), reg_to_name(i0.get_src(2))),
instr_str};
}
}
Mips2C_Line handle_generic_op2_u16(const Instruction& i0, const std::string& instr_str) {
return {fmt::format("c->{}({}, {}, {});", i0.op_name_to_string(), reg_to_name(i0.get_dst(0)),
reg_to_name(i0.get_src(0)), i0.get_src(1).get_imm()),
instr_str};
}
Mips2C_Line handle_sw(Mips2C_Output& out, const Instruction& i0, const std::string& instr_str) {
if (i0.get_src(1).is_sym() && i0.get_src(2).is_reg(rs7())) {
return handle_unknown(instr_str);
// auto name = i0.get_src(1).get_sym();
// // store into symbol table!
// SimpleAtom val;
// if (i0.get_src(0).is_reg(rs7())) {
// // store a false
// val = SimpleAtom::make_sym_val("#f");
// } else if (i0.get_src(0).is_reg(rr0())) {
// // store a 0
// val = SimpleAtom::make_int_constant(0);
// } else {
// // store a register.
// val = make_src_atom(i0.get_src(0).get_reg(), idx);
// }
// return std::make_unique<StoreOp>(4, StoreOp::Kind::INTEGER,
// SimpleAtom::make_sym_val(name).as_expr(), val, idx);
} else {
return handle_generic_store(out, i0, instr_str);
}
}
std::string dest_to_char(u8 x) {
if (x == 0) {
return "NONE";
}
std::string dest;
if (x & 8)
dest.push_back('x');
if (x & 4)
dest.push_back('y');
if (x & 2)
dest.push_back('z');
if (x & 1)
dest.push_back('w');
return dest;
}
Mips2C_Line handle_generic_op3_bc_mask(const Instruction& i0,
const std::string& instr_str,
const std::string& op_name) {
return {fmt::format("c->{}(DEST::{}, BC::{}, {}, {}, {});", op_name, dest_to_char(i0.cop2_dest),
i0.cop2_bc_to_char(), reg_to_name(i0.get_dst(0)), reg_to_name(i0.get_src(0)),
reg_to_name(i0.get_src(1))),
instr_str};
}
Mips2C_Line handle_generic_op3_mask(const Instruction& i0,
const std::string& instr_str,
const std::string& op_name) {
return {fmt::format("c->{}(DEST::{}, {}, {}, {});", op_name, dest_to_char(i0.cop2_dest),
reg_to_name(i0.get_dst(0)), reg_to_name(i0.get_src(0)),
reg_to_name(i0.get_src(1))),
instr_str};
}
Mips2C_Line handle_generic_op3(const Instruction& i0,
const std::string& instr_str,
const std::optional<std::string>& name_override) {
return {fmt::format("c->{}({}, {}, {});", name_override ? *name_override : i0.op_name_to_string(),
reg_to_name(i0.get_dst(0)), reg_to_name(i0.get_src(0)),
reg_to_name(i0.get_src(1))),
instr_str};
}
Mips2C_Line handle_generic_op2_mask(const Instruction& i0,
const std::string& instr_str,
const std::string& op_name) {
return {fmt::format("c->{}(DEST::{}, {}, {});", op_name, dest_to_char(i0.cop2_dest),
reg_to_name(i0.get_dst(0)), reg_to_name(i0.get_src(0))),
instr_str};
}
Mips2C_Line handle_generic_op2(const Instruction& i0,
const std::string& instr_str,
const std::string& op_name) {
return {fmt::format("c->{}({}, {});", op_name, reg_to_name(i0.get_dst(0)),
reg_to_name(i0.get_src(0))),
instr_str};
}
Mips2C_Line handle_or(const Instruction& i0, const std::string& instr_str) {
if (is_gpr_3(i0, InstructionKind::OR, {}, rs7(), rr0())) {
// set reg_dest to #f : or reg_dest, s7, r0
return handle_unknown(instr_str);
} else if (is_gpr_3(i0, InstructionKind::OR, {}, rr0(), rr0())) {
// set reg_dest to 0 : or reg_dest, r0, r0
return handle_unknown(instr_str);
} else if (is_gpr_3(i0, InstructionKind::OR, {}, {}, rr0())) {
// set dst to src : or dst, src, r0
return {
fmt::format("c->mov64({}, {});", reg_to_name(i0.get_dst(0)), reg_to_name(i0.get_src(0))),
instr_str};
} else {
// actually do a logical OR of two registers: or a0, a1, a2
return handle_unknown(instr_str);
}
}
Mips2C_Line handle_sll(const Instruction& i0, const std::string& instr_str) {
if (is_nop(i0)) {
return {"// nop", instr_str};
} else {
return handle_generic_op2_u16(i0, instr_str);
}
}
Mips2C_Line handle_vmula_bc(const Instruction& i0, const std::string& instr_str) {
return {fmt::format("c->vmula_bc(DEST::{}, BC::{}, {}, {});", dest_to_char(i0.cop2_dest),
i0.cop2_bc_to_char(), reg_to_name(i0.get_src(0)), reg_to_name(i0.get_src(1))),
instr_str};
}
Mips2C_Line handle_vmadda_bc(const Instruction& i0, const std::string& instr_str) {
return {fmt::format("c->vmadda_bc(DEST::{}, BC::{}, {}, {});", dest_to_char(i0.cop2_dest),
i0.cop2_bc_to_char(), reg_to_name(i0.get_src(0)), reg_to_name(i0.get_src(1))),
instr_str};
}
std::string reg64_or_zero(const InstructionAtom& atom) {
if (atom.is_reg(Register(Reg::GPR, Reg::R0))) {
return "0";
} else {
return fmt::format("c->sgpr64({})", atom.get_reg().to_string());
}
}
Mips2C_Line handle_branch_reg2(const Instruction& i0,
const std::string& instr_str,
const std::string& op_name) {
return {fmt::format("bc = {} {} {};", reg64_or_zero(i0.get_src(0)), op_name,
reg64_or_zero(i0.get_src(1))),
instr_str};
}
Mips2C_Line handle_non_likely_branch_bc(const Instruction& i0, const std::string& instr_str) {
switch (i0.kind) {
case InstructionKind::BNE:
return handle_branch_reg2(i0, instr_str, "!=");
case InstructionKind::BEQ:
return handle_branch_reg2(i0, instr_str, "==");
case InstructionKind::BLTZ:
return {fmt::format("bc = ((s64){}) < 0;", reg64_or_zero(i0.get_src(0))), instr_str};
case InstructionKind::BGTZ:
return {fmt::format("bc = ((s64){}) > 0;", reg64_or_zero(i0.get_src(0))), instr_str};
default:
return handle_unknown(instr_str);
}
}
Mips2C_Line handle_vdiv(const Instruction& i0, const std::string& instr_string) {
return {fmt::format("c->vdiv({}, BC::{}, {}, BC::{});", reg_to_name(i0.get_src(0)),
"xyzw"[i0.get_src(1).get_vf_field()], reg_to_name(i0.get_src(2)),
"xyzw"[i0.get_src(3).get_vf_field()]),
instr_string};
}
Mips2C_Line handle_por(const Instruction& i0, const std::string& instr_string) {
if (is_gpr_3(i0, InstructionKind::POR, {}, {}, rr0())) {
return {fmt::format("c->mov128_gpr_gpr({}, {});", reg_to_name(i0.get_dst(0)),
reg_to_name(i0.get_src(0))),
instr_string};
} else {
return handle_generic_op3(i0, instr_string, {});
}
}
Mips2C_Line handle_normal_instr(Mips2C_Output& output,
const Instruction& i0,
const std::string& instr_str,
int& unknown_count) {
switch (i0.kind) {
case InstructionKind::LW:
return handle_lw(output, i0, instr_str);
case InstructionKind::LBU:
case InstructionKind::LWU:
case InstructionKind::LQ:
case InstructionKind::LQC2:
return handle_generic_load(i0, instr_str);
case InstructionKind::SQ:
case InstructionKind::SQC2:
return handle_generic_store(output, i0, instr_str);
case InstructionKind::VADD_BC:
return handle_generic_op3_bc_mask(i0, instr_str, "vadd_bc");
case InstructionKind::VSUB_BC:
return handle_generic_op3_bc_mask(i0, instr_str, "vsub_bc");
case InstructionKind::VMUL_BC:
return handle_generic_op3_bc_mask(i0, instr_str, "vmul_bc");
case InstructionKind::VMUL:
return handle_generic_op3_mask(i0, instr_str, "vmul");
case InstructionKind::VADD:
return handle_generic_op3_mask(i0, instr_str, "vadd");
case InstructionKind::VSUB:
return handle_generic_op3_mask(i0, instr_str, "vsub");
case InstructionKind::OR:
return handle_or(i0, instr_str);
case InstructionKind::SW:
return handle_sw(output, i0, instr_str);
case InstructionKind::VMOVE:
return handle_generic_op2_mask(i0, instr_str, "vmove");
case InstructionKind::VITOF0:
return handle_generic_op2_mask(i0, instr_str, "vitof0");
case InstructionKind::VFTOI4:
return handle_generic_op2_mask(i0, instr_str, "vftoi4");
case InstructionKind::ANDI:
case InstructionKind::ORI:
case InstructionKind::SRA:
return handle_generic_op2_u16(i0, instr_str);
case InstructionKind::SLL:
return handle_sll(i0, instr_str);
case InstructionKind::DADDU:
case InstructionKind::ADDU:
case InstructionKind::PEXTLH:
case InstructionKind::PEXTLB:
case InstructionKind::MOVN:
case InstructionKind::MOVZ:
return handle_generic_op3(i0, instr_str, {});
case InstructionKind::AND:
return handle_generic_op3(i0, instr_str, "and_"); // and isn't allowed in C++
case InstructionKind::DADDIU:
case InstructionKind::ADDIU:
return handle_generic_op2_u16(i0, instr_str);
case InstructionKind::QMTC2:
return handle_generic_op2(i0, instr_str, "mov128_vf_gpr");
case InstructionKind::QMFC2:
return handle_generic_op2(i0, instr_str, "mov128_gpr_vf");
case InstructionKind::VMULA_BC:
return handle_vmula_bc(i0, instr_str);
case InstructionKind::VMADDA_BC:
return handle_vmadda_bc(i0, instr_str);
case InstructionKind::VMADD_BC:
return handle_generic_op3_bc_mask(i0, instr_str, "vmadd_bc");
case InstructionKind::VDIV:
return handle_vdiv(i0, instr_str);
case InstructionKind::POR:
return handle_por(i0, instr_str);
case InstructionKind::VMULQ:
return handle_generic_op2_mask(i0, instr_str, "vmulq");
case InstructionKind::VNOP:
return {"// nop", instr_str};
case InstructionKind::MFC1:
return handle_generic_op2(i0, instr_str, "mfc1");
default:
unknown_count++;
return handle_unknown(instr_str);
break;
}
}
void run_mips2c(Function* f) {
g_unknown = 0;
auto* file = f->ir2.env.file;
auto blocks = setup_preds_and_succs(*f, *file);
Mips2C_Output output;
int unknown_count = 0;
for (size_t block_idx = 0; block_idx < blocks.size(); block_idx++) {
const auto& block = blocks[block_idx];
// fmt::print("block {}: {} to {}\n", block_idx, block.start_instr, block.end_instr);
if (block_requires_label(f, blocks, block_idx)) {
output.output_label(block_idx);
} else {
// output.comment_line("block {}",)
}
for (int i = block.start_instr; i < block.end_instr; i++) {
size_t old_line_count = output.lines.size();
auto& instr = f->instructions.at(i);
auto instr_str = instr.to_string(file->labels);
if (is_branch(instr, {})) {
if (block.branch_likely) {
output.lines.push_back(handle_unknown(instr_str));
} else {
if (is_always_branch(instr)) {
// skip the branch ins.
output.lines.emplace_back("//" + instr_str, instr_str);
// then the delay slot
assert(i + 1 < block.end_instr);
i++;
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));
assert(i + 1 == block.end_instr);
// then the goto
output.lines.emplace_back(fmt::format("goto block_{};", block.succ_branch),
"branch always\n");
} else {
// set the branch condition
output.lines.push_back(handle_non_likely_branch_bc(instr, instr_str));
// then the delay slot
assert(i + 1 < block.end_instr);
i++;
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));
assert(i + 1 == block.end_instr);
// then the goto
output.lines.emplace_back(fmt::format("if (bc) {{goto block_{};}}", block.succ_branch),
"branch non-likely\n");
}
}
} else if (is_jr_ra(instr)) {
// skip
output.lines.emplace_back("//" + instr_str, instr_str);
// then the delay slot
assert(i + 1 < block.end_instr);
i++;
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));
assert(i + 1 == block.end_instr);
// then the goto
output.lines.emplace_back(fmt::format("goto end_of_function;", block.succ_branch),
"return\n");
} else {
output.lines.push_back(handle_normal_instr(output, instr, instr_str, unknown_count));
}
// fmt::print("I: {}\n", instr_str);
assert(output.lines.size() > old_line_count);
}
}
f->mips2c_output = output.write_to_string(f->guessed_name);
if (g_unknown > 0) {
lg::error("Mips to C pass in {} hit {} unknown instructions", f->name(), g_unknown);
}
}
} // namespace decompiler
+7
View File
@@ -0,0 +1,7 @@
#pragma once
namespace decompiler {
class Function;
void run_mips2c(Function* f);
} // namespace decompiler
+2 -1
View File
@@ -1,7 +1,8 @@
#pragma once
#include <vector>
#include <unordered_set>
#include <vector>
#include "decompiler/Disasm/Register.h"
namespace decompiler {
+3 -2
View File
@@ -1,7 +1,8 @@
#include <stdexcept>
#include "third-party/fmt/core.h"
#include "stack_spill.h"
#include "decompiler/Disasm/DecompilerLabel.h"
#include "stack_spill.h"
#include "third-party/fmt/core.h"
namespace decompiler {
+3 -2
View File
@@ -1,10 +1,11 @@
#pragma once
#include <string>
#include <unordered_map>
#include <vector>
#include <string>
#include "decompiler/Disasm/Instruction.h"
#include "common/util/Range.h"
#include "decompiler/Disasm/Instruction.h"
#include "decompiler/util/StackSpillMap.h"
namespace decompiler {
+8 -3
View File
@@ -1,7 +1,7 @@
#include "common/link_types.h"
#include "symbol_def_map.h"
#include "third-party/json.hpp"
#include "common/link_types.h"
#include "decompiler/ObjectFile/ObjectFileDB.h"
#include "third-party/json.hpp"
namespace decompiler {
@@ -92,7 +92,12 @@ std::optional<std::string> get_loaded_or_stored_symbol_name(const AtomicOp* op)
void SymbolMapBuilder::add_load_store_from_function(const Function& f, ObjectSymbolList* output) {
if (!f.ir2.atomic_ops_succeeded) {
lg::error("Atomic ops failed in {}", f.guessed_name.to_string());
if (!f.suspected_asm) {
// some asm functions will use mips2c which doesn't require atomic ops.
// we can't do anything with these, but we shouldn't warn.
lg::error("Atomic ops failed in {}", f.name());
}
return;
}
+1 -1
View File
@@ -1,8 +1,8 @@
#pragma once
#include <string>
#include <vector>
#include <unordered_set>
#include <vector>
namespace decompiler {
+1 -2
View File
@@ -129,8 +129,7 @@ bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, F
try {
op_types.at(op_id) = op->propagate_types(*init_types, func.ir2.env, dts);
} catch (std::runtime_error& e) {
lg::warn("Function {} failed type prop at op {}: {}", func.guessed_name.to_string(),
op_id, e.what());
lg::warn("Function {} failed type prop at op {}: {}", func.name(), op_id, e.what());
func.warnings.type_prop_warning("Failed type prop at op {} ({}): {}", op_id,
op->to_string(func.ir2.env), e.what());
func.ir2.env.set_types(block_init_types, op_types, *func.ir2.atomic_ops, my_type);
+4 -3
View File
@@ -1,12 +1,13 @@
#pragma once
#include <vector>
#include <unordered_map>
#include <string>
#include <unordered_map>
#include <vector>
#include "common/type_system/TypeSpec.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
#include "decompiler/config.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "decompiler/ObjectFile/LinkedObjectFile.h"
namespace decompiler {
bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, Function& func);
+4 -3
View File
@@ -1,10 +1,11 @@
#include <set>
#include "variable_naming.h"
#include "reg_usage.h"
#include "decompiler/Function/Function.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "decompiler/IR2/Env.h"
#include "decompiler/util/DecompilerTypeSystem.h"
#include "reg_usage.h"
#include "third-party/fmt/core.h"
#include "variable_naming.h"
namespace decompiler {
+4 -3
View File
@@ -15,14 +15,15 @@
#pragma once
#include <string>
#include <vector>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#include "common/util/assert.h"
#include "decompiler/Disasm/Register.h"
#include "decompiler/util/TP_Type.h"
#include "decompiler/IR2/IR2_common.h"
#include "decompiler/util/TP_Type.h"
namespace decompiler {
+2
View File
@@ -176,6 +176,8 @@ Config read_config_file(const std::string& path_to_config_file) {
config.hacks.format_ops_with_dynamic_string_by_func_name =
hacks_json.at("dynamic_format_arg_counts")
.get<std::unordered_map<std::string, std::vector<std::vector<int>>>>();
config.hacks.mips2c_functions_by_name =
hacks_json.at("mips2c_functions_by_name").get<std::unordered_set<std::string>>();
for (auto& entry : hacks_json.at("cond_with_else_max_lengths")) {
auto func_name = entry.at(0).get<std::string>();
+1
View File
@@ -75,6 +75,7 @@ struct DecompileHacks {
std::unordered_map<std::string, std::unordered_set<int>> blocks_ending_in_asm_branch_by_func_name;
std::unordered_map<std::string, std::vector<std::vector<int>>>
format_ops_with_dynamic_string_by_func_name;
std::unordered_set<std::string> mips2c_functions_by_name;
};
struct Config {
@@ -539,5 +539,11 @@
[94, 1]
],
"":[]
}
},
"mips2c_functions_by_name":[
"draw-string"
]
}
+3
View File
@@ -58,6 +58,9 @@ set(RUNTIME_SOURCE
kernel/kscheme.cpp
kernel/ksocket.cpp
kernel/ksound.cpp
mips2c/mips2c_table.cpp
mips2c/functions/draw_string.cpp
mips2c/functions/test_func.cpp
overlord/dma.cpp
overlord/fake_iso.cpp
overlord/iso.cpp
-5
View File
@@ -5,9 +5,6 @@
* Representation of a GOAL pointer which can be converted to/from a C pointer.
*/
#ifndef JAK_PTR_H
#define JAK_PTR_H
#include "common/util/assert.h"
#include "game/runtime.h"
#include "common/common_types.h"
@@ -88,5 +85,3 @@ Ptr<u8> make_u8_ptr(T* x) {
}
return Ptr<u8>((u8*)x - g_ee_main_mem);
}
#endif // JAK_PTR_H
+141
View File
@@ -102,6 +102,147 @@ _stack_call_linux:
; return!
ret
;; Call c++ code through mips2c.
;; GOAL will call a dynamically generated trampoline.
;; The trampoline will have pushed the exec function and stack offset onto the stack
global _mips2c_call_linux
_mips2c_call_linux:
;; grab the address to call and put it in xmm0
sub rsp, 8
movaps xmm0, [rsp + 16]
;; grab the stack offset
mov rax, [rsp + 8]
;; first, save xmms
sub rsp, 128
movaps [rsp], xmm8
movaps [rsp + 16], xmm9
movaps [rsp + 32], xmm10
movaps [rsp + 48], xmm11
movaps [rsp + 64], xmm12
movaps [rsp + 80], xmm13
movaps [rsp + 96], xmm14
movaps [rsp + 112], xmm15
push r10
push r11
;; oof
sub rsp, 1280
mov [rsp + 64], rdi ;; arg0
mov [rsp + 80], rsi ;; arg1
mov [rsp + 96], rdx ;; arg2
mov [rsp + 112], rcx ;; arg3
mov [rsp + 128], r8 ;; arg4
mov [rsp + 144], r9 ;; arg5
mov [rsp + 160], r10 ;; arg6
mov [rsp + 176], r11 ;; arg7
mov [rsp + 464], rsp ;; mip2c code's MIPS stack
mov rdi, rsp
sub rsp, rax ;; allocate space on the stack for GOAL fake stack
push rax ;; and remember this so we can find our way back
push rax
movq rax, xmm0
call rax ;; call!
;; unallocate
pop rax
pop rax
add rsp, rax
mov rax, [rsp + 32]
add rsp, 1280
pop r11
pop r10
movaps xmm8, [rsp]
movaps xmm9, [rsp + 16]
movaps xmm10, [rsp + 32]
movaps xmm11, [rsp + 48]
movaps xmm12, [rsp + 64]
movaps xmm13, [rsp + 80]
movaps xmm14, [rsp + 96]
movaps xmm15, [rsp + 112]
add rsp, 152 ;; 128 for xmm's + 16 for the stuff pushed by trampoline + 8 for stack alignment undo
ret
global _mips2c_call_windows
_mips2c_call_windows:
;; grab the address to call and put it in xmm0
sub rsp, 8
movaps xmm0, [rsp + 16]
;; grab the stack offset
mov rax, [rsp + 8]
;; first, save xmms
sub rsp, 128
movaps [rsp], xmm8
movaps [rsp + 16], xmm9
movaps [rsp + 32], xmm10
movaps [rsp + 48], xmm11
movaps [rsp + 64], xmm12
movaps [rsp + 80], xmm13
movaps [rsp + 96], xmm14
movaps [rsp + 112], xmm15
push r10
push r11
;; oof
sub rsp, 1280
mov [rsp + 64], rdi ;; arg0
mov [rsp + 80], rsi ;; arg1
mov [rsp + 96], rdx ;; arg2
mov [rsp + 112], rcx ;; arg3
mov [rsp + 128], r8 ;; arg4
mov [rsp + 144], r9 ;; arg5
mov [rsp + 160], r10 ;; arg6
mov [rsp + 176], r11 ;; arg7
mov [rsp + 464], rsp ;; mip2c code's MIPS stack
mov rcx, rsp
sub rsp, rax ;; allocate space on the stack for GOAL fake stack
push rax ;; and remember this so we can find our way back
push rax
movq rax, xmm0
sub rsp, 32
call rax ;; call!
add rsp, 32
;; unallocate
pop rax
pop rax
add rsp, rax
mov rax, [rsp + 32]
add rsp, 1280
pop r11
pop r10
movaps xmm8, [rsp]
movaps xmm9, [rsp + 16]
movaps xmm10, [rsp + 32]
movaps xmm11, [rsp + 48]
movaps xmm12, [rsp + 64]
movaps xmm13, [rsp + 80]
movaps xmm14, [rsp + 96]
movaps xmm15, [rsp + 112]
add rsp, 152 ;; 128 for xmm's + 16 for the stuff pushed by trampoline + 8 for stack alignment undo
ret
;; Call C++ code on windows, from GOAL. Pug arguments on the stack and put a pointer to this array in the first arg.
global _stack_call_win32
_stack_call_win32:
+6
View File
@@ -17,6 +17,7 @@
#include "kprint.h"
#include "common/symbols.h"
#include "common/goal_constants.h"
#include "game/mips2c/mips2c_table.h"
namespace {
// turn on printf's for debugging linking issues.
@@ -785,6 +786,11 @@ void link_control::finish() {
if (ofh->object_file_version == 3) {
// todo check function type of entry
// setup mips2c functions
for (auto& x : Mips2C::gMips2CLinkCallbacks[m_object_name]) {
x();
}
// execute top level!
if (m_entry.offset && (m_flags & LINK_FLAG_EXECUTE)) {
call_goal(m_entry.cast<Function>(), 0, 0, 0, s7.offset, g_ee_main_mem);
+8 -1
View File
@@ -34,7 +34,7 @@
#include "game/graphics/gfx.h"
#include "game/graphics/dma/dma_chain_read.h"
#include "game/graphics/dma/dma_copy.h"
#include "game/mips2c/mips2c_table.h"
#include "game/system/vm/vm.h"
#include "game/system/newpad.h"
using namespace ee;
@@ -555,6 +555,12 @@ void pc_texture_relocate(u32 dst, u32 src, u32 format) {
Gfx::texture_relocate(dst, src, format);
}
u64 pc_get_mips2c(u32 name) {
const char* n = Ptr<String>(name).c()->data();
fmt::print("Getting mips: {}\n", n);
return Mips2C::gLinkedFunctionTable.get(n);
}
/*!
* Open a file-stream. Name is a GOAL string. Mode is a GOAL symbol. Use 'read for readonly
* and anything else for write only.
@@ -718,6 +724,7 @@ void InitMachine_PCPort() {
make_function_symbol_from_c("__send-gfx-dma-chain", (void*)send_gfx_dma_chain);
make_function_symbol_from_c("__pc-texture-upload-now", (void*)pc_texture_upload_now);
make_function_symbol_from_c("__pc-texture-relocate", (void*)pc_texture_relocate);
make_function_symbol_from_c("__pc-get-mips2c", (void*)pc_get_mips2c);
// pad stuff
make_function_symbol_from_c("pc-pad-get-mapped-button", (void*)Gfx::get_mapped_button);
+1
View File
@@ -78,6 +78,7 @@ u32 crc32(const u8* data, s32 size);
void kscheme_init_globals();
void init_crc();
u64 alloc_from_heap(u32 heapSymbol, u32 type, s32 size, u32 pp);
u64 alloc_heap_object(u32 heap, u32 type, u32 size, u32 pp);
Ptr<Symbol> intern_from_c(const char* name);
Ptr<Type> intern_type_from_c(const char* name, u64 methods);
Ptr<Type> set_type_values(Ptr<Type> type, Ptr<Type> parent, u64 flags);
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
//--------------------------MIPS2C---------------------
#include "game/mips2c/mips2c_private.h"
#include "game/kernel/kscheme.h"
namespace Mips2C {
namespace test_func {
u64 execute(void* ctxt) {
auto* c = (ExecutionContext*)ctxt;
u64 result = 0;
for (int i = a0; i < t4; i++) {
result += c->sgpr64(i);
}
c->gprs[v0].du64[0] = result;
return 0;
}
} // namespace test_func
} // namespace Mips2C
+463
View File
@@ -0,0 +1,463 @@
#pragma once
#include <cstring>
#include "common/common_types.h"
#include "game/mips2c/mips2c_table.h"
// This file contains utility functions for code generated by the mips2c pass.
// This is only useful for
extern u8* g_ee_main_mem;
namespace Mips2C {
// nicknames for GPRs
enum Gpr {
r0 = 0, // hardcoded to zero
at = 1, // temp, not used by GOAL compiler, but used by GOAL's kernel inline assembly (an other
// places?)
v0 = 2, // return, temp
v1 = 3, // temp
a0 = 4, // arg0, temp
a1 = 5, // arg1, temp
a2 = 6, // arg2, temp
a3 = 7, // arg3, temp
t0 = 8, // arg4, temp
t1 = 9, // arg5, temp
t2 = 10, // arg6, temp
t3 = 11, // arg7, temp
t4 = 12, // temp
t5 = 13, // temp
t6 = 14, // temp
t7 = 15, // temp
s0 = 16, // saved
s1 = 17, // saved
s2 = 18, // saved
s3 = 19, // saved
s4 = 20, // saved
s5 = 21, // saved
s6 = 22, // process pointer
s7 = 23, // symbol table
t8 = 24, // temp
t9 = 25, // function pointer
k0 = 26, // reserved
k1 = 27, // reserved
gp = 28, // saved (C code uses this a global pointer)
sp = 29, // stack pointer
fp = 30, // global pointer (address of current function)
ra = 31, // return address
MAX_GPR = 32
};
enum VfName {
vf0 = 0,
vf1 = 1,
vf2 = 2,
vf3 = 3,
vf4 = 4,
vf5 = 5,
vf6 = 6,
vf7 = 7,
vf8 = 8,
vf9 = 9,
vf10 = 10,
vf11 = 11,
vf12 = 12,
vf13 = 13,
vf14 = 14,
vf15 = 15,
vf16 = 16,
vf17 = 17,
vf18 = 18,
vf19 = 19,
vf20 = 20,
vf21 = 21,
vf22 = 22,
vf23 = 23,
vf24 = 24,
vf25 = 25,
vf26 = 26,
vf27 = 27,
vf28 = 28,
vf29 = 29,
vf30 = 30,
vf31 = 31,
};
enum FprName {
f0 = 0,
f1 = 1,
f2 = 2,
f3 = 3,
f4 = 4,
f5 = 5,
f6 = 6,
f7 = 7,
f8 = 8,
f9 = 9,
f10 = 10,
f11 = 11,
f12 = 12,
f13 = 13,
f14 = 14,
f15 = 15,
f16 = 16,
f17 = 17,
f18 = 18,
f19 = 19,
f20 = 20,
f21 = 21,
f22 = 22,
f23 = 23,
f24 = 24,
f25 = 25,
f26 = 26,
f27 = 27,
f28 = 28,
f29 = 29,
f30 = 30,
f31 = 31,
};
// note: these are not the same as the ps2 encoding - in these the least significant bit is x.
enum class DEST {
NONE = 0,
x = 1,
y = 2,
xy = 3,
z = 4,
xz = 5,
yz = 6,
xyz = 7,
w = 8,
xw = 9,
yw = 10,
xyw = 11,
zw = 12,
xzw = 13,
yzw = 14,
xyzw = 15
};
enum class BC { x = 0, y = 1, z = 2, w = 3 };
struct ExecutionContext {
// EE general purpose registers
u128 gprs[32];
// EE fprs
float fprs[16];
// VU0 vf registers
u128 vfs[32];
u128 acc;
float Q;
u128 vf_src(int idx) {
if (idx == 0) {
u128 result;
result.f[0] = 0;
result.f[1] = 0;
result.f[2] = 0;
result.f[3] = 1.f;
return result;
} else {
return vfs[idx];
}
}
u128 gpr_src(int idx) {
if (idx == 0) {
u128 result;
result.du64[0] = 0;
result.du64[1] = 0;
return result;
} else {
return gprs[idx];
}
}
u64 sgpr64(int idx) { return gpr_src(idx).du64[0]; }
u32 gpr_addr(int idx) { return gpr_src(idx).du32[0]; }
void load_symbol(int gpr, void* sym_addr) {
s32 val;
memcpy(&val, sym_addr, 4);
gprs[gpr].ds64[0] = val; // sign extend and set
}
void lbu(int dst, int offset, int src) {
u8 val;
memcpy(&val, g_ee_main_mem + gpr_src(src).du32[0] + offset, 1);
gprs[dst].du64[0] = val;
}
void lqc2(int vf, int offset, int gpr) {
memcpy(&vfs[vf], g_ee_main_mem + gpr_src(gpr).du32[0] + offset, 16);
}
void lw(int dst, int offset, int src) {
s32 val;
memcpy(&val, g_ee_main_mem + gpr_src(src).du32[0] + offset, 4);
gprs[dst].ds64[0] = val;
}
void lwu(int dst, int offset, int src) {
u32 val;
memcpy(&val, g_ee_main_mem + gpr_src(src).du32[0] + offset, 4);
gprs[dst].du64[0] = val;
}
void lq(int dst, int offset, int src) {
memcpy(&gprs[dst].du64[0], g_ee_main_mem + gpr_addr(src) + offset, 16);
}
void sw(int src, int offset, int addr) {
auto s = gpr_src(src);
memcpy(g_ee_main_mem + gpr_addr(addr) + offset, &s.du32[0], 4);
}
void sq(int src, int offset, int addr) {
auto s = gpr_src(src);
memcpy(g_ee_main_mem + gpr_addr(addr) + offset, &s.du32[0], 16);
}
void sqc2(int src, int offset, int addr) {
auto s = vf_src(src);
memcpy(g_ee_main_mem + gpr_addr(addr) + offset, &s.du32[0], 16);
}
void vadd_bc(DEST mask, BC bc, int dest, int src0, int src1) {
auto s0 = vf_src(src0);
auto s1 = vf_src(src1);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dest].f[i] = s0.f[i] + s1.f[(int)bc];
}
}
}
void vsub_bc(DEST mask, BC bc, int dest, int src0, int src1) {
auto s0 = vf_src(src0);
auto s1 = vf_src(src1);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dest].f[i] = s0.f[i] - s1.f[(int)bc];
}
}
}
void vmul_bc(DEST mask, BC bc, int dest, int src0, int src1) {
auto s0 = vf_src(src0);
auto s1 = vf_src(src1);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dest].f[i] = s0.f[i] * s1.f[(int)bc];
}
}
}
void vmul(DEST mask, int dest, int src0, int src1) {
auto s0 = vf_src(src0);
auto s1 = vf_src(src1);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dest].f[i] = s0.f[i] * s1.f[i];
}
}
}
void vadd(DEST mask, int dest, int src0, int src1) {
auto s0 = vf_src(src0);
auto s1 = vf_src(src1);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dest].f[i] = s0.f[i] + s1.f[i];
}
}
}
void vsub(DEST mask, int dest, int src0, int src1) {
auto s0 = vf_src(src0);
auto s1 = vf_src(src1);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dest].f[i] = s0.f[i] - s1.f[i];
}
}
}
void vmula_bc(DEST mask, BC bc, int src0, int src1) {
auto s0 = vf_src(src0);
auto s1 = vf_src(src1);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
acc.f[i] = s0.f[i] * s1.f[(int)bc];
}
}
}
void vmadda_bc(DEST mask, BC bc, int src0, int src1) {
auto s0 = vf_src(src0);
auto s1 = vf_src(src1);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
acc.f[i] += s0.f[i] * s1.f[(int)bc];
}
}
}
void vmadd_bc(DEST mask, BC bc, int dst, int src0, int src1) {
auto s0 = vf_src(src0);
auto s1 = vf_src(src1);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dst].f[i] = acc.f[i] + s0.f[i] * s1.f[(int)bc];
}
}
}
void vdiv(int src0, BC bc0, int src1, BC bc1) {
Q = vf_src(src0).f[(int)bc0] / vf_src(src1).f[(int)bc1];
}
void vmulq(DEST mask, int dst, int src) {
auto s0 = vf_src(src);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dst].f[i] = s0.f[i] * Q;
}
}
}
void mov64(int dest, int src) { gprs[dest].ds64[0] = gpr_src(src).du64[0]; }
void vmove(DEST mask, int dest, int src) {
auto s = vf_src(src);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dest].f[i] = s.f[i];
}
}
}
void sll(int dst, int src, int sa) {
u32 value = gpr_src(src).du32[0] << sa;
s32 value_signed = value;
gprs[dst].ds64[0] = value_signed;
}
void sra(int dst, int src, int sa) { gprs[dst].ds64[0] = gpr_src(src).ds32[0] >> sa; }
void daddu(int dst, int src0, int src1) { gprs[dst].du64[0] = sgpr64(src0) + sgpr64(src1); }
void daddiu(int dst, int src0, s64 imm) { gprs[dst].du64[0] = sgpr64(src0) + imm; }
void addiu(int dst, int src0, s64 imm) {
s32 temp = sgpr64(src0) + imm;
gprs[dst].ds64[0] = temp;
}
void addu(int dst, int src0, int src1) {
s32 temp = sgpr64(src0) + sgpr64(src1);
gprs[dst].ds64[0] = temp;
}
void movz(int dst, int src0, int src1) {
if (sgpr64(src1) == 0) {
gprs[dst].du64[0] = sgpr64(src0);
}
}
void movn(int dst, int src0, int src1) {
if (sgpr64(src1) != 0) {
gprs[dst].du64[0] = sgpr64(src0);
}
}
void andi(int dest, int src, u64 imm) { gprs[dest].du64[0] = gpr_src(src).du64[0] & imm; }
void ori(int dest, int src, u64 imm) { gprs[dest].du64[0] = gpr_src(src).du64[0] | imm; }
void and_(int dest, int src0, int src1) {
gprs[dest].du64[0] = gpr_src(src0).du64[0] & gpr_src(src1).du64[0];
}
void pextlb(int dst, int src0, int src1) {
auto s0 = gpr_src(src0);
auto s1 = gpr_src(src1);
gprs[dst].du8[0] = s1.du8[0];
gprs[dst].du8[1] = s0.du8[0];
gprs[dst].du8[2] = s1.du8[1];
gprs[dst].du8[3] = s0.du8[1];
gprs[dst].du8[4] = s1.du8[2];
gprs[dst].du8[5] = s0.du8[2];
gprs[dst].du8[6] = s1.du8[3];
gprs[dst].du8[7] = s0.du8[3];
gprs[dst].du8[8] = s1.du8[4];
gprs[dst].du8[9] = s0.du8[4];
gprs[dst].du8[10] = s1.du8[5];
gprs[dst].du8[11] = s0.du8[5];
gprs[dst].du8[12] = s1.du8[6];
gprs[dst].du8[13] = s0.du8[6];
gprs[dst].du8[14] = s1.du8[7];
gprs[dst].du8[15] = s0.du8[7];
}
void pextlh(int dst, int src0, int src1) {
auto s0 = gpr_src(src0);
auto s1 = gpr_src(src1);
gprs[dst].du16[0] = s1.du16[0];
gprs[dst].du16[1] = s0.du16[0];
gprs[dst].du16[2] = s1.du16[1];
gprs[dst].du16[3] = s0.du16[1];
gprs[dst].du16[4] = s1.du16[2];
gprs[dst].du16[5] = s0.du16[2];
gprs[dst].du16[6] = s1.du16[3];
gprs[dst].du16[7] = s0.du16[3];
}
void por(int dst, int src0, int src1) {
auto s0 = gpr_src(src0);
auto s1 = gpr_src(src1);
gprs[dst].du64[0] = s0.du64[0] | s1.du64[0];
gprs[dst].du64[1] = s0.du64[1] | s1.du64[1];
}
void mov128_vf_gpr(int dst, int src) { vfs[dst] = gpr_src(src); }
void mov128_gpr_vf(int dst, int src) { gprs[dst] = vf_src(src); }
void mov128_gpr_gpr(int dst, int src) { gprs[dst] = gpr_src(src); }
void vitof0(DEST mask, int dst, int src) {
auto s = vf_src(src);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dst].f[i] = s.ds32[i];
}
}
}
void vftoi4(DEST mask, int dst, int src) {
auto s = vf_src(src);
for (int i = 0; i < 4; i++) {
if ((u64)mask & (1 << i)) {
vfs[dst].ds32[i] = s.f[i] * 16.f;
}
}
}
void mfc1(int dst, int src) {
s32 val;
memcpy(&val, &fprs[src], 4);
gprs[dst].ds64[0] = val;
}
};
} // namespace Mips2C
+91
View File
@@ -0,0 +1,91 @@
#include "mips2c_table.h"
#include "common/log/log.h"
#include "game/kernel/kmalloc.h"
#include "game/kernel/kscheme.h"
#include "common/symbols.h"
extern "C" {
void _mips2c_call_linux();
void _mips2c_call_windows();
}
namespace Mips2C {
namespace draw_string {
extern void link();
}
LinkedFunctionTable gLinkedFunctionTable;
std::unordered_map<std::string, std::vector<void (*)()>> gMips2CLinkCallbacks = {
{"font", {draw_string::link}}};
void LinkedFunctionTable::reg(const std::string& name, u64 (*exec)(void*), u32 stack_size) {
const auto& it = m_executes.insert({name, {exec, Ptr<u8>()}});
if (!it.second) {
lg::error("MIPS2C Function {} is registered multiple times, ignoring later registrations.",
name);
return;
}
// this is short stub that will jump to the appropriate function.
auto jump_to_asm = Ptr<u8>(alloc_heap_object(s7.offset + FIX_SYM_GLOBAL_HEAP,
*(s7 + FIX_SYM_FUNCTION_TYPE), 0x40, UNKNOWN_PP));
it.first->second.goal_trampoline = jump_to_asm;
u8* ptr = jump_to_asm.c();
{
// linux
// push the function
u64 addr = (u64)exec;
*ptr = 0x48;
ptr++;
*ptr = 0xb8;
ptr++;
memcpy(ptr, &addr, 8);
ptr += 8;
*ptr = 0x50;
ptr++;
// push the stack size
addr = stack_size;
*ptr = 0x48;
ptr++;
*ptr = 0xb8;
ptr++;
memcpy(ptr, &addr, 8);
ptr += 8;
*ptr = 0x50;
ptr++;
// call the other function
#ifdef __linux__
addr = (u64)_mips2c_call_linux;
#elif _WIN32
addr = (u64)_mips2c_call_windows;
#endif
*ptr = 0x48;
ptr++;
*ptr = 0xb8;
ptr++;
memcpy(ptr, &addr, 8);
ptr += 8;
// jumps to the mips2c call, which will return to the caller of this stub.
*ptr = 0xff;
ptr++;
*ptr = 0xe0;
}
}
u32 LinkedFunctionTable::get(const std::string& name) {
auto it = m_executes.find(name);
if (it == m_executes.end()) {
assert(false);
}
return it->second.goal_trampoline.offset;
}
} // namespace Mips2C
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include <unordered_map>
#include <vector>
#include <string>
#include "game/kernel/Ptr.h"
#include "common/common_types.h"
#include "common/util/assert.h"
namespace Mips2C {
class LinkedFunctionTable {
public:
void reg(const std::string& name, u64 (*exec)(void*), u32 goal_stack_size);
u32 get(const std::string& name);
private:
struct Func {
u64 (*c_func)(void*);
Ptr<u8> goal_trampoline;
};
std::unordered_map<std::string, Func> m_executes;
};
extern std::unordered_map<std::string, std::vector<void (*)()>> gMips2CLinkCallbacks;
extern LinkedFunctionTable gLinkedFunctionTable;
} // namespace Mips2C
+89
View File
@@ -0,0 +1,89 @@
## Using Mips2C
The Mips2C convert very literally translates MIPS assembly to C. Each op is converted to a function call:
```cpp
c->load_symbol(v1, cache.math_camera); // lw v1, *math-camera*(s7)
c->lqc2(vf26, 732, v1); // lqc2 vf26, 732(v1)
c->lqc2(vf27, 732, v1); // lqc2 vf27, 732(v1)
c->vadd_bc(DEST::xy, BC::w, vf26, vf26, vf0); // vaddw.xy vf26, vf26, vf0
c->vadd_bc(DEST::x, BC::w, vf26, vf26, vf0); // vaddw.x vf26, vf26, vf0
c->lw(v1, 72, a2); // lw v1, 72(a2)
```
This is roughly the same thing that the PCSX2 recompiler would do. However, if compiler optimizations are turned on, Mips2C code can be very efficient, as the compiler is often smart enough to avoid loading/storing consecutive uses of a MIPS register. In `draw-string`, clang with `-O3` is about 2x faster than OpenGOAL.
It also handles branches and delay slots by using `goto`:
```cpp
bc = c->sgpr64(a3) != 0; // bne a3, r0, L22
c->load_symbol(a3, cache.font12_table); // lw a3, *font12-table*(s7)
if (bc) {
goto block_2;
} // branch non-likely
```
Currently supported features are:
- All of the instructions used in `draw-string`
- Some of the vector float ops, including use of accumulator and Q
- Some of the 128-bit integer ops
- Use of the stack, but you must know the maximum stack size used by the function (not its children)
- Use of symbols
- Returning a value
The Mips2C converter is intended for complicated assembly functions. Compared to the decompiler, it is much more likely to "just work". It is currently the best option for functions which:
- Have huge amounts assembly branching, making register to variable a huge mess
- Rely on strange details of 128-bit GPR behavior that is not easy to express in OpenGOAL
- Are not understood
- Fails CFG, or other decompiler passes
To use it, add the function's name to the `mips2c` list in `hacks.jsonc`.
Not all instructions are implemented yet, but it is generally very easy to add them. It should be possible to use the stack, but this is untested. You must provide the stack size manually.
There are some limitations:
- Calling GOAL functions is not yet implemented. It is possible but tricky.
- There is no support for static data yet.
- Likely branches are not yet implemented, but should be easy.
- The output is very hard to understand
- 128-bit arguments and return values are not supported yet
## Mips2C code linking
At link-time, the mips2c code will cache symbol lookups. It will create a `Cache` structure that contains all the symbols used by the function instead of actually patching the code. To set this up, you must call the `link()` function that is autogenerated. There is a system to make this easy: you register a callback that the linker will call when linking the appropriate file.
First, at the bottom of the mips2 output there will be something like:
```cpp
// FWD DEC:
namespace draw_string { extern void link(); }
```
this must be copy-pasted into the top of `mips2c_table.cpp` (and can be removed from the `.cpp` file if desired)
Second, add a new entry to the `gMips2CLinkCallbacks` table in that same file:
```cpp
{"font", {draw_string::link}}
```
the first thing in the list is the name of the source file that should contain the function (without `.gc`), and the second thing should have the same name as the `namespace` added before.
When the linker links the `font` object file, it will call the `link` function defined there. This will add the function to the table of available mips2c functions. Note: this does **not** define the function.
Note: you will need to add a third argument to `gLinkedFunctionTable.reg(` in the auto-generated code with the maximum amount of stack space that the function can use (not including functions it calls, just local use).
## Accessing the m2c from GOAL
Replace the `defun` with:
```
(define my-func (the (function <whatever>) (__pc-get-mips2c "draw-string")))
```
You can use the same idea for methods with `method-set!`. The method name will be the decompiler name.
## Running Mips2C code
When Mips2C code is linked (for the first time), a small dynamically generated function object is created. This is a very short stub that jumps to a common implementation in `mips2c_call_linux`, that actually sets up the call.
The setup code saves the appropriate registers for the OS, allocates an `ExecutionContext` on the stack, initializes the argument registers, allocates a "fake stack array" on the stack with the requested size, and calls the C++ function.
The arguments can then be accessed through the register array. The `sp` register is set to point to the "fake stack" on the stack. In this way, it is possible to call other GOAL functions or `suspend` and all the stack stuff will just work.
Unfortunately, throwing all the registers on the stack takes a huge amount of stack space. It will be somewhere between 1 and 2 kB. For one or two functions this is probably fine, but `suspend`s inside the mips2c will probably fail, for example.
With some clever tricks it might be possible to do better, but it doesn't seem worth it at this time.
On exit, the assembly function will grab the return value from `v0` and put it in `rax`.
-5
View File
@@ -5,9 +5,6 @@
* Setup and launcher for the runtime.
*/
#ifndef JAK1_RUNTIME_H
#define JAK1_RUNTIME_H
#include "common/common_types.h"
#include <thread>
@@ -15,5 +12,3 @@ extern u8* g_ee_main_mem;
u32 exec_runtime(int argc, char** argv);
extern std::thread::id g_main_thread_id;
#endif // JAK1_RUNTIME_H
+5
View File
@@ -612,7 +612,11 @@
)
)
;; we have both GOAL and mips2c versions of this function
(define draw-string (the (function string dma-buffer font-context float) (__pc-get-mips2c "draw-string")))
;; for now, use the GOAL one. the mips2c one is super slow with the game compiled in debug mode.
(defun draw-string ((str-in string) (context dma-buffer) (arg2 font-context))
"Draw a string. Writes dma to the given buffer."
(local-vars
@@ -1614,6 +1618,7 @@
)
(defun draw-string-adv ((str string) (buf dma-buffer) (ctxt font-context))
"Draw a string and advance the position of the context."
(+! (-> ctxt origin x) (draw-string str buf ctxt))
+8
View File
@@ -574,6 +574,14 @@
)
)
(defmacro print-vf-dec (vf)
"Print out a vf register as 4x 32-bit base-10 integers"
`(let ((temp (new 'stack 'vector4w)))
(.svf temp ,vf)
(format #t " ~d ~d ~d ~d~%" (-> temp data 0) (-> temp data 1) (-> temp data 2) (-> temp data 3))
)
)
(defun vector4-dot-vu ((a vector) (b vector))
"Take the dot product of two vectors.
Does the x, y, z, and w compoments
+1
View File
@@ -308,6 +308,7 @@
(define-extern __send-gfx-dma-chain (function object object none))
(define-extern __pc-texture-upload-now (function object object none))
(define-extern __pc-texture-relocate (function object object object none))
(define-extern __pc-get-mips2c (function string function))
(define-extern pc-pad-input-mode-set (function symbol none))
(define-extern pc-pad-input-mode-get (function int))
(define-extern pc-pad-input-key-get (function int))
@@ -0,0 +1,5 @@
(let ((f (the (function int int int int int int int int int) (__pc-get-mips2c "test-func"))))
(format #t "~D~%" (f 1 2 3 4 5 6 7 8))
)
+12
View File
@@ -5,6 +5,7 @@
#include "game/runtime.h"
#include "goalc/listener/Listener.h"
#include "goalc/compiler/Compiler.h"
#include "game/mips2c/mips2c_table.h"
#include "inja.hpp"
#include "third-party/json.hpp"
@@ -895,6 +896,17 @@ TEST_F(WithGameTests, MethodCallForwardDeclared) {
{"4 12\n0\n"});
}
namespace Mips2C {
namespace test_func {
extern u64 execute(void*);
}
} // namespace Mips2C
TEST_F(WithGameTests, Mips2C) {
Mips2C::gLinkedFunctionTable.reg("test-func", Mips2C::test_func::execute, 0);
shared_compiler->runner.run_static_test(env, testCategory, "test-mips2c-call.gc", {"36\n0\n"});
}
TEST(TypeConsistency, TypeConsistency) {
Compiler compiler;
compiler.enable_throw_on_redefines();
+11 -12
View File
@@ -235,7 +235,7 @@ int main(int argc, char** argv) {
// Check to see if we've included atleast one of the DGO/CGOs in our hardcoded list
// If not BLOW UP
bool dgoValidated = false;
for (int i = 0; i < dgoList.size(); i++) {
for (int i = 0; i < (int)dgoList.size(); i++) {
std::string& dgo = dgoList.at(i);
// can either be in the DGO or CGO folder, and can either end with .CGO or .DGO
if (std::find(dgos.begin(), dgos.end(), fmt::format("DGO/{}.DGO", dgo)) != dgos.end() ||
@@ -383,10 +383,9 @@ TEST_F(OfflineDecompilation, AsmFunction) {
int failed_count = 0;
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
if (func.suspected_asm) {
if (g_functions_expected_to_reject.find(func.guessed_name.to_string()) ==
if (g_functions_expected_to_reject.find(func.name()) ==
g_functions_expected_to_reject.end()) {
lg::error("Function {} was marked as asm, but wasn't expected.",
func.guessed_name.to_string());
lg::error("Function {} was marked as asm, but wasn't expected.", func.name());
failed_count++;
}
}
@@ -402,7 +401,7 @@ TEST_F(OfflineDecompilation, CfgBuild) {
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
if (!func.suspected_asm) {
if (!func.cfg || !func.cfg->is_fully_resolved()) {
lg::error("Function {} failed cfg", func.guessed_name.to_string());
lg::error("Function {} failed cfg", func.name());
failed_count++;
}
}
@@ -419,7 +418,7 @@ TEST_F(OfflineDecompilation, AtomicOp) {
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
if (!func.suspected_asm) {
if (!func.ir2.atomic_ops || !func.ir2.atomic_ops_succeeded) {
lg::error("Function {} failed atomic ops", func.guessed_name.to_string());
lg::error("Function {} failed atomic ops", func.name());
failed_count++;
}
}
@@ -436,7 +435,7 @@ TEST_F(OfflineDecompilation, TypeAnalysis) {
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
if (!func.suspected_asm) {
if (!func.ir2.env.has_type_analysis() || !func.ir2.env.types_succeeded) {
lg::error("Function {} failed types", func.guessed_name.to_string());
lg::error("Function {} failed types", func.name());
failed_count++;
}
}
@@ -450,7 +449,7 @@ TEST_F(OfflineDecompilation, RegisterUse) {
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
if (!func.suspected_asm) {
if (!func.ir2.env.has_reg_use()) {
lg::error("Function {} failed reg use", func.guessed_name.to_string());
lg::error("Function {} failed reg use", func.name());
failed_count++;
}
}
@@ -464,7 +463,7 @@ TEST_F(OfflineDecompilation, VariableSSA) {
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
if (!func.suspected_asm) {
if (!func.ir2.env.has_local_vars()) {
lg::error("Function {} failed ssa", func.guessed_name.to_string());
lg::error("Function {} failed ssa", func.name());
failed_count++;
}
}
@@ -478,7 +477,7 @@ TEST_F(OfflineDecompilation, Structuring) {
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
if (!func.suspected_asm) {
if (!func.ir2.top_form) {
lg::error("Function {} failed structuring", func.guessed_name.to_string());
lg::error("Function {} failed structuring", func.name());
failed_count++;
}
}
@@ -492,7 +491,7 @@ TEST_F(OfflineDecompilation, Expressions) {
db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) {
if (!func.suspected_asm) {
if (!func.ir2.expressions_succeeded) {
lg::error("Function {} failed expressions", func.guessed_name.to_string());
lg::error("Function {} failed expressions", func.name());
failed_count++;
}
}
@@ -525,7 +524,7 @@ TEST_F(OfflineDecompilation, Reference) {
bool can_cache = true;
for (auto& func_list : obj_l.at(0).linked_data.functions_by_seg) {
for (auto& func : func_list) {
if (g_functions_to_skip_compiling.find(func.guessed_name.to_string()) !=
if (g_functions_to_skip_compiling.find(func.name()) !=
g_functions_to_skip_compiling.end()) {
can_cache = false;
break;
+1 -1
View File
@@ -464,7 +464,7 @@ void inspect_basics(const Ram& ram,
int field_addr = base_addr + field.offset();
if (ram.word_in_memory(field_addr)) {
auto proc_pointer = ram.word(field_addr); // pointer process
auto pid = ram.word(field_addr + 4);
// auto pid = ram.word(field_addr + 4);
if (ram.word_in_memory(proc_pointer)) {
auto proc = ram.word(proc_pointer);
auto proc_type_tag_addr = proc - 4;