From c3d5817425bfb9ad51016e11b25c0eeefb2030e0 Mon Sep 17 00:00:00 2001 From: water111 Date: Fri, 7 Aug 2026 20:04:55 -0400 Subject: [PATCH] decompiler fixes --- common/demacro/demacro.cpp | 132 +++-- common/demacro/demacro.h | 2 + common/formatter/formatter.cpp | 26 +- common/formatter/rules/rule_config.cpp | 35 +- common/formatter/rules/rule_config.h | 2 + common/type_system/TypeFieldLookup.cpp | 65 ++- common/type_system/TypeSystem.cpp | 13 +- decompiler/IR2/AtomicOp.cpp | 4 + decompiler/IR2/AtomicOpTypeAnalysis.cpp | 25 + decompiler/IR2/Env.h | 7 + decompiler/IR2/ExpressionHelpers.cpp | 22 +- decompiler/IR2/ExpressionHelpers.h | 3 + decompiler/IR2/Form.cpp | 6 + decompiler/IR2/Form.h | 2 +- decompiler/IR2/FormExpressionAnalysis.cpp | 470 ++++++++++++++++-- decompiler/ObjectFile/ObjectFileDB.h | 10 +- decompiler/ObjectFile/ObjectFileDB_IR2.cpp | 8 +- decompiler/analysis/final_output.cpp | 46 +- decompiler/analysis/insert_lets.cpp | 340 +++++++++++-- decompiler/config.cpp | 7 + decompiler/config.h | 1 + decompiler/config/jak1/all-types.gc | 155 +++--- decompiler/config/jak1/demacro.jsonc | 124 +++++ decompiler/config/jak1/jak1_config.jsonc | 1 + .../jak1/ntsc_v1/scratchpad_types.jsonc | 3 + .../config/jak1/ntsc_v1/type_casts.jsonc | 31 +- .../config/jak1/ntsc_v1/var_names.jsonc | 50 +- 27 files changed, 1284 insertions(+), 306 deletions(-) create mode 100644 decompiler/config/jak1/ntsc_v1/scratchpad_types.jsonc diff --git a/common/demacro/demacro.cpp b/common/demacro/demacro.cpp index 6eb478c155..09aba82022 100644 --- a/common/demacro/demacro.cpp +++ b/common/demacro/demacro.cpp @@ -12,9 +12,10 @@ #include "common/util/json_util.h" #include "common/util/string_util.h" +#include "tree_sitter/api.h" + #include "fmt/format.h" #include "third-party/json.hpp" -#include "tree_sitter/api.h" extern "C" { extern const TSLanguage* tree_sitter_opengoal(); @@ -50,6 +51,7 @@ struct CompiledRule { std::string name; std::vector match; std::vector rewrite; + std::unordered_map capture_types; size_t index = 0; }; @@ -74,8 +76,7 @@ bool is_comment_node(const TSNode& node) { bool is_gap_node(const TSNode& node) { const std::string_view type = ts_node_type(node); - return type == "(" || type == ")" || type == "_ws" || type == "ERROR" || - is_comment_node(node); + return type == "(" || type == ")" || type == "_ws" || type == "ERROR" || is_comment_node(node); } std::string node_text(const std::string& source, const TSNode& node) { @@ -152,9 +153,8 @@ std::vector json_string_or_array(const nlohmann::json& value, std::vector result; for (const auto& entry : value) { if (!entry.is_string()) { - throw std::runtime_error( - fmt::format("Demacro rule '{}' field '{}' must contain only strings", rule_name, - field_name)); + throw std::runtime_error(fmt::format( + "Demacro rule '{}' field '{}' must contain only strings", rule_name, field_name)); } result.push_back(entry.get()); } @@ -164,10 +164,9 @@ std::vector json_string_or_array(const nlohmann::json& value, fmt::format("Demacro rule '{}' field '{}' must be a string or array", rule_name, field_name)); } -std::string substitute_row_values( - std::string text, - const std::unordered_map& substitutions, - const std::string& rule_name) { +std::string substitute_row_values(std::string text, + const std::unordered_map& substitutions, + const std::string& rule_name) { for (const auto& [key, value] : substitutions) { const auto placeholder = "{{" + key + "}}"; size_t cursor = 0; @@ -197,6 +196,20 @@ Rule parse_rule(const nlohmann::json& entry, for (auto& form : rule.rewrite) { form = substitute_row_values(std::move(form), substitutions, rule.name); } + if (entry.contains("capture_types")) { + if (!entry.at("capture_types").is_object()) { + throw std::runtime_error( + fmt::format("Demacro rule '{}' field 'capture_types' must be an object", rule.name)); + } + for (const auto& [capture, type] : entry.at("capture_types").items()) { + if (!type.is_string()) { + throw std::runtime_error(fmt::format( + "Demacro rule '{}' capture type for '{}' must be a string", rule.name, capture)); + } + rule.capture_types.emplace( + capture, substitute_row_values(type.get(), substitutions, rule.name)); + } + } return rule; } @@ -270,8 +283,7 @@ std::vector match_children_all(const std::vector& patterns, size_t input_idx, const Captures& captures) { if (pattern_idx == patterns.size()) { - return input_idx == inputs.size() ? std::vector{captures} - : std::vector{}; + return input_idx == inputs.size() ? std::vector{captures} : std::vector{}; } std::string sequence_name; @@ -290,8 +302,7 @@ std::vector match_children_all(const std::vector& patterns, continue; } next.sequence[sequence_name] = std::move(captured); - auto matches = - match_children_all(patterns, pattern_idx + 1, inputs, input_idx + count, next); + auto matches = match_children_all(patterns, pattern_idx + 1, inputs, input_idx + count, next); results.insert(results.end(), std::make_move_iterator(matches.begin()), std::make_move_iterator(matches.end())); } @@ -304,8 +315,7 @@ std::vector match_children_all(const std::vector& patterns, std::vector results; for (auto& node_match : match_node_all(patterns.at(pattern_idx), inputs.at(input_idx), captures)) { - auto matches = - match_children_all(patterns, pattern_idx + 1, inputs, input_idx + 1, node_match); + auto matches = match_children_all(patterns, pattern_idx + 1, inputs, input_idx + 1, node_match); results.insert(results.end(), std::make_move_iterator(matches.begin()), std::make_move_iterator(matches.end())); } @@ -320,7 +330,7 @@ std::vector match_node_all(const Node& pattern, const auto previous = captures.single.find(capture_name); if (previous != captures.single.end()) { return structurally_equal(*previous->second, input) ? std::vector{captures} - : std::vector{}; + : std::vector{}; } auto result = captures; result.single[capture_name] = &input; @@ -331,8 +341,7 @@ std::vector match_node_all(const Node& pattern, return {}; } if (!pattern.is_list) { - return pattern.atom == input.atom ? std::vector{captures} - : std::vector{}; + return pattern.atom == input.atom ? std::vector{captures} : std::vector{}; } return match_children_all(pattern.children, 0, input.children, 0, captures); } @@ -373,13 +382,59 @@ std::string original_text(const Node& node, const std::string& source) { return source.substr(node.start, node.end - node.start); } +void collect_redundant_capture_casts( + const Node& node, + const Captures& captures, + const std::unordered_map& capture_types, + std::vector>* casts) { + if (!node.is_list) { + return; + } + if (node.children.size() == 3 && !node.children.at(0).is_list && + node.children.at(0).atom == "the-as" && !node.children.at(1).is_list) { + for (const auto& [capture_name, type] : capture_types) { + const auto capture = captures.single.find(capture_name); + if (capture != captures.single.end() && node.children.at(1).atom == type && + structurally_equal(node.children.at(2), *capture->second)) { + casts->emplace_back(&node, &node.children.at(2)); + return; + } + } + } + for (const auto& child : node.children) { + collect_redundant_capture_casts(child, captures, capture_types, casts); + } +} + +std::string original_text_without_redundant_capture_casts( + const Node& node, + const std::string& source, + const Captures& captures, + const std::unordered_map& capture_types) { + if (capture_types.empty()) { + return original_text(node, source); + } + std::vector> casts; + collect_redundant_capture_casts(node, captures, capture_types, &casts); + auto result = original_text(node, source); + for (auto cast = casts.rbegin(); cast != casts.rend(); ++cast) { + const auto start = cast->first->start - node.start; + const auto size = cast->first->end - cast->first->start; + result.replace(start, size, original_text(*cast->second, source)); + } + return result; +} + std::string render_template(const Node& node, const Captures& captures, - const std::string& source); + const std::string& source, + const std::unordered_map& capture_types); -std::string render_template_children(const std::vector& nodes, - const Captures& captures, - const std::string& source) { +std::string render_template_children( + const std::vector& nodes, + const Captures& captures, + const std::string& source, + const std::unordered_map& capture_types) { std::string result; for (const auto& child : nodes) { std::string sequence_name; @@ -393,13 +448,14 @@ std::string render_template_children(const std::vector& nodes, if (!result.empty()) { result += " "; } - result += original_text(*captured, source); + result += original_text_without_redundant_capture_casts(*captured, source, captures, + capture_types); } } else { if (!result.empty()) { result += " "; } - result += render_template(child, captures, source); + result += render_template(child, captures, source, capture_types); } } return result; @@ -407,19 +463,21 @@ std::string render_template_children(const std::vector& nodes, std::string render_template(const Node& node, const Captures& captures, - const std::string& source) { + const std::string& source, + const std::unordered_map& capture_types) { std::string capture_name; if (is_capture(node, &capture_name)) { const auto found = captures.single.find(capture_name); if (found == captures.single.end()) { throw std::runtime_error(fmt::format("Unknown demacro capture '${}'", capture_name)); } - return original_text(*found->second, source); + return original_text_without_redundant_capture_casts(*found->second, source, captures, + capture_types); } if (!node.is_list) { return node.atom; } - return "(" + render_template_children(node.children, captures, source) + ")"; + return "(" + render_template_children(node.children, captures, source, capture_types) + ")"; } std::string indentation_at(const std::string& source, uint32_t offset) { @@ -455,7 +513,7 @@ std::string render_rewrite(const CompiledRule& rule, replacement += "\n" + indentation; } replacement += indent_after_newlines( - render_template(form, candidate.captures, source), indentation); + render_template(form, candidate.captures, source, rule.capture_types), indentation); } // Comments are not part of semantic matching. Retain any comment which would otherwise be @@ -542,7 +600,7 @@ std::vector compile_rules(const RuleSet& rules) { result.push_back({rule.name, parse_form_sequence(rule.match, "match for demacro rule " + rule.name), parse_form_sequence(rule.rewrite, "rewrite for demacro rule " + rule.name), - i}); + rule.capture_types, i}); } return result; } @@ -571,9 +629,9 @@ RewriteResult rewrite_compiled(const std::string& source, std::vector edits; edits.reserve(selected.size()); for (const auto& candidate : selected) { - edits.push_back({candidate.start, candidate.end, candidate.rule_index, - render_rewrite(compiled.at(candidate.rule_index), candidate, parsed, - result.source)}); + edits.push_back( + {candidate.start, candidate.end, candidate.rule_index, + render_rewrite(compiled.at(candidate.rule_index), candidate, parsed, result.source)}); result.stats.at(candidate.rule_index).rewrites++; } for (auto edit = edits.rbegin(); edit != edits.rend(); ++edit) { @@ -640,8 +698,8 @@ RuleSet parse_rules(const std::string& contents, const std::string& source_name) std::unordered_map values; for (const auto& [key, value] : row.items()) { if (!value.is_string()) { - throw std::runtime_error(fmt::format( - "{} table '{}' row values must be strings", source_name, table_name)); + throw std::runtime_error( + fmt::format("{} table '{}' row values must be strings", source_name, table_name)); } values[key] = value.get(); } @@ -663,8 +721,8 @@ RuleSet parse_rules(const std::string& contents, const std::string& source_name) entry.at("name").get(), table_name)); } for (size_t i = 0; i < table->second.size(); ++i) { - result.rules.push_back(parse_rule(entry, table->second.at(i), - fmt::format("[{}:{}]", table_name, i))); + result.rules.push_back( + parse_rule(entry, table->second.at(i), fmt::format("[{}:{}]", table_name, i))); } } else { result.rules.push_back(parse_rule(entry, {}, "")); diff --git a/common/demacro/demacro.h b/common/demacro/demacro.h index b6806cdd8a..484ad86dd1 100644 --- a/common/demacro/demacro.h +++ b/common/demacro/demacro.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include "common/util/FileUtil.h" @@ -11,6 +12,7 @@ struct Rule { std::string name; std::vector match; std::vector rewrite; + std::unordered_map capture_types; }; struct RuleSet { diff --git a/common/formatter/formatter.cpp b/common/formatter/formatter.cpp index 8e10b92e59..208dca1320 100644 --- a/common/formatter/formatter.cpp +++ b/common/formatter/formatter.cpp @@ -230,6 +230,19 @@ bool form_contains_node_that_prevents_inlining(const FormatterTreeNode& curr_nod return false; } +bool body_exceeds_inline_width_limit(const FormatterTreeNode& curr_node) { + const auto& config = curr_node.formatting_config; + if (!config.inline_body_width_limit) { + return false; + } + for (int i = config.inline_body_start_index; i < (int)curr_node.refs.size(); ++i) { + if (get_total_form_inlined_width(curr_node.refs.at(i)) > *config.inline_body_width_limit) { + return true; + } + } + return false; +} + bool can_node_be_inlined(const FormatterTreeNode& curr_node, int cursor_pos) { using namespace formatter_rules; if (curr_node.formatting_config.force_inline) { @@ -248,6 +261,9 @@ bool can_node_be_inlined(const FormatterTreeNode& curr_node, int cursor_pos) { form_contains_node_that_prevents_inlining(curr_node)) { return false; } + if (body_exceeds_inline_width_limit(curr_node)) { + return false; + } // nor can we inline something that contains a comment in the middle if (form_contains_comment(curr_node)) { return false; @@ -381,13 +397,17 @@ std::vector apply_formatting(const FormatterTreeNode& curr_node, // TODO there is a hack here so that multi-line forms that are consolidated still line up properly // i have to make consolidate a more first-class feature of the config // TODO - hacky, but prevents a bad situation, clean up - if (curr_node.formatting_config.inline_until_index(form_lines) && - !str_util::contains(form_lines.at(0), ";")) { + auto inline_until_index = curr_node.formatting_config.inline_until_index(form_lines); + if (inline_until_index && body_exceeds_inline_width_limit(curr_node)) { + inline_until_index = + std::min(*inline_until_index, curr_node.formatting_config.inline_body_start_index); + } + if (inline_until_index && !str_util::contains(form_lines.at(0), ";")) { std::vector new_form_lines = {}; const auto original_form_head_width = str_util::split(form_lines.at(0), '\n').at(0).length(); bool consolidating_lines = true; for (int i = 0; i < (int)form_lines.size(); i++) { - if (i < curr_node.formatting_config.inline_until_index(form_lines)) { + if (i < *inline_until_index) { if (new_form_lines.empty()) { new_form_lines.push_back(form_lines.at(i)); } else { diff --git a/common/formatter/rules/rule_config.cpp b/common/formatter/rules/rule_config.cpp index e5b4c23f21..ecd95d8987 100644 --- a/common/formatter/rules/rule_config.cpp +++ b/common/formatter/rules/rule_config.cpp @@ -64,6 +64,16 @@ static FormFormattingConfig new_inlineable_flow_rule(int start_index, .has_constant_pairs = has_constant_pairs}; } +static FormFormattingConfig new_inlineable_body_rule(int body_start_index, + int body_width_limit, + int indentation_width) { + auto config = new_inlineable_flow_rule(body_start_index); + config.inline_body_width_limit = body_width_limit; + config.inline_body_start_index = body_start_index; + config.indentation_width = indentation_width; + return config; +} + static FormFormattingConfig new_defstate_rule(int start_index, bool has_constant_pairs = false) { FormFormattingConfig cfg = { .config_set = true, @@ -201,11 +211,12 @@ static FormFormattingConfig new_defproc_rule( return cfg; } -static FormFormattingConfig new_binding_rule(int form_head_width) { +static FormFormattingConfig new_binding_rule(int form_head_width, bool force_body_newline = false) { FormFormattingConfig cfg; cfg.config_set = true; cfg.hang_forms = false; cfg.combine_first_two_lines = true; + cfg.prevent_inlining = force_body_newline; auto binding_list_config = std::make_shared(); binding_list_config->config_set = true; binding_list_config->hang_forms = false; @@ -253,7 +264,8 @@ static FormFormattingConfig new_inline_binding_rule(int form_head_width) { return cfg; } -static FormFormattingConfig new_pair_rule(bool combine_first_two_expr) { +static FormFormattingConfig new_pair_rule(bool combine_first_two_expr, + std::optional inline_body_width_limit = {}) { FormFormattingConfig cfg; cfg.config_set = true; cfg.hang_forms = false; @@ -263,6 +275,7 @@ static FormFormattingConfig new_pair_rule(bool combine_first_two_expr) { pair_config->config_set = true; pair_config->hang_forms = false; pair_config->indentation_width = 1; + pair_config->inline_body_width_limit = inline_body_width_limit; cfg.default_index_config = pair_config; return cfg; } @@ -272,10 +285,10 @@ static FormFormattingConfig new_top_level_inline_form(bool elide_new_line) { } const std::unordered_map opengoal_form_config = { - {"case", new_pair_rule(true)}, - {"case-str", new_pair_rule(true)}, - {"cond", new_pair_rule(false)}, - {"#cond", new_pair_rule(false)}, + {"case", new_pair_rule(true, 15)}, + {"case-str", new_pair_rule(true, 15)}, + {"cond", new_pair_rule(false, 15)}, + {"#cond", new_pair_rule(false, 15)}, {"in-package", new_top_level_inline_form(true)}, {"bundles", new_top_level_inline_form(true)}, {"require", new_top_level_inline_form(true)}, @@ -294,7 +307,7 @@ const std::unordered_map opengoal_form_config {"defun-debug-recursive", new_flow_rule(4)}, {"defun-debug", new_flow_rule(3)}, {"defbehavior", new_flow_rule(4)}, - {"if", new_inlineable_flow_rule(2)}, + {"if", new_inlineable_body_rule(2, 15, 4)}, {"aif", new_inlineable_flow_rule(2)}, {"#if", new_inlineable_flow_rule(2)}, {"define", new_permissive_flow_rule()}, @@ -318,10 +331,12 @@ const std::unordered_map opengoal_form_config {"dotimes", new_flow_rule(2)}, {"dolist", new_flow_rule(2)}, {"process-spawn-function", new_flow_rule(2)}, - {"let", new_binding_rule(4)}, + {"iterate-engine-connections", new_flow_rule(2)}, + {"let", new_binding_rule(4, true)}, {"protect", new_binding_rule(4)}, - {"let*", new_binding_rule(5)}, - {"rlet", new_binding_rule(5)}, + {"let*", new_binding_rule(5, true)}, + {"slet", new_flow_rule(2)}, + {"rlet", new_binding_rule(5, true)}, {"mlet", new_binding_rule(5)}, {"when", new_flow_rule(2)}, {"awhen", new_flow_rule(2)}, diff --git a/common/formatter/rules/rule_config.h b/common/formatter/rules/rule_config.h index 8cb0a0c776..5130b1399a 100644 --- a/common/formatter/rules/rule_config.h +++ b/common/formatter/rules/rule_config.h @@ -27,6 +27,8 @@ struct FormFormattingConfig { inline_until_index = [](std::vector /*curr_lines*/) { return std::nullopt; }; bool has_constant_pairs = false; bool prevent_inlining = false; // TODO - duplicate of below + std::optional inline_body_width_limit; + int inline_body_start_index = 1; std::function should_prevent_inlining = [](FormFormattingConfig config, int /*num_refs*/) { return config.prevent_inlining; }; int parent_mutable_extra_indent = 0; diff --git a/common/type_system/TypeFieldLookup.cpp b/common/type_system/TypeFieldLookup.cpp index 432e00698f..50e0ef6557 100644 --- a/common/type_system/TypeFieldLookup.cpp +++ b/common/type_system/TypeFieldLookup.cpp @@ -427,18 +427,23 @@ void try_reverse_lookup_other(const FieldReverseLookupInput& input, node.prev = parent; node.token = token; output->results.emplace_back(false, field_deref.type, node.to_vector()); - continue; // try more! - } else { - FieldReverseLookupInput next_input; - next_input.deref = input.deref; - next_input.offset = offset_into_field - expected_offset_into_field; - next_input.stride = input.stride; - next_input.base_type = field_deref.type; - ReverseLookupNode node; - node.prev = parent; - node.token = token; - try_reverse_lookup(next_input, ts, &node, output, max_count); + if ((int)output->results.size() >= max_count) { + return; + } } + + // An exact match on an inline field can also be a match for something nested at offset + // zero. Keep searching so multi-lookup can offer both the field itself and paths such as + // an inline array's zeroth element. + FieldReverseLookupInput next_input; + next_input.deref = input.deref; + next_input.offset = offset_into_field - expected_offset_into_field; + next_input.stride = input.stride; + next_input.base_type = field_deref.type; + ReverseLookupNode node; + node.prev = parent; + node.token = token; + try_reverse_lookup(next_input, ts, &node, output, max_count); } } } @@ -481,7 +486,10 @@ void try_reverse_lookup(const FieldReverseLookupInput& input, */ FieldReverseLookupOutput TypeSystem::reverse_field_lookup( const FieldReverseLookupInput& input) const { - // just use the multi-lookup set to 1 and grab the first result. + // Use multi-lookup to collect all interpretations. Unlike multi-lookup callers, this legacy + // interface has no expected result type with which to choose among zero-offset views. Preserve + // its original behavior by preferring an exact enclosing access over any candidate that merely + // extends it. Multi-lookup still exposes both paths to type-aware callers. auto multi_result = reverse_field_multi_lookup(input, 100); /* @@ -500,7 +508,38 @@ FieldReverseLookupOutput TypeSystem::reverse_field_lookup( FieldReverseLookupOutput result; if (multi_result.success) { - result = multi_result.results.at(0); + auto selected = multi_result.results.begin(); + for (auto candidate = multi_result.results.begin(); candidate != multi_result.results.end(); + ++candidate) { + bool extends_exact_access = false; + for (const auto& possible_prefix : multi_result.results) { + if (possible_prefix.tokens.size() >= candidate->tokens.size()) { + continue; + } + + bool is_prefix = true; + for (size_t i = 0; i < possible_prefix.tokens.size(); ++i) { + const auto& prefix_token = possible_prefix.tokens.at(i); + const auto& candidate_token = candidate->tokens.at(i); + if (prefix_token.kind != candidate_token.kind || + prefix_token.name != candidate_token.name || + prefix_token.idx != candidate_token.idx) { + is_prefix = false; + break; + } + } + if (is_prefix) { + extends_exact_access = true; + break; + } + } + + if (!extends_exact_access) { + selected = candidate; + break; + } + } + result = *selected; result.success = true; } else { result.success = false; diff --git a/common/type_system/TypeSystem.cpp b/common/type_system/TypeSystem.cpp index f6b5f3f673..ac39912b1e 100644 --- a/common/type_system/TypeSystem.cpp +++ b/common/type_system/TypeSystem.cpp @@ -1155,9 +1155,9 @@ void TypeSystem::add_builtin_types(GameVersion version) { // declared before any GOAL source is loaded, so its argument enum must also be a built-in type. if (version == GameVersion::Jak1) { const std::unordered_map mem_usage_flag_entries = { - {"prototype-data", 0}, {"instance-colors", 1}, {"tie-geometry-1", 2}, - {"tie-geometry-2", 3}, {"tie-geometry-3", 4}, {"include-dead-pools", 5}, - {"resource-entity", 6}, {"resource-ambient", 7}, {"resource-camera", 8}, + {"prototype-data", 0}, {"instance-colors", 1}, {"tie-geometry-1", 2}, + {"tie-geometry-2", 3}, {"tie-geometry-3", 4}, {"include-dead-pools", 5}, + {"resource-entity", 6}, {"resource-ambient", 7}, {"resource-camera", 8}, {"resource-joint-geo", 9}}; auto* parent = get_type_of_type("uint32"); auto flags = @@ -1184,10 +1184,9 @@ void TypeSystem::add_builtin_types(GameVersion version) { declare_method(obj_type, "relocate", {}, false, make_function_typespec({"_type_", "int"}, "_type_"), false); declare_method(obj_type, "mem-usage", {}, false, - make_function_typespec( - {"_type_", "memory-usage-block", - version == GameVersion::Jak1 ? "mem-usage-flags" : "int"}, - "_type_"), + make_function_typespec({"_type_", "memory-usage-block", + version == GameVersion::Jak1 ? "mem-usage-flags" : "int"}, + "_type_"), false); // STRUCTURE diff --git a/decompiler/IR2/AtomicOp.cpp b/decompiler/IR2/AtomicOp.cpp index a43bb2fa07..8ed1b8c1ba 100644 --- a/decompiler/IR2/AtomicOp.cpp +++ b/decompiler/IR2/AtomicOp.cpp @@ -184,6 +184,10 @@ goos::Object SimpleAtom::to_form(const std::vector& labels, con case Kind::VARIABLE: return m_variable.to_form(env); case Kind::INTEGER_CONSTANT: { + if (m_int == 0x70000000 && env.scratchpad_type()) { + env.note_scratchpad_access(); + return pretty_print::to_symbol("spad"); + } if (m_display_int_as_float) { float f; s32 as_s32 = m_int; diff --git a/decompiler/IR2/AtomicOpTypeAnalysis.cpp b/decompiler/IR2/AtomicOpTypeAnalysis.cpp index 64b5c9a3b6..110e66f2ec 100644 --- a/decompiler/IR2/AtomicOpTypeAnalysis.cpp +++ b/decompiler/IR2/AtomicOpTypeAnalysis.cpp @@ -71,6 +71,9 @@ TP_Type SimpleAtom::get_type(const TypeState& input, case Kind::VARIABLE: return input.get(var().reg()); case Kind::INTEGER_CONSTANT: + if (m_int == 0x70000000 && env.scratchpad_type()) { + return TP_Type::make_from_ts(*env.scratchpad_type()); + } return TP_Type::make_from_integer(m_int); case Kind::SYMBOL_PTR: if (m_string == "#f") { @@ -993,6 +996,28 @@ TP_Type LoadVarOp::get_src_type(const TypeState& input, IR2_RegOffset ro; if (get_as_reg_offset(m_src, &ro)) { auto& input_type = input.get(ro.reg); + + // A static value label is often materialized in a register before it is loaded. Preserve the + // label's analyzed value type just as we do when the load contains the static address directly. + // In particular, GOAL returns float constants through GPRs with a signed word load; treating + // that as a generic pointer load loses the float type even though label analysis and form + // reconstruction both identify the value as a float. + if (input_type.kind == TP_Type::Kind::LABEL_ADDR && ro.offset == 0) { + const auto& hint = env.file->label_db->lookup(input_type.label_id()); + if (!hint.known) { + throw std::runtime_error( + fmt::format("Label {} was unknown in AtomicOpTypeAnalysis (load).", hint.name)); + } + if (!hint.is_value) { + throw std::runtime_error( + fmt::format("Label {} was loaded as a value but wasn't marked as one.", hint.name)); + } + if (m_size == 4 && (m_kind == Kind::SIGNED || m_kind == Kind::FLOAT) && + hint.result_type == TypeSpec("float")) { + return TP_Type::make_from_ts("float"); + } + } + if ((input_type.kind == TP_Type::Kind::TYPE_OF_TYPE_OR_CHILD || input_type.kind == TP_Type::Kind::TYPE_OF_TYPE_NO_VIRTUAL) && ro.offset >= 16 && (ro.offset & 3) == 0 && m_size == 4 && m_kind == Kind::UNSIGNED) { diff --git a/decompiler/IR2/Env.h b/decompiler/IR2/Env.h index d8a6230fab..40c0b08d3f 100644 --- a/decompiler/IR2/Env.h +++ b/decompiler/IR2/Env.h @@ -185,6 +185,11 @@ class Env { const std::unordered_map& stack_casts() const { return m_stack_typecasts; } + void set_scratchpad_type(const TypeSpec& type) { m_scratchpad_type = type; } + const std::optional& scratchpad_type() const { return m_scratchpad_type; } + void note_scratchpad_access() const { m_uses_scratchpad = true; } + bool uses_scratchpad() const { return m_uses_scratchpad; } + void set_art_group(const std::string& art_group) { m_art_group = art_group; } const std::string& art_group() const { return m_art_group; } std::optional get_art_elt_name(int idx) const; @@ -282,6 +287,8 @@ class Env { std::unordered_map> m_typecasts; std::unordered_map m_stack_typecasts; + std::optional m_scratchpad_type; + mutable bool m_uses_scratchpad = false; std::vector m_stack_structures; std::unordered_map m_var_remap; std::unordered_map m_var_retype; diff --git a/decompiler/IR2/ExpressionHelpers.cpp b/decompiler/IR2/ExpressionHelpers.cpp index 1961472582..0f567316fa 100644 --- a/decompiler/IR2/ExpressionHelpers.cpp +++ b/decompiler/IR2/ExpressionHelpers.cpp @@ -117,17 +117,27 @@ FormElement* handle_get_property_data_or_structure(const std::vector& for // get the name of the the thing we're looking up. This can be anything. Form* property_name = forms.at(1); - // get the mode. It must be interp. + // Exact data/structure lookups use sibling macros with a different default time. auto mode_atom = form_as_atom(forms.at(2)); - if (!mode_atom || !mode_atom->is_sym_ptr("interp")) { + if (!mode_atom) { lg::error("fail data: bad mode {}", forms.at(2)->to_string(env)); return nullptr; } + const bool exact = mode_atom->is_sym_ptr("exact"); + if (!mode_atom->is_sym_ptr("interp") && !exact) { + lg::error("fail data: bad mode {}", forms.at(2)->to_string(env)); + return nullptr; + } + if (exact && kind == ResLumpMacroElement::Kind::DATA) { + kind = ResLumpMacroElement::Kind::DATA_EXACT; + } else if (exact && kind == ResLumpMacroElement::Kind::STRUCT) { + kind = ResLumpMacroElement::Kind::STRUCT_EXACT; + } // get the time. It can be anything, but there's a default. auto time = forms.at(3); auto lookup_time = try_get_const_float(time); - if (lookup_time && *lookup_time == DEFAULT_RES_TIME) { + if (lookup_time && *lookup_time == (exact ? 0.f : DEFAULT_RES_TIME)) { time = nullptr; } @@ -135,7 +145,8 @@ FormElement* handle_get_property_data_or_structure(const std::vector& for Form* default_value = forms.at(4); if (default_value->to_string(env) == expcted_default) { default_value = nullptr; - } else if (kind != ResLumpMacroElement::Kind::STRUCT || + } else if ((kind != ResLumpMacroElement::Kind::STRUCT && + kind != ResLumpMacroElement::Kind::STRUCT_EXACT) || env.version != GameVersion::Jak1) { // Only Jak 1's res-lump-struct macro currently exposes a custom default. return nullptr; @@ -154,8 +165,7 @@ FormElement* handle_get_property_data_or_structure(const std::vector& for return nullptr; } - return pool.alloc_element(kind, lump_object, property_name, - default_value, + return pool.alloc_element(kind, lump_object, property_name, default_value, tag_pointer, time, default_type); } } // namespace diff --git a/decompiler/IR2/ExpressionHelpers.h b/decompiler/IR2/ExpressionHelpers.h index 8ce138a689..8db6b08c68 100644 --- a/decompiler/IR2/ExpressionHelpers.h +++ b/decompiler/IR2/ExpressionHelpers.h @@ -29,4 +29,7 @@ FormElement* last_two_in_and_to_handle_get_proc(Form* first, FormPool& pool, FormStack& stack, bool part_of_longer_sc); + +FormElement* try_to_rewrite_vector_copy(Form* dst, Form* src, FormPool& pool, const Env& env); + } // namespace decompiler diff --git a/decompiler/IR2/Form.cpp b/decompiler/IR2/Form.cpp index d14d23a130..3168e7bbbf 100644 --- a/decompiler/IR2/Form.cpp +++ b/decompiler/IR2/Form.cpp @@ -3598,9 +3598,15 @@ goos::Object ResLumpMacroElement::to_form_internal(const Env& env) const { case Kind::DATA: forms.push_back(pretty_print::to_symbol("res-lump-data")); break; + case Kind::DATA_EXACT: + forms.push_back(pretty_print::to_symbol("res-lump-data-exact")); + break; case Kind::STRUCT: forms.push_back(pretty_print::to_symbol("res-lump-struct")); break; + case Kind::STRUCT_EXACT: + forms.push_back(pretty_print::to_symbol("res-lump-struct-exact")); + break; case Kind::VALUE: forms.push_back(pretty_print::to_symbol("res-lump-value")); break; diff --git a/decompiler/IR2/Form.h b/decompiler/IR2/Form.h index c362957f17..233940229a 100644 --- a/decompiler/IR2/Form.h +++ b/decompiler/IR2/Form.h @@ -1897,7 +1897,7 @@ class WithDmaBufferAddBucketElement : public FormElement { class ResLumpMacroElement : public FormElement { public: - enum class Kind { DATA, STRUCT, VALUE, INVALID }; + enum class Kind { DATA, DATA_EXACT, STRUCT, STRUCT_EXACT, VALUE, INVALID }; ResLumpMacroElement(Kind kind, Form* lump_object, Form* property_name, diff --git a/decompiler/IR2/FormExpressionAnalysis.cpp b/decompiler/IR2/FormExpressionAnalysis.cpp index c5c5314fe7..423cd243a0 100644 --- a/decompiler/IR2/FormExpressionAnalysis.cpp +++ b/decompiler/IR2/FormExpressionAnalysis.cpp @@ -562,6 +562,109 @@ Form* cast_form(Form* in, return pool.form(new_type, in); } +std::optional try_get_deref_form_type(Form* form, + const Env& env, + std::optional token_count = {}) { + auto* deref = form->try_as_element(); + if (!deref || deref->is_addr_of()) { + return {}; + } + + std::optional current; + auto base_atom = form_as_atom(deref->base()); + if (base_atom && base_atom->is_var()) { + current = env.get_variable_type(base_atom->var(), true); + } else if (auto* cast = deref->base()->try_as_element()) { + current = cast->type(); + } else { + auto base_form = deref->base()->to_form(env); + if (base_form.is_symbol()) { + try { + current = env.dts->lookup_symbol_type(base_form.as_symbol().name_ptr); + } catch (const std::runtime_error&) { + } + } + } + if (!current) { + return {}; + } + + const auto tokens_to_follow = token_count.value_or(deref->tokens().size()); + if (tokens_to_follow > deref->tokens().size()) { + return {}; + } + for (size_t i = 0; i < tokens_to_follow; ++i) { + const auto& token = deref->tokens().at(i); + if (token.kind() == DerefToken::Kind::FIELD_NAME) { + try { + current = env.dts->ts.lookup_field_info(current->base_type(), token.field_name()).type; + } catch (const std::exception&) { + return {}; + } + } else if (token.kind() == DerefToken::Kind::INTEGER_CONSTANT || + token.kind() == DerefToken::Kind::INTEGER_EXPRESSION) { + if (!current->has_single_arg()) { + return {}; + } + const auto element_type = current->get_single_arg(); + current = element_type; + } else { + return {}; + } + } + return current; +} + +bool try_rewrite_inline_array_cursor(Form* form, const TypeSpec& expected_type, const Env& env) { + auto* deref = form->try_as_element(); + if (expected_type.base_type() != "inline-array" || !expected_type.has_single_arg() || !deref || + deref->is_addr_of() || deref->tokens().empty()) { + return false; + } + + const auto token_kind = deref->tokens().back().kind(); + if (token_kind != DerefToken::Kind::INTEGER_CONSTANT && + token_kind != DerefToken::Kind::INTEGER_EXPRESSION) { + return false; + } + + const auto element_type = try_get_deref_form_type(form, env); + const auto container_type = try_get_deref_form_type(form, env, deref->tokens().size() - 1); + if (element_type != expected_type.get_single_arg() || container_type != expected_type) { + return false; + } + + deref->set_addr_of(true); + return true; +} + +bool deref_suffix_matches_lookup(const std::vector& tokens, + size_t suffix_start, + const FieldReverseLookupOutput& lookup) { + if (tokens.size() - suffix_start != lookup.tokens.size()) { + return false; + } + for (size_t i = 0; i < lookup.tokens.size(); ++i) { + const auto& actual = tokens.at(suffix_start + i); + const auto& expected = lookup.tokens.at(i); + switch (expected.kind) { + case FieldReverseLookupOutput::Token::Kind::FIELD: + if (!actual.is_field_name(expected.name)) { + return false; + } + break; + case FieldReverseLookupOutput::Token::Kind::CONSTANT_IDX: + if (!actual.is_int(expected.idx)) { + return false; + } + break; + case FieldReverseLookupOutput::Token::Kind::VAR_IDX: + return false; + } + } + return true; +} + Form* cast_form_from(Form* in, const TypeSpec& old_type, const TypeSpec& new_type, @@ -570,6 +673,42 @@ Form* cast_form_from(Form* in, bool tc_pass = false) { auto& ts = env.dts->ts; + auto form_type = try_get_deref_form_type(in, env); + if (form_type == new_type) { + return in; + } + + // A dereference can end in a zero-offset inline view of the object the consumer expects. If + // reverse lookup confirms that the suffix changes only the type, trim it instead of casting the + // view back to its enclosing object. + auto* deref = in->try_as_element(); + if (deref && !deref->is_addr_of() && form_type) { + for (size_t keep = deref->tokens().size(); keep-- > 0;) { + auto prefix_type = try_get_deref_form_type(in, env, keep); + if (prefix_type != new_type) { + continue; + } + + FieldReverseLookupInput suffix_lookup; + suffix_lookup.deref = std::nullopt; + suffix_lookup.offset = 0; + suffix_lookup.stride = 0; + suffix_lookup.base_type = new_type; + auto suffix_results = ts.reverse_field_multi_lookup(suffix_lookup); + for (const auto& result : suffix_results.results) { + if (!result.addr_of && result.result_type == *form_type && + deref_suffix_matches_lookup(deref->tokens(), keep, result)) { + std::vector prefix_tokens(deref->tokens().begin(), + deref->tokens().begin() + keep); + if (prefix_tokens.empty()) { + return deref->base(); + } + return pool.form(deref->base(), false, std::move(prefix_tokens)); + } + } + } + } + // sometimes, accessing a field is a no-op but changes the type. For example, accessing an inlined // structure at the start of a structure. To detect this, look up all the possible derefs with no // deref or offset, then find the highest scoring one that is the right type. @@ -577,7 +716,7 @@ Form* cast_form_from(Form* in, lookup_input.deref = std::nullopt; lookup_input.offset = 0; lookup_input.stride = 0; - lookup_input.base_type = old_type; + lookup_input.base_type = form_type.value_or(old_type); auto lookup_result = ts.reverse_field_multi_lookup(lookup_input); if (lookup_result.success) { for (auto& result : lookup_result.results) { @@ -1153,6 +1292,99 @@ void SimpleExpressionElement::update_from_stack_si_1(const Env& env, make_cast_if_needed(arg, in_type, TypeSpec("int"), pool, env))); } +DerefElement* try_reassociate_inline_array_field_access(Form* field_access, + Form* product, + int stride, + const Env& env, + FormPool& pool) { + auto* deref = field_access->try_as_element(); + if (!deref || deref->is_addr_of()) { + return nullptr; + } + + auto stride_matcher = + Matcher::match_or({Matcher::cast("uint", Matcher::integer(stride)), + Matcher::cast("int", Matcher::integer(stride)), Matcher::integer(stride)}); + auto product_matcher = + Matcher::match_or({Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION), + {Matcher::any(0), stride_matcher}), + Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION), + {stride_matcher, Matcher::any(0)})}); + auto product_match = match(product_matcher, product); + if (!product_match.matched) { + return nullptr; + } + + // Preserve a field path that was already reconstructed from a constant-zero inline-array + // element, replacing only that index with the dynamic one. + if (!deref->tokens().empty() && deref->tokens().front().is_int(0)) { + std::optional base_type; + auto base_atom = form_as_atom(deref->base()); + if (base_atom && base_atom->is_var()) { + base_type = env.get_variable_type(base_atom->var(), true); + } else { + auto base_form = deref->base()->to_form(env); + if (base_form.is_symbol()) { + try { + base_type = env.dts->lookup_symbol_type(base_form.as_symbol().name_ptr); + } catch (const std::runtime_error&) { + } + } + } + if (base_type && base_type->base_type() == "inline-array") { + FieldReverseLookupInput lookup; + lookup.deref = std::nullopt; + lookup.stride = stride; + lookup.offset = 0; + lookup.base_type = *base_type; + auto reverse = env.dts->ts.reverse_field_multi_lookup(lookup); + for (const auto& candidate : reverse.results) { + if (candidate.has_variable_token()) { + auto tokens = deref->tokens(); + tokens.front() = DerefToken::make_int_expr(product_match.maps.forms.at(0)); + return pool.alloc_element(deref->base(), false, std::move(tokens)); + } + } + } + } + + // The array can also be an inline field at offset zero in the reconstructed base expression. + // Ask reverse lookup for the complete path instead of requiring the base itself to have an + // inline-array type. + auto field_type = try_get_deref_form_type(field_access, env); + if (!field_type) { + return nullptr; + } + FieldReverseLookupInput lookup; + lookup.deref = std::nullopt; + lookup.stride = stride; + lookup.offset = 0; + lookup.base_type = *field_type; + auto reverse = env.dts->ts.reverse_field_multi_lookup(lookup); + for (const auto& candidate : reverse.results) { + if (!candidate.has_variable_token()) { + continue; + } + bool used_index = false; + std::vector tokens; + for (const auto& token : candidate.tokens) { + if (token.kind == FieldReverseLookupOutput::Token::Kind::VAR_IDX) { + if (used_index) { + return nullptr; + } + used_index = true; + tokens.push_back(DerefToken::make_int_expr(product_match.maps.forms.at(0))); + } else { + tokens.push_back(to_token(token)); + } + } + auto* result = pool.alloc_element(field_access, candidate.addr_of, tokens); + result->inline_nested(); + return result; + } + return nullptr; +} + void SimpleExpressionElement::update_from_stack_add_i(const Env& env, FormPool& pool, FormStack& stack, @@ -1220,6 +1452,20 @@ void SimpleExpressionElement::update_from_stack_add_i(const Env& env, auto addition_matcher = GenericOpMatcher::or_match({GenericOpMatcher::fixed(FixedOperatorKind::ADDITION), GenericOpMatcher::fixed(FixedOperatorKind::ADDITION_PTR)}); + if (arg1_type.kind == TP_Type::Kind::PRODUCT_WITH_CONSTANT) { + if (auto* indexed = try_reassociate_inline_array_field_access( + args.at(0), args.at(1), arg1_type.get_multiplier(), env, pool)) { + result->push_back(indexed); + return; + } + } + if (arg0_type.kind == TP_Type::Kind::PRODUCT_WITH_CONSTANT) { + if (auto* indexed = try_reassociate_inline_array_field_access( + args.at(1), args.at(0), arg0_type.get_multiplier(), env, pool)) { + result->push_back(indexed); + return; + } + } if (arg0_type.kind == TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR) { // try to see if this is valid, from the type system. FieldReverseLookupInput input; @@ -2779,6 +3025,11 @@ void SetVarElement::push_to_stack(const Env& env, FormPool& pool, FormStack& sta // we aren't a reg-reg move, so update our source m_src->update_children_from_stack(env, pool, stack, true); + const auto expected_type = env.get_variable_type(m_dst, true); + if (try_rewrite_inline_array_cursor(m_src, expected_type, env)) { + m_src_type = expected_type; + } + for (auto x : m_src->elts()) { ASSERT(x->parent_form == m_src); } @@ -2899,6 +3150,17 @@ void SetFormFormElement::push_to_stack(const Env& env, FormPool& pool, FormStack ASSERT(m_real_push_count == 0); m_real_push_count++; + // An indexed element cast back to its containing inline-array type is an address/cursor update. + if (m_cast_for_set && try_rewrite_inline_array_cursor(m_src, *m_cast_for_set, env)) { + m_cast_for_set = {}; + } + if (auto* cast = m_src->try_as_element(); + cast && !cast->numeric() && + try_rewrite_inline_array_cursor(cast->source(), cast->type(), env)) { + m_src = cast->source(); + m_src->parent_element = this; + } + // check for bitfield setting: auto src_as_bf_set = dynamic_cast(m_src->try_as_single_element()); if (src_as_bf_set && !src_as_bf_set->from_pcpyud() && src_as_bf_set->mods().size() == 1) { @@ -3152,6 +3414,7 @@ bool try_to_rewrite_vector_inline_ctor(const Env& env, struct Jak1MatrixRowDeref { RegisterAccess base; std::vector matrix_tokens; + bool is_vector_view = false; }; std::optional identity_var(Form* form) { @@ -3192,14 +3455,7 @@ std::optional deref_result_type(RegisterAccess base, std::optional match_jak1_matrix_row(Form* form, int row, const Env& env) { auto* deref = form ? form->try_as_element() : nullptr; - if (!deref || deref->is_addr_of() || deref->tokens().size() < 3) { - return {}; - } - - const auto suffix = deref->tokens().size() - 3; - if (!deref->tokens().at(suffix).is_field_name("vector") || - !deref->tokens().at(suffix + 1).is_int(row) || - !deref->tokens().at(suffix + 2).is_field_name("quad")) { + if (!deref || deref->is_addr_of() || deref->tokens().size() < 2) { return {}; } @@ -3207,12 +3463,29 @@ std::optional match_jak1_matrix_row(Form* form, int row, con if (!base) { return {}; } - std::vector matrix_tokens(deref->tokens().begin(), deref->tokens().begin() + suffix); - auto matrix_type = deref_result_type(*base, matrix_tokens, env); - if (!matrix_type || *matrix_type != TypeSpec("matrix")) { + + const auto row_token = deref->tokens().size() - 2; + if (!deref->tokens().at(row_token).is_int(row) || + !deref->tokens().at(row_token + 1).is_field_name("quad")) { return {}; } - return Jak1MatrixRowDeref{*base, std::move(matrix_tokens)}; + + bool is_vector_view = true; + auto matrix_end = row_token; + if (row_token && deref->tokens().at(row_token - 1).is_field_name("vector")) { + is_vector_view = false; + matrix_end--; + } + + std::vector matrix_tokens(deref->tokens().begin(), + deref->tokens().begin() + matrix_end); + auto container_type = deref_result_type(*base, matrix_tokens, env); + const auto expected_type = + is_vector_view ? TypeSpec("inline-array", {TypeSpec("vector")}) : TypeSpec("matrix"); + if (!container_type || *container_type != expected_type) { + return {}; + } + return Jak1MatrixRowDeref{*base, std::move(matrix_tokens), is_vector_view}; } bool same_deref_tokens(std::vector lhs, std::vector rhs, const Env& env) { @@ -3253,6 +3526,21 @@ Form* append_deref_tokens(Form* base, const std::vector& tokens, For return pool.form(base, false, tokens); } +Form* matrix_from_jak1_vector_view(Form* view, FormPool& pool, const Env& env) { + auto* deref = view ? view->try_as_element() : nullptr; + if (deref && !deref->is_addr_of() && !deref->tokens().empty() && + deref->tokens().back().is_field_name("vector")) { + auto tokens = deref->tokens(); + tokens.pop_back(); + auto matrix = append_deref_tokens(deref->base(), tokens, pool); + auto matrix_type = try_get_deref_form_type(matrix, env); + if (matrix_type == TypeSpec("matrix")) { + return matrix; + } + } + return cast_form(view, TypeSpec("matrix"), pool, env); +} + bool try_to_rewrite_matrix_inline_copy(const Env& env, FormPool& pool, FormStack& stack) { if (env.func->name() == "matrix-copy!" || env.func->name() == "(method 63 collide-shape-moving)") { @@ -3265,15 +3553,19 @@ bool try_to_rewrite_matrix_inline_copy(const Env& env, FormPool& pool, FormStack // MAT should always be a variable std::vector load_src_ras, store_dest_ras, store_src_ras; std::vector src_matrix_tokens, dst_matrix_tokens; + bool src_is_vector_view = false; + bool dst_is_vector_view = false; for (int i = 0; i < 4; i++) { if (env.version == GameVersion::Jak1) { auto row = match_jak1_matrix_row(matrix_entries->at(i).source, i, env); - if (!row || (i && !same_deref_tokens(src_matrix_tokens, row->matrix_tokens, env))) { + if (!row || (i && (src_is_vector_view != row->is_vector_view || + !same_deref_tokens(src_matrix_tokens, row->matrix_tokens, env)))) { return false; } load_src_ras.push_back(row->base); if (!i) { src_matrix_tokens = std::move(row->matrix_tokens); + src_is_vector_view = row->is_vector_view; } } else { const char* names[] = {"rvec", "uvec", "fvec", "trans"}; @@ -3295,13 +3587,15 @@ bool try_to_rewrite_matrix_inline_copy(const Env& env, FormPool& pool, FormStack auto row = set ? match_jak1_matrix_row(set->dst(), i - 4, env) : std::nullopt; auto src = set ? identity_var(set->src()) : std::nullopt; if (!row || !src || - (i != 4 && !same_deref_tokens(dst_matrix_tokens, row->matrix_tokens, env))) { + (i != 4 && (dst_is_vector_view != row->is_vector_view || + !same_deref_tokens(dst_matrix_tokens, row->matrix_tokens, env)))) { return false; } store_dest_ras.push_back(row->base); store_src_ras.push_back(*src); if (i == 4) { dst_matrix_tokens = std::move(row->matrix_tokens); + dst_is_vector_view = row->is_vector_view; } } else { const char* names[] = {"rvec", "uvec", "fvec", "trans"}; @@ -3364,6 +3658,9 @@ bool try_to_rewrite_matrix_inline_copy(const Env& env, FormPool& pool, FormStack // lg::info(" popped src: {}", src_repopped->to_string(env)); } src_repopped = append_deref_tokens(src_repopped, src_matrix_tokens, pool); + if (src_is_vector_view) { + src_repopped = matrix_from_jak1_vector_view(src_repopped, pool, env); + } // src_repopped = matrix_entries->at(0).source; bool found = false; @@ -3378,10 +3675,13 @@ bool try_to_rewrite_matrix_inline_copy(const Env& env, FormPool& pool, FormStack // ra.mode() == AccessMode::WRITE); } dst_repopped = append_deref_tokens(dst_repopped, dst_matrix_tokens, pool); + if (dst_is_vector_view) { + dst_repopped = matrix_from_jak1_vector_view(dst_repopped, pool, env); + } // If the matrix is a field of the popped value, the copy result is not the value of that // containing object. Emit it as a standalone side-effecting form instead. - if (found && dst_matrix_tokens.empty()) { + if (found && dst_matrix_tokens.empty() && !dst_is_vector_view) { stack.push_value_to_reg( ra, pool.form( @@ -3530,13 +3830,30 @@ std::optional deref_container_info(DerefElement* deref, cons return {}; } - auto base = deref->base()->try_as_element(); - if (!base || base->expr().kind() != SimpleExpression::Kind::IDENTITY || - !base->expr().get_arg(0).is_var()) { + auto base = form_as_atom(deref->base()); + if (!base) { return {}; } - TypeSpec current = env.get_variable_type(base->expr().get_arg(0).var(), true); + std::optional base_type; + if (base->is_var()) { + base_type = env.get_variable_type(base->var(), true); + } else if (base->is_int() && base->get_int() == 0x70000000 && env.scratchpad_type()) { + base_type = env.scratchpad_type(); + } else { + auto base_form = deref->base()->to_form(env); + if (base_form.is_symbol()) { + try { + base_type = env.dts->lookup_symbol_type(base_form.as_symbol().name_ptr); + } catch (const std::runtime_error&) { + } + } + } + if (!base_type) { + return {}; + } + + TypeSpec current = *base_type; bool is_matrix_row = current == TypeSpec("matrix") || current == TypeSpec("matrix3"); for (size_t i = 0; i + 1 < deref->tokens().size(); ++i) { const auto& token = deref->tokens().at(i); @@ -3575,11 +3892,10 @@ Form* pop_last_deref_token(Form* form) { } } -FormElement* try_to_rewrite_vector_copy(Form* dst, - Form* src, - FormPool& pool, - const Env& env) { - if (env.func->name() == "vector-copy!") { +} // namespace + +FormElement* try_to_rewrite_vector_copy(Form* dst, Form* src, FormPool& pool, const Env& env) { + if (env.func->name() == "vector-copy!" || env.func->name() == "vector4w-copy!") { return nullptr; } @@ -3596,9 +3912,13 @@ FormElement* try_to_rewrite_vector_copy(Form* dst, const auto dst_info = deref_container_info(dst_deref, env); const auto src_info = deref_container_info(src_deref, env); - if (!dst_info || !src_info || - !env.dts->ts.tc(TypeSpec("vector"), dst_info->type) || - !env.dts->ts.tc(TypeSpec("vector"), src_info->type)) { + if (!dst_info || !src_info || !env.dts->ts.tc(TypeSpec("vector"), dst_info->type)) { + return nullptr; + } + + const bool is_vector_copy = env.dts->ts.tc(TypeSpec("vector"), src_info->type); + const bool is_vector4w_copy = src_info->type == TypeSpec("vector4w"); + if (!is_vector_copy && !is_vector4w_copy) { return nullptr; } @@ -3609,7 +3929,8 @@ FormElement* try_to_rewrite_vector_copy(Form* dst, } auto ret = pool.alloc_element( - GenericOperator::make_function(pool.form("vector-copy!")), + GenericOperator::make_function( + pool.form(is_vector4w_copy ? "vector4w-copy!" : "vector-copy!")), std::vector{pop_last_deref_token(dst), pop_last_deref_token(src)}); // lg::info("success: {}\n", ret->to_string(env)); return ret; @@ -3617,18 +3938,16 @@ FormElement* try_to_rewrite_vector_copy(Form* dst, return nullptr; } -FormElement* try_to_rewrite_vector_zero(Form* dst, - Form* value, - FormPool& pool, - const Env& env) { +namespace { + +FormElement* try_to_rewrite_vector_zero(Form* dst, Form* value, FormPool& pool, const Env& env) { if (env.func->name() == "vector-zero!") { return nullptr; } auto dst_deref = dst ? dst->try_as_element() : nullptr; const auto dst_info = dst_deref ? deref_container_info(dst_deref, env) : std::nullopt; - if (!dst_info || !env.dts->ts.tc(TypeSpec("vector"), dst_info->type) || - dst_info->is_matrix_row) { + if (!dst_info || !env.dts->ts.tc(TypeSpec("vector"), dst_info->type)) { return nullptr; } if (!match(Matcher::cast("uint128", Matcher::integer(0)), value, &env).matched) { @@ -3640,14 +3959,24 @@ FormElement* try_to_rewrite_vector_zero(Form* dst, std::vector{pop_last_deref_token(dst)}); } -bool is_pending_stack_vector_ctor(const FormStack& stack, RegisterAccess destination) { - auto entries = stack.try_getting_active_stack_entries({true}); - if (!entries || !entries->front().destination || - entries->front().destination->reg() != destination.reg()) { - return false; +bool is_pending_stack_ctor(const FormStack& stack, + RegisterAccess destination, + const TypeSpec& type, + int max_following_stores) { + for (int following_stores = 0; following_stores <= max_following_stores; ++following_stores) { + std::vector pattern(1 + following_stores, false); + pattern.front() = true; + auto entries = stack.try_getting_active_stack_entries(pattern); + if (!entries || !entries->front().destination || + entries->front().destination->reg() != destination.reg()) { + continue; + } + auto stack_value = entries->front().source->try_as_element(); + if (stack_value && stack_value->type() == type) { + return true; + } } - auto stack_value = entries->front().source->try_as_element(); - return stack_value && stack_value->type() == TypeSpec("vector"); + return false; } } // namespace @@ -3673,8 +4002,7 @@ void StorePlainDeref::push_to_stack(const Env& env, FormPool& pool, FormStack& s FormElement* fr = nullptr; - if (size() == 16 && - (env.version == GameVersion::Jak1 || env.version == GameVersion::Jak3)) { + if (size() == 16 && (env.version == GameVersion::Jak1 || env.version == GameVersion::Jak3)) { fr = try_to_rewrite_vector_copy(m_dst, popped.at(0), pool, env); } @@ -3697,8 +4025,10 @@ void StorePlainDeref::push_to_stack(const Env& env, FormPool& pool, FormStack& s val->mark_popped(); auto typed_value = make_optional_cast(m_src_cast_type, val, pool, env); FormElement* fr = nullptr; - if (size() == 16 && env.version == GameVersion::Jak1 && - !is_pending_stack_vector_ctor(stack, m_base_var)) { + const bool pending_stack_ctor = + is_pending_stack_ctor(stack, m_base_var, TypeSpec("vector"), 0) || + is_pending_stack_ctor(stack, m_base_var, TypeSpec("matrix"), 3); + if (size() == 16 && env.version == GameVersion::Jak1 && !pending_stack_ctor) { fr = try_to_rewrite_vector_zero(m_dst, typed_value, pool, env); } if (!fr) { @@ -6945,6 +7275,54 @@ void ArrayFieldAccess::update_with_val(Form* new_val, FormPool& pool, std::vector* result, bool) { + // Reverse field lookup may already have rewritten the address expression into a dereference. + // If its trailing tokens instantiate the indexed prefix of this access, retain that typed prefix + // and append the fields which belong to the load itself. + if (auto* existing = dynamic_cast(new_val->try_as_single_active_element()); + existing && !existing->is_addr_of()) { + const auto& existing_tokens = existing->tokens(); + for (size_t overlap = std::min(existing_tokens.size(), m_deref_tokens.size()); overlap > 0; + --overlap) { + bool matches = true; + bool matched_placeholder = false; + const size_t existing_start = existing_tokens.size() - overlap; + for (size_t i = 0; i < overlap; ++i) { + const auto& expected = m_deref_tokens.at(i); + const auto& actual = existing_tokens.at(existing_start + i); + switch (expected.kind()) { + case DerefToken::Kind::FIELD_NAME: + matches = actual.kind() == DerefToken::Kind::FIELD_NAME && + actual.field_name() == expected.field_name(); + break; + case DerefToken::Kind::INTEGER_CONSTANT: + matches = actual.kind() == DerefToken::Kind::INTEGER_CONSTANT && + actual.int_constant() == expected.int_constant(); + break; + case DerefToken::Kind::EXPRESSION_PLACEHOLDER: + matches = actual.kind() == DerefToken::Kind::INTEGER_EXPRESSION || + actual.kind() == DerefToken::Kind::INTEGER_CONSTANT; + matched_placeholder = matches; + break; + default: + matches = false; + break; + } + if (!matches) { + break; + } + } + + if (matches && matched_placeholder) { + auto combined_tokens = existing_tokens; + combined_tokens.insert(combined_tokens.end(), m_deref_tokens.begin() + overlap, + m_deref_tokens.end()); + result->push_back(pool.alloc_element(existing->base(), existing->is_addr_of(), + combined_tokens)); + return; + } + } + } + int power_of_two = 0; if (m_constant_offset == 0) { diff --git a/decompiler/ObjectFile/ObjectFileDB.h b/decompiler/ObjectFile/ObjectFileDB.h index ff337917da..e224659b7a 100644 --- a/decompiler/ObjectFile/ObjectFileDB.h +++ b/decompiler/ObjectFile/ObjectFileDB.h @@ -86,11 +86,11 @@ struct LetRewriteStats { int light_trail_tracker_spawn = 0; int total() const { - return dotimes + countdown + abs + abs2 + unused + ja + ja_play + case_no_else + case_with_else + - set_vector + set_vector2 + send_event + font_context_meth + proc_new + attack_info + - vector_dot + rand_float_gen + set_let + with_dma_buf_add_bucket + dma_buffer_add_gs_set + - launch_particles + call_parent_state_handler + suspend_for + font_method + - light_trail_tracker_spawn; + return dotimes + countdown + abs + abs2 + unused + ja + ja_play + case_no_else + + case_with_else + set_vector + set_vector2 + send_event + font_context_meth + proc_new + + attack_info + vector_dot + rand_float_gen + set_let + with_dma_buf_add_bucket + + dma_buffer_add_gs_set + launch_particles + call_parent_state_handler + suspend_for + + font_method + light_trail_tracker_spawn; } std::string print() const { diff --git a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp index ae97ef3fb6..de08114818 100644 --- a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp +++ b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp @@ -612,6 +612,11 @@ void ObjectFileDB::ir2_type_analysis_pass(int seg, const Config& config, ObjectF try_lookup(config.stack_type_casts_by_function_by_stack_offset, func_name); func.ir2.env.set_stack_casts(stack_casts); + auto scratchpad_type = config.scratchpad_types_by_object.find(obj_name); + if (scratchpad_type != config.scratchpad_types_by_object.end()) { + func.ir2.env.set_scratchpad_type(TypeSpec(scratchpad_type->second)); + } + if (config.hacks.pair_functions_by_name.find(func_name) != config.hacks.pair_functions_by_name.end()) { func.ir2.env.set_sloppy_pair_typing(); @@ -883,7 +888,8 @@ void ObjectFileDB::ir2_write_results(const fs::path& output_dir, auto unformatted_code = ir2_final_out(obj, imports, {}); if (!config.demacro_file.empty()) { unformatted_code = - demacro::rewrite(unformatted_code, file_util::get_file_path({config.demacro_file})).source; + demacro::rewrite(unformatted_code, file_util::get_file_path({config.demacro_file})) + .source; } auto final_name = output_dir / (obj.to_unique_name() + "_disasm.gc"); if (config.format_code) { diff --git a/decompiler/analysis/final_output.cpp b/decompiler/analysis/final_output.cpp index 78f7f6ddbb..123de41322 100644 --- a/decompiler/analysis/final_output.cpp +++ b/decompiler/analysis/final_output.cpp @@ -54,7 +54,8 @@ void append_body_to_function_definition(goos::Object* top_form, const std::vector& inline_body, const FunctionVariableDefinitions& var_dec, const TypeSpec& ts, - GameVersion version) { + GameVersion version, + const Env& env) { // Some forms like docstrings and local-vars we _always_ want to be at the top level and first (in // the order added) std::vector initial_top_level_forms; @@ -77,19 +78,32 @@ void append_body_to_function_definition(goos::Object* top_form, for (const auto& elem : initial_top_level_forms) { final_body.push_back(elem); } + std::vector scoped_body; + // If the form contains the ppointer and isn't a behavior, we need to wrap the body in `with-pp` if (var_dec.had_pp && !ts.try_get_tag("behavior")) { std::vector body_with_pp; body_with_pp.push_back(pretty_print::to_symbol("with-pp")); body_with_pp.insert(body_with_pp.end(), body_elements.begin(), body_elements.end()); - final_body.push_back(pretty_print::build_list(body_with_pp)); + scoped_body.push_back(pretty_print::build_list(body_with_pp)); } else { // otherwise, just construct the form from the body for (const auto& elem : body_elements) { - final_body.push_back(elem); + scoped_body.push_back(elem); } } + if (env.uses_scratchpad()) { + ASSERT(env.scratchpad_type()); + std::vector scratchpad_body; + scratchpad_body.push_back(pretty_print::to_symbol("slet")); + scratchpad_body.push_back(pretty_print::build_list("spad", env.scratchpad_type()->print())); + scratchpad_body.insert(scratchpad_body.end(), scoped_body.begin(), scoped_body.end()); + final_body.push_back(pretty_print::build_list(scratchpad_body)); + } else { + final_body.insert(final_body.end(), scoped_body.begin(), scoped_body.end()); + } + pretty_print::append(*top_form, pretty_print::build_list(final_body)); } } // namespace @@ -103,11 +117,13 @@ goos::Object final_output_lambda(const Function& func, GameVersion version) { if (behavior) { auto result = pretty_print::build_list(fmt::format("lambda :behavior {}", *behavior), get_arg_list_for_function(func, func.ir2.env)); - append_body_to_function_definition(&result, inline_body, var_dec, func.type, version); + append_body_to_function_definition(&result, inline_body, var_dec, func.type, version, + func.ir2.env); return result; } else { auto result = pretty_print::build_list("lambda", get_arg_list_for_function(func, func.ir2.env)); - append_body_to_function_definition(&result, inline_body, var_dec, func.type, version); + append_body_to_function_definition(&result, inline_body, var_dec, func.type, version, + func.ir2.env); return result; } } @@ -134,8 +150,7 @@ goos::Object final_output_defstate_anonymous_behavior(const Function& func, } else if (func.guessed_name.kind == FunctionName::FunctionKind::NV_STATE) { if (dts.state_metadata.count(state_name) != 0 && dts.state_metadata.at(state_name).count(handler_name) != 0) { - metadata_docstring = - dts.state_metadata.at(state_name).at(handler_name).docstring.value(); + metadata_docstring = dts.state_metadata.at(state_name).at(handler_name).docstring.value(); } } @@ -146,7 +161,8 @@ goos::Object final_output_defstate_anonymous_behavior(const Function& func, auto var_dec = func.ir2.env.local_var_type_list(func.ir2.top_form, func.type.arg_count() - 1); auto result = pretty_print::build_list("behavior", get_arg_list_for_function(func, func.ir2.env)); - append_body_to_function_definition(&result, inline_body, var_dec, func.type, dts.version()); + append_body_to_function_definition(&result, inline_body, var_dec, func.type, dts.version(), + func.ir2.env); return result; } @@ -198,7 +214,8 @@ std::string final_defun_out(const Function& func, } } - append_body_to_function_definition(&top_form, inline_body, var_dec, func.type, dts.version()); + append_body_to_function_definition(&top_form, inline_body, var_dec, func.type, dts.version(), + env); return pretty_print::to_string(top_form); } @@ -219,7 +236,7 @@ std::string final_defun_out(const Function& func, set_metadata_docstring(&inline_body, method_info.docstring.value()); } append_body_to_function_definition(&top_form, inline_body, var_dec, method_info.type, - dts.version()); + dts.version(), env); return pretty_print::to_string(top_form); } @@ -230,7 +247,8 @@ std::string final_defun_out(const Function& func, top.push_back(arguments); auto top_form = pretty_print::build_list(top); - append_body_to_function_definition(&top_form, inline_body, var_dec, func.type, dts.version()); + append_body_to_function_definition(&top_form, inline_body, var_dec, func.type, dts.version(), + env); return pretty_print::to_string(top_form); } @@ -243,7 +261,8 @@ std::string final_defun_out(const Function& func, top.push_back(arguments); auto top_form = pretty_print::build_list(top); - append_body_to_function_definition(&top_form, inline_body, var_dec, func.type, dts.version()); + append_body_to_function_definition(&top_form, inline_body, var_dec, func.type, dts.version(), + env); return pretty_print::to_string(top_form); } @@ -257,7 +276,8 @@ std::string final_defun_out(const Function& func, top.push_back(arguments); auto top_form = pretty_print::build_list(top); - append_body_to_function_definition(&top_form, inline_body, var_dec, func.type, dts.version()); + append_body_to_function_definition(&top_form, inline_body, var_dec, func.type, dts.version(), + env); return pretty_print::to_string(top_form); } diff --git a/decompiler/analysis/insert_lets.cpp b/decompiler/analysis/insert_lets.cpp index 4a14fc9000..725c1e09d8 100644 --- a/decompiler/analysis/insert_lets.cpp +++ b/decompiler/analysis/insert_lets.cpp @@ -10,6 +10,7 @@ #include "common/util/Assert.h" #include "common/util/print_float.h" +#include "decompiler/IR2/ExpressionHelpers.h" #include "decompiler/IR2/GenericElementMatcher.h" #include "decompiler/IR2/bitfields.h" #include "decompiler/util/DecompilerTypeSystem.h" @@ -102,7 +103,10 @@ bool is_constant_int(const Form* f, int val) { return as_atom && as_atom->is_int(val); } -FormElement* rewrite_as_dotimes(LetElement* in, const Env& env, FormPool& pool) { +FormElement* rewrite_as_dotimes(RegisterAccess ra, + FormElement* loop, + const Env& env, + FormPool& pool) { // dotimes OpenGOAL: /* (defmacro dotimes (var &rest body) @@ -117,24 +121,13 @@ FormElement* rewrite_as_dotimes(LetElement* in, const Env& env, FormPool& pool) ) */ - // should have this anyway, but double check so we don't throw this away. - if (in->entries().size() != 1) { - return nullptr; - } - - // look for setting a var to zero. - auto ra = in->entries().at(0).dest; - if (!is_constant_int(in->entries().at(0).src, 0)) { - return nullptr; - } - // still have to check body for the increment and have to check that the lt operates on the right // thing. Matcher while_matcher = Matcher::while_loop( Matcher::op_fixed(FixedOperatorKind::LT, {Matcher::any_reg(0), Matcher::any(1)}), Matcher::any(2)); - auto mr = match(while_matcher, in->body()); + auto mr = match(while_matcher, loop); if (!mr.matched) { return nullptr; } @@ -169,9 +162,61 @@ FormElement* rewrite_as_dotimes(LetElement* in, const Env& env, FormPool& pool) // first, remove the increment body->pop_back(); - return pool.alloc_element(CounterLoopElement::Kind::DOTIMES, - in->entries().at(0).dest, *lt_var, *inc_var, - mr.maps.forms.at(1), body); + return pool.alloc_element(CounterLoopElement::Kind::DOTIMES, ra, *lt_var, + *inc_var, mr.maps.forms.at(1), body); +} + +FormElement* rewrite_as_dotimes(LetElement* in, const Env& env, FormPool& pool) { + // should have this anyway, but double check so we don't throw this away. + if (in->entries().size() != 1) { + return nullptr; + } + + // look for setting a var to zero. + const auto& entry = in->entries().at(0); + if (!is_constant_int(entry.src, 0)) { + return nullptr; + } + + auto* loop = in->body()->try_as_single_active_element(); + if (!loop) { + return nullptr; + } + return rewrite_as_dotimes(entry.dest, loop, env, pool); +} + +bool program_var_is_confined_to(const Form* top_level_form, + const RegId& var, + FormElement* init, + FormElement* loop, + const Env& env) { + RegAccessSet all_accesses; + top_level_form->collect_vars(all_accesses, true); + + RegAccessSet confined_accesses; + init->collect_vars(confined_accesses, true); + loop->collect_vars(confined_accesses, true); + + for (const auto& access : all_accesses) { + if (env.get_program_var_id(access) == var && !confined_accesses.count(access)) { + return false; + } + } + return true; +} + +bool program_var_shares_name(const Form* top_level_form, + const RegId& var, + const std::string& name, + const Env& env) { + RegAccessSet all_accesses; + top_level_form->collect_vars(all_accesses, true); + for (const auto& access : all_accesses) { + if (env.get_program_var_id(access) != var && env.get_variable_name(access) == name) { + return true; + } + } + return false; } std::tuple rewrite_shelled_return_form( @@ -463,6 +508,12 @@ FormElement* rewrite_as_send_event(LetElement* in, auto val = param_val->to_form(env).as_int(); auto old_param = param_val; + EnumType* enum_ts = nullptr; + + if (param_idx == 0 && (msg_str == "'clear-slave-option" || msg_str == "'set-slave-option" || + msg_str == "'toggle-slave-option")) { + enum_ts = env.dts->ts.try_enum_lookup("cam-slave-options"); + } if (env.version >= GameVersion::Jak2) { auto find_int_in_vector = [](const std::vector& vec, int val) { @@ -491,9 +542,6 @@ FormElement* rewrite_as_send_event(LetElement* in, {"'set-alert-duration", {0}}, }; - // enum to cast to. - EnumType* enum_ts = nullptr; - auto float_arg_settings = jak2_float_args.find(msg_str); auto time_frame_arg_settings = jak2_time_frame_args.find(msg_str); if ((float_arg_settings != jak2_float_args.end() && @@ -522,10 +570,6 @@ FormElement* rewrite_as_send_event(LetElement* in, msg_str == "'set-object-auto-activate" || msg_str == "'end-pursuit-by-type" || msg_str == "'get-object-remaining-count")) { enum_ts = env.dts->ts.try_enum_lookup("traffic-type"); - } else if (param_idx == 0 && - (msg_str == "'clear-slave-option" || msg_str == "'set-slave-option" || - msg_str == "'toggle-slave-option")) { - enum_ts = env.dts->ts.try_enum_lookup("cam-slave-options"); } else if (param_idx == 2 && param_values.at(0)->to_string(env) == "'darkjak" && msg_str == "'change-mode") { enum_ts = env.dts->ts.try_enum_lookup("darkjak-stage"); @@ -533,12 +577,12 @@ FormElement* rewrite_as_send_event(LetElement* in, msg_str == "'change-mode") { enum_ts = env.dts->ts.try_enum_lookup("pickup-type"); } - if (enum_ts) { - if (enum_ts->is_bitfield()) { - param_val = cast_to_bitfield_enum(enum_ts, pool, env, val); - } else { - param_val = cast_to_int_enum(enum_ts, pool, env, val); - } + } + if (enum_ts) { + if (enum_ts->is_bitfield()) { + param_val = cast_to_bitfield_enum(enum_ts, pool, env, val); + } else { + param_val = cast_to_int_enum(enum_ts, pool, env, val); } } // if we didn't cast @@ -786,6 +830,33 @@ FormElement* rewrite_empty_let(LetElement* in, const Env&, FormPool&) { return in->entries().at(0).src->try_as_single_element(); } +FormElement* rewrite_spilled_stack_vector_ctor(LetElement* in, const Env& env, FormPool& pool) { + if (in->entries().size() != 1 || in->body()->elts().empty()) { + return nullptr; + } + + auto& entry = in->entries().front(); + auto* stack_value = entry.src->try_as_element(); + if (!stack_value || stack_value->type() != TypeSpec("vector")) { + return nullptr; + } + + auto zero_store = + Matcher::set(Matcher::deref(Matcher::any_reg(0), false, {DerefTokenMatcher::string("quad")}), + Matcher::cast("uint128", Matcher::integer(0))); + auto zero_match = match(zero_store, in->body()->elts().front(), &env); + if (!zero_match.matched || !zero_match.maps.regs.at(0) || + env.get_program_var_id(*zero_match.maps.regs.at(0)) != env.get_program_var_id(entry.dest)) { + return nullptr; + } + + entry.src = pool.form( + GenericOperator::make_function(pool.form("new-stack-vector0"))); + entry.src->parent_element = in; + in->body()->elts().erase(in->body()->elts().begin()); + return in; +} + FormElement* rewrite_set_let(LetElement* in, const Env& env, FormPool& pool) { /* * (let ((dest-var src)) @@ -889,6 +960,66 @@ FormElement* rewrite_set_vector(LetElement* in, const Env& env, FormPool& pool) return pool.alloc_element(op, args); } +FormElement* rewrite_set_vector_sequence(const std::array& elts, + const Env& env, + FormPool& pool) { + std::optional vector_access; + Form* vector_form = nullptr; + std::vector sources; + + for (int i = 0; i < 4; i++) { + auto* set = dynamic_cast(elts.at(i)); + if (!set) { + return nullptr; + } + + auto* deref = set->dst()->try_as_element(); + Matcher dst_matcher = Matcher::deref(Matcher::any_reg(0), false, + {DerefTokenMatcher::string(std::string(1, "xyzw"[i]))}); + auto mr = match(dst_matcher, set->dst()); + if (!deref || !mr.matched) { + return nullptr; + } + + const auto this_access = *mr.maps.regs.at(0); + if (vector_access && + env.get_program_var_id(*vector_access) != env.get_program_var_id(this_access)) { + return nullptr; + } + vector_access = this_access; + if (!vector_form) { + vector_form = deref->base(); + } + sources.push_back(set->src()); + } + + ASSERT(vector_access); + std::vector args = {vector_form}; + args.insert(args.end(), sources.begin(), sources.end()); + auto op = GenericOperator::make_function( + pool.alloc_single_element_form(nullptr, "set-vector!")); + return pool.alloc_element(op, args); +} + +FormElement* rewrite_vector_copy(LetElement* in, const Env& env, FormPool& pool) { + if (in->entries().size() != 1 || in->body()->elts().size() != 1) { + return nullptr; + } + + auto* set = dynamic_cast(in->body()->at(0)); + if (!set) { + return nullptr; + } + + const auto src = form_as_atom(set->src()); + if (!src || !src->is_var() || + env.get_program_var_id(src->var()) != env.get_program_var_id(in->entries().at(0).dest)) { + return nullptr; + } + + return try_to_rewrite_vector_copy(set->dst(), in->entries().at(0).src, pool, env); +} + FormElement* rewrite_set_vector_3(LetElement* in, const Env& env, FormPool& pool) { if (in->entries().size() != 1) { return nullptr; @@ -2718,6 +2849,14 @@ FormElement* rewrite_let(LetElement* in, const Env& env, FormPool& pool, LetRewr } } + // Stack spills can separate a stack-vector allocation from its zero-store on the expression + // stack. Once lets are reconstructed, recognize the same constructor from the binding and the + // first operation in its body. + auto as_spilled_stack_vector_ctor = rewrite_spilled_stack_vector_ctor(in, env, pool); + if (as_spilled_stack_vector_ctor) { + return as_spilled_stack_vector_ctor; + } + if (let_uses_stack_slot_access(in)) { return nullptr; } @@ -2728,6 +2867,11 @@ FormElement* rewrite_let(LetElement* in, const Env& env, FormPool& pool, LetRewr return as_unused; } + auto as_vector_copy = rewrite_vector_copy(in, env, pool); + if (as_vector_copy) { + return as_vector_copy; + } + auto as_joint_macro = rewrite_joint_macro(in, env, pool); if (as_joint_macro) { stats.ja++; @@ -3942,8 +4086,7 @@ FormElement* rewrite_ja_play_sequence(FormElement* setup, auto setup_chan = find_keyword_arg(setup_call, ":chan"); const auto setup_chan_text = setup_chan ? setup_chan->to_form(env).print() : "0"; - if (wait_loop->condition->to_string(env) != - fmt::format("(ja-done? {})", setup_chan_text)) { + if (wait_loop->condition->to_string(env) != fmt::format("(ja-done? {})", setup_chan_text)) { return nullptr; } @@ -4023,6 +4166,72 @@ bool let_uses_stack_slot_access(const LetElement* in) { bool register_can_hold_var(const Register& reg) { return reg.get_kind() == Reg::FPR || reg.get_kind() == Reg::GPR; } + +void rewrite_short_meter_constant(Form*& arg, FormPool& pool, const Env& env) { + const auto value = arg->to_form(env); + if (!value.is_float() || value.as_float() == 0.f) { + return; + } + + const auto meter_text = meters_to_string(value.as_float()); + const auto magnitude_length = + meter_text.front() == '-' ? meter_text.size() - 1 : meter_text.size(); + if (magnitude_length > 4) { + return; + } + + arg = pool.form( + GenericOperator::make_function(pool.form("meters")), + pool.form(meter_text)); +} + +void rewrite_short_degree_constant(Form*& arg, FormPool& pool, const Env& env) { + const auto value = arg->to_form(env); + if (!value.is_float() || value.as_float() == 0.f) { + return; + } + + const auto degree_text = degrees_to_string(value.as_float()); + const auto magnitude_length = + degree_text.front() == '-' ? degree_text.size() - 1 : degree_text.size(); + const bool is_integer = degree_text.find('.') == std::string::npos; + if (!is_integer && magnitude_length > 4) { + return; + } + + arg = pool.form( + GenericOperator::make_function(pool.form("degrees")), + pool.form(degree_text)); +} + +void rewrite_short_unit_arguments(GenericElement* call, FormPool& pool, const Env& env) { + if (!call->op().is_func()) { + return; + } + + static const std::unordered_set meter_forms = { + "fill-and-probe-using-line-sphere", "set-vector!", "vector-normalize-copy!", + "vector-normalize!", "vector+float*!"}; + static const std::unordered_set degree_forms = {"sin", "cos", "tan"}; + const auto op = call->op().to_form(env).print(); + const bool use_meters = meter_forms.count(op); + const bool use_degrees = degree_forms.count(op); + if (!use_meters && !use_degrees) { + return; + } + + for (auto*& arg : call->elts()) { + auto* original = arg; + if (use_meters) { + rewrite_short_meter_constant(arg, pool, env); + } else { + rewrite_short_degree_constant(arg, pool, env); + } + if (arg != original) { + arg->parent_element = call; + } + } +} } // namespace LetStats insert_lets(const Function& func, @@ -4036,6 +4245,44 @@ LetStats insert_lets(const Function& func, // } LetStats stats; + // A deliberately reused display name can cause otherwise independent program variables to be + // grouped together by let insertion. Recognize the unshelled expansion before inserting lets + // when the counter's complete lifetime is confined to the adjacent set!/while pair. Doing this + // here keeps a following co-named loop from being pulled into the first counter's let body, while + // preserving the existing behavior for co-named variables in every other situation. + top_level_form->apply_form([&](Form* f) { + auto& elts = f->elts(); + for (size_t i = 0; i + 1 < elts.size();) { + auto* init = dynamic_cast(elts.at(i)); + if (!init || !register_can_hold_var(init->dst().reg()) || + init->info().is_eliminated_coloring_move || !is_constant_int(init->src(), 0)) { + i++; + continue; + } + + const auto var = env.get_program_var_id(init->dst()); + const auto name = env.get_variable_name(init->dst()); + if (!program_var_shares_name(top_level_form, var, name, env) || + !program_var_is_confined_to(top_level_form, var, init, elts.at(i + 1), env)) { + i++; + continue; + } + + auto* dotimes = rewrite_as_dotimes(init->dst(), elts.at(i + 1), env, pool); + if (!dotimes) { + i++; + continue; + } + + dotimes->parent_form = f; + elts.at(i) = dotimes; + elts.erase(elts.begin() + i + 1); + env.set_defined_in_let(name); + let_rewrite_stats.dotimes++; + i++; + } + }); + // Stored per variable. struct PerVarInfo { std::string unique_name; // displayed name used to join deliberately co-named SSA variables @@ -4152,8 +4399,7 @@ LetStats insert_lets(const Function& func, auto first_form = info.lca_form->at(info.start_idx); auto first_form_as_set = dynamic_cast(first_form); if (first_form_as_set && register_can_hold_var(first_form_as_set->dst().reg()) && - env.get_variable_name(first_form_as_set->dst()) == - env.get_variable_name(info.access) && + env.get_variable_name(first_form_as_set->dst()) == env.get_variable_name(info.access) && !first_form_as_set->info().is_eliminated_coloring_move) { bool allowed = true; @@ -4291,6 +4537,26 @@ LetStats insert_lets(const Function& func, } }); + // set-vector! component stores can also appear inside a larger let that introduces aliases for + // a following operation. Recognize the four-store sequence without changing that let's scope. + top_level_form->apply_form([&](Form* f) { + auto& elts = f->elts(); + for (size_t i = 0; i + 3 < elts.size();) { + auto* rewritten = rewrite_set_vector_sequence( + {elts.at(i), elts.at(i + 1), elts.at(i + 2), elts.at(i + 3)}, env, pool); + if (!rewritten) { + i++; + continue; + } + + rewritten->parent_form = f; + elts.at(i) = rewritten; + elts.erase(elts.begin() + i + 1, elts.begin() + i + 4); + let_rewrite_stats.set_vector++; + i++; + } + }); + // Part 9: compact recursive lets: bool changed = true; while (changed) { @@ -4377,6 +4643,14 @@ LetStats insert_lets(const Function& func, } }); + // These vector operations conventionally take world-space distances in the selected argument + // positions. Prefer a meters form when the converted constant remains compact. + top_level_form->apply([&](FormElement* elt) { + if (auto* call = dynamic_cast(elt)) { + rewrite_short_unit_arguments(call, pool, env); + } + }); + return stats; } diff --git a/decompiler/config.cpp b/decompiler/config.cpp index 275a04fd90..316b9cb254 100644 --- a/decompiler/config.cpp +++ b/decompiler/config.cpp @@ -193,6 +193,13 @@ Config make_config_via_json(nlohmann::json& json) { } } + if (json.contains("scratchpad_types_file")) { + auto scratchpad_types_json = read_json_file_from_config(json, "scratchpad_types_file"); + for (auto& kv : scratchpad_types_json.items()) { + config.scratchpad_types_by_object[kv.key()] = kv.value().get(); + } + } + auto anon_func_json = read_json_file_from_config(json, "anonymous_function_types_file"); if (json.contains("anonymous_function_types_merge_file")) { anon_func_json.update(read_json_file_from_config(json, "anonymous_function_types_merge_file")); diff --git a/decompiler/config.h b/decompiler/config.h index 20fddebce6..be033a27fa 100644 --- a/decompiler/config.h +++ b/decompiler/config.h @@ -167,6 +167,7 @@ struct Config { std::unordered_map> label_types; std::unordered_map> stack_structure_hints_by_function; + std::unordered_map scratchpad_types_by_object; std::unordered_map object_patches; std::unordered_map bad_format_strings; diff --git a/decompiler/config/jak1/all-types.gc b/decompiler/config/jak1/all-types.gc index 307396bbd0..26d3cbd00b 100644 --- a/decompiler/config/jak1/all-types.gc +++ b/decompiler/config/jak1/all-types.gc @@ -13111,9 +13111,9 @@ past the end of the movement step." (_type_ vector) symbol) ;; 9 :type uint64 :bitfield #t (background 0) - (cak-1 1) ;; hit by player - (cak-2 2) ;; usually hit by player - (cak-3 3) ;; hit by others + (hit-by-player 1) ;; hit by player + (usually-hit-by-player 2) ;; usually hit by player + (hit-by-others 3) ;; hit by others (target 4) ;; target (water 5) (powerup 6) @@ -17483,6 +17483,16 @@ its matching records and each allocation bitmap begins with every slot free." (in-base-region 6) ) +(defenum camera-blend-to-type + :type uint64 + (direct 0) + (slave-controlled 1) + (combiner-tracked 2)) + +(defenum external-cam-option + :bitfield #t + (allow-z 0)) + ;; - Types ;; Shared gameplay-camera tuning used for collision movement, input response, @@ -17587,7 +17597,7 @@ its matching records and each allocation bitmap begins with every slot free." (prune-shallow-points! "Remove interior points whose length-weighted bend is below budget." (_type_ float) none) ;; 16 (add-point! "Append new-pos when it is at least min-dist from the tail, - optionally pruning the trail to obtain a free slot." (_type_ vector float float symbol) int) ;; 17 ; - return value is actually none but they do a manual `return` + optionally pruning the trail to obtain a free slot." (_type_ vector meters meters symbol) int) ;; 17 ; - return value is actually none but they do a manual `return` (accumulate-sample! "Walk arc-len forward from sampler and add the sampled trail position to out-pos." (_type_ float vector tracking-spline-sampler) vector) ;; 18 (sample-point! "Clear out-pos and sample the trail arc-len forward from @@ -17598,9 +17608,9 @@ its matching records and each allocation bitmap begins with every slot free." (follow-update! "Advance the trail follower toward pos using accel and max-speed. Average 64 evenly spaced samples over the adaptive sample window, apply the trail-direction correction, and return the smoothed - position." (_type_ vector float float) vector) ;; 21 + position." (_type_ vector meters meters) vector) ;; 21 (trim-to-length! "Drop the oldest trail segments until its live length is - no greater than max-len." (_type_ float) none) ;; 22 + no greater than max-len." (_type_ meters) none) ;; 22 (debug-draw "Draw the used breadcrumb chain, the endpoints of the current sampling window, and the trail correction applied to the last output." (_type_) none) ;; 23 @@ -17700,9 +17710,9 @@ its matching records and each allocation bitmap begins with every slot free." ;; slave alive together while the combiner transitions between them. (deftype camera-slave (process) ((trans vector :score 999 :inline :offset-assert 112) - (fov float :offset-assert 128) - (fov0 float :offset-assert 132) - (fov1 float :offset-assert 136) + (fov degrees :offset-assert 128) + (fov0 degrees :offset-assert 132) + (fov1 degrees :offset-assert 136) (fov-index cam-index :inline :offset-assert 144) (tracking cam-rotation-tracker :inline :offset-assert 192) (view-off-param float :offset-assert 400) @@ -17719,7 +17729,7 @@ its matching records and each allocation bitmap begins with every slot free." (max-angle-offset float :offset-assert 2192) (max-angle-curr float :offset-assert 2196) (options cam-slave-options :offset-assert 2200) - (cam-entity entity :offset-assert 2204) ; not totally confirmed yet, could be entity-actor + (cam-entity entity-camera :offset-assert 2204) ; not totally confirmed yet, could be entity-actor (velocity vector :inline :offset-assert 2208) (desired-pos vector :inline :offset-assert 2224) (time-dist-too-far uint32 :offset-assert 2240) @@ -17742,8 +17752,8 @@ its matching records and each allocation bitmap begins with every slot free." (spline-follow-dist float :offset-assert 2484) (change-event-from (pointer process-drawable) :offset-assert 2488) ;; mistycannon (enter-has-run symbol :offset-assert 2492) - (blend-from-type uint64 :offset-assert 2496) - (blend-to-type uint64 :offset-assert 2504) + (blend-from-type camera-blend-to-type :offset-assert 2496) + (blend-to-type camera-blend-to-type :offset-assert 2504) (have-phony-joystick basic :offset-assert 2512) (phony-joystick-x float :offset-assert 2516) (phony-joystick-y float :offset-assert 2520) @@ -17791,7 +17801,7 @@ its matching records and each allocation bitmap begins with every slot free." ((master-options cam-master-options :offset-assert 112) (num-slaves int32 :offset-assert 116) (slave (pointer camera-slave) 2 :offset-assert 120) - (slave-options uint32 :offset-assert 128) + (slave-options cam-slave-options :offset-assert 128) (view-off-param-save float :offset-assert 132) (changer uint32 :offset-assert 136) (cam-entity entity :offset-assert 140) ; not totally confirmed yet @@ -24238,10 +24248,8 @@ stopwatches for the next frame." ;; - Functions -(define-extern camera-bounding-box-draw (function bounding-box basic rgba none)) (define-extern collide-planes-test0 (function vector float float vector vector bounding-box vector float)) (define-extern collide-planes-test1 (function vector float vector (inline-array vector) vector float)) -(define-extern camera-cross (function vector vector vector vector4w meters basic)) (define-extern collide-planes-intersect (function vector (inline-array bounding-box) vector float)) (define-extern collide-planes (function (inline-array vector) int vector (inline-array vector) symbol)) @@ -24582,22 +24590,21 @@ optional bottom depth. Enable ordinary water particles after loading the data." optionally smooths that offset. The long-look mode uses a larger speed- and heading-dependent lead with a quartic transition." (function cam-rotation-tracker vector symbol vector)) (define-extern slave-set-rotation! "Build tracker's inverse camera rotation from its follow point, - optional point of interest and tilt; keep the target in frame, optionally blend toward the new + optional point of interest and tilt; keep the target in frame, optionally blend toward the new orientation, and remove roll." - (function cam-rotation-tracker vector cam-slave-options float symbol none)) + (function cam-rotation-tracker vector cam-slave-options float symbol int)) (define-extern camera-slave-debug (function camera-slave none)) -(define-extern camera-line-rel-len (function vector vector float vector4w none)) +(define-extern camera-line-rel-len (function vector vector meters vector4w none)) (define-extern cam-slave-get-flags "Read property's base flag word, set bits from property-on, - clear bits from property-off, and return the combined value." (function entity symbol uint128)) + clear bits from property-off, and return the combined value." (function entity symbol cam-slave-options)) (define-extern cam-slave-get-vector-with-offset "Read property from actor, using its live translation or rotation for the matching property names, add an optional property-offset vector, - and write out. Return true when a value was available." (function entity-actor vector symbol symbol)) + and write out. Return true when a value was available." (function entity-camera vector symbol symbol)) (define-extern cam-slave-get-fov "Read fov plus fov-offset from entity. A zero base fov uses the 64-degree default before applying the offset." (function entity float)) (define-extern cam-slave-get-interp-time "Read interpTime plus interpTime-offset from entity and return zero when the combined duration is at most one millisecond." (function entity float)) -(define-extern cam-slave-get-rot "Convert actor's rotation to out-matrix after composing an optional - rot-offset quaternion." (function entity-actor matrix matrix)) +(define-extern cam-slave-get-rot "Get the rotation of the entity, including optional rotation offset." (function entity-camera matrix matrix)) (define-extern cam-state-from-entity "Select the camera state described by entity: circular for a pivot, standoff for align data, spline for a camera path, the base string mode for positive stringMaxLength, or fixed otherwise. Return false for no entity." (function entity state)) @@ -24650,9 +24657,7 @@ optional bottom depth. Enable ordinary water particles after loading the data." (function matrix)) (define-extern camera-angle "Return the camera's horizontal heading from the X and Z components of the world-to-camera right axis." (function float)) -(define-extern camera-teleport-to-entity "Build a unit-scale camera transform from start-entity's - orientation and the position stored in the scale vector of its extra transform, then send it to - the camera master as an immediate teleport." +(define-extern camera-teleport-to-entity "Teleport the camera to the entity position." (function entity-actor none :behavior process)) ;; - Symbols @@ -24700,7 +24705,7 @@ optional bottom depth. Enable ordinary water particles after loading the data." (define-extern master-track-target "Update the camera master's tracked position and target orientation. Handle ordinary and drawable targets, ground-to-air vertical smoothing, edge-grab clearance, water height limits, pitch look-ahead, and the ten-meter target breadcrumb trail." - (function symbol :behavior camera-master)) + (function none :behavior camera-master)) (define-extern master-check-regions "Keep the current camera region while its padded volume still contains the target, otherwise select the first active region whose volume contains it and whose cutout does not. Fall back to the base camera when none qualifies." @@ -24711,10 +24716,10 @@ optional bottom depth. Enable ordinary water particles after loading the data." base camera when necessary." (function object :behavior camera-master)) (define-extern reset-target-tracking "Reset all target tracking state from the player, including position and facing, attack-aware string limits, water state, vertical offsets, and the target - trail." (function symbol :behavior camera-master)) + trail." (function none :behavior camera-master)) (define-extern reset-drawable-tracking "Reset all target tracking state from the selected drawable target and bone, using its bone transform when available and its control transform otherwise." - (function symbol :behavior camera-master)) + (function none :behavior camera-master)) (define-extern master-switch-to-entity "Activate region's camera state and any alternates. Keep the candidate requiring the least change from the current view, then ask the camera master to blend to it using the authored interpolation time." (function entity symbol :behavior camera-master)) @@ -24726,7 +24731,7 @@ optional bottom depth. Enable ordinary water particles after loading the data." entity's convex volume property. Each sample is an array of half-space planes; margin expands the accepted distance beyond every plane." (function vector entity float symbol symbol)) (define-extern master-base-region "Apply region's authored string-camera bounds, point of interest, - cliff height, tilt, and options to the persistent base camera." (function entity float :behavior camera-master)) + cliff height, tilt, and options to the persistent base camera." (function entity-camera float :behavior camera-master)) (define-extern setup-slave-for-hopefull "Prepare candidate's follow point and rotation when its blend-to mode requires tracking, so alternate-camera comparisons use its actual view." (function camera-slave none)) @@ -24861,7 +24866,7 @@ alternate height. Fall back to the camera position when no target exists." (function vector symbol)) (define-extern cam-string-set-position-rel! "Install a relative string-camera offset, reset the desired and smoothed positions, clear velocity, and cancel a pending line-of-sight jump." - (function vector int :behavior camera-slave)) + (function vector none :behavior camera-slave)) (define-extern cam-debug-reset-coll-tri (function none)) ;; not confirmed (define-extern cam-string-follow "Carry the horizontal target-to-camera string with target motion, enforce its current length limits, and adjust field of view and distance for the long-string mode." @@ -24891,7 +24896,6 @@ alternate height. Fall back to the camera position when no target exists." frame, classify it as clockwise, counter-clockwise, or straddling the line, and append its extents to the corresponding result bucket." (function (inline-array collide-cache-tri) vector vector float collide-los-result vector float symbol)) -(define-extern cam-debug-add-los-tri (function (inline-array collide-cache-tri) vector vector none)) (define-extern cam-los-spline-collide "Sphere-cast from a target breadcrumb toward the camera with the tighter line-of-sight radius. Return -1 for a clear segment or the first significant hit fraction." (function vector vector pat-surface float)) @@ -25090,8 +25094,8 @@ debug ranges are generated from those planes when volume marks are enabled." ((volume-type symbol :offset-assert 0) (point-count int16 :offset-assert 4) (normal-count int16 :offset-assert 6) - (first-point (pointer vector) :offset-assert 8) - (first-normal (pointer vector) :offset-assert 12) + (first-point (inline-array vector) :offset-assert 8) + (first-normal (inline-array vector) :offset-assert 12) (num-planes int32 :offset-assert 16) (plane (inline-array plane) :offset-assert 20) ) @@ -25352,7 +25356,7 @@ are both enabled." (_type_) symbol) ;; 11 "Draw the selected camera's frustum, pivot, alignment and interest points, camera and intro splines, index points, and interpolation diagnostics. The selected item blinks off every eight frames." - (function entity-actor basic)) + (function entity-camera basic)) (define-extern cam-layout-entity-volume-info "Draw every reconstructed camera volume as green vol, gray pvol, or red cutoutvol wireframe, blinking the selected volume." @@ -25379,92 +25383,92 @@ are both enabled." (_type_) symbol) ;; 11 (define-extern cam-layout-save-cam-rot "Write the camera's rot-offset quaternion when present, optionally printing the setup rotation and offset." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-cam-trans "Write trans-offset when present. Diagnostic output also shows the setup translation, the added offset, an optional subtracted translation_info level origin, and the resulting Maya position." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-pivot "Write pivot-offset when both the base pivot and its offset are present, optionally printing the base, offset, and combined position." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-align "Write align-offset when both the base alignment point and its offset are present, optionally printing the base, offset, and combined position." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-interesting "Write interesting-offset when both the base interest point and its offset are present, optionally printing the base, offset, and combined position." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-fov "Write a nonzero fov-offset in degrees and optionally print the base, offset, combined field of view, and corresponding Maya focal length." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-focalpull "Write a nonzero focalPull-offset in meters and optionally print the base, offset, and combined focal-pull distance." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-flags "Write nonzero camera flag-on and flag-off override masks, optionally printing the base and effective masks." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-introsplinetime "Write a nonzero intro-time-offset in seconds. Diagnostic output treats a zero base value as the one-second default." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-introsplineexitval "Write a nonzero intro-exitValue-offset. Diagnostic output notes that a zero base value uses the default exit value of 0.5." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-interptime "Write a nonzero interpTime-offset in seconds, optionally printing the base, offset, and combined interpolation time." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-splineoffset "Write spline-offset when a camera has no pivot and carries an explicit spline offset." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-spline-follow-dist-offset "Write spline-follow-dist-offset when a camera has no pivot and carries the distance offset." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-campointsoffset "Write campoints-offset when present, optionally printing the three meter components." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-tiltAdjust "Write a nonzero tiltAdjust-offset in degrees, optionally printing the base, offset, and combined adjustment." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-stringMinLength "Write a nonzero stringMinLength-offset in meters, optionally printing the base, offset, and combined value." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-stringMaxLength "Write a nonzero stringMaxLength-offset in meters, optionally printing the base, offset, and combined value." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-stringMinHeight "Write a nonzero stringMinHeight-offset in meters, optionally printing the base, offset, and combined value." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-stringMaxHeight "Write a nonzero stringMaxHeight-offset in meters, optionally printing the base, offset, and combined value." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-stringCliffHeight "Write a nonzero stringCliffHeight-offset in meters, optionally printing the base, offset, and combined value." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-maxAngle "Write a nonzero maxAngle-offset in degrees, optionally printing the base, offset, and combined angle." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-campoints-flags "Write nonzero campoints-flags-on and campoints-flags-off override masks, optionally printing the base and effective masks." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-layout-save-focalpull-flags "Write nonzero focalpull-flags-on and focalpull-flags-off override masks, optionally printing the base and effective masks." - (function symbol string entity-actor string)) + (function symbol string entity-actor none)) (define-extern cam-index-options->string (function cam-index-options object string)) (define-extern cam-slave-options->string (function cam-slave-options object string)) (define-extern fov->maya @@ -25486,7 +25490,7 @@ are both enabled." (_type_) symbol) ;; 11 (define-extern camera-fov-frame (function matrix vector float float float vector4w none)) (define-extern interp-test "Draw ten segments sampled from interpolator, then draw and print the current debug-t sample." - (function (function vector vector vector float vector float none) interp-test-info basic)) + (function (function vector vector vector float vector float none) interp-test-info none)) (define-extern v-slrp! "Spherically interpolate between from and to at clamped fraction t and write dst. This form derives the angle from asin of the normalized cross-product length and does not handle parallel or @@ -25495,9 +25499,8 @@ are both enabled." (_type_) symbol) ;; 11 (define-extern interp-test-deg "Draw ten eighteen-degree samples from an angle-based interpolator, then draw and print the current debug-t sample over 180 degrees." - (function (function vector vector vector vector float none) interp-test-info basic)) + (function (function vector vector vector vector float none) interp-test-info none)) (define-extern camera-line-setup (function vector4w none)) -(define-extern camera-line-draw (function vector vector symbol)) (define-extern cam-layout-intersect-dist "Return the signed distance along direction from point to plane, or a large sentinel when the direction is nearly parallel to the plane." @@ -25634,7 +25637,7 @@ are both enabled." (_type_) symbol) ;; 11 ((linevec4w vector4w 2 :inline :offset-assert 0) (color vector :inline :offset-assert 32) (plotvec vector4w 2 :inline :offset-assert 48) - (linevec vector4w 2 :inline :offset-assert 80) + (linevec vector 2 :inline :offset-assert 80) (rel-vec vector :inline :offset-assert 112) (sphere-v-start vector :inline :offset-assert 128) (sphere-v-end vector :inline :offset-assert 144) @@ -25686,7 +25689,7 @@ are both enabled." (_type_) symbol) ;; 11 ) (deftype cam-collision-record-array (inline-array-class) - ((data cam-collision-record :dynamic :offset-assert 16) + ((data cam-collision-record :dynamic :inline :offset-assert 16) ) :method-count-assert 9 :size-assert #x10 @@ -25729,7 +25732,7 @@ are both enabled." (_type_) symbol) ;; 11 "Transform and draw one world-space line using the color selected by camera-line-setup. Reject either endpoint whose transformed depth is past the debug renderer's unsigned cutoff." - (function vector vector symbol)) + (function vector vector none)) (define-extern camera-line "Draw one world-space line in color." (function vector vector vector4w none)) @@ -25741,49 +25744,47 @@ are both enabled." (_type_) symbol) ;; 11 (function vector vector float vector4w none)) (define-extern camera-sphere "Draw a sphere as a ten-by-ten latitude/longitude wireframe." - (function vector float vector none)) + (function vector meters vector4w none)) (define-extern camera-cross "Draw three perpendicular diameter lines through center. The first axis is axis-a; the other two are formed by successive crosses with axis-b and axis-a. half-length is the distance from center to each endpoint." - (function vector vector vector vector4w meters basic)) + (function vector vector vector vector4w meters none)) (define-extern camera-bounding-box-draw - "Draw the twelve edges of bounds in the original fixed gray. The two trailing - arguments are retained for compatibility but are not read." - (function bounding-box basic rgba none)) + "Draw the twelve edges of bounds in the original fixed gray." + (function bounding-box none)) (define-extern cam-debug-reset-coll-tri "Clear the per-frame camera collision and line-of-sight triangle lists." (function none)) (define-extern cam-debug-add-los-tri "Save the first collision-cache triangle, intersection, and color in the line-of-sight debug list. The list holds at most 460 entries." - (function (inline-array collide-cache-tri) vector vector none)) + (function (inline-array collide-cache-tri) vector vector4w none)) (define-extern cam-debug-add-coll-tri "Copy a camera debug triangle with a new intersection and color into the collision debug list. The list holds at most 460 entries." - (function cam-debug-tri vector cam-debug-tri none)) + (function cam-debug-tri vector vector4w none)) (define-extern cam-debug-draw-tris "Draw the recorded camera line-of-sight and collision triangles selected by the corresponding display flags, with a cross at each intersection." (function symbol)) (define-extern camera-fov-draw - "Draw one side of a camera frustum from two direction-vector addresses, + "Draw one side of a camera frustum from two direction vectors, origin, near distance, far distance, and color." - (function int int vector float float vector4w symbol)) + (function vector vector vector meters meters vector4w none)) (define-extern camera-fov-frame "Draw a camera frustum from its inverse rotation, origin, half field of view, vertical scale, horizontal scale, and color. The near and far outlines are 4096 and 20480 GOAL units from the origin." (function matrix vector float float float vector4w none)) (define-extern debug-euler - "Interpret the matrix packed from scratch.sphere-vec.w onward, print its Euler - conversion and reconstruction, and report whether any component differs by - more than 0.001." - (function cam-dbg-scratch object)) + "Convert the camera rotation matrix to euler angles and back and report whether + any component differs by more than 0.001." + (function camera-slave none)) (define-extern bike-cam-limit "Return a cosine camera limit that rises from zero to one as the nonnegative scaled input approaches 8192, and remains one beyond that range." - (function float float)) + (function meters float)) (define-extern camera-slave-debug "Draw the active camera-slave frustum, tracking basis and follow point, target body spheres, spline trail, and state-specific string, circular, or diff --git a/decompiler/config/jak1/demacro.jsonc b/decompiler/config/jak1/demacro.jsonc index 61f87e6741..5bb2a30083 100644 --- a/decompiler/config/jak1/demacro.jsonc +++ b/decompiler/config/jak1/demacro.jsonc @@ -136,6 +136,25 @@ {"type": "rgba"}, {"type": "uint128"}, {"type": "process-drawable"} + ], + "gs-reg-list": [ + {"count": "0", "fields": "", "regs": ""}, + {"count": "1", "fields": ":regs0 (gif-reg-id $reg0)", "regs": "$reg0"}, + {"count": "2", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1)", "regs": "$reg0 $reg1"}, + {"count": "3", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2)", "regs": "$reg0 $reg1 $reg2"}, + {"count": "4", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3)", "regs": "$reg0 $reg1 $reg2 $reg3"}, + {"count": "5", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4"}, + {"count": "6", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4) :regs5 (gif-reg-id $reg5)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4 $reg5"}, + {"count": "7", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4) :regs5 (gif-reg-id $reg5) :regs6 (gif-reg-id $reg6)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4 $reg5 $reg6"}, + {"count": "8", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4) :regs5 (gif-reg-id $reg5) :regs6 (gif-reg-id $reg6) :regs7 (gif-reg-id $reg7)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4 $reg5 $reg6 $reg7"}, + {"count": "9", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4) :regs5 (gif-reg-id $reg5) :regs6 (gif-reg-id $reg6) :regs7 (gif-reg-id $reg7) :regs8 (gif-reg-id $reg8)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4 $reg5 $reg6 $reg7 $reg8"}, + {"count": "10", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4) :regs5 (gif-reg-id $reg5) :regs6 (gif-reg-id $reg6) :regs7 (gif-reg-id $reg7) :regs8 (gif-reg-id $reg8) :regs9 (gif-reg-id $reg9)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4 $reg5 $reg6 $reg7 $reg8 $reg9"}, + {"count": "11", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4) :regs5 (gif-reg-id $reg5) :regs6 (gif-reg-id $reg6) :regs7 (gif-reg-id $reg7) :regs8 (gif-reg-id $reg8) :regs9 (gif-reg-id $reg9) :regs10 (gif-reg-id $reg10)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4 $reg5 $reg6 $reg7 $reg8 $reg9 $reg10"}, + {"count": "12", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4) :regs5 (gif-reg-id $reg5) :regs6 (gif-reg-id $reg6) :regs7 (gif-reg-id $reg7) :regs8 (gif-reg-id $reg8) :regs9 (gif-reg-id $reg9) :regs10 (gif-reg-id $reg10) :regs11 (gif-reg-id $reg11)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4 $reg5 $reg6 $reg7 $reg8 $reg9 $reg10 $reg11"}, + {"count": "13", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4) :regs5 (gif-reg-id $reg5) :regs6 (gif-reg-id $reg6) :regs7 (gif-reg-id $reg7) :regs8 (gif-reg-id $reg8) :regs9 (gif-reg-id $reg9) :regs10 (gif-reg-id $reg10) :regs11 (gif-reg-id $reg11) :regs12 (gif-reg-id $reg12)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4 $reg5 $reg6 $reg7 $reg8 $reg9 $reg10 $reg11 $reg12"}, + {"count": "14", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4) :regs5 (gif-reg-id $reg5) :regs6 (gif-reg-id $reg6) :regs7 (gif-reg-id $reg7) :regs8 (gif-reg-id $reg8) :regs9 (gif-reg-id $reg9) :regs10 (gif-reg-id $reg10) :regs11 (gif-reg-id $reg11) :regs12 (gif-reg-id $reg12) :regs13 (gif-reg-id $reg13)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4 $reg5 $reg6 $reg7 $reg8 $reg9 $reg10 $reg11 $reg12 $reg13"}, + {"count": "15", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4) :regs5 (gif-reg-id $reg5) :regs6 (gif-reg-id $reg6) :regs7 (gif-reg-id $reg7) :regs8 (gif-reg-id $reg8) :regs9 (gif-reg-id $reg9) :regs10 (gif-reg-id $reg10) :regs11 (gif-reg-id $reg11) :regs12 (gif-reg-id $reg12) :regs13 (gif-reg-id $reg13) :regs14 (gif-reg-id $reg14)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4 $reg5 $reg6 $reg7 $reg8 $reg9 $reg10 $reg11 $reg12 $reg13 $reg14"}, + {"count": "16", "fields": ":regs0 (gif-reg-id $reg0) :regs1 (gif-reg-id $reg1) :regs2 (gif-reg-id $reg2) :regs3 (gif-reg-id $reg3) :regs4 (gif-reg-id $reg4) :regs5 (gif-reg-id $reg5) :regs6 (gif-reg-id $reg6) :regs7 (gif-reg-id $reg7) :regs8 (gif-reg-id $reg8) :regs9 (gif-reg-id $reg9) :regs10 (gif-reg-id $reg10) :regs11 (gif-reg-id $reg11) :regs12 (gif-reg-id $reg12) :regs13 (gif-reg-id $reg13) :regs14 (gif-reg-id $reg14) :regs15 (gif-reg-id $reg15)", "regs": "$reg0 $reg1 $reg2 $reg3 $reg4 $reg5 $reg6 $reg7 $reg8 $reg9 $reg10 $reg11 $reg12 $reg13 $reg14 $reg15"} ] }, "rules": [ @@ -144,11 +163,19 @@ "match": "(-> *display* frames (-> *display* on-screen) frame)", "rewrite": "(current-frame)" }, + { + // Dereference expansion flattens accesses through current-frame into the same -> form. + // Preserve any fields after frame while recovering the macro at the head of the traversal. + "name": "current-frame-with-fields", + "match": "(-> *display* frames (-> *display* on-screen) frame $field $*remaining-fields)", + "rewrite": "(-> (current-frame) $field $*remaining-fields)" + }, { // Let insertion can merge consecutive traversals which reuse the same node and cached-next // locals. Peel off the first traversal and rebuild the remaining let; repeated rule passes // then recover every traversal in the chain. "name": "iterate-engine-connections-merged-let", + "capture_types": {"node": "connection"}, "match": "(let (($node (-> $engine alive-list next0))) $engine (let (($next (-> $node next0))) (while (!= $node (-> $engine alive-list-end)) $*body (set! $node $next) $engine (set! $next (-> $next next0))) (set! $node (-> $next-engine alive-list next0)) $next-engine (set! $next (-> $node next0)) $*remaining))", "rewrite": [ @@ -159,6 +186,7 @@ { // Function-scoped locals produce assignments instead of let bindings around the traversal. "name": "iterate-engine-connections-set-locals", + "capture_types": {"node": "connection"}, "match": [ "(set! $node (-> $engine alive-list next0))", "$engine", @@ -170,18 +198,21 @@ { // Constant engine globals sometimes survive as no-op forms around the cached-next traversal. "name": "iterate-engine-connections-with-engine-forms", + "capture_types": {"node": "connection"}, "match": "(let (($node (-> $engine alive-list next0))) $engine (let (($next (-> $node next0))) (while (!= $node (-> $engine alive-list-end)) $*body (set! $node $next) $engine (set! $next (-> $next next0)))))", "rewrite": "(iterate-engine-connections ($node $engine) $*body)" }, { "name": "iterate-engine-connections-nested-let", + "capture_types": {"node": "connection"}, "match": "(let (($node (-> $engine alive-list next0))) (let (($next (-> $node next0))) (while (!= $node (-> $engine alive-list-end)) $*body (set! $node $next) (set! $next (-> $next next0)))))", "rewrite": "(iterate-engine-connections ($node $engine) $*body)" }, { "name": "iterate-engine-connections-let-star", + "capture_types": {"node": "connection"}, "match": "(let* (($node (-> $engine alive-list next0)) ($next (-> $node next0))) (while (!= $node (-> $engine alive-list-end)) $*body (set! $node $next) (set! $next (-> $next next0))))", "rewrite": "(iterate-engine-connections ($node $engine) $*body)" @@ -190,10 +221,29 @@ // Preserve unrelated bindings which let insertion combined with the traversal's node local. // Exact single-binding lets are handled above so this fallback never emits an empty let. "name": "iterate-engine-connections-merged-bindings", + "capture_types": {"node": "connection"}, "match": "(let ($*before ($node (-> $engine alive-list next0)) $*after) $engine (let (($next (-> $node next0))) (while (!= $node (-> $engine alive-list-end)) $*body (set! $node $next) $engine (set! $next (-> $next next0)))))", "rewrite": "(let ($*before $*after) (iterate-engine-connections ($node $engine) $*body))" }, + { + // A nontrivial property-name expression forces the res-lump macro to cache both its method + // and owner before evaluating the name. Recover the macro after let insertion has reunited + // the setup and call. + "name": "res-lump-struct-computed-name-with-owner", + "match": + "(let ($*before ($getter (method-of-type res-lump get-property-struct)) ($owner $object)) (format (clear *res-key-string*) \"~S~S\" $name-part0 $name-part1) (let (($result (the-as $result-type ($getter $owner (string->symbol *res-key-string*) 'interp -1000000000.0 #f (the-as (pointer res-tag) #f) *res-static-buf*))) $*inner-after) $*body))", + "rewrite": + "(let ($*before ($result (res-lump-struct $object (string->symbol (format (clear *res-key-string*) \"~S~S\" $name-part0 $name-part1)) $result-type)) $*inner-after) $*body)" + }, + { + // Same expansion when the owner expression is already safe to use at the lookup site. + "name": "res-lump-struct-computed-name", + "match": + "(let ($*before ($getter (method-of-type res-lump get-property-struct))) (format (clear *res-key-string*) \"~S~S\" $name-part0 $name-part1) (let (($result (the-as $result-type ($getter $object (string->symbol *res-key-string*) 'interp -1000000000.0 #f (the-as (pointer res-tag) #f) *res-static-buf*))) $*inner-after) $*body))", + "rewrite": + "(let ($*before ($result (res-lump-struct $object (string->symbol (format (clear *res-key-string*) \"~S~S\" $name-part0 $name-part1)) $result-type)) $*inner-after) $*body)" + }, { "name": "with-dma-buffer-add-bucket", "match": @@ -215,6 +265,74 @@ "rewrite": "(with-dma-buffer-add-bucket (($buf $buffer) $bucket-id) :bucket-group $bucket-group $*body)" }, + { + // This is the macro's default bucket group, so spelling out the keyword adds no information. + "name": "with-dma-buffer-add-bucket-default-group", + "match": + "(with-dma-buffer-add-bucket (($buf $buffer) $bucket-id) :bucket-group (-> (current-frame) bucket-group) $*body)", + "rewrite": + "(with-dma-buffer-add-bucket (($buf $buffer) $bucket-id) $*body)" + }, + { + "name": "dma-buffer-add-cnt-vif2-object-packet", + "match": + "(let* (($alias $buf) ($packet (the-as object (-> $alias base)))) (set! (-> (the-as dma-packet $packet) dma) (new 'static 'dma-tag :id (dma-tag-id cnt))) (set! (-> (the-as dma-packet $packet) vif0) $vif0) (set! (-> (the-as dma-packet $packet) vif1) $vif1) (set! (-> $alias base) (&+ (the-as pointer $packet) 16)))", + "rewrite": + "(dma-buffer-add-cnt-vif2 $buf 0 $vif0 $vif1)" + }, + { + "name": "dma-buffer-add-cnt-vif2-typed-packet", + "match": + "(let* (($alias $buf) ($packet (the-as dma-packet (-> $alias base)))) (set! (-> $packet dma) (new 'static 'dma-tag :id (dma-tag-id cnt))) (set! (-> $packet vif0) $vif0) (set! (-> $packet vif1) $vif1) (set! (-> $alias base) (&+ (the-as pointer $packet) 16)))", + "rewrite": + "(dma-buffer-add-cnt-vif2 $buf 0 $vif0 $vif1)" + }, + { + "name": "dma-buffer-add-gif-tag-object-packet", + "match": + "(let* (($alias $buf) ($packet (the-as object (-> $alias base)))) (set! (-> (the-as gs-gif-tag $packet) tag) $tag) (set! (-> (the-as gs-gif-tag $packet) regs) $regs) (set! (-> $alias base) (&+ (the-as pointer $packet) 16)))", + "rewrite": + "(dma-buffer-add-gif-tag $buf $tag $regs)" + }, + { + "name": "dma-buffer-add-gif-tag-typed-packet", + "match": + "(let* (($alias $buf) ($packet (the-as gs-gif-tag (-> $alias base)))) (set! (-> $packet tag) $tag) (set! (-> $packet regs) $regs) (set! (-> $alias base) (&+ (the-as pointer $packet) 16)))", + "rewrite": + "(dma-buffer-add-gif-tag $buf $tag $regs)" + }, + { + "name": "dma-buffer-add-two-uint128", + "match": + "(let* (($alias $buf) ($data (-> $alias base))) (set! (-> (the-as (pointer uint128) $data) 0) $value0) (set! (-> (the-as (pointer uint128) $data) 1) $value1) (set! (-> $alias base) (&+ $data 32)))", + "rewrite": + "(dma-buffer-add-uint128 $buf $value0 $value1)" + }, + { + // Four sequential eight-byte stores are the common expansion of dma-buffer-add-uint64. + // Their individual pointer types reflect the value types; the 32-byte advance fixes the width. + "name": "dma-buffer-add-four-uint64", + "match": + "(let* (($alias $buf) ($data (-> $alias base))) (set! (-> (the-as (pointer $type0) $data) 0) $value0) (set! (-> (the-as (pointer $type1) $data) 1) $value1) (set! (-> (the-as (pointer $type2) $data) 2) $value2) (set! (-> (the-as (pointer $type3) $data) 3) $value3) (set! (-> $alias base) (&+ $data 32)))", + "rewrite": + "(dma-buffer-add-uint64 $buf $value0 $value1 $value2 $value3)" + }, + { + // Variant emitted when the saved tag pointer has only object-level type information. + "name": "with-cnt-vif-block-object-tag", + "match": + "(let (($start (the-as object (-> $buf base)))) (dma-buffer-add-cnt-vif2 $buf 0 (new 'static 'vif-tag) (new 'static 'vif-tag :cmd (vif-cmd direct) :msk #x1)) $*body (let (($qwc (/ (the-as int (+ (- -16 (the-as int $start)) (the-as int (-> $buf base)))) 16))) (cond ((nonzero? $qwc) (logior! (-> (the-as dma-packet $start) dma) (shr (shl $qwc 48) 48)) (logior! (-> (the-as (pointer uint64) $start) 1) (shl (shr (shl $qwc 48) 48) 32))) (else (set! (-> $buf base) (the-as (pointer uint64) $start))))))", + "rewrite": + "(with-cnt-vif-block ($buf) $*body)" + }, + { + // Let insertion may merge the saved tag pointer into a caller's surrounding let*. + "name": "with-cnt-vif-block-merged-bindings", + "match": + "(let* ($*before ($start (the-as dma-packet (-> $buf base))) $*after) (dma-buffer-add-cnt-vif2 $buf 0 (new 'static 'vif-tag) (new 'static 'vif-tag :cmd (vif-cmd direct) :msk #x1)) $*body (let (($qwc (/ (the-as int (+ (- -16 (the-as int $start)) (the-as int (-> $buf base)))) 16))) (cond ((nonzero? $qwc) (logior! (-> (the-as (pointer dma-tag) $start) 0) (new 'static 'dma-tag :qwc $qwc)) (logior! (-> (the-as (pointer dma-tag) $start) 1) (shl (shr (shl $qwc 48) 48) 32))) (else (set! (-> $buf base) (the-as pointer $start))))) $*tail)", + "rewrite": + "(let* ($*before $*after) (with-cnt-vif-block ($buf) $*body) $*tail)" + }, { // The font setters are methods rather than macros, but Jak 1 inlines them. The enum // constructors make these two field stores structurally unambiguous without type data. @@ -367,6 +485,12 @@ "(mem-usage-add! $usage generic-shrub-data $stream-count $stream-bytes)" ] }, + { + "name": "gs-reg-list-{{count}}", + "for_each": "gs-reg-list", + "match": "(new 'static 'gif-tag-regs {{fields}})", + "rewrite": "(gs-reg-list {{regs}})" + }, { "name": "scratchpad-object-direct-{{type}}", "for_each": "scratchpad-object-type", diff --git a/decompiler/config/jak1/jak1_config.jsonc b/decompiler/config/jak1/jak1_config.jsonc index 2d5a285cfa..6288e2e78e 100644 --- a/decompiler/config/jak1/jak1_config.jsonc +++ b/decompiler/config/jak1/jak1_config.jsonc @@ -92,6 +92,7 @@ //////////////////////////// "type_casts_file": "decompiler/config/jak1/ntsc_v1/type_casts.jsonc", + "scratchpad_types_file": "decompiler/config/jak1/ntsc_v1/scratchpad_types.jsonc", "anonymous_function_types_file": "decompiler/config/jak1/ntsc_v1/anonymous_function_types.jsonc", "var_names_file": "decompiler/config/jak1/ntsc_v1/var_names.jsonc", "label_types_file": "decompiler/config/jak1/ntsc_v1/label_types.jsonc", diff --git a/decompiler/config/jak1/ntsc_v1/scratchpad_types.jsonc b/decompiler/config/jak1/ntsc_v1/scratchpad_types.jsonc new file mode 100644 index 0000000000..2ec46b66a3 --- /dev/null +++ b/decompiler/config/jak1/ntsc_v1/scratchpad_types.jsonc @@ -0,0 +1,3 @@ +{ + "cam-debug": "cam-dbg-scratch" +} diff --git a/decompiler/config/jak1/ntsc_v1/type_casts.jsonc b/decompiler/config/jak1/ntsc_v1/type_casts.jsonc index 181f1986d2..24c67e9921 100644 --- a/decompiler/config/jak1/ntsc_v1/type_casts.jsonc +++ b/decompiler/config/jak1/ntsc_v1/type_casts.jsonc @@ -552,7 +552,7 @@ [[35, 37], "a2", "qword"] ], "(method 3 sparticle-cpuinfo)": [[106, "f0", "float"]], - "camera-teleport-to-entity": [[9, "a0", "transform"]], + // "camera-teleport-to-entity": [[9, "a0", "transform"]], "add-debug-sphere-from-table": [[[9, 18], "s1", "(inline-array vector)"]], "(method 14 actor-link-info)": [[5, "v1", "entity-links"]], "(method 15 actor-link-info)": [[5, "v1", "entity-links"]], @@ -2961,28 +2961,7 @@ [9, "v1", "float"], [16, "v1", "float"] ], - "camera-fov-frame": [ - [87, "a0", "vector"], - [128, "a0", "vector"], - [169, "a0", "vector"] - ], - "camera-sphere": [[[39, 46], "v1", "cam-dbg-scratch"]], - "camera-line-draw": [ - [34, "a0", "cam-dbg-scratch"], - [42, "a0", "cam-dbg-scratch"] - ], - "camera-plot-float-func": [ - [54, "v1", "cam-dbg-scratch"], - [62, "a0", "cam-dbg-scratch"], - [66, "a0", "cam-dbg-scratch"], - [103, "v1", "cam-dbg-scratch"], - [240, "v1", "cam-dbg-scratch"] - ], "cam-line-dma": [ - [32, "t0", "vector"], - [36, "t0", "vector"], - [45, "t0", "vector"], - [50, "t0", "vector"], [[12, 16], "a3", "dma-packet"], [[22, 25], "a3", "gs-gif-tag"], [[33, 38], "a3", "(pointer uint128)"], @@ -3093,9 +3072,7 @@ ], "cam-layout-print": [[[21, 24], "v1", "dma-packet"]], "cam-layout-entity-volume-info": [ - [58, "s4", "vector"], - [59, "s4", "vector"], - [61, "s4", "(inline-array plane-volume)"] + [[54, 72], "s4", "(inline-array vector)"] ], "cam-layout-entity-volume-info-create": [ ["_stack_", 16, "res-tag"], @@ -3104,9 +3081,7 @@ "clmf-cam-string": [["_stack_", 16, "res-tag"]], "in-cam-entity-volume?": [ ["_stack_", 16, "res-tag"], - [22, "v1", "(inline-array vector)"], - [29, "v1", "(inline-array vector)"], - [34, "v1", "(inline-array vector)"] + [[18,35], "v1", "(inline-array vector)"] ], "fisher-fish-move": [ [9, "v1", "fisher"], diff --git a/decompiler/config/jak1/ntsc_v1/var_names.jsonc b/decompiler/config/jak1/ntsc_v1/var_names.jsonc index 0baac72074..b9f5c81a00 100644 --- a/decompiler/config/jak1/ntsc_v1/var_names.jsonc +++ b/decompiler/config/jak1/ntsc_v1/var_names.jsonc @@ -12351,20 +12351,20 @@ }, "cam-line-dma": { "vars": { - "v1-3": "debug-buffer", - "a2-0": "packet-start", - "a0-3": "cnt-tag", - "a1-0": "write-buffer", - "a3-0": "dma-header", - "a1-1": "write-buffer", - "a3-2": "gif-header", - "a1-2": "write-buffer", - "a3-4": "vertex-packet", - "a3-6": "write-buffer", - "a1-3": "vertex-packet", - "a3-10": "qwc", - "a3-16": "next-tag", - "a0-4": "next-header" + "v1-3": "dma-buf" + // "a2-0": "packet-start", + // "a0-3": "cnt-tag", + // "a1-0": "write-buffer", + // "a3-0": "dma-header", + // "a1-1": "write-buffer", + // "a3-2": "gif-header", + // "a1-2": "write-buffer", + // "a3-4": "vertex-packet", + // "a3-6": "write-buffer", + // "a1-3": "vertex-packet", + // "a3-10": "qwc", + // "a3-16": "next-tag", + // "a0-4": "next-header" } }, "camera-line2d": { @@ -12424,7 +12424,7 @@ } }, "cam-debug-add-coll-tri": { - "args": ["triangle", "intersection", "color-data"], + "args": ["triangle", "intersection", "color"], "vars": { "v1-3": "saved-triangle" } @@ -12432,13 +12432,11 @@ "cam-debug-draw-tris": { "vars": { "gp-1": "i", - "gp-2": "i", - "v1-7": "color-quad", - "v1-34": "color-quad" + "gp-2": "i" } }, "camera-fov-draw": { - "args": ["direction-a-address", "direction-b-address", "origin", "near-distance", "far-distance", "color"] + "args": ["direction-a", "direction-b", "origin", "near-distance", "far-distance", "color"] }, "camera-fov-frame": { "args": ["inverse-rotation", "origin", "half-fov", "vertical-scale", "horizontal-scale", "color"] @@ -12485,7 +12483,7 @@ "s4-1": "line-start", "s5-2": "pivot-axis-end", "s5-3": "path-data", - "s4-2": "path-offset", + "s4-2": ["path-offset", "vector"], "s3-0": "previous-point", "s2-0": "current-point", "gp-1": "authored-line", @@ -12500,14 +12498,14 @@ "gp-0": "axis-origin", "a0-1": "rotation", "a1-2": "axis-destination", - "a1-5": "axis-destination", - "a1-8": "axis-destination", + "a1-5": "axis-destination2", + "a1-8": "axis-destination3", "v1-4": "origin-copy", - "v1-5": "origin-copy", - "v1-6": "origin-copy", + "v1-5": "origin-copy2", + "v1-6": "origin-copy3", "a0-3": "axis-offset", - "a0-5": "axis-offset", - "a0-7": "axis-offset" + "a0-5": "axis-offset2", + "a0-7": "axis-offset3" } }, "cam-collision-record-step": {