#include #include #include "Form.h" #include "FormStack.h" #include "GenericElementMatcher.h" #include "common/goos/PrettyPrinter.h" #include "common/log/log.h" #include "common/type_system/state.h" #include "common/util/Assert.h" #include "common/util/BitUtils.h" #include "common/util/print_float.h" #include "decompiler/IR2/ExpressionHelpers.h" #include "decompiler/IR2/bitfields.h" #include "decompiler/ObjectFile/LinkedObjectFile.h" #include "decompiler/util/DecompilerTypeSystem.h" #include "decompiler/util/data_decompile.h" #include "decompiler/util/type_utils.h" /* * TODO * - use var_to_form over expressions for vars * - check out if we can push/pop variables instead of registers? */ /*! * Basic idea: push partial expressions to the stack and pop them off as they are used. * Leftovers are left on the stack and flushed out as set! * But the challenge is knowing it's safe to pop something off of the stack. * If the value is used after the read again, then it's not. * * The tricky situation is to accidentally generate this * [simplified, we would never group like this, but similar things are possible] * (+ s5 (begin (set! s5 z) (+ x y))) * when the value of s5 used is after the (set! s5 z). * * To avoid that case, we make sure that anything after s5 in the expression cannot modify s5. If it * does, we just don't do the expression building and leave it as smaller expressions. To accomplish * this, we submit a batch of registers to pop, and the stack takes care of making sure this * property will hold. * * But what about * (+ (* s5 s4) (begin (set! s5 z) (+ x y))) * Luckily this isn't a problem. The actual (* s5 s4) will only be inserted if the multiply * instruction actually occurs before the second term in the outer addition. * The issue only occurs when a pop fails and we just insert a variable name. * In other words, this variable "insert" is the only that lets us bypass ordering. * (which makes me wonder if I should have solved this by being more careful with that... * but I have no immediate ideas unless we allow backtracking in popping which is bad) * * Now sometimes it's too hard to figure out exactly all of the variables we might pop and do * it all in one batch. We pop one thing, but we know that there are registers it shouldn't modify. * Instead of the barrier register approach, we do something easier: explicitly forbid * "outside of expression register variable side effects". * This means that you modify a register that's visible outside of the expression. Confusingly, * memory access doesn't count as a side effect and register read/writes that are part of a * chained together expression do not count. * This is kind of a hacky workaround that I'd really like to remove eventually. * I think it can basically be solved by being strategic in when we update from stack and we can * avoid the highly general "update some unknown vector of unknown things from the stack" */ namespace decompiler { namespace { Form* strip_pcypld_64(Form* in) { auto m = match(Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::PCPYLD), {Matcher::integer(0), Matcher::any(0)}), in); if (m.matched) { return m.maps.forms.at(0); } else { return in; } } std::optional get_goal_float_constant(FormElement* in) { auto as_fc = dynamic_cast(in); if (as_fc) { return as_fc->value(); } return {}; } std::optional get_goal_float_constant(Form* in) { auto elt = in->try_as_single_element(); if (elt) { return get_goal_float_constant(elt); } else { return {}; } } bool cond_has_only_single_elements(CondWithElseElement* in) { for (auto& entry : in->entries) { if (entry.body->elts().size() > 1) { return false; } } if (in->else_ir->elts().size() > 1) { return false; } return true; } } // namespace Form* try_cast_simplify(Form* in, const TypeSpec& new_type, FormPool& pool, const Env& env, bool tc_pass) { auto in_as_cast = dynamic_cast(in->try_as_single_element()); if (in_as_cast && in_as_cast->type() == new_type) { return in; // no need to cast again, it already has it! } auto in_as_reslump = in->try_as_element(); if (in_as_reslump) { in_as_reslump->apply_cast(new_type); return in; } if (env.version >= GameVersion::Jak2) { if (new_type == TypeSpec("float")) { auto ic = get_goal_integer_constant(in, env); if (ic) { // ASSERT(*ic <= UINT32_MAX); ASSERT((s64)*ic == (s64)(s32)*ic); float f; memcpy(&f, &ic.value(), sizeof(float)); return pool.form(f); } } } if (new_type == TypeSpec("meters")) { auto fc = get_goal_float_constant(in); if (!fc && env.version >= GameVersion::Jak2) { auto ic = get_goal_integer_constant(in, env); if (ic) { ASSERT((s64)*ic == (s64)(s32)*ic); float f; memcpy(&f, &ic.value(), sizeof(float)); fc = f; } } if (fc) { double div = (double)*fc / METER_LENGTH; // GOOS will use doubles here if (div * METER_LENGTH == *fc) { return pool.form( GenericOperator::make_function(pool.form("meters")), pool.form(float_to_string(div, false))); } else { lg::error("Floating point value {} could not be converted to meters.", *fc); } } } else if (new_type == TypeSpec("degrees")) { auto fc = get_goal_float_constant(in); if (fc) { double div = (double)*fc / DEGREES_LENGTH; // GOOS will use doubles here if (div * DEGREES_LENGTH == *fc) { return pool.form( GenericOperator::make_function(pool.form("degrees")), pool.form(div)); } else { lg::error("Floating point value {} could not be converted to degrees.", *fc); } } } else if (new_type == TypeSpec("handle")) { auto in_generic = in->try_as_element(); if (in_generic && (in_generic->op().is_fixed(FixedOperatorKind::PROCESS_TO_HANDLE) || in_generic->op().is_fixed(FixedOperatorKind::PPOINTER_TO_HANDLE))) { return in; } } else if (new_type == TypeSpec("process")) { auto in_generic = in->try_as_element(); if (in_generic && in_generic->op().is_fixed(FixedOperatorKind::PPOINTER_TO_PROCESS)) { return in; } } else if (new_type == TypeSpec("time-frame")) { auto ic = get_goal_integer_constant(in, env); if (ic) { s64 value = *ic; if (std::abs(value) <= 1) { // if they used a 1 as a time-frame, they likely just want to literally remove 1 // instead of (seconds 0.0034) or whatever the result would be. // also 0 is decompiled as just 0. return pool.form(SimpleAtom::make_int_constant(value)); } return pool.form( fmt::format("(seconds {})", fixed_point_to_string(value, TICKS_PER_SECOND))); } else { // not a constant like (seconds 2), probably an expression or function call. we pretend that a // cast to int happened instead. // TODO hardcode cases for rand-vu-int-range: // (rand-vu-int-range 1200 2400) -> (rand-vu-int-range (seconds 4) (seconds 8)) // return try_cast_simplify(in, TypeSpec("int"), pool, env, tc_pass); auto g = dynamic_cast(in->try_as_single_element()); if (g && g->op().kind() == GenericOperator::Kind::FUNCTION_EXPR) { auto f = dynamic_cast(g->op().func()->try_as_single_element()); if (f && f->expr().is_identity() && f->expr().get_arg(0).is_sym_val()) { auto& func_name = f->expr().get_arg(0).get_str(); if (func_name == "rand-vu-int-range" || func_name == "nav-enemy-rnd-int-range") { std::vector new_forms; for (auto& e : g->elts()) { auto as_atom_expr = dynamic_cast(e->try_as_single_element()); if (as_atom_expr && as_atom_expr->expr().is_identity()) { new_forms.push_back(try_cast_simplify(e, TypeSpec("time-frame"), pool, env, true)); } else { new_forms.push_back(e); } } // return a new rand-vu-int-range with casted args, and its own cast is stripped for // free! return pool.alloc_single_element_form(in->parent_element, g->op(), new_forms); } } } if (tc_pass) { return in; } else { return nullptr; } } } auto type_info = env.dts->ts.lookup_type_allow_partial_def(new_type); auto bitfield_info = dynamic_cast(type_info); auto enum_info = dynamic_cast(type_info); auto* in_as_cond = in->try_as_element(); // try to fix (the-as (if foo 12 13)) type stuff by applying the casts inside a cond if: // - it's casting to a bitfield/enum (this could be expanded to more in the future if needed) // - the cond has an explicit else case (otherwise the #f from not hitting any case...) // - it's not a sound-id - these are basically used like ints so it gets worse // - the cond doesn't have multiple entries in the body // in theory this could be better if we could only apply a cast to the last element in the body // but this is a bit too much work for exactly 1 case in jak 1. if ((bitfield_info || enum_info) && in_as_cond && type_info->get_name() != "sound-id" && cond_has_only_single_elements(in_as_cond)) { for (auto& cas : in_as_cond->entries) { cas.body = try_cast_simplify(cas.body, new_type, pool, env, tc_pass); cas.body->parent_element = in_as_cond; } in_as_cond->else_ir = try_cast_simplify(in_as_cond->else_ir, new_type, pool, env, tc_pass); in_as_cond->else_ir->parent_element = in_as_cond; return in; } if (bitfield_info) { // todo remove this. if (bitfield_info->get_load_size() == 8) { in = strip_pcypld_64(in); } return cast_to_bitfield(bitfield_info, new_type, pool, env, in); } if (enum_info) { if (enum_info->is_bitfield()) { return cast_to_bitfield_enum(enum_info, new_type, pool, env, in); } else { return cast_to_int_enum(enum_info, new_type, pool, env, in); } } auto as_atom = form_as_atom(in); if (as_atom && as_atom->is_var()) { if (env.get_variable_type(as_atom->var(), true) == new_type) { // we are a variable with the right type. return pool.form(*as_atom, true); } } if (tc_pass) { return in; } else { return nullptr; } } bool Form::has_side_effects() { bool has_side_effect = false; apply([&](FormElement* elt) { if (dynamic_cast(elt)) { has_side_effect = true; } }); return has_side_effect; } bool FormElement::has_side_effects() { bool has_side_effect = false; apply([&](FormElement* elt) { if (dynamic_cast(elt)) { has_side_effect = true; } }); return has_side_effect; } namespace { bool is_power_of_two(int in, int* out) { int x = 1; for (int i = 0; i < 32; i++) { if (x == in) { *out = i; return true; } x = x * 2; } return false; } /*! * Imagine: * x = foo * { // some macro/inlined thing * read from x * return x; * } * * and you want to transform it to * x = some_macro(foo, blah, ...) * * this will get you foo (and pop it from the stack), assuming the stack is sitting right after the * point where the inline thing evaluated foo. * * For later book-keeping of reg use, if it gets you something new, it will set found_orig_out, * and also give you the regaccess for the x of the x = foo. * * If you use this, you are responsible for adding code that sets x again. */ Form* repop_passthrough_arg(Form* in, FormStack& stack, const Env& env, RegisterAccess* orig_out, bool* found_orig_out) { *found_orig_out = false; auto as_atom = form_as_atom(in); if (as_atom && as_atom->is_var()) { return stack.pop_reg(as_atom->var().reg(), {}, env, true, -1, orig_out, found_orig_out); } return in; } /*! * Create a form which represents a variable. */ Form* var_to_form(const RegisterAccess& var, FormPool& pool) { return pool.form(SimpleAtom::make_var(var)); } /*! * Pop values of off the expression stack * @param vars : list of variables to pop. In order of source code evaluation. * @param env : the decompilation environment * @param pool : form allocation pool * @param stack : stack to pop from * @param output : list of locations to push results. * @param consumes : if you have a different list of variables that are consumed by this operation. */ void pop_helper(const std::vector& vars, const Env& env, FormPool& pool, FormStack& stack, const std::vector*>& output, bool allow_side_effects, const std::optional& consumes = std::nullopt, const std::vector& times_used = {}) { // to submit to stack to attempt popping std::vector submit_regs; // submit_reg[i] is for var submit_reg_to_var[i] std::vector submit_reg_to_var; // build submission for stack std::unordered_map reg_counts; for (auto& v : vars) { reg_counts[v.reg()]++; } for (size_t var_idx = 0; var_idx < vars.size(); var_idx++) { const auto& var = vars.at(var_idx); auto& ri = env.reg_use().op.at(var.idx()); RegSet consumes_to_use = consumes.value_or(ri.consumes); if (consumes_to_use.find(var.reg()) != consumes_to_use.end()) { if (reg_counts.at(var.reg()) == 1) { // we consume the register, so it's safe to try popping. int times = 1; if (!times_used.empty()) { times = times_used.at(var_idx); } auto& use_def = env.get_use_def_info(var); if (use_def.use_count() == times && use_def.def_count() == 1) { submit_reg_to_var.push_back(var_idx); submit_regs.push_back(var.reg()); } else { // auto var_id = env.get_program_var_id(var); // lg::print( // "Unsafe to pop {}: used {} times, def {} times, expected use {} ({} {} rd: // {}) ({} " // "{})\n", // var.to_string(env), use_def.use_count(), use_def.def_count(), times, // var.reg().to_string(), var.idx(), var.mode() == AccessMode::READ, // var_id.reg.to_string(), var_id.id); // if (var.to_string(env) == "a3-0") { // for (auto& use : use_def.uses) { // if (!use.disabled) { // lg::print(" at instruction {}\n", use.op_id); // } // } // } } } } } // submit and get a result! If the stack has nothing to pop, the result here may be nullptr. std::vector pop_result; // loop in reverse (later vals first) for (size_t i = submit_regs.size(); i-- > 0;) { // figure out what var we are: auto var_idx = submit_reg_to_var.at(i); // anything _less_ than this should be unmodified by the pop // it's fine to modify yourself in your pop. RegSet pop_barrier_regs; for (size_t j = 0; j < var_idx; j++) { pop_barrier_regs.insert(vars.at(j).reg()); } // do the pop, with the barrier to prevent out-of-sequence popping. pop_result.push_back( stack.pop_reg(submit_regs.at(i), pop_barrier_regs, env, allow_side_effects)); } // now flip back to the source order for making the final result std::reverse(pop_result.begin(), pop_result.end()); // final result forms. Will be nullptr if: we didn't try popping OR popping from stack failed. std::vector forms; forms.resize(vars.size(), nullptr); if (!pop_result.empty()) { // success! for (size_t i = 0; i < submit_regs.size(); i++) { // fill out vars from our submission forms.at(submit_reg_to_var.at(i)) = pop_result.at(i); } } // write the output for (size_t i = 0; i < forms.size(); i++) { if (forms.at(i)) { // we got a form. inline these in the result for (auto x : forms.at(i)->elts()) { output.at(i)->push_back(x); } } else { // we got nothing, just insert the variable name. output.at(i)->push_back(pool.alloc_element( SimpleAtom::make_var(vars.at(i)).as_expr(), vars.at(i).idx())); } } } /*! * This should be used to generate all casts. */ Form* cast_form(Form* in, const TypeSpec& new_type, FormPool& pool, const Env& env, bool tc_pass = false) { auto result = try_cast_simplify(in, new_type, pool, env, tc_pass); if (result) { return result; } return pool.form(new_type, in); } /*! * Pop each variable in the input list into a form. The variables should be given in the order * they are evaluated in the source. It is safe to put the result of these in the same expression. * This uses the barrier register approach, but it is only effective if you put all registers * appearing at the same level. */ std::vector pop_to_forms(const std::vector& vars, const Env& env, FormPool& pool, FormStack& stack, bool allow_side_effects, const std::optional& consumes = std::nullopt, const std::vector& times_to_use = {}) { std::vector forms; std::vector> forms_out; std::vector*> form_ptrs; forms_out.resize(vars.size()); form_ptrs.reserve(vars.size()); forms.reserve(vars.size()); for (auto& x : forms_out) { form_ptrs.push_back(&x); } pop_helper(vars, env, pool, stack, form_ptrs, allow_side_effects, consumes, times_to_use); for (auto& x : forms_out) { forms.push_back(pool.alloc_sequence_form(nullptr, x)); } // add casts, if needed. ASSERT(vars.size() == forms.size()); for (size_t i = 0; i < vars.size(); i++) { auto atom = form_as_atom(forms[i]); bool is_var = atom && atom->is_var(); auto cast = env.get_user_cast_for_access(vars[i]); // only cast if we didn't get a var (compacting expressions). // there is a separate system for casting variables that will do a better job. if (cast && !is_var) { forms[i] = cast_form(forms[i], *cast, pool, env); // pool.form(*cast, forms[i]); } } return forms; } /*! * type == float (exactly)? */ bool is_float_type(const Env& env, int my_idx, RegisterAccess var) { auto type = env.get_types_before_op(my_idx).get(var.reg()).typespec(); return env.dts->ts.tc(TypeSpec("float"), type); } /*! * type == int (exactly)? * note: time-frame is special. */ bool is_int_type(const TypeSpec& type) { return type == TypeSpec("int") || type == TypeSpec("time-frame"); } bool is_int_type(const Env& env, int my_idx, RegisterAccess var) { auto type = env.get_types_before_op(my_idx).get(var.reg()).typespec(); return is_int_type(type); } bool is_pointer_type(const Env& env, int my_idx, RegisterAccess var) { auto type = env.get_types_before_op(my_idx).get(var.reg()).typespec(); return type.base_type() == "pointer"; } /*! * type == uint (exactly)? */ bool is_uint_type(const TypeSpec& type) { return type == TypeSpec("uint"); } bool is_uint_type(const Env& env, int my_idx, RegisterAccess var) { auto type = env.get_types_before_op(my_idx).get(var.reg()).typespec(); return is_uint_type(type); } bool is_ptr_or_child(const Env& env, int my_idx, RegisterAccess var, bool) { // Now that decompiler types are synced up properly, we don't want this. // auto type = as_var ? env.get_variable_type(var, true).base_type() // : env.get_types_before_op(my_idx).get(var.reg()).typespec().base_type(); auto type = env.get_types_before_op(my_idx).get(var.reg()).typespec().base_type(); return type == "pointer"; } } // namespace /*! * Update a form to use values from the stack. Won't push to the stack. * This should be used to update a Form that immediately follows something being pushed. * Will only change the first element of the form - anything after that will jump sequencing */ void Form::update_children_from_stack(const Env& env, FormPool& pool, FormStack& stack, bool allow_side_effects) { ASSERT(!m_elements.empty()); std::vector new_elts; for (size_t i = 0; i < m_elements.size(); i++) { if (i == 0) { // only bother doing the first one. if (!m_elements[i]->is_popped()) { m_elements[i]->update_from_stack(env, pool, stack, &new_elts, allow_side_effects); } else { new_elts.push_back(m_elements[i]); } } else { new_elts.push_back(m_elements[i]); } } for (auto& x : new_elts) { x->parent_form = this; } m_elements = new_elts; } /*! * Default update_from_stack for an element if no specific one is provided. */ void FormElement::update_from_stack(const Env& env, FormPool&, FormStack&, std::vector*, bool) { throw std::runtime_error(fmt::format("update_from_stack NYI for {}", to_string(env))); } namespace { /*! * Try to make a pretty looking constant out of value for comparing to something of type. * If we can't do anything nice, return nullptr. */ Form* try_make_constant_for_compare(Form* value, const TypeSpec& type, FormPool& pool, const Env& env) { if (get_goal_integer_constant(value, env) && (env.dts->ts.try_enum_lookup(type) || type == TypeSpec("time-frame"))) { return cast_form(value, type, pool, env); } return nullptr; } Form* make_cast_if_needed(Form* in, const TypeSpec& in_type, const TypeSpec& out_type, FormPool& pool, const Env& env) { if (in_type == out_type) { return in; } if (out_type == TypeSpec("float") && env.dts->ts.tc(TypeSpec("float"), in_type)) { return in; } if (out_type == TypeSpec("time-frame") && in_type == TypeSpec("int")) { // we want a time-frame from an int. return try_cast_simplify(in, TypeSpec("time-frame"), pool, env, true); } return cast_form(in, out_type, pool, env); } std::vector make_casts_if_needed(const std::vector& in, const std::vector& in_types, const TypeSpec& out_type, FormPool& pool, const Env& env) { std::vector out; TypeSpec actual_out = out_type; ASSERT(in.size() == in_types.size()); for (size_t i = 0; i < in_types.size(); i++) { // check if there's variables of aliased types. we will cast to those instead. if (!get_goal_integer_constant(in.at(i), env) && out_type == TypeSpec("int")) { if (in_types.at(i) == TypeSpec("time-frame")) { // if we want int, we can output time-frame as well actual_out = TypeSpec("time-frame"); } } else if (!get_goal_float_constant(in.at(i)) && out_type == TypeSpec("float")) { if (in_types.at(i) == TypeSpec("meters")) { // if we want float, we can output meters as well // actual_out = TypeSpec("meters"); } } } for (size_t i = 0; i < in_types.size(); i++) { out.push_back(make_cast_if_needed(in.at(i), in_types.at(i), actual_out, pool, env)); } return out; } } // namespace /*! * Update a LoadSourceElement from the stack. */ void LoadSourceElement::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { mark_popped(); m_addr->update_children_from_stack(env, pool, stack, allow_side_effects); // most of the time, the AtomicOpForm logic is able to figure the load, but sometimes // it's impossible before expressions: // ori a2, r0, 33708 // lw a3, *level*(s7) // daddu a2, a2, a3 // lq a2, 0(a2) /* if (m_load_source_ro && m_load_source_ro->offset == 0) { // maybe a case like above, try to improve } */ result->push_back(this); } namespace { FormElement* label_to_form_element(const Env& env, const SimpleAtom& atom, FormPool& pool) { auto lab = env.file->labels.at(atom.label()); if (env.file->is_string(lab.target_segment, lab.offset)) { auto str = env.file->get_goal_string(lab.target_segment, lab.offset / 4 - 1, false); return pool.alloc_element(str); } else { // look for a label hint: const auto& hint = env.file->label_db->lookup(lab.name); if (!hint.known) { throw std::runtime_error( fmt::format("Label {} was unknown in FormExpressionAnalysis.", hint.name)); } if (hint.is_value) { return nullptr; } if (hint.result_type.base_type() == "function") { return nullptr; } else { return pool.alloc_element(lab, hint); } } } } // namespace void SimpleExpressionElement::update_from_stack_identity(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { auto& arg = m_expr.get_arg(0); if (arg.is_var()) { auto forms = pop_to_forms({arg.var()}, env, pool, stack, allow_side_effects); for (auto x : forms.at(0)->elts()) { result->push_back(x); } } else if (arg.is_static_addr()) { auto as_label_form_element = label_to_form_element(env, arg, pool); if (as_label_form_element) { result->push_back(as_label_form_element); } else { result->push_back(this); } return; } else if (arg.is_sym_ptr() || arg.is_sym_val() || arg.is_int() || arg.is_empty_list() || arg.is_sym_val_ptr()) { result->push_back(this); return; } else { throw std::runtime_error(fmt::format( "SimpleExpressionElement::update_from_stack_identity NYI for {}", to_string(env))); } } bool u64_valid_for_float_constant(u64 in) { u32 top = in >> 32; if (top == 0 || top == UINT32_MAX) { return true; } else { return false; } } void SimpleExpressionElement::update_from_stack_gpr_to_fpr(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { auto src = m_expr.get_arg(0); auto src_type = env.get_types_before_op(src.var().idx()).get(src.var().reg()); std::vector src_fes; if (src.is_var()) { auto forms = pop_to_forms({src.var()}, env, pool, stack, allow_side_effects); for (auto x : forms.at(0)->elts()) { src_fes.push_back(x); } } else { src_fes = {this}; } // set ourself to identity. m_expr = src.as_expr(); if (env.dts->ts.tc(TypeSpec("float"), src_type.typespec())) { // got a float as an input, we can convert it to an FPR with no effect. for (auto x : src_fes) { result->push_back(x); } } else { if (env.version != GameVersion::Jak1) { auto frm = pool.alloc_sequence_form(nullptr, src_fes); if (src_fes.size() == 1) { auto int_constant = get_goal_integer_constant(frm, env); if (int_constant && u64_valid_for_float_constant(*int_constant)) { float flt; memcpy(&flt, &int_constant.value(), sizeof(float)); if (proper_float(flt)) { result->push_back(pool.alloc_element(flt)); return; } } } // converting something else to an FPR, put an expression around it. result->push_back(pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::GPR_TO_FPR), frm)); } else { result->push_back(pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::GPR_TO_FPR), pool.alloc_sequence_form(nullptr, src_fes))); } } } void SimpleExpressionElement::update_from_stack_fpr_to_gpr(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { auto src = m_expr.get_arg(0); auto src_type = env.get_types_before_op(m_my_idx).get(src.var().reg()); if (env.dts->ts.tc(TypeSpec("float"), src_type.typespec()) || src_type.typespec() == TypeSpec("int")) { // set ourself to identity. m_expr = src.as_expr(); // then go again. ASSERT(m_popped); m_popped = false; update_from_stack(env, pool, stack, result, allow_side_effects); } else { throw std::runtime_error(fmt::format("FPR -> GPR applied to a {} in {} at {}", src_type.print(), to_string(env), m_my_idx)); } } void SimpleExpressionElement::update_from_stack_div_s(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { if (is_float_type(env, m_my_idx, m_expr.get_arg(0).var()) && is_float_type(env, m_my_idx, m_expr.get_arg(1).var())) { // todo - check the order here auto args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var()}, env, pool, stack, allow_side_effects); auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::DIVISION), args.at(0), args.at(1)); result->push_back(new_form); } else { throw std::runtime_error( fmt::format("Floating point division attempted on invalid types at OP: [{}].", m_my_idx)); } } /*! * Update a two-argument form that uses two floats. */ void SimpleExpressionElement::update_from_stack_float_2(const Env& env, FixedOperatorKind kind, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { if (is_float_type(env, m_my_idx, m_expr.get_arg(0).var()) && is_float_type(env, m_my_idx, m_expr.get_arg(1).var())) { auto args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var()}, env, pool, stack, allow_side_effects); auto new_form = pool.alloc_element(GenericOperator::make_fixed(kind), args.at(0), args.at(1)); result->push_back(new_form); } else { auto type0 = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(0).var().reg()); auto type1 = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(1).var().reg()); throw std::runtime_error(fmt::format( "[OP: {}] - Floating point math attempted on invalid types: {} and {} in op {}.", m_my_idx, type0.print(), type1.print(), to_string(env))); } } namespace { std::vector get_math_op_elements(Form* in, FixedOperatorKind kind) { auto gen_elt = in->try_as_element(); if (gen_elt && gen_elt->op().is_fixed(kind)) { return gen_elt->elts(); } else { return {in}; } } FormElement* make_and_compact_math_op(Form* arg0, Form* arg1, const std::optional& arg0_cast, const std::optional& arg1_cast, FormPool& pool, const Env& env, FixedOperatorKind operator_kind, bool inline_first, bool inline_second) { if (!arg1_cast) { std::vector arg0_elts; if (inline_first) { arg0_elts = get_math_op_elements(arg0, operator_kind); } else { arg0_elts = {arg0}; } ASSERT(!arg0_elts.empty()); if (arg0_cast) { arg0_elts.front() = cast_form(arg0_elts.front(), *arg0_cast, pool, env); } // it's fine to only cast the first thing here - the rest are already cast properly. std::vector arg1_elts; if (inline_second) { arg1_elts = get_math_op_elements(arg1, operator_kind); } else { arg1_elts = {arg1}; } ASSERT(!arg1_elts.empty()); if (arg1_cast) { arg1_elts.front() = cast_form(arg1_elts.front(), *arg1_cast, pool, env); } // add all together arg0_elts.insert(arg0_elts.end(), arg1_elts.begin(), arg1_elts.end()); return pool.alloc_element(GenericOperator::make_fixed(operator_kind), arg0_elts); } else { if (arg0_cast) { arg0 = cast_form(arg0, *arg0_cast, pool, env); } if (arg1_cast) { arg1 = cast_form(arg1, *arg1_cast, pool, env); } return pool.alloc_element(GenericOperator::make_fixed(operator_kind), arg0, arg1); } } } // namespace /*! * Update a two-argument form that uses two floats. * This is for operations like * and + that can be nested * (* (* a b)) -> (* a b c) * Note that we only apply this to the _first_ argument to keep the order of operations the same. */ void SimpleExpressionElement::update_from_stack_float_2_nestable(const Env& env, FixedOperatorKind kind, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { if (is_float_type(env, m_my_idx, m_expr.get_arg(0).var()) && is_float_type(env, m_my_idx, m_expr.get_arg(1).var())) { auto args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var()}, env, pool, stack, allow_side_effects); auto new_form = make_and_compact_math_op(args.at(0), args.at(1), {}, {}, pool, env, kind, true, false); result->push_back(new_form); } else { auto type0 = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(0).var().reg()); auto type1 = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(1).var().reg()); throw std::runtime_error(fmt::format( "[OP: {}] - Floating point math attempted on invalid types: {} and {} in op {}.", m_my_idx, type0.print(), type1.print(), to_string(env))); } } void SimpleExpressionElement::update_from_stack_float_1(const Env& env, FixedOperatorKind kind, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { if (is_float_type(env, m_my_idx, m_expr.get_arg(0).var())) { auto args = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects); auto new_form = pool.alloc_element(GenericOperator::make_fixed(kind), args.at(0)); result->push_back(new_form); } else { throw std::runtime_error( fmt::format("Floating point division attempted on invalid types at OP: [{}].", m_my_idx)); } } void SimpleExpressionElement::update_from_stack_si_1(const Env& env, FixedOperatorKind kind, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { auto in_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(0).var().reg()).typespec(); auto arg = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects).at(0); result->push_back(pool.alloc_element( GenericOperator::make_fixed(kind), make_cast_if_needed(arg, in_type, TypeSpec("int"), pool, env))); } void SimpleExpressionElement::update_from_stack_add_i(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { auto& arg0_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(0).var().reg()); auto arg0_i = is_int_type(arg0_type.typespec()); auto arg0_u = is_uint_type(arg0_type.typespec()); bool arg1_reg = m_expr.get_arg(1).is_var(); bool arg1_i = true; bool arg1_u = true; bool arg1_timeframe = false; if (arg1_reg) { auto arg1_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(1).var().reg()).typespec(); arg1_timeframe = arg1_type == TypeSpec("time-frame"); arg1_i = is_int_type(arg1_type); arg1_u = is_uint_type(arg1_type); } std::vector args; if (arg1_reg) { args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var()}, env, pool, stack, allow_side_effects); } else { // arg1 might be a label. // do arg0 like a normal var args = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects); // then try to simplify the label if (m_expr.get_arg(1).is_label()) { auto as_lab = label_to_form_element(env, m_expr.get_arg(1), pool); if (as_lab) { args.push_back(pool.alloc_single_form(nullptr, as_lab)); } else { args.push_back(pool.form(m_expr.get_arg(1))); } } else { args.push_back(pool.form(m_expr.get_arg(1))); } } bool arg0_ptr = is_ptr_or_child(env, m_my_idx, m_expr.get_arg(0).var(), true); bool arg1_ptr = false; // Look for getting an address inside of an object. // (+ process). array style access with a stride of 1. // in the case, both are vars. if (arg1_reg) { // lookup types. auto& arg1_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(1).var().reg()); arg1_ptr = is_ptr_or_child(env, m_my_idx, m_expr.get_arg(1).var(), true); // try to find symbol to string stuff auto arg0_int = get_goal_integer_constant(args.at(0), env); if (arg0_int && ((s64)*arg0_int == SYMBOL_TO_STRING_MEM_OFFSET_DECOMP[env.version]) && allowable_base_type_for_symbol_to_string(arg1_type.typespec())) { result->push_back(pool.alloc_element(args.at(1))); return; } auto addition_matcher = GenericOpMatcher::or_match({GenericOpMatcher::fixed(FixedOperatorKind::ADDITION), GenericOpMatcher::fixed(FixedOperatorKind::ADDITION_PTR)}); if (arg0_type.kind == TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR) { // try to see if this is valid, from the type system. FieldReverseLookupInput input; input.offset = arg0_type.get_integer_constant(); input.stride = 1; input.base_type = arg1_type.typespec(); auto out = env.dts->ts.reverse_field_lookup(input); if (out.success && out.has_variable_token()) { // it is. now we have to modify things // first, look for the index auto arg0_matcher = Matcher::op(addition_matcher, {Matcher::any(0), Matcher::integer(input.offset)}); auto match_result = match(arg0_matcher, args.at(0)); if (match_result.matched) { bool used_index = false; std::vector tokens; for (auto& tok : out.tokens) { if (tok.kind == FieldReverseLookupOutput::Token::Kind::VAR_IDX) { ASSERT(!used_index); used_index = true; tokens.push_back(DerefToken::make_int_expr(match_result.maps.forms.at(0))); } else { tokens.push_back(to_token(tok)); } } ASSERT(used_index); result->push_back(pool.alloc_element(args.at(1), out.addr_of, tokens)); return; } else { throw std::runtime_error( fmt::format("Failed to match for stride 1 address access with add: {}", args.at(0)->to_string(env))); } } } else if (arg0_type.kind == TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR_MULT) { // try to see if this is valid, from the type system. FieldReverseLookupInput input; input.offset = arg0_type.get_add_int_constant(); input.stride = arg0_type.get_mult_int_constant(); input.base_type = arg1_type.typespec(); auto out = env.dts->ts.reverse_field_lookup(input); if (out.success && out.has_variable_token()) { // it is. now we have to modify things // first, look for the index int p2; if (is_power_of_two(input.stride, &p2)) { // (+ (shl (-> a0-0 reg-count) 3) 28) auto arg0_matcher = Matcher::op(addition_matcher, {Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION), {Matcher::any(0), Matcher::integer(input.stride)}), Matcher::integer(input.offset)}); auto match_result = match(arg0_matcher, args.at(0)); if (match_result.matched) { bool used_index = false; std::vector tokens; for (auto& tok : out.tokens) { if (tok.kind == FieldReverseLookupOutput::Token::Kind::VAR_IDX) { ASSERT(!used_index); used_index = true; tokens.push_back(DerefToken::make_int_expr(match_result.maps.forms.at(0))); } else { tokens.push_back(to_token(tok)); } } ASSERT(used_index); result->push_back(pool.alloc_element(args.at(1), out.addr_of, tokens)); return; } else { throw std::runtime_error( fmt::format("Failed to match for stride (power 2 {}) with add: {}", input.stride, args.at(0)->to_string(env))); } } else { auto int_matcher = Matcher::integer(input.stride); auto arg0_matcher = Matcher::op( addition_matcher, {Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION), {Matcher::match_or({Matcher::cast("uint", int_matcher), int_matcher}), Matcher::any(0)}), Matcher::integer(input.offset)}); auto match_result = match(arg0_matcher, args.at(0)); if (match_result.matched) { bool used_index = false; std::vector tokens; for (auto& tok : out.tokens) { if (tok.kind == FieldReverseLookupOutput::Token::Kind::VAR_IDX) { ASSERT(!used_index); used_index = true; tokens.push_back(DerefToken::make_int_expr(match_result.maps.forms.at(0))); } else { tokens.push_back(to_token(tok)); } } ASSERT(used_index); result->push_back(pool.alloc_element(args.at(1), out.addr_of, tokens)); return; } else { throw std::runtime_error( fmt::format("Failed to match for stride (non power 2 {}) with add: {}", input.stride, args.at(0)->to_string(env))); } } } } else if (arg1_type.kind == TP_Type::Kind::PRODUCT_WITH_CONSTANT && arg0_type.kind == TP_Type::Kind::TYPESPEC && arg0_type.typespec().base_type() == "inline-array") { FieldReverseLookupInput rd_in; rd_in.deref = std::nullopt; rd_in.stride = arg1_type.get_multiplier(); rd_in.offset = 0; rd_in.base_type = arg0_type.typespec(); auto rd = env.dts->ts.reverse_field_multi_lookup(rd_in); int idx_of_success = -1; if (rd.success) { for (int i = 0; i < (int)rd.results.size(); i++) { if (rd.results.at(i).has_variable_token()) { idx_of_success = i; break; } } } if (idx_of_success >= 0) { auto& rd_ok = rd.results.at(idx_of_success); auto stride_matcher = Matcher::match_or( {Matcher::cast("uint", Matcher::integer(rd_in.stride)), Matcher::cast("int", Matcher::integer(rd_in.stride)), Matcher::integer(rd_in.stride)}); auto arg1_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 match_result = match(arg1_matcher, args.at(1)); if (match_result.matched) { bool used_index = false; std::vector tokens; for (auto& tok : rd_ok.tokens) { if (tok.kind == FieldReverseLookupOutput::Token::Kind::VAR_IDX) { ASSERT(!used_index); used_index = true; tokens.push_back(DerefToken::make_int_expr(match_result.maps.forms.at(0))); } else { tokens.push_back(to_token(tok)); } } ASSERT(used_index); result->push_back(pool.alloc_element(args.at(0), rd_ok.addr_of, tokens)); return; } else { throw std::runtime_error(fmt::format( "Failed to match product_with_constant inline array access 1 at Op. {}", m_my_idx)); } } } else if (arg0_type.kind == TP_Type::Kind::PRODUCT_WITH_CONSTANT && arg1_type.kind == TP_Type::Kind::TYPESPEC && arg1_type.typespec().base_type() == "inline-array") { FieldReverseLookupInput rd_in; rd_in.deref = std::nullopt; rd_in.stride = arg0_type.get_multiplier(); rd_in.offset = 0; rd_in.base_type = arg1_type.typespec(); auto rd = env.dts->ts.reverse_field_multi_lookup(rd_in); int idx_of_success = -1; if (rd.success) { for (int i = 0; i < (int)rd.results.size(); i++) { if (rd.results.at(i).has_variable_token()) { idx_of_success = i; break; } } } // lg::print("here {} {} {}\n", rd_in.base_type.print(), rd.success, // rd.has_variable_token()); if (idx_of_success >= 0) { auto& rd_ok = rd.results.at(idx_of_success); auto arg0_matcher = Matcher::match_or( {Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION), {Matcher::any(0), Matcher::integer(rd_in.stride)}), Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION), {Matcher::match_or({Matcher::cast("uint", Matcher::integer(rd_in.stride)), Matcher::integer(rd_in.stride)}), Matcher::any(0)})}); auto match_result = match(arg0_matcher, args.at(0)); if (match_result.matched) { bool used_index = false; std::vector tokens; for (auto& tok : rd_ok.tokens) { if (tok.kind == FieldReverseLookupOutput::Token::Kind::VAR_IDX) { ASSERT(!used_index); used_index = true; tokens.push_back(DerefToken::make_int_expr(match_result.maps.forms.at(0))); } else { tokens.push_back(to_token(tok)); } } ASSERT(used_index); result->push_back(pool.alloc_element(args.at(1), rd_ok.addr_of, tokens)); return; } else { // TODO - output error to IR lg::error("Bad {} at OP: {}", args.at(0)->to_string(env), m_my_idx); throw std::runtime_error("Failed to match product_with_constant inline array access 2."); } } } else if (arg0_type.kind == TP_Type::Kind::INTEGER_CONSTANT) { // try to see if this is valid, from the type system. FieldReverseLookupInput input; input.offset = arg0_type.get_integer_constant(); input.stride = 0; input.base_type = arg1_type.typespec(); auto out = env.dts->ts.reverse_field_lookup(input); if (out.success && !out.has_variable_token()) { // it is. now we have to modify things // first, look for the index std::vector tokens; for (auto& tok : out.tokens) { tokens.push_back(to_token(tok)); } result->push_back(pool.alloc_element(args.at(1), out.addr_of, tokens)); return; } } } if (env.dts->ts.tc(TypeSpec("structure"), arg0_type.typespec()) && m_expr.get_arg(1).is_int() && arg0_type.kind != TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR) { auto type_info = env.dts->ts.lookup_type(arg0_type.typespec()); if (type_info->get_size_in_memory() == m_expr.get_arg(1).get_int()) { auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::ADDITION_PTR), args.at(0), args.at(1)); result->push_back(new_form); return; } } auto& name = env.func->guessed_name; if (name.kind == FunctionName::FunctionKind::METHOD && name.method_id == 7 && env.func->type.arg_count() == 3) { if ((env.dts->ts.tc(TypeSpec("structure"), arg0_type.typespec()) || arg0_type.typespec().base_type() == "inline-array") && (arg1_i || arg1_u)) { auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::ADDITION_PTR), args.at(0), args.at(1)); result->push_back(new_form); return; } } if (arg0_ptr && arg0_type.kind != TP_Type::Kind::INTEGER_CONSTANT_PLUS_VAR) { auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::ADDITION_PTR), args.at(0), args.at(1)); result->push_back(new_form); } else if (arg1_ptr && arg0_type.is_integer_constant()) { // this is a bit weird, but (&+ thing ) sometimes becomes (&+ thing). // in these cases, we flip the argument order. auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::ADDITION_PTR), args.at(1), args.at(0)); result->push_back(new_form); } else { std::optional arg0_cast, arg1_cast; if (!arg0_i && !arg0_u && arg0_type.typespec() != TypeSpec("binteger") && !env.dts->ts.tc(TypeSpec("integer"), arg0_type.typespec())) { arg0_cast = TypeSpec(arg0_i ? "int" : "uint"); } if (arg0_type.typespec() == TypeSpec("time-frame") && (!arg1_i || !arg1_reg)) { arg1_cast = TypeSpec("time-frame"); } else if (!arg1_i && !arg1_u) { arg1_cast = TypeSpec(arg0_i ? "int" : "uint"); } if (arg0_type.typespec() == TypeSpec("time-frame") && !arg1_timeframe) { auto as_generic = dynamic_cast(args.at(1)->try_as_single_element()); if (as_generic && as_generic->op().kind() == GenericOperator::Kind::FUNCTION_EXPR) { auto as_func_head = dynamic_cast( as_generic->op().func()->try_as_single_element()); if (as_func_head && as_func_head->expr().is_identity() && as_func_head->expr().get_arg(0).is_sym_val()) { auto& func_name = as_func_head->expr().get_arg(0).get_str(); if (func_name == "rand-vu-int-range") { arg1_cast = TypeSpec("time-frame"); } } } } result->push_back(make_and_compact_math_op(args.at(0), args.at(1), arg0_cast, arg1_cast, pool, env, FixedOperatorKind::ADDITION, true, true)); } } void SimpleExpressionElement::update_from_stack_mult_si(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { if (m_expr.get_arg(0).is_int()) { // annoyingly there's a mult3 v1, r0, v1 in jak 2. auto arg1_i = is_int_type(env, m_my_idx, m_expr.get_arg(1).var()); auto args = pop_to_forms({m_expr.get_arg(1).var()}, env, pool, stack, allow_side_effects); if (!arg1_i) { args.at(0) = pool.form(TypeSpec("int"), args.at(1)); } auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::MULTIPLICATION), pool.form(SimpleAtom::make_int_constant(m_expr.get_arg(0).get_int())), args.at(0)); result->push_back(new_form); } else { auto arg0_i = is_int_type(env, m_my_idx, m_expr.get_arg(0).var()); auto arg1_i = is_int_type(env, m_my_idx, m_expr.get_arg(1).var()); auto args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var()}, env, pool, stack, allow_side_effects); if (!arg0_i) { args.at(0) = pool.form(TypeSpec("int"), args.at(0)); } if (!arg1_i) { args.at(1) = pool.form(TypeSpec("int"), args.at(1)); } auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::MULTIPLICATION), args.at(0), args.at(1)); result->push_back(new_form); } } void SimpleExpressionElement::update_from_stack_force_si_2(const Env& env, FixedOperatorKind kind, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects, bool reverse) { auto arg0_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(0).var().reg()).typespec(); bool is_timeframe = arg0_type == TypeSpec("time-frame"); auto arg0_i = is_int_type(arg0_type); bool arg1_i = true; bool arg1_reg = m_expr.get_arg(1).is_var(); if (arg1_reg) { auto arg1_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(1).var().reg()).typespec(); // bool is_timeframe = arg1_type == TypeSpec("time-frame"); arg1_i = is_int_type(arg1_type); } else { ASSERT(m_expr.get_arg(1).is_int()); } std::vector args; if (arg1_reg) { if (reverse) { args = pop_to_forms({m_expr.get_arg(1).var(), m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects); auto temp = args.at(1); args.at(1) = args.at(0); args.at(0) = temp; } else { args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var()}, env, pool, stack, allow_side_effects); } } else { args = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects); args.push_back(pool.form(m_expr.get_arg(1))); } switch (kind) { case FixedOperatorKind::MOD: case FixedOperatorKind::MIN: case FixedOperatorKind::MAX: // can use time-frame break; default: // makes little sense to divide by a time most of the time. is_timeframe = false; break; } if (!arg0_i) { args.at(0) = cast_form(args.at(0), is_timeframe ? TypeSpec("time-frame") : TypeSpec("int"), pool, env); } if (!arg1_i) { args.at(1) = cast_form(args.at(1), is_timeframe ? TypeSpec("time-frame") : TypeSpec("int"), pool, env); } auto new_form = pool.alloc_element(GenericOperator::make_fixed(kind), args.at(0), args.at(1)); result->push_back(new_form); } void SimpleExpressionElement::update_from_stack_force_ui_2(const Env& env, FixedOperatorKind kind, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { bool arg0_constant = !m_expr.get_arg(0).is_var(); bool arg0_u; if (!arg0_constant) { arg0_u = is_uint_type(env, m_my_idx, m_expr.get_arg(0).var()); } else { arg0_u = m_expr.get_arg(0).is_int(); } bool arg1_u = true; bool arg1_reg = m_expr.get_arg(1).is_var(); if (arg1_reg) { arg1_u = is_uint_type(env, m_my_idx, m_expr.get_arg(1).var()); } else { ASSERT(m_expr.get_arg(1).is_int()); } std::vector args; if (arg0_constant) { args = pop_to_forms({m_expr.get_arg(1).var()}, env, pool, stack, allow_side_effects); args.push_back(pool.form(m_expr.get_arg(0))); } else { if (arg1_reg) { args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var()}, env, pool, stack, allow_side_effects); } else { args = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects); args.push_back(pool.form(m_expr.get_arg(1))); } } if (!arg0_u) { args.at(0) = pool.form(TypeSpec("uint"), args.at(0)); } if (!arg1_u) { args.at(1) = pool.form(TypeSpec("uint"), args.at(1)); } auto new_form = pool.alloc_element(GenericOperator::make_fixed(kind), args.at(0), args.at(1)); result->push_back(new_form); } void SimpleExpressionElement::update_from_stack_pcypld(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { std::vector args; std::vector ras; for (int arg_idx = 0; arg_idx < m_expr.args(); arg_idx++) { if (m_expr.get_arg(arg_idx).is_var()) { ras.push_back(m_expr.get_arg(arg_idx).var()); } } auto popped_args = pop_to_forms(ras, env, pool, stack, allow_side_effects); int ras_idx = 0; for (int arg_idx = 0; arg_idx < m_expr.args(); arg_idx++) { if (m_expr.get_arg(arg_idx).is_var()) { args.push_back(popped_args.at(ras_idx)); ras_idx++; } else { args.push_back(pool.form(m_expr.get_arg(arg_idx))); } } /* pcpyud v1, s4, r0 ld a0, L152(fp) and v1, v1, a0 lui a0, 1 dsll32 a0, a0, 0 or v1, v1, a0 pcpyld v1, v1, s4 por s4, v1, r0 */ auto as_mod = args.at(0)->try_as_element(); if (as_mod && as_mod->from_pcpyud()) { auto base_form = as_mod->base()->to_form(env); auto a1_form = args.at(1)->to_form(env); if (base_form == a1_form) { as_mod->clear_pcpyud_flag(); result->push_back(as_mod); return; } else { lg::warn("pcpyud rewrite form fail: {} {}", base_form.print(), a1_form.print()); } } auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::PCPYLD), args.at(0), args.at(1)); result->push_back(new_form); } void SimpleExpressionElement::update_from_stack_vector_plus_minus_cross( FixedOperatorKind op_kind, const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { std::vector popped_args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var(), m_expr.get_arg(2).var()}, env, pool, stack, allow_side_effects); for (int i = 0; i < 3; i++) { auto arg_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(i).var().reg()); if (arg_type.typespec() != TypeSpec("vector")) { popped_args.at(i) = cast_form(popped_args.at(i), TypeSpec("vector"), pool, env); } } auto new_form = pool.alloc_element( GenericOperator::make_fixed(op_kind), std::vector{popped_args.at(0), popped_args.at(1), popped_args.at(2)}); result->push_back(new_form); } void SimpleExpressionElement::update_from_stack_vector_plus_float_times( const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { std::vector popped_args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var(), m_expr.get_arg(2).var(), m_expr.get_arg(3).var()}, env, pool, stack, allow_side_effects); for (int i = 0; i < 4; i++) { auto arg_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(i).var().reg()); TypeSpec desired_type(i == 3 ? "float" : "vector"); if (arg_type.typespec() != desired_type) { popped_args.at(i) = cast_form(popped_args.at(i), desired_type, pool, env); } } auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::VECTOR_PLUS_FLOAT_TIMES), std::vector{popped_args.at(0), popped_args.at(1), popped_args.at(2), popped_args.at(3)}); result->push_back(new_form); } void SimpleExpressionElement::update_from_stack_vector_float_product( const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { std::vector popped_args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var(), m_expr.get_arg(2).var()}, env, pool, stack, allow_side_effects); for (int i = 0; i < 3; i++) { auto arg_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(i).var().reg()); TypeSpec desired_type(i == 2 ? "float" : "vector"); if (arg_type.typespec() != desired_type) { popped_args.at(i) = cast_form(popped_args.at(i), desired_type, pool, env); } } auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::VECTOR_FLOAT_PRODUCT), std::vector{popped_args.at(0), popped_args.at(1), popped_args.at(2)}); result->push_back(new_form); } void SimpleExpressionElement::update_from_stack_vectors_in_common(FixedOperatorKind kind, const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { std::vector register_acccesses; for (int arg_idx = 0; arg_idx < m_expr.args(); arg_idx++) { register_acccesses.push_back(m_expr.get_arg(arg_idx).var()); } std::vector popped_args = pop_to_forms(register_acccesses, env, pool, stack, allow_side_effects); for (int i = 0; i < m_expr.args(); i++) { auto arg_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(i).var().reg()); if (arg_type.typespec() != TypeSpec("vector")) { popped_args.at(i) = cast_form(popped_args.at(i), TypeSpec("vector"), pool, env); } } auto new_form = pool.alloc_element(GenericOperator::make_fixed(kind), popped_args); result->push_back(new_form); } void SimpleExpressionElement::update_from_stack_copy_first_int_2(const Env& env, FixedOperatorKind kind, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { auto arg0_type = env.get_variable_type(m_expr.get_arg(0).var(), true); auto arg0_i = is_int_type(arg0_type); auto arg0_u = is_uint_type(arg0_type); if (!m_expr.get_arg(1).is_var()) { auto args = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects); if (!arg0_i && !arg0_u) { auto bti = dynamic_cast(env.dts->ts.lookup_type(arg0_type)); if (bti) { auto new_form = pool.alloc_element( GenericOperator::make_fixed(kind), args.at(0), cast_form(pool.form(m_expr.get_arg(1)), arg0_type, pool, env)); result->push_back(new_form); } else { auto new_form = pool.alloc_element( GenericOperator::make_fixed(kind), pool.form(TypeSpec("int"), args.at(0)), pool.form(m_expr.get_arg(1))); result->push_back(new_form); } } else { auto new_form = pool.alloc_element(GenericOperator::make_fixed(kind), args.at(0), pool.form(m_expr.get_arg(1))); result->push_back(new_form); } return; } auto arg1_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(1).var().reg()).typespec(); auto arg1_i = is_int_type(arg1_type); auto arg1_u = is_uint_type(arg1_type); auto args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var()}, env, pool, stack, allow_side_effects); if ((arg0_i && arg1_i) || (arg0_u && arg1_u)) { if (kind == FixedOperatorKind::SUBTRACTION && arg0_i) { if (arg0_type == TypeSpec("time-frame") && arg1_type != TypeSpec("time-frame")) { args.at(1) = cast_form(args.at(1), TypeSpec("time-frame"), pool, env); } else if (arg1_type == TypeSpec("time-frame") && arg0_type != TypeSpec("time-frame")) { args.at(0) = cast_form(args.at(0), TypeSpec("time-frame"), pool, env); } } auto new_form = pool.alloc_element(GenericOperator::make_fixed(kind), args.at(0), args.at(1)); result->push_back(new_form); } else { auto cast = pool.form(TypeSpec(arg0_i ? "int" : "uint"), args.at(1)); if (kind == FixedOperatorKind::SUBTRACTION && is_pointer_type(env, m_my_idx, m_expr.get_arg(0).var())) { kind = FixedOperatorKind::SUBTRACTION_PTR; } auto new_form = pool.alloc_element(GenericOperator::make_fixed(kind), args.at(0), cast); result->push_back(new_form); } } namespace { Form* strip_int_or_uint_cast(Form* in) { auto as_cast = in->try_as_element(); if (as_cast && (as_cast->type() == TypeSpec("int") || as_cast->type() == TypeSpec("uint"))) { return as_cast->source(); } return in; } } // namespace FormElement* SimpleExpressionElement::update_from_stack_logor_or_logand_helper( const Env& env, FixedOperatorKind kind, FormPool& pool, FormStack& stack, bool allow_side_effects) { // grab the normal variable type TypeSpec arg0_type; if (m_expr.get_arg(0).is_var()) { arg0_type = env.get_variable_type(m_expr.get_arg(0).var(), true); } else if (m_expr.get_arg(0).is_int(0)) { arg0_type = TypeSpec("int"); // ?? } else { ASSERT(false); } // and try to get it as a bitfield auto type_info = env.dts->ts.lookup_type(arg0_type); auto bitfield_info = dynamic_cast(type_info); bool had_pcpyud = false; TypeSpec bitfield_type = arg0_type; if (!bitfield_info && m_expr.get_arg(0).is_var()) { // the above won't work if we're already done a pcpyud to grab the upper 64 bits. // we need to grab the type in the register (a TP_type) and check const auto& arg0_reg_type = env.get_types_before_op(m_expr.get_arg(0).var().idx()).get(m_expr.get_arg(0).var().reg()); if (arg0_reg_type.kind == TP_Type::Kind::PCPYUD_BITFIELD) { // yes! had_pcpyud = true; bitfield_info = dynamic_cast(env.dts->ts.lookup_type(arg0_reg_type.get_bitfield_type())); ASSERT(bitfield_info); } else if (arg0_reg_type.kind == TP_Type::Kind::PCPYUD_BITFIELD_AND) { // already have the pcpyud in the thing. bitfield_info = dynamic_cast(env.dts->ts.lookup_type(arg0_reg_type.get_bitfield_type())); ASSERT(bitfield_info); } } if (bitfield_info && arg0_type.base_type() != "time-frame" && m_expr.get_arg(1).is_int()) { // andi, ori with bitfield. auto base = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects).at(0); auto read_elt = dynamic_cast(base->try_as_single_element()); if (!read_elt) { read_elt = pool.alloc_element(base, bitfield_type); ASSERT(!had_pcpyud); } else { if (had_pcpyud) { ASSERT(read_elt->has_pcpyud()); } } BitfieldManip::Kind manip_kind; if (kind == FixedOperatorKind::LOGAND) { manip_kind = BitfieldManip::Kind::LOGAND_WITH_CONSTANT_INT; } else if (kind == FixedOperatorKind::LOGIOR) { manip_kind = BitfieldManip::Kind::LOGIOR_WITH_CONSTANT_INT; } else { ASSERT(false); } BitfieldManip step(manip_kind, m_expr.get_arg(1).get_int()); auto other = read_elt->push_step(step, env.dts->ts, pool, env); if (other) { return other; } else { return read_elt; } } else if (!m_expr.get_arg(1).is_var()) { // andi, something else (don't think this can happen?) std::vector result; update_from_stack_copy_first_int_2(env, kind, pool, stack, &result, allow_side_effects); ASSERT(result.size() == 1); return result.at(0); } else { // and, two forms auto arg1_type = env.get_variable_type(m_expr.get_arg(1).var(), true); auto arg0_i = m_expr.get_arg(0).is_var() ? is_int_type(env, m_my_idx, m_expr.get_arg(0).var()) : true; auto arg0_u = m_expr.get_arg(0).is_var() ? is_uint_type(env, m_my_idx, m_expr.get_arg(0).var()) : false; auto arg1_i = is_int_type(env, m_my_idx, m_expr.get_arg(1).var()); auto arg1_u = is_uint_type(env, m_my_idx, m_expr.get_arg(1).var()); auto arg0_n = arg0_i || arg0_u; auto arg1_n = arg1_i || arg1_u; std::vector args; if (m_expr.get_arg(0).is_var()) { args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var()}, env, pool, stack, allow_side_effects); } else { args = pop_to_forms({m_expr.get_arg(1).var()}, env, pool, stack, allow_side_effects); args.insert(args.begin(), pool.form( SimpleAtom::make_int_constant(m_expr.get_arg(0).get_int()))); } if (bitfield_info) { // either the immediate didn't fit in the 16-bit imm or it's with a variable bool made_new_read_elt = false; auto read_elt = dynamic_cast(args.at(0)->try_as_single_element()); if (!read_elt) { read_elt = pool.alloc_element(args.at(0), bitfield_type); made_new_read_elt = true; ASSERT(!had_pcpyud); } else { if (had_pcpyud) { ASSERT(read_elt->has_pcpyud()); } } auto stripped_arg1 = strip_int_or_uint_cast(args.at(1)); // auto arg1_atom = form_as_atom(strip_int_or_uint_cast(args.at(1))); auto arg1_as_int = get_goal_integer_constant(stripped_arg1, env); if (arg1_as_int) { BitfieldManip::Kind manip_kind; if (kind == FixedOperatorKind::LOGAND) { manip_kind = BitfieldManip::Kind::LOGAND_WITH_CONSTANT_INT; } else if (kind == FixedOperatorKind::LOGIOR) { manip_kind = BitfieldManip::Kind::LOGIOR_WITH_CONSTANT_INT; } else { ASSERT(false); } BitfieldManip step(manip_kind, *arg1_as_int); auto other = read_elt->push_step(step, env.dts->ts, pool, env); // ASSERT(!other); // shouldn't be complete. if (other) { return other; } else { return read_elt; } } else if (!made_new_read_elt) { BitfieldManip::Kind manip_kind; if (kind == FixedOperatorKind::LOGAND) { manip_kind = BitfieldManip::Kind::LOGAND_WITH_FORM; } else if (kind == FixedOperatorKind::LOGIOR) { manip_kind = BitfieldManip::Kind::LOGIOR_WITH_FORM; } else { ASSERT(false); } auto step = BitfieldManip::from_form(manip_kind, stripped_arg1); auto other = read_elt->push_step(step, env.dts->ts, pool, env); if (other) { return other; } else { return read_elt; } } } if (((arg0_i || arg0_u) && (arg1_i || arg1_u)) || (arg0_n && arg1_type.base_type() == "pointer") || (arg1_n && arg0_type.base_type() == "pointer")) { // types already good // we also allow (logand intvar pointer) and (logand pointer intvar) auto new_form = pool.alloc_element(GenericOperator::make_fixed(kind), args.at(0), args.at(1)); return new_form; // types bad, insert cast. } else { // this is an ugly hack to make (logand (lognot (enum-bitfield xxxx)) work. // I have only one example for this, so I think this unlikely to work in all cases. if (m_expr.get_arg(1).is_var()) { auto eti = env.dts->ts.try_enum_lookup(arg1_type.base_type()); if (eti) { auto integer = get_goal_integer_constant(strip_int_or_uint_cast(args.at(0)), env); if (integer && ((s64)*integer) < 0) { // clearing a bitfield. auto elts = decompile_bitfield_enum_from_int(arg1_type, env.dts->ts, ~*integer); auto oper = GenericOperator::make_function( pool.form(arg1_type.base_type())); std::vector form_elts; for (auto& x : elts) { form_elts.push_back(pool.form(x)); } auto inverted = pool.form(oper, form_elts); auto normal = pool.form( GenericOperator::make_fixed(FixedOperatorKind::LOGNOT), inverted); auto new_form = pool.alloc_element(GenericOperator::make_fixed(kind), normal, args.at(1)); return new_form; } } } bool arg0_int_like = env.dts->ts.tc(TypeSpec("integer"), arg0_type); bool arg1_int_like = env.dts->ts.tc(TypeSpec("integer"), arg1_type); if ((arg0_int_like) && (arg1_int_like)) { // we might have a bitfield in arg1. auto arg1_bitfield = dynamic_cast(env.dts->ts.lookup_type(arg1_type)); auto arg1_enum = dynamic_cast(env.dts->ts.lookup_type(arg1_type)); if ((arg1_bitfield || arg1_enum) && arg0_type != arg1_type) { auto new_form = pool.alloc_element( GenericOperator::make_fixed(kind), cast_form(args.at(0), arg1_type, pool, env), args.at(1)); return new_form; } else { auto new_form = pool.alloc_element(GenericOperator::make_fixed(kind), args.at(0), args.at(1)); return new_form; } } // types bad, insert cast. auto cast = pool.form(TypeSpec(arg0_i ? "int" : "uint"), args.at(1)); auto new_form = pool.alloc_element(GenericOperator::make_fixed(kind), args.at(0), cast); return new_form; } } } void SimpleExpressionElement::update_from_stack_logor_or_logand(const Env& env, FixedOperatorKind kind, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { auto element = update_from_stack_logor_or_logand_helper(env, kind, pool, stack, allow_side_effects); /* (defmacro logclear (a b) "Returns the result of setting the bits in b to zero in a" `(logand (lognot ,b) ,a) ) */ constexpr int a_form = 0; constexpr int b_form = 1; auto lognot_submatcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::LOGNOT), {Matcher::any(b_form)}); auto lognot_submatchers = Matcher::match_or({Matcher::cast("uint", lognot_submatcher), Matcher::cast("int", lognot_submatcher), lognot_submatcher}); auto logclear_matcher = Matcher::match_or({Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::LOGAND), {lognot_submatchers, Matcher::any(a_form)}), Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::LOGAND), {Matcher::any(a_form), lognot_submatchers})}); auto mr = match(logclear_matcher, element); if (mr.matched) { result->push_back(pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::LOGCLEAR), std::vector{mr.maps.forms.at(a_form), mr.maps.forms.at(b_form)})); return; } // (-> (the-as process-drawable (-> v1-32 0)) pid) // (-> v1-61 0 pid) auto just_deref_matcher = Matcher::match_or( {Matcher::deref(Matcher::any_reg(0), false, {DerefTokenMatcher::integer(0), DerefTokenMatcher::string("pid")}), Matcher::deref({Matcher::cast_to_any(4, Matcher::deref(Matcher::any_reg(0), false, {DerefTokenMatcher::integer(0)}))}, false, {DerefTokenMatcher::string("pid")})}); // jak 1: // (logior (shl (-> v1-61 0 pid) 32) (.asm.sllv.r0 v1-61)) // jak 2: // (logior (if v1-61 (shl (-> v1-61 0 pid) 32) 0) (.asm.sllv.r0 v1-61)) auto pid_deref_matcher = Matcher::op_fixed(FixedOperatorKind::SHL, {just_deref_matcher, Matcher::integer(32)}); auto make_handle_matcher = Matcher::op_fixed( FixedOperatorKind::LOGIOR, {env.version == GameVersion::Jak1 ? pid_deref_matcher : Matcher::if_with_else( Matcher::op(GenericOpMatcher::condition(IR2_Condition::Kind::TRUTHY), {Matcher::any_reg(2)}), pid_deref_matcher, Matcher::integer(0)), Matcher::op_fixed(FixedOperatorKind::ASM_SLLV_R0, {Matcher::any_reg(1)})}); auto handle_mr = match(make_handle_matcher, element); if (handle_mr.matched) { auto var_a = handle_mr.maps.regs.at(0).value(); auto var_b = handle_mr.maps.regs.at(1).value(); const auto& var_name = env.get_variable_name(var_a); if (var_name == env.get_variable_name(var_b) && (env.version == GameVersion::Jak1 || var_name == env.get_variable_name(handle_mr.maps.regs.at(2).value())) && env.dts->ts.tc(TypeSpec("pointer", {TypeSpec("process-tree")}), env.get_variable_type(var_a, true))) { auto* menv = const_cast(&env); menv->disable_use(var_a); auto repopped = stack.pop_reg(var_b, {}, env, true, stack.size() - 1); if (!repopped) { lg::warn("repop failed.\n{}", stack.print(env)); repopped = var_to_form(var_b, pool); } auto proc_to_ppointer_matcher = Matcher::op_fixed(FixedOperatorKind::PROCESS_TO_PPOINTER, {Matcher::any(0)}); auto proc_to_ppointer_mr = match(proc_to_ppointer_matcher, repopped); if (proc_to_ppointer_mr.matched) { element = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::PROCESS_TO_HANDLE), proc_to_ppointer_mr.maps.forms.at(0)); } else { element = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::PPOINTER_TO_HANDLE), repopped); } } } result->push_back(element); } void SimpleExpressionElement::update_from_stack_left_shift(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { TypeSpec arg0_type; auto& arg0 = m_expr.get_arg(0); if (arg0.is_var()) { arg0_type = env.get_variable_type(m_expr.get_arg(0).var(), true); auto type_info = env.dts->ts.lookup_type(arg0_type); auto bitfield_info = dynamic_cast(type_info); if (arg0_type.base_type() != "time-frame" && bitfield_info && m_expr.get_arg(1).is_int()) { auto base = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects).at(0); auto read_elt = pool.alloc_element(base, arg0_type); BitfieldManip step(BitfieldManip::Kind::LEFT_SHIFT, m_expr.get_arg(1).get_int()); auto other = read_elt->push_step(step, env.dts->ts, pool, env); ASSERT(!other); // shouldn't be complete. result->push_back(read_elt); } else { // try to turn this into a multiplication, if possible if (m_expr.get_arg(1).is_int()) { auto args = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects); int sa = m_expr.get_arg(1).get_int(); auto as_ba = args.at(0)->try_as_element(); if (as_ba) { BitfieldManip step(BitfieldManip::Kind::LEFT_SHIFT, m_expr.get_arg(1).get_int()); auto other = as_ba->push_step(step, env.dts->ts, pool, env); ASSERT(!other); // shouldn't be complete. result->push_back(as_ba); return; } // somewhat arbitrary threshold to switch from multiplications to shift. if (sa < 10) { s64 multiplier = (s64(1) << sa); auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::MULTIPLICATION), args.at(0), pool.form(SimpleAtom::make_int_constant(multiplier))); result->push_back(new_form); return; } auto arg0_i = is_int_type(env, m_my_idx, m_expr.get_arg(0).var()); auto arg0_u = is_uint_type(env, m_my_idx, m_expr.get_arg(0).var()); if (!arg0_i && !arg0_u) { auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::SHL), pool.form(TypeSpec("int"), args.at(0)), pool.form(m_expr.get_arg(1))); result->push_back(new_form); } else { auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::SHL), args.at(0), pool.form(m_expr.get_arg(1))); result->push_back(new_form); } return; } update_from_stack_copy_first_int_2(env, FixedOperatorKind::SHL, pool, stack, result, allow_side_effects); } } else if ((arg0.is_sym_val("#f") || arg0.is_sym_ptr("#f")) && m_expr.get_arg(1).is_int()) { auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::SHL), pool.form(TypeSpec("int"), pool.form(m_expr.get_arg(0))), pool.form(m_expr.get_arg(1))); result->push_back(new_form); return; } else { ASSERT(false); } } void SimpleExpressionElement::update_from_stack_right_shift_logic(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { auto arg0_type = env.get_variable_type(m_expr.get_arg(0).var(), true); auto type_info = env.dts->ts.lookup_type(arg0_type); auto bitfield_info = dynamic_cast(type_info); if (bitfield_info && m_expr.get_arg(1).is_int()) { auto base = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects).at(0); auto read_elt = pool.alloc_element(base, arg0_type); BitfieldManip step(BitfieldManip::Kind::RIGHT_SHIFT_LOGICAL, m_expr.get_arg(1).get_int()); auto other = read_elt->push_step(step, env.dts->ts, pool, env); ASSERT(other); // should be a high field. result->push_back(other); } else { auto arg0_i = is_int_type(env, m_my_idx, m_expr.get_arg(0).var()); auto arg0_u = is_uint_type(env, m_my_idx, m_expr.get_arg(0).var()); if (m_expr.get_arg(1).is_int()) { auto arg = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects).at(0); auto as_bitfield_access = dynamic_cast(arg->try_as_single_element()); if (as_bitfield_access) { BitfieldManip step(BitfieldManip::Kind::RIGHT_SHIFT_LOGICAL, m_expr.get_arg(1).get_int()); auto next = as_bitfield_access->push_step(step, env.dts->ts, pool, env); if (next) { result->push_back(next); } else { result->push_back(as_bitfield_access); } } else { /* int sa = m_expr.get_arg(1).get_int(); if (sa < 10) { if (!arg0_u) { arg = cast_form(arg, TypeSpec("uint"), pool, env); } s64 multiplier = (s64(1) << sa); auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::DIVISION), arg, pool.alloc_single_element_form( nullptr, SimpleAtom::make_int_constant(multiplier))); result->push_back(new_form); return; } */ if (!arg0_i && !arg0_u) { auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::SHR), pool.form(TypeSpec("int"), arg), pool.form(m_expr.get_arg(1))); result->push_back(new_form); } else { auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::SHR), arg, pool.form(m_expr.get_arg(1))); result->push_back(new_form); } } } else { update_from_stack_copy_first_int_2(env, FixedOperatorKind::SHR, pool, stack, result, allow_side_effects); } } } void SimpleExpressionElement::update_from_stack_right_shift_arith(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { auto arg0_type = env.get_variable_type(m_expr.get_arg(0).var(), true); auto type_info = env.dts->ts.lookup_type(arg0_type); auto bitfield_info = dynamic_cast(type_info); if (bitfield_info && arg0_type != TypeSpec("time-frame") && m_expr.get_arg(1).is_int()) { auto base = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects).at(0); auto read_elt = pool.alloc_element(base, arg0_type); BitfieldManip step(BitfieldManip::Kind::RIGHT_SHIFT_ARITH, m_expr.get_arg(1).get_int()); auto other = read_elt->push_step(step, env.dts->ts, pool, env); ASSERT(other); // should be a high field. result->push_back(other); } else { if (m_expr.get_arg(1).is_int()) { if (m_expr.get_arg(1).get_int() < 10) { auto arg0_i = is_int_type(env, m_my_idx, m_expr.get_arg(0).var()); auto arg = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects).at(0); int sa = m_expr.get_arg(1).get_int(); if (!arg0_i) { arg = cast_form(arg, TypeSpec("int"), pool, env); } s64 multiplier = (s64(1) << sa); auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::DIVISION), arg, pool.form(SimpleAtom::make_int_constant(multiplier))); result->push_back(new_form); return; } auto arg0_i = is_int_type(env, m_my_idx, m_expr.get_arg(0).var()); auto arg0_u = is_uint_type(env, m_my_idx, m_expr.get_arg(0).var()); auto args = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects); auto as_ba = args.at(0)->try_as_element(); if (as_ba) { BitfieldManip step(BitfieldManip::Kind::RIGHT_SHIFT_ARITH, m_expr.get_arg(1).get_int()); auto other = as_ba->push_step(step, env.dts->ts, pool, env); ASSERT(other); // should be a high field. result->push_back(other); return; } if (!arg0_i && !arg0_u) { auto new_form = pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::SAR), pool.form(TypeSpec("int"), args.at(0)), pool.form(m_expr.get_arg(1))); result->push_back(new_form); } else { auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::SAR), args.at(0), pool.form(m_expr.get_arg(1))); result->push_back(new_form); } return; } update_from_stack_copy_first_int_2(env, FixedOperatorKind::SAR, pool, stack, result, allow_side_effects); } } void SimpleExpressionElement::update_from_stack_lognot(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { auto args = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects); auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::LOGNOT), args.at(0)); result->push_back(new_form); } void SimpleExpressionElement::update_from_stack_int_to_float(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { auto var = m_expr.get_arg(0).var(); auto arg = pop_to_forms({var}, env, pool, stack, allow_side_effects).at(0); // if we convert from a GPR to FPR, then immediately to int to float, we can strip away the // the gpr->fpr operation beacuse it doesn't matter. auto fpr_convert_matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::GPR_TO_FPR), {Matcher::any(0)}); auto type = env.get_types_before_op(var.idx()).get(var.reg()).typespec(); // want to allow any child of integer so integer enums can also be converted to floats. if (env.dts->ts.tc(TypeSpec("integer"), type) || type == TypeSpec("seconds")) { auto mr = match(fpr_convert_matcher, arg); if (mr.matched) { arg = mr.maps.forms.at(0); } result->push_back(pool.alloc_element(TypeSpec("float"), arg, true)); } else { throw std::runtime_error(fmt::format("At op {}, used int to float on a {} from {}: {}", m_my_idx, type.print(), var.to_form(env).print(), arg->to_string(env))); } } void SimpleExpressionElement::update_from_stack_float_to_int(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { auto var = m_expr.get_arg(0).var(); auto arg = pop_to_forms({var}, env, pool, stack, allow_side_effects).at(0); auto type = env.get_types_before_op(var.idx()).get(var.reg()).typespec(); auto fpr_convert_matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::GPR_TO_FPR), {Matcher::any(0)}); auto mr = match(fpr_convert_matcher, arg); if (type == TypeSpec("float")) { result->push_back(pool.alloc_element(TypeSpec("int"), arg, true)); } else if (env.dts->ts.tc(type, TypeSpec("float")) && mr.matched) { // this is a parent of float. normally we would fix this with a manual cast, but that is too // effective since float->int requires the type to be EXACTLY float. // so we add a cast to float first. // we also strip the gpr->fpr here result->push_back(pool.alloc_element( TypeSpec("int"), pool.form(TypeSpec("float"), mr.maps.forms.at(0), true), true)); } else { throw std::runtime_error( fmt::format("Used float to int on a {}: {}", type.print(), to_string(env))); } } namespace { GenericElement* allocate_fixed_op(FormPool& pool, FixedOperatorKind kind, Form* op1) { return pool.alloc_element(GenericOperator::make_fixed(kind), op1); } } // namespace void SimpleExpressionElement::update_from_stack_subu_l32_s7(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { auto var = m_expr.get_arg(0).var(); auto arg = pop_to_forms({var}, env, pool, stack, allow_side_effects).at(0); auto type = env.get_types_before_op(var.idx()).get(var.reg()).typespec(); if (type != TypeSpec("handle")) { env.func->warnings.warning( ".subu (32-bit) used on a {} at idx {}. This probably should be a handle.", type.print(), var.idx()); } result->push_back(allocate_fixed_op(pool, FixedOperatorKind::L32_NOT_FALSE_CBOOL, arg)); } void SimpleExpressionElement::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { mark_popped(); switch (m_expr.kind()) { case SimpleExpression::Kind::IDENTITY: update_from_stack_identity(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::GPR_TO_FPR: update_from_stack_gpr_to_fpr(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::FPR_TO_GPR: update_from_stack_fpr_to_gpr(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::DIV_S: update_from_stack_div_s(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::SUB_S: update_from_stack_float_2(env, FixedOperatorKind::SUBTRACTION, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::MUL_S: update_from_stack_float_2_nestable(env, FixedOperatorKind::MULTIPLICATION, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::ADD_S: update_from_stack_float_2_nestable(env, FixedOperatorKind::ADDITION, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::MAX_S: update_from_stack_float_2(env, FixedOperatorKind::FMAX, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::MIN_S: update_from_stack_float_2(env, FixedOperatorKind::FMIN, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::SQRT_S: update_from_stack_float_1(env, FixedOperatorKind::SQRT, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::ABS_S: update_from_stack_float_1(env, FixedOperatorKind::FABS, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::NEG: update_from_stack_si_1(env, FixedOperatorKind::SUBTRACTION, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::NEG_S: update_from_stack_float_1(env, FixedOperatorKind::SUBTRACTION, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::ADD: update_from_stack_add_i(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::SUB: update_from_stack_copy_first_int_2(env, FixedOperatorKind::SUBTRACTION, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::MUL_SIGNED: update_from_stack_mult_si(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::DIV_SIGNED: update_from_stack_force_si_2(env, FixedOperatorKind::DIVISION, pool, stack, result, allow_side_effects, false); break; case SimpleExpression::Kind::DIV_UNSIGNED: update_from_stack_force_ui_2(env, FixedOperatorKind::DIVISION, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::MOD_SIGNED: update_from_stack_force_si_2(env, FixedOperatorKind::MOD, pool, stack, result, allow_side_effects, false); break; case SimpleExpression::Kind::MOD_UNSIGNED: update_from_stack_force_ui_2(env, FixedOperatorKind::MOD, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::MIN_SIGNED: update_from_stack_force_si_2(env, FixedOperatorKind::MIN, pool, stack, result, allow_side_effects, false); break; case SimpleExpression::Kind::MAX_SIGNED: update_from_stack_force_si_2(env, FixedOperatorKind::MAX, pool, stack, result, allow_side_effects, false); break; case SimpleExpression::Kind::AND: update_from_stack_logor_or_logand(env, FixedOperatorKind::LOGAND, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::OR: update_from_stack_logor_or_logand(env, FixedOperatorKind::LOGIOR, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::NOR: update_from_stack_copy_first_int_2(env, FixedOperatorKind::LOGNOR, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::XOR: update_from_stack_copy_first_int_2(env, FixedOperatorKind::LOGXOR, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::LOGNOT: update_from_stack_lognot(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::LEFT_SHIFT: update_from_stack_left_shift(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::RIGHT_SHIFT_LOGIC: update_from_stack_right_shift_logic(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::RIGHT_SHIFT_ARITH: update_from_stack_right_shift_arith(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::MUL_UNSIGNED: update_from_stack_force_ui_2(env, FixedOperatorKind::MULTIPLICATION, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::INT_TO_FLOAT: update_from_stack_int_to_float(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::FLOAT_TO_INT: update_from_stack_float_to_int(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::PCPYLD: update_from_stack_pcypld(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::VECTOR_PLUS: update_from_stack_vector_plus_minus_cross(FixedOperatorKind::VECTOR_PLUS, env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::VECTOR_MINUS: update_from_stack_vector_plus_minus_cross(FixedOperatorKind::VECTOR_MINUS, env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::VECTOR_CROSS: update_from_stack_vector_plus_minus_cross(FixedOperatorKind::VECTOR_CROSS, env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::VECTOR_FLOAT_PRODUCT: update_from_stack_vector_float_product(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::SUBU_L32_S7: update_from_stack_subu_l32_s7(env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::VECTOR_3_DOT: update_from_stack_vectors_in_common(FixedOperatorKind::VECTOR_3_DOT, env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::VECTOR_4_DOT: update_from_stack_vectors_in_common(FixedOperatorKind::VECTOR_4_DOT, env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::VECTOR_LENGTH: update_from_stack_vectors_in_common(FixedOperatorKind::VECTOR_LENGTH, env, pool, stack, result, allow_side_effects); break; case SimpleExpression::Kind::VECTOR_PLUS_FLOAT_TIMES: update_from_stack_vector_plus_float_times(env, pool, stack, result, allow_side_effects); break; default: throw std::runtime_error( fmt::format("SimpleExpressionElement::update_from_stack NYI for {}", to_string(env))); } } /////////////////// // SetVarElement /////////////////// void SetVarElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); for (auto x : m_src->elts()) { ASSERT(x->parent_form == m_src); } ASSERT(m_src->parent_element == this); // hack for method stuff if (is_dead_set()) { stack.push_value_to_reg_dead(m_dst, m_src, true, m_src_type, m_var_info); return; } // if we are a reg-reg move that consumes the original, push it without popping from stack. // it is the Stack's responsibility to untangle these later on. if (m_src->is_single_element()) { auto src_as_se = dynamic_cast(m_src->back()); if (src_as_se) { if (src_as_se->expr().kind() == SimpleExpression::Kind::IDENTITY && src_as_se->expr().get_arg(0).is_var()) { // this can happen late in the case of coloring moves which are also gpr -> fpr's // so they don't get caught by SetVarOp::get_as_form's check. if (env.op_id_is_eliminated_coloring_move(src_as_se->expr().get_arg(0).var().idx())) { m_var_info.is_eliminated_coloring_move = true; } auto var = src_as_se->expr().get_arg(0).var(); auto& info = env.reg_use().op.at(var.idx()); if (var.reg() == Register(Reg::GPR, Reg::S6) || info.consumes.find(var.reg()) != info.consumes.end()) { stack.push_non_seq_reg_to_reg(m_dst, src_as_se->expr().get_arg(0).var(), m_src, m_src_type, m_var_info); return; } } } } // we aren't a reg-reg move, so update our source m_src->update_children_from_stack(env, pool, stack, true); for (auto x : m_src->elts()) { ASSERT(x->parent_form == m_src); } if (m_src->is_single_element()) { auto src_as_se = dynamic_cast(m_src->back()); if (src_as_se) { if (src_as_se->expr().kind() == SimpleExpression::Kind::IDENTITY && m_dst.reg().get_kind() == Reg::FPR && src_as_se->expr().get_arg(0).is_int() && src_as_se->expr().get_arg(0).get_int() == 0) { // not sure this is the best place for this. stack.push_value_to_reg(m_dst, pool.form(0.0), true, m_src_type, m_var_info); return; } // this might get skipped earlier because gpr->fpr gets wrapped in an operation that's // stripped off by update_children_from_stack. if (src_as_se->expr().kind() == SimpleExpression::Kind::IDENTITY && src_as_se->expr().get_arg(0).is_var()) { if (env.op_id_is_eliminated_coloring_move(src_as_se->expr().get_arg(0).var().idx())) { m_var_info.is_eliminated_coloring_move = true; } } } } stack.push_value_to_reg(m_dst, m_src, true, m_src_type, m_var_info); for (auto x : m_src->elts()) { ASSERT(x->parent_form == m_src); } } FormElement* SetFormFormElement::make_set_time(const Env& /*env*/, FormPool& pool, FormStack& /*stack*/) { auto matcher = match( Matcher::op(GenericOpMatcher::func(Matcher::constant_token("current-time")), {}), m_src); if (matcher.matched) { return pool.alloc_element( GenericOperator::make_function(pool.form("set-time!")), m_dst); } return nullptr; } void SetFormFormElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { ASSERT(m_popped); ASSERT(m_real_push_count == 0); m_real_push_count++; // check for bitfield setting: auto src_as_bf_set = dynamic_cast(m_src->try_as_single_element()); if (src_as_bf_set && !src_as_bf_set->from_pcpyud() && src_as_bf_set->mods().size() == 1) { auto dst_form = m_dst->to_form(env); auto src_form = src_as_bf_set->base()->to_form(env); if (dst_form == src_form) { // success! auto value = src_as_bf_set->mods().at(0).value; value->parent_element = this; // make the (-> thing bitfield) auto field_token = DerefToken::make_field_name(src_as_bf_set->mods().at(0).field_name); auto loc_elt = pool.alloc_element(m_dst, false, field_token); loc_elt->inline_nested(); auto loc = pool.alloc_single_form(nullptr, loc_elt); loc->parent_element = this; m_dst = loc; m_src = value; } } else if (src_as_bf_set) { lg::warn("invalid bf set: {}", src_as_bf_set->to_string(env)); } // setting a bitfield to zero is wonky. auto bfa = dynamic_cast(m_src->try_as_single_element()); if (bfa) { auto zero_set = bfa->get_set_field_0(env.dts->ts); if (zero_set) { auto field_token = DerefToken::make_field_name(zero_set->name()); auto loc_elt = pool.alloc_element(m_dst, false, field_token); loc_elt->inline_nested(); auto loc = pool.alloc_single_form(nullptr, loc_elt); loc->parent_element = this; m_dst = loc; auto zero = SimpleAtom::make_int_constant(0); auto zero_form = pool.form(zero); m_src = zero_form; } } typedef struct { FixedOperatorKind kind; FixedOperatorKind inplace_kind; std::vector inplace_arg; } InplaceOpInfo; const static InplaceOpInfo in_place_ops[] = { {FixedOperatorKind::ADDITION, FixedOperatorKind::ADDITION_IN_PLACE, {0, 1}}, {FixedOperatorKind::ADDITION_PTR, FixedOperatorKind::ADDITION_PTR_IN_PLACE, {0, 1}}, {FixedOperatorKind::LOGAND, FixedOperatorKind::LOGAND_IN_PLACE, {0, 1}}, {FixedOperatorKind::LOGIOR, FixedOperatorKind::LOGIOR_IN_PLACE, {0, 1}}, {FixedOperatorKind::LOGCLEAR, FixedOperatorKind::LOGCLEAR_IN_PLACE, {0, 1}}, {FixedOperatorKind::LOGXOR, FixedOperatorKind::LOGXOR_IN_PLACE, {0, 1}}}; typedef struct { std::string orig_name; std::string inplace_name; int inplace_arg; } InplaceCallInfo; const static InplaceCallInfo in_place_calls[] = {{"seek", "seek!", 0}, {"seekl", "seekl!", 0}}; auto as_set_time = make_set_time(env, pool, stack); if (as_set_time) { stack.push_form_element(as_set_time, true); return; } auto src_as_generic = m_src->try_as_element(); if (src_as_generic) { if (src_as_generic->op().is_func()) { auto funchelt = dynamic_cast(src_as_generic->op().func()->at(0)); if (funchelt && funchelt->expr().get_arg(0).is_sym_val()) { const auto& funcname = funchelt->expr().get_arg(0).get_str(); for (const auto& call_info : in_place_calls) { if (funcname == call_info.orig_name) { auto dst_form = m_dst->to_form(env); auto src_form_in_func = src_as_generic->elts().at(call_info.inplace_arg)->to_form(env); if (dst_form == src_form_in_func) { auto new_func_op = GenericOperator::make_function( pool.form(call_info.inplace_name)); GenericElement* inplace_call = nullptr; if (funcname == "seek" || funcname == "seekl") { inplace_call = pool.alloc_element( new_func_op, std::vector{m_dst, src_as_generic->elts().at(1), src_as_generic->elts().at(2)}); } ASSERT_MSG( inplace_call, fmt::format("no appropriate inplace call was generated for (set! {}) -> {}", call_info.orig_name, call_info.inplace_name)); stack.push_form_element(inplace_call, true); return; } } } } } else { for (const auto& op_info : in_place_ops) { if (src_as_generic->op().is_fixed(op_info.kind)) { auto dst_form = m_dst->to_form(env); for (int inplace_arg : op_info.inplace_arg) { auto add_form_0 = src_as_generic->elts().at(inplace_arg)->to_form(env); if (dst_form == add_form_0) { if (inplace_arg != 0) { std::swap(src_as_generic->elts().at(0), src_as_generic->elts().at(1)); } src_as_generic->op() = GenericOperator::make_fixed(op_info.inplace_kind); stack.push_form_element(src_as_generic, true); return; } } } } } } stack.push_form_element(this, true); } void StoreInSymbolElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { auto sym = pool.form(SimpleAtom::make_sym_val(m_sym_name).as_expr(), m_my_idx); auto val = pool.form(m_value, m_my_idx); val->update_children_from_stack(env, pool, stack, true); if (m_cast_for_set) { // we'd need to cast for set. Let's see if we can simplify it instead: auto simplified = try_cast_simplify(val, *m_cast_for_set, pool, env, false); if (simplified) { if (m_cast_for_define && *m_cast_for_define == *m_cast_for_set) { // if we'd need exactly the same cast for a define, we can drop it too m_cast_for_define = {}; } m_cast_for_set = {}; val = simplified; } } auto elt = pool.alloc_element(sym, val, m_cast_for_set, m_cast_for_define); elt->mark_popped(); stack.push_form_element(elt, true); } void StoreInPairElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { auto op = m_is_car ? FixedOperatorKind::CAR : FixedOperatorKind::CDR; if (m_value.is_var()) { auto vars = std::vector({m_value.var(), m_pair}); auto popped = pop_to_forms(vars, env, pool, stack, true); auto addr = pool.form(GenericOperator::make_fixed(op), popped.at(1)); addr->mark_popped(); auto fr = pool.alloc_element(addr, popped.at(0)); fr->mark_popped(); stack.push_form_element(fr, true); } else { auto val = pool.form(m_value, m_my_idx); val->mark_popped(); auto addr = pool.form(GenericOperator::make_fixed(op), pop_to_forms({m_pair}, env, pool, stack, true).at(0)); addr->mark_popped(); auto fr = pool.alloc_element(addr, val); fr->mark_popped(); stack.push_form_element(fr, true); } } namespace { Form* make_optional_cast(const std::optional& cast_type, Form* in, FormPool& pool, const Env& env) { if (cast_type) { return cast_form(in, *cast_type, pool, env); } else { return in; } } bool try_to_rewrite_vector_inline_ctor(const Env& env, FormPool& pool, FormStack& stack, const std::string& type_name) { // now, let's check for a matrix initialization. auto matrix_entries = stack.try_getting_active_stack_entries({true, false}); if (matrix_entries) { // the (set! var (new 'stack-no-clear 'matrix)) if (matrix_entries->at(0).destination->reg() == Register(Reg::GPR, Reg::R0)) { return false; } auto var_name = env.get_variable_name(*matrix_entries->at(0).destination); auto src = matrix_entries->at(0).source->try_as_element(); if (!src) { return false; } if (src->type() != TypeSpec(type_name)) { return false; } // zeroing the rows: std::vector write_vars; auto elt = matrix_entries->at(1).elt; std::vector token_matchers = {DerefTokenMatcher::string("vec"), DerefTokenMatcher::string("quad")}; if (type_name == "vector") { token_matchers = {DerefTokenMatcher::string("quad")}; } if (env.version >= GameVersion::Jak2) { token_matchers = {DerefTokenMatcher::string("quad")}; } auto matcher = Matcher::set(Matcher::deref(Matcher::any_reg(0), false, token_matchers), Matcher::cast("uint128", Matcher::integer(0))); auto mr = match(matcher, elt); if (mr.matched) { if (var_name != env.get_variable_name(*mr.maps.regs.at(0))) { return false; } write_vars.push_back(*mr.maps.regs.at(0)); } else { return false; } // success! for (auto& wv : write_vars) { env.get_use_def_info(wv); Env* menv = const_cast(&env); menv->disable_use(wv); } stack.pop(2); stack.push_value_to_reg( *matrix_entries->at(0).destination, pool.form(GenericOperator::make_function( pool.form(fmt::format("new-stack-{}0", type_name)))), true, TypeSpec(type_name)); return true; } return false; } bool try_to_rewrite_matrix_inline_ctor(const Env& env, FormPool& pool, FormStack& stack) { // now, let's check for a matrix initialization. auto matrix_entries = stack.try_getting_active_stack_entries({true, false, false, false, false}); if (matrix_entries) { // the (set! var (new 'stack-no-clear 'matrix)) if (matrix_entries->at(0).destination->reg() == Register(Reg::GPR, Reg::R0)) { return false; } auto var_name = env.get_variable_name(*matrix_entries->at(0).destination); auto src = matrix_entries->at(0).source->try_as_element(); if (!src) { return false; } if (src->type() != TypeSpec("matrix")) { return false; } // zeroing the rows: std::vector write_vars; if (env.version == GameVersion::Jak1) { for (int i = 0; i < 4; i++) { auto elt = matrix_entries->at(i + 1).elt; auto matcher = Matcher::set( Matcher::deref(Matcher::any_reg(0), false, {DerefTokenMatcher::string("vector"), DerefTokenMatcher::integer(i), DerefTokenMatcher::string("quad")}), Matcher::cast("uint128", Matcher::integer(0))); auto mr = match(matcher, elt); if (mr.matched) { if (var_name != env.get_variable_name(*mr.maps.regs.at(0))) { return false; } write_vars.push_back(*mr.maps.regs.at(0)); } else { return false; } } } else { for (int i = 0; i < 4; i++) { auto elt = matrix_entries->at(i + 1).elt; Matcher matcher; if (i == 3) { matcher = Matcher::set(Matcher::deref(Matcher::any_reg(0), false, {DerefTokenMatcher::string("trans"), DerefTokenMatcher::string("quad")}), Matcher::cast("uint128", Matcher::integer(0))); } else { matcher = Matcher::set( Matcher::deref(Matcher::any_reg(0), false, {DerefTokenMatcher::string("quad"), DerefTokenMatcher::integer(i)}), Matcher::cast("uint128", Matcher::integer(0))); } auto mr = match(matcher, elt); if (mr.matched) { if (var_name != env.get_variable_name(*mr.maps.regs.at(0))) { return false; } write_vars.push_back(*mr.maps.regs.at(0)); } else { return false; } } } // success! for (auto& wv : write_vars) { env.get_use_def_info(wv); Env* menv = const_cast(&env); menv->disable_use(wv); } stack.pop(5); stack.push_value_to_reg(*matrix_entries->at(0).destination, pool.form(GenericOperator::make_function( pool.form("new-stack-matrix0"))), true, TypeSpec("matrix")); return true; } return false; } } // namespace void StorePlainDeref::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); if (m_expr.is_var()) { // this matches the order in Compiler::compile_set auto vars = std::vector({m_expr.var(), m_base_var}); // for 16-byte stores, the order is backward. Why???? if (size() == 16) { std::swap(vars.at(0), vars.at(1)); } auto popped = pop_to_forms(vars, env, pool, stack, true); if (size() == 16) { std::swap(popped.at(0), popped.at(1)); } m_dst->try_as_element()->set_base( make_optional_cast(m_dst_cast_type, popped.at(1), pool, env)); m_dst->mark_popped(); m_dst->try_as_element()->inline_nested(); auto fr = pool.alloc_element( m_dst, make_optional_cast(m_src_cast_type, popped.at(0), pool, env)); // so the bitfield set check can run fr->mark_popped(); fr->push_to_stack(env, pool, stack); } else { auto vars = std::vector({m_base_var}); auto popped = pop_to_forms(vars, env, pool, stack, true); m_dst->try_as_element()->set_base( make_optional_cast(m_dst_cast_type, popped.at(0), pool, env)); m_dst->mark_popped(); m_dst->try_as_element()->inline_nested(); auto val = pool.form(m_expr, m_my_idx); val->mark_popped(); auto fr = pool.alloc_element( m_dst, make_optional_cast(m_src_cast_type, val, pool, env)); fr->mark_popped(); stack.push_form_element(fr, true); } if (!try_to_rewrite_matrix_inline_ctor(env, pool, stack)) { if (!try_to_rewrite_vector_inline_ctor(env, pool, stack, "vector")) { try_to_rewrite_vector_inline_ctor(env, pool, stack, "quaternion"); } } } void StoreArrayAccess::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); Form* expr_form = nullptr; Form* array_form = nullptr; if (m_expr.is_var()) { auto vars = std::vector({m_expr.var(), m_base_var}); auto popped = pop_to_forms(vars, env, pool, stack, true); m_dst->mark_popped(); expr_form = popped.at(0); array_form = popped.at(1); } else { auto vars = std::vector({m_base_var}); auto popped = pop_to_forms(vars, env, pool, stack, true); m_dst->mark_popped(); expr_form = pool.form(m_expr, m_my_idx); array_form = popped.at(0); } std::vector forms_out; m_dst->update_with_val(array_form, env, pool, &forms_out, true); auto form_out = pool.alloc_sequence_form(nullptr, forms_out); auto fr = pool.alloc_element( form_out, make_optional_cast(m_src_cast_type, expr_form, pool, env)); fr->mark_popped(); fr->push_to_stack(env, pool, stack); } /////////////////// // AshElement /////////////////// void AshElement::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { mark_popped(); auto forms = pop_to_forms({value, shift_amount}, env, pool, stack, allow_side_effects, consumed); auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::ARITH_SHIFT), forms.at(0), forms.at(1)); result->push_back(new_form); } /////////////////// // AbsElement /////////////////// void AbsElement::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { mark_popped(); auto forms = pop_to_forms({source}, env, pool, stack, allow_side_effects, consumed); auto new_form = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::ABS), forms.at(0)); result->push_back(new_form); } namespace { /*! * Try to recognize setting the next state. */ Form* get_set_next_state(FormElement* set_elt, const Env& env) { auto as_set = dynamic_cast(set_elt); if (!as_set) { return nullptr; } auto dst = as_set->dst(); auto dst_matcher = Matcher::deref(Matcher::any_reg(0), false, {DerefTokenMatcher::string("next-state")}); auto mr = match(dst_matcher, dst); if (!mr.matched) { lg::error("failed to match dst {}", dst->to_string(env)); return nullptr; } if (mr.maps.regs.at(0)->reg() != Register(Reg::GPR, Reg::S6)) { lg::error("failed to match pp reg, got {}", mr.maps.regs.at(0)->reg().to_string()); return nullptr; } return as_set->src(); } } // namespace /////////////////// // FunctionCallElement /////////////////// namespace { std::optional get_form_reg_acc(Form* in) { auto as_simple_atom = dynamic_cast(in->try_as_single_active_element()); if (as_simple_atom) { if (as_simple_atom->atom().is_var()) { return as_simple_atom->atom().var(); } } auto as_expr = dynamic_cast(in->try_as_single_active_element()); if (as_expr && as_expr->expr().is_identity()) { auto atom = as_expr->expr().get_arg(0); if (atom.is_var()) { return atom.var(); } } return {}; } } // namespace void FunctionCallElement::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { mark_popped(); std::vector args; auto nargs = m_op->arg_vars().size(); args.resize(nargs, nullptr); std::vector all_pop_vars = {m_op->function_var()}; for (size_t i = 0; i < nargs; i++) { all_pop_vars.push_back(m_op->arg_vars().at(i)); } TypeSpec function_type; auto& in_type_state = env.get_types_before_op(all_pop_vars.at(0).idx()); auto& tp_type = in_type_state.get(all_pop_vars.at(0).reg()); if (env.has_type_analysis()) { function_type = tp_type.typespec(); } // if we're actually a go: Form* go_next_state = nullptr; if (tp_type.kind == TP_Type::Kind::ENTER_STATE_FUNCTION) { auto& next_state_type = in_type_state.next_state_type; if (next_state_type.typespec().base_type() != "state") { throw std::runtime_error("Bad state type in expressions (not state): " + next_state_type.print()); } if (next_state_type.typespec().arg_count() == 0) { throw std::runtime_error("Bad state type in expressions (no args): " + next_state_type.print()); } // modify our type for the go. function_type = state_to_go_function(next_state_type.typespec(), TypeSpec("object")); // up next, we need to deal with the // (set! (-> pp next-state) process-drawable-art-error) auto stack_back = stack.pop_back(pool); auto next_state = get_set_next_state(stack_back, env); if (!next_state) { throw std::runtime_error( fmt::format("Expressions couldn't figure out this go. The back of the stack was {} and " "we expected to see something set (-> pp next-state) instead.", stack_back->to_string(env))); } go_next_state = next_state; } bool swap_function = tp_type.kind == TP_Type::Kind::NON_VIRTUAL_METHOD && all_pop_vars.size() >= 2; if (tp_type.kind == TP_Type::Kind::NON_VIRTUAL_METHOD) { // this is a hack to make some weird macro for calling res-lump methods work if (env.dts->ts.tc(TypeSpec("res-lump"), tp_type.method_from_type())) { swap_function = false; } } if (swap_function) { std::swap(all_pop_vars.at(0), all_pop_vars.at(1)); } auto unstacked = pop_to_forms(all_pop_vars, env, pool, stack, allow_side_effects); if (swap_function) { std::swap(unstacked.at(0), unstacked.at(1)); std::swap(all_pop_vars.at(0), all_pop_vars.at(1)); } if (tp_type.kind == TP_Type::Kind::RUN_FUNCTION_IN_PROCESS_FUNCTION) { if (unstacked.at(0)->to_string(env) == "run-function-in-process") { unstacked.at(0) = pool.form("run-now-in-process"); } else { // couldn't pop. need to add a cast. TypeSpec failed_cast("function"); for (int i = 0; i < ((int)unstacked.size()) - 1; i++) { failed_cast.add_arg(TypeSpec("object")); } failed_cast.add_arg(TypeSpec("none")); unstacked.at(0) = pool.form(failed_cast, unstacked.at(0)); } } if (tp_type.kind == TP_Type::Kind::SET_TO_RUN_FUNCTION) { if (unstacked.at(0)->to_string(env) != "set-to-run") { throw std::runtime_error( fmt::format("Expression pass could not find the set-to-run function. Found " "{} instead. Make sure there are no casts on this function.", all_pop_vars.at(0).to_string(env))); } unstacked.at(0) = pool.form("run-next-time-in-process"); // also need to clean up (-> main-thread) to just auto matcher = Matcher::deref(Matcher::any(0), false, {DerefTokenMatcher::string("main-thread")}); auto mr = match(matcher, unstacked.at(1)); if (!mr.matched) { throw std::runtime_error(fmt::format("set-to-run called on a strange thread: {}", unstacked.at(1)->to_string(env))); } unstacked.at(1) = mr.maps.forms.at(0); } std::vector arg_forms; bool has_good_types = env.has_type_analysis() && function_type.arg_count() == nargs + 1; TypeSpec first_arg_type; for (size_t arg_id = 0; arg_id < nargs; arg_id++) { auto val = unstacked.at(arg_id + 1); // first is the function itself. auto& var = all_pop_vars.at(arg_id + 1); if (has_good_types) { auto actual_arg_type = env.get_types_before_op(var.idx()).get(var.reg()).typespec(); if (arg_id == 0) { first_arg_type = actual_arg_type; } auto desired_arg_type = function_type.get_arg(arg_id); if (env.dts->should_attempt_cast_simplify(desired_arg_type, actual_arg_type)) { arg_forms.push_back(cast_form(val, desired_arg_type, pool, env, env.dts->ts.tc(desired_arg_type, actual_arg_type))); } else { arg_forms.push_back(val); } } else { arg_forms.push_back(val); } } FormElement* new_form = nullptr; if (go_next_state) { // see if we're a virtual go Matcher virtual_go_state_matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::METHOD_OF_OBJECT), {Matcher::any(0), Matcher::any_constant_token(1)}); auto virtual_go_mr = match(virtual_go_state_matcher, go_next_state); if (virtual_go_mr.matched && virtual_go_mr.maps.forms.at(0)->to_string(env) == "self") { arg_forms.insert(arg_forms.begin(), pool.form(virtual_go_mr.maps.strings.at(1))); auto go_form = pool.alloc_element( GenericOperator::make_function(pool.form("go-virtual")), arg_forms); result->push_back(go_form); return; } arg_forms.insert(arg_forms.begin(), go_next_state); auto go_form = pool.alloc_element( GenericOperator::make_function(pool.form("go")), arg_forms); result->push_back(go_form); return; } // tpage and texture macros { auto func = Matcher::symbol("lookup-texture-by-id"); auto func_fast = Matcher::symbol("lookup-texture-by-id-fast"); auto mr = match(func, unstacked.at(0)); auto mr_fast = match(func_fast, unstacked.at(0)); if (mr.matched || mr_fast.matched) { auto tex_id = Matcher::any_integer(0); auto mr2 = match(tex_id, unstacked.at(1)); if (mr2.matched) { auto id = mr2.maps.ints.at(0); u16 tpage = (id & 0xfff00000) >> 20; u16 idx = (id & 0x000fff00) >> 8; auto fixed_id = tpage << 16 | idx; if (!env.dts->textures.empty() && env.dts->textures.find(fixed_id) != env.dts->textures.end()) { std::vector macro_args; auto tex = env.dts->textures.at(fixed_id); macro_args.push_back(pool.form(tex.name)); macro_args.push_back(pool.form(tex.tpage_name)); auto macro = pool.alloc_element( GenericOperator::make_function(pool.form("get-texture")), macro_args); result->push_back(macro); return; } } } } { // deal with virtual method calls. auto matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::METHOD_OF_OBJECT), {Matcher::any_reg(0), Matcher::any(1)}); auto mr = match(matcher, unstacked.at(0)); if (mr.matched && nargs >= 1) { auto vtable_reg = mr.maps.regs.at(0); ASSERT(vtable_reg); auto vtable_var_name = env.get_variable_name(*vtable_reg); auto arg0_mr = match(Matcher::any_reg(0), unstacked.at(1)); if (arg0_mr.matched && env.get_variable_name(*arg0_mr.maps.regs.at(0)) == vtable_var_name) { if (tp_type.kind != TP_Type::Kind::VIRTUAL_METHOD && tp_type.kind != TP_Type::Kind::GET_ART_BY_NAME_METHOD) { throw std::runtime_error( "Method internal mismatch. METHOD_OF_OBJECT operator didn't get a VIRTUAL_METHOD " "type."); } bool is_res_lump = tp_type.method_from_type().base_type() == "res-lump" || tp_type.method_from_type().base_type() == "entity-actor"; bool should_use_virtual = env.dts->ts.should_use_virtual_methods(tp_type.method_from_type(), tp_type.method_id()); if (!should_use_virtual && !is_res_lump) { throw std::runtime_error( fmt::format("Method call on {} id {} at {} used a virtual call unexpectedly.", tp_type.method_from_type().print(), tp_type.method_id(), "ya")); } if (should_use_virtual) { // lg::print("STACK\n{}\n\n", stack.print(env)); auto pop = pop_to_forms({*arg0_mr.maps.regs.at(0)}, env, pool, stack, allow_side_effects, {}, {2}) .at(0); // lg::print("GOT: {}\n", pop->to_string(env)); arg_forms.at(0) = pop; auto head = mr.maps.forms.at(1); auto head_obj = head->to_form(env); // rewrite get-art-by-name-method calls to get-art-by-name, which is a macro that // will apply the appropriate cast. if (tp_type.kind == TP_Type::Kind::GET_ART_BY_NAME_METHOD) { ASSERT(head_obj.is_symbol("get-art-by-name-method")); head = pool.form("get-art-by-name"); } else { if (head_obj.is_symbol("set-subtask-hook!")) { // change the integer argument to a constant. auto arg2o = arg_forms.at(2)->to_form(env); if (arg2o.is_int()) { int hook = arg2o.as_int(); static const std::vector hook_names = { "TASK_MANAGER_INIT_HOOK", "TASK_MANAGER_CLEANUP_HOOK", "TASK_MANAGER_UPDATE_HOOK", "TASK_MANAGER_CODE_HOOK", "TASK_MANAGER_COMPLETE_HOOK", "TASK_MANAGER_FAIL_HOOK", "TASK_MANAGER_EVENT_HOOK"}; if (hook >= 0 && hook < (int)hook_names.size()) { arg_forms.at(2) = pool.alloc_single_element_form( arg_forms.at(2)->parent_element, hook_names.at(hook)); } } } else if (head_obj.is_symbol() && tp_type.method_from_type().base_type() == "setting-control" && arg_forms.at(0)->to_form(env).is_symbol("*setting-control*") && arg_forms.size() > 1) { auto arg1_reg = get_form_reg_acc(arg_forms.at(1)); bool has_params = true; if (arg1_reg && arg1_reg->reg().is_s6()) { std::string new_head; if (head_obj.is_symbol("add-setting")) { new_head = "add-setting!"; } else if (head_obj.is_symbol("set-setting")) { new_head = "set-setting!"; } else if (head_obj.is_symbol("remove-setting")) { new_head = "remove-setting!"; has_params = false; } if (!new_head.empty()) { auto oldp = head->parent_element; head = pool.form(new_head); head->parent_element = oldp; arg_forms.erase(arg_forms.begin()); arg_forms.erase(arg_forms.begin()); if (has_params) { auto argset = arg_forms.at(0)->to_string(env); argset = argset.substr(1); auto argsym = arg_forms.at(1)->to_string(env); // convert the float param if (env.version >= GameVersion::Jak2) { static const std::unordered_set use_degrees_settings = { "matrix-blend-max-angle", "fov", }; static const std::unordered_set use_meters_settings = { "string-spline-max-move", "string-spline-max-move-player", "string-spline-accel", "string-spline-accel-player", "string-max-length", "string-min-length", "string-max-height", "string-min-height", "gun-max-height", "gun-min-height", "head-offset", "foot-offset", "extra-follow-height", "target-height", }; auto argf = arg_forms.at(2); arg_forms.at(2) = try_cast_simplify(argf, TypeSpec("float"), pool, env, true); if (argsym != "'rel") { if (use_degrees_settings.find(argset) != use_degrees_settings.end()) { arg_forms.at(2) = try_cast_simplify(arg_forms.at(2), TypeSpec("degrees"), pool, env, true); } else if (use_meters_settings.find(argset) != use_meters_settings.end()) { arg_forms.at(2) = try_cast_simplify(arg_forms.at(2), TypeSpec("meters"), pool, env, true); } } arg_forms.at(2)->parent_element = argf->parent_element; } // convert the int param static const std::unordered_map use_bitenum_settings = { {"process-mask", "process-mask"}, {"features", "game-feature"}, {"slave-options", "cam-slave-options"}, {"minimap", "minimap-flag"}, {"task-mask", "task-mask"}, }; static const std::unordered_map use_enum_settings = { {"sound-flava", "music-flava"}, {"exclusive-task", "game-task"}, }; auto argi = arg_forms.at(3); auto argi_o = argi->to_form(env); if (argi_o.is_int()) { if (use_bitenum_settings.find(argset) != use_bitenum_settings.end()) { auto en = env.dts->ts.try_enum_lookup(use_bitenum_settings.at(argset)); if (en) { arg_forms.at(3) = cast_to_bitfield_enum(en, pool, env, argi_o.as_int()); } } else if (use_enum_settings.find(argset) != use_enum_settings.end()) { auto en = env.dts->ts.try_enum_lookup(use_enum_settings.at(argset)); if (en) { arg_forms.at(3) = cast_to_int_enum(en, pool, env, argi_o.as_int()); } } } arg_forms.at(3)->parent_element = argi->parent_element; } } } } else if (env.func->process_stack_size > 0 && head_obj.is_symbol("stack-size-set!")) { // override process stack size auto old_size = arg_forms.at(1)->to_form(env); if (old_size.is_int()) { arg_forms.at(1) = pool.alloc_single_element_form( arg_forms.at(1)->parent_element, std::to_string(env.func->process_stack_size)); env.func->warnings.info("Process stack size was changed from {} to {}", old_size.as_int(), env.func->process_stack_size); } } } new_form = pool.alloc_element(GenericOperator::make_function(head), arg_forms); result->push_back(new_form); ASSERT(!go_next_state); return; } } } } // check for sound-play stuff { if (arg_forms.size() == 7 && unstacked.at(0)->to_form(env).is_symbol("sound-play-by-name")) { auto ssn = arg_forms.at(0)->to_string(env); static const std::string ssn_check = "(static-sound-name \""; // idk what a good way to do this is :( if (ssn.substr(0, ssn_check.size()) == ssn_check) { // get sound name auto sound_name = ssn.substr(ssn_check.size(), ssn.size() - ssn_check.size() - 2); // get sound id auto so_id_f = arg_forms.at(1); if (so_id_f->to_string(env) == "(new-sound-id)") { so_id_f = nullptr; } // get sound volume bool panic = false; auto so_vol_o = arg_forms.at(2)->to_form(env); Form* so_vol_f = nullptr; if (so_vol_o.is_int()) { auto vol_as_flt_temp = fixed_point_to_float(so_vol_o.as_int(), 1024) * 100; // fixed point convert is good but floating point accuracy sucks so we can do even better // with a hardcoded case for (int i = 0; i < 100; ++i) { if (int(i * 1024.0f / 100.0f) == so_vol_o.as_int()) { vol_as_flt_temp = i; break; } } // make the number now... if (int(vol_as_flt_temp * 1024.0f / 100.0f) == so_vol_o.as_int()) { if (so_vol_o.as_int() != 1024) { so_vol_f = pool.form(float_to_string(vol_as_flt_temp, false)); } } } else { auto mr_vol = match( Matcher::cast("int", Matcher::op_fixed(FixedOperatorKind::MULTIPLICATION, {Matcher::single(10.24f), Matcher::any(0)})), arg_forms.at(2)); if (mr_vol.matched) { so_vol_f = mr_vol.maps.forms.at(0); } else { // AAAHHHH i dont know how to handle this volume thing! panic = true; } } // get sound pitch mod auto so_pitch_o = arg_forms.at(3)->to_form(env); Form* so_pitch_f = nullptr; if (so_pitch_o.is_int()) { if (so_pitch_o.as_int() != 0) { so_pitch_f = pool.form(fixed_point_to_string(so_pitch_o.as_int(), 1524)); } } else { auto mr_pitch = match( Matcher::cast("int", Matcher::op_fixed(FixedOperatorKind::MULTIPLICATION, {Matcher::single(1524), Matcher::any(0)})), arg_forms.at(3)); if (mr_pitch.matched) { so_pitch_f = mr_pitch.maps.forms.at(0); } else { panic = true; } } // rest if (!panic) { auto so_bend = arg_forms.at(4); if (so_bend->to_form(env).is_int(0)) { so_bend = nullptr; } auto elt_group = arg_forms.at(5)->try_as_element(); if (elt_group && elt_group->op().is_func() && elt_group->op().func()->to_form(env).is_symbol("sound-group")) { Form* so_group_f = nullptr; if (elt_group->elts().size() == 1 && !elt_group->elts().at(0)->to_form(env).is_symbol("sfx")) { so_group_f = pool.form( elt_group->elts().at(0)->to_form(env).as_symbol().name_ptr); } auto so_positional_f = arg_forms.at(6); if (so_positional_f->to_form(env).is_symbol("#t")) { so_positional_f = nullptr; } // now make the macro call! std::vector macro_args; macro_args.push_back(pool.form(sound_name)); if (so_id_f) { macro_args.push_back(pool.form(":id")); macro_args.push_back(so_id_f); } if (so_vol_f) { macro_args.push_back(pool.form(":vol")); macro_args.push_back(so_vol_f); } if (so_pitch_f) { macro_args.push_back(pool.form(":pitch")); macro_args.push_back(so_pitch_f); } if (so_bend) { macro_args.push_back(pool.form(":bend")); macro_args.push_back(so_bend); } if (so_group_f) { macro_args.push_back(pool.form(":group")); macro_args.push_back(so_group_f); } if (so_positional_f) { macro_args.push_back(pool.form(":position")); macro_args.push_back(so_positional_f); } new_form = pool.alloc_element( GenericOperator::make_function(pool.form("sound-play")), macro_args); result->push_back(new_form); return; } } } } } new_form = pool.alloc_element(GenericOperator::make_function(unstacked.at(0)), arg_forms); { // detect method calls: // ex: ((method-of-type pair new) (quote global) pair gp-0 a3-0) constexpr int type_for_method = 0; constexpr int method_name = 1; auto deref_matcher = Matcher::op( GenericOpMatcher::fixed(FixedOperatorKind::METHOD_OF_TYPE), {Matcher::any_symbol(type_for_method), Matcher::any_constant_token(method_name)}); auto matcher = Matcher::op_with_rest(GenericOpMatcher::func(deref_matcher), {}); auto temp_form = pool.alloc_single_form(nullptr, new_form); auto match_result = match(matcher, temp_form); if (match_result.matched) { const auto type_1 = match_result.maps.strings.at(type_for_method); const auto& name = match_result.maps.strings.at(method_name); if (name == "new" && type_1 == "object") { // calling the new method of object. This is a special case that turns into an (object-new // macro. The arguments are allocation type-to-make and size of type // symbol, type, int. std::vector new_args = dynamic_cast(new_form)->elts(); // if needed, cast to to correct type. std::vector expected_arg_types = {TypeSpec("symbol"), TypeSpec("type"), TypeSpec("int")}; ASSERT(new_args.size() >= 3); for (size_t i = 0; i < 3; i++) { auto& var = all_pop_vars.at(i + 1); // 0 is the function itself. auto arg_type = env.get_types_before_op(var.idx()).get(var.reg()).typespec(); if (!env.dts->ts.tc(expected_arg_types.at(i), arg_type)) { new_args.at(i) = pool.form(expected_arg_types.at(i), new_args.at(i)); } } auto new_op = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::OBJECT_NEW), new_args); result->push_back(new_op); ASSERT(!go_next_state); return; } if (name == "new" && type_1 == "type") { std::vector new_args = dynamic_cast(new_form)->elts(); auto new_op = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::TYPE_NEW), new_args); result->push_back(new_op); ASSERT(!go_next_state); return; } else if (name == "new") { constexpr int allocation = 2; constexpr int type_for_arg = 3; auto alloc_matcher = Matcher::any_quoted_symbol(allocation); auto type_arg_matcher = Matcher::any_symbol(type_for_arg); matcher = Matcher::op_with_rest(GenericOpMatcher::func(deref_matcher), {alloc_matcher, type_arg_matcher}); match_result = match(matcher, temp_form); if (match_result.matched) { auto& alloc = match_result.maps.strings.at(allocation); if (alloc != "global" && alloc != "debug" && alloc != "process" && alloc != "loading-level") { throw std::runtime_error("Unrecognized heap symbol for new: " + alloc); } auto type_2 = match_result.maps.strings.at(type_for_arg); if (type_1 != type_2) { throw std::runtime_error( fmt::format("Inconsistent types in method call: {} and {}", type_1, type_2)); } if (type_2 == "array") { type_2 = "boxed-array"; } auto quoted_type = pool.form(SimpleAtom::make_sym_ptr(type_2)); if (alloc == "global" && type_1 == "pair") { // cons! // (new 'global 'pair a b) -> (cons a b) std::vector cons_args = {dynamic_cast(new_form)->elts().at(2), dynamic_cast(new_form)->elts().at(3)}; auto cons_op = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::CONS), cons_args); result->push_back(cons_op); ASSERT(!go_next_state); return; } else { // just normal construction on the heap std::vector new_args = dynamic_cast(new_form)->elts(); new_args.at(1) = quoted_type; auto new_op = pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::NEW), new_args); result->push_back(new_op); ASSERT(!go_next_state); return; } } // possible else case here to catch fixed-type new's in a nicer way } } } { // detect method calls: // ex: ((method-of-type x blah) arg...) constexpr int method_name = 0; constexpr int type_source = 1; auto deref_matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::METHOD_OF_TYPE), {Matcher::any(type_source), Matcher::any_constant_token(method_name)}); auto matcher = Matcher::op_with_rest(GenericOpMatcher::func(deref_matcher), {}); auto temp_form = pool.alloc_single_form(nullptr, new_form); auto match_result = match(matcher, temp_form); if (match_result.matched) { const auto name = match_result.maps.strings.at(method_name); if (name != "new") { // only do these checks on non-new methods. New methods are treated as functions because // they are never virtual and are never called like a method. if (tp_type.kind != TP_Type::Kind::NON_VIRTUAL_METHOD) { throw std::runtime_error(fmt::format( "Method internal mismatch. METHOD_OF_TYPE operator didn't get a NON_VIRTUAL_METHOD " "type. Got {} instead. {} {}", tp_type.print(), name, match_result.maps.forms.at(type_source)->to_string(env))); } } auto type_source_form = match_result.maps.forms.at(type_source); auto type_source_obj = type_source_form->to_form(env); bool is_res_lump = type_source_obj.is_symbol("res-lump"); if (name == "get-property-value-float" && is_res_lump) { auto as_macro = handle_get_property_value_float(arg_forms, pool, env); if (as_macro) { result->push_back(as_macro); return; } } else if (name == "get-property-data" && is_res_lump) { auto as_macro = handle_get_property_data(arg_forms, pool, env); if (as_macro) { result->push_back(as_macro); return; } } else if (name == "get-property-struct" && is_res_lump) { auto as_macro = handle_get_property_struct(arg_forms, pool, env); if (as_macro) { result->push_back(as_macro); return; } } else if (name == "get-property-value" && is_res_lump) { auto as_macro = handle_get_property_value(arg_forms, pool, env); if (as_macro) { result->push_back(as_macro); return; } } else if (name == "eval!" && arg_forms.size() == 2 && type_source_obj.is_symbol("script-context")) { // jak 2 macro for eval'ing a "script" auto arg_context = arg_forms.at(0); // the script context auto arg_script = arg_forms.at(1); // the script itself auto mr_new_script_context = match(Matcher::op_fixed(FixedOperatorKind::NEW, {Matcher::constant_token("'stack"), Matcher::constant_token("'script-context"), Matcher::any(0), Matcher::any(1), Matcher::any(2)}), arg_context); if (mr_new_script_context.matched) { // grab the arguments passed to the context alloc. nullptr = dont put in macro (uses // defaults) auto ctx_key = mr_new_script_context.maps.forms.at(0); // a key given to the context auto ctx_proc = mr_new_script_context.maps.forms.at(1); // the process the context's bound to auto ctx_vector = mr_new_script_context.maps.forms.at(2); // some vector for positioning // default for arg is (process->ppointer self) [weird] auto mr_ctx_key = match( Matcher::op_fixed(FixedOperatorKind::PROCESS_TO_PPOINTER, {Matcher::s6()}), ctx_key); if (mr_ctx_key.matched) { ctx_key = nullptr; } // default for process arg is just self auto mr_ctx_proc = match(Matcher::s6(), ctx_proc); if (mr_ctx_proc.matched) { ctx_proc = nullptr; } // default for vector is just #f auto mr_ctx_vector = match(Matcher::cast("vector", Matcher::symbol("#f")), ctx_vector); if (mr_ctx_vector.matched) { ctx_vector = nullptr; } // build the macro! std::vector macro_args; macro_args.push_back(arg_script); if (ctx_key) { macro_args.push_back(pool.form(":key")); macro_args.push_back(ctx_key); } if (ctx_proc) { macro_args.push_back(pool.form(":proc")); macro_args.push_back(ctx_proc); } if (ctx_vector) { macro_args.push_back(pool.form(":vector")); macro_args.push_back(ctx_vector); } result->push_back(pool.alloc_element( GenericOperator::make_function(pool.form("script-eval")), macro_args)); return; } } // if the type is the exact type of the argument, we want to build it into a method call if (type_source_obj.is_symbol(first_arg_type.base_type()) && name != "new") { if (env.dts->ts.should_use_virtual_methods(tp_type.method_from_type(), tp_type.method_id())) { throw std::runtime_error(fmt::format( "Expected type {} method id {} to use virtual methods, but it didn't. Set option " ":final in the deftype to disable virtual method calls", tp_type.method_from_type().print(), tp_type.method_id())); } auto method_op = pool.form(name); auto gop = GenericOperator::make_function(method_op); result->push_back(pool.alloc_element(gop, arg_forms)); ASSERT(!go_next_state); return; } if (name == "new" && arg_forms.size() >= 2) { bool got_stack_new = true; // method // (the-as symbol (new 'stack-no-clear 'draw-context)) // draw-context auto first_cast = arg_forms.at(0)->try_as_element(); if (!first_cast || first_cast->type() != TypeSpec("symbol")) { got_stack_new = false; } if (got_stack_new) { auto new_op = first_cast->source()->try_as_element(); if (!new_op || new_op->type().base_type() != type_source_form->to_string(env)) { got_stack_new = false; } } if (got_stack_new) { if (arg_forms.at(1)->to_string(env) != type_source_form->to_string(env)) { got_stack_new = false; } } if (got_stack_new) { std::vector stack_new_args; stack_new_args.push_back(pool.form("'stack")); if (type_source_form->to_string(env) == "array") { stack_new_args.push_back(pool.form("'boxed-array")); } else { stack_new_args.push_back(pool.form( fmt::format("'{}", type_source_form->to_string(env)))); } for (size_t i = 2; i < arg_forms.size(); i++) { stack_new_args.push_back(arg_forms.at(i)); } result->push_back(pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::NEW), stack_new_args)); ASSERT(!go_next_state); return; } } auto method_op = pool.form(type_source_form, name, false); auto gop = GenericOperator::make_function(method_op); result->push_back(pool.alloc_element(gop, arg_forms)); return; } } // detect launch-particles macro { if (unstacked.at(0)->to_form(env).is_symbol("sp-launch-particles-var")) { auto system = arg_forms.at(0); auto part = arg_forms.at(1); auto origin = arg_forms.at(2); auto launch_state = arg_forms.at(3); auto launch_control = arg_forms.at(4); auto rate = arg_forms.at(5); std::vector macro; if (system->to_string(env) != "*sp-particle-system-2d*") { macro.push_back(pool.form(":system")); macro.push_back(system); } macro.push_back(part); macro.push_back(origin); auto mr_launch_state = match(Matcher::cast("sparticle-launch-state", Matcher::symbol("#f")), launch_state); auto mr_launch_control = match(Matcher::cast("sparticle-launch-control", Matcher::symbol("#f")), launch_control); if (!mr_launch_state.matched) { macro.push_back(pool.form(":launch-state")); macro.push_back(launch_state); } if (!mr_launch_control.matched) { macro.push_back(pool.form(":launch-control")); macro.push_back(launch_control); } if (rate->to_string(env) != "1.0") { macro.push_back(pool.form(":rate")); macro.push_back(rate); } if (env.version != GameVersion::Jak1) { macro.push_back(pool.form(":origin-is-matrix")); macro.push_back(pool.form("#t")); } new_form = pool.alloc_element( GenericOperator::make_function(pool.form("launch-particles")), macro); } } // detect call-parent-method { const auto& guessed_name = env.func->guessed_name; if (guessed_name.kind == FunctionName::FunctionKind::METHOD) { // detect stuff like: ((find-parent-method...) arg...) auto mr_find_parent = match(Matcher::func(Matcher::symbol("find-parent-method"), {Matcher::symbol(env.func->method_of_type), Matcher::integer(env.func->guessed_name.method_id)}), unstacked.at(0) ); if (mr_find_parent.matched) { new_form = pool.alloc_element( GenericOperator::make_function(pool.form("call-parent-method")), arg_forms); } } else if (guessed_name.kind == FunctionName::FunctionKind::V_STATE && arg_forms.size() == 2) { // here, simply detect (find-parent-method...) // auto mr_find_parent = match(Matcher::symbol("find-parent-method"), unstacked.at(0)); if (mr_find_parent.matched) { auto state_info = env.dts->ts.lookup_method(guessed_name.type_name, guessed_name.state_name); if (arg_forms.at(0)->to_string(env) == guessed_name.type_name && arg_forms.at(1)->to_string(env) == std::to_string(state_info.id)) { new_form = pool.alloc_element( GenericOperator::make_function(pool.form("find-parent-state"))); } } } } result->push_back(new_form); } void FunctionCallElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { std::vector rewritten; update_from_stack(env, pool, stack, &rewritten, true); for (auto x : rewritten) { stack.push_form_element(x, true); } } /////////////////// // DerefElement /////////////////// ConstantTokenElement* DerefElement::try_as_art_const(const Env& env, FormPool& pool) { auto mr = match( Matcher::deref(Matcher::s6(), false, {DerefTokenMatcher::string("draw"), DerefTokenMatcher::string("art-group"), DerefTokenMatcher::string("data"), DerefTokenMatcher::any_integer(0)}), this); if (mr.matched) { auto elt_name = env.get_art_elt_name(mr.maps.ints.at(0)); if (elt_name) { return pool.alloc_element(*elt_name); } else { lg::error("function `{}`: did not find art element {} in {}", env.func->name(), mr.maps.ints.at(0), env.art_group()); } } return nullptr; } GenericElement* DerefElement::try_as_joint_node_index(const Env& env, FormPool& pool) { auto mr = match(Matcher::deref(Matcher::s6(), false, {DerefTokenMatcher::string("node-list"), DerefTokenMatcher::string("data"), DerefTokenMatcher::any_integer(0)}), this); if (mr.matched) { // lg::print("func {} joint-geo: {}\n", env.func->name(), env.joint_geo()); auto info = env.dts->jg_info; std::vector args; auto joint_name = env.get_joint_node_name(mr.maps.ints.at(0)); args.push_back(pool.form(env.joint_geo())); if (joint_name) { args.push_back(pool.form(joint_name.value())); return pool.alloc_element( GenericOperator::make_function(pool.form("joint-node")), args); } else { lg::error("function `{}`: did not find joint node {} in {}", env.func->name(), mr.maps.ints.at(0), env.joint_geo()); } } return nullptr; } GenericElement* DerefElement::try_as_curtime(const Env& env, FormPool& pool) { if (env.version == GameVersion::Jak1) { auto mr = match(Matcher::deref(Matcher::symbol("*display*"), false, {DerefTokenMatcher::string("base-frame-counter")}), this); if (mr.matched) { return pool.alloc_element( GenericOperator::make_function(pool.form("current-time"))); } } else { auto mr = match(Matcher::deref(Matcher::s6(), false, {DerefTokenMatcher::string("clock"), DerefTokenMatcher::string("frame-counter")}), this); if (mr.matched) { return pool.alloc_element( GenericOperator::make_function(pool.form("current-time"))); } } return nullptr; } GenericElement* DerefElement::try_as_seconds_per_frame(const Env& env, FormPool& pool) { if (env.version == GameVersion::Jak1) { auto mr = match(Matcher::deref(Matcher::symbol("*display*"), false, {DerefTokenMatcher::string("seconds-per-frame")}), this); if (mr.matched) { return pool.alloc_element( GenericOperator::make_function(pool.form("seconds-per-frame"))); } } else { auto mr = match(Matcher::deref(Matcher::s6(), false, {DerefTokenMatcher::string("clock"), DerefTokenMatcher::string("seconds-per-frame")}), this); if (mr.matched) { return pool.alloc_element( GenericOperator::make_function(pool.form("seconds-per-frame"))); } } return nullptr; } void DerefElement::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { mark_popped(); // todo - update var tokens from stack? m_base->update_children_from_stack(env, pool, stack, allow_side_effects); // look for sym->str-ptr auto sym_str = m_base->try_as_element(); if (sym_str && m_tokens.size() == 1 && m_tokens.at(0).is_int(0)) { result->push_back(pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::SYMBOL_TO_STRING), sym_str->src())); return; } // merge nested ->'s inline_nested(); auto as_art = try_as_art_const(env, pool); if (as_art) { result->push_back(as_art); return; } auto as_jnode = try_as_joint_node_index(env, pool); if (as_jnode) { result->push_back(as_jnode); return; } auto as_simple_expr = m_base->try_as_element(); if (env.version >= GameVersion::Jak2 && as_simple_expr && as_simple_expr->expr().is_identity() && as_simple_expr->expr().get_arg(0).is_sym_val() && as_simple_expr->expr().get_arg(0).get_str() == "*game-info*" && m_tokens.size() >= 2 && m_tokens.at(0).is_field_name("sub-task-list") && m_tokens.at(1).is_int()) { m_tokens.at(1) = DerefToken::make_int_expr(cast_to_int_enum( env.dts->ts.try_enum_lookup("game-task-node"), pool, env, m_tokens.at(1).int_constant())); } // current-time macro auto as_curtime = try_as_curtime(env, pool); if (as_curtime) { result->push_back(as_curtime); return; } // seconds-per-frame macro auto as_seconds_per_frame = try_as_seconds_per_frame(env, pool); if (as_seconds_per_frame) { result->push_back(as_seconds_per_frame); return; } result->push_back(this); } void DerefElement::inline_nested() { auto as_deref = dynamic_cast(m_base->try_as_single_element()); if (as_deref) { if (!m_is_addr_of && !as_deref->is_addr_of()) { m_tokens.insert(m_tokens.begin(), as_deref->tokens().begin(), as_deref->tokens().end()); m_base = as_deref->m_base; } } } /////////////////// // UntilElement /////////////////// void UntilElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { // in asm: // LTOP: // body // condition // jump to top // so we can end up getting the body/condition wrong. // the way the CfgPass works means that we put too much in condition. // we can safely move stuff from the top of condition to the bottom of body. mark_popped(); std::vector condition_to_body; { FormStack condition_temp_stack(false); for (auto& entry : condition->elts()) { entry->push_to_stack(env, pool, condition_temp_stack); } condition_to_body = condition_temp_stack.rewrite(pool, env); condition->clear(); ASSERT(!condition_to_body.empty()); condition->push_back(condition_to_body.back()); condition_to_body.pop_back(); } { FormStack body_temp_stack(false); for (auto& entry : body->elts()) { entry->push_to_stack(env, pool, body_temp_stack); } auto new_entries = body_temp_stack.rewrite(pool, env); body->clear(); for (auto e : new_entries) { if (!dynamic_cast(e)) { body->push_back(e); } } for (auto e : condition_to_body) { if (!dynamic_cast(e)) { body->push_back(e); } } if (body->size() == 0) { body->push_back(pool.alloc_element()); } } stack.push_form_element(this, true); if (false_destination) { env.func->warnings.warning("new jak 2 until loop case, check carefully"); stack.push_value_to_reg(*false_destination, pool.form(SimpleAtom::make_sym_val("#f")), true, TypeSpec("symbol")); RegAccessSet accessed_regs; body->collect_vars(accessed_regs, true); condition->collect_vars(accessed_regs, true); auto check_name = env.get_variable_name(*false_destination); for (auto& reg : accessed_regs) { if (env.get_variable_name(reg) == check_name) { ASSERT_MSG(false, fmt::format("Jak 2 loop uses delay slot variable improperly: {} {}\n", env.func->name(), check_name)); } } } } void WhileElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); bool first = true; for (auto form : {body, condition}) { FormStack temp_stack(first && stack.is_root()); first = false; for (auto& entry : form->elts()) { entry->push_to_stack(env, pool, temp_stack); } auto new_entries = temp_stack.rewrite(pool, env); form->clear(); for (auto e : new_entries) { form->push_back(e); } } stack.push_form_element(this, true); } namespace { // (if x (-> x ppointer)) -> (process->ppointer x) Form* try_rewrite_as_process_to_ppointer(CondNoElseElement* value, FormStack& stack, FormPool& pool, const Env& env) { if (value->entries.size() != 1) { return nullptr; } auto condition = value->entries.at(0).condition; auto body = value->entries[0].body; // safe to look for a reg directly here. auto condition_matcher = Matcher::op(GenericOpMatcher::condition(IR2_Condition::Kind::TRUTHY), {Matcher::any_reg(0)}); auto condition_mr = match(condition_matcher, condition); if (!condition_mr.matched) { return nullptr; } auto body_matcher = Matcher::deref(Matcher::any_reg(0), false, {DerefTokenMatcher::string("ppointer")}); auto body_mr = match(body_matcher, body); if (!body_mr.matched) { return nullptr; } auto body_var = *body_mr.maps.regs.at(0); auto condition_var = *condition_mr.maps.regs.at(0); if (env.get_variable_name(body_var) != env.get_variable_name(condition_var)) { return nullptr; } // lg::print("Matched condition {} in {}\n", condition_var.to_string(env), // value->to_string(env)); auto* menv = const_cast(&env); menv->disable_use(body_var); auto repopped = stack.pop_reg(condition_var, {}, env, true); if (!repopped) { repopped = var_to_form(condition_var, pool); } else { if (!env.dts->ts.tc(TypeSpec("process"), env.get_variable_type(condition_var, true))) { repopped = pool.form(TypeSpec("process"), repopped); } } return pool.form( GenericOperator::make_fixed(FixedOperatorKind::PROCESS_TO_PPOINTER), repopped); } // (if x (-> x 0 self)) -> (ppointer->process x) Form* try_rewrite_as_pppointer_to_process(CondNoElseElement* value, FormStack& stack, FormPool& pool, const Env& env) { if (value->entries.size() != 1) { return nullptr; } auto condition = value->entries.at(0).condition; auto body = value->entries[0].body; // safe to look for a reg directly here. auto condition_matcher = Matcher::op(GenericOpMatcher::condition(IR2_Condition::Kind::TRUTHY), {Matcher::any_reg(0)}); auto condition_mr = match(condition_matcher, condition); if (!condition_mr.matched) { return nullptr; } auto body_matcher = Matcher::deref(Matcher::any_reg(0), false, {DerefTokenMatcher::integer(0), DerefTokenMatcher::string("self")}); auto body_mr = match(body_matcher, body); if (!body_mr.matched) { return nullptr; } auto body_var = *body_mr.maps.regs.at(0); auto condition_var = *condition_mr.maps.regs.at(0); if (env.get_variable_name(body_var) != env.get_variable_name(condition_var)) { return nullptr; } // lg::print("Matched condition {} in {}\n", condition_var.to_string(env), // value->to_string(env)); auto* menv = const_cast(&env); menv->disable_use(body_var); auto repopped = stack.pop_reg(condition_var, {}, env, true); if (!repopped) { repopped = var_to_form(condition_var, pool); } return pool.form( GenericOperator::make_fixed(FixedOperatorKind::PPOINTER_TO_PROCESS), repopped); } // (if (> (-> self skel active-channels) 0) // (-> self skel root-channel 0 frame-group) // ) // (ja-group :chan 0) Form* try_rewrite_as_ja_group(CondNoElseElement* value, FormStack& /*stack*/, FormPool& pool, const Env& env) { if (value->entries.size() != 1) { return nullptr; } auto condition = value->entries.at(0).condition; auto body = value->entries[0].body; // safe to look for a reg directly here. auto condition_matcher = Matcher::op_fixed( FixedOperatorKind::GT, {Matcher::deref(Matcher::s6(), false, {DerefTokenMatcher::string("skel"), DerefTokenMatcher::string("active-channels")}), Matcher::any(0)}); auto condition_mr = match(condition_matcher, condition); if (!condition_mr.matched) { return nullptr; } auto body_matcher = Matcher::deref( Matcher::s6(), false, {DerefTokenMatcher::string("skel"), DerefTokenMatcher::string("root-channel"), DerefTokenMatcher::any_expr_or_int(0), DerefTokenMatcher::string("frame-group")}); auto body_mr = match(body_matcher, body); if (!body_mr.matched) { return nullptr; } auto chan = body_mr.int_or_form_to_form(pool, 0); auto obj_chan = chan->to_form(env); if (condition_mr.maps.forms.at(0)->to_form(env) == obj_chan) { auto func = GenericOperator::make_function(pool.form("ja-group")); if (obj_chan.is_int(0)) { return pool.form(func); } else { return pool.form(func, pool.form(":chan"), condition_mr.maps.forms.at(0)); } } return nullptr; } } // namespace /////////////////// // CondNoElseElement /////////////////// void CondNoElseElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); if (already_rewritten) { stack.push_form_element(this, true); return; } // the first condition is special auto first_condition = entries.front().condition; // lets evaluate in on the parent stack... for (auto x : first_condition->elts()) { x->push_to_stack(env, pool, stack); } RegisterAccess write_as_value = final_destination; bool first = true; for (auto& entry : entries) { for (auto form : {entry.condition, entry.body}) { if (form == first_condition) { form->clear(); form->push_back(stack.pop_back(pool)); } else { FormStack temp_stack(first && stack.is_root()); first = false; for (auto& elt : form->elts()) { elt->push_to_stack(env, pool, temp_stack); } std::vector new_entries; if (form == entry.body && used_as_value) { // try to advance us to the real write so we don't use the final_destination, // which may contain the wrong variable, but right register. std::optional written_var; new_entries = rewrite_to_get_var(temp_stack, pool, final_destination, env, &written_var); if (written_var) { write_as_value = *written_var; } } else { new_entries = temp_stack.rewrite(pool, env); } form->clear(); for (auto e : new_entries) { form->push_back(e); } } } } if (used_as_value) { // TODO - is this wrong? auto as_process_to_ppointer = try_rewrite_as_process_to_ppointer(this, stack, pool, env); if (as_process_to_ppointer) { stack.push_value_to_reg(write_as_value, as_process_to_ppointer, true, env.get_variable_type(final_destination, false)); } else { auto as_ppointer_to_process = try_rewrite_as_pppointer_to_process(this, stack, pool, env); if (as_ppointer_to_process) { stack.push_value_to_reg(write_as_value, as_ppointer_to_process, true, env.get_variable_type(final_destination, false)); } else { auto as_ja_group = try_rewrite_as_ja_group(this, stack, pool, env); if (as_ja_group) { 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)); } } } } else { stack.push_form_element(this, true); } already_rewritten = true; } void CondWithElseElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); if (already_rewritten) { stack.push_form_element(this, true); return; } // first, let's try to detect if all bodies write the same value std::optional last_var; bool rewrite_as_set = true; // the first condition is special auto first_condition = entries.front().condition; // lets evaluate in on the parent stack... for (auto x : first_condition->elts()) { x->push_to_stack(env, pool, stack); } // process conditions and bodies for (auto& entry : entries) { for (auto form : {entry.condition, entry.body}) { if (form == first_condition) { form->clear(); form->push_back(stack.pop_back(pool)); } else { FormStack temp_stack(false); if (form == entry.body) { auto as_setvar = dynamic_cast(form->elts().back()); if (as_setvar && as_setvar->is_dead_set() && as_setvar->src_type() != TypeSpec("float")) { rewrite_as_set = false; } } for (auto& elt : form->elts()) { elt->push_to_stack(env, pool, temp_stack); } std::vector new_entries; new_entries = temp_stack.rewrite(pool, env); form->clear(); for (auto e : new_entries) { form->push_back(e); } } } } // process else. FormStack temp_stack(false); for (auto& elt : else_ir->elts()) { elt->push_to_stack(env, pool, temp_stack); } std::vector new_entries; new_entries = temp_stack.rewrite(pool, env); else_ir->clear(); for (auto e : new_entries) { else_ir->push_back(e); } // collect all forms which should write the output. std::vector write_output_forms; for (const auto& entry : entries) { write_output_forms.push_back(entry.body); } write_output_forms.push_back(else_ir); // check all to see if they write the value. std::vector dest_sets; std::vector source_types; // only explicit accesses that aren't move-eliminated int empty_count = 0; for (auto form : write_output_forms) { auto last_in_body = dynamic_cast(form->elts().back()); if (last_in_body) { dest_sets.push_back(last_in_body); if (last_var.has_value()) { if (last_var->reg() != last_in_body->dst().reg()) { rewrite_as_set = false; break; } // note: we use the dest type here because the rewrite will leave behind a cast to this. auto type = env.get_variable_type(last_in_body->dst(), true); source_types.push_back(type); } last_var = last_in_body->dst(); } empty_count++; } if (empty_count > 0 && env.aggressively_reject_cond_to_value_rewrite) { rewrite_as_set = false; } if (!last_var.has_value()) { rewrite_as_set = false; } // determine if set destination is used bool set_unused = false; if (rewrite_as_set) { auto& info = env.reg_use().op.at(last_var->idx()); if (info.written_and_unused.find(last_var->reg()) != info.written_and_unused.end()) { set_unused = true; } } // rewrite extra sets as needed. if (rewrite_as_set && !set_unused) { for (auto& entry : entries) { rewrite_to_get_var(entry.body->elts(), pool, *last_var, env); entry.body->claim_all_children(); } rewrite_to_get_var(else_ir->elts(), pool, *last_var, env); else_ir->claim_all_children(); } // update register info if (rewrite_as_set && !set_unused) { // might not be the same if a set is eliminated by a coloring move. // ASSERT(dest_sets.size() == write_output_forms.size()); if (!dest_sets.empty()) { for (size_t i = 0; i < dest_sets.size() - 1; i++) { auto var = dest_sets.at(i)->dst(); auto* env2 = const_cast(&env); env2->disable_def(var, env2->func->warnings); } } } // merge conds in the else block. auto else_as_another_cond = else_ir->try_as_element(); if (else_as_another_cond) { while (else_as_another_cond) { for (auto& e : else_as_another_cond->entries) { entries.push_back(e); } else_ir = else_as_another_cond->else_ir; else_as_another_cond = else_ir->try_as_element(); } } if (rewrite_as_set) { if (set_unused) { stack.push_form_element(this, true); } else { // We may need to insert a cast here. // Note: // I think this might skip a cast if you have something like // (set! x (if y z (expr))) and z requires a cast, but the move from z to x is // eliminated by GOAL's register allocator. // lg::print("func: {}\n", env.func->name()); // // lg::print("checking:\n"); // for (auto& t : source_types) { // lg::print(" {}\n", t.print()); // } auto expected_type = env.get_variable_type(*last_var, true); // lg::print("The expected type is {}\n", expected_type.print()); auto result_type = source_types.empty() ? expected_type : env.dts->ts.lowest_common_ancestor(source_types); // lg::print("but we actually got {}\n", result_type.print()); Form* result_value = pool.alloc_single_form(nullptr, this); if (!env.dts->ts.tc(expected_type, result_type)) { result_value = pool.form(expected_type, result_value); } // lg::print("{}\n", result_value->to_string(env)); stack.push_value_to_reg(*last_var, result_value, true, env.get_variable_type(*last_var, false)); } } else { stack.push_form_element(this, true); } already_rewritten = true; } /////////////////// // ShortCircuitElement /////////////////// FormElement* sc_to_handle_get_proc(ShortCircuitElement* elt, const Env& env, FormPool& pool, FormStack& stack) { if (elt->kind != ShortCircuitElement::AND) { return nullptr; } if (elt->entries.size() < 2) { return nullptr; } Form* last = elt->entries[elt->entries.size() - 1].condition; Form* second_to_last = elt->entries[elt->entries.size() - 2].condition; auto result = last_two_in_and_to_handle_get_proc(second_to_last, last, env, pool, stack, elt->entries.size() > 2); if (!result) { return nullptr; } if (elt->entries.size() == 2) { // just replace the whole thing return result; } else { elt->entries.pop_back(); elt->entries.back().condition = pool.alloc_single_form(elt, result); } return nullptr; } void ShortCircuitElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); if (already_rewritten) { stack.push_form_element(this, true); return; } // the first condition is special auto first_condition = entries.front().condition; // lets evaluate in on the parent stack... for (auto x : first_condition->elts()) { x->push_to_stack(env, pool, stack); } for (int i = 0; i < int(entries.size()); i++) { auto& entry = entries.at(i); if (entry.condition == first_condition) { entry.condition->clear(); entry.condition->push_back(stack.pop_back(pool)); } else { FormStack temp_stack(false); for (auto& elt : entry.condition->elts()) { elt->push_to_stack(env, pool, temp_stack); } std::vector new_entries; if (i == int(entries.size()) - 1) { new_entries = rewrite_to_get_var(temp_stack, pool, final_result, env); } else { new_entries = temp_stack.rewrite(pool, env); } entry.condition->clear(); for (auto e : new_entries) { if (dynamic_cast(e)) { continue; } entry.condition->push_back(e); } if (entry.condition->elts().empty()) { entry.condition->push_back(pool.alloc_element()); } } } FormElement* to_push = this; auto as_handle_get = sc_to_handle_get_proc(this, env, pool, stack); if (as_handle_get) { to_push = as_handle_get; } ASSERT(used_as_value.has_value()); stack.push_value_to_reg(final_result, pool.alloc_single_form(nullptr, to_push), true, env.get_variable_type(final_result, false)); already_rewritten = true; } void ShortCircuitElement::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool) { mark_popped(); (void)stack; if (already_rewritten) { result->push_back(this); return; } for (int i = 0; i < int(entries.size()); i++) { auto& entry = entries.at(i); FormStack temp_stack(false); for (auto& elt : entry.condition->elts()) { elt->push_to_stack(env, pool, temp_stack); } std::vector new_entries; if (i == int(entries.size()) - 1) { new_entries = rewrite_to_get_var(temp_stack, pool, final_result, env); } else { new_entries = temp_stack.rewrite(pool, env); } entry.condition->clear(); for (auto e : new_entries) { entry.condition->push_back(e); } } result->push_back(this); already_rewritten = true; } namespace { Matcher make_int_uint_cast_matcher(const Matcher& thing) { return Matcher::match_or({Matcher::cast("uint", thing), Matcher::cast("int", thing), thing}); } } // namespace /////////////////// // ConditionElement /////////////////// namespace { Form* try_make_constant_from_int_for_compare(s64 value, const TypeSpec& type, FormPool& pool, const Env& env) { auto enum_type_info = env.dts->ts.try_enum_lookup(type); if (enum_type_info) { if (enum_type_info->is_bitfield()) { if (value != 0) { // prefer (zero? x) for bitfield enums. return cast_to_bitfield_enum(enum_type_info, pool, env, value); } } else { return cast_to_int_enum(enum_type_info, pool, env, value); } } return nullptr; } std::vector cast_to_64_bit(const std::vector& forms, const std::vector& types, FormPool& pool, const Env& env) { std::vector result; for (size_t i = 0; i < forms.size(); i++) { if (env.dts->ts.tc(TypeSpec("uint128"), types.at(i))) { result.push_back(cast_form(forms[i], TypeSpec("uint"), pool, env)); } else if (env.dts->ts.tc(TypeSpec("int128"), types.at(i))) { result.push_back(cast_form(forms[i], TypeSpec("int"), pool, env)); } else { result.push_back(forms[i]); } } return result; } std::vector cast_for_64bit_equality_check(const std::vector& forms, const std::vector& types, FormPool& pool, const Env& env) { if (env.version == GameVersion::Jak1) { return cast_to_64_bit(forms, types, pool, env); } std::vector result; for (size_t i = 0; i < forms.size(); i++) { if (env.dts->ts.tc(TypeSpec("uint128"), types.at(i))) { result.push_back(cast_form(forms[i], TypeSpec("uint"), pool, env)); } else if (env.dts->ts.tc(TypeSpec("int128"), types.at(i))) { result.push_back(cast_form(forms[i], TypeSpec("int"), pool, env)); } else if (env.dts->ts.tc(TypeSpec("float"), types.at(i))) { result.push_back(cast_form(forms[i], TypeSpec("int"), pool, env)); } else { result.push_back(forms[i]); } } return result; } FormElement* try_make_nonzero_logtest(Form* in, FormPool& pool) { /* (defmacro logtest? (a b) "does a have any of the bits in b?" `(nonzero? (logand ,a ,b)) ) */ auto static const logand_matcher = Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::LOGAND), {Matcher::any(0), Matcher::any(1)}); auto mr_logand = match(logand_matcher, in); if (mr_logand.matched) { return pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::LOGTEST), mr_logand.maps.forms.at(0), mr_logand.maps.forms.at(1)); } return nullptr; } FormElement* try_make_focus_test_macro(Form* in, FormPool& pool) { /* (defmacro focus-test? (pfoc &rest status) `(logtest? (-> (the process-focusable ,pfoc) focus-status) (focus-status ,@status))) pfoc first: (logtest? (-> self focus-status) (focus-status dead hit grabbed)) enum first: (logtest? (focus-status mech) (-> a1-2 focus-status)) */ auto logtest_focus_matcher_pfoc = Matcher::op( GenericOpMatcher::fixed(FixedOperatorKind::LOGTEST), {Matcher::deref(Matcher::any(0), false, {DerefTokenMatcher::string("focus-status")}), Matcher::op_with_rest(GenericOpMatcher::func(Matcher::constant_token("focus-status")), {})}); auto logtest_focus_matcher_enum = Matcher::op( GenericOpMatcher::fixed(FixedOperatorKind::LOGTEST), { Matcher::op_with_rest(GenericOpMatcher::func(Matcher::constant_token("focus-status")), {}), Matcher::deref(Matcher::any(0), false, {DerefTokenMatcher::string("focus-status")}), }); auto mr_logtest_pfoc = match(logtest_focus_matcher_pfoc, in); auto mr_logtest_enum = match(logtest_focus_matcher_enum, in); if (mr_logtest_pfoc.matched) { auto logtest_elt = dynamic_cast(in->at(0)); if (logtest_elt != nullptr) { auto focus_form = logtest_elt->elts().at(1); std::vector macro; macro.push_back(mr_logtest_pfoc.maps.forms.at(0)); auto* focus = dynamic_cast(focus_form->at(0)); if (focus) { macro.insert(macro.end(), focus->elts().begin(), focus->elts().end()); } return pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::FOCUS_TEST), macro); } } if (mr_logtest_enum.matched) { auto logtest_elt = dynamic_cast(in->at(0)); if (logtest_elt != nullptr) { auto focus_form = logtest_elt->elts().at(0); std::vector macro; macro.push_back(mr_logtest_enum.maps.forms.at(0)); auto* focus = dynamic_cast(focus_form->at(0)); if (focus) { macro.insert(macro.end(), focus->elts().begin(), focus->elts().end()); } return pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::FOCUS_TEST), macro); } } return nullptr; } FormElement* try_make_logtest_mouse_macro(Form* in, FormPool& pool) { /* (defmacro mouse-pressed () `(-> *mouse* button0-rel 0) ) (defmacro mouse-hold () `(-> *mouse* button0-abs 0) ) (defmacro mouse-pressed? (&rest buttons) `(logtest? (mouse-pressed) (mouse-buttons ,@buttons)) ) (defmacro mouse-hold? (&rest buttons) `(logtest? (mouse-hold) (mouse-buttons ,@buttons)) ) */ auto static const mouse_matcher = Matcher::op( GenericOpMatcher::fixed(FixedOperatorKind::LOGTEST), {Matcher::deref(Matcher::symbol("*mouse*"), false, {DerefTokenMatcher::any_string(2), DerefTokenMatcher::integer(0)}), Matcher::op_with_rest(GenericOpMatcher::func(Matcher::constant_token("mouse-buttons")), {})}); auto static const mouse_matcher_inv = Matcher::op( GenericOpMatcher::fixed(FixedOperatorKind::LOGTEST), {Matcher::op_with_rest(GenericOpMatcher::func(Matcher::constant_token("mouse-buttons")), {}), Matcher::deref(Matcher::symbol("*mouse*"), false, {DerefTokenMatcher::any_string(2), DerefTokenMatcher::integer(0)})}); bool inv_match = false; auto mr = match(mouse_matcher, in); if (!mr.matched) { mr = match(mouse_matcher_inv, in); inv_match = true; } if (mr.matched) { enum { ABS, REL, NIL } t = NIL; if (mr.maps.strings.at(2) == "button0-abs") { t = ABS; } else if (mr.maps.strings.at(2) == "button0-rel") { t = REL; } if (t != NIL) { auto logtest_elt = dynamic_cast(in->at(0)); if (logtest_elt) { auto buttons_form = logtest_elt->elts().at(inv_match ? 0 : 1); std::vector v; GenericElement* butts = dynamic_cast(buttons_form->at(0)); // the form with the buttons itself if (butts) { v = butts->elts(); } return pool.alloc_element( GenericOperator::make_fixed(t == ABS ? FixedOperatorKind::MOUSE_HOLD_P : FixedOperatorKind::MOUSE_PRESSED_P), v); } } } return nullptr; } FormElement* try_make_logtest_cpad_macro(Form* in, FormPool& pool) { /* (defmacro cpad-pressed (pad-idx) `(-> *cpad-list* cpads ,pad-idx button0-rel 0) ) (defmacro cpad-hold (pad-idx) `(-> *cpad-list* cpads ,pad-idx button0-abs 0) ) (defmacro cpad-pressed? (pad-idx &rest buttons) `(logtest? (cpad-pressed ,pad-idx) (pad-buttons ,@buttons)) ) (defmacro cpad-hold? (pad-idx &rest buttons) `(logtest? (cpad-hold ,pad-idx) (pad-buttons ,@buttons)) ) */ auto static const cpad_matcher = Matcher::op( GenericOpMatcher::fixed(FixedOperatorKind::LOGTEST), {Matcher::deref(Matcher::symbol("*cpad-list*"), false, {DerefTokenMatcher::string("cpads"), DerefTokenMatcher::any_expr_or_int(0), DerefTokenMatcher::any_string(2), DerefTokenMatcher::integer(0)}), Matcher::op_with_rest(GenericOpMatcher::func(Matcher::constant_token("pad-buttons")), {})}); auto static const cpad_matcher_inv = Matcher::op( GenericOpMatcher::fixed(FixedOperatorKind::LOGTEST), {Matcher::op_with_rest(GenericOpMatcher::func(Matcher::constant_token("pad-buttons")), {}), Matcher::deref(Matcher::symbol("*cpad-list*"), false, {DerefTokenMatcher::string("cpads"), DerefTokenMatcher::any_expr_or_int(0), DerefTokenMatcher::any_string(2), DerefTokenMatcher::integer(0)})}); bool inv_match = false; auto mr = match(cpad_matcher, in); if (!mr.matched) { mr = match(cpad_matcher_inv, in); inv_match = true; } if (mr.matched) { enum { ABS, REL, NIL } t = NIL; if (mr.maps.strings.at(2) == "button0-abs") { t = ABS; } else if (mr.maps.strings.at(2) == "button0-rel") { t = REL; } if (t != NIL) { auto logtest_elt = dynamic_cast(in->at(0)); if (logtest_elt) { auto buttons_form = logtest_elt->elts().at(inv_match ? 0 : 1); std::vector v; v.push_back(mr.int_or_form_to_form(pool, 0)); GenericElement* butts = dynamic_cast(buttons_form->at(0)); // the form with the buttons itself if (butts) { v.insert(v.end(), butts->elts().begin(), butts->elts().end()); } return pool.alloc_element( GenericOperator::make_fixed(t == ABS ? FixedOperatorKind::CPAD_HOLD_P : FixedOperatorKind::CPAD_PRESSED_P), v); } } } return nullptr; } FormElement* convert_logtest_to_fancy_macro(FormPool& pool, Form* logtest_form) { auto as_cpad_macro = try_make_logtest_cpad_macro(logtest_form, pool); if (as_cpad_macro) { return as_cpad_macro; } auto as_mouse_macro = try_make_logtest_mouse_macro(logtest_form, pool); if (as_mouse_macro) { return as_mouse_macro; } auto focus_test_macro = try_make_focus_test_macro(logtest_form, pool); if (focus_test_macro) { return focus_test_macro; } return nullptr; } } // namespace FormElement* ConditionElement::make_zero_check_generic(const Env& env, FormPool& pool, const std::vector& source_forms, const std::vector& source_types) { // (zero? (+ thing small-integer)) -> (= thing (- small-integer)) ASSERT(source_forms.size() == 1); auto enum_type_info = env.dts->ts.try_enum_lookup(source_types.at(0)); if (enum_type_info && !enum_type_info->is_bitfield()) { // (zero? (+ (the-as uint arg0) (the-as uint -2))) check enum value auto mr = match( Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::ADDITION), {make_int_uint_cast_matcher(Matcher::any(0)), Matcher::match_or({Matcher::any_integer(1), make_int_uint_cast_matcher(Matcher::any_integer(1))})}), source_forms.at(0)); if (mr.matched) { s64 value = mr.maps.ints.at(1); value = -value; auto enum_constant = cast_to_int_enum(enum_type_info, pool, env, value); return pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::EQ), std::vector{mr.maps.forms.at(0), enum_constant}); } } { auto mr = match(Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::ADDITION), {Matcher::any(0), Matcher::any_integer(1)}), source_forms.at(0)); if (mr.matched) { s64 value = -mr.maps.ints.at(1); auto value_form = pool.form(SimpleAtom::make_int_constant(value)); return pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::EQ), std::vector{mr.maps.forms.at(0), value_form}); } } auto nice_constant = try_make_constant_from_int_for_compare(0, source_types.at(0), pool, env); if (nice_constant) { return pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::EQ), std::vector{source_forms.at(0), nice_constant}); } auto as_logtest = try_make_nonzero_logtest(source_forms.at(0), pool); if (as_logtest) { auto logtest_form = pool.alloc_single_form(nullptr, as_logtest); auto fancy_form = convert_logtest_to_fancy_macro(pool, logtest_form); if (fancy_form) { return pool.alloc_element( GenericOperator::make_compare(IR2_Condition::Kind::FALSE), pool.alloc_single_form(nullptr, fancy_form)); } return pool.alloc_element( GenericOperator::make_compare(IR2_Condition::Kind::FALSE), logtest_form); } return pool.alloc_element(GenericOperator::make_compare(m_kind), source_forms); } FormElement* ConditionElement::make_nonzero_check_generic(const Env& env, FormPool& pool, const std::vector& source_forms, const std::vector&) { // for (nonzero? (-> obj bitfield)) FormElement* bitfield_compare = nullptr; ASSERT(source_forms.size() == 1); auto as_bitfield_op = dynamic_cast(source_forms.at(0)->try_as_single_element()); if (as_bitfield_op) { bitfield_compare = as_bitfield_op->push_step( BitfieldManip(BitfieldManip::Kind::NONZERO_COMPARE, 0), env.dts->ts, pool, env); } if (bitfield_compare) { return bitfield_compare; } auto mr = match(Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::ADDITION), {Matcher::any(0), Matcher::any_integer(1)}), source_forms.at(0)); if (mr.matched) { s64 value = -mr.maps.ints.at(1); auto value_form = pool.form(SimpleAtom::make_int_constant(value)); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::NEQ), std::vector{mr.maps.forms.at(0), value_form}); } auto as_logtest = try_make_nonzero_logtest(source_forms.at(0), pool); if (as_logtest) { auto logtest_form = pool.alloc_single_form(nullptr, as_logtest); auto fancy_form = convert_logtest_to_fancy_macro(pool, logtest_form); if (fancy_form) { return fancy_form; } else { return as_logtest; } } return pool.alloc_element(GenericOperator::make_compare(m_kind), source_forms); } FormElement* ConditionElement::make_equal_check_generic(const Env& env, FormPool& pool, const std::vector& source_forms, const std::vector& source_types) { ASSERT(source_forms.size() == 2); // (= thing '()) auto ref = source_forms.at(1); auto ref_atom = form_as_atom(ref); if (ref_atom && ref_atom->get_kind() == SimpleAtom::Kind::EMPTY_LIST) { // null? return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::NULLP), source_forms.at(0)); } else { auto nice_constant = try_make_constant_for_compare(source_forms.at(1), source_types.at(0), pool, env); if (nice_constant) { auto forms_with_cast = source_forms; forms_with_cast.at(1) = nice_constant; return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::EQ), forms_with_cast); } else { nice_constant = try_make_constant_for_compare(source_forms.at(0), source_types.at(1), pool, env); if (nice_constant) { auto forms_with_cast = source_forms; forms_with_cast.at(0) = nice_constant; return pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::EQ), forms_with_cast); } else { // (= (ja-group) // (-> self draw art-group data 7) // ) // actually (ja-group? (-> self draw art-group data 7) :channel channel) auto mr = match(Matcher::op(GenericOpMatcher::func(Matcher::constant_token("ja-group")), {}), source_forms.at(0)); // check if both things matched if (mr.matched) { // grab args from the ja-group and pass them on std::vector macro_args; macro_args.push_back(source_forms.at(1)); auto jagroup = source_forms.at(0)->try_as_element(); for (size_t i = 1; i < jagroup->elts().size(); ++i) { macro_args.push_back(jagroup->elts().at(i)); } return pool.alloc_element( GenericOperator::make_function(pool.form("ja-group?")), macro_args); } } return pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::EQ), cast_for_64bit_equality_check(source_forms, source_types, pool, env)); } } } FormElement* ConditionElement::make_not_equal_check_generic( const Env& env, FormPool& pool, const std::vector& source_forms, const std::vector& source_types) { ASSERT(source_forms.size() == 2); // (!= thing '()) auto ref = source_forms.at(1); auto ref_atom = form_as_atom(ref); if (ref_atom && ref_atom->get_kind() == SimpleAtom::Kind::EMPTY_LIST) { // null? return pool.alloc_element( GenericOperator::make_compare(IR2_Condition::Kind::FALSE), pool.form(GenericOperator::make_fixed(FixedOperatorKind::NULLP), source_forms.at(0))); } else { auto nice_constant = try_make_constant_for_compare(source_forms.at(1), source_types.at(0), pool, env); if (nice_constant) { auto forms_with_cast = source_forms; forms_with_cast.at(1) = nice_constant; return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::NEQ), forms_with_cast); } else { return pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::NEQ), cast_for_64bit_equality_check(source_forms, source_types, pool, env)); } } } FormElement* ConditionElement::make_less_than_zero_signed_check_generic( const Env& env, FormPool& pool, const std::vector& source_forms, const std::vector& types) { ASSERT(source_forms.size() == 1); // (< (shl (the-as int iter) 62) 0) -> (pair? iter) // match (shl [(the-as int [x]) | [x]] 62) auto shift_match = match(Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::SHL), { Matcher::match_or({Matcher::cast("int", Matcher::any(0)), Matcher::any(0)}), // the val Matcher::integer(62) // get the bit in the highest position. }), source_forms.at(0)); if (shift_match.matched) { return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::PAIRP), shift_match.maps.forms.at(0)); } else { auto casted = make_casts_if_needed(source_forms, types, TypeSpec("int"), pool, env); auto zero = pool.form(SimpleAtom::make_int_constant(0)); casted.push_back(zero); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::LT), casted); } } FormElement* ConditionElement::make_geq_zero_signed_check_generic( const Env& env, FormPool& pool, const std::vector& source_forms, const std::vector& types) { ASSERT(source_forms.size() == 1); // (>= (shl (the-as int iter) 62) 0) -> (not (pair? iter)) // match (shl [(the-as int [x]) | [x]] 62) auto shift_match = match(Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::SHL), { Matcher::match_or({Matcher::cast("int", Matcher::any(0)), Matcher::any(0)}), // the val Matcher::integer(62) // get the bit in the highest position. }), source_forms.at(0)); if (shift_match.matched) { return pool.alloc_element( GenericOperator::make_compare(IR2_Condition::Kind::FALSE), pool.form(GenericOperator::make_fixed(FixedOperatorKind::PAIRP), shift_match.maps.forms.at(0))); } else { auto casted = make_casts_if_needed(source_forms, types, TypeSpec("int"), pool, env); auto zero = pool.form(SimpleAtom::make_int_constant(0)); casted.push_back(zero); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::GEQ), casted); } } FormElement* ConditionElement::make_geq_zero_unsigned_check_generic( const Env& env, FormPool& pool, const std::vector& source_forms, const std::vector& types) { ASSERT(source_forms.size() == 1); // (>= (shl (the-as int iter) 62) 0) -> (not (pair? iter)) // match (shl [(the-as int [x]) | [x]] 62) auto shift_match = match(Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::SHL), { Matcher::match_or({Matcher::cast("uint", Matcher::any(0)), Matcher::any(0)}), // the val Matcher::integer(62) // get the bit in the highest position. }), source_forms.at(0)); auto casted = make_casts_if_needed(source_forms, types, TypeSpec("uint"), pool, env); auto zero = pool.form(SimpleAtom::make_int_constant(0)); casted.push_back(zero); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::GEQ), casted); } FormElement* ConditionElement::make_time_elapsed(const Env& env, FormPool& pool, const std::vector& source_forms, const std::vector& types) { // geq case: // (>= (- (current-time) (-> self state-time)) (seconds 5)) // to // (time-elapsed? (-> self state-time) (seconds 5)) // lt case: // (< (- (current-time) (-> self state-time)) (seconds 5)) // to // (not (time-elapsed? (-> self state-time) (seconds 5))) auto matcher = match( Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::SUBTRACTION), {Matcher::op(GenericOpMatcher::func(Matcher::constant_token("current-time")), {}), Matcher::any(0)}), source_forms.at(0)); if (matcher.matched) { auto time_elapsed = matcher.maps.forms.at(0); auto time = source_forms.at(1); std::vector args; args.push_back(time_elapsed); args.push_back(time); // TODO - how to handle unsigned case? if (m_kind == IR2_Condition::Kind::LESS_THAN_SIGNED) { return pool.alloc_element( GenericOperator::make_compare(IR2_Condition::Kind::FALSE), pool.form( GenericOperator::make_function(pool.form("time-elapsed?")), make_casts_if_needed(args, types, TypeSpec("time-frame"), pool, env))); } else if (m_kind == IR2_Condition::Kind::GEQ_SIGNED) { return pool.alloc_element( GenericOperator::make_function(pool.form("time-elapsed?")), make_casts_if_needed(args, types, TypeSpec("time-frame"), pool, env)); } } return nullptr; } FormElement* ConditionElement::make_generic(const Env& env, FormPool& pool, const std::vector& source_forms, const std::vector& types) { switch (m_kind) { case IR2_Condition::Kind::ZERO: return make_zero_check_generic(env, pool, source_forms, types); case IR2_Condition::Kind::NONZERO: return make_nonzero_check_generic(env, pool, source_forms, types); case IR2_Condition::Kind::TRUTHY: case IR2_Condition::Kind::FALSE: case IR2_Condition::Kind::IS_PAIR: case IR2_Condition::Kind::IS_NOT_PAIR: case IR2_Condition::Kind::ALWAYS: // kind of a hack, we fall back to the old condition operator which is special cased // to print the truthy condition in a nice way. and we use it for other things that don't // require fancy renaming. return pool.alloc_element(GenericOperator::make_compare(m_kind), source_forms); case IR2_Condition::Kind::EQUAL: return make_equal_check_generic(env, pool, source_forms, types); case IR2_Condition::Kind::NOT_EQUAL: return make_not_equal_check_generic(env, pool, source_forms, types); case IR2_Condition::Kind::LESS_THAN_SIGNED: { auto time_elapsed = make_time_elapsed(env, pool, source_forms, types); if (time_elapsed) { return time_elapsed; } return pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::LT), make_casts_if_needed(source_forms, types, TypeSpec("int"), pool, env)); } case IR2_Condition::Kind::LESS_THAN_UNSIGNED: { auto time_elapsed = make_time_elapsed(env, pool, source_forms, types); if (time_elapsed) { return time_elapsed; } return pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::LT), make_casts_if_needed(source_forms, types, TypeSpec("uint"), pool, env)); } case IR2_Condition::Kind::GEQ_SIGNED: { auto time_elapsed = make_time_elapsed(env, pool, source_forms, types); if (time_elapsed) { return time_elapsed; } return pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::GEQ), make_casts_if_needed(source_forms, types, TypeSpec("int"), pool, env)); } case IR2_Condition::Kind::GEQ_UNSIGNED: { auto time_elapsed = make_time_elapsed(env, pool, source_forms, types); if (time_elapsed) { return time_elapsed; } return pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::GEQ), make_casts_if_needed(source_forms, types, TypeSpec("uint"), pool, env)); } case IR2_Condition::Kind::LESS_THAN_ZERO_SIGNED: { return make_less_than_zero_signed_check_generic(env, pool, source_forms, types); } case IR2_Condition::Kind::LESS_THAN_ZERO_UNSIGNED: { auto casted = make_casts_if_needed(source_forms, types, TypeSpec("uint"), pool, env); auto zero = pool.form(SimpleAtom::make_int_constant(0)); casted.push_back(zero); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::LT), casted); } case IR2_Condition::Kind::LEQ_ZERO_SIGNED: { auto casted = make_casts_if_needed(source_forms, types, TypeSpec("int"), pool, env); auto zero = pool.form(SimpleAtom::make_int_constant(0)); casted.push_back(zero); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::LEQ), casted); } case IR2_Condition::Kind::LEQ_ZERO_UNSIGNED: { auto casted = make_casts_if_needed(source_forms, types, TypeSpec("uint"), pool, env); auto zero = pool.form(SimpleAtom::make_int_constant(0)); casted.push_back(zero); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::LEQ), casted); } case IR2_Condition::Kind::GEQ_ZERO_SIGNED: { return make_geq_zero_signed_check_generic(env, pool, source_forms, types); } case IR2_Condition::Kind::GEQ_ZERO_UNSIGNED: { return make_geq_zero_unsigned_check_generic(env, pool, source_forms, types); } case IR2_Condition::Kind::GREATER_THAN_ZERO_UNSIGNED: { auto casted = make_casts_if_needed(source_forms, types, TypeSpec("uint"), pool, env); auto zero = pool.form(SimpleAtom::make_int_constant(0)); casted.push_back(zero); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::GT), casted); } case IR2_Condition::Kind::GREATER_THAN_ZERO_SIGNED: { auto casted = make_casts_if_needed(source_forms, types, TypeSpec("int"), pool, env); auto zero = pool.form(SimpleAtom::make_int_constant(0)); casted.push_back(zero); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::GT), casted); } case IR2_Condition::Kind::FLOAT_NOT_EQUAL: { auto casted = make_casts_if_needed(source_forms, types, TypeSpec("float"), pool, env); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::NEQ), casted); } case IR2_Condition::Kind::FLOAT_EQUAL: { auto casted = make_casts_if_needed(source_forms, types, TypeSpec("float"), pool, env); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::EQ), casted); } case IR2_Condition::Kind::FLOAT_LEQ: { auto casted = make_casts_if_needed(source_forms, types, TypeSpec("float"), pool, env); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::LEQ), casted); } case IR2_Condition::Kind::FLOAT_LESS_THAN: { auto casted = make_casts_if_needed(source_forms, types, TypeSpec("float"), pool, env); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::LT), casted); } case IR2_Condition::Kind::FLOAT_GEQ: { auto casted = make_casts_if_needed(source_forms, types, TypeSpec("float"), pool, env); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::GEQ), casted); } case IR2_Condition::Kind::FLOAT_GREATER_THAN: { // never emitted by normal branch conditions auto casted = make_casts_if_needed(source_forms, types, TypeSpec("float"), pool, env); return pool.alloc_element(GenericOperator::make_fixed(FixedOperatorKind::GT), casted); } default: throw std::runtime_error("ConditionElement::make_generic NYI for kind " + get_condition_kind_name(m_kind)); } } void ConditionElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); std::vector source_forms, popped_forms; std::vector source_types; std::vector vars; for (int i = 0; i < get_condition_num_args(m_kind); i++) { if (m_src[i]->is_var()) { auto& var = m_src[i]->var(); vars.push_back(var); source_types.push_back(env.get_types_before_op(var.idx()).get(var.reg()).typespec()); } else if (m_src[i]->is_int()) { if (m_src[i]->get_int() == 0 && condition_uses_float(m_kind)) { // if we're doing a floating point comparison, and one of our arguments is a constant // which is an "integer zero", treat it as a floating point zero. source_types.push_back(TypeSpec("float")); } else { source_types.push_back(TypeSpec("int")); } } else if (m_src[i]->is_sym_val() && m_src[i]->get_str() == "#f") { source_types.push_back(TypeSpec("symbol")); } else { throw std::runtime_error(fmt::format( "Unsupported atom in ConditionElement::push_to_stack: {}", m_src[i]->to_string(env))); } } if (m_flipped) { std::reverse(vars.begin(), vars.end()); } popped_forms = pop_to_forms(vars, env, pool, stack, true, m_consumed); if (m_flipped) { std::reverse(popped_forms.begin(), popped_forms.end()); } int popped_counter = 0; for (int i = 0; i < get_condition_num_args(m_kind); i++) { if (m_src[i]->is_var()) { source_forms.push_back(popped_forms.at(popped_counter++)); } else { source_forms.push_back(pool.form(*m_src[i])); } } ASSERT(popped_counter == int(popped_forms.size())); ASSERT(source_forms.size() == source_types.size()); stack.push_form_element(make_generic(env, pool, source_forms, source_types), true); } void ConditionElement::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { mark_popped(); std::vector source_forms, popped_forms; std::vector source_types; std::vector vars; for (int i = 0; i < get_condition_num_args(m_kind); i++) { if (m_src[i]->is_var()) { auto& var = m_src[i]->var(); vars.push_back(var); source_types.push_back(env.get_types_before_op(var.idx()).get(var.reg()).typespec()); } else if (m_src[i]->is_int()) { if (m_src[i]->get_int() == 0 && condition_uses_float(m_kind)) { // if we're doing a floating point comparison, and one of our arguments is a constant // which is an "integer zero", treat it as a floating point zero. source_types.push_back(TypeSpec("float")); } else { source_types.push_back(TypeSpec("int")); } } else if (m_src[i]->is_sym_val() && m_src[i]->get_str() == "#f") { source_types.push_back(TypeSpec("symbol")); } else { throw std::runtime_error("Unsupported atom in ConditionElement::update_from_stack: " + m_src[i]->to_string(env)); } } if (m_flipped) { std::reverse(vars.begin(), vars.end()); } popped_forms = pop_to_forms(vars, env, pool, stack, allow_side_effects, m_consumed); if (m_flipped) { std::reverse(popped_forms.begin(), popped_forms.end()); } int popped_counter = 0; for (int i = 0; i < get_condition_num_args(m_kind); i++) { if (m_src[i]->is_var()) { source_forms.push_back(popped_forms.at(popped_counter++)); } else { source_forms.push_back(pool.form(*m_src[i])); } } ASSERT(popped_counter == int(popped_forms.size())); ASSERT(source_forms.size() == source_types.size()); result->push_back(make_generic(env, pool, source_forms, source_types)); } void ReturnElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); FormStack temp_stack(false); for (auto& elt : return_code->elts()) { elt->push_to_stack(env, pool, temp_stack); } std::vector new_entries; std::optional var; new_entries = rewrite_to_get_var(temp_stack, pool, env.end_var(), env, &var); ASSERT(!new_entries.empty()); return_code->clear(); for (int i = 0; i < ((int)new_entries.size()) - 1; i++) { stack.push_form_element(new_entries.at(i), true); } return_code->push_back(new_entries.back()); if (var) { const auto& func_type = env.func->type.last_arg(); return_type = env.get_variable_type(*var, false); // functions with no return can return stuff. if (func_type != return_type && func_type != TypeSpec("none")) { auto as_cast = return_code->try_as_element(); if (as_cast) { return_code->clear(); as_cast->set_type(func_type); return_code->push_back(as_cast); } else { return_code = cast_form(return_code, func_type, pool, env); return_code->parent_element = this; } } } stack.push_form_element(this, true); } namespace { void push_asm_srl_to_stack(const AsmOp* op, FormElement* form_elt, const Env& env, FormPool& pool, FormStack& stack) { // we will try to convert this into a bitfield operation. If this fails, fall back to assembly. auto var = op->src(0); ASSERT(var.has_value()); // srl should always have this. auto dst = op->dst(); ASSERT(dst.has_value()); auto integer_atom = op->instruction().get_src(1); ASSERT(integer_atom.is_imm()); auto integer = integer_atom.get_imm(); auto arg0_type = env.get_variable_type(*var, true); auto type_info = env.dts->ts.lookup_type(arg0_type); auto bitfield_info = dynamic_cast(type_info); if (bitfield_info) { auto base = pop_to_forms({*var}, env, pool, stack, true).at(0); auto read_elt = pool.alloc_element(base, arg0_type); BitfieldManip step(BitfieldManip::Kind::RIGHT_SHIFT_LOGICAL_32BIT, integer); auto other = read_elt->push_step(step, env.dts->ts, pool, env); ASSERT(other); // should be a high field. stack.push_value_to_reg(*dst, pool.alloc_single_form(nullptr, other), true, env.get_variable_type(*dst, true)); } else { // auto src_var = pop_to_forms({*var}, env, pool, stack, true).at(0); auto as_ba = src_var->try_as_element(); if (as_ba) { BitfieldManip step(BitfieldManip::Kind::RIGHT_SHIFT_LOGICAL_32BIT, integer); auto other = as_ba->push_step(step, env.dts->ts, pool, env); ASSERT(other); // should immediately get a field. stack.push_value_to_reg(*dst, pool.alloc_single_form(nullptr, other), true, env.get_variable_type(*dst, true)); } else { stack.push_form_element(form_elt, true); // throw std::runtime_error( // fmt::format("Got invalid bitfield manip for srl at op {}: {} type was {}", op->op_id(), // src_var->to_string(env), arg0_type.print())); } } } void push_asm_sllv_to_stack(const AsmOp* op, FormElement* form_elt, const Env& env, FormPool& pool, FormStack& stack) { auto var = op->src(0); ASSERT(var.has_value()); auto dst = op->dst(); ASSERT(dst.has_value()); auto arg0_type = env.get_variable_type(*var, true); auto type_info = env.dts->ts.lookup_type(arg0_type); auto bitfield_info = dynamic_cast(type_info); if (op->instruction().src[1].is_reg(Register(Reg::GPR, Reg::R0))) { if (bitfield_info) { auto base = pop_to_forms({*var}, env, pool, stack, true).at(0); auto read_elt = pool.alloc_element(base, arg0_type); BitfieldManip step(BitfieldManip::Kind::SLLV_SEXT, 0); auto other = read_elt->push_step(step, env.dts->ts, pool, env); ASSERT(other); // should immediately get a field. stack.push_value_to_reg(*dst, pool.alloc_single_form(nullptr, other), true, env.get_variable_type(*dst, true)); } else { auto src_var = pop_to_forms({*var}, env, pool, stack, true).at(0); auto as_ba = src_var->try_as_element(); if (as_ba) { // part of existing chain. BitfieldManip step(BitfieldManip::Kind::SLLV_SEXT, 0); auto other = as_ba->push_step(step, env.dts->ts, pool, env); ASSERT(other); // should immediately get a field. stack.push_value_to_reg(*dst, pool.alloc_single_form(nullptr, other), true, env.get_variable_type(*dst, true)); } else { // push it to a weird looking form for initial bitfield setting. // these are lazily converted at the destination. stack.push_value_to_reg( *dst, pool.form(GenericOperator::make_fixed(FixedOperatorKind::ASM_SLLV_R0), src_var), true, env.get_variable_type(*dst, true)); } } } else { stack.push_form_element(form_elt, true); } } void push_asm_pcpyud_to_stack(const AsmOp* op, FormElement* form_elt, const Env& env, FormPool& pool, FormStack& stack) { // pcpyud v1, gp, r0 for example. auto var = op->src(0); ASSERT(var.has_value()); auto dst = op->dst(); ASSERT(dst.has_value()); auto possible_r0 = op->src(1); ASSERT(possible_r0.has_value()); auto arg0_type = env.get_variable_type(*var, true); auto type_info = env.dts->ts.lookup_type(arg0_type); auto bitfield_info = dynamic_cast(type_info); if (bitfield_info && possible_r0->reg() == Register(Reg::GPR, Reg::R0)) { auto base = pop_to_forms({*var}, env, pool, stack, true).at(0); auto read_elt = pool.alloc_element(base, arg0_type); read_elt->push_pcpyud(env.dts->ts, pool, env); stack.push_value_to_reg(*dst, pool.alloc_single_form(nullptr, read_elt), true, env.get_variable_type(*dst, true)); } else { stack.push_form_element(form_elt, true); } } void push_asm_pextuw_to_stack(const AsmOp* op, FormElement* form_elt, const Env& env, FormPool& pool, FormStack& stack) { // (.pextuw t0-0 r0-0 obj) auto var = op->src(1); ASSERT(var.has_value()); auto dst = op->dst(); ASSERT(dst.has_value()); auto possible_r0 = op->src(0); ASSERT(possible_r0.has_value()); auto arg0_type = env.get_variable_type(*var, true); auto type_info = env.dts->ts.lookup_type(arg0_type); auto bitfield_info = dynamic_cast(type_info); if (bitfield_info && possible_r0->reg() == Register(Reg::GPR, Reg::R0)) { auto base = pop_to_forms({*var}, env, pool, stack, true).at(0); auto read_elt = pool.alloc_element(base, arg0_type); BitfieldManip step(BitfieldManip::Kind::PEXTUW, 0); auto other = read_elt->push_step(step, env.dts->ts, pool, env); ASSERT(other); // should immediately get a field. stack.push_value_to_reg(*dst, pool.alloc_single_form(nullptr, other), true, env.get_variable_type(*dst, true)); } else { stack.push_form_element(form_elt, true); } } /* void push_asm_madds_to_stack(const AsmOp* op, FormElement* form_elt, const Env& env, FormPool& pool, FormStack& stack) { auto src0 = op->src(0); ASSERT(src0.has_value()); auto src1 = op->src(1); ASSERT(src1.has_value()); auto dst = op->dst(); ASSERT(dst.has_value()); auto vars = pop_to_forms({*src0, *src1}, env, pool, stack, true); stack.push_value_to_reg( *dst, pool.alloc_single_element_form( nullptr, GenericOperator::make_fixed(FixedOperatorKind::ASM_MADDS), vars), true, env.get_variable_type(*dst, true)); } */ void push_asm_to_stack(const AsmOp* op, FormElement* form_elt, const Env& env, FormPool& pool, FormStack& stack) { switch (op->instruction().kind) { case InstructionKind::SRL: push_asm_srl_to_stack(op, form_elt, env, pool, stack); break; case InstructionKind::SLLV: push_asm_sllv_to_stack(op, form_elt, env, pool, stack); break; case InstructionKind::PCPYUD: push_asm_pcpyud_to_stack(op, form_elt, env, pool, stack); break; case InstructionKind::PEXTUW: push_asm_pextuw_to_stack(op, form_elt, env, pool, stack); break; /* case InstructionKind::MADDS: push_asm_madds_to_stack(op, form_elt, env, pool, stack); break; */ default: stack.push_form_element(form_elt, true); break; } } } // namespace void AtomicOpElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); auto as_end = dynamic_cast(m_op); if (as_end) { // we don't want to push this to the stack (for now at least) return; } auto as_special = dynamic_cast(m_op); if (as_special) { if (as_special->kind() == SpecialOp::Kind::NOP || as_special->kind() == SpecialOp::Kind::BREAK || as_special->kind() == SpecialOp::Kind::CRASH || as_special->kind() == SpecialOp::Kind::SUSPEND) { stack.push_form_element(this, true); return; } } auto as_asm = dynamic_cast(m_op); if (as_asm) { push_asm_to_stack(as_asm, this, env, pool, stack); return; } auto as_branch = dynamic_cast(m_op); if (as_branch && !as_branch->is_likely()) { // this is a bit of a hack, but we go AsmBranchOp -> AsmBranchElement -> TranslatedAsmBranch auto delay = as_branch->branch_delay(); ASSERT(delay); // this might not be enough - we may need to back up to the cfg builder and do something there. auto del = pool.form(delay); auto be = pool.alloc_element(as_branch, del, false); be->push_to_stack(env, pool, stack); return; } throw std::runtime_error("Cannot push atomic op to stack: " + m_op->to_string(env)); } void AsmOpElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); push_asm_to_stack(m_op, this, env, pool, stack); } void GenericElement::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool) { mark_popped(); if (m_elts.size() == 1) { // a bit of a hack, but AtomicOpForm uses this for loading car/cdr // this is safe to do. m_elts.front()->update_children_from_stack(env, pool, stack, true); } result->push_back(this); } void AsmBranchElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { // create a condition element RegSet consumed; if (env.has_reg_use()) { consumed = env.reg_use().op.at(m_branch_op->op_id()).consumes; } std::optional vars[2]; for (int i = 0; i < get_condition_num_args(m_branch_op->condition().kind()); i++) { vars[i] = m_branch_op->condition().src(i); } auto ce = pool.alloc_element(m_branch_op->condition().kind(), vars[0], vars[1], consumed, false); // and update it from the stack. std::vector ce_updated; ce->update_from_stack(env, pool, stack, &ce_updated, true); auto branch_condition = pool.alloc_sequence_form(nullptr, ce_updated); auto op = pool.alloc_element( branch_condition, m_branch_delay, m_branch_op->label_id(), m_branch_op->is_likely()); // lg::print("rewrote as {}\n", op->to_string(env)); stack.push_form_element(op, true); } void BranchElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { // These will appear if we have an asm-branch that looked like a normal branch. // create a condition element RegSet consumed; if (env.has_reg_use()) { consumed = env.reg_use().op.at(m_op->op_id()).consumes; } std::optional vars[2]; for (int i = 0; i < get_condition_num_args(m_op->condition().kind()); i++) { vars[i] = m_op->condition().src(i); } auto ce = pool.alloc_element(m_op->condition().kind(), vars[0], vars[1], consumed, false); // and update it from the stack. std::vector ce_updated; ce->update_from_stack(env, pool, stack, &ce_updated, true); auto branch_condition = pool.alloc_sequence_form(nullptr, ce_updated); Form* branch_delay = nullptr; switch (m_op->branch_delay().kind()) { case IR2_BranchDelay::Kind::NOP: { branch_delay = nullptr; } break; case IR2_BranchDelay::Kind::SET_REG_REG: { auto src = m_op->branch_delay().var(1); auto dst = m_op->branch_delay().var(0); auto src_form = pool.form(SimpleAtom::make_var(src)); branch_delay = pool.form(dst, src_form, true, env.get_variable_type(src, true)); } break; case IR2_BranchDelay::Kind::SET_REG_FALSE: { auto dst = m_op->branch_delay().var(0); auto src_form = pool.form(SimpleAtom::make_sym_val("#f")); branch_delay = pool.form(dst, src_form, true, TypeSpec("symbol")); } break; case IR2_BranchDelay::Kind::SET_REG_TRUE: { auto dst = m_op->branch_delay().var(0); auto src_form = pool.form(SimpleAtom::make_sym_val("#t")); branch_delay = pool.form(dst, src_form, true, TypeSpec("symbol")); } break; default: throw std::runtime_error("Unhandled branch delay in BranchElement::push_to_stack: " + m_op->to_string(env)); } ASSERT(!m_op->likely()); auto op = pool.alloc_element(branch_condition, branch_delay, m_op->label_id(), m_op->likely()); // lg::print("rewrote (non-asm) as {}\n", op->to_string(env)); stack.push_form_element(op, true); } void GenericElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { (void)env; (void)pool; mark_popped(); stack.push_form_element(this, true); } //////////////////////// // DynamicMethodAccess //////////////////////// void DynamicMethodAccess::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { mark_popped(); // auto new_val = stack.pop_reg(m_source, {}, env, allow_side_effects); auto new_val = pop_to_forms({m_source}, env, pool, stack, allow_side_effects).at(0); auto reg0_matcher = Matcher::match_or({Matcher::any_reg(0), Matcher::cast("uint", Matcher::any_reg(0))}); auto reg1_matcher = Matcher::match_or({Matcher::any_reg(1), Matcher::cast("int", Matcher::any_reg(1))}); // (+ (* method-id 4) (the-as int child-type)) auto mult_matcher = Matcher::op_fixed(FixedOperatorKind::MULTIPLICATION, {reg0_matcher, Matcher::integer(4)}); auto matcher = Matcher::op_fixed(FixedOperatorKind::ADDITION, {mult_matcher, reg1_matcher}); auto match_result = match(matcher, new_val); if (!match_result.matched) { throw std::runtime_error("Could not match DynamicMethodAccess values: " + new_val->to_string(env)); } auto idx = match_result.maps.regs.at(0); auto base = match_result.maps.regs.at(1); ASSERT(idx.has_value() && base.has_value()); auto deref = pool.alloc_element( var_to_form(base.value(), pool), false, std::vector{DerefToken::make_field_name("method-table"), DerefToken::make_int_expr(var_to_form(idx.value(), pool))}); result->push_back(deref); } //////////////////////// // ArrayFieldAccess //////////////////////// void ArrayFieldAccess::update_with_val(Form* new_val, const Env& env, FormPool& pool, std::vector* result, bool) { int power_of_two = 0; if (m_constant_offset == 0) { if (m_expected_stride == 1) { auto base_matcher = Matcher::match_or({Matcher::cast("int", Matcher::any(0)), Matcher::cast("uint", Matcher::any(0)), Matcher::any(0)}); auto offset_matcher = Matcher::match_or({Matcher::cast("int", Matcher::any(1)), Matcher::cast("uint", Matcher::any(1)), Matcher::any(1)}); // (&+ data-ptr ) auto matcher = Matcher::match_or( {Matcher::op_fixed(FixedOperatorKind::ADDITION, {base_matcher, offset_matcher}), Matcher::op_fixed(FixedOperatorKind::ADDITION_PTR, {base_matcher, offset_matcher})}); auto match_result = match(matcher, new_val); if (!match_result.matched) { throw std::runtime_error( fmt::format("Failed to match array stride 1 load {}", new_val->to_string(env))); } auto idx = match_result.maps.forms.at(1); auto base = match_result.maps.forms.at(0); ASSERT(idx && base); if (m_flipped) { std::swap(idx, base); } std::vector tokens = m_deref_tokens; for (auto& x : tokens) { if (x.kind() == DerefToken::Kind::EXPRESSION_PLACEHOLDER) { x = DerefToken::make_int_expr(idx); } } // tokens.push_back(DerefToken::make_int_expr(idx)); auto deref = pool.alloc_element(base, false, tokens); result->push_back(deref); } else if (is_power_of_two(m_expected_stride, &power_of_two)) { // reg0 is base // reg1 is idx auto reg0_matcher = Matcher::match_or({Matcher::cast("int", Matcher::any(0)), Matcher::cast("uint", Matcher::any(0)), Matcher::any(0)}); auto reg1_matcher = Matcher::match_or({Matcher::cast("uint", Matcher::any(1)), Matcher::any(1)}); auto mult_matcher = Matcher::op_fixed(FixedOperatorKind::MULTIPLICATION, {reg1_matcher, Matcher::integer(m_expected_stride)}); mult_matcher = Matcher::match_or({Matcher::cast("uint", mult_matcher), mult_matcher}); auto matcher = Matcher::match_or( {Matcher::op_fixed(FixedOperatorKind::ADDITION, {reg0_matcher, mult_matcher}), Matcher::op_fixed(FixedOperatorKind::ADDITION_PTR, {reg0_matcher, mult_matcher})}); auto match_result = match(matcher, new_val); if (!match_result.matched) { matcher = Matcher::match_or( {Matcher::op_fixed(FixedOperatorKind::ADDITION, {mult_matcher, reg0_matcher}), Matcher::op_fixed(FixedOperatorKind::ADDITION_PTR, {mult_matcher, reg0_matcher})}); match_result = match(matcher, new_val); if (!match_result.matched) { result->push_back(this); return; lg::error("power {}", power_of_two); throw std::runtime_error( "Couldn't match ArrayFieldAccess (stride power of 2, 0 offset) values: " + new_val->to_string(env)); } } auto idx = match_result.maps.forms.at(1); auto base = match_result.maps.forms.at(0); ASSERT(idx && base); std::vector tokens = m_deref_tokens; for (auto& x : tokens) { if (x.kind() == DerefToken::Kind::EXPRESSION_PLACEHOLDER) { x = DerefToken::make_int_expr(idx); } } // tokens.push_back(DerefToken::make_int_expr(idx)); auto deref = pool.alloc_element(base, false, tokens); result->push_back(deref); } else { auto mult_matcher = Matcher::op( GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION), {Matcher::match_or({Matcher::cast("uint", Matcher::integer(m_expected_stride)), Matcher::integer(m_expected_stride)}), Matcher::any(0)}); mult_matcher = Matcher::match_or( {Matcher::cast("uint", mult_matcher), Matcher::cast("int", mult_matcher), mult_matcher}); auto op_match = GenericOpMatcher::or_match({GenericOpMatcher::fixed(FixedOperatorKind::ADDITION), GenericOpMatcher::fixed(FixedOperatorKind::ADDITION_PTR)}); auto add_matcher = Matcher::op(op_match, {Matcher::any(1), mult_matcher}); add_matcher = Matcher::match_or({add_matcher, Matcher::op(op_match, {mult_matcher, Matcher::any(1)})}); auto mr = match(add_matcher, new_val); if (!mr.matched) { throw std::runtime_error("Failed to match non-power of two case: " + new_val->to_string(env)); } auto base = strip_int_or_uint_cast(mr.maps.forms.at(1)); auto idx = mr.maps.forms.at(0); ASSERT(idx && base); std::vector tokens = m_deref_tokens; for (auto& x : tokens) { if (x.kind() == DerefToken::Kind::EXPRESSION_PLACEHOLDER) { x = DerefToken::make_int_expr(idx); } } auto deref = pool.alloc_element(base, false, tokens); result->push_back(deref); } } else { if (m_expected_stride == 1) { // reg0 is idx auto reg0_matcher = Matcher::match_or({Matcher::cast("int", Matcher::any(0)), Matcher::cast("uint", Matcher::any(0)), Matcher::any(0)}); // reg1 is base auto reg1_matcher = Matcher::match_or({Matcher::cast("int", Matcher::any(1)), Matcher::cast("uint", Matcher::any(1)), Matcher::any(1)}); auto matcher = Matcher::op_fixed(FixedOperatorKind::ADDITION, {reg0_matcher, reg1_matcher}); auto match_result = match(matcher, new_val); if (!match_result.matched) { throw std::runtime_error("Could not match ArrayFieldAccess (stride 1) values: " + new_val->to_string(env)); } auto idx = match_result.maps.forms.at(0); auto base = match_result.maps.forms.at(1); ASSERT(idx && base); std::vector tokens = m_deref_tokens; for (auto& x : tokens) { if (x.kind() == DerefToken::Kind::EXPRESSION_PLACEHOLDER) { x = DerefToken::make_int_expr(idx); } } // tokens.push_back(DerefToken::make_int_expr(var_to_form(idx.value(), pool))); auto deref = pool.alloc_element(base, false, tokens); result->push_back(deref); } else if (is_power_of_two(m_expected_stride, &power_of_two)) { // (+ (sll (the-as uint a1-0) 2) (the-as int a0-0)) // (+ gp-0 (the-as uint (shl (the-as uint (shl (the-as uint s4-0) 2)) 2))) auto reg0_matcher = Matcher::match_or({Matcher::cast("uint", Matcher::any(0)), Matcher::any(0)}); auto reg1_matcher = Matcher::match_or({Matcher::cast("uint", Matcher::any(1)), Matcher::cast("int", Matcher::any(1)), Matcher::any(1)}); auto mult_matcher = Matcher::op_fixed(FixedOperatorKind::MULTIPLICATION, {reg0_matcher, Matcher::integer(m_expected_stride)}); mult_matcher = Matcher::match_or({Matcher::cast("uint", mult_matcher), mult_matcher}); auto matcher = Matcher::op_fixed(FixedOperatorKind::ADDITION, {mult_matcher, reg1_matcher}); matcher = Matcher::match_or({matcher, Matcher::op_fixed(FixedOperatorKind::ADDITION_PTR, {reg1_matcher, mult_matcher})}); auto match_result = match(matcher, new_val); Form* idx = nullptr; Form* base = nullptr; // TODO - figure out why it sometimes happens the other way. if (!match_result.matched) { matcher = Matcher::op_fixed(FixedOperatorKind::ADDITION, {reg1_matcher, mult_matcher}); match_result = match(matcher, new_val); if (!match_result.matched) { throw std::runtime_error("Could not match ArrayFieldAccess (stride power of 2) values: " + new_val->to_string(env)); } } idx = match_result.maps.forms.at(0); base = match_result.maps.forms.at(1); ASSERT(idx && base); std::vector tokens = m_deref_tokens; for (auto& x : tokens) { if (x.kind() == DerefToken::Kind::EXPRESSION_PLACEHOLDER) { x = DerefToken::make_int_expr(idx); } } // tokens.push_back(DerefToken::make_int_expr(var_to_form(idx.value(), pool))); auto deref = pool.alloc_element(base, false, tokens); result->push_back(deref); } else { // (+ v0-0 (the-as uint (* 12 (+ a3-0 -1)))) // (+ (the-as uint *texture-page-dir*) (* (the-as uint 12) (-> arg0 page)) auto mult_matcher = Matcher::op( GenericOpMatcher::fixed(FixedOperatorKind::MULTIPLICATION), {Matcher::match_or({Matcher::cast("uint", Matcher::integer(m_expected_stride)), Matcher::integer(m_expected_stride)}), Matcher::any(0)}); mult_matcher = Matcher::match_or( {Matcher::cast("uint", mult_matcher), Matcher::cast("int", mult_matcher), mult_matcher}); auto op_match = GenericOpMatcher::or_match({GenericOpMatcher::fixed(FixedOperatorKind::ADDITION), GenericOpMatcher::fixed(FixedOperatorKind::ADDITION_PTR)}); auto add_matcher = Matcher::op(op_match, {Matcher::any(1), mult_matcher}); add_matcher = Matcher::match_or({add_matcher, Matcher::op(op_match, {mult_matcher, Matcher::any(1)})}); auto mr = match(add_matcher, new_val); if (!mr.matched) { throw std::runtime_error("Failed to match non-power of two case: " + new_val->to_string(env)); } auto base = strip_int_or_uint_cast(mr.maps.forms.at(1)); auto idx = mr.maps.forms.at(0); ASSERT(idx && base); std::vector tokens = m_deref_tokens; for (auto& x : tokens) { if (x.kind() == DerefToken::Kind::EXPRESSION_PLACEHOLDER) { x = DerefToken::make_int_expr(idx); } } auto deref = pool.alloc_element(base, false, tokens); result->push_back(deref); } } } void ArrayFieldAccess::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { mark_popped(); auto new_val = pop_to_forms({m_source}, env, pool, stack, allow_side_effects).at(0); update_with_val(new_val, env, pool, result, allow_side_effects); } //////////////////////// // CastElement //////////////////////// void CastElement::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { mark_popped(); m_source->update_children_from_stack(env, pool, stack, allow_side_effects); result->push_back(this); } //////////////////////// // TypeOfElement //////////////////////// void TypeOfElement::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { mark_popped(); value->update_children_from_stack(env, pool, stack, allow_side_effects); result->push_back(this); } //////////////////////// // EmptyElement //////////////////////// void EmptyElement::push_to_stack(const Env&, FormPool&, FormStack& stack) { mark_popped(); stack.push_form_element(this, true); } void StoreElement::push_to_stack(const Env&, FormPool&, FormStack& stack) { mark_popped(); stack.push_form_element(this, true); } bool is_symbol_true(const Form* form) { auto as_simple = dynamic_cast(form->try_as_single_element()); if (as_simple && as_simple->expr().is_identity() && as_simple->expr().get_arg(0).is_sym_ptr() && as_simple->expr().get_arg(0).get_str() == "#t") { return true; } return false; } void ConditionalMoveFalseElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); // pop the value and the original auto popped = pop_to_forms({old_value, source}, env, pool, stack, true); if (!is_symbol_true(popped.at(0))) { lg::warn("{}: Failed to ConditionalMoveFalseElement::push_to_stack", env.func->name()); stack.push_value_to_reg(source, popped.at(1), true, TypeSpec("symbol")); stack.push_form_element(this, true); return; } Form* val = nullptr; if (on_zero) { auto as_logtest = try_make_nonzero_logtest(popped.at(1), pool); if (as_logtest) { auto logtest_form = pool.alloc_single_form(nullptr, as_logtest); auto fancy_form = convert_logtest_to_fancy_macro(pool, logtest_form); if (fancy_form) { val = pool.alloc_single_form(nullptr, fancy_form); } else { val = logtest_form; } } } else { auto as_logtest = try_make_nonzero_logtest(popped.at(1), pool); if (as_logtest) { auto logtest_form = pool.alloc_single_form(nullptr, as_logtest); auto not_form = pool.form( GenericOperator::make_compare(IR2_Condition::Kind::FALSE), logtest_form); auto fancy_form = convert_logtest_to_fancy_macro(pool, not_form); if (fancy_form) { val = pool.alloc_single_form(nullptr, fancy_form); } else { val = not_form; } } } if (!val) { val = pool.form( GenericOperator::make_compare(on_zero ? IR2_Condition::Kind::NONZERO : IR2_Condition::Kind::ZERO), std::vector{popped.at(1)}); } stack.push_value_to_reg(dest, val, true, TypeSpec("symbol")); } /////////////////////////// // StackSpillStoreElement /////////////////////////// void StackSpillStoreElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); Form* src; if (m_value.is_var()) { src = pop_to_forms({m_value.var()}, env, pool, stack, true).at(0); } else { src = pool.form(m_value); } auto dst = pool.form(env.get_spill_slot_var_name(m_stack_offset)); if (m_cast_type) { src = cast_form(src, *m_cast_type, pool, env); } stack.push_form_element(pool.alloc_element(dst, src), true); } namespace { /*! * Is the given form an assembly form to load data from a vector to the given vf register? */ Form* is_load_store_vector_to_reg(const Register& reg, FormElement* form, bool is_load, int* idx_out) { auto as_vf_op = dynamic_cast(form); if (!as_vf_op) { return nullptr; } if (as_vf_op->is_load() != is_load) { return nullptr; } if (as_vf_op->vf_reg() != reg) { return nullptr; } // check that we actually got a real vector type, not some other thing that happens to have a // quad. auto& addr_type = as_vf_op->addr_type(); if (!addr_type || addr_type != TypeSpec("vector")) { return nullptr; } // make sure we load from the right spot, and extract the base. auto loc = as_vf_op->location(); auto matcher = Matcher::deref(Matcher::any(0), true, {DerefTokenMatcher::string("quad")}); auto mr = match(matcher, loc); if (!mr.matched) { return nullptr; } // output the index of the actual store op, for reguse purposes. if (idx_out) { *idx_out = as_vf_op->my_idx(); } // got it! return mr.maps.forms.at(0); } /*! * Try to convert a form to a regaccess. */ std::optional form_as_ra(Form* form) { auto as_atom = form_as_atom(form); if (as_atom && as_atom->is_var()) { return as_atom->var(); } return {}; } bool try_vector_reset_inline(const Env& env, FormPool& pool, FormStack& stack, FormElement* store_element) { // the store int store_idx = -1; auto store = is_load_store_vector_to_reg(Register(Reg::VF, 0), store_element, false, &store_idx); if (!store) { return false; } // remove these from the stack. // stack.pop(1); // the store here _should_ have failed propagation and just given us a variable. // if this is causing issues, we can run this check before propagating, as well call this from // the function that attempts the pop. auto store_var = form_as_ra(store); if (!store_var) { env.func->warnings.warning("Almost found vector reset, but couldn't get store var."); // stack.push_form_element(new_thing->elts().at(0), true); return false; } // ignore the store as a use. This will allow the entire vector-! expression to be expression // propagated, if it is appropriate. if (store_var) { auto menv = const_cast(&env); menv->disable_use(*store_var); } // now try to see if we can pop the first arg (destination vector). bool got_orig = false; RegisterAccess orig; store = repop_passthrough_arg(store, stack, env, &orig, &got_orig); if (!store) { return false; } // create the actual form Form* new_thing = pool.form( GenericOperator::make_function(pool.form("vector-reset!")), std::vector{store}); if (got_orig) { // we got a value for the destination. because we used the special repop passthrough, // we're responsible for inserting a set to set the var that we "stole" from. // We do this through push_value_to_reg, so it can be propagated if needed, but only if // somebody will actually read the output. // to tell, we look at the live out of the store op and the end - the earlier one would of // course be live out always because the store will read it again. auto& op_info = env.reg_use().op.at(store_idx); if (op_info.live.find(orig.reg()) == op_info.live.end()) { // nobody reads it, don't bother. stack.push_form_element(new_thing->elts().at(0), true); } else { stack.push_value_to_reg(orig, new_thing, true, TypeSpec("vector")); } } else { stack.push_form_element(new_thing->elts().at(0), true); } return true; } } // namespace void VectorFloatLoadStoreElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); auto loc_as_deref = m_location->try_as_element(); if (loc_as_deref) { auto root = loc_as_deref->base(); auto atom = form_as_atom(root); if (atom && atom->get_kind() == SimpleAtom::Kind::VARIABLE) { m_addr_type = env.get_variable_type(atom->var(), true); } } auto name = env.func->name(); // don't find vector-! inside of vector-!. if (!m_is_load && name != "vector-!" && name != "vector+!" && name != "vector-reset!") { if (try_vector_reset_inline(env, pool, stack, this)) { return; } } if (loc_as_deref) { auto root = loc_as_deref->base(); auto atom = form_as_atom(root); if (atom && atom->get_kind() == SimpleAtom::Kind::VARIABLE) { loc_as_deref->set_base(pop_to_forms({atom->var()}, env, pool, stack, true).at(0)); } } stack.push_form_element(this, true); } void MethodOfTypeElement::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { mark_popped(); auto type = pop_to_forms({m_type_reg}, env, pool, stack, allow_side_effects).at(0); auto type_as_deref = type->try_as_element(); if (type_as_deref) { if (type_as_deref->tokens().size() > 1 && type_as_deref->tokens().back().is_field_name("type")) { type_as_deref->tokens().pop_back(); result->push_back(pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::METHOD_OF_OBJECT), std::vector{type, pool.form(m_method_info.name)})); return; } else if (type_as_deref->tokens().size() == 1 && type_as_deref->tokens().back().is_field_name("type")) { result->push_back(pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::METHOD_OF_OBJECT), std::vector{type_as_deref->base(), pool.form(m_method_info.name)})); return; } } result->push_back(pool.alloc_element( GenericOperator::make_fixed(FixedOperatorKind::METHOD_OF_TYPE), std::vector{type, pool.form(m_method_info.name)})); } void SimpleAtomElement::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void StringConstantElement::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void GetMethodElement::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void CondNoElseElement::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void ConstantTokenElement::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void ConstantFloatElement::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void StackStructureDefElement::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void StackSpillValueElement::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void GetSymbolStringPointer::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void DefstateElement::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void DefskelgroupElement::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void DefpartgroupElement::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void DefpartElement::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void ResLumpMacroElement::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void WithDmaBufferAddBucketElement::update_from_stack(const Env&, FormPool&, FormStack&, std::vector* result, bool) { mark_popped(); result->push_back(this); } void LabelDerefElement::update_from_stack(const Env& env, FormPool& pool, FormStack& stack, std::vector* result, bool allow_side_effects) { mark_popped(); auto label_var = pop_to_forms({m_var}, env, pool, stack, allow_side_effects).at(0); auto atom = form_as_atom(label_var); if (!atom || !atom->is_label()) { throw std::runtime_error(fmt::format("LabelDerefElement didn't get a label, got {} instead", label_var->to_string(env))); } if (atom->label() != m_lid) { throw std::runtime_error( fmt::format("Label ID error in LabelDerefElement: {} vs {}", atom->label(), m_lid)); } auto as_label = make_label_load(m_lid, env, pool, m_size, m_load_kind); if (!as_label) { throw std::runtime_error( fmt::format("Unable to figure out label load for {}", env.file->labels.at(m_lid).name)); } result->push_back(as_label); } void LabelElement::push_to_stack(const Env&, FormPool&, FormStack& stack) { mark_popped(); stack.push_form_element(this, true); } void BreakElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) { mark_popped(); FormStack temp_stack(false); for (auto& elt : return_code->elts()) { elt->push_to_stack(env, pool, temp_stack); } std::vector new_entries; new_entries = temp_stack.rewrite(pool, env); ASSERT(!new_entries.empty()); return_code->clear(); for (int i = 0; i < ((int)new_entries.size()); i++) { stack.push_form_element(new_entries.at(i), true); } return_code->push_back(pool.alloc_element()); stack.push_form_element(this, true); } } // namespace decompiler