[decompiler] handle pointer to symbol value, clean up prints on offline test (#1978)

- fix issue described in
https://github.com/open-goal/jak-project/issues/1939
- fix `text`, which was manually patched with the wrong offset (was
reading the symbol value off by one byte)
- clean up some random useless prints
- make the offline tests keep trying if there's a comparison error,
clean up the output a bit so the diffs are all at the end.
This commit is contained in:
water111
2022-10-16 18:19:59 -04:00
committed by GitHub
parent e7bb0fb68d
commit ddd60fca48
21 changed files with 291 additions and 509 deletions
+13 -10
View File
@@ -30,6 +30,8 @@ std::string InstructionAtom::to_string(const std::vector<DecompilerLabel>& label
return "Q";
case IMM_SYM:
return sym;
case IMM_SYM_VAL_PTR:
return sym;
case VF_FIELD:
ASSERT(imm >= 0 && imm < 4);
return fmt::format(".{}", "xyzw"[imm]);
@@ -79,9 +81,17 @@ void InstructionAtom::set_vu_q() {
/*!
* Make this atom a symbol.
*/
void InstructionAtom::set_sym(std::string _sym) {
void InstructionAtom::set_sym(const std::string& _sym) {
kind = IMM_SYM;
sym = std::move(_sym);
sym = _sym;
}
/*!
* Make this atom a symbol value pointer.
*/
void InstructionAtom::set_sym_val_ptr(const std::string& _sym) {
kind = IMM_SYM_VAL_PTR;
sym = _sym;
}
/*!
@@ -129,17 +139,10 @@ int InstructionAtom::get_label() const {
* Get as symbol, or error if not a symbol.
*/
std::string InstructionAtom::get_sym() const {
ASSERT(kind == IMM_SYM);
ASSERT(kind == IMM_SYM || kind == IMM_SYM_VAL_PTR);
return sym;
}
/*!
* True if this atom is some sort of constant that doesn't involve linking.
*/
bool InstructionAtom::is_link_or_label() const {
return kind == IMM_SYM || kind == LABEL;
}
bool InstructionAtom::operator==(const InstructionAtom& other) const {
if (kind != other.kind) {
return false;
+12 -11
View File
@@ -20,13 +20,14 @@ constexpr int MAX_INTRUCTION_DEST = 1;
// An "atom", representing a single register, immediate, etc... for use in an Instruction.
struct InstructionAtom {
enum AtomKind {
REGISTER, // An EE Register
IMM, // An immediate value (stored as int32)
IMM_SYM, // An immediate value (a symbolic link)
LABEL, // A label in a LinkedObjectFile
VU_ACC, // The VU0 Accumulator
VU_Q, // The VU0 Q Register
VF_FIELD, // Field specifier
REGISTER, // An EE Register
IMM, // An immediate value (stored as int32)
IMM_SYM, // An immediate value (a symbolic link)
IMM_SYM_VAL_PTR, // pointer to value of a symbol
LABEL, // A label in a LinkedObjectFile
VU_ACC, // The VU0 Accumulator
VU_Q, // The VU0 Q Register
VF_FIELD, // Field specifier
INVALID
} kind = INVALID;
@@ -35,7 +36,8 @@ struct InstructionAtom {
void set_label(int id);
void set_vu_q();
void set_vu_acc();
void set_sym(std::string _sym);
void set_sym(const std::string& _sym);
void set_sym_val_ptr(const std::string& _sym);
void set_vf_field(uint32_t value);
Register get_reg() const;
@@ -46,15 +48,14 @@ struct InstructionAtom {
std::string to_string(const std::vector<DecompilerLabel>& labels) const;
bool is_link_or_label() const;
bool is_reg() const { return kind == REGISTER; }
bool is_imm() const { return kind == IMM; }
bool is_label() const { return kind == LABEL; }
bool is_sym() const { return kind == IMM_SYM; }
bool is_sym() const { return kind == IMM_SYM || kind == IMM_SYM_VAL_PTR; }
bool is_reg(Register r) const { return kind == REGISTER && reg == r; }
bool is_imm(int32_t i) const { return kind == IMM && imm == i; }
bool is_sym(const std::string& name) const { return kind == IMM_SYM && name == sym; }
bool is_sym(const std::string& name) const { return is_sym() && name == sym; }
bool operator==(const InstructionAtom& other) const;
bool operator!=(const InstructionAtom& other) const { return !((*this) == other); }
+11
View File
@@ -1193,6 +1193,17 @@ Instruction decode_instruction(LinkedWord& word, LinkedObjectFile& file, int seg
ASSERT(fixed);
}
if (word.kind() == LinkedWord::SYM_VAL_OFFSET) {
bool fixed = false;
for (int j = 0; j < i.n_src; j++) {
if (i.src[j].kind == InstructionAtom::IMM) {
fixed = true;
i.src[j].set_sym_val_ptr(word.symbol_name());
}
}
ASSERT(fixed);
}
if (word.kind() == LinkedWord::HI_PTR) {
ASSERT(i.kind == InstructionKind::LUI);
bool fixed = false;
+4 -7
View File
@@ -504,8 +504,7 @@ void Function::find_method_defs(LinkedObjectFile& file, DecompilerTypeSystem& dt
for (const auto& instr : instructions) {
// look for lw t9, method-set!(s7)
if (instr.kind == InstructionKind::LW && instr.get_dst(0).get_reg() == make_gpr(Reg::T9) &&
instr.get_src(0).kind == InstructionAtom::IMM_SYM &&
instr.get_src(0).get_sym() == "method-set!" &&
instr.get_src(0).is_sym() && instr.get_src(0).get_sym() == "method-set!" &&
instr.get_src(1).get_reg() == make_gpr(Reg::S7)) {
state = 1;
continue;
@@ -514,8 +513,7 @@ void Function::find_method_defs(LinkedObjectFile& file, DecompilerTypeSystem& dt
if (state == 1) {
// look for lw a0, type-name(s7)
if (instr.kind == InstructionKind::LW && instr.get_dst(0).get_reg() == make_gpr(Reg::A0) &&
instr.get_src(0).kind == InstructionAtom::IMM_SYM &&
instr.get_src(1).get_reg() == make_gpr(Reg::S7)) {
instr.get_src(0).is_sym() && instr.get_src(1).get_reg() == make_gpr(Reg::S7)) {
type_name = instr.get_src(0).get_sym();
state = 2;
continue;
@@ -587,7 +585,7 @@ void Function::find_type_defs(LinkedObjectFile& file, DecompilerTypeSystem& dts)
for (const auto& instr : instructions) {
// look for lw xx, type(s7)
if (instr.kind == InstructionKind::LW && instr.get_src(0).kind == InstructionAtom::IMM_SYM &&
if (instr.kind == InstructionKind::LW && instr.get_src(0).is_sym() &&
instr.get_src(0).get_sym() == "type" && instr.get_src(1).get_reg() == make_gpr(Reg::S7)) {
state = 1;
temp_reg = instr.get_dst(0).get_reg();
@@ -621,8 +619,7 @@ void Function::find_type_defs(LinkedObjectFile& file, DecompilerTypeSystem& dts)
if (state == 3) {
// look for lw a1, parent-type(s7)
if (instr.kind == InstructionKind::LW && instr.get_dst(0).get_reg() == make_gpr(Reg::A1) &&
instr.get_src(0).kind == InstructionAtom::IMM_SYM &&
instr.get_src(1).get_reg() == make_gpr(Reg::S7)) {
instr.get_src(0).is_sym() && instr.get_src(1).get_reg() == make_gpr(Reg::S7)) {
state = 4;
parent_type = instr.get_src(0).get_sym();
continue;
+9
View File
@@ -122,6 +122,13 @@ SimpleAtom SimpleAtom::make_sym_val(const std::string& name) {
return result;
}
SimpleAtom SimpleAtom::make_sym_val_ptr(const std::string& name) {
SimpleAtom result;
result.m_kind = Kind::SYMBOL_VAL_PTR;
result.m_string = name;
return result;
}
SimpleAtom SimpleAtom::make_empty_list() {
SimpleAtom result;
result.m_kind = Kind::EMPTY_LIST;
@@ -206,6 +213,8 @@ goos::Object SimpleAtom::to_form(const std::vector<DecompilerLabel>& labels, con
return pretty_print::to_symbol("'()");
case Kind::STATIC_ADDRESS:
return pretty_print::to_symbol(labels.at(m_int).name);
case Kind::SYMBOL_VAL_PTR:
return pretty_print::to_symbol(fmt::format("(&-> '{} value)", m_string));
default:
ASSERT(false);
return {};
+4 -1
View File
@@ -131,6 +131,7 @@ class SimpleAtom {
INTEGER_CONSTANT,
SYMBOL_PTR,
SYMBOL_VAL,
SYMBOL_VAL_PTR,
EMPTY_LIST,
STATIC_ADDRESS,
INVALID
@@ -140,6 +141,7 @@ class SimpleAtom {
static SimpleAtom make_var(const RegisterAccess& var);
static SimpleAtom make_sym_ptr(const std::string& name);
static SimpleAtom make_sym_val(const std::string& name);
static SimpleAtom make_sym_val_ptr(const std::string& name);
static SimpleAtom make_empty_list();
static SimpleAtom make_int_constant(s64 value);
static SimpleAtom make_static_address(int static_label_id);
@@ -174,6 +176,7 @@ class SimpleAtom {
bool is_sym_val(const std::string& str) const {
return m_kind == Kind::SYMBOL_VAL && m_string == str;
}
bool is_sym_val_ptr() const { return m_kind == Kind::SYMBOL_VAL_PTR; };
bool is_empty_list() const { return m_kind == Kind::EMPTY_LIST; };
bool is_static_addr() const { return m_kind == Kind::STATIC_ADDRESS; };
Kind get_kind() const { return m_kind; }
@@ -184,7 +187,7 @@ class SimpleAtom {
SimpleExpression as_expr() const;
TP_Type get_type(const TypeState& input, const Env& env, const DecompilerTypeSystem& dts) const;
const std::string& get_str() const {
ASSERT(is_sym_ptr() || is_sym_val());
ASSERT(is_sym_ptr() || is_sym_val() || is_sym_val_ptr());
return m_string;
}
void mark_as_float();
+2 -1
View File
@@ -753,7 +753,8 @@ void SimpleExpressionElement::update_from_stack_identity(const Env& env,
}
}
} else if (arg.is_sym_ptr() || arg.is_sym_val() || arg.is_int() || arg.is_empty_list()) {
} else if (arg.is_sym_ptr() || arg.is_sym_val() || arg.is_int() || arg.is_empty_list() ||
arg.is_sym_val_ptr()) {
result->push_back(this);
return;
} else {
+8 -2
View File
@@ -189,11 +189,14 @@ void LinkedObjectFile::symbol_link_word(int source_segment,
* Add link information that a word's lower 16 bits are the offset of the given symbol relative to
* the symbol table register.
*/
void LinkedObjectFile::symbol_link_offset(int source_segment, int source_offset, const char* name) {
void LinkedObjectFile::symbol_link_offset(int source_segment,
int source_offset,
const char* name,
bool subtract_one) {
ASSERT((source_offset % 4) == 0);
auto& word = words_by_seg.at(source_segment).at(source_offset / 4);
ASSERT(word.kind() == LinkedWord::PLAIN_DATA);
word.set_to_symbol(LinkedWord::SYM_OFFSET, name);
word.set_to_symbol(subtract_one ? LinkedWord::SYM_VAL_OFFSET : LinkedWord::SYM_OFFSET, name);
}
/*!
@@ -312,6 +315,9 @@ void LinkedObjectFile::append_word_to_string(std::string& dest, const LinkedWord
case LinkedWord::SYM_OFFSET:
sprintf(buff, " .sym-off 0x%x %s\n", word.data >> 16, word.symbol_name().c_str());
break;
case LinkedWord::SYM_VAL_OFFSET:
sprintf(buff, " .sym-val-off 0x%x %s\n", word.data >> 16, word.symbol_name().c_str());
break;
default:
throw std::runtime_error("nyi");
}
+4 -1
View File
@@ -41,7 +41,10 @@ class LinkedObjectFile {
int source_offset,
const char* name,
LinkedWord::Kind kind);
void symbol_link_offset(int source_segment, int source_offset, const char* name);
void symbol_link_offset(int source_segment,
int source_offset,
const char* name,
bool subtract_one);
Function& get_function_at_label(int label_id);
Function* try_get_function_at_label(int label_id);
Function* try_get_function_at_label(const DecompilerLabel& label);
@@ -143,8 +143,8 @@ static uint32_t c_symlink2(LinkedObjectFile& f,
ASSERT((code_value & 0xffff) == 0 || (code_value & 0xffff) == 0xffff);
ASSERT(kind == SymbolLinkKind::SYMBOL);
// ASSERT(false); // this case does not occur in V2/V4. It does in V3.
f.symbol_link_offset(seg_id, code_ptr_offset - initial_offset, name);
f.symbol_link_offset(seg_id, code_ptr_offset - initial_offset, name,
(code_value & 0xffff) == 0xffff);
}
} while (data.at(link_ptr_offset));
@@ -197,9 +197,11 @@ static uint32_t c_symlink3(LinkedObjectFile& f,
f.symbol_link_word(seg, code_ptr - initial_offset, name, word_kind);
} else {
u16 lower = code_value & 0xffff;
ASSERT(lower == 0 || lower == 0xffff);
f.stats.v3_symbol_link_offset++;
ASSERT(kind == SymbolLinkKind::SYMBOL);
f.symbol_link_offset(seg, code_ptr - initial_offset, name);
f.symbol_link_offset(seg, code_ptr - initial_offset, name, lower == 0xffff);
}
} while (data.at(link_ptr));
+11 -9
View File
@@ -24,14 +24,15 @@ namespace decompiler {
class LinkedWord {
public:
enum Kind : u8 {
PLAIN_DATA, // just plain data
PTR, // pointer to a location
HI_PTR, // lower 16-bits of this data are the upper 16 bits of a pointer
LO_PTR, // lower 16-bits of this data are the lower 16 bits of a pointer
SYM_PTR, // this is a pointer to a symbol
EMPTY_PTR, // this is a pointer to the empty list
SYM_OFFSET, // this is an offset of a symbol in the symbol table
TYPE_PTR // this is a pointer to a type
PLAIN_DATA, // just plain data
PTR, // pointer to a location
HI_PTR, // lower 16-bits of this data are the upper 16 bits of a pointer
LO_PTR, // lower 16-bits of this data are the lower 16 bits of a pointer
SYM_PTR, // this is a pointer to a symbol
EMPTY_PTR, // this is a pointer to the empty list
SYM_OFFSET, // this is an offset of a symbol in the symbol table
SYM_VAL_OFFSET, // offset to the value of the symbol (different in jak 2)
TYPE_PTR // this is a pointer to a type
};
private:
@@ -46,7 +47,8 @@ class LinkedWord {
explicit LinkedWord(uint32_t _data) : data(_data) {}
bool holds_string() const {
return m_kind == SYM_PTR || m_kind == SYM_OFFSET || m_kind == TYPE_PTR;
return m_kind == SYM_PTR || m_kind == SYM_OFFSET || m_kind == TYPE_PTR ||
m_kind == SYM_VAL_OFFSET;
}
LinkedWord(const LinkedWord& other) {
@@ -1496,13 +1496,10 @@ void inspect_top_level_for_metadata(Function& top_level,
continue;
}
lg::print("got 1\n");
// lwu t9, 16(v1) ;; [ 21] (set! t9-0 (l.wu (+ v1-10 16)))
// ;; [v1: <the etype type> ] -> [t9: (function symbol type int type)
const auto& aop_1 = top_level.ir2.atomic_ops->ops.at(i + 1);
if (!is_set_reg_to_load(aop_1.get(), Register(Reg::GPR, Reg::T9), 16)) {
lg::print("fail1\n");
continue;
}
@@ -1510,7 +1507,6 @@ void inspect_top_level_for_metadata(Function& top_level,
const auto& aop_2 = top_level.ir2.atomic_ops->ops.at(i + 2);
auto type_name = get_set_reg_to_symbol_ptr(aop_2.get(), Register(Reg::GPR, Reg::A0));
if (!type_name) {
lg::print("fail2\n");
continue;
}
@@ -1518,7 +1514,6 @@ void inspect_top_level_for_metadata(Function& top_level,
const auto& aop_3 = top_level.ir2.atomic_ops->ops.at(i + 3);
auto parent_name = get_set_reg_to_symbol_value(aop_3.get(), Register(Reg::GPR, Reg::A1));
if (!parent_name) {
lg::print("fail3\n");
continue;
}
@@ -1526,14 +1521,12 @@ void inspect_top_level_for_metadata(Function& top_level,
const auto& aop_4 = top_level.ir2.atomic_ops->ops.at(i + 4);
auto flags = get_set_reg_to_u64_load(aop_4.get(), Register(Reg::GPR, Reg::A2), file);
if (!flags) {
lg::print("fail3\n");
continue;
}
// jalr ra, t9 ;; [ 25] (call! a0-0 a1-0 a2-0)
const auto& aop_5 = top_level.ir2.atomic_ops->ops.at(i + 5);
if (!dynamic_cast<CallOp*>(aop_5.get())) {
lg::print("fial4\n");
continue;
}
+8 -2
View File
@@ -575,8 +575,14 @@ std::unique_ptr<AtomicOp> convert_lw_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);
if (i0.get_src(1).kind == InstructionAtom::IMM_SYM_VAL_PTR) {
return std::make_unique<SetVarOp>(
make_dst_var(i0, idx), SimpleAtom::make_sym_val_ptr(i0.get_src(1).get_sym()).as_expr(),
idx);
} else {
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(empty_pair_offset_from_s7(version))) {
// get empty pair
-29
View File
@@ -1249,69 +1249,57 @@ Form* try_sc_as_type_of_jak2(FormPool& pool, Function& f, const ShortCircuit* vt
// get the branch ir's
auto b0_ptr = cfg_to_ir(pool, f, b0_c); // should be begin.
if (b0_ptr->size() <= 2) {
lg::print("fail1\n");
return nullptr;
}
auto b1_ptr = cfg_to_ir(pool, f, b1_c);
if (b1_ptr->size() <= 1) {
lg::print("fail2\n");
return nullptr;
}
auto b2_ptr = cfg_to_ir(pool, f, b2_c);
if (b2_ptr->size() <= 1) {
lg::print("fail3\n");
return nullptr;
}
auto b3_ptr = cfg_to_ir(pool, f, b3_c);
auto b3_ir = dynamic_cast<SetVarElement*>(b3_ptr->try_as_single_element());
if (!b3_ir) {
lg::print("fail4\n");
return nullptr;
}
// identify the left shift
auto set_shift_left = dynamic_cast<SetVarElement*>(b0_ptr->at(b0_ptr->size() - 3));
if (!set_shift_left) {
lg::print("fail5\n");
return nullptr;
}
auto temp_reg0 = set_shift_left->dst();
auto shift_left =
dynamic_cast<SimpleExpressionElement*>(set_shift_left->src()->try_as_single_element());
if (!shift_left || shift_left->expr().kind() != SimpleExpression::Kind::LEFT_SHIFT) {
lg::print("fail6\n");
return nullptr;
}
auto src_reg = shift_left->expr().get_arg(0).var();
auto sa_left = shift_left->expr().get_arg(1);
if (!sa_left.is_int() || sa_left.get_int() != 61) {
lg::print("fail7\n");
return nullptr;
}
// identify the right shift
auto set_shift_right = dynamic_cast<SetVarElement*>(b0_ptr->at(b0_ptr->size() - 2));
if (!set_shift_right) {
lg::print("fail8\n");
return nullptr;
}
if (set_shift_right->dst().reg() != set_shift_left->dst().reg()) {
lg::print("fail9\n");
return nullptr;
}
auto shift_right =
dynamic_cast<SimpleExpressionElement*>(set_shift_right->src()->try_as_single_element());
if (!shift_right || shift_right->expr().kind() != SimpleExpression::Kind::RIGHT_SHIFT_LOGIC) {
lg::print("fail10\n");
return nullptr;
}
if (temp_reg0.reg() != shift_right->expr().get_arg(0).var().reg()) {
lg::print("fail11\n");
return nullptr;
}
auto sa_right = shift_right->expr().get_arg(1);
if (!sa_right.is_int() || sa_right.get_int() != 61) {
lg::print("fail12\n");
return nullptr;
}
@@ -1321,7 +1309,6 @@ Form* try_sc_as_type_of_jak2(FormPool& pool, Function& f, const ShortCircuit* vt
if (!first_branch || !is_set_symbol_value(b0_delay_op, "binteger") ||
first_branch->op()->condition().kind() != IR2_Condition::Kind::ZERO ||
!first_branch->op()->likely()) {
lg::print("fail13\n");
return nullptr;
}
auto temp_reg = first_branch->op()->condition().src(0).var();
@@ -1330,19 +1317,16 @@ Form* try_sc_as_type_of_jak2(FormPool& pool, Function& f, const ShortCircuit* vt
// branch 1
if (b1_ptr->size() != 2) {
lg::print("fail14\n");
return nullptr;
}
auto second_branch_pre_op = dynamic_cast<SetVarElement*>(b1_ptr->at(0));
if (!second_branch_pre_op) {
lg::print("fail15\n");
return nullptr;
}
{
auto pos = second_branch_pre_op->src();
auto pos_as_se = pos->try_as_element<SimpleExpressionElement>();
if (!pos_as_se || !pos_as_se->expr().is_identity() || !pos_as_se->expr().get_arg(0).is_int(4)) {
lg::print("fail16\n");
return nullptr;
}
}
@@ -1358,30 +1342,24 @@ Form* try_sc_as_type_of_jak2(FormPool& pool, Function& f, const ShortCircuit* vt
second_branch->op()->condition().src(0).var().reg() != temp_reg0.reg() ||
!second_branch->op()->condition().src(1).is_var() ||
second_branch->op()->condition().src(1).var().reg() != temp_reg1.reg()) {
lg::print("fail17\n");
return nullptr;
}
if (!b1_d) {
lg::print("fail18\n");
return nullptr;
}
auto b1_delay_op = get_delay_load_op(f, b1_d);
if (b1_delay_op.kind() != LoadVarOp::Kind::UNSIGNED || b1_delay_op.size() != 4) {
lg::print("fail19\n");
return nullptr;
}
IR2_RegOffset ro;
if (!get_as_reg_offset(b1_delay_op.src(), &ro)) {
lg::print("fail20\n");
return nullptr;
}
if (ro.offset != -4) {
lg::print("fail21\n");
return nullptr;
}
if (ro.reg != src_reg.reg() || b1_delay_op.get_set_destination().reg() != dst_reg.reg()) {
lg::print("fail22\n");
return nullptr;
}
@@ -1395,19 +1373,16 @@ Form* try_sc_as_type_of_jak2(FormPool& pool, Function& f, const ShortCircuit* vt
lw a0, pair(s7)
*/
if (b2_ptr->size() != 2) {
lg::print("fail23\n");
return nullptr;
}
auto third_branch_pre_op = dynamic_cast<SetVarElement*>(b2_ptr->at(0));
if (!third_branch_pre_op) {
lg::print("fail24\n");
return nullptr;
}
{
auto pos = third_branch_pre_op->src();
auto pos_as_se = pos->try_as_element<SimpleExpressionElement>();
if (!pos_as_se || !pos_as_se->expr().is_identity() || !pos_as_se->expr().get_arg(0).is_int(2)) {
lg::print("fail25\n");
return nullptr;
}
}
@@ -1418,22 +1393,18 @@ Form* try_sc_as_type_of_jak2(FormPool& pool, Function& f, const ShortCircuit* vt
third_branch->op()->condition().src(0).var().reg() != temp_reg0.reg() ||
!third_branch->op()->condition().src(1).is_var() ||
third_branch->op()->condition().src(1).var().reg() != temp_reg2.reg()) {
lg::print("fail26\n");
return nullptr;
}
if (!b2_d) {
lg::print("fail27\n");
return nullptr;
}
auto b2_delay_op = get_delay_op(f, b2_d);
if (!is_set_symbol_value(b2_delay_op, "pair") || b2_delay_op.dst().reg() != dst_reg.reg()) {
lg::print("fail28\n");
return nullptr;
}
if (!is_set_symbol_value(b3_ir, "symbol")) {
lg::print("fail29\n");
return nullptr;
}
+2 -2
View File
@@ -26034,7 +26034,7 @@
(define-extern load-game-text-info
"Load text, if needed. txt-name is the group name, curr-text is the _symbol_ for
the game-text-info, and heap is the heap to load to. The heap will be cleared."
(function string symbol kheap int))
(function string (pointer object) kheap int))
(define-extern load-level-text-files
"Load the text files needed for level idx.
This function made more sense back when text files were split up, but in the end they put everything
@@ -29997,7 +29997,7 @@
(define-extern *hud-sprite-work* hud-sprite-work)
(define-extern hud-create-icon (function none))
(define-extern hud-hidden (state hud)) ;; (state hud)
(define-extern hud-arriving (state hud)) ;;
(define-extern hud-arriving (state hud)) ;;
(define-extern hud-in (state hud)) ;; (state hud)
(define-extern hud-leaving (state int hud)) ;; (state int hud)
(define-extern hud-init-by-other (function object :behavior hud))
+11
View File
@@ -189,6 +189,13 @@ TP_Type get_type_symbol_ptr(const std::string& name) {
}
}
TP_Type get_type_symbol_val_ptr(const std::string& name,
const DecompilerTypeSystem& dts,
const Env& env) {
return TP_Type::make_from_ts(
TypeSpec("pointer", {get_type_symbol_val(name, dts, env).typespec()}));
}
/*!
* Try to figure out the type of an atom.
*/
@@ -610,6 +617,10 @@ void types2_for_atom(types2::Type& type_out,
auto type = get_type_symbol_ptr(atom.get_str());
type_out.type = type;
} break;
case SimpleAtom::Kind::SYMBOL_VAL_PTR: {
auto type = get_type_symbol_val_ptr(atom.get_str(), dts, env);
type_out.type = type;
} break;
case SimpleAtom::Kind::INTEGER_CONSTANT: {
// auto type = TP_Type::make_from_integer(atom.get_int());
// type_out.type = type;
+4
View File
@@ -799,6 +799,10 @@ std::string debug_print(const LinkedWord& word) {
return fmt::format("offset '{}", word.symbol_name());
case LinkedWord::SYM_PTR:
return fmt::format("ptr '{}", word.symbol_name());
case LinkedWord::SYM_VAL_OFFSET:
return fmt::format("val-ptr '{}", word.symbol_name());
default:
ASSERT(false);
}
}
+1 -1
View File
@@ -11,7 +11,7 @@
(define-extern load-game-text-info
"Load text, if needed. txt-name is the group name, curr-text is the _symbol_ for
the game-text-info, and heap is the heap to load to. The heap will be cleared."
(function string symbol kheap int))
(function string (pointer object) kheap int))
(define-extern print-game-text-scaled "Print text, with a given scaling" (function string float font-context int none))
(define-extern print-game-text "Print text." (function string font-context symbol int int float))
+6 -6
View File
@@ -226,11 +226,11 @@
(define text-is-loading #f)
;; WARN: Found some very strange gotos. Check result carefully, this is not well tested.
(defun load-game-text-info ((arg0 string) (arg1 symbol) (arg2 kheap))
(defun load-game-text-info ((arg0 string) (arg1 (pointer object)) (arg2 kheap))
"Load text, if needed. txt-name is the group name, curr-text is the _symbol_ for
the game-text-info, and heap is the heap to load to. The heap will be cleared."
(local-vars (v0-3 int) (sv-16 game-text-info) (sv-24 int) (sv-32 int) (sv-40 int))
(set! sv-16 (the-as game-text-info (-> arg1 value)))
(set! sv-16 (the-as game-text-info (-> arg1 0)))
(set! sv-24 (the-as int (-> *setting-control* user-current language)))
(set! sv-32 0)
(set! sv-40 (&- (-> arg2 top) (the-as uint (-> arg2 base))))
@@ -290,11 +290,11 @@
(flush-cache 0)
(let ((s3-1 link))
(format (clear *temp-string*) "~D~S.TXT" sv-24 arg0)
(set! (-> arg1 value) (s3-1 s2-1 (-> *temp-string* data) sv-32 arg2 0))
(set! (-> arg1 0) (s3-1 s2-1 (-> *temp-string* data) sv-32 arg2 0))
)
)
(if (<= (the-as int (-> arg1 value)) 0)
(set! (-> arg1 value) (the-as object #f))
(if (<= (the-as int (-> arg1 0)) 0)
(set! (-> arg1 0) (the-as object #f))
)
)
(set! v0-3 0)
@@ -307,7 +307,7 @@
This function made more sense back when text files were split up, but in the end they put everything
in a single text group and file."
(if (or *level-text-file-load-flag* (>= arg0 0))
(load-game-text-info "common" '*common-text* *common-text-heap*)
(load-game-text-info "common" (&-> '*common-text* value) *common-text-heap*)
)
0
(none)
+69 -344
View File
@@ -1,28 +1,19 @@
;;-*-Lisp-*-
(in-package goal)
;; failed to figure out what this is:
(kmemopen global "text")
;; definition for symbol *expand-buf-number*, type int
(define *expand-buf-number* 0)
;; definition for symbol *game-text-word*, type string
(define *game-text-word* (new 'global 'string 256 (the-as string #f)))
;; definition for symbol *game-text-line*, type string
(define *game-text-line* (new 'global 'string 1024 (the-as string #f)))
;; definition for symbol *expanded-text-line0*, type string
(define *expanded-text-line0* (new 'global 'string 1024 (the-as string #f)))
;; definition for symbol *expanded-text-line1*, type string
(define *expanded-text-line1* (new 'global 'string 1024 (the-as string #f)))
;; definition for symbol *level-text-file-load-flag*, type symbol
(define *level-text-file-load-flag* #t)
;; failed to figure out what this is:
(when (zero? (-> *common-text-heap* base))
(let ((gp-0 *common-text-heap*))
(set! (-> gp-0 base) (kmalloc global #x10000 (kmalloc-flags) "heap"))
@@ -32,10 +23,8 @@
)
)
;; failed to figure out what this is:
(kmemclose)
;; definition for method 7 of type game-text-info
(defmethod relocate game-text-info ((obj game-text-info) (arg0 int))
(let ((v1-1 (-> *level* loading-level)))
(when v1-1
@@ -46,19 +35,14 @@
obj
)
;; definition for method 4 of type game-text-info
(defmethod length game-text-info ((obj game-text-info))
(-> obj length)
)
;; definition for method 5 of type game-text-info
;; WARN: Return type mismatch uint vs int.
(defmethod asize-of game-text-info ((obj game-text-info))
(the-as int (+ (-> obj type size) (* (-> obj length) 8)))
)
;; definition for method 3 of type game-text-info
;; INFO: this function exists in multiple non-identical object files
(defmethod inspect game-text-info ((obj game-text-info))
(format #t "[~8x] ~A~%" obj (-> obj type))
(format #t "~Tlength: ~D~%" (-> obj length))
@@ -71,7 +55,6 @@
obj
)
;; definition for method 8 of type game-text-info
(defmethod mem-usage game-text-info ((obj game-text-info) (arg0 memory-usage-block) (arg1 int))
(set! (-> arg0 length) (max 84 (-> arg0 length)))
(set! (-> arg0 data 83 name) "string")
@@ -92,7 +75,6 @@
obj
)
;; definition for function convert-korean-text
(defun convert-korean-text ((arg0 string))
"Converts the provided [[string]] of [[game-text]] into korean. Returns a [[string]]"
(local-vars (v1-21 int))
@@ -181,7 +163,6 @@
)
)
;; definition for method 9 of type game-text-info
(defmethod lookup-text! game-text-info ((obj game-text-info) (arg0 game-text-id) (arg1 symbol))
(cond
((= obj #f)
@@ -234,7 +215,6 @@
)
)
;; definition for method 23 of type level
(defmethod lookup-text level ((obj level) (arg0 game-text-id) (arg1 symbol))
(let ((v1-0 *common-text*))
(dotimes (a3-0 (-> obj loaded-text-info-count))
@@ -246,281 +226,95 @@
)
)
;; definition for symbol text-is-loading, type symbol
(define text-is-loading #f)
;; definition for function load-game-text-info
;; WARN: Found some very strange gotos. Check result carefully, this is not well tested.
;; ERROR: failed type prop at 3: Could not figure out load: (set! v1 (l.wu gp))
;; WARN: Return type mismatch none vs int.
(defun load-game-text-info ((a0-0 string) (a1-0 symbol) (a2-0 kheap))
(defun load-game-text-info ((arg0 string) (arg1 (pointer object)) (arg2 kheap))
"Load text, if needed. txt-name is the group name, curr-text is the _symbol_ for
the game-text-info, and heap is the heap to load to. The heap will be cleared."
(local-vars
(v0-0 none)
(v0-1 none)
(v0-2 none)
(v0-3 none)
(v0-4 none)
(v0-5 none)
(v0-6 none)
(v0-7 none)
(v0-8 none)
(v0-9 none)
(v0-10 none)
(v0-11 none)
(v0-12 none)
(v0-13 none)
(v1-0 game-text-info)
(v1-1 none)
(v1-2 none)
(v1-3 none)
(v1-4 none)
(v1-5 none)
(v1-6 none)
(v1-7 none)
(v1-9 none)
(v1-10 none)
(v1-11 none)
(v1-12 none)
(v1-13 none)
(v1-14 none)
(v1-16 none)
(v1-17 none)
(v1-18 none)
(v1-20 none)
(v1-21 none)
(v1-24 none)
(v1-25 none)
(v1-26 none)
(a0-1 none)
(a0-2 none)
(a0-3 none)
(a0-4 none)
(a0-5 none)
(a0-6 none)
(a0-7 none)
(a0-8 none)
(a0-10 none)
(a0-11 none)
(a0-12 none)
(a0-13 none)
(a0-14 none)
(a0-15 none)
(a0-17 none)
(a0-18 none)
(a0-19 none)
(a0-20 none)
(a0-21 none)
(a0-22 none)
(a1-2 none)
(a1-3 none)
(a1-4 none)
(a1-5 none)
(a1-6 none)
(a1-7 none)
(a1-8 none)
(a1-9 none)
(a2-1 none)
(a2-2 none)
(a2-3 none)
(a2-4 none)
(a2-5 none)
(a2-6 none)
(a3-1 none)
(a3-2 none)
(t0-0 none)
(s1-0 none)
(s2-0 none)
(s2-1 none)
(s3-0 none)
(s3-1 none)
(t9-0 none)
(t9-1 none)
(t9-2 none)
(t9-3 none)
(t9-4 none)
(t9-5 none)
(t9-6 none)
(t9-7 none)
(t9-8 none)
(t9-9 none)
(t9-10 none)
(t9-11 none)
(t9-12 none)
(sv-16 none)
(sv-24 none)
(sv-32 none)
(sv-40 none)
)
(when (begin
(when (begin
(and (begin
(set! v1-0 (the-as game-text-info (l.wu a1-0)))
(set! sv-16 v1-0)
(set! v1-1 (the-as none *setting-control*))
(set! v1-2 (the-as none (l.d (+ v1-1 28))))
(set! sv-24 v1-2)
(set! sv-32 0)
(set! v1-3 (the-as none (-> a2-0 top)))
(set! a0-1 (the-as none (-> a2-0 base)))
(set! v1-4 (the-as none (- v1-3 a0-1)))
(set! sv-40 v1-4)
(set! t9-0 (the-as none scf-get-territory))
(set! v0-0 (the-as none (call!)))
(set! v1-5 (the-as none (+ v0-0 -1)))
(set! a0-2 (the-as none (zero? v1-5)))
a0-2
)
(begin (set! v1-7 sv-24) (zero? v1-7))
(begin (set! t9-1 (the-as none demo?)) (set! v0-1 (the-as none (call!))) (set! v1-6 (the-as none (not v0-1))))
)
v1-6
)
(set! v1-9 (the-as none 7))
(set! sv-24 v1-9)
)
(or (begin (set! v1-10 sv-16) (set! a0-3 (the-as none (= v1-10 #f))) a0-3)
(begin
(set! v1-12 sv-16)
(set! v1-13 (the-as none (l.w (+ v1-12 4))))
(set! a0-4 sv-24)
(set! a0-5 (the-as none (!= v1-13 a0-4)))
a0-5
)
(begin
(set! t9-2 (the-as none string=))
(set! v1-14 sv-16)
(set! a0-6 (the-as none (l.wu (+ v1-14 8))))
(set! a1-1 (the-as none a0-0))
(set! v0-2 (the-as none (call!)))
(set! v1-11 (the-as none (not v0-2)))
)
)
v1-11
)
(cond
((begin
(set! v1-16 (the-as none a2-0))
(set! a0-7 (the-as none (l.wu v1-16)))
(s.w! (+ v1-16 8) a0-7)
((b! #t L50 (nop!)) (nop!))
(label cfg-16)
(set! v0-3 (the-as none 0))
((b! #t L56 (nop!)) (nop!))
(label cfg-17)
(set! s3-0 (the-as none str-load))
(set! s2-0 (the-as none format))
(set! t9-3 (the-as none clear))
(set! a0-8 (the-as none *temp-string*))
(call!)
(set! a0-9 (the-as none v0-4))
(set! a1-2 (the-as none L100))
(set! a2-1 sv-24)
(set! a3-0 (the-as none a0-0))
(set! t9-4 (the-as none s2-0))
(call!)
(set! a0-10 (the-as none *temp-string*))
(set! a1-3 (the-as none -1))
(set! v1-17 (the-as none -64))
(set! a2-2 (the-as none (l.wu (+ a2-0 8))))
(set! a2-3 (the-as none (+ a2-2 63)))
(set! a2-4 (the-as none (logand v1-17 a2-3)))
(set! v1-18 (the-as none (l.wu (+ a2-0 4))))
(set! a3-1 (the-as none (l.wu (+ a2-0 8))))
(set! a3-2 (the-as none (- v1-18 a3-1)))
(set! t9-5 (the-as none s3-0))
(set! v0-6 (the-as none (call!)))
((b! (not v0-6) L49 (nop!)) (nop!))
(label cfg-19)
(set! t9-6 (the-as none str-load-status))
(set! a0-11 (& sv-32))
(set! v0-7 (the-as none (call!)))
(set! v1-20 (the-as none v0-7))
(set! a0-12 (the-as none 'error))
((b! (!= v1-20 a0-12) L52 (set! a0-13 #f)) (empty-form))
(set! t9-7 (the-as none format))
(set! a0-14 (the-as none 0))
(set! a1-4 (the-as none L99))
(call!)
(set! v0-3 (the-as none 0))
((b! #t L56 (nop!)) (nop!))
(set! v1-21 (the-as none 0))
((b! #t L54 (nop!)) (nop!))
(label cfg-22)
(set! a0-15 sv-32)
(set! a1-5 sv-40)
(set! a1-6 (the-as none (+ a1-5 -300)))
(>=.si a0-15 a1-6)
)
(return (begin
(set! t9-8 (the-as none format))
(set! a0-17 (the-as none 0))
(set! a1-7 (the-as none L98))
(call!)
(set! v0-3 (the-as none 0))
)
)
)
((begin (set! a0-18 (the-as none 'busy)) (= v1-20 a0-18))
(begin (nop!) (nop!) (nop!) (nop!) (nop!) (nop!) (goto cfg-19))
)
(local-vars (v0-3 int) (sv-16 game-text-info) (sv-24 int) (sv-32 int) (sv-40 int))
(set! sv-16 (the-as game-text-info (-> arg1 0)))
(set! sv-24 (the-as int (-> *setting-control* user-current language)))
(set! sv-32 0)
(set! sv-40 (&- (-> arg2 top) (the-as uint (-> arg2 base))))
(if (and (= (scf-get-territory) 1) (= sv-24 (language-enum english)) (not (demo?)))
(set! sv-24 7)
)
(if (begin
(label cfg-27)
(set! v1-24 (the-as none -64))
(set! a0-19 (the-as none (l.wu (+ a2-0 8))))
(set! a0-20 (the-as none (+ a0-19 63)))
(set! s2-1 (the-as none (logand v1-24 a0-20)))
(set! t9-9 (the-as none flush-cache))
(set! a0-21 (the-as none 0))
(call!)
(set! s3-1 (the-as none link))
(set! s1-0 (the-as none format))
(set! t9-10 (the-as none clear))
(set! a0-22 (the-as none *temp-string*))
(call!)
(set! a0-23 (the-as none v0-11))
(set! a1-8 (the-as none L100))
(set! a2-5 sv-24)
(set! t9-11 (the-as none s1-0))
(set! a3-3 (the-as none a0-0))
(call!)
(set! v1-25 (the-as none *temp-string*))
(set! a1-9 (the-as none (+ v1-25 4)))
(set! a2-6 sv-32)
(set! t0-0 (the-as none 0))
(set! t9-12 (the-as none s3-1))
(set! a0-24 (the-as none s2-1))
(set! a3-4 (the-as none a2-0))
(set! v0-13 (the-as none (call!)))
(s.w! a1-0 v0-13)
(set! v1-26 (the-as none (l.wu a1-0)))
(<=0.si v1-26)
)
(s.w! a1-0 #f)
(when (or (= sv-16 #f) (!= (-> sv-16 language-id) sv-24) (not (string= (-> sv-16 group-name) arg0)))
(let ((v1-16 arg2))
(set! (-> v1-16 current) (-> v1-16 base))
)
(b! #t cfg-17 :delay (nop!))
(label cfg-16)
(set! v0-3 0)
(b! #t cfg-30 :delay (nop!))
(label cfg-17)
(let ((s3-0 str-load))
(format (clear *temp-string*) "~D~S.TXT" sv-24 arg0)
(b!
(not (s3-0
*temp-string*
-1
(logand -64 (&+ (-> arg2 current) 63))
(&- (-> arg2 top) (the-as uint (-> arg2 current)))
)
)
cfg-16
:delay (nop!)
)
)
(label cfg-19)
(let ((v1-20 (str-load-status (the-as (pointer int32) (& sv-32)))))
(b! (!= v1-20 'error) cfg-22 :delay (empty-form))
(format 0 "Error loading text~%")
(set! v0-3 0)
(b! #t cfg-30 :delay (nop!))
(the-as none 0)
(b! #t cfg-27 :delay (nop!))
(label cfg-22)
(cond
((>= sv-32 (+ sv-40 -300))
(format 0 "Game text heap overrun!~%")
(return 0)
)
((= v1-20 'busy)
(nop!)
(nop!)
(nop!)
(nop!)
(nop!)
(nop!)
(goto cfg-19)
)
)
)
(label cfg-27)
(let ((s2-1 (logand -64 (&+ (-> arg2 current) 63))))
(flush-cache 0)
(let ((s3-1 link))
(format (clear *temp-string*) "~D~S.TXT" sv-24 arg0)
(set! (-> arg1 0) (s3-1 s2-1 (-> *temp-string* data) sv-32 arg2 0))
)
)
(if (<= (the-as int (-> arg1 0)) 0)
(set! (-> arg1 0) (the-as object #f))
)
)
(set! v0-3 (the-as none 0))
(set! v0-3 0)
(label cfg-30)
(ret-value v0-3)
v0-3
)
;; definition for function load-level-text-files
;; WARN: Return type mismatch int vs none.
(defun load-level-text-files ((arg0 int))
"Load the text files needed for level idx.
This function made more sense back when text files were split up, but in the end they put everything
in a single text group and file."
(if (or *level-text-file-load-flag* (>= arg0 0))
(load-game-text-info "common" '*common-text* *common-text-heap*)
(load-game-text-info "common" (&-> '*common-text* value) *common-text-heap*)
)
0
(none)
)
;; definition for function draw-debug-text-box
;; WARN: Return type mismatch int vs none.
(defun draw-debug-text-box ((arg0 font-context))
"Draws some lines"
(when *cheat-mode*
@@ -566,8 +360,6 @@
(none)
)
;; definition for function print-game-text-scaled
;; WARN: Return type mismatch int vs none.
(defun print-game-text-scaled ((arg0 string) (arg1 float) (arg2 font-context) (arg3 int))
"Print text, with a given scaling"
(let ((f26-0 (-> arg2 width))
@@ -602,69 +394,6 @@
(none)
)
;; definition for function print-game-text
;; WARN: Stack slot offset 48 signed mismatch
;; WARN: Stack slot offset 16 signed mismatch
;; WARN: Stack slot offset 20 signed mismatch
;; WARN: Stack slot offset 56 signed mismatch
;; WARN: Stack slot offset 52 signed mismatch
;; WARN: Stack slot offset 44 signed mismatch
;; WARN: Stack slot offset 36 signed mismatch
;; WARN: Stack slot offset 56 signed mismatch
;; WARN: Stack slot offset 40 signed mismatch
;; WARN: Stack slot offset 36 signed mismatch
;; WARN: Stack slot offset 48 signed mismatch
;; WARN: Stack slot offset 16 signed mismatch
;; WARN: Stack slot offset 20 signed mismatch
;; WARN: Stack slot offset 56 signed mismatch
;; WARN: Stack slot offset 52 signed mismatch
;; WARN: Stack slot offset 44 signed mismatch
;; WARN: Stack slot offset 36 signed mismatch
;; WARN: Stack slot offset 56 signed mismatch
;; WARN: Stack slot offset 40 signed mismatch
;; WARN: Stack slot offset 36 signed mismatch
;; WARN: Stack slot offset 48 signed mismatch
;; WARN: Stack slot offset 16 signed mismatch
;; WARN: Stack slot offset 20 signed mismatch
;; WARN: Stack slot offset 56 signed mismatch
;; WARN: Stack slot offset 52 signed mismatch
;; WARN: Stack slot offset 44 signed mismatch
;; WARN: Stack slot offset 36 signed mismatch
;; WARN: Stack slot offset 48 signed mismatch
;; WARN: Stack slot offset 16 signed mismatch
;; WARN: Stack slot offset 20 signed mismatch
;; WARN: Stack slot offset 56 signed mismatch
;; WARN: Stack slot offset 48 signed mismatch
;; WARN: Stack slot offset 16 signed mismatch
;; WARN: Stack slot offset 20 signed mismatch
;; WARN: Stack slot offset 56 signed mismatch
;; WARN: Stack slot offset 52 signed mismatch
;; WARN: Stack slot offset 44 signed mismatch
;; WARN: Stack slot offset 36 signed mismatch
;; WARN: Stack slot offset 56 signed mismatch
;; WARN: Stack slot offset 40 signed mismatch
;; WARN: Stack slot offset 36 signed mismatch
;; WARN: Stack slot offset 48 signed mismatch
;; WARN: Stack slot offset 16 signed mismatch
;; WARN: Stack slot offset 20 signed mismatch
;; WARN: Stack slot offset 56 signed mismatch
;; WARN: Stack slot offset 52 signed mismatch
;; WARN: Stack slot offset 44 signed mismatch
;; WARN: Stack slot offset 36 signed mismatch
;; WARN: Stack slot offset 56 signed mismatch
;; WARN: Stack slot offset 40 signed mismatch
;; WARN: Stack slot offset 36 signed mismatch
;; WARN: Stack slot offset 48 signed mismatch
;; WARN: Stack slot offset 16 signed mismatch
;; WARN: Stack slot offset 20 signed mismatch
;; WARN: Stack slot offset 56 signed mismatch
;; WARN: Stack slot offset 52 signed mismatch
;; WARN: Stack slot offset 44 signed mismatch
;; WARN: Stack slot offset 36 signed mismatch
;; WARN: Stack slot offset 48 signed mismatch
;; WARN: Stack slot offset 16 signed mismatch
;; WARN: Stack slot offset 20 signed mismatch
;; WARN: Stack slot offset 56 signed mismatch
(defun print-game-text ((arg0 string) (arg1 font-context) (arg2 symbol) (arg3 int) (arg4 int))
"Print text."
(local-vars
@@ -859,8 +588,6 @@
)
)
;; definition for function disable-level-text-file-loading
;; WARN: Return type mismatch int vs none.
(defun disable-level-text-file-loading ()
"Disables [[*level-text-file-load-flag*]]"
(set! *level-text-file-load-flag* #f)
@@ -868,8 +595,6 @@
(none)
)
;; definition for function enable-level-text-file-loading
;; WARN: Return type mismatch int vs none.
(defun enable-level-text-file-loading ()
"Disables [[*level-text-file-load-flag*]]"
(set! *level-text-file-load-flag* #t)
+107 -73
View File
@@ -174,12 +174,26 @@ int line_count(const std::string& str) {
}
struct CompareResult {
std::vector<std::string> failing_files;
struct Fail {
std::string filename;
std::string diff;
};
std::vector<Fail> failing_files;
int total_files = 0;
int ok_files = 0;
int total_lines = 0;
bool total_pass = true;
void add(const CompareResult& other) {
failing_files.insert(failing_files.end(), other.failing_files.begin(),
other.failing_files.end());
total_files += other.total_files;
ok_files += other.ok_files;
total_lines += other.total_lines;
if (!other.total_pass) {
total_pass = false;
}
}
};
CompareResult compare(Decompiler& dc, const std::vector<DecompilerFile>& refs, bool dump_mode) {
@@ -192,11 +206,8 @@ CompareResult compare(Decompiler& dc, const std::vector<DecompilerFile>& refs, b
compare_result.total_files++;
compare_result.total_lines += line_count(result);
if (result != ref) {
compare_result.failing_files.push_back(file.unique_name);
compare_result.failing_files.push_back({file.unique_name, diff_strings(ref, result)});
compare_result.total_pass = false;
fmt::print("Reference test failure on {}:\n", file.unique_name);
fmt::print("{}\n", diff_strings(ref, result));
if (dump_mode) {
auto failure_dir = file_util::get_jak_project_dir() / "failures";
file_util::create_dir_if_needed(failure_dir);
@@ -211,18 +222,35 @@ CompareResult compare(Decompiler& dc, const std::vector<DecompilerFile>& refs, b
return compare_result;
}
bool compile(Decompiler& dc,
const std::vector<DecompilerFile>& refs,
const OfflineTestConfig& config,
const std::string& game_name) {
fmt::print("Setting up compiler...\n");
struct CompileResult {
bool ok = true;
struct Fail {
std::string filename;
std::string error;
};
std::vector<Fail> failing_files;
int num_lines = 0;
void add(const CompileResult& other) {
failing_files.insert(failing_files.end(), other.failing_files.begin(),
other.failing_files.end());
num_lines += other.num_lines;
if (!other.ok) {
ok = false;
}
}
};
CompileResult compile(Decompiler& dc,
const std::vector<DecompilerFile>& refs,
const OfflineTestConfig& config,
const std::string& game_name) {
CompileResult result;
Compiler compiler(game_name_to_version(game_name));
compiler.run_front_end_on_file({"decompiler", "config", game_name_to_all_types[game_name]});
compiler.run_front_end_on_file(
{"test", "decompiler", "reference", game_name, "decompiler-macros.gc"});
Timer timer;
int total_lines = 0;
for (const auto& file : refs) {
if (config.skip_compile_files.count(file.name_in_dgo)) {
@@ -239,15 +267,13 @@ bool compile(Decompiler& dc,
total_lines += line_count(src);
compiler.run_full_compiler_on_string_no_save(src, file.name_in_dgo);
} catch (const std::exception& e) {
fmt::print("Compiler exception: {}\n", e.what());
return false;
result.ok = false;
result.failing_files.push_back({file.name_in_dgo, e.what()});
}
}
auto time = timer.getSeconds();
fmt::print("Total Lines Compiled: {}. Lines/second: {:.1f}\n", total_lines,
(float)total_lines / time);
return true;
result.num_lines = total_lines;
return result;
}
std::vector<DecompilerArtFile> find_art_files(const std::string& game_name,
@@ -411,10 +437,26 @@ std::optional<OfflineTestConfig> parse_config(const std::string_view& game_name)
/// @brief A simple struct to contain the reason for failure from a thread
struct OfflineTestResult {
int exit_code;
int exit_code = 0;
std::string reason;
OfflineTestResult(int _exit_code, std::string _reason) : exit_code(_exit_code), reason(_reason) {}
float time_spent_compiling = 0;
float time_spent_decompiling = 0;
float total_time = 0;
CompareResult compare;
CompileResult compile;
void add(const OfflineTestResult& other) {
if (other.exit_code) {
exit_code = other.exit_code;
}
time_spent_compiling += other.time_spent_compiling;
time_spent_decompiling += other.time_spent_decompiling;
total_time += other.total_time;
compare.add(other.compare);
compile.add(other.compile);
}
};
int main(int argc, char* argv[]) {
@@ -477,7 +519,7 @@ int main(int argc, char* argv[]) {
}
// First, prepare our batches of files to be processed
std::vector<std::vector<DecompilerFile>> work_groups = {};
for (int i = 0; i < num_threads; i++) {
for (size_t i = 0; i < num_threads; i++) {
work_groups.push_back({});
}
int total_added = 0;
@@ -493,75 +535,67 @@ int main(int argc, char* argv[]) {
decompiler::init_opcode_info();
for (const auto& work_group : work_groups) {
threads.push_back(std::async(std::launch::async, [&]() {
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
lg::info("Setting up decompiler and loading files...");
OfflineTestResult result;
Timer total_timer;
Timer decompiler_timer;
auto decompiler = setup_decompiler(work_group, art_files, fs::path(iso_data_path),
config.value(), game_name);
lg::info("Took {}ms", std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - begin)
.count());
begin = std::chrono::steady_clock::now();
lg::info("Disassembling files...");
disassemble(decompiler);
lg::info("Took {}ms", std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - begin)
.count());
begin = std::chrono::steady_clock::now();
lg::info("Decompiling...");
decompile(decompiler, config.value());
// It's about 100ms per file to decompile on average
// meaning that when we have all 900 files, a full offline test will take 1.5 minutes
lg::info("Took {}ms", std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - begin)
.count());
result.time_spent_decompiling = decompiler_timer.getSeconds();
begin = std::chrono::steady_clock::now();
lg::info("Comparing...");
auto compare_result = compare(decompiler, work_group, dump_current_output);
lg::info("Took {}ms", std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - begin)
.count());
lg::info("Compared {} lines. {}/{} files passed.", compare_result.total_lines,
compare_result.ok_files, compare_result.total_files);
lg::info("Dump? {}\n", dump_current_output);
if (!compare_result.failing_files.empty()) {
lg::error("Failing files:");
for (auto& f : compare_result.failing_files) {
lg::error("- {}", f);
}
lg::error("Comparison failed.");
// No point continuing to compile if the comparison has failed
return OfflineTestResult(1, "Comparison Failed");
result.compare = compare(decompiler, work_group, dump_current_output);
if (!result.compare.total_pass) {
result.exit_code = 1;
}
begin = std::chrono::steady_clock::now();
lg::info("Compiling...");
bool compile_result = compile(decompiler, work_group, config.value(), game_name);
// Compiling on the otherhand, is around 20ms per file
lg::info("Took {}ms", std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - begin)
.count());
if (!compile_result) {
return OfflineTestResult(1, "Compilation Failed");
Timer compile_timer;
result.compile = compile(decompiler, work_group, config.value(), game_name);
result.time_spent_compiling = compile_timer.getSeconds();
if (!result.compile.ok) {
result.exit_code = 1;
}
result.total_time = total_timer.getSeconds();
return OfflineTestResult(0, "");
return result;
}));
}
// Fail fast over any thread tripping over
// summarize results:
OfflineTestResult total;
for (auto& thread : threads) {
auto ret = thread.get();
if (ret.exit_code != 0) {
lg::error(ret.reason);
return ret.exit_code;
total.add(ret);
}
if (!total.compare.total_pass) {
lg::error("Comparison failed.");
for (auto& f : total.compare.failing_files) {
fmt::print("{}\n", f.diff);
}
lg::error("Failing files:");
for (auto& f : total.compare.failing_files) {
lg::error("- {}", f.filename);
}
}
return 0;
if (!total.compile.ok) {
for (auto& f : total.compile.failing_files) {
lg::error("{}", f.filename);
fmt::print("{}\n", f.error);
}
}
fmt::print("Compiled {} lines in {:.3f}s ({} lines/sec)\n", total.compile.num_lines,
total.time_spent_compiling,
(int)(total.compile.num_lines / total.time_spent_compiling));
if (!total.exit_code) {
fmt::print("pass!\n");
}
return total.exit_code;
}