#include "ExpressionHelpers.h" #include "common/goal_constants.h" #include "common/log/log.h" #include "decompiler/IR2/Env.h" #include "decompiler/IR2/Form.h" #include "decompiler/IR2/FormStack.h" #include "decompiler/IR2/GenericElementMatcher.h" namespace decompiler { // needed for jak 2. std::optional try_get_const_float(const Form* form) { auto* as_cfe = form->try_as_element(); if (as_cfe) { return as_cfe->value(); } auto atom = form_as_atom(form); if (atom && atom->is_integer_promoted_to_float()) { return atom->get_integer_promoted_to_float(); } return {}; } FormElement* handle_get_property_value_float(const std::vector& forms, FormPool& pool, const Env& env) { ASSERT(forms.size() == 7); // lump object // name // 'interp // -1000000000.0 = DEFAULT_RES_TIME // default value // tag pointer // *res-static-buf* // get the res-lump. This can be anything. Form* lump_object = forms.at(0); // 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. auto mode_atom = form_as_atom(forms.at(2)); if (!mode_atom || !mode_atom->is_sym_ptr("interp")) { lg::error("fail: bad mode {}", forms.at(2)->to_string(env)); return nullptr; } // get the time. It must be DEFAULT_RES_TIME auto lookup_time = try_get_const_float(forms.at(3)); if (!lookup_time || *lookup_time != DEFAULT_RES_TIME) { lg::error("fail: bad time {}\n", forms.at(3)->to_string(env)); return nullptr; } // get the default value. It can be anything... Form* default_value = forms.at(4); // but let's see if it's 0, because that's the default in the macro auto default_value_float = try_get_const_float(default_value); if (default_value_float && *default_value_float == 0) { default_value = nullptr; } // get the tag pointer. It can be anything... Form* tag_pointer = forms.at(5); // but let's see if it's (the-as (pointer res-tag) #f) if (tag_pointer->to_string(env) == "(the-as (pointer res-tag) #f)") { tag_pointer = nullptr; } // get the buffer. It should be *res-static-buf* auto buf_atom = form_as_atom(forms.at(6)); if (!buf_atom || !buf_atom->is_sym_val("*res-static-buf*")) { return nullptr; } // (lump name &key (tag-ptr (the-as (pointer res-tag) #f)) &key (default 0.0)) std::vector macro_args; macro_args.push_back(lump_object); macro_args.push_back(property_name); if (default_value) { macro_args.push_back(pool.form(":default")); macro_args.push_back(default_value); } if (tag_pointer) { macro_args.push_back(pool.form(":tag-ptr")); macro_args.push_back(tag_pointer); } GenericOperator op = GenericOperator::make_function(pool.form("res-lump-float")); return pool.alloc_element(op, macro_args); } namespace { FormElement* handle_get_property_data_or_structure(const std::vector& forms, FormPool& pool, const Env& env, ResLumpMacroElement::Kind kind, const std::string& expcted_default, const TypeSpec& default_type) { // (-> obj entity) // 'water-anim-fade-dist // 'interp // -1000000000.0 // (the-as pointer #f) // (the-as (pointer res-tag) #f) // *res-static-buf* // get the res-lump. This can be anything. Form* lump_object = forms.at(0); // 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. auto mode_atom = form_as_atom(forms.at(2)); if (!mode_atom || !mode_atom->is_sym_ptr("interp")) { lg::error("fail data: bad mode {}", forms.at(2)->to_string(env)); return nullptr; } // 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) { time = nullptr; } // get the default value. It must be (the-as pointer #f) Form* default_value = forms.at(4); // but let's see if it's 0, because that's the default in the macro if (default_value->to_string(env) != expcted_default) { lg::error("fail data: bad default {}", default_value->to_string(env)); return nullptr; } // get the tag pointer. It can be anything... Form* tag_pointer = forms.at(5); // but let's see if it's (the-as (pointer res-tag) #f) if (tag_pointer->to_string(env) == "(the-as (pointer res-tag) #f)") { tag_pointer = nullptr; } // get the buffer. It should be *res-static-buf* auto buf_atom = form_as_atom(forms.at(6)); if (!buf_atom || !buf_atom->is_sym_val("*res-static-buf*")) { return nullptr; } return pool.alloc_element(kind, lump_object, property_name, nullptr, // default, must be #f tag_pointer, time, default_type); } } // namespace FormElement* handle_get_property_data(const std::vector& forms, FormPool& pool, const Env& env) { return handle_get_property_data_or_structure(forms, pool, env, ResLumpMacroElement::Kind::DATA, "(the-as pointer #f)", TypeSpec("pointer")); } FormElement* handle_get_property_struct(const std::vector& forms, FormPool& pool, const Env& env) { return handle_get_property_data_or_structure( forms, pool, env, ResLumpMacroElement::Kind::STRUCT, env.version >= GameVersion::Jak2 ? "(the-as structure #f)" : "#f", TypeSpec("structure")); } FormElement* handle_get_property_value(const std::vector& forms, FormPool& pool, const Env& env) { // get the res-lump. This can be anything. Form* lump_object = forms.at(0); // 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. auto mode_atom = form_as_atom(forms.at(2)); if (!mode_atom || !mode_atom->is_sym_ptr("interp")) { lg::error("fail data: bad mode {}", forms.at(2)->to_string(env)); return nullptr; } // get the time. It can be anything, but there's a default. auto time = forms.at(3); auto lookup_time = time->try_as_element(); if (lookup_time && lookup_time->value() == DEFAULT_RES_TIME) { time = nullptr; } // get the default value. It can be whatever. Form* default_value = forms.at(4); // but let's see if it's 0, because that's the default in the macro if (default_value->to_string(env) == "(the-as uint128 0)") { default_value = nullptr; } // get the tag pointer. It can be anything... Form* tag_pointer = forms.at(5); // but let's see if it's (the-as (pointer res-tag) #f) if (tag_pointer->to_string(env) == "(the-as (pointer res-tag) #f)") { tag_pointer = nullptr; } // get the buffer. It should be *res-static-buf* auto buf_atom = form_as_atom(forms.at(6)); if (!buf_atom || !buf_atom->is_sym_val("*res-static-buf*")) { return nullptr; } return pool.alloc_element(ResLumpMacroElement::Kind::VALUE, lump_object, property_name, default_value, tag_pointer, time, TypeSpec("uint128")); } namespace { Form* var_to_form(const RegisterAccess& var, FormPool& pool) { return pool.alloc_single_element_form(nullptr, SimpleAtom::make_var(var)); } /*! * Try to see if this form is a variable, or variable in a cast. If so, return the variable, * and set cast_out if there was a cast. */ std::optional try_strip_cast_get_var(Form* in, std::optional& cast_out) { auto* elt = in->try_as_single_element(); if (!elt) { return {}; } auto* as_cast_elt = dynamic_cast(elt); if (as_cast_elt) { cast_out = as_cast_elt->type(); elt = as_cast_elt->source()->try_as_single_element(); } if (!elt) { return {}; } auto as_atom = form_element_as_atom(elt); if (!as_atom || !as_atom->is_var()) { return {}; } return as_atom->var(); } /*! * Return (the-as ) or . */ FormElement* maybe_cast(FormElement* in, std::optional& maybe_cast_type, FormPool& pool) { if (maybe_cast_type) { return pool.alloc_element(*maybe_cast_type, pool.alloc_single_form(nullptr, in)); } else { return in; } } } // namespace /*! * Recognize the handle->process macro. * If it occurs inside of another and, the part_of_longer_sc argument should be set. * * Will move the cast out from the `if` to surround the output. */ FormElement* last_two_in_and_to_handle_get_proc(Form* first, Form* second, const Env& env, FormPool& pool, FormStack& stack, bool part_of_longer_sc) { constexpr int reg_input_1 = 0; constexpr int reg_input_2 = 1; constexpr int reg_input_3 = 2; constexpr int reg_temp_1 = 10; constexpr int reg_temp_2 = 11; constexpr int kValInIf = 12; // only used if part of a longer sc. Form* longer_sc_src = nullptr; // the source (can be found without repopping) Form longer_sc_first_form; // the equivalent of the first form for a normal handle->proc RegisterAccess longer_sc_var; // the second temp var that only appears in this case. // check first. Matcher first_matcher = Matcher::op(GenericOpMatcher::condition(IR2_Condition::Kind::NONZERO), {Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::L32_NOT_FALSE_CBOOL), {Matcher::any_reg(reg_input_1)})}); // if we're part of a longer, the first will actually be (being (set! foo <>) (... foo)) // so let's strip out the set, then remember what was being set. If it all works out, // we can just substitute the <> into the macro if (part_of_longer_sc) { if (first->size() != 2) { return nullptr; } auto as_var_set = dynamic_cast(first->elts().at(0)); if (!as_var_set) { return nullptr; } longer_sc_var = as_var_set->dst(); longer_sc_src = as_var_set->src(); longer_sc_first_form.elts().push_back(first->elts().at(1)); first = &longer_sc_first_form; } auto first_result = match(first_matcher, first); if (!first_result.matched) { return nullptr; } // auto first_use_of_in = *first_result.maps.regs.at(reg_input_1); // lg::print("reg1: {}\n", first_use_of_in.to_string(env)); auto setup_matcher = Matcher::set_var( Matcher::deref(Matcher::any_reg(reg_input_2), false, {DerefTokenMatcher::string("process"), DerefTokenMatcher::integer(0)}), reg_temp_1); auto if_matcher = Matcher::if_no_else( Matcher::op( GenericOpMatcher::fixed(FixedOperatorKind::EQ), {Matcher::deref(Matcher::any_reg(reg_input_3), false, {DerefTokenMatcher::string("pid")}), Matcher::deref(Matcher::any_reg(reg_temp_2), false, {DerefTokenMatcher::string("pid")})}), Matcher::any(kValInIf)); auto second_matcher = Matcher::begin({setup_matcher, if_matcher}); auto second_result = match(second_matcher, second); if (!second_result.matched) { return nullptr; } auto in1 = *first_result.maps.regs.at(reg_input_1); auto in2 = *second_result.maps.regs.at(reg_input_2); auto in3 = *second_result.maps.regs.at(reg_input_3); auto in_name = in1.to_string(env); if (in_name != in2.to_string(env)) { return nullptr; } if (in_name != in3.to_string(env)) { return nullptr; } auto temp_name = second_result.maps.regs.at(reg_temp_1)->to_string(env); if (temp_name != second_result.maps.regs.at(reg_temp_2)->to_string(env)) { return nullptr; } std::optional cast_type_for_result; auto val_in_if = try_strip_cast_get_var(second_result.maps.forms.at(kValInIf), cast_type_for_result); if (!val_in_if || temp_name != val_in_if->to_string(env)) { return nullptr; } const auto& temp_use_def = env.get_use_def_info(*second_result.maps.regs.at(reg_temp_1)); if (temp_use_def.use_count() != 2 || temp_use_def.def_count() != 1) { lg::error("failed usedef: {} {}", temp_use_def.use_count(), temp_use_def.def_count()); return nullptr; } if (part_of_longer_sc) { // check that our temporary name matches (it's the var used inside the macro) if (in_name != longer_sc_var.to_string(env)) { lg::error("failed var name: {} vs {}", temp_name, longer_sc_var.to_string(env)); return nullptr; } // check that our temporary has the right usage pattern. const auto& outer_temp_usedef = env.get_use_def_info(longer_sc_var); if (outer_temp_usedef.use_count() != 3 || outer_temp_usedef.def_count() != 1) { lg::error("failed usedef2: {} {}", outer_temp_usedef.use_count(), outer_temp_usedef.def_count()); return nullptr; } return maybe_cast( pool.alloc_element( GenericOperator::make_function( pool.alloc_single_element_form(nullptr, "handle->process")), longer_sc_src), cast_type_for_result, pool); } else { // modify use def: auto* menv = const_cast(&env); menv->disable_use(in2); menv->disable_use(in3); auto repopped = stack.pop_reg(in1, {}, env, true); // lg::print("repopped: {}\n", repopped->to_string(env)); if (!repopped) { repopped = var_to_form(in1, pool); } return maybe_cast( pool.alloc_element( GenericOperator::make_function( pool.alloc_single_element_form(nullptr, "handle->process")), repopped), cast_type_for_result, pool); } } } // namespace decompiler