From a83d7ab9120be1a2aa920525851ae2695114cdbf Mon Sep 17 00:00:00 2001 From: water Date: Wed, 2 Jun 2021 20:21:54 -0400 Subject: [PATCH 1/4] support taking the address of variables --- common/goos/Reader.cpp | 3 + decompiler/IR2/FormExpressionAnalysis.cpp | 1 - .../analysis/anonymous_function_def.cpp | 1 + docs/markdown/progress-notes/changelog.md | 4 +- goalc/compiler/Compiler.cpp | 6 ++ goalc/compiler/Env.h | 2 + goalc/compiler/IR.cpp | 42 +++++++++ goalc/compiler/IR.h | 24 ++--- goalc/compiler/Val.h | 3 + goalc/compiler/compilation/Type.cpp | 33 ++++++- goalc/regalloc/Allocator.cpp | 88 ++++++++----------- goalc/regalloc/allocate.h | 7 +- .../with_game/test-addr-of-var.gc | 14 +++ test/goalc/test_with_game.cpp | 7 ++ 14 files changed, 166 insertions(+), 69 deletions(-) create mode 100644 test/goalc/source_templates/with_game/test-addr-of-var.gc diff --git a/common/goos/Reader.cpp b/common/goos/Reader.cpp index bdc358d43a..1bc27248c7 100644 --- a/common/goos/Reader.cpp +++ b/common/goos/Reader.cpp @@ -382,6 +382,9 @@ Object Reader::read_list(TextStream& ts, bool expect_close_paren) { while (kv != reader_macros.end()) { // build a stack of reader macros to apply to this form. reader_macro_string_stack.push_back(kv->second); + if (!ts.text_remains()) { + throw_reader_error(ts, "Something must follow a reader macro", 0); + } tok = get_next_token(ts); kv = reader_macros.find(tok.text); } diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index 805e0c554d..eee417f76e 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -1081,7 +1081,6 @@ void SimpleExpressionElement::update_from_stack_logor_or_logand(const Env& env, // this is an ugly hack to make (logand (lognot (enum-bitfield xxxx)) work. // I have only one example for this, so I think this unlikely to work in all cases. if (m_expr.get_arg(1).is_var()) { - auto arg1_type = env.get_variable_type(m_expr.get_arg(1).var(), true); auto eti = env.dts->ts.try_enum_lookup(arg1_type.base_type()); if (eti) { auto integer = get_goal_integer_constant(args.at(0), env); diff --git a/decompiler/analysis/anonymous_function_def.cpp b/decompiler/analysis/anonymous_function_def.cpp index fbd91d4270..2489072a87 100644 --- a/decompiler/analysis/anonymous_function_def.cpp +++ b/decompiler/analysis/anonymous_function_def.cpp @@ -29,6 +29,7 @@ int insert_anonymous_functions(Form* top_level_form, f->clear(); f->push_back(pool.alloc_element(result)); + replaced++; } } } diff --git a/docs/markdown/progress-notes/changelog.md b/docs/markdown/progress-notes/changelog.md index a51872a095..90944cfd23 100644 --- a/docs/markdown/progress-notes/changelog.md +++ b/docs/markdown/progress-notes/changelog.md @@ -158,4 +158,6 @@ ## V0.8 New Calling Convention for 128-bit - 128-bit values may now be used in function arguments and return values. - Fixed a bug where reader errors in `goal-lib.gc` or any error in `goos-lib.gs` would cause a crash -- Fixed a bug where `''a` or similar repeated reader macros would generate a reader error. \ No newline at end of file +- Fixed a bug where `''a` or similar repeated reader macros would generate a reader error. +- Fixed a bug where reader macros without a following form would trigger an assert. +- It is now possible to take the address of a lexical variable. The variable will be spilled to the stack automatically. \ No newline at end of file diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index 726e26275d..ff1eccc968 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -198,6 +198,12 @@ void Compiler::color_object_file(FileEnv* env) { input.debug_instruction_names.push_back(i->print()); } + for (auto& reg_val : f->reg_vals()) { + if (reg_val->forced_on_stack()) { + input.force_on_stack_regs.insert(reg_val->ireg().id); + } + } + input.max_vars = f->max_vars(); input.constraints = f->constraints(); input.stack_slots_for_stack_vars = f->stack_slots_used_for_stack_vars(); diff --git a/goalc/compiler/Env.h b/goalc/compiler/Env.h index b04097370f..afc34a490a 100644 --- a/goalc/compiler/Env.h +++ b/goalc/compiler/Env.h @@ -206,6 +206,8 @@ class FunctionEnv : public DeclareEnv { return (T*)m_envs.back().get(); } + const std::vector>& reg_vals() const { return m_iregs; } + int segment = -1; std::string method_of_type_name = "#f"; bool is_asm_func = false; diff --git a/goalc/compiler/IR.cpp b/goalc/compiler/IR.cpp index 38f52ff029..42035c26f6 100644 --- a/goalc/compiler/IR.cpp +++ b/goalc/compiler/IR.cpp @@ -34,6 +34,19 @@ Register get_reg(const RegVal* rv, const AllocationResult& allocs, emitter::IR_R } } +int get_stack_offset(const RegVal* rv, const AllocationResult& allocs) { + if (rv->rlet_constraint().has_value()) { + // should be impossible. Can't take the address of an inline assembly form register. + assert(false); + } else { + assert(rv->forced_on_stack()); + auto& ass = allocs.ass_as_ranges.at(rv->ireg().id); + auto stack_slot = ass.assignment.at(0).stack_slot; + assert(stack_slot >= 0); + return stack_slot * 8; + } +} + Register get_no_color_reg(const RegVal* rv) { if (!rv->rlet_constraint().has_value()) { throw std::runtime_error( @@ -48,6 +61,7 @@ Register get_reg_asm(const RegVal* rv, bool use_coloring) { return use_coloring ? get_reg(rv, allocs, irec) : get_no_color_reg(rv); } + void load_constant(u64 value, emitter::ObjectGenerator* gen, emitter::IR_Record irec, @@ -429,6 +443,34 @@ void IR_FunctionCall::do_codegen(emitter::ObjectGenerator* gen, // todo, can we do a sub to undo the modification to the register? does that actually work? } +///////////////////// +// RegValAddr +///////////////////// + +IR_RegValAddr::IR_RegValAddr(const RegVal* dest, const RegVal* src) : m_dest(dest), m_src(src) {} + +std::string IR_RegValAddr::print() { + return fmt::format("mov {}, &{}", m_dest->print(), m_src->print()); +} + +RegAllocInstr IR_RegValAddr::to_rai() { + RegAllocInstr rai; + rai.write.push_back(m_dest->ireg()); + // we don't actually read the value in m_src, so we don't need to add it here. + return rai; +} + +void IR_RegValAddr::do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) { + int stack_offset = get_stack_offset(m_src, allocs); + auto dst = get_reg(m_dest, allocs, irec); + // x86 pointer to var + gen->add_instr(IGen::lea_reg_plus_off(dst, RSP, stack_offset), irec); + // x86 -> GOAL pointer + gen->add_instr(IGen::sub_gpr64_gpr64(dst, emitter::gRegInfo.get_offset_reg()), irec); +} + ///////////////////// // StaticVarAddr ///////////////////// diff --git a/goalc/compiler/IR.h b/goalc/compiler/IR.h index d6340e8ce4..eaa99a7937 100644 --- a/goalc/compiler/IR.h +++ b/goalc/compiler/IR.h @@ -1,8 +1,5 @@ #pragma once -#ifndef JAK_IR_H -#define JAK_IR_H - #include #include "CodeGenerator.h" #include "goalc/regalloc/allocate.h" @@ -24,12 +21,6 @@ class IR { virtual ~IR() = default; }; -// class IR_Set : public IR { -// public: -// std::string print() override; -// RegAllocInstr to_rai() override; -//}; - class IR_Return : public IR { public: IR_Return(const RegVal* return_reg, const RegVal* value, emitter::Register ret_reg); @@ -140,6 +131,20 @@ class IR_FunctionCall : public IR { std::optional m_ret_reg; }; +class IR_RegValAddr : public IR { + public: + IR_RegValAddr(const RegVal* dest, const RegVal* src); + std::string print() override; + RegAllocInstr to_rai() override; + void do_codegen(emitter::ObjectGenerator* gen, + const AllocationResult& allocs, + emitter::IR_Record irec) override; + + protected: + const RegVal* m_dest = nullptr; + const RegVal* m_src = nullptr; +}; + class IR_StaticVarAddr : public IR { public: IR_StaticVarAddr(const RegVal* dest, const StaticObject* src); @@ -675,4 +680,3 @@ class IR_SqrtVF : public IR_Asm { const RegVal* m_dst = nullptr; const RegVal* m_src = nullptr; }; -#endif // JAK_IR_H diff --git a/goalc/compiler/Val.h b/goalc/compiler/Val.h index 69f9d486dc..d1076b7c4e 100644 --- a/goalc/compiler/Val.h +++ b/goalc/compiler/Val.h @@ -78,10 +78,13 @@ class RegVal : public Val { RegVal* to_xmm128(Env* fe) override; void set_rlet_constraint(emitter::Register reg); const std::optional& rlet_constraint() const; + void force_on_stack() { m_on_stack = true; } + bool forced_on_stack() const { return m_on_stack; } protected: IRegister m_ireg; std::optional m_rlet_constraint = std::nullopt; + bool m_on_stack = false; // this should be spilled onto the stack always }; /*! diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index d59d91f522..d711b944f3 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -712,6 +712,16 @@ Val* Compiler::compile_deref(const goos::Object& form, const goos::Object& _rest return result; } +TypeSpec coerce_to_stack_spill_type(const TypeSpec& in) { + if (in == TypeSpec("int")) { + return TypeSpec("int64"); + } else if (in == TypeSpec("uint")) { + return TypeSpec("uint64"); + } else { + return in; + } +} + /*! * Compile the (& x) form. */ @@ -720,10 +730,27 @@ Val* Compiler::compile_addr_of(const goos::Object& form, const goos::Object& res va_check(form, args, {{}}, {}); auto loc = compile_error_guard(args.unnamed.at(0), env); auto as_mem_deref = dynamic_cast(loc); - if (!as_mem_deref) { - throw_compiler_error(form, "Cannot take the address of {}.", loc->print()); + if (as_mem_deref) { + return as_mem_deref->base; } - return as_mem_deref->base; + + // for now, we will only allow taking the address for something that's already in a register, + // like a function parameter or a lexical variable (declared in a let). + // This avoids weird things like taking the address of a constant or some other weird temporary - + // we could spill it to the stack and let you do this, but most of the time it's probably not what + // you wanted to do. + auto as_reg = dynamic_cast(loc); + if (as_reg) { + // so we can take the address + as_reg->force_on_stack(); + auto result = + env->make_gpr(m_ts.make_pointer_typespec(coerce_to_stack_spill_type(as_reg->type()))); + env->emit_ir(result, as_reg); + return result; + } + + throw_compiler_error(form, "Cannot take the address of {}.", loc->print()); + return nullptr; } /*! diff --git a/goalc/regalloc/Allocator.cpp b/goalc/regalloc/Allocator.cpp index 4ab6101cbd..9c20a5ae3f 100644 --- a/goalc/regalloc/Allocator.cpp +++ b/goalc/regalloc/Allocator.cpp @@ -692,7 +692,7 @@ bool try_spill_coloring(int var, RegAllocCache* cache, const AllocationInput& in StackOp::Op bonus; bonus.reg_class = cache->iregs.at(var).reg_class; - // we may have a constaint in here + // we may have a constraint in here auto& current_assignment = lr.assignment.at(instr - lr.min); auto& op = in.instructions.at(instr); @@ -718,6 +718,7 @@ bool try_spill_coloring(int var, RegAllocCache* cache, const AllocationInput& in // flag it as spilled, but currently in a GPR. current_assignment.spilled = true; + current_assignment.stack_slot = get_stack_slot_for_var(var, cache); bonus.reg = current_assignment.reg; } else { // not assigned. @@ -775,21 +776,6 @@ bool try_spill_coloring(int var, RegAllocCache* cache, const AllocationInput& in if (spill_assignment.reg == -1) { printf("SPILLING FAILED BECAUSE WE COULDN'T FIND A TEMP REGISTER!\n"); assert(false); - // std::vector can_try_spilling; - // for(uint32_t other_spill = 0; other_spill < was_colored.size(); other_spill++) - // { - // if((int)other_spill != var && was_colored.at(other_spill)) { - // LOG("TRY SPILL %d?\n", other_spill); - // if(try_spill_coloring(other_spill)) { - // LOG("SPILL OK.\n"); - // if(try_spill_coloring(var)) { - // return true; - // } - // } else { - // LOG("SPILL %d failed.\n", other_spill); - // } - // } - // } return false; } @@ -826,50 +812,54 @@ bool do_allocation_for_var(int var, RegAllocCache* cache, const AllocationInput& in, int debug_trace) { - // first, let's see if there's a hint... - auto& lr = cache->live_ranges.at(var); + bool can_be_in_register = in.force_on_stack_regs.find(var) == in.force_on_stack_regs.end(); bool colored = false; - if (lr.best_hint.is_assigned()) { - colored = try_assignment_for_var(var, lr.best_hint, cache, in, debug_trace); - if (debug_trace >= 2) { - printf("var %d reg %s ? %d\n", var, lr.best_hint.to_string().c_str(), colored); + + if (can_be_in_register) { + // first, let's see if there's a hint... + auto& lr = cache->live_ranges.at(var); + if (lr.best_hint.is_assigned()) { + colored = try_assignment_for_var(var, lr.best_hint, cache, in, debug_trace); + if (debug_trace >= 2) { + printf("var %d reg %s ? %d\n", var, lr.best_hint.to_string().c_str(), colored); + } } - } - auto reg_order = get_default_alloc_order_for_var(var, cache, false); - auto& all_reg_order = get_default_alloc_order_for_var(var, cache, true); + auto reg_order = get_default_alloc_order_for_var(var, cache, false); + auto& all_reg_order = get_default_alloc_order_for_var(var, cache, true); - // todo, try other regs.. - if (!colored && move_eliminator) { - auto& first_instr = in.instructions.at(lr.min); - auto& last_instr = in.instructions.at(lr.max); + // todo, try other regs.. + if (!colored && move_eliminator) { + auto& first_instr = in.instructions.at(lr.min); + auto& last_instr = in.instructions.at(lr.max); - if (!colored && last_instr.is_move) { - auto& possible_coloring = cache->live_ranges.at(last_instr.write.front().id).get(lr.max); - if (possible_coloring.is_assigned() && in_vec(all_reg_order, possible_coloring.reg)) { - colored = try_assignment_for_var(var, possible_coloring, cache, in, debug_trace); + if (!colored && last_instr.is_move) { + auto& possible_coloring = cache->live_ranges.at(last_instr.write.front().id).get(lr.max); + if (possible_coloring.is_assigned() && in_vec(all_reg_order, possible_coloring.reg)) { + colored = try_assignment_for_var(var, possible_coloring, cache, in, debug_trace); + } } - } - if (!colored && first_instr.is_move) { - auto& possible_coloring = cache->live_ranges.at(first_instr.read.front().id).get(lr.min); - if (possible_coloring.is_assigned() && in_vec(all_reg_order, possible_coloring.reg)) { - colored = try_assignment_for_var(var, possible_coloring, cache, in, debug_trace); + if (!colored && first_instr.is_move) { + auto& possible_coloring = cache->live_ranges.at(first_instr.read.front().id).get(lr.min); + if (possible_coloring.is_assigned() && in_vec(all_reg_order, possible_coloring.reg)) { + colored = try_assignment_for_var(var, possible_coloring, cache, in, debug_trace); + } } } - } - // auto reg_order = get_default_reg_alloc_order(); + // auto reg_order = get_default_reg_alloc_order(); - for (auto reg : reg_order) { - if (colored) - break; - Assignment ass; - ass.kind = Assignment::Kind::REGISTER; - ass.reg = reg; - colored = try_assignment_for_var(var, ass, cache, in, debug_trace); - if (debug_trace >= 1) { - printf("var %d reg %s ? %d\n", var, ass.to_string().c_str(), colored); + for (auto reg : reg_order) { + if (colored) + break; + Assignment ass; + ass.kind = Assignment::Kind::REGISTER; + ass.reg = reg; + colored = try_assignment_for_var(var, ass, cache, in, debug_trace); + if (debug_trace >= 1) { + printf("var %d reg %s ? %d\n", var, ass.to_string().c_str(), colored); + } } } diff --git a/goalc/regalloc/allocate.h b/goalc/regalloc/allocate.h index a637cd8e69..2376e50282 100644 --- a/goalc/regalloc/allocate.h +++ b/goalc/regalloc/allocate.h @@ -9,10 +9,8 @@ * allocate_registers algorithm. */ -#ifndef JAK_ALLOCATE_H -#define JAK_ALLOCATE_H - #include +#include #include "goalc/emitter/Register.h" #include "IRegister.h" #include "allocate_common.h" @@ -94,6 +92,7 @@ struct AllocationResult { struct AllocationInput { std::vector instructions; // all instructions in the function std::vector constraints; // all register constraints + std::unordered_set force_on_stack_regs; // registers which must be on the stack int max_vars = -1; // maximum register id. std::vector debug_instruction_names; // optional, for debug prints int stack_slots_for_stack_vars = 0; @@ -117,5 +116,3 @@ struct AllocationInput { }; AllocationResult allocate_registers(const AllocationInput& input); - -#endif // JAK_ALLOCATE_H diff --git a/test/goalc/source_templates/with_game/test-addr-of-var.gc b/test/goalc/source_templates/with_game/test-addr-of-var.gc new file mode 100644 index 0000000000..86700e67da --- /dev/null +++ b/test/goalc/source_templates/with_game/test-addr-of-var.gc @@ -0,0 +1,14 @@ +(defun set-pointer-to-val ((x (pointer int64)) (y int)) + (set! (-> x) (-> (& y))) + ) + +(defun address-of-var-test-func ((x int) (y int)) + (let ((z y)) + (format #t "x: ~d y: ~d z: ~d~%" x y z) + (set-pointer-to-val (& x) 13) + (set-pointer-to-val (& z) 15) + (format #t "x: ~d y: ~d z: ~d~%" x y z) + ) + ) + +(address-of-var-test-func 25 35) diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index c970e13324..311aa430c6 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -746,6 +746,13 @@ TEST_F(WithGameTests, Function128) { "0\n"}); } +TEST_F(WithGameTests, AddrOfVar) { + runner.run_static_test(env, testCategory, "test-addr-of-var.gc", + {"x: 25 y: 35 z: 35\n" + "x: 13 y: 35 z: 15\n" + "0\n"}); +} + TEST(TypeConsistency, TypeConsistency) { Compiler compiler; compiler.enable_throw_on_redefines(); From 9ef5c9c088ac45327a60b2936a14ba7827ee3203 Mon Sep 17 00:00:00 2001 From: water Date: Wed, 2 Jun 2021 21:21:05 -0400 Subject: [PATCH 2/4] partially working stack variables --- decompiler/IR2/AtomicOpForm.cpp | 21 ++++++++++---- decompiler/IR2/AtomicOpTypeAnalysis.cpp | 28 +++++++++++++------ decompiler/IR2/Env.cpp | 8 +++--- decompiler/IR2/Env.h | 12 ++++---- decompiler/IR2/Form.cpp | 17 ++++++----- decompiler/IR2/Form.h | 6 ++-- decompiler/IR2/FormExpressionAnalysis.cpp | 12 ++++---- decompiler/IR2/IR2_common.h | 1 + decompiler/ObjectFile/ObjectFileDB_IR2.cpp | 3 +- decompiler/analysis/insert_lets.cpp | 2 +- decompiler/config.cpp | 9 +++--- decompiler/config.h | 7 +++-- decompiler/config/all-types.gc | 3 +- decompiler/config/jak1_ntsc_black_label.jsonc | 2 +- .../jak1_ntsc_black_label/label_types.jsonc | 4 +++ ...tack_vars.jsonc => stack_structures.jsonc} | 0 .../jak1_ntsc_black_label/type_casts.jsonc | 4 +++ .../jak1_ntsc_black_label/var_names.jsonc | 7 +++++ decompiler/util/config_parsers.cpp | 18 ++++++------ decompiler/util/config_parsers.h | 2 +- test/decompiler/FormRegressionTest.cpp | 21 +++++++------- test/decompiler/FormRegressionTest.h | 14 +++++----- test/decompiler/test_FormExpressionBuild2.cpp | 22 +++++++-------- 23 files changed, 136 insertions(+), 87 deletions(-) rename decompiler/config/jak1_ntsc_black_label/{stack_vars.jsonc => stack_structures.jsonc} (100%) diff --git a/decompiler/IR2/AtomicOpForm.cpp b/decompiler/IR2/AtomicOpForm.cpp index 43e6b4fe6b..810b4dc250 100644 --- a/decompiler/IR2/AtomicOpForm.cpp +++ b/decompiler/IR2/AtomicOpForm.cpp @@ -43,15 +43,26 @@ FormElement* SetVarOp::get_as_form(FormPool& pool, const Env& env) const { if (env.has_type_analysis() && m_src.args() == 2 && m_src.get_arg(1).is_int() && m_src.get_arg(0).is_var() && m_src.kind() == SimpleExpression::Kind::ADD) { if (m_src.get_arg(0).var().reg() == Register(Reg::GPR, Reg::SP)) { - // get a stack variable. - for (auto& var : env.stack_var_hints()) { - if (var.hint.stack_offset == m_src.get_arg(1).get_int()) { + // get a stack structure. + int offset = m_src.get_arg(1).get_int(); + for (auto& structure : env.stack_structure_hints()) { + if (structure.hint.stack_offset == offset) { // match! return pool.alloc_element( - m_dst, pool.alloc_single_element_form(nullptr, var), true, - var.ref_type); + m_dst, pool.alloc_single_element_form(nullptr, structure), + true, structure.ref_type); } } + // get a stack variable + auto& spill_map = env.stack_spills().map(); + if (spill_map.find(offset) != spill_map.end()) { + return pool.alloc_element( + m_dst, + pool.alloc_single_element_form( + nullptr, GenericOperator::make_fixed(FixedOperatorKind::ADDRESS_OF), + pool.alloc_single_element_form(nullptr, -1, offset, false)), + true, env.stack_slot_entries.at(offset).typespec); + } } else { // access a field auto arg0_type = env.get_types_before_op(m_my_idx).get(m_src.get_arg(0).var().reg()); diff --git a/decompiler/IR2/AtomicOpTypeAnalysis.cpp b/decompiler/IR2/AtomicOpTypeAnalysis.cpp index c57ccecbfe..4fcb993df6 100644 --- a/decompiler/IR2/AtomicOpTypeAnalysis.cpp +++ b/decompiler/IR2/AtomicOpTypeAnalysis.cpp @@ -234,17 +234,21 @@ namespace { */ TP_Type get_stack_type_at_constant_offset(int offset, const Env& env, - const DecompilerTypeSystem& dts) { + const DecompilerTypeSystem& dts, + const TypeState& types) { (void)dts; - for (auto& var : env.stack_var_hints()) { - if (offset < var.hint.stack_offset || offset >= (var.hint.stack_offset + var.size)) { + + // first look for a stack structure + for (auto& structure : env.stack_structure_hints()) { + if (offset < structure.hint.stack_offset || + offset >= (structure.hint.stack_offset + structure.size)) { continue; // reject, it isn't in this variable } - if (offset == var.hint.stack_offset) { + if (offset == structure.hint.stack_offset) { // special case just getting the variable - if (var.hint.container_type == StackVariableHint::ContainerType::NONE) { - return TP_Type::make_from_ts(coerce_to_reg_type(var.ref_type)); + if (structure.hint.container_type == StackStructureHint::ContainerType::NONE) { + return TP_Type::make_from_ts(coerce_to_reg_type(structure.ref_type)); } } @@ -266,7 +270,15 @@ TP_Type get_stack_type_at_constant_offset(int offset, */ // if we fail, keep trying others. This lets us have overlays in stack memory. } - throw std::runtime_error(fmt::format("Failed to find a stack variable at offset {}", offset)); + + // look for a stack variable + auto kv = types.spill_slots.find(offset); + if (kv != types.spill_slots.end()) { + return kv->second; + } + + throw std::runtime_error( + fmt::format("Failed to find a stack variable or structure at offset {}", offset)); } } // namespace @@ -373,7 +385,7 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, // get stack address: if (m_args[0].is_var() && m_args[0].var().reg() == Register(Reg::GPR, Reg::SP) && m_args[1].is_int()) { - return get_stack_type_at_constant_offset(m_args[1].get_int(), env, dts); + return get_stack_type_at_constant_offset(m_args[1].get_int(), env, dts, input); } if (arg0_type.is_product_with(4) && tc(dts, TypeSpec("type"), arg1_type)) { diff --git a/decompiler/IR2/Env.cpp b/decompiler/IR2/Env.cpp index 9c0b202d74..e2bfed5417 100644 --- a/decompiler/IR2/Env.cpp +++ b/decompiler/IR2/Env.cpp @@ -499,16 +499,16 @@ void Env::disable_use(const RegisterAccess& access) { * Set the stack hints. This must be done before type analysis. * This actually parses the types, so it should be done after the dts is set up. */ -void Env::set_stack_var_hints(const std::vector& hints) { +void Env::set_stack_structure_hints(const std::vector& hints) { for (auto& hint : hints) { - StackVarEntry entry; + StackStructureEntry entry; entry.hint = hint; // parse the type spec. TypeSpec base_typespec = dts->parse_type_spec(hint.element_type); auto type_info = dts->ts.lookup_type(base_typespec); switch (hint.container_type) { - case StackVariableHint::ContainerType::NONE: + case StackStructureHint::ContainerType::NONE: // just a plain object on the stack. if (!type_info->is_reference()) { throw std::runtime_error( @@ -531,7 +531,7 @@ void Env::set_stack_var_hints(const std::vector& hints) { assert(false); } - m_stack_vars.push_back(entry); + m_stack_structures.push_back(entry); } } diff --git a/decompiler/IR2/Env.h b/decompiler/IR2/Env.h index cacc15b518..1cfbdd5fa3 100644 --- a/decompiler/IR2/Env.h +++ b/decompiler/IR2/Env.h @@ -22,8 +22,8 @@ struct VariableWithCast { std::optional cast; }; -struct StackVarEntry { - StackVariableHint hint; +struct StackStructureEntry { + StackStructureHint hint; TypeSpec ref_type; // the actual type of the address. int size = -1; }; @@ -158,8 +158,10 @@ class Env { m_label_types = types; } - void set_stack_var_hints(const std::vector& hints); - const std::vector& stack_var_hints() const { return m_stack_vars; } + void set_stack_structure_hints(const std::vector& hints); + const std::vector& stack_structure_hints() const { + return m_stack_structures; + } const UseDefInfo& get_use_def_info(const RegisterAccess& ra) const; void disable_use(const RegisterAccess& access); @@ -211,7 +213,7 @@ class Env { bool m_allow_sloppy_pair_typing = false; std::unordered_map> m_typecasts; - std::vector m_stack_vars; + std::vector m_stack_structures; std::unordered_map m_var_remap; std::unordered_map m_var_retype; std::unordered_map m_label_types; diff --git a/decompiler/IR2/Form.cpp b/decompiler/IR2/Form.cpp index 495ad908a2..917fb351ee 100644 --- a/decompiler/IR2/Form.cpp +++ b/decompiler/IR2/Form.cpp @@ -1538,6 +1538,8 @@ std::string fixed_operator_to_string(FixedOperatorKind kind) { return "make-u128"; case FixedOperatorKind::SYMBOL_TO_STRING: return "symbol->string"; + case FixedOperatorKind::ADDRESS_OF: + return "&"; default: assert(false); return ""; @@ -2265,11 +2267,12 @@ void LambdaDefinitionElement::get_modified_regs(RegSet&) const {} // StackVarDefElement ///////////////////////////// -StackVarDefElement::StackVarDefElement(const StackVarEntry& entry) : m_entry(entry) {} +StackStructureDefElement::StackStructureDefElement(const StackStructureEntry& entry) + : m_entry(entry) {} -goos::Object StackVarDefElement::to_form_internal(const Env&) const { +goos::Object StackStructureDefElement::to_form_internal(const Env&) const { switch (m_entry.hint.container_type) { - case StackVariableHint::ContainerType::NONE: + case StackStructureHint::ContainerType::NONE: return pretty_print::build_list( fmt::format("new 'stack-no-clear '{}", m_entry.ref_type.print())); default: @@ -2277,15 +2280,15 @@ goos::Object StackVarDefElement::to_form_internal(const Env&) const { } } -void StackVarDefElement::apply_form(const std::function&) {} +void StackStructureDefElement::apply_form(const std::function&) {} -void StackVarDefElement::apply(const std::function& f) { +void StackStructureDefElement::apply(const std::function& f) { f(this); } -void StackVarDefElement::collect_vars(RegAccessSet&, bool) const {} +void StackStructureDefElement::collect_vars(RegAccessSet&, bool) const {} -void StackVarDefElement::get_modified_regs(RegSet&) const {} +void StackStructureDefElement::get_modified_regs(RegSet&) const {} //////////////////////////////// // VectorFloatLoadStoreElement diff --git a/decompiler/IR2/Form.h b/decompiler/IR2/Form.h index 1d0ebb936d..cdb7c742ca 100644 --- a/decompiler/IR2/Form.h +++ b/decompiler/IR2/Form.h @@ -1318,9 +1318,9 @@ class LambdaDefinitionElement : public FormElement { goos::Object m_def; }; -class StackVarDefElement : public FormElement { +class StackStructureDefElement : public FormElement { public: - StackVarDefElement(const StackVarEntry& entry); + StackStructureDefElement(const StackStructureEntry& entry); goos::Object to_form_internal(const Env& env) const override; void apply(const std::function& f) override; void apply_form(const std::function& f) override; @@ -1334,7 +1334,7 @@ class StackVarDefElement : public FormElement { const TypeSpec& type() const { return m_entry.ref_type; } private: - StackVarEntry m_entry; + StackStructureEntry m_entry; }; class VectorFloatLoadStoreElement : public FormElement { diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index eee417f76e..87ecf1a5f4 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -2024,7 +2024,7 @@ void FunctionCallElement::update_from_stack(const Env& env, } if (got_stack_new) { - auto new_op = first_cast->source()->try_as_element(); + auto new_op = first_cast->source()->try_as_element(); if (!new_op || new_op->type().base_type() != type_source_form->to_string(env)) { got_stack_new = false; } @@ -3566,11 +3566,11 @@ void ConstantFloatElement::update_from_stack(const Env&, result->push_back(this); } -void StackVarDefElement::update_from_stack(const Env&, - FormPool&, - FormStack&, - std::vector* result, - bool) { +void StackStructureDefElement::update_from_stack(const Env&, + FormPool&, + FormStack&, + std::vector* result, + bool) { mark_popped(); result->push_back(this); } diff --git a/decompiler/IR2/IR2_common.h b/decompiler/IR2/IR2_common.h index 5a27eb7ad2..db5662b03d 100644 --- a/decompiler/IR2/IR2_common.h +++ b/decompiler/IR2/IR2_common.h @@ -145,6 +145,7 @@ enum class FixedOperatorKind { NONE, PCPYLD, SYMBOL_TO_STRING, + ADDRESS_OF, INVALID }; diff --git a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp index 9fe12a0f0a..475795b71e 100644 --- a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp +++ b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp @@ -373,7 +373,8 @@ void ObjectFileDB::ir2_type_analysis_pass(const Config& config) { config.hacks.reject_cond_to_value.end()) { func.ir2.env.aggressively_reject_cond_to_value_rewrite = true; } - func.ir2.env.set_stack_var_hints(try_lookup(config.stack_var_hints_by_function, func_name)); + func.ir2.env.set_stack_structure_hints( + try_lookup(config.stack_structure_hints_by_function, func_name)); if (run_type_analysis_ir2(ts, dts, func)) { successful_functions++; func.ir2.env.types_succeeded = true; diff --git a/decompiler/analysis/insert_lets.cpp b/decompiler/analysis/insert_lets.cpp index 3807f60ff5..57e051f8be 100644 --- a/decompiler/analysis/insert_lets.cpp +++ b/decompiler/analysis/insert_lets.cpp @@ -276,7 +276,7 @@ FormElement* fix_up_vector_inline_zero(LetElement* in, const Env& env, FormPool& } Form* src = in->entries().at(0).src; - auto src_as_stackvar = src->try_as_element(); + auto src_as_stackvar = src->try_as_element(); if (!src_as_stackvar) { return nullptr; } diff --git a/decompiler/config.cpp b/decompiler/config.cpp index 844abed4d7..73876fad19 100644 --- a/decompiler/config.cpp +++ b/decompiler/config.cpp @@ -126,11 +126,12 @@ Config read_config_file(const std::string& path_to_config_file) { } } - auto stack_vars_json = read_json_file_from_config(cfg, "stack_vars_file"); - for (auto& kv : stack_vars_json.items()) { + auto stack_structures_json = read_json_file_from_config(cfg, "stack_structures_file"); + for (auto& kv : stack_structures_json.items()) { auto& func_name = kv.key(); - auto& stack_vars = kv.value(); - config.stack_var_hints_by_function[func_name] = parse_stack_var_hints(stack_vars); + auto& stack_structures = kv.value(); + config.stack_structure_hints_by_function[func_name] = + parse_stack_structure_hints(stack_structures); } auto hacks_json = read_json_file_from_config(cfg, "hacks_file"); diff --git a/decompiler/config.h b/decompiler/config.h index 2094a72151..58b7589357 100644 --- a/decompiler/config.h +++ b/decompiler/config.h @@ -28,9 +28,9 @@ struct LocalVarOverride { }; /*! - * Information about a variable pointing to some data on the stack. + * Information about a structure on the stack. */ -struct StackVariableHint { +struct StackStructureHint { std::string element_type; // type of the thing stored // todo - is boxed array on the stack supported? enum class ContainerType { @@ -90,7 +90,8 @@ struct Config { std::unordered_map> function_var_overrides; std::unordered_map> label_types; - std::unordered_map> stack_var_hints_by_function; + std::unordered_map> + stack_structure_hints_by_function; DecompileHacks hacks; }; diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index 1f26d90106..f38178e090 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -8887,6 +8887,7 @@ :method-count-assert 22 :size-assert #x20 :flag-assert #x1600000020 + :final ;; field extra is a basic loaded with a signed load (:methods (new (symbol type int int) _type_ 0) @@ -13043,7 +13044,7 @@ ;; - Symbols -(define-extern entity-actor-count function) +(define-extern entity-actor-count (function res-lump symbol int)) (define-extern entity-actor-lookup function) (define-extern entity-by-name function) (define-extern entity-by-aid function) diff --git a/decompiler/config/jak1_ntsc_black_label.jsonc b/decompiler/config/jak1_ntsc_black_label.jsonc index 604c0122ea..64fea87854 100644 --- a/decompiler/config/jak1_ntsc_black_label.jsonc +++ b/decompiler/config/jak1_ntsc_black_label.jsonc @@ -67,7 +67,7 @@ "anonymous_function_types_file": "decompiler/config/jak1_ntsc_black_label/anonymous_function_types.jsonc", "var_names_file": "decompiler/config/jak1_ntsc_black_label/var_names.jsonc", "label_types_file": "decompiler/config/jak1_ntsc_black_label/label_types.jsonc", - "stack_vars_file": "decompiler/config/jak1_ntsc_black_label/stack_vars.jsonc", + "stack_structures_file": "decompiler/config/jak1_ntsc_black_label/stack_structures.jsonc", "hacks_file": "decompiler/config/jak1_ntsc_black_label/hacks.jsonc", "inputs_file": "decompiler/config/jak1_ntsc_black_label/inputs.jsonc", diff --git a/decompiler/config/jak1_ntsc_black_label/label_types.jsonc b/decompiler/config/jak1_ntsc_black_label/label_types.jsonc index e0eb4ebe27..5be584a176 100644 --- a/decompiler/config/jak1_ntsc_black_label/label_types.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/label_types.jsonc @@ -460,5 +460,9 @@ "smush-control-h": [ ["L20", "float", true] + ], + + "actor-link-h": [ + ["L79", "float", true] ] } diff --git a/decompiler/config/jak1_ntsc_black_label/stack_vars.jsonc b/decompiler/config/jak1_ntsc_black_label/stack_structures.jsonc similarity index 100% rename from decompiler/config/jak1_ntsc_black_label/stack_vars.jsonc rename to decompiler/config/jak1_ntsc_black_label/stack_structures.jsonc diff --git a/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc b/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc index 38d8f9eb0a..6d611fe5ae 100644 --- a/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc @@ -459,5 +459,9 @@ "ripple-find-height": [ [[22, 72], "s4", "mei-ripple"] + ], + + "entity-actor-count": [ + [12, "v1", "res-tag"] // TODO this should be removed. ] } diff --git a/decompiler/config/jak1_ntsc_black_label/var_names.jsonc b/decompiler/config/jak1_ntsc_black_label/var_names.jsonc index f1d66b6d10..3ac8d2147a 100644 --- a/decompiler/config/jak1_ntsc_black_label/var_names.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/var_names.jsonc @@ -1753,5 +1753,12 @@ "v1-14": "hi-tag-idx-out", "t1-0": "most-recent-invalid-time-idx" } + }, + + "entity-actor-count": { + "args": ["res", "name"], + "vars":{ + "sv-16":"tag" + } } } diff --git a/decompiler/util/config_parsers.cpp b/decompiler/util/config_parsers.cpp index 4c40bcef48..7d53496e2f 100644 --- a/decompiler/util/config_parsers.cpp +++ b/decompiler/util/config_parsers.cpp @@ -2,25 +2,25 @@ #include "common/util/json_util.h" namespace decompiler { -std::vector parse_stack_var_hints(const nlohmann::json& json) { - std::vector result; - for (auto& stack_var : json) { - StackVariableHint hint; - hint.stack_offset = stack_var.at(0).get(); - auto& type_info = stack_var.at(1); +std::vector parse_stack_structure_hints(const nlohmann::json& json) { + std::vector result; + for (auto& stack_structure : json) { + StackStructureHint hint; + hint.stack_offset = stack_structure.at(0).get(); + auto& type_info = stack_structure.at(1); if (type_info.is_array()) { auto container_type = type_info.at(0).get(); if (container_type == "array") { - hint.container_type = StackVariableHint::ContainerType::ARRAY; + hint.container_type = StackStructureHint::ContainerType::ARRAY; } else if (container_type == "inline-array") { - hint.container_type = StackVariableHint::ContainerType::INLINE_ARRAY; + hint.container_type = StackStructureHint::ContainerType::INLINE_ARRAY; } else { throw std::runtime_error("Container type is invalid: " + container_type); } hint.element_type = type_info.at(1).get(); hint.container_size = type_info.at(2).get(); } else if (type_info.is_string()) { - hint.container_type = StackVariableHint::ContainerType::NONE; + hint.container_type = StackStructureHint::ContainerType::NONE; hint.container_size = -1; hint.element_type = type_info.get(); } else { diff --git a/decompiler/util/config_parsers.h b/decompiler/util/config_parsers.h index ac32473011..19d71aa08e 100644 --- a/decompiler/util/config_parsers.h +++ b/decompiler/util/config_parsers.h @@ -4,7 +4,7 @@ #include "third-party/json.hpp" namespace decompiler { -std::vector parse_stack_var_hints(const nlohmann::json& json); +std::vector parse_stack_structure_hints(const nlohmann::json& json); std::unordered_map> parse_cast_hints( const nlohmann::json& casts); } // namespace decompiler diff --git a/test/decompiler/FormRegressionTest.cpp b/test/decompiler/FormRegressionTest.cpp index 1875914afc..d2dba22108 100644 --- a/test/decompiler/FormRegressionTest.cpp +++ b/test/decompiler/FormRegressionTest.cpp @@ -178,9 +178,10 @@ std::unique_ptr FormRegressionTest::make_function( test->func.ir2.env.set_sloppy_pair_typing(); } - if (!settings.stack_var_json.empty()) { - auto stack_hints = parse_stack_var_hints(nlohmann::json::parse(settings.stack_var_json)); - test->func.ir2.env.set_stack_var_hints(stack_hints); + if (!settings.stack_structure_json.empty()) { + auto stack_hints = + parse_stack_structure_hints(nlohmann::json::parse(settings.stack_structure_json)); + test->func.ir2.env.set_stack_structure_hints(stack_hints); } // analyze types @@ -293,15 +294,15 @@ void FormRegressionTest::test_final_function( EXPECT_TRUE(expected_form == actual_form); } -void FormRegressionTest::test_with_stack_vars(const std::string& code, - const std::string& type, - const std::string& expected, - const std::string& stack_map_json, - const std::string& cast_json, - const std::string& var_map_json) { +void FormRegressionTest::test_with_stack_structures(const std::string& code, + const std::string& type, + const std::string& expected, + const std::string& stack_map_json, + const std::string& cast_json, + const std::string& var_map_json) { TestSettings settings; settings.do_expressions = true; - settings.stack_var_json = stack_map_json; + settings.stack_structure_json = stack_map_json; settings.var_map_json = var_map_json; settings.casts_json = cast_json; test(code, type, expected, settings); diff --git a/test/decompiler/FormRegressionTest.h b/test/decompiler/FormRegressionTest.h index dd26f6d2da..f7be888dbd 100644 --- a/test/decompiler/FormRegressionTest.h +++ b/test/decompiler/FormRegressionTest.h @@ -18,7 +18,7 @@ struct TestSettings { std::vector> strings; std::string casts_json; std::string var_map_json; - std::string stack_var_json; + std::string stack_structure_json; }; class FormRegressionTest : public ::testing::Test { @@ -90,10 +90,10 @@ class FormRegressionTest : public ::testing::Test { test(code, type, expected, settings); } - void test_with_stack_vars(const std::string& code, - const std::string& type, - const std::string& expected, - const std::string& stack_map_json, - const std::string& cast_json = "", - const std::string& var_map_json = ""); + void test_with_stack_structures(const std::string& code, + const std::string& type, + const std::string& expected, + const std::string& stack_map_json, + const std::string& cast_json = "", + const std::string& var_map_json = ""); }; \ No newline at end of file diff --git a/test/decompiler/test_FormExpressionBuild2.cpp b/test/decompiler/test_FormExpressionBuild2.cpp index ada0a6a21e..6cb9c14c02 100644 --- a/test/decompiler/test_FormExpressionBuild2.cpp +++ b/test/decompiler/test_FormExpressionBuild2.cpp @@ -49,10 +49,10 @@ TEST_F(FormRegressionTest, MatrixPMult) { " )\n" " arg0\n" " )"; - test_with_stack_vars(func, type, expected, - "[\n" - " [16, \"matrix\"]\n" - " ]"); + test_with_stack_structures(func, type, expected, + "[\n" + " [16, \"matrix\"]\n" + " ]"); } // TODO- this should also work without the cast, but be uglier. @@ -95,11 +95,11 @@ TEST_F(FormRegressionTest, VectorXQuaternionWithCast) { " )\n" " arg0\n" " )"; - test_with_stack_vars(func, type, expected, - "[\n" - " [16, \"matrix\"]\n" - " ]", - "[[10, \"v1\", \"(pointer uint128)\"]]"); + test_with_stack_structures(func, type, expected, + "[\n" + " [16, \"matrix\"]\n" + " ]", + "[[10, \"v1\", \"(pointer uint128)\"]]"); } TEST_F(FormRegressionTest, EliminateFloatDeadSet) { @@ -226,7 +226,7 @@ TEST_F(FormRegressionTest, EliminateFloatDeadSet) { " )\n" " )\n" " )"; - test_with_stack_vars(func, type, expected, "[]"); + test_with_stack_structures(func, type, expected, "[]"); } TEST_F(FormRegressionTest, IterateProcessTree) { @@ -317,7 +317,7 @@ TEST_F(FormRegressionTest, IterateProcessTree) { " )\n" " s4-0\n" " )"; - test_with_stack_vars(func, type, expected, "[]"); + test_with_stack_structures(func, type, expected, "[]"); } TEST_F(FormRegressionTest, InspectVifStatBitfield) { From 26b8207fee584109fa27159f8d9046c4c79f4726 Mon Sep 17 00:00:00 2001 From: water Date: Fri, 4 Jun 2021 13:13:42 -0400 Subject: [PATCH 3/4] implement type cast stuff --- decompiler/IR2/AtomicOpTypeAnalysis.cpp | 2 +- decompiler/IR2/Env.h | 14 +++++++-- decompiler/ObjectFile/ObjectFileDB_IR2.cpp | 8 +++-- decompiler/analysis/type_analysis.cpp | 24 ++++++++++----- decompiler/config.cpp | 29 ++++++++++++++----- decompiler/config.h | 13 +++++++-- .../jak1_ntsc_black_label/type_casts.jsonc | 2 +- decompiler/util/config_parsers.cpp | 6 ++-- decompiler/util/config_parsers.h | 2 +- goal_src/engine/data/res-h.gc | 3 ++ test/decompiler/FormRegressionTest.h | 2 +- 11 files changed, 76 insertions(+), 29 deletions(-) diff --git a/decompiler/IR2/AtomicOpTypeAnalysis.cpp b/decompiler/IR2/AtomicOpTypeAnalysis.cpp index 4fcb993df6..c94ebb29d3 100644 --- a/decompiler/IR2/AtomicOpTypeAnalysis.cpp +++ b/decompiler/IR2/AtomicOpTypeAnalysis.cpp @@ -274,7 +274,7 @@ TP_Type get_stack_type_at_constant_offset(int offset, // look for a stack variable auto kv = types.spill_slots.find(offset); if (kv != types.spill_slots.end()) { - return kv->second; + return TP_Type::make_from_ts(TypeSpec("pointer", {kv->second.typespec()})); } throw std::runtime_error( diff --git a/decompiler/IR2/Env.h b/decompiler/IR2/Env.h index 1cfbdd5fa3..321905f0a5 100644 --- a/decompiler/IR2/Env.h +++ b/decompiler/IR2/Env.h @@ -131,11 +131,18 @@ class Env { bool allow_sloppy_pair_typing() const { return m_allow_sloppy_pair_typing; } void set_sloppy_pair_typing() { m_allow_sloppy_pair_typing = true; } - void set_type_casts(const std::unordered_map>& casts) { + void set_type_casts(const std::unordered_map>& casts) { m_typecasts = casts; } + const std::unordered_map>& casts() const { + return m_typecasts; + } + + void set_stack_casts(const std::unordered_map& casts) { + m_stack_typecasts = casts; + } - const std::unordered_map>& casts() const { return m_typecasts; } + const std::unordered_map& stack_casts() const { return m_stack_typecasts; } void set_remap_for_function(int nargs); void set_remap_for_method(int nargs); @@ -212,7 +219,8 @@ class Env { bool m_allow_sloppy_pair_typing = false; - std::unordered_map> m_typecasts; + std::unordered_map> m_typecasts; + std::unordered_map m_stack_typecasts; std::vector m_stack_structures; std::unordered_map m_var_remap; std::unordered_map m_var_retype; diff --git a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp index 475795b71e..9630c46793 100644 --- a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp +++ b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp @@ -360,10 +360,14 @@ void ObjectFileDB::ir2_type_analysis_pass(const Config& config) { attempted_functions++; // try type analysis here. auto func_name = func.guessed_name.to_string(); - auto casts = try_lookup(config.type_casts_by_function_by_atomic_op_idx, func_name); + auto register_casts = + try_lookup(config.register_type_casts_by_function_by_atomic_op_idx, func_name); + func.ir2.env.set_type_casts(register_casts); auto label_types = try_lookup(config.label_types, data.to_unique_name()); - func.ir2.env.set_type_casts(casts); func.ir2.env.set_label_types(label_types); + auto stack_casts = + try_lookup(config.stack_type_casts_by_function_by_stack_offset, func_name); + func.ir2.env.set_stack_casts(stack_casts); if (config.hacks.pair_functions_by_name.find(func_name) != config.hacks.pair_functions_by_name.end()) { func.ir2.env.set_sloppy_pair_typing(); diff --git a/decompiler/analysis/type_analysis.cpp b/decompiler/analysis/type_analysis.cpp index ed9820580b..d84ee2ede7 100644 --- a/decompiler/analysis/type_analysis.cpp +++ b/decompiler/analysis/type_analysis.cpp @@ -28,7 +28,7 @@ TypeState construct_initial_typestate(const TypeSpec& f_ts, const Env& env) { * Modify the the given type state based on the given casts. */ void modify_input_types_for_casts( - const std::vector& casts, + const std::vector& casts, TypeState* state, std::unordered_map* changed_types, DecompilerTypeSystem& dts) { @@ -56,7 +56,8 @@ void modify_input_types_for_casts( void try_modify_input_types_for_casts( int idx, - const std::unordered_map>& casts, + const std::unordered_map>& casts, + const std::unordered_map& stack_casts, TypeState* state, std::unordered_map* changed_types, DecompilerTypeSystem& dts) { @@ -65,6 +66,15 @@ void try_modify_input_types_for_casts( // fmt::print("at idx {}, casting:\n", idx); modify_input_types_for_casts(kv->second, state, changed_types, dts); } + + for (const auto& [offset, cast] : stack_casts) { + auto stack_kv = state->spill_slots.find(offset); + if (stack_kv == state->spill_slots.end()) { + throw std::runtime_error( + fmt::format("Got a stack cast at offset {}, but didn't find a variable there.", offset)); + } + state->spill_slots[offset] = TP_Type::make_from_ts(dts.parse_type_spec(cast.type_name)); + } } } // namespace @@ -106,8 +116,8 @@ bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, F for (int op_id = aop->block_id_to_first_atomic_op.at(block_id); op_id < aop->block_id_to_end_atomic_op.at(block_id); op_id++) { std::unordered_map restore_cast_types; - try_modify_input_types_for_casts(op_id, func.ir2.env.casts(), init_types, - &restore_cast_types, dts); + try_modify_input_types_for_casts(op_id, func.ir2.env.casts(), func.ir2.env.stack_casts(), + init_types, &restore_cast_types, dts); auto& op = aop->ops.at(op_id); @@ -159,11 +169,11 @@ bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, F for (int op_id = aop->block_id_to_first_atomic_op.at(block_id); op_id < aop->block_id_to_end_atomic_op.at(block_id); op_id++) { if (op_id == aop->block_id_to_first_atomic_op.at(block_id)) { - try_modify_input_types_for_casts(op_id, func.ir2.env.casts(), + try_modify_input_types_for_casts(op_id, func.ir2.env.casts(), func.ir2.env.stack_casts(), &block_init_types.at(block_id), nullptr, dts); } else { - try_modify_input_types_for_casts(op_id, func.ir2.env.casts(), &op_types.at(op_id - 1), - nullptr, dts); + try_modify_input_types_for_casts(op_id, func.ir2.env.casts(), func.ir2.env.stack_casts(), + &op_types.at(op_id - 1), nullptr, dts); } } } diff --git a/decompiler/config.cpp b/decompiler/config.cpp index 73876fad19..5e1c055777 100644 --- a/decompiler/config.cpp +++ b/decompiler/config.cpp @@ -62,13 +62,28 @@ Config read_config_file(const std::string& path_to_config_file) { auto& function_name = kv.key(); auto& casts = kv.value(); for (auto& cast : casts) { - auto idx_range = parse_json_optional_integer_range(cast.at(0)); - for (auto idx : idx_range) { - TypeCast type_cast; - type_cast.atomic_op_idx = idx; - type_cast.reg = Register(cast.at(1)); - type_cast.type_name = cast.at(2).get(); - config.type_casts_by_function_by_atomic_op_idx[function_name][idx].push_back(type_cast); + if (cast.at(0).is_string()) { + auto cast_name = cast.at(0).get(); + if (cast_name == "_stack_") { + // it's a stack var cast + StackTypeCast stack_cast; + stack_cast.stack_offset = cast.at(1).get(); + stack_cast.type_name = cast.at(2).get(); + config.stack_type_casts_by_function_by_stack_offset[function_name] + [stack_cast.stack_offset] = stack_cast; + } else { + throw std::runtime_error(fmt::format("Unknown cast type: {}", cast_name)); + } + } else { + auto idx_range = parse_json_optional_integer_range(cast.at(0)); + for (auto idx : idx_range) { + RegisterTypeCast type_cast; + type_cast.atomic_op_idx = idx; + type_cast.reg = Register(cast.at(1)); + type_cast.type_name = cast.at(2).get(); + config.register_type_casts_by_function_by_atomic_op_idx[function_name][idx].push_back( + type_cast); + } } } } diff --git a/decompiler/config.h b/decompiler/config.h index 58b7589357..2603b78dcc 100644 --- a/decompiler/config.h +++ b/decompiler/config.h @@ -8,12 +8,17 @@ #include "decompiler/Disasm/Register.h" namespace decompiler { -struct TypeCast { +struct RegisterTypeCast { int atomic_op_idx = -1; Register reg; std::string type_name; }; +struct StackTypeCast { + int stack_offset = -1; + std::string type_name; +}; + struct LabelType { std::string type_name; bool is_const = false; @@ -82,8 +87,10 @@ struct Config { bool generate_symbol_definition_map = false; std::unordered_set allowed_objects; - std::unordered_map>> - type_casts_by_function_by_atomic_op_idx; + std::unordered_map>> + register_type_casts_by_function_by_atomic_op_idx; + std::unordered_map> + stack_type_casts_by_function_by_stack_offset; std::unordered_map> anon_function_types_by_obj_by_id; std::unordered_map> function_arg_names; diff --git a/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc b/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc index 6d611fe5ae..3310815dad 100644 --- a/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc @@ -462,6 +462,6 @@ ], "entity-actor-count": [ - [12, "v1", "res-tag"] // TODO this should be removed. + ["_stack_", 16, "res-tag"] ] } diff --git a/decompiler/util/config_parsers.cpp b/decompiler/util/config_parsers.cpp index 7d53496e2f..c5349074d7 100644 --- a/decompiler/util/config_parsers.cpp +++ b/decompiler/util/config_parsers.cpp @@ -31,14 +31,14 @@ std::vector parse_stack_structure_hints(const nlohmann::json return result; } -std::unordered_map> parse_cast_hints( +std::unordered_map> parse_cast_hints( const nlohmann::json& casts) { - std::unordered_map> out; + std::unordered_map> out; for (auto& cast : casts) { auto idx_range = parse_json_optional_integer_range(cast.at(0)); for (auto idx : idx_range) { - TypeCast type_cast; + RegisterTypeCast type_cast; type_cast.atomic_op_idx = idx; type_cast.reg = Register(cast.at(1)); type_cast.type_name = cast.at(2).get(); diff --git a/decompiler/util/config_parsers.h b/decompiler/util/config_parsers.h index 19d71aa08e..49fb2a9ca2 100644 --- a/decompiler/util/config_parsers.h +++ b/decompiler/util/config_parsers.h @@ -5,6 +5,6 @@ namespace decompiler { std::vector parse_stack_structure_hints(const nlohmann::json& json); -std::unordered_map> parse_cast_hints( +std::unordered_map> parse_cast_hints( const nlohmann::json& casts); } // namespace decompiler diff --git a/goal_src/engine/data/res-h.gc b/goal_src/engine/data/res-h.gc index 8acfbe2f71..0121454721 100644 --- a/goal_src/engine/data/res-h.gc +++ b/goal_src/engine/data/res-h.gc @@ -34,6 +34,9 @@ (extra basic :offset-assert 24) (tag (pointer res-tag) :offset-assert 28) ) + ;; res-lump is a basic, but never uses virtual method calls, so it is considered "final" + :final + :method-count-assert 22 :size-assert #x20 :flag-assert #x1600000020 diff --git a/test/decompiler/FormRegressionTest.h b/test/decompiler/FormRegressionTest.h index f7be888dbd..e428749623 100644 --- a/test/decompiler/FormRegressionTest.h +++ b/test/decompiler/FormRegressionTest.h @@ -8,7 +8,7 @@ #include "decompiler/ObjectFile/LinkedObjectFile.h" namespace decompiler { -struct TypeCast; +struct RegisterTypeCast; } struct TestSettings { From 93faaeb5bcc0e3a9d558d9c07be7a8c9f9c58698 Mon Sep 17 00:00:00 2001 From: water Date: Fri, 4 Jun 2021 13:28:21 -0400 Subject: [PATCH 4/4] remove final --- decompiler/config/all-types.gc | 1 - goal_src/engine/data/res-h.gc | 2 -- 2 files changed, 3 deletions(-) diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index cac13583e8..4722895300 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -8886,7 +8886,6 @@ :method-count-assert 22 :size-assert #x20 :flag-assert #x1600000020 - :final ;; field extra is a basic loaded with a signed load (:methods (new (symbol type int int) _type_ 0) diff --git a/goal_src/engine/data/res-h.gc b/goal_src/engine/data/res-h.gc index 0121454721..58fe645c02 100644 --- a/goal_src/engine/data/res-h.gc +++ b/goal_src/engine/data/res-h.gc @@ -34,8 +34,6 @@ (extra basic :offset-assert 24) (tag (pointer res-tag) :offset-assert 28) ) - ;; res-lump is a basic, but never uses virtual method calls, so it is considered "final" - :final :method-count-assert 22 :size-assert #x20