mirror of
https://github.com/open-goal/jak-project
synced 2026-08-08 10:34:30 -04:00
decompiler fixes
This commit is contained in:
+95
-37
@@ -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<Node> match;
|
||||
std::vector<Node> rewrite;
|
||||
std::unordered_map<std::string, std::string> 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<std::string> json_string_or_array(const nlohmann::json& value,
|
||||
std::vector<std::string> 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<std::string>());
|
||||
}
|
||||
@@ -164,10 +164,9 @@ std::vector<std::string> 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<std::string, std::string>& substitutions,
|
||||
const std::string& rule_name) {
|
||||
std::string substitute_row_values(std::string text,
|
||||
const std::unordered_map<std::string, std::string>& 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<std::string>(), substitutions, rule.name));
|
||||
}
|
||||
}
|
||||
return rule;
|
||||
}
|
||||
|
||||
@@ -270,8 +283,7 @@ std::vector<Captures> match_children_all(const std::vector<Node>& patterns,
|
||||
size_t input_idx,
|
||||
const Captures& captures) {
|
||||
if (pattern_idx == patterns.size()) {
|
||||
return input_idx == inputs.size() ? std::vector<Captures>{captures}
|
||||
: std::vector<Captures>{};
|
||||
return input_idx == inputs.size() ? std::vector<Captures>{captures} : std::vector<Captures>{};
|
||||
}
|
||||
|
||||
std::string sequence_name;
|
||||
@@ -290,8 +302,7 @@ std::vector<Captures> match_children_all(const std::vector<Node>& 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<Captures> match_children_all(const std::vector<Node>& patterns,
|
||||
std::vector<Captures> 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<Captures> 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>{captures}
|
||||
: std::vector<Captures>{};
|
||||
: std::vector<Captures>{};
|
||||
}
|
||||
auto result = captures;
|
||||
result.single[capture_name] = &input;
|
||||
@@ -331,8 +341,7 @@ std::vector<Captures> match_node_all(const Node& pattern,
|
||||
return {};
|
||||
}
|
||||
if (!pattern.is_list) {
|
||||
return pattern.atom == input.atom ? std::vector<Captures>{captures}
|
||||
: std::vector<Captures>{};
|
||||
return pattern.atom == input.atom ? std::vector<Captures>{captures} : std::vector<Captures>{};
|
||||
}
|
||||
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<std::string, std::string>& capture_types,
|
||||
std::vector<std::pair<const Node*, const Node*>>* 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<std::string, std::string>& capture_types) {
|
||||
if (capture_types.empty()) {
|
||||
return original_text(node, source);
|
||||
}
|
||||
std::vector<std::pair<const Node*, const Node*>> 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<std::string, std::string>& capture_types);
|
||||
|
||||
std::string render_template_children(const std::vector<Node>& nodes,
|
||||
const Captures& captures,
|
||||
const std::string& source) {
|
||||
std::string render_template_children(
|
||||
const std::vector<Node>& nodes,
|
||||
const Captures& captures,
|
||||
const std::string& source,
|
||||
const std::unordered_map<std::string, std::string>& 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<Node>& 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<Node>& nodes,
|
||||
|
||||
std::string render_template(const Node& node,
|
||||
const Captures& captures,
|
||||
const std::string& source) {
|
||||
const std::string& source,
|
||||
const std::unordered_map<std::string, std::string>& 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<CompiledRule> 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<Edit> 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<std::string, std::string> 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<std::string>();
|
||||
}
|
||||
@@ -663,8 +721,8 @@ RuleSet parse_rules(const std::string& contents, const std::string& source_name)
|
||||
entry.at("name").get<std::string>(), 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, {}, ""));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "common/util/FileUtil.h"
|
||||
@@ -11,6 +12,7 @@ struct Rule {
|
||||
std::string name;
|
||||
std::vector<std::string> match;
|
||||
std::vector<std::string> rewrite;
|
||||
std::unordered_map<std::string, std::string> capture_types;
|
||||
};
|
||||
|
||||
struct RuleSet {
|
||||
|
||||
@@ -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<std::string> 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<std::string> 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 {
|
||||
|
||||
@@ -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<FormFormattingConfig>();
|
||||
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<int> 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<std::string, FormFormattingConfig> 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<std::string, FormFormattingConfig> 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<std::string, FormFormattingConfig> 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)},
|
||||
|
||||
@@ -27,6 +27,8 @@ struct FormFormattingConfig {
|
||||
inline_until_index = [](std::vector<std::string> /*curr_lines*/) { return std::nullopt; };
|
||||
bool has_constant_pairs = false;
|
||||
bool prevent_inlining = false; // TODO - duplicate of below
|
||||
std::optional<int> inline_body_width_limit;
|
||||
int inline_body_start_index = 1;
|
||||
std::function<bool(FormFormattingConfig, int num_refs)> should_prevent_inlining =
|
||||
[](FormFormattingConfig config, int /*num_refs*/) { return config.prevent_inlining; };
|
||||
int parent_mutable_extra_indent = 0;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<std::string, s64> 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<ValueType>("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
|
||||
|
||||
@@ -184,6 +184,10 @@ goos::Object SimpleAtom::to_form(const std::vector<DecompilerLabel>& 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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -185,6 +185,11 @@ class Env {
|
||||
|
||||
const std::unordered_map<int, StackTypeCast>& stack_casts() const { return m_stack_typecasts; }
|
||||
|
||||
void set_scratchpad_type(const TypeSpec& type) { m_scratchpad_type = type; }
|
||||
const std::optional<TypeSpec>& 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<std::string> get_art_elt_name(int idx) const;
|
||||
@@ -282,6 +287,8 @@ class Env {
|
||||
|
||||
std::unordered_map<int, std::vector<RegisterTypeCast>> m_typecasts;
|
||||
std::unordered_map<int, StackTypeCast> m_stack_typecasts;
|
||||
std::optional<TypeSpec> m_scratchpad_type;
|
||||
mutable bool m_uses_scratchpad = false;
|
||||
std::vector<StackStructureEntry> m_stack_structures;
|
||||
std::unordered_map<std::string, std::string> m_var_remap;
|
||||
std::unordered_map<std::string, TypeSpec> m_var_retype;
|
||||
|
||||
@@ -117,17 +117,27 @@ FormElement* handle_get_property_data_or_structure(const std::vector<Form*>& 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<Form*>& 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<Form*>& for
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return pool.alloc_element<ResLumpMacroElement>(kind, lump_object, property_name,
|
||||
default_value,
|
||||
return pool.alloc_element<ResLumpMacroElement>(kind, lump_object, property_name, default_value,
|
||||
tag_pointer, time, default_type);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -562,6 +562,109 @@ Form* cast_form(Form* in,
|
||||
return pool.form<CastElement>(new_type, in);
|
||||
}
|
||||
|
||||
std::optional<TypeSpec> try_get_deref_form_type(Form* form,
|
||||
const Env& env,
|
||||
std::optional<size_t> token_count = {}) {
|
||||
auto* deref = form->try_as_element<DerefElement>();
|
||||
if (!deref || deref->is_addr_of()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<TypeSpec> 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<CastElement>()) {
|
||||
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<DerefElement>();
|
||||
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<DerefToken>& 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<DerefElement>();
|
||||
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<DerefToken> prefix_tokens(deref->tokens().begin(),
|
||||
deref->tokens().begin() + keep);
|
||||
if (prefix_tokens.empty()) {
|
||||
return deref->base();
|
||||
}
|
||||
return pool.form<DerefElement>(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<DerefElement>();
|
||||
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<TypeSpec> 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<DerefElement>(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<DerefToken> 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<DerefElement>(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<CastElement>();
|
||||
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<ModifiedCopyBitfieldElement*>(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<DerefToken> matrix_tokens;
|
||||
bool is_vector_view = false;
|
||||
};
|
||||
|
||||
std::optional<RegisterAccess> identity_var(Form* form) {
|
||||
@@ -3192,14 +3455,7 @@ std::optional<TypeSpec> deref_result_type(RegisterAccess base,
|
||||
|
||||
std::optional<Jak1MatrixRowDeref> match_jak1_matrix_row(Form* form, int row, const Env& env) {
|
||||
auto* deref = form ? form->try_as_element<DerefElement>() : 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<Jak1MatrixRowDeref> match_jak1_matrix_row(Form* form, int row, con
|
||||
if (!base) {
|
||||
return {};
|
||||
}
|
||||
std::vector<DerefToken> 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<DerefToken> 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<DerefToken> lhs, std::vector<DerefToken> rhs, const Env& env) {
|
||||
@@ -3253,6 +3526,21 @@ Form* append_deref_tokens(Form* base, const std::vector<DerefToken>& tokens, For
|
||||
return pool.form<DerefElement>(base, false, tokens);
|
||||
}
|
||||
|
||||
Form* matrix_from_jak1_vector_view(Form* view, FormPool& pool, const Env& env) {
|
||||
auto* deref = view ? view->try_as_element<DerefElement>() : 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<RegisterAccess> load_src_ras, store_dest_ras, store_src_ras;
|
||||
std::vector<DerefToken> 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<GenericElement>(
|
||||
@@ -3530,13 +3830,30 @@ std::optional<DerefContainerInfo> deref_container_info(DerefElement* deref, cons
|
||||
return {};
|
||||
}
|
||||
|
||||
auto base = deref->base()->try_as_element<SimpleExpressionElement>();
|
||||
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<TypeSpec> 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<GenericElement>(
|
||||
GenericOperator::make_function(pool.form<ConstantTokenElement>("vector-copy!")),
|
||||
GenericOperator::make_function(
|
||||
pool.form<ConstantTokenElement>(is_vector4w_copy ? "vector4w-copy!" : "vector-copy!")),
|
||||
std::vector<Form*>{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<DerefElement>() : 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<Form*>{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<bool> 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<StackStructureDefElement>();
|
||||
if (stack_value && stack_value->type() == type) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
auto stack_value = entries->front().source->try_as_element<StackStructureDefElement>();
|
||||
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<FormElement*>* 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<DerefElement*>(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<DerefElement>(existing->base(), existing->is_addr_of(),
|
||||
combined_tokens));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int power_of_two = 0;
|
||||
|
||||
if (m_constant_offset == 0) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -54,7 +54,8 @@ void append_body_to_function_definition(goos::Object* top_form,
|
||||
const std::vector<goos::Object>& 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<goos::Object> 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<goos::Object> 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<goos::Object> 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<goos::Object> 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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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>(CounterLoopElement::Kind::DOTIMES,
|
||||
in->entries().at(0).dest, *lt_var, *inc_var,
|
||||
mr.maps.forms.at(1), body);
|
||||
return pool.alloc_element<CounterLoopElement>(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<MatchResult, Form*, bool> 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<int>& 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<StackStructureDefElement>();
|
||||
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<GenericElement>(
|
||||
GenericOperator::make_function(pool.form<ConstantTokenElement>("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<GenericElement>(op, args);
|
||||
}
|
||||
|
||||
FormElement* rewrite_set_vector_sequence(const std::array<FormElement*, 4>& elts,
|
||||
const Env& env,
|
||||
FormPool& pool) {
|
||||
std::optional<RegisterAccess> vector_access;
|
||||
Form* vector_form = nullptr;
|
||||
std::vector<Form*> sources;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
auto* set = dynamic_cast<SetFormFormElement*>(elts.at(i));
|
||||
if (!set) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* deref = set->dst()->try_as_element<DerefElement>();
|
||||
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<Form*> args = {vector_form};
|
||||
args.insert(args.end(), sources.begin(), sources.end());
|
||||
auto op = GenericOperator::make_function(
|
||||
pool.alloc_single_element_form<ConstantTokenElement>(nullptr, "set-vector!"));
|
||||
return pool.alloc_element<GenericElement>(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<SetFormFormElement*>(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<GenericElement>(
|
||||
GenericOperator::make_function(pool.form<ConstantTokenElement>("meters")),
|
||||
pool.form<ConstantTokenElement>(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<GenericElement>(
|
||||
GenericOperator::make_function(pool.form<ConstantTokenElement>("degrees")),
|
||||
pool.form<ConstantTokenElement>(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<std::string> meter_forms = {
|
||||
"fill-and-probe-using-line-sphere", "set-vector!", "vector-normalize-copy!",
|
||||
"vector-normalize!", "vector+float*!"};
|
||||
static const std::unordered_set<std::string> 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<SetVarElement*>(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<SetVarElement*>(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<GenericElement*>(elt)) {
|
||||
rewrite_short_unit_arguments(call, pool, env);
|
||||
}
|
||||
});
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<std::string>();
|
||||
}
|
||||
}
|
||||
|
||||
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"));
|
||||
|
||||
@@ -167,6 +167,7 @@ struct Config {
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, LabelConfigInfo>> label_types;
|
||||
std::unordered_map<std::string, std::vector<StackStructureHint>>
|
||||
stack_structure_hints_by_function;
|
||||
std::unordered_map<std::string, std::string> scratchpad_types_by_object;
|
||||
std::unordered_map<std::string, ObjectPatchInfo> object_patches;
|
||||
|
||||
std::unordered_map<std::string, int> bad_format_strings;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"cam-debug": "cam-dbg-scratch"
|
||||
}
|
||||
@@ -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"],
|
||||
|
||||
@@ -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": {
|
||||
|
||||
Reference in New Issue
Block a user