mirror of
https://github.com/open-goal/jak-project
synced 2026-07-09 14:55:51 -04:00
[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:
@@ -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;
|
||||
|
||||
@@ -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); }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user