mirror of
https://github.com/open-goal/jak-project.git
synced 2024-11-23 22:29:53 +00:00
4d634cf50d
* update refs * first pass * update refs 2 * update * update * update * stuff and things * accidentally wiped this * fancy decomp for static sound specs * clang * 5 more refs
486 lines
18 KiB
C++
486 lines
18 KiB
C++
|
|
|
|
#include "find_defstates.h"
|
|
#include "common/goos/PrettyPrinter.h"
|
|
#include "common/type_system/state.h"
|
|
#include "decompiler/IR2/Form.h"
|
|
#include "decompiler/IR2/GenericElementMatcher.h"
|
|
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
|
|
|
namespace decompiler {
|
|
|
|
constexpr bool debug_defstates = false;
|
|
constexpr bool print_renames = false;
|
|
|
|
namespace {
|
|
|
|
/*!
|
|
* Given the (set! <the-state> <reg-with-static-state>), returns the name of the state and the
|
|
* more specific type.
|
|
*/
|
|
std::pair<std::string, TypeSpec> get_state_info(FormElement* state_set, const Env& env) {
|
|
auto sff = dynamic_cast<SetFormFormElement*>(state_set);
|
|
if (!sff) {
|
|
env.func->warnings.warn_and_throw(
|
|
"Failed to identify defstate. The state symbol set was supposed to be: {}, but "
|
|
"this doesn't look like a set.",
|
|
state_set->to_string(env));
|
|
}
|
|
|
|
auto atom = form_as_atom(sff->dst());
|
|
if (!atom || atom->get_kind() != SimpleAtom::Kind::SYMBOL_VAL) {
|
|
env.func->warnings.warn_and_throw(
|
|
"Failed to identify defstate. The state symbol set was: {}, which doesn't set a symbol",
|
|
state_set->to_string(env));
|
|
}
|
|
|
|
std::string state_name = atom->get_str();
|
|
|
|
auto type = env.dts->symbol_types.find(state_name);
|
|
if (type == env.dts->symbol_types.end()) {
|
|
env.func->warnings.warn_and_throw(
|
|
"Identified a defstate for state {}, but there is no type information for this state.",
|
|
state_name);
|
|
}
|
|
|
|
if (type->second.base_type() != "state") {
|
|
env.func->warnings.warn_and_throw(
|
|
"Identified a defstate for state {}, but our type information thinks it is a {}, not a "
|
|
"state.",
|
|
state_name, type->second.print());
|
|
}
|
|
|
|
if (type->second.arg_count() == 0) {
|
|
env.func->warnings.warn_and_throw(
|
|
"Identified a defstate for state {}, but there is no argument information.", state_name);
|
|
}
|
|
|
|
if (type->second.last_arg() == TypeSpec("none")) {
|
|
env.func->warnings.warn_and_throw(
|
|
"Identified a defstate for state {}, but the process type is none. You must "
|
|
"provide a process type as the final argument of a state",
|
|
state_name);
|
|
}
|
|
|
|
return {atom->get_str(), type->second};
|
|
}
|
|
|
|
std::vector<DefstateElement::Entry> get_defstate_entries(
|
|
Form* body,
|
|
int body_index,
|
|
const Env& env,
|
|
const std::string& state_name,
|
|
const RegisterAccess& let_dest_var,
|
|
const TypeSpec& state_type,
|
|
FormPool& pool,
|
|
const std::optional<std::string>& virtual_child = {},
|
|
const std::unordered_map<std::string, std::unordered_set<std::string>>& skip_states = {}) {
|
|
std::vector<DefstateElement::Entry> entries;
|
|
|
|
// next, all the handlers
|
|
for (; body_index < body->size(); body_index++) {
|
|
DefstateElement::Entry this_entry;
|
|
auto matcher =
|
|
Matcher::set(Matcher::deref(Matcher::any_reg(0), false, {DerefTokenMatcher::any_string(1)}),
|
|
Matcher::any(2));
|
|
Form temp;
|
|
temp.elts().push_back(body->at(body_index));
|
|
auto mr = match(matcher, &temp);
|
|
|
|
if (!mr.matched) {
|
|
env.func->warnings.warn_and_throw(
|
|
"In defstate for state {}, failed to recognize handler set: {}", state_name,
|
|
temp.to_string(env));
|
|
}
|
|
|
|
auto var = mr.maps.regs.at(0);
|
|
auto name = mr.maps.strings.at(1);
|
|
auto val = mr.maps.forms.at(2);
|
|
|
|
auto handler_kind = handler_name_to_kind(name);
|
|
while (val->try_as_element<CastElement>()) {
|
|
val = val->try_as_element<CastElement>()->source();
|
|
}
|
|
this_entry.val = val;
|
|
this_entry.kind = handler_kind;
|
|
this_entry.is_behavior = false;
|
|
|
|
if (!var || env.get_variable_name(*var) != env.get_variable_name(let_dest_var)) {
|
|
if (var) {
|
|
env.func->warnings.warn_and_throw("Messed up defstate. State is in {}, but we set {}",
|
|
env.get_variable_name(let_dest_var),
|
|
env.get_variable_name(*var));
|
|
} else {
|
|
ASSERT(false);
|
|
}
|
|
}
|
|
|
|
if (debug_defstates) {
|
|
fmt::print("SET: {} to {}\n", name, val->to_string(env));
|
|
}
|
|
|
|
// now we try to find a function
|
|
|
|
auto handler_atom = form_as_atom(val);
|
|
if (handler_atom && handler_atom->is_label()) {
|
|
auto handler_func = env.file->try_get_function_at_label(handler_atom->label());
|
|
if (!handler_func) {
|
|
env.func->warnings.warn_and_throw("Failed to find handler function.");
|
|
}
|
|
|
|
this_entry.is_behavior = true;
|
|
if (print_renames) {
|
|
fmt::print("RENAME: {} to ", handler_func->name());
|
|
}
|
|
|
|
if (virtual_child) {
|
|
handler_func->guessed_name.set_as_v_state(*virtual_child, state_name, handler_kind);
|
|
} else {
|
|
handler_func->guessed_name.set_as_nv_state(state_name, handler_kind);
|
|
}
|
|
if (print_renames) {
|
|
fmt::print("{}\n", handler_func->name());
|
|
}
|
|
|
|
// scary part - modify the function type!
|
|
handler_func->type = get_state_handler_type(handler_kind, state_type);
|
|
} else if (handler_atom && handler_atom->is_sym_val()) {
|
|
auto sym_type = env.dts->lookup_symbol_type(handler_atom->get_str());
|
|
auto expected_type = get_state_handler_type(handler_kind, state_type);
|
|
if (!env.dts->ts.tc(expected_type, sym_type)) {
|
|
this_entry.val =
|
|
pool.alloc_single_element_form<CastElement>(nullptr, expected_type, this_entry.val);
|
|
}
|
|
}
|
|
// name = code/event/etc
|
|
std::string name_to_check_for_skip = state_name;
|
|
if (skip_states.count(name_to_check_for_skip) == 0) {
|
|
name_to_check_for_skip =
|
|
fmt::format("({} {})", state_name, state_type.last_arg().base_type());
|
|
}
|
|
if (skip_states.count(name_to_check_for_skip) > 0) {
|
|
if (skip_states.at(name_to_check_for_skip).find(name) !=
|
|
skip_states.at(name_to_check_for_skip).end()) {
|
|
env.func->warnings.general_warning("SKIP: skipping '{}' handler for state '{}'", name,
|
|
name_to_check_for_skip);
|
|
continue;
|
|
}
|
|
}
|
|
entries.push_back(this_entry);
|
|
}
|
|
return entries;
|
|
}
|
|
|
|
FormElement* rewrite_nonvirtual_defstate(
|
|
LetElement* elt,
|
|
const Env& env,
|
|
const std::string& expected_state_name,
|
|
FormPool& pool,
|
|
const std::unordered_map<std::string, std::unordered_set<std::string>>& skip_states = {}) {
|
|
// first thing in the body should be something like:
|
|
// (set! teetertotter-idle (the-as (state none) v1-3))
|
|
ASSERT(elt->body()->size() > 0);
|
|
int body_index = 0;
|
|
|
|
// the setup
|
|
auto first_in_body = elt->body()->at(body_index);
|
|
auto info = get_state_info(first_in_body, env);
|
|
if (info.first != expected_state_name) {
|
|
env.func->warnings.warn_and_throw(
|
|
"Inconsistent defstate name. code has {}, static state has {}", info.first,
|
|
expected_state_name);
|
|
}
|
|
if (debug_defstates) {
|
|
fmt::print("State: {} Type: {}\n", info.first, info.second.print());
|
|
}
|
|
body_index++;
|
|
|
|
auto entries =
|
|
get_defstate_entries(elt->body(), body_index, env, info.first, elt->entries().at(0).dest,
|
|
info.second, pool, {}, skip_states);
|
|
|
|
return pool.alloc_element<DefstateElement>(info.second.last_arg().base_type(), info.first,
|
|
entries, false, false);
|
|
}
|
|
|
|
struct VirtualStateInfo {
|
|
TypeSpec type_from_ts;
|
|
};
|
|
|
|
FormElement* strip_cast(FormElement* in) {
|
|
auto casted = dynamic_cast<CastElement*>(in);
|
|
while (casted) {
|
|
in = casted->source()->try_as_single_element();
|
|
casted = dynamic_cast<CastElement*>(in);
|
|
}
|
|
return in;
|
|
}
|
|
|
|
std::string verify_empty_state_and_get_name(DecompiledDataElement* state, const Env& env) {
|
|
auto lab = state->label();
|
|
// should have:
|
|
/*
|
|
.type state
|
|
L25:
|
|
.symbol teetertotter-launch
|
|
.symbol #f
|
|
.symbol #f
|
|
.symbol #f
|
|
.symbol #f
|
|
.symbol #f
|
|
.symbol #f
|
|
.symbol #f
|
|
*/
|
|
|
|
int start_word_idx = (lab.offset / 4) - 1;
|
|
auto& words = env.file->words_by_seg.at(lab.target_segment);
|
|
|
|
auto first_word = words.at(start_word_idx);
|
|
if (first_word.kind() != LinkedWord::TYPE_PTR || first_word.symbol_name() != "state") {
|
|
env.func->warnings.warn_and_throw("Reference to state bad: invalid type pointer");
|
|
}
|
|
|
|
auto name_word = words.at(start_word_idx + 1);
|
|
if (name_word.kind() != LinkedWord::SYM_PTR) {
|
|
env.func->warnings.warn_and_throw("Reference to state bad: invalid name");
|
|
}
|
|
|
|
for (int i = 0; i < 7; i++) {
|
|
auto& word = words.at(start_word_idx + 2 + i);
|
|
if (word.kind() != LinkedWord::SYM_PTR || word.symbol_name() != "#f") {
|
|
env.func->warnings.warn_and_throw(
|
|
"Reference to state bad: got a non #f in the initial fields");
|
|
}
|
|
}
|
|
|
|
return name_word.symbol_name();
|
|
}
|
|
|
|
FormElement* rewrite_virtual_defstate(
|
|
LetElement* elt,
|
|
const Env& env,
|
|
const std::string& expected_state_name,
|
|
FormPool& pool,
|
|
const std::unordered_map<std::string, std::unordered_set<std::string>>& skip_states = {}) {
|
|
ASSERT(elt->body()->size() > 1);
|
|
// variable at the top of let, contains the static state with name exptected_state_name
|
|
auto state_var_from_let_def = elt->entries().at(0).dest;
|
|
// our index into the let body
|
|
int body_idx = 0;
|
|
|
|
// see if the first thing is an inherit-state.
|
|
auto maybe_inherit_form = elt->body()->at(body_idx);
|
|
// fmt::print("first is {}\n", maybe_inherit_form->to_string(env));
|
|
Form temp;
|
|
temp.elts().push_back(maybe_inherit_form);
|
|
// (inherit-state gp-1 (method-of-type plat-button dummy-24))
|
|
auto inherit_matcher = Matcher::op(GenericOpMatcher::func(Matcher::symbol("inherit-state")),
|
|
{Matcher::any_reg(0), Matcher::any(1)});
|
|
|
|
struct InheritInfo {
|
|
std::string parent_type_name;
|
|
std::string method_name;
|
|
};
|
|
std::optional<InheritInfo> inherit_info;
|
|
|
|
auto inherit_mr = match(inherit_matcher, &temp);
|
|
if (!inherit_mr.matched) {
|
|
// no inherit. This means that we should be the first in the type tree to define this state.
|
|
inherit_info = {};
|
|
} else {
|
|
// found the inherit. advance body_idx so we move on to the next form.
|
|
body_idx++;
|
|
|
|
// expect this to match the variable in the top let
|
|
auto state_var = *inherit_mr.maps.regs.at(0);
|
|
// this expression should be the thing we inherit from.
|
|
auto parent_state = inherit_mr.maps.forms.at(1);
|
|
|
|
if (env.get_variable_name(state_var_from_let_def) != env.get_variable_name(state_var)) {
|
|
env.func->warnings.warn_and_throw(
|
|
"Variable name disagreement in virtual defstate: began with {}, but did method "
|
|
"set using {}",
|
|
env.get_variable_name(state_var_from_let_def), env.get_variable_name(state_var));
|
|
}
|
|
|
|
// if there's a cast here, it probably means that there's no :state in the deftype.
|
|
// let's warn here instead of trying to go on.
|
|
auto parent_state_cast = parent_state->try_as_element<CastElement>();
|
|
if (parent_state_cast) {
|
|
env.func->warnings.warn_and_throw(
|
|
"virtual defstate attempted on something that isn't a state: {}\nDid you "
|
|
"forget to put :state in the method definition?",
|
|
parent_state_cast->to_string(env));
|
|
}
|
|
|
|
// identify the (method-of-type ...) form that grabs the parent state.
|
|
auto mot_matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::METHOD_OF_TYPE),
|
|
{Matcher::any_symbol(0), Matcher::any_constant_token(1)});
|
|
auto mot_mr = match(mot_matcher, parent_state);
|
|
if (!mot_mr.matched) {
|
|
env.func->warnings.warn_and_throw(
|
|
"Failed to recognize virtual defstate. Got a {} as the parent to inherit from.",
|
|
parent_state->to_string(env));
|
|
}
|
|
|
|
inherit_info = {{mot_mr.maps.strings.at(0), mot_mr.maps.strings.at(1)}};
|
|
}
|
|
|
|
// checks to check: method type is a state
|
|
// if inherit matches expected.
|
|
|
|
// next, find (method-set! sunken-elevator 22 (the-as function gp-0))
|
|
auto method_set_form = elt->body()->at(body_idx);
|
|
temp = Form();
|
|
temp.elts().push_back(method_set_form);
|
|
auto mset_matcher =
|
|
Matcher::op(GenericOpMatcher::func(Matcher::symbol("method-set!")),
|
|
{Matcher::any_symbol(0), Matcher::any_integer(1), Matcher::any(2)});
|
|
auto mset_mr = match(mset_matcher, &temp);
|
|
if (!mset_mr.matched) {
|
|
env.func->warnings.warn_and_throw(
|
|
"Failed to recognize virtual defstate. Got a {} as the second thing, but was "
|
|
"expecting method-set! call",
|
|
temp.to_string(env));
|
|
}
|
|
|
|
// the actual type that gets this as a state
|
|
auto type_name = mset_mr.maps.strings.at(0);
|
|
auto method_id = mset_mr.maps.ints.at(1);
|
|
|
|
// should be the state again.
|
|
auto val = strip_cast(mset_mr.maps.forms.at(2)->try_as_single_element());
|
|
if (val->to_string(env) != env.get_variable_name(state_var_from_let_def)) {
|
|
env.func->warnings.warn_and_throw(
|
|
"Variable name disagreement in virtual defstate: began with {}, but did method "
|
|
"set using {}",
|
|
val->to_string(env), env.get_variable_name(state_var_from_let_def));
|
|
}
|
|
|
|
// we should double check that the type in the defstate is correct
|
|
auto method_info = env.dts->ts.lookup_method(type_name, method_id);
|
|
if (method_info.type.base_type() != "state" ||
|
|
method_info.type.last_arg().base_type() != "_type_") {
|
|
env.func->warnings.warn_and_throw(
|
|
"Virtual defstate is defining a virtual state in method {} of {}, but the type "
|
|
"of this method is {}, which is not a valid virtual state type (must be "
|
|
"\"(state ... _type_)\")",
|
|
method_info.name, type_name, method_info.type.print());
|
|
}
|
|
|
|
bool state_override = false;
|
|
{
|
|
MethodInfo parent_method_info;
|
|
auto parent_type_name = env.dts->ts.lookup_type(type_name)->get_parent();
|
|
if (env.dts->ts.try_lookup_method(parent_type_name, method_id, &parent_method_info)) {
|
|
if (!inherit_info) {
|
|
// did NOT inherit parent state, this is an override!
|
|
state_override = true;
|
|
// env.func->warnings.warn_and_throw(
|
|
// "Virtual defstate for state {} in type {}: the state was defined in the "
|
|
// "parent but wasn't inherited.",
|
|
// expected_state_name, type_name);
|
|
}
|
|
} else {
|
|
if (inherit_info) {
|
|
env.func->warnings.warn_and_throw(
|
|
"Virtual defstate for state {} in type {}: the state wasn't defined in the "
|
|
"parent but was inherited.",
|
|
expected_state_name, type_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
// checks: parent_type_name is the parent
|
|
if (inherit_info) {
|
|
auto child_type_info = env.dts->ts.lookup_type(type_name);
|
|
if (child_type_info->get_parent() != inherit_info->parent_type_name) {
|
|
env.func->warnings.warn_and_throw(
|
|
"Parent type disagreement in virtual defstate. The state is inherited from {}, but the "
|
|
"parent is {}",
|
|
inherit_info->parent_type_name, child_type_info->get_parent());
|
|
}
|
|
|
|
auto parent_method_info = env.dts->ts.lookup_method(inherit_info->parent_type_name, method_id);
|
|
if (parent_method_info.name != inherit_info->method_name) {
|
|
env.func->warnings.warn_and_throw(
|
|
"Disagreement between inherit and define. We inherited from method {}, but redefine {}",
|
|
inherit_info->method_name, parent_method_info.name);
|
|
}
|
|
}
|
|
|
|
// name matches
|
|
|
|
if (expected_state_name != method_info.name) {
|
|
env.func->warnings.warn_and_throw(
|
|
"Disagreement between state name and type system name. The state is named {}, "
|
|
"but the slot is named {}, defined in type {}",
|
|
expected_state_name, method_info.name, method_info.defined_in_type);
|
|
}
|
|
|
|
// fmt::print("is a {}\n", typeid(*parent_state->try_as_single_element()).name());
|
|
|
|
auto entries = get_defstate_entries(
|
|
elt->body(), body_idx + 1, env, expected_state_name, elt->entries().at(0).dest,
|
|
method_info.type.substitute_for_method_call(type_name), pool, type_name, skip_states);
|
|
|
|
return pool.alloc_element<DefstateElement>(type_name, expected_state_name, entries, true,
|
|
state_override);
|
|
}
|
|
|
|
bool is_nonvirtual_state(LetElement* elt) {
|
|
return dynamic_cast<SetFormFormElement*>(elt->body()->at(0));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void run_defstate(
|
|
Function& top_level_func,
|
|
const std::unordered_map<std::string, std::unordered_set<std::string>>& skip_states) {
|
|
auto& env = top_level_func.ir2.env;
|
|
auto& pool = *top_level_func.ir2.form_pool;
|
|
if (!top_level_func.ir2.top_form) {
|
|
return;
|
|
}
|
|
top_level_func.ir2.top_form->apply_form([&](Form* form) {
|
|
for (auto& fe : form->elts()) {
|
|
auto as_let = dynamic_cast<LetElement*>(fe);
|
|
if (as_let && as_let->entries().size() == 1) {
|
|
/* Looks something like this:
|
|
(let ((v1-3 <static-data L28>))
|
|
(set! teetertotter-idle (the-as (state none) v1-3))
|
|
(set! (-> v1-3 event) L17)
|
|
(set! (-> v1-3 code) L15)
|
|
(set! (-> v1-3 post) transform-post)
|
|
)
|
|
*/
|
|
|
|
// first, see if we get a label:
|
|
auto src_as_label = as_let->entries().at(0).src->try_as_element<DecompiledDataElement>();
|
|
if (src_as_label &&
|
|
env.get_variable_type(as_let->entries().at(0).dest, false) == TypeSpec("state")) {
|
|
std::string expected_state_name = verify_empty_state_and_get_name(src_as_label, env);
|
|
if (debug_defstates) {
|
|
fmt::print("got state let:\n{}\n", pretty_print::to_string(as_let->to_form(env)));
|
|
}
|
|
|
|
if (is_nonvirtual_state(as_let)) {
|
|
auto rewritten =
|
|
rewrite_nonvirtual_defstate(as_let, env, expected_state_name, pool, skip_states);
|
|
if (rewritten) {
|
|
fe = rewritten;
|
|
}
|
|
} else {
|
|
auto rewritten =
|
|
rewrite_virtual_defstate(as_let, env, expected_state_name, pool, skip_states);
|
|
if (rewritten) {
|
|
fe = rewritten;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
} // namespace decompiler
|