diff --git a/common/type_system/TypeFieldLookup.cpp b/common/type_system/TypeFieldLookup.cpp index cff9b76b34..a7f3c996bc 100644 --- a/common/type_system/TypeFieldLookup.cpp +++ b/common/type_system/TypeFieldLookup.cpp @@ -439,11 +439,9 @@ bool TypeSystem::try_reverse_lookup_other(const FieldReverseLookupInput& input, if (field.is_inline()) { expected_offset_into_field = lookup_type(field.type())->get_offset(); } - if (offset_into_field == expected_offset_into_field && !input.deref.has_value()) { - // get the inline field. - if (input.stride) { - continue; - } + if (offset_into_field == expected_offset_into_field && !input.deref.has_value() && + !input.stride) { + // get the inline field exactly path->push_back(token); *result_type = field_deref.type; *addr_of = false; diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index 2947997c7d..be78c10215 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -1200,185 +1200,6 @@ TypeSpec coerce_to_reg_type(const TypeSpec& in) { return in; } -bool debug_reverse_deref = false; - -/*! - * Todo: - * - I suspect inlined basics will be off by 4-bytes, depending on where the basic field starts. - * - Inline array is not yet implemented. - */ -bool TypeSystem::reverse_deref(const ReverseDerefInputInfo& input, - std::vector* path, - bool* addr_of, - TypeSpec* result_type) const { - if (!input.mem_deref) { - assert(input.load_size == 0); - } - if (debug_reverse_deref) { - fmt::print("Reverse Deref Type {} Offset {} Deref {} Load Size {} Signed {}\n", - input.input_type.print(), input.offset, input.mem_deref, input.load_size, - input.sign_extend); - } - - if (input.offset == 0 && !input.mem_deref) { - // base case, we are here! - *addr_of = false; - return true; - } - - auto base_input_type = input.input_type.base_type(); - if (base_input_type == "pointer") { - auto di = get_deref_info(input.input_type); - int closest_index = input.offset / di.stride; - int offset_into_elt = input.offset - (closest_index * di.stride); - auto base_type = di.result_type; - - ReverseDerefInfo::DerefToken token; - token.kind = ReverseDerefInfo::DerefToken::INDEX; - token.index = closest_index; - - if (!di.mem_deref) { - return false; - } - assert(di.mem_deref); - if (offset_into_elt == 0) { - if (input.mem_deref) { - // todo - this is a hack to let quadword loads always succeed because we don't support it - // correctly at this point. - if (input.load_size == 16 || - (di.load_size == input.load_size && di.sign_extend == input.sign_extend)) { - path->push_back(token); - *addr_of = false; - *result_type = base_type; - return true; - } else { - if (debug_reverse_deref) { - fmt::print("load size {} {}, sext {} {}, input {}\n", di.load_size, input.load_size, - di.sign_extend, input.sign_extend, input.input_type.print().c_str()); - } - return false; - } - } else { - path->push_back(token); - *addr_of = true; - *result_type = make_pointer_typespec(base_type); - return true; - } - } else { - return false; - } - - } else if (base_input_type == "inline-array") { - if (debug_reverse_deref) { - fmt::print("Got inline-array case\n"); - } - // todo - return false; - } else { - auto type_info = lookup_type(input.input_type); - auto structure_type = dynamic_cast(type_info); - if (!structure_type) { - if (debug_reverse_deref) { - fmt::print("Failed structure type check\n"); - } - return false; - } - auto corrected_offset = input.offset + type_info->get_offset(); - for (auto& field : structure_type->fields()) { - auto field_deref = lookup_field_info(type_info->get_name(), field.name()); - if (debug_reverse_deref) { - fmt::print("Offset is {}, {} try field {} {} which is {}, {}\n", corrected_offset, - corrected_offset + input.load_size, field.name(), field_deref.type.print(), - field.offset(), field.offset() + get_size_in_type(field)); - } - if (corrected_offset >= field.offset() && (corrected_offset + std::max(1, input.load_size) <= - field.offset() + get_size_in_type(field) || - field.is_dynamic())) { - if (debug_reverse_deref) { - fmt::print(" ok, using field {}\n", field.name()); - } - // we are somewhere in this field! - int offset_into_field = corrected_offset - field.offset(); - - ReverseDerefInfo::DerefToken token; - token.kind = ReverseDerefInfo::DerefToken::FIELD; - token.name = field.name(); - - if (offset_into_field == 0) { - if (field_deref.needs_deref) { - if (input.mem_deref) { - // perfect match to a field requiring a deref, which we have. - TypeSpec loc_type = make_pointer_typespec(field_deref.type); - auto di = get_deref_info(loc_type); - if (di.load_size == input.load_size && di.sign_extend == input.sign_extend) { - path->push_back(token); - *addr_of = false; - *result_type = field_deref.type; - return true; - } else { - return false; - } - } else { - // we didn't deref the field, so it's an addr of - path->push_back(token); - *addr_of = true; - *result_type = make_pointer_typespec(field_deref.type); - return true; - } - } else { - // field doesn't need deref to access. - if (input.mem_deref) { - // but we did deref... - // let's look deeper in this field. - path->push_back(token); - ReverseDerefInputInfo r_input = input; - r_input.offset = offset_into_field; - r_input.input_type = field_deref.type; - return reverse_deref(r_input, path, addr_of, result_type); - } else { - // and we didn't deref. - path->push_back(token); - *result_type = field_deref.type; - *addr_of = false; - return true; - } - } - } else { - // we are partially inside of a field here. - if (field_deref.needs_deref) { - // hmm.. shouldn't be possible - if (debug_reverse_deref) { - fmt::print("Failed extra deref case: {}.\n", field.print()); - } - return false; - } else { - // we should try again. - path->push_back(token); - ReverseDerefInputInfo r_input = input; - r_input.offset = offset_into_field; - r_input.input_type = field_deref.type; - return reverse_deref(r_input, path, addr_of, result_type); - } - } - } - } - } - - if (debug_reverse_deref) { - fmt::print("Failed (reached end)\n"); - } - return false; -} - -ReverseDerefInfo TypeSystem::get_reverse_deref_info(const ReverseDerefInputInfo& input) const { - if (!input.mem_deref) { - assert(input.load_size == 0); - } - ReverseDerefInfo result; - result.success = reverse_deref(input, &result.deref_path, &result.addr_of, &result.result_type); - return result; -} - /*! * Is the given type a bitfield type? */ diff --git a/common/type_system/TypeSystem.h b/common/type_system/TypeSystem.h index 6a316a9f7a..ed4e4b960f 100644 --- a/common/type_system/TypeSystem.h +++ b/common/type_system/TypeSystem.h @@ -54,38 +54,6 @@ struct DerefInfo { TypeSpec result_type; }; -struct ReverseDerefInfo { - struct DerefToken { - enum Kind { INDEX, FIELD } kind; - std::string name; - int index; - std::string print() const { - switch (kind) { - case INDEX: - return std::to_string(index); - case FIELD: - return name; - default: - assert(false); - } - } - }; - - TypeSpec result_type; - std::vector deref_path; - bool success = false; - bool addr_of = false; -}; - -struct ReverseDerefInputInfo { - int offset = -1; - bool mem_deref = false; - RegClass reg = RegClass::INVALID; - int load_size = -1; - bool sign_extend = false; - TypeSpec input_type; -}; - /*! * A description of a dereference (size + sign extend) */ @@ -129,7 +97,6 @@ class TypeSystem { std::string get_runtime_type(const TypeSpec& ts); DerefInfo get_deref_info(const TypeSpec& ts) const; - ReverseDerefInfo get_reverse_deref_info(const ReverseDerefInputInfo& input) const; FieldReverseLookupOutput reverse_field_lookup(const FieldReverseLookupInput& input) const; bool fully_defined_type_exists(const std::string& name) const; @@ -216,10 +183,6 @@ class TypeSystem { TypeSpec lowest_common_ancestor(const std::vector& types) const; private: - bool reverse_deref(const ReverseDerefInputInfo& input, - std::vector* path, - bool* addr_of, - TypeSpec* result_type) const; bool try_reverse_lookup(const FieldReverseLookupInput& input, std::vector* path, bool* addr_of, diff --git a/decompiler/IR2/AtomicOp.cpp b/decompiler/IR2/AtomicOp.cpp index d186287702..d513743ebe 100644 --- a/decompiler/IR2/AtomicOp.cpp +++ b/decompiler/IR2/AtomicOp.cpp @@ -698,6 +698,20 @@ IR2_Condition::Kind get_condition_opposite(IR2_Condition::Kind kind) { } } +bool condition_uses_float(IR2_Condition::Kind kind) { + switch (kind) { + case IR2_Condition::Kind::FLOAT_LESS_THAN: + case IR2_Condition::Kind::FLOAT_LEQ: + case IR2_Condition::Kind::FLOAT_NOT_EQUAL: + case IR2_Condition::Kind::FLOAT_EQUAL: + case IR2_Condition::Kind::FLOAT_GEQ: + case IR2_Condition::Kind::FLOAT_GREATER_THAN: + return true; + default: + return false; + } +} + IR2_Condition::IR2_Condition(Kind kind) : m_kind(kind) { assert(get_condition_num_args(m_kind) == 0); } diff --git a/decompiler/IR2/AtomicOp.h b/decompiler/IR2/AtomicOp.h index 00c4a54e1a..6a43e21de4 100644 --- a/decompiler/IR2/AtomicOp.h +++ b/decompiler/IR2/AtomicOp.h @@ -377,6 +377,7 @@ class IR2_Condition { std::string get_condition_kind_name(IR2_Condition::Kind kind); int get_condition_num_args(IR2_Condition::Kind kind); IR2_Condition::Kind get_condition_opposite(IR2_Condition::Kind kind); +bool condition_uses_float(IR2_Condition::Kind kind); /*! * Set a variable to a GOAL boolean, based off of a condition. diff --git a/decompiler/IR2/AtomicOpForm.cpp b/decompiler/IR2/AtomicOpForm.cpp index 8522d1c713..decbdbf49d 100644 --- a/decompiler/IR2/AtomicOpForm.cpp +++ b/decompiler/IR2/AtomicOpForm.cpp @@ -17,20 +17,6 @@ RegClass get_reg_kind(const Register& r) { assert(false); } } - -DerefToken to_token(FieldReverseLookupOutput::Token in) { - switch (in.kind) { - case FieldReverseLookupOutput::Token::Kind::FIELD: - return DerefToken::make_field_name(in.name); - case FieldReverseLookupOutput::Token::Kind::CONSTANT_IDX: - return DerefToken::make_int_constant(in.idx); - case FieldReverseLookupOutput::Token::Kind::VAR_IDX: - return DerefToken::make_expr_placeholder(); - default: - // temp - throw std::runtime_error("Cannot convert rd lookup token to deref token"); - } -} } // namespace ConditionElement* BranchOp::get_condition_as_form(FormPool& pool, const Env& env) const { diff --git a/decompiler/IR2/AtomicOpTypeAnalysis.cpp b/decompiler/IR2/AtomicOpTypeAnalysis.cpp index d8d229de21..9eec09bba5 100644 --- a/decompiler/IR2/AtomicOpTypeAnalysis.cpp +++ b/decompiler/IR2/AtomicOpTypeAnalysis.cpp @@ -135,6 +135,10 @@ TP_Type SimpleExpression::get_type(const TypeState& input, return m_args[0].get_type(input, env, dts); case Kind::GPR_TO_FPR: { const auto& in_type = input.get(get_arg(0).var().reg()); + if (in_type.is_integer_constant(0)) { + // GOAL is smart enough to use binary 0b0 as floating point 0. + return TP_Type::make_from_ts("float"); + } return in_type; } case Kind::FPR_TO_GPR: @@ -146,7 +150,11 @@ TP_Type SimpleExpression::get_type(const TypeState& input, case Kind::ABS_S: case Kind::NEG_S: case Kind::INT_TO_FLOAT: + case Kind::MIN_S: + case Kind::MAX_S: return TP_Type::make_from_ts("float"); + case Kind::FLOAT_TO_INT: + return TP_Type::make_from_ts("int"); case Kind::ADD: case Kind::SUB: case Kind::MUL_SIGNED: @@ -249,6 +257,11 @@ TP_Type SimpleExpression::get_type_int2(const TypeState& input, // no need to track the type because we don't know the method index anyway. return TP_Type::make_partial_dyanmic_vtable_access(); } + + if (arg1_type.is_integer_constant() && is_int_or_uint(dts, arg0_type)) { + return TP_Type::make_from_integer_constant_plus_var(arg1_type.get_integer_constant(), + arg0_type.typespec()); + } break; default: @@ -418,6 +431,12 @@ TypeState SetVarOp::propagate_types_internal(const TypeState& input, const Env& env, DecompilerTypeSystem& dts) { TypeState result = input; + if (m_dst.reg().get_kind() == Reg::FPR && m_src.is_identity() && m_src.get_arg(0).is_int() && + m_src.get_arg(0).get_int() == 0) { + // mtc fX, r0 should be a float type. GOAL was smart enough to do this. + result.get(m_dst.reg()) = TP_Type::make_from_ts("float"); + return result; + } result.get(m_dst.reg()) = m_src.get_type(input, env, dts); return result; } diff --git a/decompiler/IR2/Env.cpp b/decompiler/IR2/Env.cpp index 362e3be47c..d920ab0713 100644 --- a/decompiler/IR2/Env.cpp +++ b/decompiler/IR2/Env.cpp @@ -16,6 +16,7 @@ void Env::set_remap_for_function(int nargs) { var_name.push_back('0'); m_var_remap[var_name] = ("arg" + std::to_string(i)); } + m_var_remap["s6-0"] = "pp"; } void Env::set_remap_for_new_method(int nargs) { @@ -29,6 +30,7 @@ void Env::set_remap_for_new_method(int nargs) { var_name.push_back('0'); m_var_remap[var_name] = ("arg" + std::to_string(i - 2)); } + m_var_remap["s6-0"] = "pp"; } void Env::set_remap_for_method(int nargs) { @@ -41,6 +43,7 @@ void Env::set_remap_for_method(int nargs) { var_name.push_back('0'); m_var_remap[var_name] = ("arg" + std::to_string(i - 1)); } + m_var_remap["s6-0"] = "pp"; } void Env::map_args_from_config(const std::vector& args_names, diff --git a/decompiler/IR2/Form.cpp b/decompiler/IR2/Form.cpp index dd39927200..786d70cb09 100644 --- a/decompiler/IR2/Form.cpp +++ b/decompiler/IR2/Form.cpp @@ -3,6 +3,7 @@ #include #include "decompiler/ObjectFile/LinkedObjectFile.h" #include "common/goos/PrettyPrinter.h" +#include "common/type_system/TypeSystem.h" namespace decompiler { @@ -1133,6 +1134,12 @@ std::string fixed_operator_to_string(FixedOperatorKind kind) { return "min"; case FixedOperatorKind::MAX: return "max"; + case FixedOperatorKind::FABS: + return "fabs"; + case FixedOperatorKind::FMIN: + return "fmin"; + case FixedOperatorKind::FMAX: + return "fmax"; case FixedOperatorKind::LOGAND: return "logand"; case FixedOperatorKind::LOGIOR: @@ -1173,6 +1180,8 @@ std::string fixed_operator_to_string(FixedOperatorKind kind) { return "="; case FixedOperatorKind::NEQ: return "!="; + case FixedOperatorKind::METHOD_OF_OBJECT: + return "method-of-object"; default: assert(false); } @@ -1390,6 +1399,20 @@ void DerefToken::get_modified_regs(RegSet& regs) const { } } +DerefToken to_token(const FieldReverseLookupOutput::Token& in) { + switch (in.kind) { + case FieldReverseLookupOutput::Token::Kind::FIELD: + return DerefToken::make_field_name(in.name); + case FieldReverseLookupOutput::Token::Kind::CONSTANT_IDX: + return DerefToken::make_int_constant(in.idx); + case FieldReverseLookupOutput::Token::Kind::VAR_IDX: + return DerefToken::make_expr_placeholder(); + default: + // temp + throw std::runtime_error("Cannot convert rd lookup token to deref token"); + } +} + DerefElement::DerefElement(Form* base, bool is_addr_of, DerefToken token) : m_base(base), m_is_addr_of(is_addr_of), m_tokens({std::move(token)}) { m_base->parent_element = this; @@ -1564,4 +1587,18 @@ void StringConstantElement::apply_form(const std::function&) {} void StringConstantElement::collect_vars(VariableSet&) const {} void StringConstantElement::get_modified_regs(RegSet&) const {} +///////////////////////////// +// ConstantTokenElement +///////////////////////////// +ConstantTokenElement::ConstantTokenElement(const std::string& value) : m_value(value) {} + +goos::Object ConstantTokenElement::to_form_internal(const Env&) const { + return pretty_print::to_symbol(m_value); +} + +void ConstantTokenElement::apply(const std::function&) {} +void ConstantTokenElement::apply_form(const std::function&) {} +void ConstantTokenElement::collect_vars(VariableSet&) const {} +void ConstantTokenElement::get_modified_regs(RegSet&) const {} + } // namespace decompiler diff --git a/decompiler/IR2/Form.h b/decompiler/IR2/Form.h index 0ed74a22d2..795e8f38cb 100644 --- a/decompiler/IR2/Form.h +++ b/decompiler/IR2/Form.h @@ -7,6 +7,7 @@ #include "decompiler/Disasm/Register.h" #include "decompiler/IR2/AtomicOp.h" #include "common/goos/Object.h" +#include "common/type_system/TypeSystem.h" namespace decompiler { class Form; @@ -132,6 +133,11 @@ class SimpleExpressionElement : public FormElement { FormStack& stack, std::vector* result, bool allow_side_effects); + void update_from_stack_float_to_int(const Env& env, + FormPool& pool, + FormStack& stack, + std::vector* result, + bool allow_side_effects); void update_from_stack_copy_first_int_2(const Env& env, FixedOperatorKind kind, FormPool& pool, @@ -856,6 +862,8 @@ class DerefToken { Form* m_expr = nullptr; }; +DerefToken to_token(const FieldReverseLookupOutput::Token& in); + class DerefElement : public FormElement { public: DerefElement(Form* base, bool is_addr_of, DerefToken token); @@ -947,6 +955,24 @@ class StringConstantElement : public FormElement { void apply_form(const std::function& f) override; void collect_vars(VariableSet& vars) const override; void get_modified_regs(RegSet& regs) const override; + void update_from_stack(const Env& env, + FormPool& pool, + FormStack& stack, + std::vector* result, + bool allow_side_effects) override; + + private: + std::string m_value; +}; + +class ConstantTokenElement : public FormElement { + public: + ConstantTokenElement(const std::string& value); + 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; + void collect_vars(VariableSet& vars) const override; + void get_modified_regs(RegSet& regs) const override; private: std::string m_value; diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index 4fc53e19a1..e804530a08 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -331,13 +331,14 @@ void SimpleExpressionElement::update_from_stack_fpr_to_gpr(const Env& env, bool allow_side_effects) { auto src = m_expr.get_arg(0); auto src_type = env.get_types_before_op(m_my_idx).get(src.var().reg()); - if (src_type.typespec() == TypeSpec("float")) { + if (src_type.typespec() == TypeSpec("float") || src_type.typespec() == TypeSpec("int")) { // set ourself to identity. m_expr = src.as_expr(); // then go again. update_from_stack(env, pool, stack, result, allow_side_effects); } else { - throw std::runtime_error(fmt::format("FPR -> GPR applied to a {}", src_type.print())); + throw std::runtime_error( + fmt::format("FPR -> GPR applied to a {} in {}", src_type.print(), to_string(env))); } } @@ -376,7 +377,11 @@ void SimpleExpressionElement::update_from_stack_float_2(const Env& env, args.at(0), args.at(1)); result->push_back(new_form); } else { - throw std::runtime_error(fmt::format("Floating point math attempted on invalid types.")); + auto type0 = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(0).var().reg()); + auto type1 = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(1).var().reg()); + throw std::runtime_error( + fmt::format("Floating point math attempted on invalid types: {} and {} in op {}.", + type0.print(), type1.print(), to_string(env))); } } @@ -422,6 +427,47 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env, args.push_back(pool.alloc_single_element_form(nullptr, m_expr.get_arg(1))); } + // Look for getting an address inside of an object. + // (+ process). array style access with a stride of 1. + // in the case, both are vars. + if (arg1_reg) { + // lookup types. + auto arg1_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(1).var().reg()); + auto arg0_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(0).var().reg()); + if (arg0_type.kind == TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR) { + // try to see if this is valid, from the type system. + FieldReverseLookupInput input; + input.offset = arg0_type.get_integer_constant(); + input.stride = 1; + input.base_type = arg1_type.typespec(); + auto out = env.dts->ts.reverse_field_lookup(input); + if (out.success) { + // it is. now we have to modify things + // first, look for the index + auto arg0_matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::ADDITION), + {Matcher::any(0), Matcher::integer(input.offset)}); + auto match_result = match(arg0_matcher, args.at(0)); + if (match_result.matched) { + bool used_index = false; + std::vector tokens; + for (auto& tok : out.tokens) { + if (tok.kind == FieldReverseLookupOutput::Token::Kind::VAR_IDX) { + assert(!used_index); + used_index = true; + tokens.push_back(DerefToken::make_int_expr(match_result.maps.forms.at(0))); + } else { + tokens.push_back(to_token(tok)); + } + } + result->push_back(pool.alloc_element(args.at(1), out.addr_of, tokens)); + return; + } else { + throw std::runtime_error("Failed to match for stride 1 address access with add."); + } + } + } + } + if ((arg0_i && arg1_i) || (arg0_u && arg1_u)) { auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::ADDITION), args.at(0), args.at(1)); @@ -612,6 +658,21 @@ void SimpleExpressionElement::update_from_stack_int_to_float(const Env& env, } } +void SimpleExpressionElement::update_from_stack_float_to_int(const Env& env, + FormPool& pool, + FormStack& stack, + std::vector* result, + bool allow_side_effects) { + auto var = m_expr.get_arg(0).var(); + auto arg = pop_to_forms({var}, env, pool, stack, allow_side_effects).at(0); + auto type = env.get_types_before_op(var.idx()).get(var.reg()).typespec(); + if (type == TypeSpec("float")) { + result->push_back(pool.alloc_element(TypeSpec("int"), arg, true)); + } else { + throw std::runtime_error("Used float to int on a " + type.print()); + } +} + void SimpleExpressionElement::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, @@ -642,12 +703,20 @@ void SimpleExpressionElement::update_from_stack(const Env& env, update_from_stack_float_2(env, FixedOperatorKind::ADDITION, pool, stack, result, allow_side_effects); break; + case SimpleExpression::Kind::MAX_S: + update_from_stack_float_2(env, FixedOperatorKind::FMAX, pool, stack, result, + allow_side_effects); + break; + case SimpleExpression::Kind::MIN_S: + update_from_stack_float_2(env, FixedOperatorKind::FMIN, pool, stack, result, + allow_side_effects); + break; case SimpleExpression::Kind::SQRT_S: update_from_stack_float_1(env, FixedOperatorKind::SQRT, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::ABS_S: - update_from_stack_float_1(env, FixedOperatorKind::ABS, pool, stack, result, + update_from_stack_float_1(env, FixedOperatorKind::FABS, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::NEG_S: @@ -718,6 +787,9 @@ void SimpleExpressionElement::update_from_stack(const Env& env, case SimpleExpression::Kind::INT_TO_FLOAT: update_from_stack_int_to_float(env, pool, stack, result, allow_side_effects); break; + case SimpleExpression::Kind::FLOAT_TO_INT: + update_from_stack_float_to_int(env, pool, stack, result, allow_side_effects); + break; default: throw std::runtime_error( fmt::format("SimpleExpressionElement::update_from_stack NYI for {}", to_string(env))); @@ -737,11 +809,18 @@ void SetVarElement::push_to_stack(const Env& env, FormPool& pool, FormStack& sta for (auto x : m_src->elts()) { assert(x->parent_form == m_src); } + if (m_src->is_single_element()) { auto src_as_se = dynamic_cast(m_src->back()); if (src_as_se) { if (src_as_se->expr().kind() == SimpleExpression::Kind::IDENTITY && src_as_se->expr().get_arg(0).is_var()) { + // this can happen late in the case of coloring moves which are also gpr -> fpr's + // so they don't get caught by SetVarOp::get_as_form's check. + if (env.op_id_is_eliminated_coloring_move(src_as_se->expr().get_arg(0).var().idx())) { + m_var_info.is_eliminated_coloring_move = true; + } + auto var = src_as_se->expr().get_arg(0).var(); auto& info = env.reg_use().op.at(var.idx()); if (info.consumes.find(var.reg()) != info.consumes.end()) { @@ -996,6 +1075,8 @@ void DerefElement::update_from_stack(const Env& env, bool allow_side_effects) { // todo - update var tokens from stack? m_base->update_children_from_stack(env, pool, stack, allow_side_effects); + + // merge nested ->'s auto as_deref = dynamic_cast(m_base->try_as_single_element()); if (as_deref) { if (!m_is_addr_of && !as_deref->is_addr_of()) { @@ -1003,7 +1084,25 @@ void DerefElement::update_from_stack(const Env& env, m_base = as_deref->m_base; } } - result->push_back(this); + + // rewrite access to the method table to use method-of-object + // (-> type methods-by-name ) + // (method-of-object ) + auto get_method_matcher = Matcher::deref( + Matcher::any(0), false, + {DerefTokenMatcher::string("type"), DerefTokenMatcher::string("methods-by-name"), + DerefTokenMatcher::any_string(1)}); + Form hack_form; + hack_form.elts() = {this}; + auto mr = match(get_method_matcher, &hack_form); + if (mr.matched) { + auto method_op = pool.alloc_element( + GenericOperator::make_fixed(FixedOperatorKind::METHOD_OF_OBJECT), mr.maps.forms.at(0), + pool.alloc_single_element_form(nullptr, mr.maps.strings.at(1))); + result->push_back(method_op); + } else { + result->push_back(this); + } } /////////////////// @@ -1341,6 +1440,11 @@ FormElement* ConditionElement::make_generic(const Env&, GenericOperator::make_fixed(FixedOperatorKind::GEQ), make_cast(source_forms, types, TypeSpec("uint"), pool)); + case IR2_Condition::Kind::GEQ_SIGNED: + return pool.alloc_element( + GenericOperator::make_fixed(FixedOperatorKind::GEQ), + make_cast(source_forms, types, TypeSpec("int"), pool)); + case IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED: { auto casted = make_cast(source_forms, types, TypeSpec("int"), pool); auto zero = pool.alloc_single_element_form( @@ -1415,7 +1519,13 @@ void ConditionElement::push_to_stack(const Env& env, FormPool& pool, FormStack& vars.push_back(var); source_types.push_back(env.get_types_before_op(var.idx()).get(var.reg()).typespec()); } else if (m_src[i]->is_int()) { - source_types.push_back(TypeSpec("int")); + if (m_src[i]->get_int() == 0 && condition_uses_float(m_kind)) { + // if we're doing a floating point comparison, and one of our arguments is a constant + // which is an "integer zero", treat it as a floating point zero. + source_types.push_back(TypeSpec("float")); + } else { + source_types.push_back(TypeSpec("int")); + } } else { throw std::runtime_error("Unsupported atom in ConditionElement::push_to_stack"); } @@ -1639,7 +1749,7 @@ void ArrayFieldAccess::update_from_stack(const Env& env, if (m_expected_stride == 1) { // reg0 is idx auto reg0_matcher = - Matcher::match_or({Matcher::any_reg(0), Matcher::cast("int", Matcher::any_reg(0))}); + Matcher::match_or({Matcher::any(0), Matcher::cast("int", Matcher::any_reg(0))}); // reg1 is base auto reg1_matcher = Matcher::match_or({Matcher::any_reg(1), Matcher::cast("int", Matcher::any_reg(1))}); @@ -1649,14 +1759,14 @@ void ArrayFieldAccess::update_from_stack(const Env& env, throw std::runtime_error("Couldn't match ArrayFieldAccess (stride 1) values: " + new_val->to_string(env)); } - auto idx = match_result.maps.regs.at(0); + auto idx = match_result.maps.forms.at(0); auto base = match_result.maps.regs.at(1); - assert(idx.has_value() && base.has_value()); + assert(idx && base.has_value()); std::vector tokens = m_deref_tokens; for (auto& x : tokens) { if (x.kind() == DerefToken::Kind::EXPRESSION_PLACEHOLDER) { - x = DerefToken::make_int_expr(var_to_form(idx.value(), pool)); + x = DerefToken::make_int_expr(idx); } } // tokens.push_back(DerefToken::make_int_expr(var_to_form(idx.value(), pool))); @@ -1760,4 +1870,12 @@ void SimpleExpressionElement::push_to_stack(const Env&, FormPool&, FormStack& st stack.push_form_element(this, true); } +void StringConstantElement::update_from_stack(const Env&, + FormPool&, + FormStack&, + std::vector* result, + bool) { + result->push_back(this); +} + } // namespace decompiler diff --git a/decompiler/IR2/IR2_common.h b/decompiler/IR2/IR2_common.h index 3fcb62f115..a9157cdd1e 100644 --- a/decompiler/IR2/IR2_common.h +++ b/decompiler/IR2/IR2_common.h @@ -101,6 +101,9 @@ enum class FixedOperatorKind { ABS, MIN, MAX, + FABS, + FMIN, + FMAX, LOGAND, LOGIOR, LOGXOR, @@ -121,6 +124,7 @@ enum class FixedOperatorKind { EQ, NEQ, CONS, + METHOD_OF_OBJECT, INVALID }; diff --git a/decompiler/analysis/variable_naming.cpp b/decompiler/analysis/variable_naming.cpp index ed8ba6cad1..e3425b2c64 100644 --- a/decompiler/analysis/variable_naming.cpp +++ b/decompiler/analysis/variable_naming.cpp @@ -260,6 +260,17 @@ bool is_saved_reg(Register r) { } } +bool is_possible_coloring_move(Register dst, Register src) { + if (is_arg_reg(src) && is_saved_reg(dst)) { + return true; + } + + if (dst.get_kind() == Reg::FPR && dst.get_fpr() < 20 && is_arg_reg(src)) { + return true; + } + return false; +} + /*! * Create a "really crude" SSA, as described in * "Aycock and Horspool Simple Generation of Static Single-Assignment Form" @@ -321,22 +332,20 @@ SSA make_rc_ssa(const Function& function, const RegUsageInfo& rui, const Functio SSA::Ins ssa_i(op_id); if (block_id == 0 && !got_not_arg_coloring) { + got_not_arg_coloring = true; auto as_set = dynamic_cast(op.get()); if (as_set) { - if (as_set->src().is_identity() && as_set->src().get_arg(0).is_var()) { + if ((as_set->src().kind() == SimpleExpression::Kind::GPR_TO_FPR || + as_set->src().is_identity()) && + as_set->src().get_arg(0).is_var()) { auto src = as_set->src().get_arg(0).var().reg(); auto dst = as_set->dst().reg(); - if (is_arg_reg(src) && is_saved_reg(dst) && + if (is_possible_coloring_move(dst, src) && rui.op.at(op_id).consumes.find(src) != rui.op.at(op_id).consumes.end()) { ssa_i.is_arg_coloring_move = true; - } else { - got_not_arg_coloring = true; + got_not_arg_coloring = false; } - } else { - got_not_arg_coloring = true; } - } else { - got_not_arg_coloring = true; } } diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index a14b0072b2..841e114deb 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -702,6 +702,22 @@ ;;;;;;;;;;;; START OF GAME.CGO ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; TYPES-H ;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; + +(deftype time-frame (int64) + () + :flag-assert #x900000008 + ) + +(deftype part-id (uint32) + () + :flag-assert #x900000004 + ) + ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; MATH ;;;;;;;;;;;;;;;;;;;;;; @@ -724,27 +740,36 @@ (define-extern seek (function float float float float)) (define-extern lerp (function float float float float)) (define-extern lerp-scale (function float float float float float float)) +(define-extern lerp-clamp (function float float float float)) +(define-extern seekl (function int int int int)) +(define-extern rand-vu-init (function float float)) +(define-extern rand-vu (function float)) +(define-extern rand-vu-nostep (function float)) +(define-extern rand-vu-float-range (function float float float)) +(define-extern rand-vu-percent? (function float symbol)) +(define-extern rand-vu-int-range (function int int int)) +(define-extern rand-vu-int-count (function int int)) ; ;;(define-extern xyzwh object) ;; unknown type -; (define-extern rand-vu-init function) -; (define-extern rand-vu-nostep function) -; (define-extern rand-vu function) +; +; +; ; (define-extern lerp function) ; (define-extern lerp-scale function) -; (define-extern rand-vu-int-range function) -; (define-extern lerp-clamp function) +; + ; ;;(define-extern random-generator object) ;; unknown type ; (define-extern integral? function) ; (define-extern rand-uint31-gen function) -; (define-extern rand-vu-int-count function) +; ; (define-extern fractional-part function) ; (define-extern seek function) ; ;;(define-extern xyzw object) ;; unknown type -; (define-extern rand-vu-percent? function) +; ; ;;(define-extern *random-generator* object) ;; unknown type -; (define-extern rand-vu-float-range function) +; ; ;;(define-extern rgba object) ;; unknown type -; (define-extern seekl function) +; ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; @@ -757,16 +782,18 @@ (deftype bit-array (basic) ((length int32 :offset-assert 4) (allocated-length int32 :offset-assert 8) - (_pad uint8) + (_pad uint8 :offset-assert 12) + (bytes uint8 :dynamic :offset 12) ) :method-count-assert 13 :size-assert #xd :flag-assert #xd0000000d (:methods - (dummy-9 () none 9) - (dummy-10 () none 10) - (dummy-11 () none 11) - (dummy-12 () none 12) + (new (symbol type int) _type_ 0) + (get-bit (_type_ int) symbol 9) + (clear-bit (_type_ int) int 10) + (set-bit (_type_ int) int 11) + (clear (_type_) _type_ 12) ) ) @@ -997,6 +1024,9 @@ ) ;; todo isphere +(deftype isphere (vec4s) + () + ) (deftype box8s (structure) ((data float 8 :offset-assert 0) @@ -1103,8 +1133,12 @@ (define-extern *zero-vector* vector) +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; BOUNDING-BOX-H ;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; -;; bounding-box-h (deftype bounding-box (structure) ((min vector :inline :offset-assert 0) (max vector :inline :offset-assert 16) @@ -1123,7 +1157,6 @@ ) ) -;; bounding-box-h (deftype bounding-box4w (structure) ((min vector4w :inline :offset-assert 0) (max vector4w :inline :offset-assert 16) @@ -1133,7 +1166,6 @@ :flag-assert #x900000020 ) -;; bounding-box-h (deftype bounding-box-both (structure) ((box bounding-box :inline :offset-assert 0) (box4w bounding-box4w :inline :offset-assert 32) @@ -1143,7 +1175,12 @@ :flag-assert #x900000040 ) -;; matrix-h +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; MATRIX-H ;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; + (deftype matrix (structure) ((data float 16 :offset-assert 0) (vector vector 4 :offset 0) @@ -1167,7 +1204,6 @@ :flag-assert #x900000030 ) -;; guess on signs here (deftype matrix4h (structure) ((data int16 16 :offset-assert 0) (vector4h vector4h 4 :offset 0) @@ -1178,6 +1214,13 @@ :flag-assert #x900000020 ) + +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; QUATERNION-H ;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; + (deftype quaternion (structure) ((data float 4 :offset-assert 0) (x float :offset 0) @@ -1192,7 +1235,12 @@ :flag-assert #x900000010 ) -;; euler-h +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; EULER-H ;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; + (deftype euler-angles (vector) () :method-count-assert 9 @@ -1200,7 +1248,12 @@ :flag-assert #x900000010 ) -;; transform-h +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; TRANSFORM-H ;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; + (deftype transform (structure) ((trans vector :inline :offset-assert 0) (rot vector :inline :offset-assert 16) @@ -1211,7 +1264,6 @@ :flag-assert #x900000030 ) -;; transform-h (deftype trs (basic) ((trans vector :inline :offset-assert 16) (rot vector :inline :offset-assert 32) @@ -1222,7 +1274,12 @@ :flag-assert #x900000040 ) -;; geometry-h +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; GEOMETRY-H ;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; + (deftype curve (structure) ((cverts uint32 :offset-assert 0) (num-cverts int32 :offset-assert 4) @@ -1235,7 +1292,6 @@ :flag-assert #x900000014 ) -;; geometry-h (deftype border-plane (basic) ((name basic :offset-assert 4) (action basic :offset-assert 8) @@ -1252,7 +1308,12 @@ ) ) -; ;; transformq-h +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; TRANSFORMQ-H ;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; + (deftype transformq (transform) ((quat quaternion :inline :offset 16) ) @@ -1269,8 +1330,6 @@ :flag-assert #x900000040 ) - -;; transformq-h (deftype trsqv (trsq) ((pause-adjust-distance float :offset 4) ;; todo meters (nav-radius float :offset 8) ;; todo meters @@ -1307,6 +1366,15 @@ ) ) +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; BOUNDING-BOX ;;;;;;;;;;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; + +(define-extern box-vector-inside? (function bounding-box vector symbol)) +(define-extern box-vector-enside? (function bounding-box vector symbol)) + (deftype sound-rpc-cmd (structure) ((rsvd1 uint16 :offset-assert 0) (command uint16 :offset-assert 2) @@ -31505,8 +31573,7 @@ ;;(define-extern trsq object) ;; unknown type (define-extern deg-diff function) (define-extern vector-y-angle function) -(define-extern box-vector-inside? function) -(define-extern box-vector-enside? function) + (define-extern vector3s-rotate*! function) (define-extern matrix-inverse-of-rot-trans! function) (define-extern matrix-3x3-determinant function) diff --git a/decompiler/config/jak1_ntsc_black_label/type_hints.jsonc b/decompiler/config/jak1_ntsc_black_label/type_hints.jsonc index 1ac50d3a91..193ec033e6 100644 --- a/decompiler/config/jak1_ntsc_black_label/type_hints.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/type_hints.jsonc @@ -61,10 +61,6 @@ [3, ["v1", "int"], ["a0", "int"]] ], - "remove-exit":[ - [0, ["s6", "process"]] - ], - "(method 0 process)":[ [12, ["a0", "int"]], [13, ["v0", "process"]] diff --git a/decompiler/config/jak1_ntsc_black_label/var_names.jsonc b/decompiler/config/jak1_ntsc_black_label/var_names.jsonc index 1ffba0f04a..1e0687ab9f 100644 --- a/decompiler/config/jak1_ntsc_black_label/var_names.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/var_names.jsonc @@ -192,6 +192,46 @@ "valid?":{ "args":["obj", "expected-type", "name", "allow-false", "print-dest"], "vars":{"v1-1":"in-goal-mem"} + }, + + "seek":{ + "args":["x", "target", "diff"], + "vars":{"f2-0":"err"} + }, + + "lerp":{ + "args":["minimum", "maximum", "amount"] + }, + + "lerp-scale":{ + "args":["min-out", "max-out", "in", "min-in", "max-in"], + "vars":{"f0-1":"scale"} + }, + + "lerp-clamp":{ + "args":["minimum", "maximum", "amount"] + }, + + "rand-vu-int-range":{ + "args":["first", "second"], + "vars":{"f0-4":"float-in-range"} + }, + + "(method 0 bit-array)":{ + "args":["allocation", "type-to-make", "length"], + "vars":{"v0-0":"obj"} + }, + + "(method 12 bit-array)":{ + "vars":{"v1-2":"idx"} + }, + + "box-vector-enside?":{ + "args":["box", "pt"] + }, + + "box-vector-inside?":{ + "args":["box", "pt"] } diff --git a/decompiler/util/DecompilerTypeSystem.cpp b/decompiler/util/DecompilerTypeSystem.cpp index c7bd48f496..e83b0dbf40 100644 --- a/decompiler/util/DecompilerTypeSystem.cpp +++ b/decompiler/util/DecompilerTypeSystem.cpp @@ -173,7 +173,7 @@ TP_Type DecompilerTypeSystem::tp_lca(const TP_Type& existing, bool* changed) const { // starting from most vague to most specific - // simplist case, no difference. + // simplest case, no difference. if (existing == add) { *changed = false; return existing; @@ -252,6 +252,18 @@ TP_Type DecompilerTypeSystem::tp_lca(const TP_Type& existing, *changed = true; return TP_Type::make_from_ts(TypeSpec("string")); } + case TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR: + if (existing.get_integer_constant() == add.get_integer_constant()) { + auto new_child = TP_Type::make_from_integer_constant_plus_var( + existing.get_integer_constant(), + coerce_to_reg_type(ts.lowest_common_ancestor(existing.get_objects_typespec(), + add.get_objects_typespec()))); + *changed = (new_child != existing); + return new_child; + } else { + *changed = true; + return TP_Type::make_from_ts("int"); + } case TP_Type::Kind::FALSE_AS_NULL: case TP_Type::Kind::UNINITIALIZED: diff --git a/decompiler/util/TP_Type.cpp b/decompiler/util/TP_Type.cpp index 63bf4e567b..bfdf122d9d 100644 --- a/decompiler/util/TP_Type.cpp +++ b/decompiler/util/TP_Type.cpp @@ -47,6 +47,8 @@ std::string TP_Type::print() const { return fmt::format("", m_int); case Kind::INTEGER_CONSTANT: return fmt::format("", m_int); + case Kind::INTEGER_CONSTANT_PLUS_VAR: + return fmt::format("", m_int, m_ts.print()); case Kind::DYNAMIC_METHOD_ACCESS: return fmt::format(""); case Kind::INVALID: @@ -81,6 +83,8 @@ bool TP_Type::operator==(const TP_Type& other) const { return m_int == other.m_int; case Kind::FORMAT_STRING: return m_int == other.m_int; + case Kind::INTEGER_CONSTANT_PLUS_VAR: + return m_int == other.m_int && m_ts == other.m_ts; case Kind::DYNAMIC_METHOD_ACCESS: return true; case Kind::INVALID: @@ -119,6 +123,8 @@ TypeSpec TP_Type::typespec() const { return TypeSpec("string"); case Kind::INTEGER_CONSTANT: return TypeSpec("int"); + case Kind::INTEGER_CONSTANT_PLUS_VAR: + return m_ts; case Kind::DYNAMIC_METHOD_ACCESS: return TypeSpec("object"); case Kind::FORMAT_STRING: diff --git a/decompiler/util/TP_Type.h b/decompiler/util/TP_Type.h index 019705a9be..8ee9c65104 100644 --- a/decompiler/util/TP_Type.h +++ b/decompiler/util/TP_Type.h @@ -21,11 +21,13 @@ class TP_Type { UNINITIALIZED, // representing data which is uninitialized. PRODUCT_WITH_CONSTANT, // representing: (val * multiplier) OBJECT_PLUS_PRODUCT_WITH_CONSTANT, // address: obj + (val * multiplier) - OBJECT_NEW_METHOD, // the method new of object, as used in an (object-new) or similar. - STRING_CONSTANT, // a string that's part of the string pool - FORMAT_STRING, // a string with a given number of format arguments - INTEGER_CONSTANT, // a constant integer. - DYNAMIC_METHOD_ACCESS, // partial access into a + OBJECT_NEW_METHOD, // the method new of object, as used in an (object-new) or similar. + STRING_CONSTANT, // a string that's part of the string pool + FORMAT_STRING, // a string with a given number of format arguments + INTEGER_CONSTANT, // a constant integer. + INTEGER_CONSTANT_PLUS_VAR, // constant + variable. used in stuff like (&-> obj inline-val-arr + // x) + DYNAMIC_METHOD_ACCESS, // partial access into a INVALID } kind = Kind::UNINITIALIZED; TP_Type() = default; @@ -104,6 +106,14 @@ class TP_Type { return result; } + static TP_Type make_from_integer_constant_plus_var(int64_t value, const TypeSpec& var_type) { + TP_Type result; + result.kind = Kind::INTEGER_CONSTANT_PLUS_VAR; + result.m_int = value; + result.m_ts = var_type; + return result; + } + static TP_Type make_from_product(int64_t multiplier, bool is_signed) { TP_Type result; result.kind = Kind::PRODUCT_WITH_CONSTANT; @@ -134,7 +144,7 @@ class TP_Type { } const TypeSpec& get_objects_typespec() const { - assert(kind == Kind::TYPESPEC); + assert(kind == Kind::TYPESPEC || kind == Kind::INTEGER_CONSTANT_PLUS_VAR); return m_ts; } @@ -159,7 +169,7 @@ class TP_Type { } uint64_t get_integer_constant() const { - assert(kind == Kind::INTEGER_CONSTANT); + assert(kind == Kind::INTEGER_CONSTANT || kind == Kind::INTEGER_CONSTANT_PLUS_VAR); return m_int; } diff --git a/doc/decompiler_changelog.md b/doc/decompiler_changelog.md index a29c0c4d2e..a29e634448 100644 --- a/doc/decompiler_changelog.md +++ b/doc/decompiler_changelog.md @@ -13,3 +13,14 @@ - Many more useless `set!`s will be removed - Stores into arrays are supported - Fixed bug where unused/eliminated temporaries would sometimes be used as the result of a block + +## Version 3 +- Normal use of the process pointer will now show up as `pp`. Weird use will still be weird. +- `(method-of-object ...)` will now be recognized +- Accessing the address of a variable element of an inline array is supported in some cases. More examples are needed before all work. But for example: `(&-> v0-0 stack (-> v0-0 allocated-length))` where `stack` is an `uint8 :dynamic`. +- Cleaned up unneeded casts floating point constant `0.0`. +- Support for `fmin`/`fmax` +- Fixed a bug where integer `abs` appeared instead of `fabs`. +- Some support for float -> integer conversions, but it is not 100% yet. +- Eliminate inserted coloring moves for function arguments that use `mtc1`. +- Support for `>=` for signed numbers. \ No newline at end of file diff --git a/goal_src/engine/geometry/bounding-box.gc b/goal_src/engine/geometry/bounding-box.gc index 5bc6aa2ac1..4f4b3bb9f4 100644 --- a/goal_src/engine/geometry/bounding-box.gc +++ b/goal_src/engine/geometry/bounding-box.gc @@ -5,3 +5,26 @@ ;; name in dgo: bounding-box ;; dgos: GAME, ENGINE +(defun box-vector-enside? ((box bounding-box) (pt vector)) + "Is the point in the box? On the edge doesn't count" + (and + (< (-> box min data 0) (-> pt data 0)) + (< (-> box min data 1) (-> pt data 1)) + (< (-> box min data 2) (-> pt data 2)) + (< (-> pt data 0) (-> box max data 0)) + (< (-> pt data 1) (-> box max data 1)) + (< (-> pt data 2) (-> box max data 2)) + ) + ) + +(defun box-vector-inside? ((box bounding-box) (pt vector)) + "Is the point in the box? On the edge counts." + (and + (>= (-> pt data 0) (-> box min data 0)) + (>= (-> pt data 1) (-> box min data 1)) + (>= (-> pt data 2) (-> box min data 2)) + (>= (-> box max data 0) (-> pt data 0)) + (>= (-> box max data 1) (-> pt data 1)) + (>= (-> box max data 2) (-> pt data 2)) + ) + ) diff --git a/goal_src/engine/math/euler-h.gc b/goal_src/engine/math/euler-h.gc index 0875c21c1d..14d2e87982 100644 --- a/goal_src/engine/math/euler-h.gc +++ b/goal_src/engine/math/euler-h.gc @@ -5,3 +5,14 @@ ;; name in dgo: euler-h ;; dgos: GAME, ENGINE +;; maybe euler angle storage orders? +(define EulSafe (new 'static 'boxed-array int32 4 0 1 2 0)) +(define EulNext (new 'static 'boxed-array int32 4 1 2 0 1)) + +;; just uses the same xyzw and data array as vector. +(deftype euler-angles (vector) + () + :method-count-assert 9 + :size-assert #x10 + :flag-assert #x900000010 + ) diff --git a/goal_src/engine/math/math.gc b/goal_src/engine/math/math.gc index 12ab483c4b..c0b98bf3ac 100644 --- a/goal_src/engine/math/math.gc +++ b/goal_src/engine/math/math.gc @@ -43,26 +43,61 @@ (- x (truncate x)) ) -;; todo, rgba, xyzw, xyzwh +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; bitfield types +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; TODO: verify these are in the right order. +;; maybe used in timer-h for the color field of profile-frame? +(deftype rgba (uint32) + ((r uint8 :offset 0) + (g uint8 :offset 8) + (b uint8 :offset 16) + (a uint8 :offset 24) + ) + :flag-assert #x900000004 + ) + +;; TODO: fields +(deftype xyzw (uint128) + () + :flag-assert #x900000010 + ) + +;; TODO: fields +(deftype xyzwh (uint128) + () + :flag-assert #x900000010 + ) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; utility functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun log2 ((x int)) - "Straight out of Bit Twiddling Hacks graphics.stanford.edu" - (- (sar (the-as integer (the float x)) 23) 127) + "Straight out of Bit Twiddling Hacks graphics.stanford.edu. + This website is old enough that they possibly used this back + in 1999." + (- (sar (the-as int (the float x)) 23) 127) ) (defun seek ((x float) (target float) (diff float)) "Move x toward target by at most diff, with floats" (let ((err (- target x))) - ;; if we can get there all at once - (if (<= (fabs err) diff) - (return-from #f target) + (cond + ((>= diff (fabs err)) + ;; can get there in one go + target + ) + ((>= err 0) + ;; increase + (+ x diff) + ) + (else + (- x diff) + ) ) - - (if (>= err 0) - (+ x diff) - (- x diff) - ) - ) + ) ) (defun lerp ((minimum float) (maximum float) (amount float)) @@ -83,19 +118,19 @@ ) (defun lerp-clamp ((minimum float) (maximum float) (amount float)) - "Interpolate between minimum and maximum. Clamp output. - For some reason, the interpolate here is done in a less efficient way than lerp." - (if (<= amount 0.0) - (return-from #f minimum) - ) - - (if (>= amount 1.0) - (return-from #f maximum) - ) - ;; lerp computes this part, but more efficiently - (+ (* (- 1.0 amount) minimum) - (* amount maximum) + "Interpolate between minimum and maximum, but saturate the amount to [0, 1]" + (cond + ((>= 0.0 amount) + minimum) + ((>= amount 1.0) + maximum) + (else + ;; the implementation in lerp uses fewer operations... + (+ (* (- 1.0 amount) minimum) + (* amount maximum) + ) ) + ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -117,10 +152,107 @@ ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;;;; random vu +;;;; random vu hardware ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; added in OpenGOAL +(define *_vu-reg-R_* 0) + ;; TODO +(defun rand-vu-init ((seed float)) + "Initialize the VU0 random generator" + ;; (.ctc2.i R arg0) + ;; (.cfc2.i v0 R) + (set! *_vu-reg-R_* + (logior #x3F800000 (logand (the-as int seed) #x007FFFFF)) + ) + (the-as float *_vu-reg-R_*) + ) + +(rand-vu-init 1.418091058731079) + +;; rand-vu +(defun rand-vu () + "Get a random number in [0, 1) and advance the random generator." + ;; (.vrget.xyzw vf1) - get current random + (let ((current-random *_vu-reg-R_*)) + ;; here they update the random generate with some junk + ;; for now, we don't do this in OpenGOAL. + ;; (.vsqrty Q vf1) + ;; (.vaddq.x vf2 vf0 Q) ;; you're not allowed to do this! + ;; (.vrxorw vf2) + + ;; and advance + ;; (.vrnext.xyzw vf1) + (let ((x (logand 1 (shr current-random 4))) + (y (logand 1 (shr current-random 22))) + ) + (set! current-random (shl current-random 1)) + (set! current-random (logxor current-random (logxor x y))) + (set! *_vu-reg-R_* (logior #x3f800000 (logand current-random #x7fffff))) + ) + ) + + ;; (.vsubw.xyzw vf1 vf1 vf0) + ;; (.qmfc2.i v0 vf1) + (- (the-as float *_vu-reg-R_*) 1.0) + ) + +;; rand-vu-nostep +(defun rand-vu-nostep () + "Get the number currently in the random generator. + This will be equal to the last call of (rand-vu) + This will not update the random generator" + (- (the-as float *_vu-reg-R_*) 1.0) + ) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; random vu utilities +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun rand-vu-float-range ((minimum float) (maximum float)) + "Get a random number in the given range. + TODO: is this inclusive? I think it's [min, max)" + (+ minimum (* (rand-vu) (- maximum minimum))) + ) + +(defun rand-vu-percent? ((prob float)) + "Get a boolean that's true with the given probability." + (>= prob (rand-vu)) + ) + +(defun rand-vu-int-range ((first int) (second int)) + "Get an integer the given range. Inclusive of both? + It looks like they actually did this right??" + + (local-vars (float-in-range float)) + + ;; increment the larger of the range, so it is inclusive + ;; (we should have (max - min) + 1 buckets) + (if (< first second) + (set! second (+ second 1)) + (set! first (+ first 1)) + ) + + ;; get a float in the range + (set! float-in-range + (rand-vu-float-range (the float first) (the float second)) + ) + + ;; negatives round up and positives round down. + ;; but we want all to round consistently, so we subtract one from negatives. + (when (< float-in-range 0) + (set! float-in-range (+ -1.0 float-in-range)) + ) + ;; and back to integer. + (the int float-in-range) + ) + +(defun rand-vu-int-count ((maximum int)) + "Get an integer in [0, maximum)" + (the int (* (rand-vu) (the float maximum))) + ) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; terrible random integer generator @@ -166,4 +298,4 @@ (set! (-> gen seed) result) result ) - ) \ No newline at end of file + ) diff --git a/goal_src/engine/math/transformq-h.gc b/goal_src/engine/math/transformq-h.gc index 9a8909df71..4bbc2ad096 100644 --- a/goal_src/engine/math/transformq-h.gc +++ b/goal_src/engine/math/transformq-h.gc @@ -5,3 +5,61 @@ ;; name in dgo: transformq-h ;; dgos: GAME, ENGINE +;; transforms, but using quaternions to represent rotations. + +(deftype transformq (transform) + ;; this overlays the rot field of transform. + ((quat quaternion :inline :offset 16) + ) + :method-count-assert 9 + :size-assert #x30 + :flag-assert #x900000030 + ) + +(deftype trsq (trs) + ;; this overlays the rot field of trs. + ((quat quaternion :inline :offset 32) + ) + :method-count-assert 9 + :size-assert #x40 + :flag-assert #x900000040 + ) + +;; Representing a translate/rotate/scale with a quaternion and a velocity. +(deftype trsqv (trsq) + ((pause-adjust-distance float :offset 4) ;; todo meters + (nav-radius float :offset 8) ;; todo meters + (transv vector :inline :offset-assert 64) + (rotv vector :inline :offset-assert 80) + (scalev vector :inline :offset-assert 96) + (dir-targ quaternion :inline :offset-assert 112) + (angle-change-time uint64 :offset-assert 128) + (old-y-angle-diff float :offset-assert 136) + ) + :method-count-assert 28 + :size-assert #x8c + :flag-assert #x1c0000008c + (:methods + (dummy-9 () none 9) + (dummy-10 () none 10) + (dummy-11 () none 11) + (dummy-12 () none 12) + (dummy-13 () none 13) + (dummy-14 () none 14) + (dummy-15 () none 15) + (dummy-16 () none 16) + (dummy-17 () none 17) + (dummy-18 () none 18) + (dummy-19 () none 19) + (dummy-20 () none 20) + (dummy-21 () none 21) + (dummy-22 () none 22) + (dummy-23 () none 23) + (dummy-24 () none 24) + (dummy-25 () none 25) + (dummy-26 () none 26) + (dummy-27 () none 27) + ) + ) + +;; todo trsqv methods 23 and 24 diff --git a/goal_src/engine/math/trigonometry-h.gc b/goal_src/engine/math/trigonometry-h.gc index 1f5432b00d..2c1a3c93a5 100644 --- a/goal_src/engine/math/trigonometry-h.gc +++ b/goal_src/engine/math/trigonometry-h.gc @@ -5,3 +5,4 @@ ;; name in dgo: trigonometry-h ;; dgos: GAME, ENGINE +;; this file generates no code. \ No newline at end of file diff --git a/goal_src/engine/math/vector-h.gc b/goal_src/engine/math/vector-h.gc index 8f1216b2e8..7ba8a9e714 100644 --- a/goal_src/engine/math/vector-h.gc +++ b/goal_src/engine/math/vector-h.gc @@ -12,27 +12,96 @@ (deftype bit-array (basic) ((length int32 :offset-assert 4) (allocated-length int32 :offset-assert 8) - (_pad uint8) + ;; neither of these show up in the inspect. + ;; it seems like there's a single byte of data array + ;; included in the type already + (_pad uint8 :offset-assert 12) + (bytes uint8 :dynamic :offset 12) ) :method-count-assert 13 :size-assert #xd :flag-assert #xd0000000d (:methods - (dummy-9 () none 9) - (dummy-10 () none 10) - (dummy-11 () none 11) - (dummy-12 () none 12) - ) + (new (symbol type int) _type_ 0) + (get-bit (_type_ int) symbol 9) + (clear-bit (_type_ int) int 10) + (set-bit (_type_ int) int 11) + (clear (_type_) _type_ 12) + ) ) -;; todo new -;; todo 4 -;; todo 5 -;; todo 9 -;; todo 10 -;; todo 11 -;; todo 12 -;; +(defmethod new bit-array ((allocation symbol) (type-to-make type) (length int)) + "Allocate a new bit-array which can hold length bits. + Sets both the length and the allocated-length to this length." + (local-vars (obj bit-array)) + ;; remove one byte, we get one in the type already. + (set! obj (object-new allocation type-to-make + (+ (+ (sar (logand -8 (+ length 7)) 3) -1) + (the-as int (-> type-to-make size)) + ) + ) + ) + (set! (-> obj length) length) + (set! (-> obj allocated-length) length) + obj + ) + +(defmethod length bit-array ((obj bit-array)) + "Get the length (in bits)" + (-> obj length) + ) + +(defmethod asize-of bit-array ((obj bit-array)) + "Get the size in memory. + It is wrong and says its one bit longer, which is safe." + (the-as int + (+ (-> obj type size) + (the-as uint (sar (logand -8 (+ (-> obj allocated-length) 7)) 3)) + ) + ) + ) + +(defmethod get-bit bit-array ((obj bit-array) (idx int)) + "Is the bit at idx set or not?" + (local-vars (byte uint)) + ;; read the byte + (set! byte (-> obj bytes (sar idx 3))) + (nonzero? (logand byte (the-as uint (ash 1 (logand idx 7))))) + ) + +(defmethod clear-bit bit-array ((obj bit-array) (idx int)) + "Clear the bit at position idx" + (set! (-> obj bytes (sar idx 3)) + (logand (-> obj bytes (sar idx 3)) + (the-as uint (lognot (ash 1 (logand idx 7)))) + ) + ) + 0 + ) + +(defmethod set-bit bit-array ((obj bit-array) (idx int)) + "Set the bit at position idx" + (set! (-> obj bytes (sar idx 3)) + (logior (-> obj bytes (sar idx 3)) + (the-as uint (ash 1 (logand idx 7))) + ) + ) + 0 + ) + +(defmethod clear bit-array ((obj bit-array)) + "Set all bits to zero." + (local-vars (idx int)) + (let ((idx (sar (logand -8 (+ (-> obj allocated-length) 7)) 3))) + (while (nonzero? idx) + (set! idx (+ idx -1)) + (nop!) + (nop!) + (set! (-> obj bytes idx) 0) + ) + obj + ) + ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; vector types (integer) @@ -315,13 +384,13 @@ :flag-assert #x900000010 ) -; todo -; (deftype isphere (vec4s) -; () -; :method-count-assert 9 -; :size-assert #x10 -; :flag-assert #x900000010 -; ) + +(deftype isphere (vec4s) + () + :method-count-assert 9 + :size-assert #x10 + :flag-assert #x900000010 + ) (deftype box8s (structure) ((data float 8 :offset-assert 0) @@ -543,4 +612,4 @@ dst ) -(define *zero-vector* (new 'static 'vector :x 0. :y 0. :z 0. :w 0.)) \ No newline at end of file +(define *zero-vector* (new 'static 'vector :x 0. :y 0. :z 0. :w 0.)) diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index fe81bbe09a..3941071541 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -140,7 +140,8 @@ class Compiler { Val* compile_stack_new(const goos::Object& form, const goos::Object& type, const goos::Object* rest, - Env* env); + Env* env, + bool call_constructor); TypeSystem m_ts; std::unique_ptr m_global_env = nullptr; diff --git a/goalc/compiler/compilation/Type.cpp b/goalc/compiler/compilation/Type.cpp index 553826334e..9668ec0519 100644 --- a/goalc/compiler/compilation/Type.cpp +++ b/goalc/compiler/compilation/Type.cpp @@ -785,7 +785,8 @@ Val* Compiler::compile_static_new(const goos::Object& form, Val* Compiler::compile_stack_new(const goos::Object& form, const goos::Object& type, const goos::Object* rest, - Env* env) { + Env* env, + bool call_constructor) { auto type_of_object = parse_typespec(unquote(type)); auto fe = get_parent_env_of_type(env); if (type_of_object == TypeSpec("inline-array") || type_of_object == TypeSpec("array")) { @@ -841,20 +842,30 @@ Val* Compiler::compile_stack_new(const goos::Object& form, // allocation auto mem = fe->allocate_aligned_stack_variable(type_of_object, ti->get_size_in_memory(), 16) ->to_gpr(env); - // the new method actual takes a "symbol" according the type system. So we have to cheat it. - mem->set_type(TypeSpec("symbol")); - args.push_back(mem); - // type - args.push_back(compile_get_symbol_value(form, type_of_object.base_type(), env)->to_reg(env)); - // the other arguments - for_each_in_list(*rest, [&](const goos::Object& o) { - args.push_back(compile_error_guard(o, env)->to_reg(env)); - }); + if (call_constructor) { + // the new method actual takes a "symbol" according the type system. So we have to cheat it. + mem->set_type(TypeSpec("symbol")); + args.push_back(mem); + // type + args.push_back(compile_get_symbol_value(form, type_of_object.base_type(), env)->to_reg(env)); + // the other arguments + for_each_in_list(*rest, [&](const goos::Object& o) { + args.push_back(compile_error_guard(o, env)->to_reg(env)); + }); - auto new_method = compile_get_method_of_type(form, type_of_object, "new", env); - auto new_obj = compile_real_function_call(form, new_method, args, env); - new_obj->set_type(type_of_object); - return new_obj; + auto new_method = compile_get_method_of_type(form, type_of_object, "new", env); + auto new_obj = compile_real_function_call(form, new_method, args, env); + new_obj->set_type(type_of_object); + return new_obj; + } else { + if (ti->get_offset()) { + throw std::runtime_error("Cannot stack allocate with no constructor for a " + + ti->get_name()); + } else { + mem->set_type(type_of_object); + return mem; + } + } } } @@ -872,7 +883,9 @@ Val* Compiler::compile_new(const goos::Object& form, const goos::Object& _rest, // put in code. return compile_static_new(form, type, rest, env); } else if (allocation == "stack") { - return compile_stack_new(form, type, rest, env); + return compile_stack_new(form, type, rest, env, true); + } else if (allocation == "stack-no-constructor") { + return compile_stack_new(form, type, rest, env, false); } throw_compiler_error(form, "Unsupported new form"); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3b91759a54..adff240132 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable(goalc-test ${CMAKE_CURRENT_LIST_DIR}/decompiler/test_FormExpressionBuildLong.cpp ${CMAKE_CURRENT_LIST_DIR}/decompiler/test_InstructionParser.cpp ${CMAKE_CURRENT_LIST_DIR}/decompiler/test_gkernel_decomp.cpp + ${CMAKE_CURRENT_LIST_DIR}/decompiler/test_math_decomp.cpp ${GOALC_TEST_FRAMEWORK_SOURCES} ${GOALC_TEST_CASES}) diff --git a/test/decompiler/test_gkernel_decomp.cpp b/test/decompiler/test_gkernel_decomp.cpp index cdad51dc27..102d1ad329 100644 --- a/test/decompiler/test_gkernel_decomp.cpp +++ b/test/decompiler/test_gkernel_decomp.cpp @@ -276,3 +276,477 @@ TEST_F(FormRegressionTest, ExprMethod9Thread) { " )"; test_with_expr(func, type, expected, false, "", {{"L342", "1 ~A ~%"}, {"L341", "2 ~A ~%"}}); } + +TEST_F(FormRegressionTest, ExprMethod0Thread) { + std::string func = + " sll r0, r0, 0\n" + " lwu v1, 44(a2)\n" + " beq s7, v1, L266\n" + " sll r0, r0, 0\n" + + " daddiu v0, t1, -7164\n" + " beq r0, r0, L267\n" + " sll r0, r0, 0\n" + + "L266:\n" + " addiu v1, r0, -16\n" + " lwu a0, 84(a2)\n" + " daddiu a0, a0, 15\n" + " and v1, v1, a0\n" + " lhu a0, 8(a1)\n" + " daddu a0, v1, a0\n" + " daddu a0, a0, t0\n" + " sw a0, 84(a2)\n" + " daddiu v0, v1, 4\n" + + "L267:\n" + " sw a1, -4(v0)\n" + " sw a3, 0(v0)\n" + " sw a2, 4(v0)\n" + " sw t1, 24(v0)\n" + " sw t1, 28(v0)\n" + " lwu v1, 44(a2)\n" + " sw v1, 8(v0)\n" + " sw v0, 44(a2)\n" + " lwu v1, -4(v0)\n" + " lwu v1, 56(v1)\n" + " sw v1, 12(v0)\n" + " lwu v1, -4(v0)\n" + " lwu v1, 60(v1)\n" + " sw v1, 16(v0)\n" + " sw t0, 32(v0)\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function symbol type process symbol int pointer thread)"; + std::string expected = + "(begin\n" + " (set!\n" + " v0-0\n" + " (cond\n" + " ((-> arg2 top-thread) (+ arg5 (the-as uint -7164)))\n" + " (else\n" + " (set!\n" + " v1-2\n" + " (logand -16 (the-as int (+ (-> arg2 heap-cur) (the-as uint 15))))\n" + " )\n" + " (set! (-> arg2 heap-cur) (+ (+ v1-2 (the-as int (-> arg1 size))) arg4))\n" + " (+ v1-2 4)\n" + " )\n" + " )\n" + " )\n" + " (set! (-> (the-as cpu-thread v0-0) type) arg1)\n" + " (set! (-> v0-0 name) arg3)\n" + " (set! (-> v0-0 process) arg2)\n" + " (set! (-> v0-0 sp) arg5)\n" + " (set! (-> v0-0 stack-top) arg5)\n" + " (set! (-> v0-0 previous) (-> arg2 top-thread))\n" + " (set! (-> arg2 top-thread) v0-0)\n" + // TODO - we could make this method access nicer. + " (set! (-> v0-0 suspend-hook) (method-of-object v0-0 thread-suspend))\n" + " (set! (-> v0-0 resume-hook) (method-of-object v0-0 thread-resume))\n" + " (set! (-> v0-0 stack-size) arg4)\n" + " v0-0\n" + " )"; + test_with_expr(func, type, expected, false, "cpu-thread", {}, + parse_hint_json("[[13, [\"v0\", \"cpu-thread\"]]]")); +} + +TEST_F(FormRegressionTest, ExprMethod5CpuThread) { + std::string func = + " sll r0, r0, 0\n" + "L264:\n" + " lwu v1, -4(a0)\n" + " lhu v1, 8(v1)\n" + " lw a0, 32(a0)\n" + " daddu v0, v1, a0\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function cpu-thread int)"; + std::string expected = "(the-as int (+ (-> arg0 type size) (the-as uint (-> arg0 stack-size))))"; + test_with_expr(func, type, expected, false); +} + +TEST_F(FormRegressionTest, RemoveExit) { + std::string func = + " sll r0, r0, 0\n" + "L262:\n" + " lwu v1, 88(s6)\n" + " beq s7, v1, L263\n" + " or v0, s7, r0\n" + + " lwu v1, 88(s6)\n" + " lwu v0, 4(v1)\n" + " sw v0, 88(s6)\n" + + "L263:\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function stack-frame)"; + std::string expected = + "(when\n" + " (-> pp stack-frame-top)\n" + " (set! v0-0 (-> pp stack-frame-top next))\n" + " (set! (-> pp stack-frame-top) v0-0)\n" + " v0-0\n" + " )"; + test_with_expr(func, type, expected, false); +} + +TEST_F(FormRegressionTest, RemoveMethod0ProcessTree) { + std::string func = + " sll r0, r0, 0\n" + " daddiu sp, sp, -32\n" + " sd ra, 0(sp)\n" + " sq gp, 16(sp)\n" + + " or gp, a2, r0\n" + " lw v1, object(s7)\n" + " lwu t9, 16(v1)\n" + " or v1, a1, r0\n" + " lhu a2, 8(a1)\n" + " or a1, v1, r0\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " sw gp, 0(v0)\n" + " addiu v1, r0, 256\n" + " sw v1, 4(v0)\n" + " sw s7, 8(v0)\n" + " sw s7, 12(v0)\n" + " sw s7, 16(v0)\n" + " sw v0, 24(v0)\n" + " daddiu v1, v0, 24\n" + " sw v1, 20(v0)\n" + " ld ra, 0(sp)\n" + " lq gp, 16(sp)\n" + " jr ra\n" + " daddiu sp, sp, 32"; + std::string type = "(function symbol type basic process-tree)"; + std::string expected = + "(begin\n" + " (set! v0-0 (object-new arg0 arg1 (the-as int (-> arg1 size))))\n" + " (set! (-> v0-0 name) arg2)\n" + " (set! (-> v0-0 mask) 256)\n" + " (set! (-> v0-0 parent) (quote #f))\n" + " (set! (-> v0-0 brother) (quote #f))\n" + " (set! (-> v0-0 child) (quote #f))\n" + " (set! (-> v0-0 self) v0-0)\n" + " (set! (-> v0-0 ppointer) (&-> v0-0 self))\n" + " v0-0\n" + " )"; + test_with_expr(func, type, expected, false, "process-tree"); +} + +TEST_F(FormRegressionTest, RemoveMethod3ProcessTree) { + std::string func = + " sll r0, r0, 0\n" + + " daddiu sp, sp, -32\n" + " sd ra, 0(sp)\n" + " sd fp, 8(sp)\n" + " or fp, t9, r0\n" + " sq gp, 16(sp)\n" + + " or gp, a0, r0\n" + " lw t9, format(s7)\n" + " daddiu a0, s7, #t\n" + " daddiu a1, fp, L340\n" + " or a2, gp, r0\n" + " lwu a3, -4(gp)\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " lw t9, format(s7)\n" + " daddiu a0, s7, #t\n" + " daddiu a1, fp, L339\n" + " lwu a2, 0(gp)\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " lw t9, format(s7)\n" + " daddiu a0, s7, #t\n" + " daddiu a1, fp, L338\n" + " lwu a2, 4(gp)\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " lw t9, format(s7)\n" + " daddiu a0, s7, #t\n" + " daddiu a1, fp, L337\n" + " lwu v1, 8(gp)\n" + " beq s7, v1, L258\n" + " or a2, s7, r0\n" + + " lwu v1, 0(v1)\n" + " lwu a2, 24(v1)\n" + + "L258:\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " lw t9, format(s7)\n" + " daddiu a0, s7, #t\n" + " daddiu a1, fp, L336\n" + " lwu v1, 12(gp)\n" + " beq s7, v1, L259\n" + " or a2, s7, r0\n" + + " lwu v1, 0(v1)\n" + " lwu a2, 24(v1)\n" + + "L259:\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " lw t9, format(s7)\n" + " daddiu a0, s7, #t\n" + " daddiu a1, fp, L335\n" + " lwu v1, 16(gp)\n" + " beq s7, v1, L260\n" + " or a2, s7, r0\n" + + " lwu v1, 0(v1)\n" + " lwu a2, 24(v1)\n" + + "L260:\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " or v0, gp, r0\n" + " ld ra, 0(sp)\n" + " ld fp, 8(sp)\n" + " lq gp, 16(sp)\n" + " jr ra\n" + " daddiu sp, sp, 32"; + std::string type = "(function process-tree process-tree)"; + // gross, but expected. see https://github.com/water111/jak-project/issues/254 + std::string expected = + "(begin\n" + " (format (quote #t) \"[~8x] ~A~%\" arg0 (-> arg0 type))\n" + " (format (quote #t) \"~Tname: ~S~%\" (-> arg0 name))\n" + " (format (quote #t) \"~Tmask: #x~X~%\" (-> arg0 mask))\n" + " (set! t9-3 format)\n" + " (set! a0-4 (quote #t))\n" + " (set! a1-3 \"~Tparent: ~A~%\")\n" + " (set! v1-0 (-> arg0 parent))\n" + " (t9-3 a0-4 a1-3 (if v1-0 (-> v1-0 0 self)))\n" + " (set! t9-4 format)\n" + " (set! a0-5 (quote #t))\n" + " (set! a1-4 \"~Tbrother: ~A~%\")\n" + " (set! v1-2 (-> arg0 brother))\n" + " (set! a2-4 (if v1-2 (-> v1-2 0 self)))\n" + " (t9-4 a0-5 a1-4 a2-4)\n" + " (set! t9-5 format)\n" + " (set! a0-6 (quote #t))\n" + " (set! a1-5 \"~Tchild: ~A~%\")\n" + " (set! v1-4 (-> arg0 child))\n" + " (t9-5 a0-6 a1-5 (if v1-4 (-> v1-4 0 self)))\n" + " arg0\n" + " )"; + test_with_expr(func, type, expected, false, "process-tree", + {{"L340", "[~8x] ~A~%"}, + {"L339", "~Tname: ~S~%"}, + {"L338", "~Tmask: #x~X~%"}, + {"L337", "~Tparent: ~A~%"}, + {"L336", "~Tbrother: ~A~%"}, + {"L335", "~Tchild: ~A~%"}}); +} + +TEST_F(FormRegressionTest, ExprMethod0Process) { + std::string func = + " sll r0, r0, 0\n" + "L254:\n" + " daddiu sp, sp, -48\n" + " sd ra, 0(sp)\n" + " sq s5, 16(sp)\n" + " sq gp, 32(sp)\n" + + " or s5, a2, r0\n" + " or gp, a3, r0\n" + " lw v1, symbol(s7)\n" + " lwu a2, -4(a0)\n" + " bne a2, v1, L255\n" + " sll r0, r0, 0\n" + + " lw v1, object(s7)\n" + " lwu t9, 16(v1)\n" + " lw v1, process(s7)\n" + " lhu v1, 8(v1)\n" + " daddu a2, v1, gp\n" + " jalr ra, t9\n" + " sll v0, ra, 0\n" + + " beq r0, r0, L256\n" + " sll r0, r0, 0\n" + + "L255:\n" + " daddiu v0, a0, 4\n" + + "L256:\n" + " sw s5, 0(v0)\n" + " daddiu v1, s7, dead\n" + " sw v1, 32(v0)\n" + " sw r0, 36(v0)\n" + " sw s7, 28(v0)\n" + " sw gp, 68(v0)\n" + " sw s7, 44(v0)\n" + " sw s7, 40(v0)\n" + " daddiu v1, v0, 108\n" + " sw v1, 84(v0)\n" + " sw v1, 76(v0)\n" + " lw v1, 68(v0)\n" + " daddiu v1, v1, 108\n" + " daddu v1, v1, v0\n" + " sw v1, 80(v0)\n" + " lwu v1, 80(v0)\n" + " sw v1, 88(v0)\n" + " sw s7, 88(v0)\n" + " sw s7, 52(v0)\n" + " sw s7, 72(v0)\n" + " sw s7, 48(v0)\n" + " sw s7, 56(v0)\n" + " sw s7, 60(v0)\n" + " sw s7, 64(v0)\n" + " sw s7, 8(v0)\n" + " sw s7, 12(v0)\n" + " sw s7, 16(v0)\n" + " sw v0, 24(v0)\n" + " daddiu v1, v0, 24\n" + " sw v1, 20(v0)\n" + " ld ra, 0(sp)\n" + " lq gp, 32(sp)\n" + " lq s5, 16(sp)\n" + " jr ra\n" + " daddiu sp, sp, 48"; + std::string type = "(function symbol type basic int process)"; + std::string expected = + "(begin\n" + " (set!\n" + " v0-0\n" + " (if\n" + " (= (-> arg0 type) symbol)\n" + " (object-new arg0 arg1 (the-as int (+ (-> process size) (the-as uint arg3))))\n" + " (+ (the-as int arg0) 4)\n" + " )\n" + " )\n" + " (set! (-> (the-as process v0-0) name) arg2)\n" + " (set! (-> v0-0 status) (quote dead))\n" + " (set! (-> v0-0 pid) 0)\n" + " (set! (-> v0-0 pool) (quote #f))\n" + " (set! (-> v0-0 allocated-length) arg3)\n" + " (set! (-> v0-0 top-thread) (quote #f))\n" + " (set! (-> v0-0 main-thread) (quote #f))\n" + " (set! v1-5 (-> v0-0 stack))\n" + " (set! (-> v0-0 heap-cur) v1-5)\n" + " (set! (-> v0-0 heap-base) v1-5)\n" + " (set! (-> v0-0 heap-top) (&-> v0-0 stack (-> v0-0 allocated-length)))\n" + " (set! (-> v0-0 stack-frame-top) (-> v0-0 heap-top))\n" + " (set! (-> v0-0 stack-frame-top) (quote #f))\n" + " (set! (-> v0-0 state) (quote #f))\n" + " (set! (-> v0-0 next-state) (quote #f))\n" + " (set! (-> v0-0 entity) (quote #f))\n" + " (set! (-> v0-0 trans-hook) (quote #f))\n" + " (set! (-> v0-0 post-hook) (quote #f))\n" + " (set! (-> v0-0 event-hook) (quote #f))\n" + " (set! (-> v0-0 parent) (quote #f))\n" + " (set! (-> v0-0 brother) (quote #f))\n" + " (set! (-> v0-0 child) (quote #f))\n" + " (set! (-> v0-0 self) v0-0)\n" + " (set! (-> v0-0 ppointer) (&-> v0-0 self))\n" + " v0-0\n" + " )"; + test_with_expr(func, type, expected, false, "process", {}, + parse_hint_json("[\t\t[12, [\"a0\", \"int\"]],\n" + "\t\t[13, [\"v0\", \"process\"]]]")); +} + +// TEST_F(FormRegressionTest, ExprInspectProcessHeap) { +// std::string func = +// " sll r0, r0, 0\n" +// "L251:\n" +// " daddiu sp, sp, -64\n" +// " sd ra, 0(sp)\n" +// " sq s4, 16(sp)\n" +// " sq s5, 32(sp)\n" +// " sq gp, 48(sp)\n" +// +// " or gp, a0, r0\n" +// " lwu v1, 76(gp)\n" +// " daddiu s5, v1, 4\n" +// " beq r0, r0, L253\n" +// " sll r0, r0, 0\n" +// +// "L252:\n" +// " or a0, s5, r0\n" +// " lwu v1, -4(a0)\n" +// " lwu t9, 28(v1)\n" +// " jalr ra, t9\n" +// " sll v0, ra, 0\n" +// +// " or v1, v0, r0\n" +// " addiu s4, r0, -16\n" +// " or a0, s5, r0\n" +// " lwu v1, -4(a0)\n" +// " lwu t9, 36(v1)\n" +// " jalr ra, t9\n" +// " sll v0, ra, 0\n" +// +// " or v1, v0, r0\n" +// " daddiu v1, v1, 15\n" +// " and v1, s4, v1\n" +// " daddu s5, s5, v1\n" +// +// "L253:\n" +// " lwu v1, 84(gp)\n" +// " slt v1, s5, v1\n" +// " bne v1, r0, L252\n" +// " sll r0, r0, 0\n" +// +// " or v0, s7, r0\n" +// " ld ra, 0(sp)\n" +// " lq gp, 48(sp)\n" +// " lq s5, 32(sp)\n" +// " lq s4, 16(sp)\n" +// " jr ra\n" +// " daddiu sp, sp, 64"; +// std::string type = "(function process symbol)"; +// std::string expected = +// "(begin\n" +// " (set!\n" +// " v0-0\n" +// " (if\n" +// " (= (-> arg0 type) symbol)\n" +// " (object-new arg0 arg1 (the-as int (+ (-> process size) (the-as uint arg3))))\n" +// " (+ (the-as int arg0) 4)\n" +// " )\n" +// " )\n" +// " (set! (-> (the-as process v0-0) name) arg2)\n" +// " (set! (-> v0-0 status) (quote dead))\n" +// " (set! (-> v0-0 pid) 0)\n" +// " (set! (-> v0-0 pool) (quote #f))\n" +// " (set! (-> v0-0 allocated-length) arg3)\n" +// " (set! (-> v0-0 top-thread) (quote #f))\n" +// " (set! (-> v0-0 main-thread) (quote #f))\n" +// " (set! v1-5 (-> v0-0 stack))\n" +// " (set! (-> v0-0 heap-cur) v1-5)\n" +// " (set! (-> v0-0 heap-base) v1-5)\n" +// " (set! (-> v0-0 heap-top) (&-> v0-0 stack (-> v0-0 allocated-length)))\n" +// " (set! (-> v0-0 stack-frame-top) (-> v0-0 heap-top))\n" +// " (set! (-> v0-0 stack-frame-top) (quote #f))\n" +// " (set! (-> v0-0 state) (quote #f))\n" +// " (set! (-> v0-0 next-state) (quote #f))\n" +// " (set! (-> v0-0 entity) (quote #f))\n" +// " (set! (-> v0-0 trans-hook) (quote #f))\n" +// " (set! (-> v0-0 post-hook) (quote #f))\n" +// " (set! (-> v0-0 event-hook) (quote #f))\n" +// " (set! (-> v0-0 parent) (quote #f))\n" +// " (set! (-> v0-0 brother) (quote #f))\n" +// " (set! (-> v0-0 child) (quote #f))\n" +// " (set! (-> v0-0 self) v0-0)\n" +// " (set! (-> v0-0 ppointer) (&-> v0-0 self))\n" +// " v0-0\n" +// " )"; +// test_with_expr(func, type, expected, false, "", {}, +// parse_hint_json("[\t\t[4, [\"s5\", \"basic\"]],\n" +// "\t\t[17, [\"s5\", \"int\"]]]")); +//} \ No newline at end of file diff --git a/test/decompiler/test_math_decomp.cpp b/test/decompiler/test_math_decomp.cpp new file mode 100644 index 0000000000..93573b1b65 --- /dev/null +++ b/test/decompiler/test_math_decomp.cpp @@ -0,0 +1,106 @@ +#include "gtest/gtest.h" +#include "FormRegressionTest.h" + +using namespace decompiler; + +TEST_F(FormRegressionTest, ExprTruncate) { + std::string func = + " sll r0, r0, 0\n" + " mtc1 f0, a0\n" + " cvt.w.s f0, f0\n" + " cvt.s.w f0, f0\n" + " mfc1 v0, f0\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function float float)"; + std::string expected = "(the float (the int arg0))"; + test_with_expr(func, type, expected); +} + +TEST_F(FormRegressionTest, ExprIntegralP) { + std::string func = + " sll r0, r0, 0\n" + " mtc1 f0, a0\n" + " cvt.w.s f0, f0\n" + " cvt.s.w f0, f0\n" + " mfc1 v1, f0\n" + " mtc1 f0, v1\n" + " mtc1 f1, a0\n" + " c.eq.s f0, f1\n" + " bc1t L31\n" + " daddiu v0, s7, 8\n" + + " or v0, s7, r0\n" + "L31:\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function float float)"; + std::string expected = "(the-as float (= (the float (the int arg0)) arg0))"; + test_with_expr(func, type, expected); +} + +TEST_F(FormRegressionTest, ExprFractionalPart) { + std::string func = + " sll r0, r0, 0\n" + " mtc1 f0, a0\n" + " mtc1 f1, a0\n" + " cvt.w.s f1, f1\n" + " cvt.s.w f1, f1\n" + " mfc1 v1, f1\n" + " mtc1 f1, v1\n" + " sub.s f0, f0, f1\n" + " mfc1 v0, f0\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function float float)"; + std::string expected = "(- arg0 (the float (the int arg0)))"; + test_with_expr(func, type, expected); +} + +TEST_F(FormRegressionTest, ExprSeek) { + std::string func = + " sll r0, r0, 0\n" + "L24:\n" + " mtc1 f0, a0\n" + " mtc1 f3, a1\n" + " mtc1 f1, a2\n" + " sub.s f2, f3, f0\n" + " abs.s f4, f2\n" + " c.lt.s f1, f4\n" + " bc1t L25\n" + " sll r0, r0, 0\n" + + " mfc1 v0, f3\n" + " beq r0, r0, L27\n" + " sll r0, r0, 0\n" + + "L25:\n" + " mtc1 f3, r0\n" + " c.lt.s f2, f3\n" + " bc1t L26\n" + " sll r0, r0, 0\n" + + " add.s f0, f0, f1\n" + " mfc1 v0, f0\n" + " beq r0, r0, L27\n" + " sll r0, r0, 0\n" + + "L26:\n" + " sub.s f0, f0, f1\n" + " mfc1 v0, f0\n" + + "L27:\n" + " jr ra\n" + " daddu sp, sp, r0"; + std::string type = "(function float float float float)"; + std::string expected = + "(begin\n" + " (set! f2-0 (- arg1 arg0))\n" + " (cond\n" + " ((>= arg2 (fabs f2-0)) arg1)\n" + " ((>= f2-0 0) (+ arg0 arg2))\n" + " (else (- arg0 arg2))\n" + " )\n" + " )"; + test_with_expr(func, type, expected); +} \ No newline at end of file