diff --git a/boot_kernel.sh b/boot_kernel.sh index 5129fc191d..062c75ec0c 100755 --- a/boot_kernel.sh +++ b/boot_kernel.sh @@ -3,4 +3,4 @@ # Directory of this script DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -$DIR/build/game/gk -fakeiso -debug "$@" +$DIR/build/game/gk -fakeiso -debug -nodisplay"$@" diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 3090fbe587..40f409c49f 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -19,6 +19,7 @@ add_library(common util/DgoReader.cpp util/DgoWriter.cpp util/FileUtil.cpp + util/json_util.cpp util/Timer.cpp ) diff --git a/common/goos/PrettyPrinter.cpp b/common/goos/PrettyPrinter.cpp index bb5f0bda46..f0bc951013 100644 --- a/common/goos/PrettyPrinter.cpp +++ b/common/goos/PrettyPrinter.cpp @@ -509,6 +509,10 @@ void insertSpecialBreaks(NodePool& pool, PrettyPrinterNode* node) { } } + if (name == "begin") { + breakList(pool, node->paren); + } + if (name == "defun" || name == "defmethod" || name == "defun-debug" || name == "let" || name == "let*") { auto* first_list = getNextListOnLine(node); diff --git a/common/type_system/Type.h b/common/type_system/Type.h index c6941ee0b0..09afee0cb9 100644 --- a/common/type_system/Type.h +++ b/common/type_system/Type.h @@ -185,6 +185,7 @@ class Field { void set_dynamic(); void set_array(int size); void set_inline(); + void mark_as_user_placed() { m_placed_by_user = true; } std::string print() const; const TypeSpec& type() const { return m_type; } bool is_inline() const { return m_inline; } @@ -193,6 +194,7 @@ class Field { const std::string& name() const { return m_name; } int offset() const { return m_offset; } bool skip_in_decomp() const { return m_skip_in_static_decomp; } + bool user_placed() const { return m_placed_by_user; } bool operator==(const Field& other) const; int alignment() const { @@ -221,6 +223,7 @@ class Field { int m_array_size = 0; int m_alignment = -1; bool m_skip_in_static_decomp = false; + bool m_placed_by_user = false; // was this field placed manually by the user? }; class StructureType : public ReferenceType { @@ -243,6 +246,8 @@ class StructureType : public ReferenceType { bool is_dynamic() const { return m_dynamic; } ~StructureType() = default; void set_pack(bool pack) { m_pack = pack; } + bool is_packed() const { return m_pack; } + bool is_allowed_misalign() const { return m_allow_misalign; }; void set_allow_misalign(bool misalign) { m_allow_misalign = misalign; } protected: diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index 331833094e..5622fcd61f 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -301,7 +301,10 @@ Type* TypeSystem::lookup_type_allow_partial_def(const std::string& name) const { } else if (fwd_dec->second == BASIC) { return lookup_type("basic"); } else { - fmt::print("[TypeSystem] The type {} is not fully define (allow partial).\n", name); + fmt::print( + "[TypeSystem] The type {} is known to be a type, but has not been defined with deftype " + "or properly forward declared with declare-type\n", + name); } } else { @@ -646,6 +649,7 @@ int TypeSystem::add_field_to_type(StructureType* type, offset = align(type->get_size_in_memory(), field_alignment); } else { int aligned_offset = align(offset, field_alignment); + field.mark_as_user_placed(); if (offset != aligned_offset) { fmt::print( "[TypeSystem] Tried to overwrite offset of field to be {}, but it is not aligned " @@ -1277,6 +1281,17 @@ void TypeSystem::add_field_to_bitfield(BitFieldType* type, */ std::string TypeSystem::generate_deftype_footer(const Type* type) const { std::string result; + + auto as_structure = dynamic_cast(type); + if (as_structure) { + if (as_structure->is_packed()) { + result.append(" :pack-me\n"); + } + if (as_structure->is_allowed_misalign()) { + result.append(" :allow-misaligned"); + } + } + auto method_count = get_next_method_id(type); result.append(fmt::format(" :method-count-assert {}\n", get_next_method_id(type))); result.append(fmt::format(" :size-assert #x{:x}\n", type->get_size_in_memory())); @@ -1332,8 +1347,9 @@ std::string TypeSystem::generate_deftype_for_structure(const StructureType* st) int longest_type_name = 0; int longest_mods = 0; - std::string inline_string = ":inline"; - std::string dynamic_string = ":dynamic"; + const std::string inline_string = ":inline"; + const std::string dynamic_string = ":dynamic"; + const std::string user_offset_string = ":offset xxx"; for (size_t i = st->first_unique_field_idx(); i < st->fields().size(); i++) { const auto& field = st->fields().at(i); @@ -1359,6 +1375,13 @@ std::string TypeSystem::generate_deftype_for_structure(const StructureType* st) } mods += dynamic_string.size(); } + + if (field.user_placed()) { + if (mods) { + mods++; + } + mods += user_offset_string.size(); + } longest_mods = std::max(longest_mods, mods); } @@ -1385,11 +1408,18 @@ std::string TypeSystem::generate_deftype_for_structure(const StructureType* st) mods += dynamic_string; mods += " "; } - result.append(mods); - result.append(longest_mods - int(mods.size() - 1), ' '); - result.append(":offset-assert "); - result.append(std::to_string(field.offset())); + if (field.user_placed()) { + mods += fmt::format(":offset {:3d}", field.offset()); + } + result.append(mods); + + if (!field.user_placed()) { + result.append(longest_mods - int(mods.size() - 1), ' '); + result.append(":offset-assert "); + result.append(std::to_string(field.offset())); + } + result.append(")\n "); } diff --git a/common/type_system/deftype.cpp b/common/type_system/deftype.cpp index af86ecd251..54fda703fc 100644 --- a/common/type_system/deftype.cpp +++ b/common/type_system/deftype.cpp @@ -198,15 +198,7 @@ void declare_method(Type* type, TypeSystem* type_system, const goos::Object& def TypeSpec function_typespec("function"); for_each_in_list(args, [&](const goos::Object& o) { - if (o.is_symbol()) { - function_typespec.add_arg(parse_typespec(type_system, o)); - } else { - auto next = cdr(&o); - function_typespec.add_arg(parse_typespec(type_system, car(next))); - if (!cdr(next)->is_empty_list()) { - throw std::runtime_error("too many things in method def arg type: " + def.print()); - }; - } + function_typespec.add_arg(parse_typespec(type_system, o)); }); function_typespec.add_arg(parse_typespec(type_system, return_type)); @@ -415,7 +407,7 @@ BitFieldTypeDefResult parse_bitfield_type_def(BitFieldType* type, } // namespace -TypeSpec parse_typespec(TypeSystem* type_system, const goos::Object& src) { +TypeSpec parse_typespec(const TypeSystem* type_system, const goos::Object& src) { if (src.is_symbol()) { return type_system->make_typespec(symbol_string(src)); } else if (src.is_pair()) { diff --git a/common/type_system/deftype.h b/common/type_system/deftype.h index d9369af2a7..bf8b31582b 100644 --- a/common/type_system/deftype.h +++ b/common/type_system/deftype.h @@ -18,4 +18,4 @@ struct DeftypeResult { }; DeftypeResult parse_deftype(const goos::Object& deftype, TypeSystem* ts); -TypeSpec parse_typespec(TypeSystem* type_system, const goos::Object& src); +TypeSpec parse_typespec(const TypeSystem* type_system, const goos::Object& src); diff --git a/common/util/Range.h b/common/util/Range.h new file mode 100644 index 0000000000..cae263593d --- /dev/null +++ b/common/util/Range.h @@ -0,0 +1,64 @@ +#pragma once + +#include + +/*! + * A Range is similar to a Python range. It can represent an empty range or a range of consecutive + * values. It supports iterators so you can write a loop like: + * for (auto x : Range(1, 4)) { + * } + * and it will work like you expect, without allocating memory for all the elements. + * + * A Range's iterator actually store values, not references, so this is designed to be used + * with small things, like integers. + * + * Note - for size and iterator distances, a Range will use the same type as its elements. + */ +template +class Range { + public: + Range() = default; + Range(const T& start, const T& end) : m_start(start), m_end(end) {} + const T& first() const { return m_start; } + const T& last() const { return m_end; } + bool contains(T& val) const { return val >= m_start && val < m_end; } + bool empty() const { return m_end <= m_start; } + T size() const { return m_end - m_start; } + + struct Iterator { + using iterator_category = std::input_iterator_tag; + using difference_type = T; + using value_type = T; + using pointer = const T*; + using reference = const T&; + + Iterator(const T& val) : m_val(val) {} + + reference operator*() const { return m_val; } + pointer operator->() { return &m_val; } + + Iterator& operator++() { + m_val++; + return *this; + } + + const Iterator operator++(int) { + Iterator old = *this; + ++(*this); + return old; + } + + friend bool operator==(const Iterator& a, const Iterator& b) { return a.m_val == b.m_val; } + friend bool operator!=(const Iterator& a, const Iterator& b) { return a.m_val != b.m_val; } + + private: + T m_val; + }; + + Iterator begin() { return Iterator(m_start); } + Iterator end() { return Iterator(m_end); } + + private: + T m_start = {}; + T m_end = {}; +}; \ No newline at end of file diff --git a/common/util/json_util.cpp b/common/util/json_util.cpp new file mode 100644 index 0000000000..95d098ecef --- /dev/null +++ b/common/util/json_util.cpp @@ -0,0 +1,100 @@ +#include +#include "json_util.h" + +/*! + * Strip out // and / * comments + * Does not strip comments from within a string. + * Assumes \" is used to escape quotes inside of a string. + */ +std::string strip_cpp_style_comments(const std::string& input) { + std::string output; + + enum State { NORMAL, BLOCK_COMMENT, LINE_COMMENT, STRING } state = NORMAL; + + for (size_t i = 0; i < input.size(); i++) { + char c = input[i]; + char next = i < (input.size() - 1) ? input[i + 1] : '\0'; + char prev = i > 0 ? input[i - 1] : '\0'; + + switch (state) { + case NORMAL: + if (c == '/' && next == '*') { + // Normal -> block + state = BLOCK_COMMENT; + i++; // skip over * + } else if (c == '/' && next == '/') { + // Normal -> line + state = LINE_COMMENT; + i++; + } else if (c == '"') { + // Normal -> string + state = STRING; + output.push_back(c); + } else { + // Normal -> normal + output.push_back(c); + } + break; + case BLOCK_COMMENT: + if (c == '*' && next == '/') { + // Block -> normal + state = NORMAL; + i++; + } + // otherwise stay in block comment + break; + case LINE_COMMENT: + if (c == '\n') { + state = NORMAL; + output.push_back(c); + } + break; + case STRING: + if (c == '"' && prev != '\\') { + state = NORMAL; + } + output.push_back(c); + break; + default: + assert(false); + } + } + + if (state == BLOCK_COMMENT) { + throw std::runtime_error("strip_cpp_style_comments ended in a block comment"); + } + + if (state == STRING) { + throw std::runtime_error("strip_cpp_style_comments ended in a string."); + } + + return output; +} + +/*! + * Parse JSON file with comments stripped. Unlike the default comment stripping feature + * of nlohmann::json, this allows you to have multiple comments in a row! + */ +nlohmann::json parse_commented_json(const std::string& input) { + return nlohmann::json::parse(strip_cpp_style_comments(input)); +} + +/*! + * Parse something like: + * 2 -> Range(2, 3) + * [2, 3] -> Range(2, 3) + * [2, 10] -> Range(2, 10) + */ +Range parse_json_optional_integer_range(const nlohmann::json& json) { + if (json.is_number_integer()) { + auto first = json.get(); + return Range(first, first + 1); + } else if (json.is_array()) { + if (json.size() != 2) { + throw std::runtime_error("Invalid array size in parse_json_optional_integer_range"); + } + return {json[0].get(), json[1].get()}; + } else { + throw std::runtime_error("Invalid json as input to parse_json_optional_integer_range"); + } +} \ No newline at end of file diff --git a/common/util/json_util.h b/common/util/json_util.h new file mode 100644 index 0000000000..d068e93140 --- /dev/null +++ b/common/util/json_util.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include "common/util/Range.h" +#include "third-party/json.hpp" + +std::string strip_cpp_style_comments(const std::string& input); +nlohmann::json parse_commented_json(const std::string& input); +Range parse_json_optional_integer_range(const nlohmann::json& json); diff --git a/common/versions.h b/common/versions.h index e0342f17da..638af3e087 100644 --- a/common/versions.h +++ b/common/versions.h @@ -10,7 +10,7 @@ namespace versions { // language version (OpenGOAL) constexpr s32 GOAL_VERSION_MAJOR = 0; -constexpr s32 GOAL_VERSION_MINOR = 6; +constexpr s32 GOAL_VERSION_MINOR = 7; constexpr int DECOMPILER_VERSION = 4; diff --git a/decompiler/CMakeLists.txt b/decompiler/CMakeLists.txt index 3a7f918906..388b463e8d 100644 --- a/decompiler/CMakeLists.txt +++ b/decompiler/CMakeLists.txt @@ -6,6 +6,7 @@ add_library( analysis/cfg_builder.cpp analysis/expression_build.cpp analysis/final_output.cpp + analysis/inline_asm_rewrite.cpp analysis/insert_lets.cpp analysis/reg_usage.cpp analysis/variable_naming.cpp @@ -39,6 +40,7 @@ add_library( IR2/FormExpressionAnalysis.cpp IR2/FormStack.cpp IR2/GenericElementMatcher.cpp + IR2/OpenGoalMapping.cpp ObjectFile/LinkedObjectFile.cpp ObjectFile/LinkedObjectFileCreation.cpp @@ -50,8 +52,7 @@ add_library( util/DecompilerTypeSystem.cpp util/TP_Type.cpp - config.cpp - "analysis/inline_asm_rewrite.cpp" "analysis/inline_asm_rewrite.h" "IR2/OpenGoalMapping.cpp") + config.cpp) target_link_libraries(decomp lzokay diff --git a/decompiler/Disasm/Register.h b/decompiler/Disasm/Register.h index 88c7e39a14..78e9719a88 100644 --- a/decompiler/Disasm/Register.h +++ b/decompiler/Disasm/Register.h @@ -6,6 +6,7 @@ */ #include +#include #include namespace decompiler { @@ -135,6 +136,10 @@ class Register { Register() = default; Register(Reg::RegisterKind kind, uint32_t num); Register(const std::string& name); + static Register get_arg_reg(int idx) { + assert(idx >= 0 && idx < 8); + return Register(Reg::GPR, Reg::A0 + idx); + } const char* to_charp() const; std::string to_string() const; Reg::RegisterKind get_kind() const; diff --git a/decompiler/Function/Function.h b/decompiler/Function/Function.h index 59dfc58aae..9794d70a0e 100644 --- a/decompiler/Function/Function.h +++ b/decompiler/Function/Function.h @@ -94,7 +94,7 @@ class Function { bool run_type_analysis_ir2(const TypeSpec& my_type, DecompilerTypeSystem& dts, LinkedObjectFile& file, - const std::unordered_map>& hints, + const std::unordered_map>& casts, const std::unordered_map& label_types); BlockTopologicalSort bb_topo_sort(); @@ -169,6 +169,7 @@ class Function { std::string debug_form_string; bool print_debug_forms = false; bool expressions_succeeded = false; + bool types_succeeded = false; } ir2; private: diff --git a/decompiler/Function/TypeAnalysis.cpp b/decompiler/Function/TypeAnalysis.cpp index f2eb3b5b75..b9ecd7d52e 100644 --- a/decompiler/Function/TypeAnalysis.cpp +++ b/decompiler/Function/TypeAnalysis.cpp @@ -22,10 +22,29 @@ TypeState construct_initial_typestate(const TypeSpec& f_ts) { return result; } -void apply_hints(const std::vector& hints, TypeState* state, DecompilerTypeSystem& dts) { - for (auto& hint : hints) { +/*! + * Modify the the given type state based on the given casts. + */ +void modify_input_types_for_casts( + const std::vector& casts, + TypeState* state, + std::unordered_map* changed_types, + DecompilerTypeSystem& dts) { + for (auto& cast : casts) { try { - state->get(hint.reg) = TP_Type::make_from_ts(dts.parse_type_spec(hint.type_name)); + auto type_from_cast = TP_Type::make_from_ts(dts.parse_type_spec(cast.type_name)); + auto original_type = state->get(cast.reg); + // fmt::print("Cast reg {} : {} -> {}\n", cast.reg.to_string(), original_type.print(), + // type_from_cast.print()); + if (original_type != type_from_cast) { + // the cast will have an effect on types. If we are removing the original type, remember it + if (changed_types && changed_types->find(cast.reg) == changed_types->end()) { + (*changed_types)[cast.reg] = original_type; + } + + // and actually update the typ + state->get(cast.reg) = type_from_cast; + } } catch (std::exception& e) { printf("failed to parse hint: %s\n", e.what()); assert(false); @@ -33,13 +52,16 @@ void apply_hints(const std::vector& hints, TypeState* state, Decompile } } -void try_apply_hints(int idx, - const std::unordered_map>& hints, - TypeState* state, - DecompilerTypeSystem& dts) { - auto kv = hints.find(idx); - if (kv != hints.end()) { - apply_hints(kv->second, state, dts); +void try_modify_input_types_for_casts( + int idx, + const std::unordered_map>& casts, + TypeState* state, + std::unordered_map* changed_types, + DecompilerTypeSystem& dts) { + auto kv = casts.find(idx); + if (kv != casts.end()) { + // fmt::print("at idx {}, casting:\n", idx); + modify_input_types_for_casts(kv->second, state, changed_types, dts); } } } // namespace @@ -48,10 +70,10 @@ bool Function::run_type_analysis_ir2( const TypeSpec& my_type, DecompilerTypeSystem& dts, LinkedObjectFile& file, - const std::unordered_map>& hints, + const std::unordered_map>& casts, const std::unordered_map& label_types) { (void)file; - ir2.env.set_type_hints(hints); + ir2.env.set_type_casts(casts); ir2.env.set_label_types(label_types); // STEP 0 - set decompiler type system settings for this function. In config we can manually // specify some settings for type propagation to reduce the strictness of type propagation. @@ -95,7 +117,8 @@ bool Function::run_type_analysis_ir2( // STEP 2 - initialize type state for the first block to the function argument types. block_init_types.at(0) = construct_initial_typestate(my_type); // and add hints from config - try_apply_hints(0, hints, &block_init_types.at(0), dts); + // TODO - this should be moved into the construct_initial_typestate + // try_apply_hints(0, hints, &block_init_types.at(0), dts); // STEP 3 - propagate types until the result stops changing bool run_again = true; @@ -108,14 +131,14 @@ bool Function::run_type_analysis_ir2( 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++) { // apply type hints only if we are not the first op. - if (op_id != aop->block_id_to_first_atomic_op.at(block_id)) { - try_apply_hints(op_id, hints, init_types, dts); - } + + std::unordered_map restore_cast_types; + // if (op_id != aop->block_id_to_first_atomic_op.at(block_id)) { + try_modify_input_types_for_casts(op_id, casts, init_types, &restore_cast_types, dts); + //} auto& op = aop->ops.at(op_id); - // while the implementation of propagate_types_internal is in progress, it may throw - // for unimplemented cases. Eventually this try/catch should be removed. try { op_types.at(op_id) = op->propagate_types(*init_types, ir2.env, dts); } catch (std::runtime_error& e) { @@ -125,7 +148,15 @@ bool Function::run_type_analysis_ir2( return false; } - // todo, set run again?? + // undo casts. + for (auto& restore_kv : restore_cast_types) { + if (std::find(op->write_regs().begin(), op->write_regs().end(), restore_kv.first) == + op->write_regs().end()) { + op_types.at(op_id).get(restore_kv.first) = restore_kv.second; + } + + init_types->get(restore_kv.first) = restore_kv.second; + } // for the next op... init_types = &op_types.at(op_id); @@ -135,8 +166,9 @@ bool Function::run_type_analysis_ir2( for (auto succ_block_id : {block.succ_ft, block.succ_branch}) { if (succ_block_id != -1) { // apply hint - try_apply_hints(aop->block_id_to_first_atomic_op.at(succ_block_id), hints, init_types, - dts); + // try_apply_hints(aop->block_id_to_first_atomic_op.at(succ_block_id), hints, + // init_types, + // dts); // set types to LCA (current, new) if (dts.tp_lca(&block_init_types.at(succ_block_id), *init_types)) { @@ -153,6 +185,19 @@ bool Function::run_type_analysis_ir2( warnings.info("Return type mismatch {} vs {}.", last_type.print(), my_type.last_arg().print()); } + // and apply final casts: + for (auto block_id : order.vist_order) { + 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, casts, &block_init_types.at(block_id), nullptr, + dts); + } else { + try_modify_input_types_for_casts(op_id, casts, &op_types.at(op_id - 1), nullptr, dts); + } + } + } + ir2.env.set_types(block_init_types, op_types, *ir2.atomic_ops); return true; diff --git a/decompiler/IR2/AtomicOp.cpp b/decompiler/IR2/AtomicOp.cpp index ffdb7cd05f..fe09ed918b 100644 --- a/decompiler/IR2/AtomicOp.cpp +++ b/decompiler/IR2/AtomicOp.cpp @@ -32,10 +32,10 @@ goos::Object RegisterAccess::to_form(const Env& env, Print mode) const { return pretty_print::to_symbol(fmt::format("{}-{:03d}-{}", m_reg.to_charp(), m_atomic_idx, m_mode == AccessMode::READ ? 'r' : 'w')); case Print::AS_VARIABLE: - return env.get_variable_name(m_reg, m_atomic_idx, m_mode); + return env.get_variable_name_with_cast(m_reg, m_atomic_idx, m_mode); case Print::AUTOMATIC: if (env.has_local_vars()) { - return env.get_variable_name(m_reg, m_atomic_idx, m_mode); + return env.get_variable_name_with_cast(m_reg, m_atomic_idx, m_mode); } else { return pretty_print::to_symbol(m_reg.to_string()); } diff --git a/decompiler/IR2/AtomicOp.h b/decompiler/IR2/AtomicOp.h index 4f029bce88..069b46a2b1 100644 --- a/decompiler/IR2/AtomicOp.h +++ b/decompiler/IR2/AtomicOp.h @@ -284,6 +284,7 @@ class SetVarOp : public AtomicOp { private: RegisterAccess m_dst; SimpleExpression m_src; + std::optional m_source_type; }; /*! @@ -464,6 +465,7 @@ class LoadVarOp : public AtomicOp { int m_size = -1; RegisterAccess m_dst; SimpleExpression m_src; + std::optional m_type; }; /*! diff --git a/decompiler/IR2/AtomicOpForm.cpp b/decompiler/IR2/AtomicOpForm.cpp index 1ff9aceebf..92bc6df09b 100644 --- a/decompiler/IR2/AtomicOpForm.cpp +++ b/decompiler/IR2/AtomicOpForm.cpp @@ -59,14 +59,17 @@ FormElement* SetVarOp::get_as_form(FormPool& pool, const Env& env) const { } auto load = pool.alloc_single_element_form(nullptr, source, rd.addr_of, tokens); - return pool.alloc_element(m_dst, load, true); + + return pool.alloc_element(m_dst, load, true, + m_source_type.value_or(TypeSpec("object"))); } } } // create element auto source = pool.alloc_single_element_form(nullptr, m_src, m_my_idx); - auto result = pool.alloc_element(m_dst, source, is_sequence_point()); + auto result = pool.alloc_element(m_dst, source, is_sequence_point(), + m_source_type.value_or(TypeSpec("object"))); // do some analysis to look for coloring moves which are already eliminated, // dead sets, and dead set falses. @@ -101,7 +104,7 @@ FormElement* AsmOp::get_as_form(FormPool& pool, const Env&) const { FormElement* SetVarConditionOp::get_as_form(FormPool& pool, const Env& env) const { return pool.alloc_element( m_dst, pool.alloc_single_form(nullptr, m_condition.get_as_form(pool, env, m_my_idx)), - is_sequence_point()); + is_sequence_point(), TypeSpec("symbol")); } FormElement* StoreOp::get_as_form(FormPool& pool, const Env& env) const { @@ -309,7 +312,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const { auto source = pool.alloc_single_element_form( nullptr, SimpleAtom::make_var(ro.var).as_expr(), m_my_idx); auto load = pool.alloc_single_element_form(nullptr, source, false, tokens); - return pool.alloc_element(m_dst, load, true); + return pool.alloc_element(m_dst, load, true, + m_type.value_or(TypeSpec("object"))); } // todo structure method @@ -321,7 +325,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const { // access method vtable. The input is type + (4 * method), and the 16 is the offset // of method 0. auto load = pool.alloc_single_element_form(nullptr, ro.var); - return pool.alloc_element(m_dst, load, true); + return pool.alloc_element(m_dst, load, true, + m_type.value_or(TypeSpec("object"))); } if (input_type.kind == TP_Type::Kind::OBJECT_PLUS_PRODUCT_WITH_CONSTANT) { @@ -348,7 +353,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const { // different in different cases. auto load = pool.alloc_single_element_form( nullptr, ro.var, tokens, input_type.get_multiplier(), ro.offset); - return pool.alloc_element(m_dst, load, true); + return pool.alloc_element(m_dst, load, true, + m_type.value_or(TypeSpec("object"))); } } @@ -363,7 +369,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const { auto load = pool.alloc_single_element_form( nullptr, GenericOperator::make_fixed(FixedOperatorKind::CDR), source); // cdr = another pair. - return pool.alloc_element(m_dst, load, true); + return pool.alloc_element(m_dst, load, true, + m_type.value_or(TypeSpec("object"))); } else if (ro.offset == -2) { // car = some object. auto source = pool.alloc_single_element_form( @@ -371,7 +378,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const { auto load = pool.alloc_single_element_form( nullptr, GenericOperator::make_fixed(FixedOperatorKind::CAR), source); // cdr = another pair. - return pool.alloc_element(m_dst, load, true); + return pool.alloc_element(m_dst, load, true, + m_type.value_or(TypeSpec("object"))); } } @@ -400,7 +408,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const { auto load = pool.alloc_single_element_form(nullptr, source, rd.addr_of, tokens); - return pool.alloc_element(m_dst, load, true); + return pool.alloc_element(m_dst, load, true, + m_type.value_or(TypeSpec("object"))); } if (input_type.typespec() == TypeSpec("pointer")) { @@ -433,7 +442,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const { nullptr, TypeSpec("pointer", {TypeSpec(cast_type)}), dest); auto deref = pool.alloc_single_element_form(nullptr, cast_dest, false, std::vector()); - return pool.alloc_element(m_dst, deref, true); + return pool.alloc_element(m_dst, deref, true, + m_type.value_or(TypeSpec("object"))); } } } @@ -453,7 +463,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const { float value; memcpy(&value, &word.data, 4); auto float_elt = pool.alloc_single_element_form(nullptr, value); - return pool.alloc_element(m_dst, float_elt, true); + return pool.alloc_element(m_dst, float_elt, true, + m_type.value_or(TypeSpec("object"))); } else if (hint->second.type_name == "uint64" && m_kind != Kind::FLOAT && m_size == 8) { assert((label.offset % 8) == 0); auto word0 = env.file->words_by_seg.at(label.target_segment).at(label.offset / 4); @@ -466,7 +477,8 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const { memcpy(((u8*)&value) + 4, &word1.data, 4); auto val_elt = pool.alloc_single_element_form( nullptr, fmt::format("#x{:x}", value)); - return pool.alloc_element(m_dst, val_elt, true); + return pool.alloc_element(m_dst, val_elt, true, + m_type.value_or(TypeSpec("object"))); } } } @@ -474,7 +486,7 @@ FormElement* LoadVarOp::get_as_form(FormPool& pool, const Env& env) const { auto source = pool.alloc_single_element_form(nullptr, m_src, m_my_idx); auto load = pool.alloc_single_element_form(nullptr, source, m_size, m_kind); - return pool.alloc_element(m_dst, load, true); + return pool.alloc_element(m_dst, load, true, m_type.value_or(TypeSpec("object"))); } FormElement* BranchOp::get_as_form(FormPool& pool, const Env&) const { @@ -500,7 +512,14 @@ FormElement* CallOp::get_as_form(FormPool& pool, const Env& env) const { // this is a little scary in the case that type analysis doesn't run and relies on the fact // that CallOp falls back to writing v0 in the case where the function type isn't known. RegisterAccess out_var(AccessMode::WRITE, Register(Reg::GPR, Reg::V0), m_my_idx); - return pool.alloc_element(out_var, pool.alloc_single_form(nullptr, call), true); + TypeSpec result_type("object"); + + if (m_call_type_set) { + result_type = m_call_type.last_arg(); + } + + return pool.alloc_element(out_var, pool.alloc_single_form(nullptr, call), true, + result_type); } else { throw std::runtime_error("CallOp::get_as_expr not yet implemented"); } diff --git a/decompiler/IR2/AtomicOpTypeAnalysis.cpp b/decompiler/IR2/AtomicOpTypeAnalysis.cpp index d73a3aeb31..171ccefde8 100644 --- a/decompiler/IR2/AtomicOpTypeAnalysis.cpp +++ b/decompiler/IR2/AtomicOpTypeAnalysis.cpp @@ -465,9 +465,12 @@ TypeState SetVarOp::propagate_types_internal(const TypeState& input, 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"); + m_source_type = TypeSpec("float"); return result; } - result.get(m_dst.reg()) = m_src.get_type(input, env, dts); + auto type = m_src.get_type(input, env, dts); + result.get(m_dst.reg()) = type; + m_source_type = type.typespec(); return result; } @@ -735,7 +738,9 @@ TypeState LoadVarOp::propagate_types_internal(const TypeState& input, const Env& env, DecompilerTypeSystem& dts) { TypeState result = input; - result.get(m_dst.reg()) = get_src_type(input, env, dts); + auto load_type = get_src_type(input, env, dts); + result.get(m_dst.reg()) = load_type; + m_type = load_type.typespec(); return result; } diff --git a/decompiler/IR2/Env.cpp b/decompiler/IR2/Env.cpp index 51353bbc43..b5434c71a5 100644 --- a/decompiler/IR2/Env.cpp +++ b/decompiler/IR2/Env.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "Env.h" #include "Form.h" #include "decompiler/analysis/atomic_op_builder.h" @@ -62,6 +63,23 @@ void Env::map_args_from_config(const std::vector& args_names, } } +void Env::map_args_from_config( + const std::vector& args_names, + const std::unordered_map& var_overrides) { + for (size_t i = 0; i < args_names.size(); i++) { + std::string var_name; + var_name.push_back(i >= 4 ? 't' : 'a'); + var_name.push_back('0' + (i % 4)); + var_name.push_back('-'); + var_name.push_back('0'); + m_var_remap[var_name] = args_names[i]; + } + + for (auto& x : var_overrides) { + m_var_remap[x.first] = x.second.name; + } +} + const std::string& Env::remapped_name(const std::string& name) const { auto kv = m_var_remap.find(name); if (kv != m_var_remap.end()) { @@ -71,22 +89,79 @@ const std::string& Env::remapped_name(const std::string& name) const { } } -goos::Object Env::get_variable_name(Register reg, int atomic_idx, AccessMode mode) const { +goos::Object Env::get_variable_name_with_cast(Register reg, int atomic_idx, AccessMode mode) const { if (reg.get_kind() == Reg::FPR || reg.get_kind() == Reg::GPR) { - std::string lookup_name = m_var_names.lookup(reg, atomic_idx, mode).name(); - auto remapped = m_var_remap.find(lookup_name); + auto& var_info = m_var_names.lookup(reg, atomic_idx, mode); + // this is a bit of a confusing process. The first step is to grab the auto-generated name: + std::string original_name = var_info.name(); + auto lookup_name = original_name; + + // and then see if there's a user remapping of it + auto remapped = m_var_remap.find(original_name); if (remapped != m_var_remap.end()) { lookup_name = remapped->second; } - auto type_kv = m_typehints.find(atomic_idx); - if (type_kv != m_typehints.end()) { + + // get the type of the variable. This is the type of thing if we do no casts. + // first, get the type the decompiler found + auto type_of_var = var_info.type.typespec(); + // and the user's type. + auto retype_kv = m_var_retype.find(original_name); + if (retype_kv != m_var_retype.end()) { + type_of_var = retype_kv->second; + } + + // next, we insert type casts that make enforce the user override. + auto type_kv = m_typecasts.find(atomic_idx); + if (type_kv != m_typecasts.end()) { for (auto& x : type_kv->second) { if (x.reg == reg) { - // TODO - redo this! - return pretty_print::build_list("the-as", x.type_name, lookup_name); + // let's make sure the above claim is true + TypeSpec type_in_reg; + if (has_type_analysis() && mode == AccessMode::READ) { + type_in_reg = get_types_for_op_mode(atomic_idx, AccessMode::READ).get(reg).typespec(); + if (type_in_reg.print() != x.type_name) { + lg::error( + "Decompiler type consistency error. There was a typecast for reg {} at idx {} " + "(var {}) to type {}, but the actual type is {} ({})", + reg.to_charp(), atomic_idx, lookup_name, x.type_name, type_in_reg.print(), + type_in_reg.print()); + assert(false); + } + } + + if (type_of_var != type_in_reg) { + // TODO - use the when possible? + return pretty_print::build_list("the-as", x.type_name, lookup_name); + } } } } + + // type analysis stuff runs before variable types, so we insert casts that account + // for the changing types due to the lca(uses) that is used to generate variable types. + auto type_of_reg = get_types_for_op_mode(atomic_idx, mode).get(reg).typespec(); + if (mode == AccessMode::READ) { + // note - this may be stricter than needed. but that's ok. + + if (type_of_var != type_of_reg) { + // fmt::print("casting {} (reg {}, idx {}): reg type {} var type {} remapped var type + // {}\n ", + // lookup_name, reg.to_charp(), atomic_idx, type_of_reg.print(), + // var_info.type.typespec().print(), type_of_var.print()); + return pretty_print::build_list("the-as", type_of_reg.print(), lookup_name); + } + } else { + // if we're setting a variable, we are a little less strict. + // let's leave this to set!'s for now. This is tricky with stuff like (if y x) where the move + // is eliminated so the RegisterAccess points to the "wrong" place. + // if (!dts->ts.tc(type_of_var, type_of_reg)) { + // fmt::print("op {} reg {} type {}\n", atomic_idx, reg.to_charp(), + // get_types_for_op_mode(atomic_idx, mode).get(reg).print()); return + // pretty_print::build_list("the-as", type_of_reg.print(), lookup_name); + // } + } + return pretty_print::to_symbol(lookup_name); } else { return pretty_print::to_symbol(reg.to_charp()); @@ -106,6 +181,28 @@ std::string Env::get_variable_name(const RegisterAccess& access) const { } } +/*! + * Get the type of the variable currently in the register. + * NOTE: this is _NOT_ the most specific type known to the decompiler, but instead the type + * of the variable. + */ +TypeSpec Env::get_variable_type(const RegisterAccess& access) const { + if (access.reg().get_kind() == Reg::FPR || access.reg().get_kind() == Reg::GPR) { + auto& var_info = m_var_names.lookup(access.reg(), access.idx(), access.mode()); + std::string original_name = var_info.name(); + + auto type_of_var = var_info.type.typespec(); + auto retype_kv = m_var_retype.find(original_name); + if (retype_kv != m_var_retype.end()) { + type_of_var = retype_kv->second; + } + + return type_of_var; + } else { + throw std::runtime_error("Types are not supported for this kind of register"); + } +} + /*! * Update the Env with the result of the type analysis pass. */ diff --git a/decompiler/IR2/Env.h b/decompiler/IR2/Env.h index 2d20b8dcef..e629ac18ec 100644 --- a/decompiler/IR2/Env.h +++ b/decompiler/IR2/Env.h @@ -44,8 +44,9 @@ class Env { } // TODO - remove this. - goos::Object get_variable_name(Register reg, int atomic_idx, AccessMode mode) const; + goos::Object get_variable_name_with_cast(Register reg, int atomic_idx, AccessMode mode) const; std::string get_variable_name(const RegisterAccess& access) const; + TypeSpec get_variable_type(const RegisterAccess& access) const; /*! * Get the types in registers _after_ the given operation has completed. @@ -60,6 +61,14 @@ class Env { return *m_op_init_types.at(atomic_op_id); } + const TypeState& get_types_for_op_mode(int atomic_op_id, AccessMode mode) const { + if (mode == AccessMode::READ) { + return get_types_before_op(atomic_op_id); + } else { + return get_types_after_op(atomic_op_id); + } + } + /*! * Get the types in registers at the beginning of this basic block, before any operations * have occurred. @@ -92,8 +101,8 @@ 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_hints(const std::unordered_map>& hints) { - m_typehints = hints; + void set_type_casts(const std::unordered_map>& casts) { + m_typecasts = casts; } void set_remap_for_function(int nargs); @@ -101,6 +110,8 @@ class Env { void set_remap_for_new_method(int nargs); void map_args_from_config(const std::vector& args_names, const std::unordered_map& var_names); + void map_args_from_config(const std::vector& args_names, + const std::unordered_map& var_overrides); const std::string& remapped_name(const std::string& name) const; @@ -130,6 +141,8 @@ class Env { void set_defined_in_let(const std::string& var) { m_vars_defined_in_let.insert(var); } + void set_retype_map(const std::unordered_map& map) { m_var_retype = map; } + LinkedObjectFile* file = nullptr; DecompilerTypeSystem* dts = nullptr; @@ -149,8 +162,9 @@ class Env { bool m_allow_sloppy_pair_typing = false; - std::unordered_map> m_typehints; + std::unordered_map> m_typecasts; std::unordered_map m_var_remap; + std::unordered_map m_var_retype; std::unordered_map m_label_types; std::unordered_set m_vars_defined_in_let; diff --git a/decompiler/IR2/Form.cpp b/decompiler/IR2/Form.cpp index 1fd9e0c54f..9853d8248a 100644 --- a/decompiler/IR2/Form.cpp +++ b/decompiler/IR2/Form.cpp @@ -284,8 +284,13 @@ void SimpleAtomElement::get_modified_regs(RegSet& regs) const { SetVarElement::SetVarElement(const RegisterAccess& var, Form* value, bool is_sequence_point, + TypeSpec src_type, const SetVarInfo& info) - : m_dst(var), m_src(value), m_is_sequence_point(is_sequence_point), m_var_info(info) { + : m_dst(var), + m_src(value), + m_is_sequence_point(is_sequence_point), + m_src_type(src_type), + m_var_info(info) { value->parent_element = this; } diff --git a/decompiler/IR2/Form.h b/decompiler/IR2/Form.h index f127600bc8..ce4ba2e799 100644 --- a/decompiler/IR2/Form.h +++ b/decompiler/IR2/Form.h @@ -243,6 +243,7 @@ class SetVarElement : public FormElement { SetVarElement(const RegisterAccess& var, Form* value, bool is_sequence_point, + TypeSpec src_type, const SetVarInfo& info = {}); goos::Object to_form_internal(const Env& env) const override; void apply(const std::function& f) override; @@ -266,12 +267,13 @@ class SetVarElement : public FormElement { void mark_as_dead_false() { m_var_info.is_dead_false = true; } const SetVarInfo& info() const { return m_var_info; } + const TypeSpec src_type() const { return m_src_type; } private: RegisterAccess m_dst; Form* m_src = nullptr; bool m_is_sequence_point = true; - + TypeSpec m_src_type; SetVarInfo m_var_info; }; diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index b2fb6b8ea9..2a5b1a7403 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -999,7 +999,7 @@ void SetVarElement::push_to_stack(const Env& env, FormPool& pool, FormStack& sta // hack for method stuff if (is_dead_set()) { - stack.push_value_to_reg_dead(m_dst, m_src, true, m_var_info); + stack.push_value_to_reg_dead(m_dst, m_src, true, m_src_type, m_var_info); return; } @@ -1020,7 +1020,7 @@ void SetVarElement::push_to_stack(const Env& env, FormPool& pool, FormStack& sta auto& info = env.reg_use().op.at(var.idx()); if (info.consumes.find(var.reg()) != info.consumes.end()) { stack.push_non_seq_reg_to_reg(m_dst, src_as_se->expr().get_arg(0).var(), m_src, - m_var_info); + m_src_type, m_var_info); return; } } @@ -1043,7 +1043,7 @@ void SetVarElement::push_to_stack(const Env& env, FormPool& pool, FormStack& sta // not sure this is the best place for this. stack.push_value_to_reg(m_dst, pool.alloc_single_element_form(nullptr, 0.0), - true, m_var_info); + true, m_src_type, m_var_info); return; } @@ -1058,7 +1058,7 @@ void SetVarElement::push_to_stack(const Env& env, FormPool& pool, FormStack& sta } } - stack.push_value_to_reg(m_dst, m_src, true, m_var_info); + stack.push_value_to_reg(m_dst, m_src, true, m_src_type, m_var_info); for (auto x : m_src->elts()) { assert(x->parent_form == m_src); } @@ -1648,7 +1648,9 @@ void CondNoElseElement::push_to_stack(const Env& env, FormPool& pool, FormStack& } if (used_as_value) { - stack.push_value_to_reg(final_destination, pool.alloc_single_form(nullptr, this), true); + // TODO - is this wrong? + stack.push_value_to_reg(final_destination, pool.alloc_single_form(nullptr, this), true, + env.get_variable_type(final_destination)); } else { stack.push_form_element(this, true); } @@ -1772,7 +1774,8 @@ void CondWithElseElement::push_to_stack(const Env& env, FormPool& pool, FormStac if (set_unused) { stack.push_form_element(this, true); } else { - stack.push_value_to_reg(*last_var, pool.alloc_single_form(nullptr, this), true); + stack.push_value_to_reg(*last_var, pool.alloc_single_form(nullptr, this), true, + env.get_variable_type(*last_var)); } } else { stack.push_form_element(this, true); @@ -1830,7 +1833,8 @@ void ShortCircuitElement::push_to_stack(const Env& env, FormPool& pool, FormStac } assert(used_as_value.has_value()); - stack.push_value_to_reg(final_result, pool.alloc_single_form(nullptr, this), true); + stack.push_value_to_reg(final_result, pool.alloc_single_form(nullptr, this), true, + env.get_variable_type(final_result)); already_rewritten = true; } } @@ -2533,7 +2537,7 @@ void ConditionalMoveFalseElement::push_to_stack(const Env& env, FormPool& pool, auto popped = pop_to_forms({old_value, source}, env, pool, stack, true); if (!is_symbol_true(popped.at(0))) { lg::warn("Failed to ConditionalMoveFalseElement::push_to_stack"); - stack.push_value_to_reg(source, popped.at(1), true); + stack.push_value_to_reg(source, popped.at(1), true, TypeSpec("symbol")); stack.push_form_element(this, true); return; } @@ -2543,7 +2547,7 @@ void ConditionalMoveFalseElement::push_to_stack(const Env& env, FormPool& pool, GenericOperator::make_compare(on_zero ? IR2_Condition::Kind::NONZERO : IR2_Condition::Kind::ZERO), std::vector{popped.at(1)}), - true); + true, TypeSpec("symbol")); } void SimpleAtomElement::update_from_stack(const Env&, diff --git a/decompiler/IR2/FormStack.cpp b/decompiler/IR2/FormStack.cpp index 7265faf8a8..d5cc96aab1 100644 --- a/decompiler/IR2/FormStack.cpp +++ b/decompiler/IR2/FormStack.cpp @@ -41,6 +41,7 @@ std::string FormStack::print(const Env& env) { void FormStack::push_value_to_reg(RegisterAccess var, Form* value, bool sequence_point, + TypeSpec type, const SetVarInfo& info) { assert(value); StackEntry entry; @@ -48,6 +49,7 @@ void FormStack::push_value_to_reg(RegisterAccess var, entry.sequence_point = sequence_point; entry.destination = var; entry.source = value; + entry.set_type = type; entry.set_info = info; m_stack.push_back(entry); } @@ -55,6 +57,7 @@ void FormStack::push_value_to_reg(RegisterAccess var, void FormStack::push_value_to_reg_dead(RegisterAccess var, Form* value, bool sequence_point, + TypeSpec type, const SetVarInfo& info) { assert(value); StackEntry entry; @@ -62,6 +65,7 @@ void FormStack::push_value_to_reg_dead(RegisterAccess var, entry.sequence_point = sequence_point; entry.destination = var; entry.source = value; + entry.set_type = type; entry.set_info = info; m_stack.push_back(entry); } @@ -69,6 +73,7 @@ void FormStack::push_value_to_reg_dead(RegisterAccess var, void FormStack::push_non_seq_reg_to_reg(const RegisterAccess& dst, const RegisterAccess& src, Form* src_as_form, + TypeSpec type, const SetVarInfo& info) { assert(src_as_form); StackEntry entry; @@ -77,6 +82,7 @@ void FormStack::push_non_seq_reg_to_reg(const RegisterAccess& dst, entry.destination = dst; entry.non_seq_source = src; entry.source = src_as_form; + entry.set_type = type; entry.set_info = info; entry.is_compactable = true; m_stack.push_back(entry); @@ -217,7 +223,7 @@ FormElement* FormStack::pop_back(FormPool& pool) { } else { assert(back.destination.has_value()); auto elt = pool.alloc_element(*back.destination, back.source, - back.sequence_point, back.set_info); + back.sequence_point, back.set_type, back.set_info); back.source->parent_element = elt; return elt; } @@ -320,7 +326,7 @@ std::vector FormStack::rewrite(FormPool& pool, const Env& env) { } auto elt = pool.alloc_element(*e.destination, simplified_source, - e.sequence_point, e.set_info); + e.sequence_point, e.set_type, e.set_info); e.source->parent_element = elt; auto final_elt = try_rewrites_in_place(elt, env, pool); diff --git a/decompiler/IR2/FormStack.h b/decompiler/IR2/FormStack.h index 5adc90b393..5110c13e23 100644 --- a/decompiler/IR2/FormStack.h +++ b/decompiler/IR2/FormStack.h @@ -16,15 +16,19 @@ class FormStack { void push_value_to_reg(RegisterAccess var, Form* value, bool sequence_point, + TypeSpec type, const SetVarInfo& info = {}); void push_non_seq_reg_to_reg(const RegisterAccess& dst, const RegisterAccess& src, Form* src_as_form, + TypeSpec type, const SetVarInfo& info = {}); void push_value_to_reg_dead(RegisterAccess var, Form* value, bool sequence_point, + TypeSpec type, const SetVarInfo& info = {}); + void push_form_element(FormElement* elt, bool sequence_point); Form* pop_reg(const RegisterAccess& var, const RegSet& barrier, @@ -53,10 +57,11 @@ class FormStack { FormElement* elt = nullptr; bool sequence_point = false; - TP_Type type; + // TP_Type type; bool is_compactable = false; SetVarInfo set_info; + TypeSpec set_type; std::string print(const Env& env) const; }; diff --git a/decompiler/ObjectFile/ObjectFileDB.cpp b/decompiler/ObjectFile/ObjectFileDB.cpp index f1fbfcbac5..e97bcec1df 100644 --- a/decompiler/ObjectFile/ObjectFileDB.cpp +++ b/decompiler/ObjectFile/ObjectFileDB.cpp @@ -25,7 +25,7 @@ #include "decompiler/IR/BasicOpBuilder.h" #include "decompiler/Function/TypeInspector.h" #include "common/log/log.h" -#include "third-party/json.hpp" +#include "common/util/json_util.h" namespace decompiler { namespace { @@ -162,7 +162,7 @@ ObjectFileDB::ObjectFileDB(const std::vector& _dgos, } void ObjectFileDB::load_map_file(const std::string& map_data) { - auto j = nlohmann::json::parse(map_data, nullptr, true, true); + auto j = parse_commented_json(map_data); for (auto& x : j) { auto mapped_name = x[0].get(); diff --git a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp index 5184803d20..b3d3eca4a1 100644 --- a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp +++ b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp @@ -293,10 +293,12 @@ void ObjectFileDB::ir2_type_analysis_pass() { func.type = ts; attempted_functions++; // try type analysis here. - auto hints = get_config().type_hints_by_function_by_idx[func.guessed_name.to_string()]; + auto hints = + get_config().type_casts_by_function_by_atomic_op_idx[func.guessed_name.to_string()]; auto label_types = get_config().label_types[data.to_unique_name()]; if (func.run_type_analysis_ir2(ts, dts, data.linked_data, hints, label_types)) { successful_functions++; + func.ir2.types_succeeded = true; } else { func.warnings.type_prop_warning("Type analysis failed"); } @@ -419,7 +421,17 @@ void ObjectFileDB::ir2_build_expressions() { total++; if (func.ir2.top_form && func.ir2.env.has_type_analysis() && func.ir2.env.has_local_vars()) { attempted++; - if (convert_to_expressions(func.ir2.top_form, *func.ir2.form_pool, func, dts)) { + auto name = func.guessed_name.to_string(); + auto arg_config = get_config().function_arg_names.find(name); + auto var_config = get_config().function_var_overrides.find(name); + if (convert_to_expressions(func.ir2.top_form, *func.ir2.form_pool, func, + arg_config != get_config().function_arg_names.end() + ? arg_config->second + : std::vector{}, + var_config != get_config().function_var_overrides.end() + ? var_config->second + : std::unordered_map{}, + dts)) { successful++; func.ir2.print_debug_forms = true; func.ir2.expressions_succeeded = true; diff --git a/decompiler/analysis/cfg_builder.cpp b/decompiler/analysis/cfg_builder.cpp index 7738b05a76..23b2db39d0 100644 --- a/decompiler/analysis/cfg_builder.cpp +++ b/decompiler/analysis/cfg_builder.cpp @@ -618,7 +618,7 @@ void convert_cond_no_else_to_compare(FormPool& pool, dynamic_cast(cne->entries.front().condition->try_as_single_element()); auto condition_replacement = condition.first->op()->get_condition_as_form(pool, f.ir2.env); auto crf = pool.alloc_single_form(nullptr, condition_replacement); - auto replacement = pool.alloc_element(dst, crf, true); + auto replacement = pool.alloc_element(dst, crf, true, TypeSpec("symbol")); replacement->parent_form = cne->parent_form; if (condition_as_single) { @@ -1004,7 +1004,7 @@ Form* try_sc_as_abs(FormPool& pool, Function& f, const ShortCircuit* vtx) { } auto src_abs = pool.alloc_single_element_form(nullptr, input.var(), consumed); - auto replacement = pool.alloc_element(output, src_abs, true); + auto replacement = pool.alloc_element(output, src_abs, true, TypeSpec("int")); b0_ptr->push_back(replacement); return b0_ptr; @@ -1117,7 +1117,7 @@ Form* try_sc_as_ash(FormPool& pool, Function& f, const ShortCircuit* vtx) { // setup auto ash_form = pool.alloc_single_element_form( nullptr, shift_ir.var(), value_ir.var(), clobber_ir, is_arith, consumed); - auto set_form = pool.alloc_element(dest_ir, ash_form, true); + auto set_form = pool.alloc_element(dest_ir, ash_form, true, TypeSpec("int")); b0_c_ptr->push_back(set_form); // fix up reg info @@ -1268,7 +1268,7 @@ Form* try_sc_as_type_of(FormPool& pool, Function& f, const ShortCircuit* vtx) { auto obj = pool.alloc_single_element_form( nullptr, shift->expr().get_arg(0).as_expr(), set_shift->dst().idx()); auto type_op = pool.alloc_single_element_form(nullptr, obj, clobber); - auto op = pool.alloc_element(else_case->dst(), type_op, true); + auto op = pool.alloc_element(else_case->dst(), type_op, true, TypeSpec("type")); b0_ptr->push_back(op); // fix register info diff --git a/decompiler/analysis/expression_build.cpp b/decompiler/analysis/expression_build.cpp index 04712b1970..426ec28df2 100644 --- a/decompiler/analysis/expression_build.cpp +++ b/decompiler/analysis/expression_build.cpp @@ -7,66 +7,27 @@ namespace decompiler { -/*void clean_up_ifs(Form* top_level_form, const Env&) { - bool changed = true; - while (changed) { - for (auto x : top_level_form->elts()) { - assert(x->parent_form == top_level_form); - } - changed = false; - - for (auto x : top_level_form->elts()) { - assert(x->parent_form == top_level_form); - } - top_level_form->apply([&](FormElement* elt) { - auto as_ge = dynamic_cast(elt); - if (!as_ge) { - return; - } - - if (as_ge->op().kind() == GenericOperator::Kind::CONDITION_OPERATOR) { - if (as_ge->op().condition_kind() == IR2_Condition::Kind::TRUTHY) { - assert(as_ge->elts().size() == 1); - auto top_condition = as_ge->elts().front(); - if (!top_condition->is_single_element() && elt->parent_form) { - auto real_condition = top_condition->back(); - top_condition->pop_back(); - - auto& parent_vector = elt->parent_form->elts(); - // find us in the parent vector - auto me = std::find_if(parent_vector.begin(), parent_vector.end(), - [&](FormElement* x) { return x == elt; }); - assert(me != parent_vector.end()); - - // now insert the fake condition - for (auto& x : top_condition->elts()) { - x->parent_form = elt->parent_form; - } - parent_vector.insert(me, top_condition->elts().begin(), top_condition->elts().end()); - top_condition->elts() = {real_condition}; - changed = true; - } - } - } - }); - } -}*/ - -bool convert_to_expressions(Form* top_level_form, - FormPool& pool, - Function& f, - const DecompilerTypeSystem& dts) { +/*! + * The main expression building pass. + */ +bool convert_to_expressions( + Form* top_level_form, + FormPool& pool, + Function& f, + const std::vector& arg_names, + const std::unordered_map& var_override_map, + const DecompilerTypeSystem& dts) { assert(top_level_form); - // fmt::print("Before anything:\n{}\n", - // pretty_print::to_string(top_level_form->to_form(f.ir2.env))); try { + // create the root expression stack for the function FormStack stack(true); + // and add all entries for (auto& entry : top_level_form->elts()) { - // fmt::print("push {} to stack\n", entry->to_form(f.ir2.env).print()); entry->push_to_stack(f.ir2.env, pool, stack); - // fmt::print("Stack is now:\n{}\n", stack.print(f.ir2.env)); } + + // rewrite the stack to get the correct final value std::vector new_entries; if (f.type.last_arg() != TypeSpec("none")) { auto return_var = f.ir2.atomic_ops->end_op().return_var(); @@ -82,20 +43,26 @@ bool convert_to_expressions(Form* top_level_form, new_entries.push_back(cast); } } else { + // or just get all the expressions new_entries = stack.rewrite(pool, f.ir2.env); } + + // if we are a totally empty function, insert a placeholder so we don't have to handle + // the zero element case ever. if (new_entries.empty()) { new_entries.push_back(pool.alloc_element()); } + + // turn us back into a form. top_level_form->clear(); for (auto x : new_entries) { top_level_form->push_back(x); } + // and sanity check for tree errors. for (auto x : top_level_form->elts()) { assert(x->parent_form == top_level_form); } - // fix up stuff } catch (std::exception& e) { f.warnings.expression_build_warning("In {}: {}", f.guessed_name.to_string(), e.what()); @@ -103,6 +70,8 @@ bool convert_to_expressions(Form* top_level_form, return false; } + // set argument names to some reasonable defaults. these will be used if the user doesn't + // give us anything more specific. if (f.guessed_name.kind == FunctionName::FunctionKind::GLOBAL) { f.ir2.env.set_remap_for_function(f.type.arg_count() - 1); } else if (f.guessed_name.kind == FunctionName::FunctionKind::METHOD) { @@ -113,20 +82,18 @@ bool convert_to_expressions(Form* top_level_form, } } - auto config_map = get_config().function_arg_names.find(f.guessed_name.to_string()); - if (config_map != get_config().function_arg_names.end()) { - std::unordered_map map2; - auto var_map = get_config().function_var_names.find(f.guessed_name.to_string()); - if (var_map != get_config().function_var_names.end()) { - map2 = var_map->second; - } - f.ir2.env.map_args_from_config(config_map->second, map2); - } else { - auto var_map = get_config().function_var_names.find(f.guessed_name.to_string()); - if (var_map != get_config().function_var_names.end()) { - f.ir2.env.map_args_from_config({}, var_map->second); + // get variable names from the user. + f.ir2.env.map_args_from_config(arg_names, var_override_map); + + // override variable types from the user. + + std::unordered_map retype; + for (auto& remap : var_override_map) { + if (remap.second.type) { + retype[remap.first] = dts.parse_type_spec(*remap.second.type); } } + f.ir2.env.set_retype_map(retype); return true; } diff --git a/decompiler/analysis/expression_build.h b/decompiler/analysis/expression_build.h index 6cf6220656..88b01c026d 100644 --- a/decompiler/analysis/expression_build.h +++ b/decompiler/analysis/expression_build.h @@ -1,12 +1,20 @@ #pragma once +#include +#include +#include + namespace decompiler { class Form; class Function; class FormPool; class DecompilerTypeSystem; -bool convert_to_expressions(Form* top_level_form, - FormPool& pool, - Function& f, - const DecompilerTypeSystem& dts); +struct LocalVarOverride; +bool convert_to_expressions( + Form* top_level_form, + FormPool& pool, + Function& f, + const std::vector& arg_names, + const std::unordered_map& var_override_map, + const DecompilerTypeSystem& dts); } // namespace decompiler \ No newline at end of file diff --git a/decompiler/analysis/insert_lets.cpp b/decompiler/analysis/insert_lets.cpp index f052e2b3ff..993376f4cf 100644 --- a/decompiler/analysis/insert_lets.cpp +++ b/decompiler/analysis/insert_lets.cpp @@ -216,6 +216,21 @@ FormElement* rewrite_let(LetElement* in, const Env& env, FormPool& pool) { // nothing matched. return nullptr; } + +Form* insert_cast_for_let(RegisterAccess dst, + const TypeSpec& src_type, + Form* src, + FormPool& pool, + const Env& env) { + auto dst_type = env.get_variable_type(dst); + + if (src_type != dst_type) { + // fmt::print("inserting let cast because {} != {}\n", dst_type.print(), src_type.print()); + return pool.alloc_single_element_form(nullptr, dst_type, src); + } + + return src; +} } // namespace LetStats insert_lets(const Function& func, Env& env, FormPool& pool, Form* top_level_form) { @@ -407,7 +422,10 @@ LetStats insert_lets(const Function& func, Env& env, FormPool& pool, Form* top_l } assert(elt_idx == let_desc.end_elt); auto new_let = pool.alloc_element(pool.alloc_sequence_form(nullptr, body)); - new_let->add_def(let_desc.set_form->dst(), let_desc.set_form->src()); + // insert a cast, if needed. + auto casted_src = insert_cast_for_let(let_desc.set_form->dst(), let_desc.set_form->src_type(), + let_desc.set_form->src(), pool, env); + new_let->add_def(let_desc.set_form->dst(), casted_src); env.set_defined_in_let(let_desc.name); lets.at(let_idx) = new_let; } diff --git a/decompiler/analysis/variable_naming.cpp b/decompiler/analysis/variable_naming.cpp index e08565054b..05c5e8ef18 100644 --- a/decompiler/analysis/variable_naming.cpp +++ b/decompiler/analysis/variable_naming.cpp @@ -611,6 +611,45 @@ void SSA::make_vars(const Function& function, const DecompilerTypeSystem& dts) { init_types = end_types; } } + + // override the types of the variables for function arguments: + assert(function.type.arg_count() > 0); + for (int arg_idx = 0; arg_idx < int(function.type.arg_count()) - 1; arg_idx++) { + auto arg_reg = Register::get_arg_reg(arg_idx); + if (!program_read_vars[arg_reg].empty()) { + program_read_vars[arg_reg].at(0).type = TP_Type::make_from_ts(function.type.get_arg(arg_idx)); + } + + if (!program_write_vars[arg_reg].empty()) { + program_write_vars[arg_reg].at(0).type = + TP_Type::make_from_ts(function.type.get_arg(arg_idx)); + } + } + + // copy types from input argument coloring moves: + for (auto& instr : blocks.at(0).ins) { + if (instr.is_arg_coloring_move) { + auto src_ssa = instr.src.at(0); + for (int arg_idx = 0; arg_idx < int(function.type.arg_count()) - 1; arg_idx++) { + if (Register::get_arg_reg(arg_idx) == src_ssa.reg()) { + // copy the type from here. + auto dst = instr.dst; + assert(dst); + auto dst_reg = instr.dst->reg(); + auto dst_varid = map.var_id(*dst); + if ((int)program_read_vars[dst_reg].size() > dst_varid) { + program_read_vars[dst_reg].at(dst_varid).type = + TP_Type::make_from_ts(function.type.get_arg(arg_idx)); + } + + if ((int)program_write_vars[dst_reg].size() > dst_varid) { + program_write_vars[dst_reg].at(dst_varid).type = + TP_Type::make_from_ts(function.type.get_arg(arg_idx)); + } + } + } + } + } } void remap_color_move( diff --git a/decompiler/config.cpp b/decompiler/config.cpp index 2b04e5bd67..b6a27b0633 100644 --- a/decompiler/config.cpp +++ b/decompiler/config.cpp @@ -2,6 +2,7 @@ #include "third-party/json.hpp" #include "third-party/fmt/core.h" #include "common/util/FileUtil.h" +#include "common/util/json_util.h" namespace decompiler { Config gConfig; @@ -18,7 +19,7 @@ namespace { nlohmann::json read_json_file_from_config(const nlohmann::json& cfg, const std::string& file_key) { auto file_name = cfg.at(file_key).get(); auto file_txt = file_util::read_text_file(file_util::get_file_path({file_name})); - return nlohmann::json::parse(file_txt, nullptr, true, true); + return parse_commented_json(file_txt); } } // namespace @@ -27,8 +28,7 @@ nlohmann::json read_json_file_from_config(const nlohmann::json& cfg, const std:: */ void set_config(const std::string& path_to_config_file) { auto config_str = file_util::read_text_file(path_to_config_file); - // to ignore comments in json, which may be useful - auto cfg = nlohmann::json::parse(config_str, nullptr, true, true); + auto cfg = parse_commented_json(config_str); gConfig.game_version = cfg.at("game_version").get(); gConfig.dgo_names = cfg.at("dgo_names").get>(); @@ -83,18 +83,18 @@ void set_config(const std::string& path_to_config_file) { gConfig.allowed_objects.insert(x); } - auto type_hints_json = read_json_file_from_config(cfg, "type_hints_file"); - for (auto& kv : type_hints_json.items()) { + auto type_casts_json = read_json_file_from_config(cfg, "type_casts_file"); + for (auto& kv : type_casts_json.items()) { auto& function_name = kv.key(); - auto& hints = kv.value(); - for (auto& hint : hints) { - auto idx = hint.at(0).get(); - for (size_t i = 1; i < hint.size(); i++) { - auto& assignment = hint.at(i); - TypeHint type_hint; - type_hint.reg = Register(assignment.at(0).get()); - type_hint.type_name = assignment.at(1).get(); - gConfig.type_hints_by_function_by_idx[function_name][idx].push_back(type_hint); + 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(); + gConfig.type_casts_by_function_by_atomic_op_idx[function_name][idx].push_back(type_cast); } } } @@ -121,8 +121,17 @@ void set_config(const std::string& path_to_config_file) { auto var = kv.value().find("vars"); if (var != kv.value().end()) { - for (auto& vkv : var->get>()) { - gConfig.function_var_names[function_name][vkv.first] = vkv.second; + for (auto& vkv : var->get>()) { + LocalVarOverride override; + if (vkv.second.is_string()) { + override.name = vkv.second.get(); + } else if (vkv.second.is_array()) { + override.name = vkv.second[0].get(); + override.type = vkv.second[1].get(); + } else { + throw std::runtime_error("Invalid function var override."); + } + gConfig.function_var_overrides[function_name][vkv.first] = override; } } } diff --git a/decompiler/config.h b/decompiler/config.h index 01186ba1d3..62e85e2e9a 100644 --- a/decompiler/config.h +++ b/decompiler/config.h @@ -8,7 +8,8 @@ #include "decompiler/Disasm/Register.h" namespace decompiler { -struct TypeHint { +struct TypeCast { + int atomic_op_idx = -1; Register reg; std::string type_name; }; @@ -19,6 +20,13 @@ struct LabelType { std::optional array_size; }; +struct LocalVarOverride { + std::string name; + // this may be left out, indicating that the variable should use the type determined + // by the type analysis pass. + std::optional type; +}; + struct Config { int game_version = -1; std::vector dgo_names; @@ -45,12 +53,13 @@ struct Config { std::unordered_set pair_functions_by_name; std::unordered_set no_type_analysis_functions_by_name; std::unordered_set allowed_objects; - std::unordered_map>> - type_hints_by_function_by_idx; + std::unordered_map>> + type_casts_by_function_by_atomic_op_idx; std::unordered_map> anon_function_types_by_obj_by_id; std::unordered_map> function_arg_names; - std::unordered_map> function_var_names; + std::unordered_map> + function_var_overrides; std::unordered_map> label_types; bool run_ir2 = false; diff --git a/decompiler/config/all-types.gc b/decompiler/config/all-types.gc index 5621baa8aa..64e5b46fcc 100644 --- a/decompiler/config/all-types.gc +++ b/decompiler/config/all-types.gc @@ -222,9 +222,9 @@ ) (:methods - (stack-size-set! ((this thread) (stack-size int)) none 9) - (thread-suspend ((this _type_)) none 10) - (thread-resume ((to-resume _type_)) none 11) + (stack-size-set! (_type_ int) none 9) + (thread-suspend (_type_) none 10) + (thread-resume (_type_) none 11) ) :method-count-assert 12 @@ -244,9 +244,9 @@ ) (:methods - (new ((allocation symbol) (type-to-make type) (parent-process process) (name symbol) (stack-size int) (stack-top pointer)) _type_ 0) - (thread-suspend ((this _type_)) none 10) - (thread-resume ((to-resume _type_)) none 11) + (new (symbol type process symbol int pointer) _type_ 0) + (thread-suspend (_type_) none 10) + (thread-resume (_type_) none 11) ) :method-count-assert 12 @@ -266,11 +266,11 @@ ) (:methods - (new ((allocation symbol) (type-to-make type) (name basic)) _type_ 0) - (activate ((obj _type_) (dest process-tree) (name basic) (stack-top pointer)) process-tree 9) - (deactivate ((obj _type_)) none 10) + (new (symbol type basic) _type_ 0) + (activate (_type_ process-tree basic pointer) process-tree 9) + (deactivate (_type_) none 10) (dummy-method-11 () none 11) - (run-logic? ((obj _type_)) symbol 12) + (run-logic? (_type_) symbol 12) (dummy-method () none 13) ) @@ -303,11 +303,11 @@ (:methods - (new ((allocation symbol) (type-to-make type) (name basic) (stack-size int)) _type_ 0) - (activate ((obj _type_) (dest process-tree) (name basic) (stack-top pointer)) process-tree 9) - (deactivate ((obj process)) none 10) + (new (symbol type basic int) _type_ 0) + (activate (_type_ process-tree basic pointer) process-tree 9) + (deactivate (process) none 10) (dummy-method-11 () none 11) - (run-logic? ((obj process)) symbol 12) + (run-logic? (process) symbol 12) (dummy-method () none 13) ) @@ -322,9 +322,9 @@ ;; nothing new! ) (:methods - (new ((allocation symbol) (type-to-make type) (count int) (stack-size int) (name basic)) _type_ 0) - (get-process ((pool _type_) (type-to-make type) (stack-size int)) process 14) - (return-process ((pool _type_) (proc process)) none 15) + (new (symbol type int int basic) _type_ 0) + (get-process (_type_ type int) process 14) + (return-process ( _type_ process) none 15) ) :size-assert #x20 :method-count-assert 16 @@ -360,18 +360,18 @@ ) (:methods - (new ((allocation symbol) (type-to-make type) (name basic) (allocated-length int) (heap-size int)) _type_ 0) - (compact ((this dead-pool-heap) (count int)) none 16) - (shrink-heap ((this dead-pool-heap) (proc process)) dead-pool-heap 17) - (churn ((this dead-pool-heap) (count int)) none 18) - (memory-used ((this dead-pool-heap)) int 19) - (memory-total ((this dead-pool-heap)) int 20) - (gap-size ((this dead-pool-heap) (rec dead-pool-heap-rec)) int 21) - (gap-location ((this dead-pool-heap) (rec dead-pool-heap-rec)) pointer 22) - (find-gap ((this dead-pool-heap) (rec dead-pool-heap-rec)) dead-pool-heap-rec 23) - (find-gap-by-size ((this dead-pool-heap) (size int)) dead-pool-heap-rec 24) - (memory-free ((this dead-pool-heap)) int 25) - (compact-time ((this dead-pool-heap)) uint 26) + (new (symbol type basic int int) _type_ 0) + (compact (dead-pool-heap int) none 16) + (shrink-heap (dead-pool-heap process) dead-pool-heap 17) + (churn (dead-pool-heap int) none 18) + (memory-used (dead-pool-heap) int 19) + (memory-total (dead-pool-heap) int 20) + (gap-size (dead-pool-heap dead-pool-heap-rec) int 21) + (gap-location (dead-pool-heap dead-pool-heap-rec) pointer 22) + (find-gap (dead-pool-heap dead-pool-heap-rec) dead-pool-heap-rec 23) + (find-gap-by-size (dead-pool-heap int) dead-pool-heap-rec 24) + (memory-free (dead-pool-heap) int 25) + (compact-time (dead-pool-heap) uint 26) ) :method-count-assert 27 @@ -400,7 +400,7 @@ ) (:methods - (new ((allocation symbol) (type-to-make type) (name symbol) (func function) (params (pointer uint64))) object 0) + (new (symbol type symbol function (pointer uint64)) object 0) ) :method-count-assert 9 @@ -412,7 +412,7 @@ ((exit (function object) :offset-assert 12) ) (:methods - (new ((allocation symbol) (type-to-make type) (func (function object))) protect-frame) + (new (symbol type (function object)) protect-frame) ) :method-count-assert 9 @@ -438,11 +438,11 @@ (event (function basic int basic event-message-block object) :offset-assert 32) ) (:methods - (new ((allocation symbol) (type-to-make type) (name basic) (code function) - (trans (function object)) - (enter (function object object object object object object object)) - (exit (function object)) - (event (function basic int basic event-message-block object))) _type_ 0) + (new (symbol type basic function + (function object) + (function object object object object object object object) + (function object) + (function basic int basic event-message-block object)) _type_ 0) ) :method-count-assert 9 :size-assert #x24 @@ -576,8 +576,7 @@ ;;(define-extern post object) ;; unknown type ;;(define-extern dead object) ;; unknown type -;; todo -;;(define-extern *dead-pool-list* object) ;; unknown type +(define-extern *dead-pool-list* pair) ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -3187,7 +3186,7 @@ (file uint32 :offset-assert 16) ) (:methods - (new ((allocation symbol) (type-to-make type) (name string) (mode basic)) _type_) + (new (symbol type string basic) _type_) ) :method-count-assert 9 :size-assert #x14 @@ -3990,20 +3989,20 @@ (:methods (new (symbol type basic int) _type_ 0) (inspect-all-connections (engine) none 9) - (apply-to-connections (engine (xx (function connectable none))) none 10) - (apply-to-connections-reverse (engine (xx (function connectable none))) none 11) + (apply-to-connections (engine (function connectable none)) none 10) + (apply-to-connections-reverse (engine (function connectable none)) none 11) (execute-connections (engine object) none 12) (execute-connections-and-move-to-dead (engine object) none 13) (execute-connections-if-needed (engine object) none 14) - (add-connection (engine process (xx (function object object object object object)) object object object) connection 15) + (add-connection (engine process (function object object object object object) object object object) connection 15) (remove-from-process (engine process) none 16) - (remove-matching (engine (xx (function connection engine symbol))) none 17) + (remove-matching (engine (function connection engine symbol)) none 17) (remove-all (engine) none 18) (remove-by-param1 (engine object) none 19) (remove-by-param2 (engine int) none 20) (get-first-connectable (engine) connectable 21) (get-last-connectable (engine) connectable 22) - (unknown-1 (engine (xx (pointer uint32))) uint 23) + (unknown-1 (engine (pointer uint32)) uint 23) ) ) @@ -9202,7 +9201,7 @@ (dummy-14 () none 14) (dummy-15 () none 15) (get-head (_type_) touching-prims-entry 16) - (unknown1 (_type_ (xx (pointer uint32))) uint 17) + (unknown1 (_type_ (pointer uint32)) uint 17) ) ) diff --git a/decompiler/config/jak1_ntsc_black_label.jsonc b/decompiler/config/jak1_ntsc_black_label.jsonc index efd13c97ab..a332a4d26a 100644 --- a/decompiler/config/jak1_ntsc_black_label.jsonc +++ b/decompiler/config/jak1_ntsc_black_label.jsonc @@ -53,7 +53,7 @@ "str_file_names_":[], "allowed_objects":["gcommon"], - "type_hints_file":"decompiler/config/jak1_ntsc_black_label/type_hints.jsonc", + "type_casts_file":"decompiler/config/jak1_ntsc_black_label/type_casts.jsonc", "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", diff --git a/decompiler/config/jak1_ntsc_black_label/label_types.jsonc b/decompiler/config/jak1_ntsc_black_label/label_types.jsonc index e779aed3aa..5d187a7184 100644 --- a/decompiler/config/jak1_ntsc_black_label/label_types.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/label_types.jsonc @@ -4,6 +4,10 @@ ["L346", "uint64", true] ], + "gkernel": [ + ["L345", "_auto_", true] + ], + "math": [ ["L41", "float", true], ["L34", "float", true], diff --git a/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc b/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc new file mode 100644 index 0000000000..1f7c507c1c --- /dev/null +++ b/decompiler/config/jak1_ntsc_black_label/type_casts.jsonc @@ -0,0 +1,105 @@ +// This file replaces type_hints.jsonc. +// Functions are identified by their "unique name". This is the name after ".function" in the IR2 file. +// Each cast entry represents an override of a register's type at a certain point the function. +// These modifications do not propagate like normal types, so you may have to apply these modifications +// over a range of indices. + +// Entry format: [index, register, override] +// - The index can either be specified as a single integer, or as [min, max]. +// In the case of [min, max], the min index is included, but the max is not. ([1, 4] = 1, 2, 3). +// - The register is a string with the plain PS2 register name. +// - The type is a string with a valid GOAL typespec. +// It is parsed exactly like the compiler, so you can use compound types. +// You should only use register types here. + +{ + // GCOMMON + "(method 2 array)": [ + [23, "gp", "(array int32)"], + [43, "gp", "(array uint32)"], + [63, "gp", "(array int64)"], + [83, "gp", "(array uint64)"], + [102, "gp", "(array int8)"], + [121, "gp", "(array uint8)"], + [141, "gp", "(array int16)"], + [161, "gp", "(array uint16)"], + [186, "gp", "(array uint128)"], + [204, "gp", "(array int32)"], + [223, "gp", "(array float)"], + [232, "gp", "(array float)"], + [249, "gp", "(array basic)"], + [258, "gp", "(array basic)"] + ], + + "(method 3 array)": [ + [44, "gp", "(array int32)"], + [62, "gp", "(array uint32)"], + [80, "gp", "(array int64)"], + [98, "gp", "(array uint64)"], + [115, "gp", "(array int8)"], + [132, "gp", "(array int8)"], // bug in game + [150, "gp", "(array int16)"], + [168, "gp", "(array uint16)"], + [190, "gp", "(array uint128)"], + [207, "gp", "(array int32)"], + [226, "gp", "(array float)"], + [243, "gp", "(array basic)"] + ], + + // GKERNEL-H + "(method 2 handle)": [ + [10, "a2", "(pointer process)"], + [11, "a3", "process"], + [12, "v1", "int"], + [16, "gp", "int"] + ], + + // GKERNEL + "(method 0 cpu-thread)": [[[13, 28], "v0", "cpu-thread"]], + + "(method 0 process)": [ + [12, "a0", "int"], + [[13, 43], "v0", "process"] + ], + + "(method 0 dead-pool-heap)": [ + [60, "v0", "int"], // a lie, actually the 115 is an align16 constant propagated on addr of heap start. + [63, "a0", "pointer"], + [[61, 73], "v0", "dead-pool-heap"] + ], + + "(method 21 dead-pool-heap)": [ + [5, "v1", "pointer"], + [13, "a0", "pointer"], + [25, "v1", "pointer"] + ], + + "(method 5 dead-pool-heap)": [ + [3, "v1", "int"], + [3, "a0", "int"] + ], + + "(method 0 protect-frame)": [ + [0, "a0", "int"], + [[1, 8], "v0", "protect-frame"] + ], + + "(method 10 process)": [[[24, 30], "s4", "protect-frame"]], + + "(method 9 process)": [[43, "s5", "process"]], + + "(method 14 dead-pool)": [ + [[24, 26], "v1", "(pointer process-tree)"], + [[30, 39], "s4", "(pointer process-tree)"] + ], + + "inspect-process-heap": [ + [[4, 11], "s5", "basic"], + [17, "s5", "int"] + ], + + "name=": [ + [24, "a1", "symbol"], + [39, "a0", "symbol"] + ] +} diff --git a/decompiler/config/jak1_ntsc_black_label/type_hints.jsonc b/decompiler/config/jak1_ntsc_black_label/type_hints.jsonc index 2e0a46846e..2a5be3f1bc 100644 --- a/decompiler/config/jak1_ntsc_black_label/type_hints.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/type_hints.jsonc @@ -1,3 +1,8 @@ +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// THIS FILE IS NO LONGER USED!!!!!!!!!!!!!!!!!!!!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// see type_casts.jsonc instead. + { "(method 2 array)":[ [23, ["gp", "(array int32)"]], diff --git a/decompiler/config/jak1_ntsc_black_label/var_names.jsonc b/decompiler/config/jak1_ntsc_black_label/var_names.jsonc index 37ceb6b757..7c026d1c0d 100644 --- a/decompiler/config/jak1_ntsc_black_label/var_names.jsonc +++ b/decompiler/config/jak1_ntsc_black_label/var_names.jsonc @@ -195,6 +195,11 @@ "args":["this", "rec"] }, + + "(method 0 dead-pool-heap)":{ + "vars":{"v0-0":["obj", "dead-pool-heap"]} + }, + "seek":{ "args":["x", "target", "diff"], "vars":{"f2-0":"err"} diff --git a/decompiler/util/DecompilerTypeSystem.cpp b/decompiler/util/DecompilerTypeSystem.cpp index c9818af3f5..a67018a776 100644 --- a/decompiler/util/DecompilerTypeSystem.cpp +++ b/decompiler/util/DecompilerTypeSystem.cpp @@ -88,7 +88,7 @@ void DecompilerTypeSystem::parse_type_defs(const std::vector& file_ }); } -TypeSpec DecompilerTypeSystem::parse_type_spec(const std::string& str) { +TypeSpec DecompilerTypeSystem::parse_type_spec(const std::string& str) const { auto read = m_reader.read_from_string(str); auto data = cdr(read); return parse_typespec(&ts, car(data)); diff --git a/decompiler/util/DecompilerTypeSystem.h b/decompiler/util/DecompilerTypeSystem.h index f82e029753..5161c914ce 100644 --- a/decompiler/util/DecompilerTypeSystem.h +++ b/decompiler/util/DecompilerTypeSystem.h @@ -32,7 +32,7 @@ class DecompilerTypeSystem { void add_symbol(const std::string& name, const TypeSpec& type_spec); void parse_type_defs(const std::vector& file_path); - TypeSpec parse_type_spec(const std::string& str); + TypeSpec parse_type_spec(const std::string& str) const; void add_type_flags(const std::string& name, u64 flags); void add_type_parent(const std::string& child, const std::string& parent); std::string dump_symbol_types(); @@ -55,7 +55,7 @@ class DecompilerTypeSystem { } type_prop_settings; private: - goos::Reader m_reader; + mutable goos::Reader m_reader; }; } // namespace decompiler diff --git a/doc/changelog.md b/doc/changelog.md index bd23b565ae..535e3c1447 100644 --- a/doc/changelog.md +++ b/doc/changelog.md @@ -119,4 +119,8 @@ - Static type reference link data now has the correct number of methods. This prevents errors like `dkernel: trying to redefine a type 'game-info' with 29 methods when it had 12, try restarting` when you did not actually redefine the number of methods. - Added `get-info` to figure out what something is and where it's defined. - Added `autocomplete` to get auto-completions for a prefix. -- Added `add-macro-to-autocomplete` to add a macro to the auto-completer. \ No newline at end of file +- Added `add-macro-to-autocomplete` to add a macro to the auto-completer. + +## V0.7 +- There is now an option for `allow-misaligned` which allows the alignment of an struct type to be less than 16-bytes when inlined, without enabling array packing. This seems like a stupid option, but GOAL has this in some places, so we support it too. +- In method declarations in a `deftype`, you can no longer provide argument names. There was ambiguity when parsing a compound typespec vs named argument. The names were not used for anything. diff --git a/gk.sh b/gk.sh index 5cef8b8f47..d335f42565 100755 --- a/gk.sh +++ b/gk.sh @@ -3,4 +3,4 @@ # Directory of this script DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -$DIR/build/game/gk -fakeiso -debug -nokernel "$@" +$DIR/build/game/gk -fakeiso -debug -nokernel -nodisplay "$@" diff --git a/goal_src/engine/collide/collide-touch-h.gc b/goal_src/engine/collide/collide-touch-h.gc index 6068895339..3694e64bfe 100644 --- a/goal_src/engine/collide/collide-touch-h.gc +++ b/goal_src/engine/collide/collide-touch-h.gc @@ -111,7 +111,7 @@ (dummy-14 () none 14) (dummy-15 () none 15) (get-head (_type_) touching-prims-entry 16) - (unknown1 (_type_ (xx (pointer uint32))) uint 17) + (unknown1 (_type_ (pointer uint32)) uint 17) ) ) diff --git a/goal_src/engine/engine/connect.gc b/goal_src/engine/engine/connect.gc index d8f5068b3e..aaeb1d1673 100644 --- a/goal_src/engine/engine/connect.gc +++ b/goal_src/engine/engine/connect.gc @@ -101,20 +101,20 @@ (:methods (new (symbol type basic int) _type_ 0) (inspect-all-connections (engine) none 9) - (apply-to-connections (engine (xx (function connectable none))) none 10) - (apply-to-connections-reverse (engine (xx (function connectable none))) none 11) + (apply-to-connections (engine (function connectable none)) none 10) + (apply-to-connections-reverse (engine (function connectable none)) none 11) (execute-connections (engine object) none 12) (execute-connections-and-move-to-dead (engine object) none 13) (execute-connections-if-needed (engine object) none 14) - (add-connection (engine process (xx (function object object object object object)) object object object) connection 15) + (add-connection (engine process (function object object object object object) object object object) connection 15) (remove-from-process (engine process) none 16) - (remove-matching (engine (xx (function connection engine symbol))) none 17) + (remove-matching (engine (function connection engine symbol)) none 17) (remove-all (engine) none 18) (remove-by-param1 (engine object) none 19) (remove-by-param2 (engine int) none 20) (get-first-connectable (engine) connectable 21) (get-last-connectable (engine) connectable 22) - (unknown-1 (engine (xx (pointer uint32))) uint 23) + (unknown-1 (engine (pointer uint32)) uint 23) ) ) diff --git a/goal_src/engine/load/file-io.gc b/goal_src/engine/load/file-io.gc index 3c51f69ca5..a2f4e0c2c8 100644 --- a/goal_src/engine/load/file-io.gc +++ b/goal_src/engine/load/file-io.gc @@ -19,7 +19,7 @@ (file uint32 :offset-assert 16) ) (:methods - (new ((allocation symbol) (type-to-make type) (name string) (mode basic)) _type_) + (new (symbol type string basic) _type_) ) :method-count-assert 9 :size-assert #x14 diff --git a/goal_src/kernel-defs.gc b/goal_src/kernel-defs.gc index d5fd488bfd..2f955a7fb0 100644 --- a/goal_src/kernel-defs.gc +++ b/goal_src/kernel-defs.gc @@ -158,3 +158,48 @@ (define-extern rpc-call (function int uint uint uint int uint int uint)) (define-extern rpc-busy? (function int uint)) + + +;;;;;;;;;;;;;;;;;;;;;;;; +;;;; kernel types +;;;;;;;;;;;;;;;;;;;;;;;; + +;; These are types that are both: +;; - set up in the kernel +;; - not set up in GOAL code +;; So these deftypes generate no code. + +(deftype process-tree (basic) + ((name basic :offset-assert 4) + (mask uint32 :offset-assert 8) + (parent (pointer process-tree) :offset-assert 12) + (brother (pointer process-tree) :offset-assert 16) + (child (pointer process-tree) :offset-assert 20) + (ppointer (pointer process-tree) :offset-assert 24) + (self process-tree :offset-assert 28) + ) + + (:methods + (new (symbol type basic) _type_ 0) + (activate (_type_ process-tree basic pointer) process-tree 9) + (deactivate (_type_) none 10) + (dummy-method-11 () none 11) + (run-logic? (_type_) symbol 12) + (dummy-method () none 13) + ) + + :size-assert #x20 + :method-count-assert 14 + :no-runtime-type ;; already defined by kscheme. Don't do it again. + ) + +(deftype stack-frame (basic) + ((name basic :offset 4) + (next stack-frame :offset 8) ;; which way does this point? + ) + + :size-assert #xc + :method-count-assert 9 + :flag-assert #x90000000c + :no-runtime-type ;; already constructed, don't do it again. + ) \ No newline at end of file diff --git a/goal_src/kernel/gkernel-h.gc b/goal_src/kernel/gkernel-h.gc index d533cbf338..68a5578f48 100644 --- a/goal_src/kernel/gkernel-h.gc +++ b/goal_src/kernel/gkernel-h.gc @@ -157,10 +157,9 @@ ) (:methods - ;; todo, triple check these method numbers. - (stack-size-set! ((this thread) (stack-size int)) none 9) - (thread-suspend ((this _type_)) none 10) ;; only safe on a cpu-thread, but slot exists for thread - (thread-resume ((to-resume _type_)) none 11) ;; only safe on a cpu-thread, but slot exists for thread + (stack-size-set! (_type_ int) none 9) + (thread-suspend (_type_) none 10) + (thread-resume (_type_) none 11) ) :size-assert #x28 @@ -185,9 +184,9 @@ ) (:methods - (new ((allocation symbol) (type-to-make type) (parent-process process) (name symbol) (stack-size int) (stack-top pointer)) _type_ 0) - (thread-suspend ((this _type_)) none 10) - (thread-resume ((to-resume _type_)) none 11) + (new (symbol type process symbol int pointer) _type_ 0) + (thread-suspend (_type_) none 10) + (thread-resume (_type_) none 11) ) :size-assert #x80 @@ -209,12 +208,12 @@ ) (:methods - (new ((allocation symbol) (type-to-make type) (name basic)) _type_ 0) - (activate ((obj _type_) (dest process-tree) (name basic) (stack-top pointer)) process-tree 9) - (deactivate ((obj _type_)) none 10) - (dummy-method-11 () none 11) - (run-logic? ((obj _type_)) symbol 12) - (dummy-method () none 13) + (new (symbol type basic) _type_ 0) + (activate (_type_ process-tree basic pointer) process-tree 9) + (deactivate (_type_) none 10) + (dummy-method-11 () none 11) + (run-logic? (_type_) symbol 12) + (dummy-method () none 13) ) :size-assert #x20 @@ -246,12 +245,12 @@ ) (:methods - (new ((allocation symbol) (type-to-make type) (name basic) (stack-size int)) _type_ 0) - (activate ((obj _type_) (dest process-tree) (name basic) (stack-top pointer)) process-tree 9) - (deactivate ((obj process)) none 10) - (dummy-method-11 () none 11) - (run-logic? ((obj process)) symbol 12) - (dummy-method () none 13) + (new (symbol type basic int) _type_ 0) + (activate (_type_ process-tree basic pointer) process-tree 9) + (deactivate (process) none 10) + (dummy-method-11 () none 11) + (run-logic? (process) symbol 12) + (dummy-method () none 13) ) :size-assert #x70 @@ -266,9 +265,9 @@ ;; nothing new! ) (:methods - (new ((allocation symbol) (type-to-make type) (count int) (stack-size int) (name basic)) _type_ 0) - (get-process ((pool _type_) (type-to-make type) (stack-size int)) process 14) - (return-process ((pool _type_) (proc process)) none 15) + (new (symbol type int int basic) _type_ 0) + (get-process (_type_ type int) process 14) + (return-process ( _type_ process) none 15) ) :size-assert #x20 :method-count-assert 16 @@ -310,18 +309,18 @@ (process-list dead-pool-heap-rec :inline :dynamic :offset-assert 104) ) (:methods - (new ((allocation symbol) (type-to-make type) (name basic) (allocated-length int) (heap-size int)) _type_ 0) - (compact ((this dead-pool-heap) (count int)) none 16) - (shrink-heap ((this dead-pool-heap) (proc process)) dead-pool-heap 17) - (churn ((this dead-pool-heap) (count int)) none 18) - (memory-used ((this dead-pool-heap)) int 19) - (memory-total ((this dead-pool-heap)) int 20) - (gap-size ((this dead-pool-heap) (rec dead-pool-heap-rec)) int 21) - (gap-location ((this dead-pool-heap) (rec dead-pool-heap-rec)) pointer 22) - (find-gap ((this dead-pool-heap) (rec dead-pool-heap-rec)) dead-pool-heap-rec 23) - (find-gap-by-size ((this dead-pool-heap) (size int)) dead-pool-heap-rec 24) - (memory-free ((this dead-pool-heap)) int 25) - (compact-time ((this dead-pool-heap)) uint 26) + (new (symbol type basic int int) _type_ 0) + (compact (dead-pool-heap int) none 16) + (shrink-heap (dead-pool-heap process) dead-pool-heap 17) + (churn (dead-pool-heap int) none 18) + (memory-used (dead-pool-heap) int 19) + (memory-total (dead-pool-heap) int 20) + (gap-size (dead-pool-heap dead-pool-heap-rec) int 21) + (gap-location (dead-pool-heap dead-pool-heap-rec) pointer 22) + (find-gap (dead-pool-heap dead-pool-heap-rec) dead-pool-heap-rec 23) + (find-gap-by-size (dead-pool-heap int) dead-pool-heap-rec 24) + (memory-free (dead-pool-heap) int 25) + (compact-time (dead-pool-heap) uint 26) ) :size-assert #x68 @@ -360,7 +359,7 @@ ) (:methods - (new ((allocation symbol) (type-to-make type) (name symbol) (func function) (params (pointer uint64))) object 0) + (new (symbol type symbol function (pointer uint64)) object 0) ) :size-assert #xb0 :method-count-assert 9 @@ -372,7 +371,7 @@ ((exit (function object) :offset-assert 12)) ;; function to call to clean up (:methods - (new ((allocation symbol) (type-to-make type) (func (function object))) protect-frame) + (new (symbol type (function object)) protect-frame) ) :size-assert 16 :method-count-assert 9 @@ -436,9 +435,11 @@ (event (function basic int basic event-message-block object) :offset-assert 32) ) (:methods - (new ((allocation symbol) (type-to-make type) (name basic) (code function) - (trans (function object)) (enter (function object object object object object object object)) (exit (function object)) - (event (function basic int basic event-message-block object))) _type_ 0) + (new (symbol type basic function + (function object) + (function object object object object object object object) + (function object) + (function basic int basic event-message-block object)) _type_ 0) ) :method-count-assert 9 :size-assert #x24 diff --git a/test/decompiler/FormRegressionTest.cpp b/test/decompiler/FormRegressionTest.cpp index 99a4130e57..e130f5ad89 100644 --- a/test/decompiler/FormRegressionTest.cpp +++ b/test/decompiler/FormRegressionTest.cpp @@ -7,6 +7,7 @@ #include "decompiler/analysis/final_output.h" #include "decompiler/analysis/insert_lets.h" #include "common/goos/PrettyPrinter.h" +#include "common/util/json_util.h" #include "decompiler/IR2/Form.h" #include "third-party/json.hpp" @@ -67,6 +68,43 @@ void FormRegressionTest::TestData::add_string_at_label(const std::string& label_ EXPECT_TRUE(false); } +std::pair, std::unordered_map> +parse_var_json(const std::string& str) { + if (str.empty()) { + return {}; + } + + std::vector args; + std::unordered_map var_overrides; + + auto j = parse_commented_json(str); + + auto arg = j.find("args"); + if (arg != j.end()) { + for (auto& x : arg.value()) { + args.push_back(x); + } + } + + auto var = j.find("vars"); + if (var != j.end()) { + for (auto& vkv : var->get>()) { + LocalVarOverride override; + if (vkv.second.is_string()) { + override.name = vkv.second.get(); + } else if (vkv.second.is_array()) { + override.name = vkv.second[0].get(); + override.type = vkv.second[1].get(); + } else { + throw std::runtime_error("Invalid function var override."); + } + var_overrides[vkv.first] = override; + } + } + + return {args, var_overrides}; +} + std::unique_ptr FormRegressionTest::make_function( const std::string& code, const TypeSpec& function_type, @@ -74,7 +112,8 @@ std::unique_ptr FormRegressionTest::make_function( bool allow_pairs, const std::string& method_name, const std::vector>& strings, - const std::unordered_map>& hints) { + const std::unordered_map>& casts, + const std::string& var_map_json) { dts->type_prop_settings.locked = true; dts->type_prop_settings.reset(); dts->type_prop_settings.allow_pair = allow_pairs; @@ -112,7 +151,7 @@ std::unique_ptr FormRegressionTest::make_function( test->func.ir2.atomic_ops_succeeded = true; test->func.ir2.env.set_end_var(test->func.ir2.atomic_ops->end_op().return_var()); - EXPECT_TRUE(test->func.run_type_analysis_ir2(function_type, *dts, test->file, hints, {})); + EXPECT_TRUE(test->func.run_type_analysis_ir2(function_type, *dts, test->file, casts, {})); test->func.ir2.env.set_reg_use(analyze_ir2_register_usage(test->func)); @@ -133,8 +172,9 @@ std::unique_ptr FormRegressionTest::make_function( test->func.ir2.top_form->collect_vars(vars, true); if (do_expressions) { + auto config = parse_var_json(var_map_json); bool success = convert_to_expressions(test->func.ir2.top_form, *test->func.ir2.form_pool, - test->func, *dts); + test->func, config.first, config.second, *dts); EXPECT_TRUE(success); if (!success) { @@ -167,9 +207,11 @@ void FormRegressionTest::test(const std::string& code, bool allow_pairs, const std::string& method_name, const std::vector>& strings, - const std::unordered_map>& hints) { + const std::unordered_map>& casts, + const std::string& var_map_json) { auto ts = dts->parse_type_spec(type); - auto test = make_function(code, ts, do_expressions, allow_pairs, method_name, strings, hints); + auto test = make_function(code, ts, do_expressions, allow_pairs, method_name, strings, casts, + var_map_json); ASSERT_TRUE(test); auto expected_form = pretty_print::get_pretty_printer_reader().read_from_string(expected, false).as_pair()->car; @@ -193,9 +235,10 @@ void FormRegressionTest::test_final_function( const std::string& expected, bool allow_pairs, const std::vector>& strings, - const std::unordered_map>& hints) { + const std::unordered_map>& casts, + const std::string& var_map_json) { auto ts = dts->parse_type_spec(type); - auto test = make_function(code, ts, true, allow_pairs, "", strings, hints); + auto test = make_function(code, ts, true, allow_pairs, "", strings, casts, var_map_json); ASSERT_TRUE(test); auto expected_form = pretty_print::get_pretty_printer_reader().read_from_string(expected, false).as_pair()->car; @@ -211,20 +254,22 @@ void FormRegressionTest::test_final_function( EXPECT_TRUE(expected_form == actual_form); } -std::unordered_map> FormRegressionTest::parse_hint_json( +std::unordered_map> FormRegressionTest::parse_cast_json( const std::string& in) { - std::unordered_map> out; - auto hints = nlohmann::json::parse(in); - for (auto& hint : hints) { - auto idx = hint.at(0).get(); - for (size_t i = 1; i < hint.size(); i++) { - auto& assignment = hint.at(i); - TypeHint type_hint; - type_hint.reg = Register(assignment.at(0).get()); - type_hint.type_name = assignment.at(1).get(); - out[idx].push_back(type_hint); + std::unordered_map> out; + auto casts = nlohmann::json::parse(in); + + 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(); + out[idx].push_back(type_cast); } } + return out; } diff --git a/test/decompiler/FormRegressionTest.h b/test/decompiler/FormRegressionTest.h index b463f408b6..ba1f4b8d58 100644 --- a/test/decompiler/FormRegressionTest.h +++ b/test/decompiler/FormRegressionTest.h @@ -8,7 +8,7 @@ #include "decompiler/ObjectFile/LinkedObjectFile.h" namespace decompiler { -struct TypeHint; +struct TypeCast; } class FormRegressionTest : public ::testing::Test { @@ -34,7 +34,8 @@ class FormRegressionTest : public ::testing::Test { bool allow_pairs = false, const std::string& method_name = "", const std::vector>& strings = {}, - const std::unordered_map>& hints = {}); + const std::unordered_map>& casts = {}, + const std::string& var_map_json = ""); void test(const std::string& code, const std::string& type, @@ -43,7 +44,8 @@ class FormRegressionTest : public ::testing::Test { bool allow_pairs = false, const std::string& method_name = "", const std::vector>& strings = {}, - const std::unordered_map>& hints = {}); + const std::unordered_map>& casts = {}, + const std::string& var_map_json = ""); void test_no_expr(const std::string& code, const std::string& type, @@ -51,19 +53,20 @@ class FormRegressionTest : public ::testing::Test { bool allow_pairs = false, const std::string& method_name = "", const std::vector>& strings = {}, - const std::unordered_map>& hints = {}) { - test(code, type, expected, false, allow_pairs, method_name, strings, hints); + const std::unordered_map>& casts = {}, + const std::string& var_map_json = "") { + test(code, type, expected, false, allow_pairs, method_name, strings, casts, var_map_json); } - void test_with_expr( - const std::string& code, - const std::string& type, - const std::string& expected, - bool allow_pairs = false, - const std::string& method_name = "", - const std::vector>& strings = {}, - const std::unordered_map>& hints = {}) { - test(code, type, expected, true, allow_pairs, method_name, strings, hints); + void test_with_expr(const std::string& code, + const std::string& type, + const std::string& expected, + bool allow_pairs = false, + const std::string& method_name = "", + const std::vector>& strings = {}, + const std::unordered_map>& casts = {}, + const std::string& var_map_json = "") { + test(code, type, expected, true, allow_pairs, method_name, strings, casts, var_map_json); } void test_final_function( @@ -72,7 +75,8 @@ class FormRegressionTest : public ::testing::Test { const std::string& expected, bool allow_pairs = false, const std::vector>& strings = {}, - const std::unordered_map>& hints = {}); + const std::unordered_map>& casts = {}, + const std::string& var_map_json = ""); - std::unordered_map> parse_hint_json(const std::string& in); + std::unordered_map> parse_cast_json(const std::string& in); }; \ No newline at end of file diff --git a/test/decompiler/reference/gcommon_REF.gc b/test/decompiler/reference/gcommon_REF.gc index 8c46374670..ffb8cd397c 100644 --- a/test/decompiler/reference/gcommon_REF.gc +++ b/test/decompiler/reference/gcommon_REF.gc @@ -235,7 +235,10 @@ (let ((obj-type (-> obj type)) (end-type object) ) - (until (begin (set! obj-type (-> obj-type parent)) (= obj-type end-type)) + (until (begin + (set! obj-type (-> obj-type parent)) + (= obj-type end-type) + ) (if (= obj-type parent-type) (return #t) ) @@ -247,11 +250,10 @@ ;; definition for function type-type? (defun type-type? ((child-type type) (parent-type type)) (let ((end-type object)) - (until - (begin - (set! child-type (-> child-type parent)) - (or (= child-type end-type) (zero? child-type)) - ) + (until (begin + (set! child-type (-> child-type parent)) + (or (= child-type end-type) (zero? child-type)) + ) (if (= child-type parent-type) (return #t) ) @@ -834,7 +836,7 @@ ) (else (dotimes (s5-9 (-> obj length)) - (format #t "~T [~D] ~D~%" s5-9 (-> obj s5-9)) + (format #t "~T [~D] ~D~%" s5-9 (-> (the-as (array int32) obj) s5-9)) ) ) ) @@ -844,12 +846,12 @@ (cond ((= (-> obj content-type) float) (dotimes (s5-10 (-> obj length)) - (format #t "~T [~D] ~f~%" s5-10 (-> obj s5-10)) + (format #t "~T [~D] ~f~%" s5-10 (-> (the-as (array float) obj) s5-10)) ) ) (else (dotimes (s5-11 (-> obj length)) - (format #t "~T [~D] ~A~%" s5-11 (-> obj s5-11)) + (format #t "~T [~D] ~A~%" s5-11 (-> (the-as (array basic) obj) s5-11)) ) ) ) @@ -1111,9 +1113,10 @@ ) #f ) - ((or (not in-goal-mem) (begin (let ((v1-10 #x8000)) - (.daddu v1-11 v1-10 s7-0) - ) + ((or (not in-goal-mem) (begin + (let ((v1-10 #x8000)) + (.daddu v1-11 v1-10 s7-0) + ) (< (the-as uint obj) (the-as uint v1-11)) ) ) @@ -1283,9 +1286,10 @@ ) ) ) - ((begin (let ((v1-47 #x8000)) - (.daddu v1-48 v1-47 s7-0) - ) + ((begin + (let ((v1-47 #x8000)) + (.daddu v1-48 v1-47 s7-0) + ) (< (the-as uint obj) (the-as uint v1-48)) ) (if diff --git a/test/decompiler/reference/gkernel-h_REF.gc b/test/decompiler/reference/gkernel-h_REF.gc new file mode 100644 index 0000000000..2b34499f70 --- /dev/null +++ b/test/decompiler/reference/gkernel-h_REF.gc @@ -0,0 +1,384 @@ +;;-*-Lisp-*- +(in-package goal) + +;; definition of type kernel-context +(deftype kernel-context (basic) + ((prevent-from-run uint32 :offset-assert 4) + (require-for-run uint32 :offset-assert 8) + (allow-to-run uint32 :offset-assert 12) + (next-pid int32 :offset-assert 16) + (fast-stack-top pointer :offset-assert 20) + (current-process basic :offset-assert 24) + (relocating-process basic :offset-assert 28) + (relocating-min int32 :offset-assert 32) + (relocating-max int32 :offset-assert 36) + (relocating-offset int32 :offset-assert 40) + (low-memory-message basic :offset-assert 44) + ) + :method-count-assert 9 + :size-assert #x30 + :flag-assert #x900000030 + ) + +;; definition for method 3 of type kernel-context +(defmethod inspect kernel-context ((obj kernel-context)) + (format #t "[~8x] ~A~%" obj (-> obj type)) + (format #t "~Tprevent-from-run: ~D~%" (-> obj prevent-from-run)) + (format #t "~Trequire-for-run: ~D~%" (-> obj require-for-run)) + (format #t "~Tallow-to-run: ~D~%" (-> obj allow-to-run)) + (format #t "~Tnext-pid: ~D~%" (-> obj next-pid)) + (format #t "~Tfast-stack-top: #x~X~%" (-> obj fast-stack-top)) + (format #t "~Tcurrent-process: ~A~%" (-> obj current-process)) + (format #t "~Trelocating-process: ~A~%" (-> obj relocating-process)) + (format #t "~Trelocating-min: #x~X~%" (-> obj relocating-min)) + (format #t "~Trelocating-max: #x~X~%" (-> obj relocating-max)) + (format #t "~Trelocating-offset: ~D~%" (-> obj relocating-offset)) + (format #t "~Tlow-memory-message: ~A~%" (-> obj low-memory-message)) + obj + ) + +;; definition of type thread +(deftype thread (basic) + ((name basic :offset-assert 4) + (process process :offset-assert 8) + (previous thread :offset-assert 12) + (suspend-hook (function cpu-thread none) :offset-assert 16) + (resume-hook (function cpu-thread none) :offset-assert 20) + (pc pointer :offset-assert 24) + (sp pointer :offset-assert 28) + (stack-top pointer :offset-assert 32) + (stack-size int32 :offset-assert 36) + ) + :method-count-assert 12 + :size-assert #x28 + :flag-assert #xc00000028 + (:methods + (stack-size-set! (_type_ int) none 9) + (thread-suspend (_type_) none 10) + (thread-resume (_type_) none 11) + ) + ) + +;; definition for method 3 of type thread +(defmethod inspect thread ((obj thread)) + (format #t "[~8x] ~A~%" obj (-> obj type)) + (format #t "~Tname: ~A~%" (-> obj name)) + (format #t "~Tprocess: ~A~%" (-> obj process)) + (format #t "~Tprevious: ~A~%" (-> obj previous)) + (format #t "~Tsuspend-hook: ~A~%" (-> obj suspend-hook)) + (format #t "~Tresume-hook: ~A~%" (-> obj resume-hook)) + (format #t "~Tpc: #x~X~%" (-> obj pc)) + (format #t "~Tsp: #x~X~%" (-> obj sp)) + (format #t "~Tstack-top: #x~X~%" (-> obj stack-top)) + (format #t "~Tstack-size: ~D~%" (-> obj stack-size)) + obj + ) + +;; definition of type cpu-thread +(deftype cpu-thread (thread) + ((rreg uint64 7 :offset-assert 40) + (freg float 8 :offset-assert 96) + (stack uint8 :dynamic :offset-assert 128) + ) + :method-count-assert 12 + :size-assert #x80 + :flag-assert #xc00000080 + (:methods + (new (symbol type process symbol int pointer) _type_ 0) + ) + ) + +;; definition for method 3 of type cpu-thread +(defmethod inspect cpu-thread ((obj cpu-thread)) + (format #t "[~8x] ~A~%" obj (-> obj type)) + (format #t "~Tname: ~A~%" (-> obj name)) + (format #t "~Tprocess: ~A~%" (-> obj process)) + (format #t "~Tprevious: ~A~%" (-> obj previous)) + (format #t "~Tsuspend-hook: ~A~%" (-> obj suspend-hook)) + (format #t "~Tresume-hook: ~A~%" (-> obj resume-hook)) + (format #t "~Tpc: #x~X~%" (-> obj pc)) + (format #t "~Tsp: #x~X~%" (-> obj sp)) + (format #t "~Tstack-top: #x~X~%" (-> obj stack-top)) + (format #t "~Tstack-size: ~D~%" (-> obj stack-size)) + (format #t "~Trreg[8] @ #x~X~%" (-> obj rreg)) + (format #t "~Tfreg[6] @ #x~X~%" (&-> obj freg 2)) + (format #t "~Tstack[0] @ #x~X~%" (-> obj stack)) + obj + ) + +;; definition of type dead-pool +(deftype dead-pool (process-tree) + () + :method-count-assert 16 + :size-assert #x20 + :flag-assert #x1000000020 + (:methods + (new (symbol type int int basic) _type_ 0) + (get-process (_type_ type int) process 14) + (return-process (_type_ process) none 15) + ) + ) + +;; definition for method 3 of type dead-pool +(defmethod inspect dead-pool ((obj dead-pool)) + (format #t "[~8x] ~A~%" obj (-> obj type)) + (format #t "~Tname: ~A~%" (-> obj name)) + (format #t "~Tmask: ~D~%" (-> obj mask)) + (format #t "~Tparent: #x~X~%" (-> obj parent)) + (format #t "~Tbrother: #x~X~%" (-> obj brother)) + (format #t "~Tchild: #x~X~%" (-> obj child)) + (format #t "~Tppointer: #x~X~%" (-> obj ppointer)) + (format #t "~Tself: ~A~%" (-> obj self)) + obj + ) + +;; definition of type dead-pool-heap-rec +(deftype dead-pool-heap-rec (structure) + ((process process :offset-assert 0) + (prev dead-pool-heap-rec :offset-assert 4) + (next dead-pool-heap-rec :offset-assert 8) + ) + :pack-me + :method-count-assert 9 + :size-assert #xc + :flag-assert #x90000000c + ) + +;; definition for method 3 of type dead-pool-heap-rec +(defmethod inspect dead-pool-heap-rec ((obj dead-pool-heap-rec)) + (format #t "[~8x] ~A~%" obj 'dead-pool-heap-rec) + (format #t "~Tprocess: ~A~%" (-> obj process)) + (format #t "~Tprev: #~%" (-> obj prev)) + (format #t "~Tnext: #~%" (-> obj next)) + obj + ) + +;; definition of type dead-pool-heap +(deftype dead-pool-heap (dead-pool) + ((allocated-length int32 :offset-assert 32) + (compact-time uint32 :offset-assert 36) + (compact-count-targ uint32 :offset-assert 40) + (compact-count uint32 :offset-assert 44) + (fill-percent float :offset-assert 48) + (first-gap dead-pool-heap-rec :offset-assert 52) + (first-shrink dead-pool-heap-rec :offset-assert 56) + (heap kheap :inline :offset-assert 64) + (alive-list dead-pool-heap-rec :inline :offset-assert 80) + (last dead-pool-heap-rec :offset 84) + (dead-list dead-pool-heap-rec :inline :offset-assert 92) + (process-list dead-pool-heap-rec :inline :dynamic :offset-assert 104) + ) + :method-count-assert 27 + :size-assert #x68 + :flag-assert #x1b00000068 + (:methods + (new (symbol type basic int int) _type_ 0) + (compact (dead-pool-heap int) none 16) + (shrink-heap (dead-pool-heap process) dead-pool-heap 17) + (churn (dead-pool-heap int) none 18) + (memory-used (dead-pool-heap) int 19) + (memory-total (dead-pool-heap) int 20) + (gap-size (dead-pool-heap dead-pool-heap-rec) int 21) + (gap-location (dead-pool-heap dead-pool-heap-rec) pointer 22) + (find-gap (dead-pool-heap dead-pool-heap-rec) dead-pool-heap-rec 23) + (find-gap-by-size (dead-pool-heap int) dead-pool-heap-rec 24) + (memory-free (dead-pool-heap) int 25) + (compact-time (dead-pool-heap) uint 26) + ) + ) + +;; definition for method 3 of type dead-pool-heap +;; INFO: this function exists in multiple non-identical object files +(defmethod inspect dead-pool-heap ((obj dead-pool-heap)) + (format #t "[~8x] ~A~%" obj (-> obj type)) + (format #t "~Tname: ~A~%" (-> obj name)) + (format #t "~Tmask: ~D~%" (-> obj mask)) + (format #t "~Tparent: #x~X~%" (-> obj parent)) + (format #t "~Tbrother: #x~X~%" (-> obj brother)) + (format #t "~Tchild: #x~X~%" (-> obj child)) + (format #t "~Tppointer: #x~X~%" (-> obj ppointer)) + (format #t "~Tself: ~A~%" (-> obj self)) + (format #t "~Tallocated-length: ~D~%" (-> obj allocated-length)) + (format #t "~Tcompact-time: ~D~%" (-> obj compact-time)) + (format #t "~Tcompact-count-targ: ~D~%" (-> obj compact-count-targ)) + (format #t "~Tcompact-count: ~D~%" (-> obj compact-count)) + (format #t "~Tfill-percent: ~f~%" (-> obj fill-percent)) + (format #t "~Tfirst-gap: #~%" (-> obj first-gap)) + (format + #t + "~Tfirst-shrink: #~%" + (-> obj first-shrink) + ) + (format #t "~Theap: #~%" (-> obj heap)) + (format #t "~Talive-list: #~%" (-> obj alive-list)) + (format #t "~Tlast: #~%" (-> obj alive-list prev)) + (format #t "~Tdead-list: #~%" (-> obj dead-list)) + (format #t "~Tprocess-list[0] @ #x~X~%" (-> obj process-list)) + obj + ) + +;; definition of type catch-frame +(deftype catch-frame (stack-frame) + ((sp int32 :offset-assert 12) + (ra int32 :offset-assert 16) + (freg float 10 :offset-assert 20) + (rreg uint128 7 :offset-assert 64) + ) + :method-count-assert 9 + :size-assert #xb0 + :flag-assert #x9000000b0 + (:methods + (new (symbol type symbol function (pointer uint64)) object 0) + ) + ) + +;; definition for method 3 of type catch-frame +(defmethod inspect catch-frame ((obj catch-frame)) + (format #t "[~8x] ~A~%" obj (-> obj type)) + (format #t "~Tname: ~A~%" (-> obj name)) + (format #t "~Tnext: ~A~%" (-> obj next)) + (format #t "~Tsp: #x~X~%" (-> obj sp)) + (format #t "~Tra: #x~X~%" (-> obj ra)) + (format #t "~Tfreg[6] @ #x~X~%" (-> obj freg)) + (format #t "~Trreg[8] @ #x~X~%" (&-> obj freg 7)) + obj + ) + +;; definition of type protect-frame +(deftype protect-frame (stack-frame) + ((exit (function object) :offset-assert 12) + ) + :method-count-assert 9 + :size-assert #x10 + :flag-assert #x900000010 + (:methods + (new (symbol type (function object)) protect-frame 0) + ) + ) + +;; definition for method 3 of type protect-frame +(defmethod inspect protect-frame ((obj protect-frame)) + (format #t "[~8x] ~A~%" obj (-> obj type)) + (format #t "~Tname: ~A~%" (-> obj name)) + (format #t "~Tnext: ~A~%" (-> obj next)) + (format #t "~Texit: ~A~%" (-> obj exit)) + obj + ) + +;; definition of type handle +(deftype handle (uint64) + ((process (pointer process) :offset 0 :size 32) + (pid int32 :offset 32 :size 32) + (u64 uint64 :offset 0 :size 64) + ) + :method-count-assert 9 + :size-assert #x8 + :flag-assert #x900000008 + ) + +;; definition for method 3 of type handle +(defmethod inspect handle ((obj handle)) + (format #t "[~8x] ~A~%" obj 'handle) + (format #t "~Tprocess: #x~X~%" (shr (shl (the-as int obj) 32) 32)) + (format #t "~Tpid: ~D~%" (sar (the-as int obj) 32)) + obj + ) + +;; definition for method 2 of type handle +;; WARN: Unsupported inline assembly instruction kind - [5] +;; WARN: Unsupported inline assembly instruction kind - [73] +(defmethod print handle ((obj handle)) + (local-vars (a2-0 int) (s7-0 none)) + (if (nonzero? obj) + (let ((t9-0 format) + (a0-1 #t) + (a1-0 "#") + (v1-0 obj) + ) + (.subu a2-0 (the-as handle v1-0) s7-0) + (let ((a2-1 (and (nonzero? a2-0) (begin + (.sllv a2-2 (the-as handle v1-0) r0-0) + (let ((a3-0 (-> a2-2 0))) + (set! + a2-1 + (if (= (sar v1-0 32) (-> a3-0 pid)) + a3-0 + ) + ) + ) + (let ((v1-2 a2-1)) + ) + a2-1 + ) + ) + ) + ) + (t9-0 a0-1 a1-0 a2-1 (sar (the-as int obj) 32)) + ) + ) + (format #t "#") + ) + obj + ) + +;; definition of type state +(deftype state (protect-frame) + ((code function :offset-assert 16) + (trans (function object) :offset-assert 20) + (post function :offset-assert 24) + (enter (function object object object object object object object) :offset-assert 28) + (event (function basic int basic event-message-block object) :offset-assert 32) + ) + :method-count-assert 9 + :size-assert #x24 + :flag-assert #x900000024 + (:methods + (new (symbol type basic function (function object) (function object object object object object object object) (function object) (function basic int basic event-message-block object)) _type_ 0) + ) + ) + +;; definition for method 3 of type state +(defmethod inspect state ((obj state)) + (format #t "[~8x] ~A~%" obj (-> obj type)) + (format #t "~Tname: ~A~%" (-> obj name)) + (format #t "~Tnext: ~A~%" (-> obj next)) + (format #t "~Texit: ~A~%" (-> obj exit)) + (format #t "~Tcode: ~A~%" (-> obj code)) + (format #t "~Ttrans: ~A~%" (-> obj trans)) + (format #t "~Tpost: ~A~%" (-> obj post)) + (format #t "~Tenter: ~A~%" (-> obj enter)) + (format #t "~Tevent: ~A~%" (-> obj event)) + obj + ) + +;; definition of type event-message-block +(deftype event-message-block (structure) + ((to basic :offset-assert 0) + (from basic :offset-assert 4) + (num-params int32 :offset-assert 8) + (message basic :offset-assert 12) + (param uint64 7 :offset-assert 16) + ) + :method-count-assert 9 + :size-assert #x48 + :flag-assert #x900000048 + ) + +;; definition for method 3 of type event-message-block +(defmethod inspect event-message-block ((obj event-message-block)) + (format #t "[~8x] ~A~%" obj 'event-message-block) + (format #t "~Tto: ~A~%" (-> obj to)) + (format #t "~Tfrom: ~A~%" (-> obj from)) + (format #t "~Tnum-params: ~D~%" (-> obj num-params)) + (format #t "~Tmessage: ~A~%" (-> obj message)) + (format #t "~Tparam[7] @ #x~X~%" (-> obj param)) + obj + ) + +;; failed to figure out what this is: +(let ((v0-11 0)) + ) + + + + diff --git a/test/decompiler/test_FormBeforeExpressions.cpp b/test/decompiler/test_FormBeforeExpressions.cpp index 5001c9704b..717ae5dc73 100644 --- a/test/decompiler/test_FormBeforeExpressions.cpp +++ b/test/decompiler/test_FormBeforeExpressions.cpp @@ -410,7 +410,7 @@ TEST_F(FormRegressionTest, SimpleLoopMergeCheck) { " lw v0, -2(a0)\n" " jr ra\n" " daddu sp, sp, r0"; - std::string type = "(function pair int)"; + std::string type = "(function object int)"; std::string expected = "(begin\n" " (set! v1-0 0)\n" diff --git a/test/decompiler/test_FormExpressionBuild.cpp b/test/decompiler/test_FormExpressionBuild.cpp index d3df983f04..b8a2ee17fa 100644 --- a/test/decompiler/test_FormExpressionBuild.cpp +++ b/test/decompiler/test_FormExpressionBuild.cpp @@ -2273,15 +2273,17 @@ TEST_F(FormRegressionTest, ExprPrintName) { " (string= (the-as string arg0) (the-as string arg1))\n" " )\n" " ((and (= (-> arg0 type) string) (= (-> arg1 type) symbol))\n" - " (string= (the-as string arg0) (-> (+ 65336 (the-as int arg1)) 0))\n" + " (string= (the-as string arg0) (the-as string (-> (the-as (pointer uint32) (+ 65336 " + "(the-as int arg1))))))\n" " )\n" " ((and (= (-> arg1 type) string) (= (-> arg0 type) symbol))\n" - " (string= (the-as string arg1) (-> (+ 65336 (the-as int arg0)) 0))\n" + " (string= (the-as string arg1) (the-as string (-> (the-as (pointer uint32) (+ 65336 " + "(the-as int arg0))))))\n" " )\n" " )"; test_with_expr(func, type, expected, false, "", {}, - parse_hint_json("[\t\t[24, [\"a1\", \"symbol\"]],\n" - "\t\t[39, [\"a0\", \"symbol\"]]]")); + parse_cast_json("[\t\t[24, \"a1\", \"symbol\"],\n" + "\t\t[39, \"a0\", \"symbol\"]]")); } TEST_F(FormRegressionTest, ExprProfileBarMethod9) { diff --git a/test/decompiler/test_FormExpressionBuildLong.cpp b/test/decompiler/test_FormExpressionBuildLong.cpp index 42870253ad..50b6b08844 100644 --- a/test/decompiler/test_FormExpressionBuildLong.cpp +++ b/test/decompiler/test_FormExpressionBuildLong.cpp @@ -690,21 +690,21 @@ TEST_F(FormRegressionTest, ExprArrayMethod2) { {"L336", "~A"}, {"L335", " ~A"}, {"L334", ")"}}, - parse_hint_json("[" - "\t\t[23, [\"gp\", \"(array int32)\"]],\n" - "\t\t[43, [\"gp\", \"(array uint32)\"]],\n" - "\t\t[63, [\"gp\", \"(array int64)\"]],\n" - "\t\t[83, [\"gp\", \"(array uint64)\"]],\n" - "\t\t[102, [\"gp\", \"(array int8)\"]],\n" - "\t\t[121, [\"gp\", \"(array uint8)\"]],\n" - "\t\t[141, [\"gp\", \"(array int16)\"]],\n" - "\t\t[161, [\"gp\", \"(array uint16)\"]],\n" - "\t\t[186, [\"gp\", \"(array uint128)\"]],\n" - "\t\t[204, [\"gp\", \"(array int32)\"]],\n" - "\t\t[223, [\"gp\", \"(array float)\"]],\n" - "\t\t[232, [\"gp\", \"(array float)\"]],\n" - "\t\t[249, [\"gp\", \"(array basic)\"]],\n" - "\t\t[258, [\"gp\", \"(array basic)\"]]]")); + parse_cast_json("[" + "\t\t[23, \"gp\", \"(array int32)\"],\n" + "\t\t[43, \"gp\", \"(array uint32)\"],\n" + "\t\t[63, \"gp\", \"(array int64)\"],\n" + "\t\t[83, \"gp\", \"(array uint64)\"],\n" + "\t\t[102, \"gp\", \"(array int8)\"],\n" + "\t\t[121, \"gp\", \"(array uint8)\"],\n" + "\t\t[141, \"gp\", \"(array int16)\"],\n" + "\t\t[161, \"gp\", \"(array uint16)\"],\n" + "\t\t[186, \"gp\", \"(array uint128)\"],\n" + "\t\t[204, \"gp\", \"(array int32)\"],\n" + "\t\t[223, \"gp\", \"(array float)\"],\n" + "\t\t[232, \"gp\", \"(array float)\"],\n" + "\t\t[249, \"gp\", \"(array basic)\"],\n" + "\t\t[258, \"gp\", \"(array basic)\"]]")); } TEST_F(FormRegressionTest, ExprArrayMethod3) { @@ -1238,7 +1238,7 @@ TEST_F(FormRegressionTest, ExprArrayMethod3) { " (else\n" " (dotimes\n" " (s5-9 (-> arg0 length))\n" - " (format #t \"~T [~D] ~D~%\" s5-9 (-> arg0 s5-9))\n" + " (format #t \"~T [~D] ~D~%\" s5-9 (-> (the-as (array int32) arg0) s5-9))\n" " )\n" " )\n" " )\n" @@ -1272,18 +1272,18 @@ TEST_F(FormRegressionTest, ExprArrayMethod3) { {"L327", "~T [~D] #x~X~%"}, {"L326", "~T [~D] ~f~%"}, {"L325", "~T [~D] ~A~%"}}, - parse_hint_json("[\t\t[44, [\"gp\", \"(array int32)\"]],\n" - "\t\t[62, [\"gp\", \"(array uint32)\"]],\n" - "\t\t[80, [\"gp\", \"(array int64)\"]],\n" - "\t\t[98, [\"gp\", \"(array uint64)\"]],\n" - "\t\t[115, [\"gp\", \"(array int8)\"]],\n" - "\t\t[132, [\"gp\", \"(array int8)\"]],\n" - "\t\t[150, [\"gp\", \"(array int16)\"]],\n" - "\t\t[168, [\"gp\", \"(array uint16)\"]],\n" - "\t\t[191, [\"gp\", \"(array uint128)\"]],\n" - "\t\t[204, [\"gp\", \"(array int32)\"]],\n" - "\t\t[226, [\"gp\", \"(array float)\"]],\n" - "\t\t[243, [\"gp\", \"(array basic)\"]]]")); + parse_cast_json("[\t\t[44, \"gp\", \"(array int32)\"],\n" + "\t\t[62, \"gp\", \"(array uint32)\"],\n" + "\t\t[80, \"gp\", \"(array int64)\"],\n" + "\t\t[98, \"gp\", \"(array uint64)\"],\n" + "\t\t[115, \"gp\", \"(array int8)\"],\n" + "\t\t[132, \"gp\", \"(array int8)\"],\n" + "\t\t[150, \"gp\", \"(array int16)\"],\n" + "\t\t[168, \"gp\", \"(array uint16)\"],\n" + "\t\t[191, \"gp\", \"(array uint128)\"],\n" + "\t\t[207, \"gp\", \"(array int32)\"],\n" + "\t\t[226, \"gp\", \"(array float)\"],\n" + "\t\t[243, \"gp\", \"(array basic)\"]]")); } TEST_F(FormRegressionTest, ExprValid) { @@ -2387,7 +2387,7 @@ TEST_F(FormRegressionTest, ExprStringToInt) { " (>= (the-as uint 70) (-> a0-3 0))\n" " )\n" " (set! v0-0 (+ (+ (-> a0-3 0) -55) (the-as uint (shl v0-0 4))))\n" - " (let ((a1-14 v0-0)))\n" + " (let ((a1-14 (the-as uint v0-0))))\n" " )\n" " (else\n" " (cond\n" @@ -2396,11 +2396,11 @@ TEST_F(FormRegressionTest, ExprStringToInt) { " (>= (the-as uint 102) (-> a0-3 0))\n" " )\n" " (set! v0-0 (+ (+ (-> a0-3 0) -87) (the-as uint (shl v0-0 4))))\n" - " (let ((a1-20 v0-0)))\n" + " (let ((a1-20 (the-as uint v0-0))))\n" " )\n" " (else\n" " (set! v0-0 (+ (+ (-> a0-3 0) -48) (the-as uint (shl v0-0 4))))\n" - " (let ((a1-23 v0-0)))\n" + " (let ((a1-23 (the-as uint v0-0))))\n" " )\n" " )\n" " )\n" diff --git a/test/decompiler/test_gkernel_decomp.cpp b/test/decompiler/test_gkernel_decomp.cpp index 09c9f23900..82ef133c3a 100644 --- a/test/decompiler/test_gkernel_decomp.cpp +++ b/test/decompiler/test_gkernel_decomp.cpp @@ -323,7 +323,7 @@ TEST_F(FormRegressionTest, ExprMethod0Thread) { " 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 type = "(function symbol type process symbol int pointer cpu-thread)"; std::string expected = "(let\n" " ((v0-0\n" @@ -339,19 +339,21 @@ TEST_F(FormRegressionTest, ExprMethod0Thread) { " )\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" - " (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" + " (set! (-> (the-as cpu-thread v0-0) name) arg3)\n" + " (set! (-> (the-as cpu-thread v0-0) process) arg2)\n" + " (set! (-> (the-as cpu-thread v0-0) sp) arg5)\n" + " (set! (-> (the-as cpu-thread v0-0) stack-top) arg5)\n" + " (set! (-> (the-as cpu-thread v0-0) previous) (-> arg2 top-thread))\n" + " (set! (-> arg2 top-thread) (the-as cpu-thread v0-0))\n" + " (set! (-> (the-as cpu-thread v0-0) suspend-hook) (method-of-object (the-as cpu-thread " + "v0-0) thread-suspend))\n" + " (set! (-> (the-as cpu-thread v0-0) resume-hook) (method-of-object (the-as cpu-thread " + "v0-0) thread-resume))\n" + " (set! (-> (the-as cpu-thread v0-0) stack-size) arg4)\n" + " (the-as cpu-thread v0-0)\n" " )"; test_with_expr(func, type, expected, false, "cpu-thread", {}, - parse_hint_json("[[13, [\"v0\", \"cpu-thread\"]]]")); + parse_cast_json("[[[13, 28], \"v0\", \"cpu-thread\"]]")); } TEST_F(FormRegressionTest, ExprMethod5CpuThread) { @@ -635,36 +637,37 @@ TEST_F(FormRegressionTest, ExprMethod0Process) { " )\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) #f)\n" - " (set! (-> v0-0 allocated-length) arg3)\n" - " (set! (-> v0-0 top-thread) #f)\n" - " (set! (-> v0-0 main-thread) #f)\n" + " (set! (-> (the-as process v0-0) status) (quote dead))\n" + " (set! (-> (the-as process v0-0) pid) 0)\n" + " (set! (-> (the-as process v0-0) pool) #f)\n" + " (set! (-> (the-as process v0-0) allocated-length) arg3)\n" + " (set! (-> (the-as process v0-0) top-thread) #f)\n" + " (set! (-> (the-as process v0-0) main-thread) #f)\n" " (let\n" - " ((v1-5 (-> v0-0 stack)))\n" - " (set! (-> v0-0 heap-cur) v1-5)\n" - " (set! (-> v0-0 heap-base) v1-5)\n" + " ((v1-5 (-> (the-as process v0-0) stack)))\n" + " (set! (-> (the-as process v0-0) heap-cur) v1-5)\n" + " (set! (-> (the-as process v0-0) heap-base) v1-5)\n" " )\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) #f)\n" - " (set! (-> v0-0 state) #f)\n" - " (set! (-> v0-0 next-state) #f)\n" - " (set! (-> v0-0 entity) #f)\n" - " (set! (-> v0-0 trans-hook) #f)\n" - " (set! (-> v0-0 post-hook) #f)\n" - " (set! (-> v0-0 event-hook) #f)\n" - " (set! (-> v0-0 parent) #f)\n" - " (set! (-> v0-0 brother) #f)\n" - " (set! (-> v0-0 child) #f)\n" - " (set! (-> v0-0 self) v0-0)\n" - " (set! (-> v0-0 ppointer) (&-> v0-0 self))\n" - " v0-0\n" + " (set! (-> (the-as process v0-0) heap-top) (&-> (the-as process v0-0) stack (-> (the-as " + "process v0-0) allocated-length)))\n" + " (set! (-> (the-as process v0-0) stack-frame-top) (-> (the-as process v0-0) heap-top))\n" + " (set! (-> (the-as process v0-0) stack-frame-top) #f)\n" + " (set! (-> (the-as process v0-0) state) #f)\n" + " (set! (-> (the-as process v0-0) next-state) #f)\n" + " (set! (-> (the-as process v0-0) entity) #f)\n" + " (set! (-> (the-as process v0-0) trans-hook) #f)\n" + " (set! (-> (the-as process v0-0) post-hook) #f)\n" + " (set! (-> (the-as process v0-0) event-hook) #f)\n" + " (set! (-> (the-as process v0-0) parent) #f)\n" + " (set! (-> (the-as process v0-0) brother) #f)\n" + " (set! (-> (the-as process v0-0) child) #f)\n" + " (set! (-> (the-as process v0-0) self) (the-as process v0-0))\n" + " (set! (-> (the-as process v0-0) ppointer) (&-> (the-as process v0-0) self))\n" + " (the-as process 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\"]]]")); + parse_cast_json("[\t\t[12, \"a0\", \"int\"],\n" + "\t\t[[13, 43], \"v0\", \"process\"]]")); } TEST_F(FormRegressionTest, ExprInspectProcessHeap) { @@ -720,18 +723,19 @@ TEST_F(FormRegressionTest, ExprInspectProcessHeap) { std::string expected = "(begin\n" " (let\n" - " ((s5-0 (&+ (-> arg0 heap-base) 4)))\n" + " ((obj (the-as basic (&+ (-> arg0 heap-base) 4))))\n" " (while\n" - " (< (the-as int s5-0) (the-as int (-> arg0 heap-cur)))\n" - " (inspect (the-as basic s5-0))\n" - " (+! (the-as int s5-0) (logand -16 (+ (asize-of s5-0) 15)))\n" + " (< (the-as int obj) (the-as int (-> arg0 heap-cur)))\n" + " (inspect obj)\n" + " (+! (the-as int obj) (logand -16 (+ (asize-of obj) 15)))\n" " )\n" " )\n" " #f\n" " )"; test_with_expr(func, type, expected, false, "", {}, - parse_hint_json("[\t\t[4, [\"s5\", \"basic\"]],\n" - "\t\t[17, [\"s5\", \"int\"]]]")); + parse_cast_json("[\t\t[[4,11], \"s5\", \"basic\"],\n" + "\t\t[[17,20], \"s5\", \"int\"]]"), + "{\"vars\":{\"s5-0\":[\"obj\", \"basic\"]}}"); } // note: skipped method 3 process @@ -1049,50 +1053,50 @@ TEST_F(FormRegressionTest, ExprMethod14DeadPool) { std::string type = "(function dead-pool type int process)"; // todo - why does one work but not the other?? std::string expected = - "(let\n" - " ((s4-0 (-> arg0 child)))\n" + "(let ((s4-0 (the-as object (-> arg0 child))))\n" " (when\n" - " (and (not s4-0) *debug-segment* (!= arg0 *debug-dead-pool*))\n" + " (and\n" + " (not (the-as (pointer process-tree) s4-0))\n" + " *debug-segment*\n" + " (!= arg0 *debug-dead-pool*)\n" + " )\n" " (set! s4-0 (get-process *debug-dead-pool* arg1 arg2))\n" - " (if\n" - " s4-0\n" - " (let\n" - " ((t9-1 format)\n" - " (a0-2 0)\n" - " (a1-2\n" - " \"WARNING: ~A ~A had to be allocated from the debug pool, because ~A was empty.~%\"\n" - " )\n" - " (a2-1 arg1)\n" - " (v1-6 s4-0)\n" - " )\n" - " (t9-1\n" - " a0-2\n" - " a1-2\n" - " a2-1\n" - " (if v1-6 (-> (the-as (pointer process-tree) v1-6) 0 self))\n" + " (if (the-as process s4-0)\n" + " (let ((t9-1 format)\n" + " (a0-2 0)\n" + " (a1-2\n" + " \"WARNING: ~A ~A had to be allocated from the debug pool, because ~A was " + "empty.~%\"\n" + " )\n" + " (a2-1 arg1)\n" + " (v1-6 (the-as process s4-0))\n" + " )\n" + " (t9-1 a0-2 a1-2 a2-1 (if (the-as process v1-6)\n" + " (-> (the-as (pointer process-tree) v1-6) 0 self)\n" + " )\n" " (-> arg0 name)\n" " )\n" " )\n" " )\n" " )\n" - " (the-as\n" - " process\n" - " (cond\n" - " (s4-0\n" - " (set! (-> (the-as (pointer process-tree) s4-0) 0 type) arg1)\n" - " (-> s4-0 0)\n" - " )\n" - " (else\n" - " (format\n" - " 0\n" - " \"WARNING: ~A ~A could not be allocated, because ~A was empty.~%\"\n" - " arg1\n" - " (if s4-0 (-> s4-0 0 self))\n" - " (-> arg0 name)\n" - " )\n" - " #f\n" - " )\n" - " )\n" + " (the-as process (cond\n" + " (s4-0\n" + " (set! (-> (the-as (pointer process-tree) s4-0) 0 type) arg1)\n" + " (-> (the-as (pointer process-tree) s4-0) 0)\n" + " )\n" + " (else\n" + " (format\n" + " 0\n" + " \"WARNING: ~A ~A could not be allocated, because ~A was empty.~%\"\n" + " arg1\n" + " (if (the-as (pointer process-tree) s4-0)\n" + " (-> (the-as (pointer process-tree) s4-0) 0 self)\n" + " )\n" + " (-> arg0 name)\n" + " )\n" + " #f\n" + " )\n" + " )\n" " )\n" " )"; @@ -1101,8 +1105,8 @@ TEST_F(FormRegressionTest, ExprMethod14DeadPool) { func, type, expected, false, "dead-pool", {{"L315", "WARNING: ~A ~A had to be allocated from the debug pool, because ~A was empty.~%"}, {"L314", "WARNING: ~A ~A could not be allocated, because ~A was empty.~%"}}, - parse_hint_json("[\t\t[24, [\"v1\", \"(pointer process-tree)\"]],\n" - "\t\t[30, [\"s4\", \"(pointer process-tree)\"]]]")); + parse_cast_json("[\t\t[24, \"v1\", \"(pointer process-tree)\"],\n" + "\t\t[[30,39], \"s4\", \"(pointer process-tree)\"]]")); } TEST_F(FormRegressionTest, ExprMethod15DeadPool) { @@ -1226,7 +1230,7 @@ TEST_F(FormRegressionTest, ExprMethod0DeadPoolHeap) { std::string type = "(function symbol type basic int int dead-pool-heap)"; std::string expected = "(let\n" - " ((v0-0\n" + " ((obj\n" " (object-new\n" " arg0\n" " arg1\n" @@ -1240,46 +1244,48 @@ TEST_F(FormRegressionTest, ExprMethod0DeadPoolHeap) { " )\n" " )\n" " )\n" - " (set! (-> v0-0 name) arg2)\n" - " (set! (-> v0-0 mask) 256)\n" - " (set! (-> v0-0 allocated-length) arg3)\n" - " (set! (-> v0-0 parent) #f)\n" - " (set! (-> v0-0 brother) #f)\n" - " (set! (-> v0-0 child) #f)\n" - " (set! (-> v0-0 self) v0-0)\n" - " (set! (-> v0-0 ppointer) (&-> v0-0 self))\n" + " (set! (-> obj name) arg2)\n" + " (set! (-> obj mask) 256)\n" + " (set! (-> obj allocated-length) arg3)\n" + " (set! (-> obj parent) #f)\n" + " (set! (-> obj brother) #f)\n" + " (set! (-> obj child) #f)\n" + " (set! (-> obj self) obj)\n" + " (set! (-> obj ppointer) (&-> obj self))\n" " (let\n" " ((v1-4 arg3))\n" " (while\n" " (nonzero? v1-4)\n" " (+! v1-4 -1)\n" " (let\n" - " ((a0-4 (-> v0-0 process-list v1-4)))\n" + " ((a0-4 (-> obj process-list v1-4)))\n" " (set! (-> a0-4 process) *null-process*)\n" - " (set! (-> a0-4 next) (-> v0-0 process-list (+ v1-4 1)))\n" + " (set! (-> a0-4 next) (-> obj process-list (+ v1-4 1)))\n" " )\n" " )\n" " )\n" - " (set! (-> v0-0 dead-list next) (-> v0-0 process-list))\n" - " (set! (-> v0-0 alive-list process) #f)\n" - " (set! (-> v0-0 process-list (+ arg3 -1) next) #f)\n" - " (set! (-> v0-0 alive-list prev) (-> v0-0 alive-list))\n" - " (set! (-> v0-0 alive-list next) #f)\n" - " (set! (-> v0-0 alive-list process) #f)\n" - " (set! (-> v0-0 first-gap) (-> v0-0 alive-list))\n" - " (set! (-> v0-0 first-shrink) #f)\n" + " (set! (-> obj dead-list next) (-> obj process-list))\n" + " (set! (-> obj alive-list process) #f)\n" + " (set! (-> obj process-list (+ arg3 -1) next) #f)\n" + " (set! (-> obj alive-list prev) (-> obj alive-list))\n" + " (set! (-> obj alive-list next) #f)\n" + " (set! (-> obj alive-list process) #f)\n" + " (set! (-> obj first-gap) (-> obj alive-list))\n" + " (set! (-> obj first-shrink) #f)\n" " (set!\n" - " (-> v0-0 heap base)\n" - " (logand -16 (the-as int (&+ (+ (the-as int v0-0) 115) (* 12 arg3))))\n" + " (-> obj heap base)\n" + " (logand -16 (+ (+ (the-as int obj) 115) (* 12 arg3)))\n" " )\n" - " (set! (-> v0-0 heap current) (-> v0-0 heap base))\n" - " (set! (-> v0-0 heap top) (&+ (-> v0-0 heap base) arg4))\n" - " (set! (-> v0-0 heap top-base) (-> v0-0 heap top))\n" - " v0-0\n" + " (set! (-> obj heap current) (-> obj heap base))\n" + " (set! (-> obj heap top) (&+ (-> obj heap base) arg4))\n" + " (set! (-> obj heap top-base) (-> obj heap top))\n" + " obj\n" " )"; - test_with_expr(func, type, expected, false, "dead-pool-heap", {}, - parse_hint_json("[\t\t[60, [\"v0\", \"int\"]],\n" - "\t\t[61, [\"a0\", \"pointer\"], [\"v0\", \"dead-pool-heap\"]]]")); + test_with_expr( + func, type, expected, false, "dead-pool-heap", {}, + parse_cast_json("[\t\t[60, \"v0\", \"int\"],\n" + "\t\t[61, \"a0\", \"pointer\"], [61, \"v0\", \"dead-pool-heap\"]]"), + "{\"vars\":{\"v0-0\":[\"obj\", \"dead-pool-heap\"]}}"); } TEST_F(FormRegressionTest, ExprMethod22DeadPoolHeap) { @@ -1399,9 +1405,9 @@ TEST_F(FormRegressionTest, ExprMethod21DeadPoolHeap) { " )\n" " )"; test_with_expr(func, type, expected, false, "", {}, - parse_hint_json("[\t\t[5, [\"v1\", \"pointer\"]],\n" - "\t\t[13, [\"a0\", \"pointer\"]],\n" - "\t\t[25, [\"v1\", \"pointer\"]]]")); + parse_cast_json("[\t\t[5, \"v1\", \"pointer\"],\n" + "\t\t[13, \"a0\", \"pointer\"],\n" + "\t\t[25, \"v1\", \"pointer\"]]")); } TEST_F(FormRegressionTest, ExprMethod3DeadPoolHeap) { @@ -1595,7 +1601,7 @@ TEST_F(FormRegressionTest, ExprMethod5DeadPoolHeap) { std::string type = "(function dead-pool-heap int)"; std::string expected = "(+ (- -4 (the-as int arg0)) (-> arg0 heap top))"; test_with_expr(func, type, expected, false, "", {}, - parse_hint_json("[[3, [\"v1\", \"int\"], [\"a0\", \"int\"]]]")); + parse_cast_json("[[3, \"v1\", \"int\"], [3, \"a0\", \"int\"]]")); } TEST_F(FormRegressionTest, ExprMethod19DeadPoolHeap) { @@ -1979,7 +1985,7 @@ TEST_F(FormRegressionTest, ExprMethod14DeadPoolHeap) { std::string type = "(function dead-pool-heap type int process)"; std::string expected = "(let\n" - " ((s4-0 (-> arg0 dead-list next)) (s3-0 #f))\n" + " ((s4-0 (-> arg0 dead-list next)) (s3-0 (the-as process #f)))\n" " (let\n" " ((s1-0 (find-gap-by-size arg0 (+ (-> process size) (the-as uint arg2)))))\n" " (cond\n" diff --git a/test/goalc/source_templates/variables/stack-structure-align.gc b/test/goalc/source_templates/variables/stack-structure-align.gc index afeffb88e4..c790fdf796 100644 --- a/test/goalc/source_templates/variables/stack-structure-align.gc +++ b/test/goalc/source_templates/variables/stack-structure-align.gc @@ -3,7 +3,7 @@ (count uint32) (pad uint8 12)) (:methods - (new ((allocation symbol) (type-to-make type) (init-count int)) _type_ 0) + (new (symbol type int) _type_ 0) ) ) diff --git a/test/offline/offline_test_main.cpp b/test/offline/offline_test_main.cpp index a63a325e01..2c6b07a2ce 100644 --- a/test/offline/offline_test_main.cpp +++ b/test/offline/offline_test_main.cpp @@ -9,15 +9,14 @@ namespace { // the object files to test -const std::unordered_set g_object_files_to_decompile = { - "gcommon", - "gstring-h", -}; +const std::unordered_set g_object_files_to_decompile = {"gcommon", "gstring-h", + "gkernel-h", "gkernel"}; // the object files to check against a reference in test/decompiler/reference const std::vector g_object_files_to_check_against_reference = { "gcommon", // NOTE: this file needs work, but adding it for now just to test the framework. - "gstring-h"}; + "gstring-h", "gkernel-h", + /*"gkernel"*/}; // the functions we expect the decompiler to skip const std::unordered_set expected_skip_in_decompiler = { @@ -34,6 +33,7 @@ const std::unordered_set expected_skip_in_decompiler = { "return-from-thread-dead", // kernel -> user "return-from-thread", // kernel -> user "return-from-exception", // ps2 exception -> ps2 user + "run-function-in-process", // temp while stack vars aren't supported. // pskernel "kernel-check-hardwired-addresses", // ps2 ee kernel debug hook "kernel-read-function", // ps2 ee kernel debug hook @@ -47,25 +47,50 @@ const std::unordered_set skip_in_compiling = { ////////////////////// // these functions are not implemented by the compiler in OpenGOAL, but are in GOAL. - "abs", "ash", "min", "max", "lognor", + "abs", + "ash", + "min", + "max", + "lognor", // weird PS2 specific debug registers: "breakpoint-range-set!", // these require 128-bit integers. We want these eventually, but disabling for now to focus // on more important issues. - "(method 3 vec4s)", "(method 2 vec4s)", "qmem-copy<-!", "qmem-copy->!", "(method 2 array)", + "(method 3 vec4s)", + "(method 2 vec4s)", + "qmem-copy<-!", + "qmem-copy->!", + "(method 2 array)", "(method 3 array)", // does weird stuff with the type system. - "print", "printl", "inspect", + "print", + "printl", + "inspect", // inline assembly - "valid?"}; + "valid?", + + ////////////////////// + // GKERNEL-H + ////////////////////// + // bitfields, possibly inline assembly + "(method 2 handle)", +}; // The decompiler does not attempt to insert forward definitions, as this would be part of an // unimplemented full-program type analysis pass. For now, we manually specify all functions // that should have a forward definition here. const std::string g_forward_type_defs = + // used out of order "(define-extern name= (function basic basic symbol))\n" - // todo - check if recursive? - "(define-extern fact (function int int))"; + // recursive + "(define-extern fact (function int int))\n" + // gkernel-h + "(declare-type process basic)\n" + "(declare-type stack-frame basic)\n" + "(declare-type state basic)\n" + "(declare-type cpu-thread basic)\n" + "(declare-type dead-pool basic)\n" + "(declare-type event-message-block structure)\n"; // default location for the data. It can be changed with a command line argument. std::string g_iso_data_path = ""; @@ -252,7 +277,7 @@ TEST_F(OfflineDecompilation, TypeAnalysis) { int failed_count = 0; db->for_each_function([&](decompiler::Function& func, int, decompiler::ObjectFileData&) { if (!func.suspected_asm) { - if (!func.ir2.env.has_type_analysis()) { + if (!func.ir2.env.has_type_analysis() || !func.ir2.types_succeeded) { lg::error("Function {} failed types", func.guessed_name.to_string()); failed_count++; } diff --git a/test/test_common_util.cpp b/test/test_common_util.cpp index 4c629e4ca5..c8e23d12b0 100644 --- a/test/test_common_util.cpp +++ b/test/test_common_util.cpp @@ -2,6 +2,8 @@ #include "common/util/Trie.h" #include "gtest/gtest.h" #include "test/all_jak1_symbols.h" +#include "common/util/json_util.h" +#include "common/util/Range.h" #include #include @@ -38,4 +40,41 @@ TEST(CommonUtil, Trie) { EXPECT_FALSE(test.lookup("path1") == nullptr); EXPECT_TRUE(test.lookup("path-") == nullptr); EXPECT_FALSE(test.lookup("path1-k") == nullptr); +} + +TEST(CommonUtil, StripComments) { + std::string test_input = + R"( +test "asdf /* y */ /////a\"bcd" +///////// commented out! +// /* also commented out + +/* this is a block comment "with an unterminated string. +*/ and its done +)"; + + std::string test_expected = + R"( +test "asdf /* y */ /////a\"bcd" + + + + and its done +)"; + + EXPECT_EQ(strip_cpp_style_comments(test_input), test_expected); +} + +TEST(CommonUtil, RangeIterator) { + std::vector result = {}, expected_result = {4, 5, 6, 7}; + + for (auto x : Range(4, 8)) { + result.push_back(x); + } + + EXPECT_EQ(result, expected_result); + EXPECT_TRUE(Range().empty()); + EXPECT_FALSE(Range(3, 4).empty()); + EXPECT_EQ(1, Range(3, 4).size()); + EXPECT_EQ(4, Range(4, 8).size()); } \ No newline at end of file