Skip to content

Commit

Permalink
[decompiler] as-type and font method support (#3855)
Browse files Browse the repository at this point in the history
Add support for `as-type` macro, and detecting inline font methods. This
works in all three games but I've only updated jak 3's goal_src for now.
Eventually I will go back and work through the others, but I want to get
more decompiler features in first.


![image](https://github.com/user-attachments/assets/5c31bf85-97b4-437c-bc4b-dc054e60551e)

---------

Co-authored-by: water111 <[email protected]>
  • Loading branch information
water111 and water111 authored Feb 2, 2025
1 parent d5590ab commit 48cb9bb
Show file tree
Hide file tree
Showing 645 changed files with 5,391 additions and 16,694 deletions.
78 changes: 73 additions & 5 deletions decompiler/IR2/FormExpressionAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4638,6 +4638,67 @@ Form* try_rewrite_as_process_to_ppointer(CondNoElseElement* value,
GenericOperator::make_fixed(FixedOperatorKind::PROCESS_TO_PPOINTER), repopped);
}

/*!
* (if (type? foo bar)
* foo
* )
*/
Form* try_rewrite_as_as_type(CondNoElseElement* value,
FormStack& stack,
FormPool& pool,
const Env& env,
const TypeSpec& resulting_type) {
if (value->entries.size() != 1) {
return nullptr;
}

auto condition = value->entries.at(0).condition;
auto body = value->entries[0].body;

auto condition_matcher = Matcher::op(
GenericOpMatcher::condition(IR2_Condition::Kind::TRUTHY),
{Matcher::func(Matcher::symbol("type?"), {Matcher::any_reg(0), Matcher::any_symbol(1)})});

auto condition_mr = match(condition_matcher, condition);
if (!condition_mr.matched) {
return nullptr;
}

auto body_matcher = Matcher::any_reg(0);
auto body_mr = match(body_matcher, body);
if (!body_mr.matched) {
body_mr = match(Matcher::cast_to_any(1, body_matcher), body);
if (!body_mr.matched) {
return nullptr;
}
}
auto body_var = body_mr.maps.regs.at(0).value();
auto condition_var = condition_mr.maps.regs.at(0).value();
auto* menv = const_cast<Env*>(&env);
menv->disable_use(body_var);
auto repopped = stack.pop_reg(condition_var, {}, env, true);
if (!repopped) {
repopped = var_to_form(condition_var, pool);
}
auto new_type = condition_mr.maps.strings.at(1);

auto result = pool.form<GenericElement>(
GenericOperator::make_function(pool.form<ConstantTokenElement>("as-type")),
std::vector<Form*>{repopped, pool.form<ConstantTokenElement>(new_type)});

// silly cast situation:
// sometimes there is something dumb like (the specific (as-type foo general))
// we have to make sure that we keep the leading cast.
// HACK: inserting casts more aggressively in Jak 2 because I am too lazy to fix up all the
// slightly wrong casts that matter now.
if (resulting_type != TypeSpec(new_type) &&
(env.version == GameVersion::Jak2 || env.dts->ts.tc(TypeSpec(new_type), resulting_type))) {
return pool.form<CastElement>(resulting_type, result);
} else {
return result;
}
}

// (if x (-> x 0 self)) -> (ppointer->process x)
Form* try_rewrite_as_pppointer_to_process(CondNoElseElement* value,
FormStack& stack,
Expand Down Expand Up @@ -4810,11 +4871,18 @@ void CondNoElseElement::push_to_stack(const Env& env, FormPool& pool, FormStack&
stack.push_value_to_reg(write_as_value, as_ja_group, true,
env.get_variable_type(final_destination, false));
} else {
// lg::print("func {} final destination {} type {}\n", env.func->name(),
// final_destination.to_string(env),
// env.get_variable_type(final_destination, false).print());
stack.push_value_to_reg(write_as_value, pool.alloc_single_form(nullptr, this), true,
env.get_variable_type(final_destination, false));
auto as_as_type = try_rewrite_as_as_type(this, stack, pool, env,
env.get_variable_type(final_destination, true));
if (as_as_type) {
stack.push_value_to_reg(write_as_value, as_as_type, true,
env.get_variable_type(final_destination, false));
} else {
// lg::print("func {} final destination {} type {}\n", env.func->name(),
// final_destination.to_string(env),
// env.get_variable_type(final_destination, false).print());
stack.push_value_to_reg(write_as_value, pool.alloc_single_form(nullptr, this), true,
env.get_variable_type(final_destination, false));
}
}
}
}
Expand Down
18 changes: 18 additions & 0 deletions decompiler/IR2/GenericElementMatcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ Matcher Matcher::cast(const std::string& type, Matcher value) {
return m;
}

Matcher Matcher::numeric_cast(const std::string& type, Matcher value) {
Matcher m;
m.m_kind = Kind::NUMERIC_CAST;
m.m_str = type;
m.m_sub_matchers = {value};
return m;
}

Matcher Matcher::cast_to_any(int type_out, Matcher value) {
Matcher m;
m.m_kind = Kind::CAST_TO_ANY;
Expand Down Expand Up @@ -412,6 +420,16 @@ bool Matcher::do_match(Form* input, MatchResult::Maps* maps_out, const Env* cons
return false;
} break;

case Kind::NUMERIC_CAST: {
auto as_cast = dynamic_cast<CastElement*>(input->try_as_single_active_element());
if (as_cast && as_cast->numeric()) {
if (as_cast->type().print() == m_str) {
return m_sub_matchers.at(0).do_match(as_cast->source(), maps_out, env);
}
}
return false;
} break;

case Kind::CAST_TO_ANY: {
auto as_cast = dynamic_cast<CastElement*>(input->try_as_single_active_element());
if (as_cast) {
Expand Down
2 changes: 2 additions & 0 deletions decompiler/IR2/GenericElementMatcher.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class Matcher {
static Matcher set_var(const Matcher& src, int dst_match_id); // var-form
static Matcher match_or(const std::vector<Matcher>& args);
static Matcher cast(const std::string& type, Matcher value);
static Matcher numeric_cast(const std::string& type, Matcher value);
static Matcher cast_to_any(int type_out, Matcher value);
static Matcher any(int match_id = -1);
static Matcher integer(std::optional<int> value);
Expand Down Expand Up @@ -88,6 +89,7 @@ class Matcher {
GENERIC_OP_WITH_REST,
OR,
CAST,
NUMERIC_CAST,
CAST_TO_ANY,
ANY,
INT,
Expand Down
4 changes: 3 additions & 1 deletion decompiler/ObjectFile/ObjectFileDB.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,13 @@ struct LetRewriteStats {
int launch_particles = 0;
int call_parent_state_handler = 0;
int suspend_for = 0;
int font_method = 0;

int total() const {
return dotimes + countdown + abs + abs2 + unused + ja + 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;
launch_particles + call_parent_state_handler + suspend_for + font_method;
}

std::string print() const {
Expand Down Expand Up @@ -115,6 +116,7 @@ struct LetRewriteStats {
out += fmt::format(" launch_particles: {}\n", launch_particles);
out += fmt::format(" call_parent_state_handler: {}\n", call_parent_state_handler);
out += fmt::format(" suspend_for: {}\n", suspend_for);
out += fmt::format(" font_method: {}\n", font_method);
return out;
}

Expand Down
121 changes: 121 additions & 0 deletions decompiler/analysis/insert_lets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2212,6 +2212,50 @@ FormElement* rewrite_attack_info(LetElement* in, const Env& env, FormPool& pool)
return elt;
}

FormElement* rewrite_set_font_single(LetElement* in,
const Env& env,
FormPool& pool,
const char* deref_name,
const char* op_name,
bool cast_to_float) {
/*
(let ((v1-10 gp-0))
(set! (-> v1-10 scale) 0.6)
)
*/

if (in->entries().size() != 1) {
return nullptr;
}
if (in->body()->elts().size() != 1) {
return nullptr;
}

Form* font_obj_expr = in->entries().at(0).src;
RegisterAccess font_obj_reg = in->entries().at(0).dest;

if (env.get_variable_type(font_obj_reg, true) != TypeSpec("font-context")) {
return nullptr;
}

auto src_matcher =
cast_to_float ? Matcher::numeric_cast("float", Matcher::any(0)) : Matcher::any(0);
Matcher set_matcher = Matcher::set(Matcher::deref(Matcher::reg(font_obj_reg.reg()), false,
{DerefTokenMatcher::string(deref_name)}),
src_matcher);
auto set_mr = match(set_matcher, in->body()->at(0));

if (!set_mr.matched) {
return nullptr;
}

auto elt = pool.alloc_element<GenericElement>(
GenericOperator::make_function(pool.form<ConstantTokenElement>(op_name)),
std::vector<Form*>{font_obj_expr, set_mr.maps.forms.at(0)});
elt->parent_form = in->parent_form;
return elt;
}

/*!
* Attempt to rewrite a let as another form. If it cannot be rewritten, this will return nullptr.
*/
Expand Down Expand Up @@ -2320,6 +2364,24 @@ FormElement* rewrite_let(LetElement* in, const Env& env, FormPool& pool, LetRewr
return as_call_parent_state;
}

auto as_font_scale = rewrite_set_font_single(in, env, pool, "scale", "set-scale!", false);
if (as_font_scale) {
stats.font_method++;
return as_font_scale;
}

auto as_font_width = rewrite_set_font_single(in, env, pool, "width", "set-width!", true);
if (as_font_width) {
stats.font_method++;
return as_font_width;
}

auto as_font_height = rewrite_set_font_single(in, env, pool, "height", "set-height!", true);
if (as_font_height) {
stats.font_method++;
return as_font_height;
}

// nothing matched.
return nullptr;
}
Expand Down Expand Up @@ -2687,6 +2749,59 @@ FormElement* rewrite_with_dma_buf_add_bucket(LetElement* in, const Env& env, For
return elt;
}

FormElement* rewrite_set_font_origin(LetElement* in, const Env& env, FormPool& pool) {
/*
(let ((v1-9 gp-0)
(a1-1 36)
(a0-4 140)
)
(set! (-> v1-9 origin x) (the float a1-1))
(set! (-> v1-9 origin y) (the float a0-4))
)
*/
if (in->entries().size() != 3) {
return nullptr;
}
if (in->body()->elts().size() != 2) {
return nullptr;
}

Form* font_obj_expr = in->entries().at(0).src;
RegisterAccess font_obj_reg = in->entries().at(0).dest;

if (env.get_variable_type(font_obj_reg, true) != TypeSpec("font-context")) {
return nullptr;
}

Form* x_val = in->entries().at(1).src;
Form* y_val = in->entries().at(2).src;

Matcher x_matcher = Matcher::set(
Matcher::deref(Matcher::reg(font_obj_reg.reg()), false,
{DerefTokenMatcher::string("origin"), DerefTokenMatcher::string("x")}),
Matcher::numeric_cast("float", Matcher::reg(in->entries().at(1).dest.reg())));
auto x_mr = match(x_matcher, in->body()->at(0));

Matcher y_matcher = Matcher::set(
Matcher::deref(Matcher::reg(font_obj_reg.reg()), false,
{DerefTokenMatcher::string("origin"), DerefTokenMatcher::string("y")}),
Matcher::numeric_cast("float", Matcher::reg(in->entries().at(2).dest.reg())));
auto y_mr = match(y_matcher, in->body()->at(1));

if (!x_mr.matched) {
return nullptr;
}
if (!y_mr.matched) {
return nullptr;
}

auto elt = pool.alloc_element<GenericElement>(
GenericOperator::make_function(pool.form<ConstantTokenElement>("set-origin!")),
std::vector<Form*>{font_obj_expr, x_val, y_val});
elt->parent_form = in->parent_form;
return elt;
}

FormElement* rewrite_launch_particles(LetElement* in, const Env& env, FormPool& pool) {
/*
* (let ((t9-0 sp-launch-particles-var)
Expand Down Expand Up @@ -2846,6 +2961,12 @@ FormElement* rewrite_multi_let(LetElement* in,
}
}

auto as_font_set_origin = rewrite_set_font_origin(in, env, pool);
if (as_font_set_origin) {
stats.font_method++;
return as_font_set_origin;
}

return in;
}

Expand Down
20 changes: 12 additions & 8 deletions decompiler/config/jak3/ntsc_v1/type_casts.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -3194,6 +3194,16 @@
[93, "a1", "process-focusable"],
[140, "a1", "process-focusable"]
],
"(method 106 bot)": [
[[16, 31], "v1", "process-focusable"],
[[40, 58], "v1", "process-focusable"],
[[92, 95], "v1", "process-focusable"]
],
"(method 106 ashelin)": [
[[16, 21], "v1", "process-focusable"],
[[40, 57], "v1", "process-focusable"],
[[92, 95], "v1", "process-focusable"]
],
"(method 140 enemy)": [[18, "a1", "process-focusable"]],
"(method 142 enemy)": [[[0, 40], "v0", "knocked-type"]],
"get-penetrate-using-from-attack-event": [
Expand Down Expand Up @@ -6607,8 +6617,7 @@
[40, "a0", "process-focusable"]
],
"(method 26 task-manager-temple-climb)": [
[126, "s5", "process-focusable"],
[130, "s5", "process-focusable"]
[[0, 148], "s5", "process-focusable"]
],
"(method 26 task-manager-desert-beast-battle)": [
[39, "a0", "process-focusable"]
Expand Down Expand Up @@ -9790,12 +9799,7 @@
[319, "a1", "ff-squad-control"]
],
"(method 252 crimson-guard)": [
[74, "s5", "process-focusable"],
[69, "s5", "process-focusable"],
[126, "s5", "process-focusable"],
[146, "s5", "process-focusable"],
[202, "s5", "process-focusable"],
[205, "s5", "process-focusable"]
[[0, 223], "s5", "process-focusable"]
],
"(method 51 ff-squad-control)": [
[13, "v1", "connection"],
Expand Down
3 changes: 3 additions & 0 deletions decompiler/config/jak3/ntsc_v1/var_names.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@
"(method 10 process-tree)": {
"args": ["this", "ent"]
},
"(method 106 bot)": {
"vars": {"v1-3": ["v1-3", "process-focusable"]}
},
"(method 0 clock)": {
"args": ["allocation", "type-to-make", "index"]
},
Expand Down
13 changes: 13 additions & 0 deletions goal_src/jak2/kernel/gcommon.gc
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,19 @@
,@args)
)

(defmacro as-type (this type)
"If this is the specified type, return this cast to that type. Otherwise, return #f."
(with-gensyms (obj)
`(the ,type (let ((,obj ,this))
(if (type? ,obj ,type)
,obj
)
)
)
)
)


(defun ref ((arg0 object) (arg1 int))
"Get the n-th item in a linked list. No range checking."
(dotimes (v1-0 arg1)
Expand Down
Loading

0 comments on commit 48cb9bb

Please sign in to comment.