Files
jak-project/decompiler/IR2/FormExpressionAnalysis.cpp
T
water111 f3c63f26bb fix let* format, new on stack guessing case, type failure, handle casts (#1860)
Fixes https://github.com/open-goal/jak-project/issues/1821 by adding a
special case for `new` method calls where the argument with type
`symbol` is actually an address to uninitialized structure on the stack.

Fixes https://github.com/open-goal/jak-project/issues/1849 (or at least
the cause of the issue Vaser gave in chat, and one random one I found in
`debug-sphere`)

Fixes https://github.com/open-goal/jak-project/issues/1853

Fixes https://github.com/open-goal/jak-project/issues/1857 by moving the
cast into the cond if the body is a single form and the destination type
is a bitfield/enum which is likely to work well. Seems to work on the
examples we could find in jak 1 and jak 2.

Also fixes an issue with casts on the result of `handle->process` (a
common place to use casts)

the output of process->handle is a plain process. Most of the time, you
end up casting this to a more specific. If you add a cast on every use
of the variable, the decompiler will decide to change the type of that
variable to the more specific type, and this breaks the handle cast.

so previously it was impossible to get code like
```
    (let* ((s2-0 (the-as swingpole (handle->process (-> self control hack))))
           (gp-0 (-> s2-0 dir))
           )
```
But now it will work
2022-09-07 21:58:09 -04:00

6076 lines
237 KiB
C++

#include "Form.h"
#include "FormStack.h"
#include "GenericElementMatcher.h"
#include "common/goos/PrettyPrinter.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/goal_constants.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<float> get_goal_float_constant(FormElement* in) {
auto as_fc = dynamic_cast<ConstantFloatElement*>(in);
if (as_fc) {
return as_fc->value();
}
return {};
}
std::optional<float> 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<CastElement*>(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<ResLumpMacroElement>();
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<ConstantFloatElement>(f);
}
}
}
if (new_type == TypeSpec("meters")) {
auto fc = get_goal_float_constant(in);
if (fc) {
double div = (double)*fc / METER_LENGTH; // GOOS will use doubles here
if (div * METER_LENGTH == *fc) {
return pool.form<GenericElement>(
GenericOperator::make_function(pool.form<ConstantTokenElement>("meters")),
pool.form<ConstantFloatElement>(div));
} 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<GenericElement>(
GenericOperator::make_function(pool.form<ConstantTokenElement>("degrees")),
pool.form<ConstantFloatElement>(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<GenericElement>();
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<GenericElement>();
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<SimpleAtomElement>(SimpleAtom::make_int_constant(value));
}
return pool.form<ConstantTokenElement>(
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<GenericElement*>(in->try_as_single_element());
if (g && g->op().kind() == GenericOperator::Kind::FUNCTION_EXPR) {
auto f = dynamic_cast<SimpleExpressionElement*>(g->op().func()->try_as_single_element());
if (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<Form*> new_forms;
for (auto& e : g->elts()) {
auto as_atom_expr =
dynamic_cast<SimpleExpressionElement*>(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<GenericElement>(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<BitFieldType*>(type_info);
auto enum_info = dynamic_cast<EnumType*>(type_info);
auto* in_as_cond = in->try_as_element<CondWithElseElement>();
// try to fix (the-as <enum> (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<SimpleAtomElement>(*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<SetVarElement*>(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<SetVarElement*>(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<SimpleAtomElement>(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<RegisterAccess>& vars,
const Env& env,
FormPool& pool,
FormStack& stack,
const std::vector<std::vector<FormElement*>*>& output,
bool allow_side_effects,
const std::optional<RegSet>& consumes = std::nullopt,
const std::vector<int>& times_used = {}) {
// to submit to stack to attempt popping
std::vector<Register> submit_regs;
// submit_reg[i] is for var submit_reg_to_var[i]
std::vector<size_t> submit_reg_to_var;
// build submission for stack
std::unordered_map<Register, int, Register::hash> 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);
// fmt::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) {
// fmt::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<Form*> 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<Form*> 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<SimpleExpressionElement>(
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<CastElement>(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<Form*> pop_to_forms(const std::vector<RegisterAccess>& vars,
const Env& env,
FormPool& pool,
FormStack& stack,
bool allow_side_effects,
const std::optional<RegSet>& consumes = std::nullopt,
const std::vector<int>& times_to_use = {}) {
std::vector<Form*> forms;
std::vector<std::vector<FormElement*>> forms_out;
std::vector<std::vector<FormElement*>*> 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<CastElement>(*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<FormElement*> 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<FormElement*>*,
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<Form*> make_casts_if_needed(const std::vector<Form*>& in,
const std::vector<TypeSpec>& in_types,
const TypeSpec& out_type,
FormPool& pool,
const Env& env) {
std::vector<Form*> 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<FormElement*>* result,
bool allow_side_effects) {
mark_popped();
m_addr->update_children_from_stack(env, pool, stack, allow_side_effects);
result->push_back(this);
}
void SimpleExpressionElement::update_from_stack_identity(const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* 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 lab = env.file->labels.at(arg.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);
result->push_back(pool.alloc_element<StringConstantElement>(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) {
result->push_back(this);
return;
}
if (hint.result_type.base_type() == "function") {
result->push_back(this);
return;
} else {
result->push_back(pool.alloc_element<DecompiledDataElement>(lab, hint));
return;
}
}
} else if (arg.is_sym_ptr() || arg.is_sym_val() || arg.is_int() || arg.is_empty_list()) {
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<FormElement*>* 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<FormElement*> 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));
result->push_back(pool.alloc_element<ConstantFloatElement>(flt));
return;
}
}
// converting something else to an FPR, put an expression around it.
result->push_back(pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::GPR_TO_FPR), frm));
} else {
result->push_back(pool.alloc_element<GenericElement>(
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<FormElement*>* 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<FormElement*>* 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<GenericElement>(
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<FormElement*>* 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<GenericElement>(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<Form*> get_math_op_elements(Form* in, FixedOperatorKind kind) {
auto gen_elt = in->try_as_element<GenericElement>();
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<TypeSpec>& arg0_cast,
const std::optional<TypeSpec>& arg1_cast,
FormPool& pool,
const Env& env,
FixedOperatorKind operator_kind,
bool inline_first,
bool inline_second) {
if (!arg1_cast) {
std::vector<Form*> 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<Form*> 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<GenericElement>(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<GenericElement>(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<FormElement*>* 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<FormElement*>* 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<GenericElement>(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<FormElement*>* 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<GenericElement>(
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<FormElement*>* 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<Form*> 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 {
args = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects);
args.push_back(pool.form<SimpleAtomElement>(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.
// (+ <integer 108 + int> 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);
u64 symbol_to_string_offset = -1;
switch (env.version) {
case GameVersion::Jak1:
symbol_to_string_offset = DECOMP_SYM_INFO_OFFSET + 4;
break;
case GameVersion::Jak2:
symbol_to_string_offset = jak2::SYM_TO_STRING_OFFSET;
break;
default:
ASSERT(false);
}
if (arg0_int && (*arg0_int == symbol_to_string_offset) &&
arg1_type.typespec() == TypeSpec("symbol")) {
result->push_back(pool.alloc_element<GetSymbolStringPointer>(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<DerefToken> 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<DerefElement>(args.at(1), out.addr_of, tokens));
return;
} else {
throw std::runtime_error("Failed to match for stride 1 address access with add.");
}
}
} 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<DerefToken> 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<DerefElement>(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<DerefToken> 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<DerefElement>(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<DerefToken> 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<DerefElement>(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;
}
}
}
// fmt::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<DerefToken> 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<DerefElement>(args.at(1), rd_ok.addr_of, tokens));
return;
} else {
// TODO - output error to IR
lg::error("Bad {} at OP: {}\n", args.at(0)->to_string(env), m_my_idx);
throw std::runtime_error("Failed to match product_with_constant inline array access 2.");
}
}
}
}
if (env.dts->ts.tc(TypeSpec("structure"), arg0_type.typespec()) && m_expr.get_arg(1).is_int()) {
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<GenericElement>(
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<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::ADDITION_PTR), args.at(0), args.at(1));
result->push_back(new_form);
return;
}
}
if (arg0_ptr) {
auto new_form = pool.alloc_element<GenericElement>(
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 <constant>) sometimes becomes (&+ <constant> thing).
// in these cases, we flip the argument order.
auto new_form = pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::ADDITION_PTR), args.at(1), args.at(0));
result->push_back(new_form);
} else {
std::optional<TypeSpec> 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<GenericElement*>(args.at(1)->try_as_single_element());
if (as_generic && as_generic->op().kind() == GenericOperator::Kind::FUNCTION_EXPR) {
auto as_func_head = dynamic_cast<SimpleExpressionElement*>(
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<FormElement*>* result,
bool allow_side_effects) {
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<CastElement>(TypeSpec("int"), args.at(0));
}
if (!arg1_i) {
args.at(1) = pool.form<CastElement>(TypeSpec("int"), args.at(1));
}
auto new_form = pool.alloc_element<GenericElement>(
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<FormElement*>* 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<Form*> 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<SimpleAtomElement>(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<GenericElement>(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<FormElement*>* result,
bool allow_side_effects) {
auto arg0_u = is_uint_type(env, m_my_idx, m_expr.get_arg(0).var());
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<Form*> 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 {
args = pop_to_forms({m_expr.get_arg(0).var()}, env, pool, stack, allow_side_effects);
args.push_back(pool.form<SimpleAtomElement>(m_expr.get_arg(1)));
}
if (!arg0_u) {
args.at(0) = pool.form<CastElement>(TypeSpec("uint"), args.at(0));
}
if (!arg1_u) {
args.at(1) = pool.form<CastElement>(TypeSpec("uint"), args.at(1));
}
auto new_form =
pool.alloc_element<GenericElement>(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<FormElement*>* result,
bool allow_side_effects) {
std::vector<Form*> args;
std::vector<RegisterAccess> 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<SimpleAtomElement>(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<ModifiedCopyBitfieldElement>();
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 {
fmt::print("pcpyud rewrite form fail: {} {}\n", base_form.print(), a1_form.print());
}
}
auto new_form = pool.alloc_element<GenericElement>(
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<FormElement*>* result,
bool allow_side_effects) {
std::vector<Form*> 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<GenericElement>(
GenericOperator::make_fixed(op_kind),
std::vector<Form*>{popped_args.at(0), popped_args.at(1), popped_args.at(2)});
result->push_back(new_form);
}
void SimpleExpressionElement::update_from_stack_vector_float_product(
const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* result,
bool allow_side_effects) {
std::vector<Form*> 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<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::VECTOR_FLOAT_PRODUCT),
std::vector<Form*>{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<FormElement*>* result,
bool allow_side_effects) {
std::vector<RegisterAccess> 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<Form*> 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<GenericElement>(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<FormElement*>* 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<EnumType*>(env.dts->ts.lookup_type(arg0_type));
if (bti) {
auto new_form = pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(kind), args.at(0),
cast_form(pool.form<SimpleAtomElement>(m_expr.get_arg(1)), arg0_type, pool, env));
result->push_back(new_form);
} else {
auto new_form = pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(kind), pool.form<CastElement>(TypeSpec("int"), args.at(0)),
pool.form<SimpleAtomElement>(m_expr.get_arg(1)));
result->push_back(new_form);
}
} else {
auto new_form =
pool.alloc_element<GenericElement>(GenericOperator::make_fixed(kind), args.at(0),
pool.form<SimpleAtomElement>(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<GenericElement>(GenericOperator::make_fixed(kind),
args.at(0), args.at(1));
result->push_back(new_form);
} else {
auto cast = pool.form<CastElement>(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<GenericElement>(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<CastElement>();
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<BitFieldType*>(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<BitFieldType*>(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<BitFieldType*>(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<BitfieldAccessElement*>(base->try_as_single_element());
if (!read_elt) {
read_elt = pool.alloc_element<BitfieldAccessElement>(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<FormElement*> 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<Form*> 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<SimpleAtomElement>(
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<BitfieldAccessElement*>(args.at(0)->try_as_single_element());
if (!read_elt) {
read_elt = pool.alloc_element<BitfieldAccessElement>(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<GenericElement>(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<ConstantTokenElement>(arg1_type.base_type()));
std::vector<Form*> form_elts;
for (auto& x : elts) {
form_elts.push_back(pool.form<ConstantTokenElement>(x));
}
auto inverted = pool.form<GenericElement>(oper, form_elts);
auto normal = pool.form<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::LOGNOT), inverted);
auto new_form = pool.alloc_element<GenericElement>(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<BitFieldType*>(env.dts->ts.lookup_type(arg1_type));
auto arg1_enum = dynamic_cast<EnumType*>(env.dts->ts.lookup_type(arg1_type));
if ((arg1_bitfield || arg1_enum) && arg0_type != arg1_type) {
auto new_form = pool.alloc_element<GenericElement>(
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<GenericElement>(GenericOperator::make_fixed(kind),
args.at(0), args.at(1));
return new_form;
}
}
// types bad, insert cast.
auto cast = pool.form<CastElement>(TypeSpec(arg0_i ? "int" : "uint"), args.at(1));
auto new_form =
pool.alloc_element<GenericElement>(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<FormElement*>* 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<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::LOGCLEAR),
std::vector<Form*>{mr.maps.forms.at(a_form), mr.maps.forms.at(b_form)}));
return;
}
// 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,
{Matcher::deref(Matcher::any_reg(0), false,
{DerefTokenMatcher::integer(0), DerefTokenMatcher::string("pid")}),
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")}),
env.get_variable_type(var_a, true))) {
auto* menv = const_cast<Env*>(&env);
menv->disable_use(var_a);
auto repopped = stack.pop_reg(var_b, {}, env, true, stack.size() - 1);
if (!repopped) {
fmt::print("repop failed.\n{}\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<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::PROCESS_TO_HANDLE),
proc_to_ppointer_mr.maps.forms.at(0));
} else {
element = pool.alloc_element<GenericElement>(
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<FormElement*>* 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<BitFieldType*>(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<BitfieldAccessElement>(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<BitfieldAccessElement>();
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<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::MULTIPLICATION), args.at(0),
pool.form<SimpleAtomElement>(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<GenericElement>(GenericOperator::make_fixed(FixedOperatorKind::SHL),
pool.form<CastElement>(TypeSpec("int"), args.at(0)),
pool.form<SimpleAtomElement>(m_expr.get_arg(1)));
result->push_back(new_form);
} else {
auto new_form = pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::SHL), args.at(0),
pool.form<SimpleAtomElement>(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);
}
}
void SimpleExpressionElement::update_from_stack_right_shift_logic(const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* 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<BitFieldType*>(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<BitfieldAccessElement>(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<BitfieldAccessElement*>(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<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::DIVISION), arg,
pool.alloc_single_element_form<SimpleAtomElement>(
nullptr, SimpleAtom::make_int_constant(multiplier)));
result->push_back(new_form);
return;
}
*/
if (!arg0_i && !arg0_u) {
auto new_form = pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::SHR),
pool.form<CastElement>(TypeSpec("int"), arg),
pool.form<SimpleAtomElement>(m_expr.get_arg(1)));
result->push_back(new_form);
} else {
auto new_form = pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::SHR), arg,
pool.form<SimpleAtomElement>(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<FormElement*>* 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<BitFieldType*>(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<BitfieldAccessElement>(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<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::DIVISION), arg,
pool.form<SimpleAtomElement>(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<BitfieldAccessElement>();
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<GenericElement>(GenericOperator::make_fixed(FixedOperatorKind::SAR),
pool.form<CastElement>(TypeSpec("int"), args.at(0)),
pool.form<SimpleAtomElement>(m_expr.get_arg(1)));
result->push_back(new_form);
} else {
auto new_form = pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::SAR), args.at(0),
pool.form<SimpleAtomElement>(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<FormElement*>* 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<GenericElement>(
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<FormElement*>* 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<CastElement>(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<FormElement*>* 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<CastElement>(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<CastElement>(
TypeSpec("int"), pool.form<CastElement>(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<GenericElement>(GenericOperator::make_fixed(kind), op1);
}
} // namespace
void SimpleExpressionElement::update_from_stack_subu_l32_s7(const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* 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<FormElement*>* 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;
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<SimpleExpressionElement*>(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<SimpleExpressionElement*>(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<ConstantFloatElement>(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);
}
}
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<ModifiedCopyBitfieldElement*>(m_src->try_as_single_element());
if (src_as_bf_set && !src_as_bf_set->from_pcpyud() && src_as_bf_set->mods().size() == 1) {
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<DerefElement>(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) {
fmt::print("invalid bf set: {}\n", src_as_bf_set->to_string(env));
}
// setting a bitfield to zero is wonky.
auto bfa = dynamic_cast<BitfieldAccessElement*>(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<DerefElement>(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<SimpleAtomElement>(zero);
m_src = zero_form;
}
}
typedef struct {
FixedOperatorKind kind;
FixedOperatorKind inplace_kind;
int inplace_arg;
} InplaceOpInfo;
const static InplaceOpInfo in_place_ops[] = {
{FixedOperatorKind::ADDITION, FixedOperatorKind::ADDITION_IN_PLACE, 0},
{FixedOperatorKind::ADDITION_PTR, FixedOperatorKind::ADDITION_PTR_IN_PLACE, 0},
{FixedOperatorKind::LOGAND, FixedOperatorKind::LOGAND_IN_PLACE, 0},
{FixedOperatorKind::LOGIOR, FixedOperatorKind::LOGIOR_IN_PLACE, 0},
{FixedOperatorKind::LOGCLEAR, FixedOperatorKind::LOGCLEAR_IN_PLACE, 0},
{FixedOperatorKind::LOGXOR, FixedOperatorKind::LOGXOR_IN_PLACE, 0}};
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 src_as_generic = m_src->try_as_element<GenericElement>();
if (src_as_generic) {
if (src_as_generic->op().is_func()) {
auto funchelt = dynamic_cast<SimpleExpressionElement*>(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<ConstantTokenElement>(call_info.inplace_name));
GenericElement* inplace_call = nullptr;
if (funcname == "seek" || funcname == "seekl") {
inplace_call = pool.alloc_element<GenericElement>(
new_func_op, std::vector<Form*>{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);
auto add_form_0 = src_as_generic->elts().at(op_info.inplace_arg)->to_form(env);
if (dst_form == add_form_0) {
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<SimpleExpressionElement>(SimpleAtom::make_sym_val(m_sym_name).as_expr(), m_my_idx);
auto val = pool.form<SimpleExpressionElement>(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<SetFormFormElement>(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<RegisterAccess>({m_value.var(), m_pair});
auto popped = pop_to_forms(vars, env, pool, stack, true);
auto addr = pool.form<GenericElement>(GenericOperator::make_fixed(op), popped.at(1));
addr->mark_popped();
auto fr = pool.alloc_element<SetFormFormElement>(addr, popped.at(0));
fr->mark_popped();
stack.push_form_element(fr, true);
} else {
auto val = pool.form<SimpleExpressionElement>(m_value, m_my_idx);
val->mark_popped();
auto addr = pool.form<GenericElement>(GenericOperator::make_fixed(op),
pop_to_forms({m_pair}, env, pool, stack, true).at(0));
addr->mark_popped();
auto fr = pool.alloc_element<SetFormFormElement>(addr, val);
fr->mark_popped();
stack.push_form_element(fr, true);
}
}
namespace {
Form* make_optional_cast(const std::optional<TypeSpec>& 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<StackStructureDefElement>();
if (!src) {
return false;
}
if (src->type() != TypeSpec(type_name)) {
return false;
}
// zeroing the rows:
std::vector<RegisterAccess> write_vars;
auto elt = matrix_entries->at(1).elt;
std::vector<DerefTokenMatcher> token_matchers = {DerefTokenMatcher::string("vec"),
DerefTokenMatcher::string("quad")};
if (type_name == "vector") {
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*>(&env);
menv->disable_use(wv);
}
stack.pop(2);
stack.push_value_to_reg(
*matrix_entries->at(0).destination,
pool.form<GenericElement>(GenericOperator::make_function(
pool.form<ConstantTokenElement>(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<StackStructureDefElement>();
if (!src) {
return false;
}
if (src->type() != TypeSpec("matrix")) {
return false;
}
// zeroing the rows:
std::vector<RegisterAccess> 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("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;
}
}
}
// success!
for (auto& wv : write_vars) {
env.get_use_def_info(wv);
Env* menv = const_cast<Env*>(&env);
menv->disable_use(wv);
}
stack.pop(5);
stack.push_value_to_reg(*matrix_entries->at(0).destination,
pool.form<GenericElement>(GenericOperator::make_function(
pool.form<ConstantTokenElement>("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<RegisterAccess>({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<DerefElement>()->set_base(
make_optional_cast(m_dst_cast_type, popped.at(1), pool, env));
m_dst->mark_popped();
m_dst->try_as_element<DerefElement>()->inline_nested();
auto fr = pool.alloc_element<SetFormFormElement>(
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<RegisterAccess>({m_base_var});
auto popped = pop_to_forms(vars, env, pool, stack, true);
m_dst->try_as_element<DerefElement>()->set_base(
make_optional_cast(m_dst_cast_type, popped.at(0), pool, env));
m_dst->mark_popped();
m_dst->try_as_element<DerefElement>()->inline_nested();
auto val = pool.form<SimpleExpressionElement>(m_expr, m_my_idx);
val->mark_popped();
auto fr = pool.alloc_element<SetFormFormElement>(
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<RegisterAccess>({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<RegisterAccess>({m_base_var});
auto popped = pop_to_forms(vars, env, pool, stack, true);
m_dst->mark_popped();
expr_form = pool.form<SimpleExpressionElement>(m_expr, m_my_idx);
array_form = popped.at(0);
}
std::vector<FormElement*> 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<SetFormFormElement>(
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<FormElement*>* 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<GenericElement>(
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<FormElement*>* 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<GenericElement>(
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<SetFormFormElement*>(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) {
fmt::print("failed to match dst {}\n", dst->to_string(env));
return nullptr;
}
if (mr.maps.regs.at(0)->reg() != Register(Reg::GPR, Reg::S6)) {
fmt::print("failed to match pp reg, got {}\n", mr.maps.regs.at(0)->reg().to_string());
return nullptr;
}
return as_set->src();
}
} // namespace
///////////////////
// FunctionCallElement
///////////////////
namespace {
std::optional<RegisterAccess> get_form_reg_acc(Form* in) {
auto as_simple_atom = dynamic_cast<SimpleAtomElement*>(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<SimpleExpressionElement*>(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<FormElement*>* result,
bool allow_side_effects) {
mark_popped();
std::vector<Form*> args;
auto nargs = m_op->arg_vars().size();
args.resize(nargs, nullptr);
std::vector<RegisterAccess> 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<ConstantTokenElement>("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<CastElement>(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<ConstantTokenElement>("run-next-time-in-process");
// also need to clean up (-> <blah> main-thread) to just <blah>
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<Form*> 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<ConstantTokenElement>(virtual_go_mr.maps.strings.at(1)));
auto go_form = pool.alloc_element<GenericElement>(
GenericOperator::make_function(pool.form<ConstantTokenElement>("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<GenericElement>(
GenericOperator::make_function(pool.form<ConstantTokenElement>("go")), arg_forms);
result->push_back(go_form);
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";
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 {} used a virtual call unexpectedly.",
tp_type.method_from_type().print(), tp_type.method_id()));
}
if (should_use_virtual) {
// fmt::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);
// fmt::print("GOT: {}\n", pop->to_string(env));
arg_forms.at(0) = pop;
auto head = mr.maps.forms.at(1);
// 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->to_string(env) == "get-art-by-name-method");
head = pool.form<ConstantTokenElement>("get-art-by-name");
}
auto head_obj = head->to_form(env);
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));
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!";
}
if (!new_head.empty()) {
auto oldp = head->parent_element;
head = pool.form<ConstantTokenElement>(new_head);
head->parent_element = oldp;
arg_forms.erase(arg_forms.begin());
arg_forms.erase(arg_forms.begin());
if (arg_forms.size() > 3) {
auto argi = arg_forms.at(3);
auto argi_o = argi->to_form(env);
if (argi_o.is_int()) {
auto argset = arg_forms.at(0)->to_string(env);
if (argset == "'process-mask") {
auto en = env.dts->ts.try_enum_lookup("process-mask");
if (en) {
arg_forms.at(3) =
cast_to_bitfield_enum(env.dts->ts.try_enum_lookup("process-mask"), pool,
env, argi_o.as_int());
}
} else if (argset == "'sound-flava") {
auto en = env.dts->ts.try_enum_lookup("music-flava");
if (en) {
arg_forms.at(3) = cast_to_int_enum(
env.dts->ts.try_enum_lookup("music-flava"), pool, env, argi_o.as_int());
}
}
}
arg_forms.at(3)->parent_element = argi->parent_element;
}
}
}
}
new_form =
pool.alloc_element<GenericElement>(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<ConstantTokenElement>(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<ConstantTokenElement>(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<GenericElement>();
if (elt_group && elt_group->op().is_func() &&
elt_group->op().func()->to_form(env).is_symbol("sound-group") &&
elt_group->elts().size() == 1) {
Form* so_group_f = nullptr;
if (!elt_group->elts().at(0)->to_form(env).is_symbol("sfx")) {
so_group_f = pool.form<ConstantTokenElement>(
elt_group->elts().at(0)->to_form(env).as_symbol()->name);
}
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<Form*> macro_args;
macro_args.push_back(pool.form<StringConstantElement>(sound_name));
if (so_id_f) {
macro_args.push_back(pool.form<ConstantTokenElement>(":id"));
macro_args.push_back(so_id_f);
}
if (so_vol_f) {
macro_args.push_back(pool.form<ConstantTokenElement>(":vol"));
macro_args.push_back(so_vol_f);
}
if (so_pitch_f) {
macro_args.push_back(pool.form<ConstantTokenElement>(":pitch"));
macro_args.push_back(so_pitch_f);
}
if (so_bend) {
macro_args.push_back(pool.form<ConstantTokenElement>(":bend"));
macro_args.push_back(so_bend);
}
if (so_group_f) {
macro_args.push_back(pool.form<ConstantTokenElement>(":group"));
macro_args.push_back(so_group_f);
}
if (so_positional_f) {
macro_args.push_back(pool.form<ConstantTokenElement>(":position"));
macro_args.push_back(so_positional_f);
}
new_form = pool.alloc_element<GenericElement>(
GenericOperator::make_function(pool.form<ConstantTokenElement>("sound-play")),
macro_args);
result->push_back(new_form);
return;
}
}
}
}
}
new_form = pool.alloc_element<GenericElement>(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) {
auto type_1 = match_result.maps.strings.at(type_for_method);
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<Form*> new_args = dynamic_cast<GenericElement*>(new_form)->elts();
// if needed, cast to to correct type.
std::vector<TypeSpec> 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<CastElement>(expected_arg_types.at(i), new_args.at(i));
}
}
auto new_op = pool.alloc_element<GenericElement>(
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<Form*> new_args = dynamic_cast<GenericElement*>(new_form)->elts();
auto new_op = pool.alloc_element<GenericElement>(
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<SimpleAtomElement>(SimpleAtom::make_sym_ptr(type_2));
if (alloc == "global" && type_1 == "pair") {
// cons!
// (new 'global 'pair a b) -> (cons a b)
std::vector<Form*> cons_args = {dynamic_cast<GenericElement*>(new_form)->elts().at(2),
dynamic_cast<GenericElement*>(new_form)->elts().at(3)};
auto cons_op = pool.alloc_element<GenericElement>(
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<Form*> new_args = dynamic_cast<GenericElement*>(new_form)->elts();
new_args.at(1) = quoted_type;
auto new_op = pool.alloc_element<GenericElement>(
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) {
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);
if (name == "get-property-value-float" && type_source_form->to_string(env) == "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" && type_source_form->to_string(env) == "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" && type_source_form->to_string(env) == "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" && type_source_form->to_string(env) == "res-lump") {
auto as_macro = handle_get_property_value(arg_forms, pool, env);
if (as_macro) {
result->push_back(as_macro);
return;
}
}
// if the type is the exact type of the argument, we want to build it into a method call
if (type_source_form->to_string(env) == 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<ConstantTokenElement>(name);
auto gop = GenericOperator::make_function(method_op);
result->push_back(pool.alloc_element<GenericElement>(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<CastElement>();
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<StackStructureDefElement>();
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<Form*> stack_new_args;
stack_new_args.push_back(pool.form<ConstantTokenElement>("'stack"));
stack_new_args.push_back(pool.form<ConstantTokenElement>(
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<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::NEW), stack_new_args));
ASSERT(!go_next_state);
return;
}
}
auto method_op = pool.form<GetMethodElement>(type_source_form, name, false);
auto gop = GenericOperator::make_function(method_op);
result->push_back(pool.alloc_element<GenericElement>(gop, arg_forms));
return;
}
}
result->push_back(new_form);
}
void FunctionCallElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) {
std::vector<FormElement*> 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<ConstantTokenElement>(*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;
}
void DerefElement::update_from_stack(const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* 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<GetSymbolStringPointer>();
if (sym_str && m_tokens.size() == 1 && m_tokens.at(0).is_int(0)) {
result->push_back(pool.alloc_element<GenericElement>(
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;
}
result->push_back(this);
}
void DerefElement::inline_nested() {
auto as_deref = dynamic_cast<DerefElement*>(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<FormElement*> 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<EmptyElement*>(e)) {
body->push_back(e);
}
}
for (auto e : condition_to_body) {
if (!dynamic_cast<EmptyElement*>(e)) {
body->push_back(e);
}
}
if (body->size() == 0) {
body->push_back(pool.alloc_element<EmptyElement>());
}
}
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<SimpleAtomElement>(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;
}
// fmt::print("Matched condition {} in {}\n", condition_var.to_string(env),
// value->to_string(env));
auto* menv = const_cast<Env*>(&env);
menv->disable_use(body_var);
auto repopped = stack.pop_reg(condition_var, {}, env, true);
if (!repopped) {
repopped = var_to_form(condition_var, pool);
} else {
if (!env.dts->ts.tc(TypeSpec("process"), env.get_variable_type(condition_var, true))) {
repopped = pool.form<CastElement>(TypeSpec("process"), repopped);
}
}
return pool.form<GenericElement>(
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;
}
// fmt::print("Matched condition {} in {}\n", condition_var.to_string(env),
// value->to_string(env));
auto* menv = const_cast<Env*>(&env);
menv->disable_use(body_var);
auto repopped = stack.pop_reg(condition_var, {}, env, true);
if (!repopped) {
repopped = var_to_form(condition_var, pool);
}
return pool.form<GenericElement>(
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<ConstantTokenElement>("ja-group"));
if (obj_chan.is_int(0)) {
return pool.form<GenericElement>(func);
} else {
return pool.form<GenericElement>(func, pool.form<ConstantTokenElement>(":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<FormElement*> 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<RegisterAccess> 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 {
// fmt::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<RegisterAccess> 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<SetVarElement*>(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<FormElement*> 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<FormElement*> 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<Form*> 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<SetVarElement*> dest_sets;
std::vector<TypeSpec> 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<SetVarElement*>(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*>(&env);
env2->disable_def(var, env2->func->warnings);
}
}
}
// merge conds in the else block.
auto else_as_another_cond = else_ir->try_as_element<CondWithElseElement>();
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<CondWithElseElement>();
}
}
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.
// fmt::print("func: {}\n", env.func->name());
//
// fmt::print("checking:\n");
// for (auto& t : source_types) {
// fmt::print(" {}\n", t.print());
// }
auto expected_type = env.get_variable_type(*last_var, true);
// fmt::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);
// fmt::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<CastElement>(expected_type, result_value);
}
// fmt::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<FormElement*> 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<EmptyElement*>(e)) {
continue;
}
entry.condition->push_back(e);
}
if (entry.condition->elts().empty()) {
entry.condition->push_back(pool.alloc_element<EmptyElement>());
}
}
}
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<FormElement*>* 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<FormElement*> 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<Form*> cast_to_64_bit(const std::vector<Form*>& forms,
const std::vector<TypeSpec>& types,
FormPool& pool,
const Env& env) {
std::vector<Form*> 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;
}
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 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<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::LOGTEST), mr_logand.maps.forms.at(0),
mr_logand.maps.forms.at(1));
}
return nullptr;
}
} // namespace
FormElement* ConditionElement::make_zero_check_generic(const Env& env,
FormPool& pool,
const std::vector<Form*>& source_forms,
const std::vector<TypeSpec>& 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<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::EQ),
std::vector<Form*>{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<SimpleAtomElement>(SimpleAtom::make_int_constant(value));
return pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::EQ),
std::vector<Form*>{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<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::EQ),
std::vector<Form*>{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 not_form = pool.alloc_element<GenericElement>(
GenericOperator::make_compare(IR2_Condition::Kind::FALSE), logtest_form);
return not_form;
}
*/
return pool.alloc_element<GenericElement>(GenericOperator::make_compare(m_kind), source_forms);
}
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 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 mr = match(cpad_matcher, in);
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<GenericElement*>(in->at(0));
if (logtest_elt != nullptr) {
auto buttons_form = logtest_elt->elts().at(1);
std::vector<Form*> v;
v.push_back(mr.int_or_form_to_form(pool, 0));
GenericElement* butts =
dynamic_cast<GenericElement*>(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<GenericElement>(
GenericOperator::make_fixed(t == ABS ? FixedOperatorKind::CPAD_HOLD_P
: FixedOperatorKind::CPAD_PRESSED_P),
v);
}
}
}
return nullptr;
}
FormElement* ConditionElement::make_nonzero_check_generic(const Env& env,
FormPool& pool,
const std::vector<Form*>& source_forms,
const std::vector<TypeSpec>&) {
// for (nonzero? (-> obj bitfield))
FormElement* bitfield_compare = nullptr;
ASSERT(source_forms.size() == 1);
auto as_bitfield_op =
dynamic_cast<BitfieldAccessElement*>(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<SimpleAtomElement>(SimpleAtom::make_int_constant(value));
return pool.alloc_element<GenericElement>(GenericOperator::make_fixed(FixedOperatorKind::NEQ),
std::vector<Form*>{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 as_cpad_macro = try_make_logtest_cpad_macro(logtest_form, pool);
if (as_cpad_macro) {
return as_cpad_macro;
}
return as_logtest;
}
return pool.alloc_element<GenericElement>(GenericOperator::make_compare(m_kind), source_forms);
}
FormElement* ConditionElement::make_equal_check_generic(const Env& env,
FormPool& pool,
const std::vector<Form*>& source_forms,
const std::vector<TypeSpec>& 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<GenericElement>(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<GenericElement>(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<Form*> macro_args;
macro_args.push_back(source_forms.at(1));
auto jagroup = source_forms.at(0)->try_as_element<GenericElement>();
for (size_t i = 1; i < jagroup->elts().size(); ++i) {
macro_args.push_back(jagroup->elts().at(i));
}
return pool.alloc_element<GenericElement>(
GenericOperator::make_function(pool.form<ConstantTokenElement>("ja-group?")),
macro_args);
}
}
return pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::EQ),
cast_to_64_bit(source_forms, source_types, pool, env));
}
}
}
FormElement* ConditionElement::make_not_equal_check_generic(
const Env& env,
FormPool& pool,
const std::vector<Form*>& source_forms,
const std::vector<TypeSpec>& 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<GenericElement>(
GenericOperator::make_compare(IR2_Condition::Kind::FALSE),
pool.form<GenericElement>(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<GenericElement>(GenericOperator::make_fixed(FixedOperatorKind::NEQ),
forms_with_cast);
} else {
return pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::NEQ),
cast_to_64_bit(source_forms, source_types, pool, env));
}
}
}
FormElement* ConditionElement::make_less_than_zero_signed_check_generic(
const Env& env,
FormPool& pool,
const std::vector<Form*>& source_forms,
const std::vector<TypeSpec>& 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<GenericElement>(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<SimpleAtomElement>(SimpleAtom::make_int_constant(0));
casted.push_back(zero);
return pool.alloc_element<GenericElement>(GenericOperator::make_fixed(FixedOperatorKind::LT),
casted);
}
}
FormElement* ConditionElement::make_geq_zero_signed_check_generic(
const Env& env,
FormPool& pool,
const std::vector<Form*>& source_forms,
const std::vector<TypeSpec>& 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<GenericElement>(
GenericOperator::make_compare(IR2_Condition::Kind::FALSE),
pool.form<GenericElement>(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<SimpleAtomElement>(SimpleAtom::make_int_constant(0));
casted.push_back(zero);
return pool.alloc_element<GenericElement>(GenericOperator::make_fixed(FixedOperatorKind::GEQ),
casted);
}
}
FormElement* ConditionElement::make_geq_zero_unsigned_check_generic(
const Env& env,
FormPool& pool,
const std::vector<Form*>& source_forms,
const std::vector<TypeSpec>& 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<SimpleAtomElement>(SimpleAtom::make_int_constant(0));
casted.push_back(zero);
return pool.alloc_element<GenericElement>(GenericOperator::make_fixed(FixedOperatorKind::GEQ),
casted);
}
FormElement* ConditionElement::make_generic(const Env& env,
FormPool& pool,
const std::vector<Form*>& source_forms,
const std::vector<TypeSpec>& 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<GenericElement>(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:
return pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::LT),
make_casts_if_needed(source_forms, types, TypeSpec("int"), pool, env));
case IR2_Condition::Kind::LESS_THAN_UNSIGNED:
return pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::LT),
make_casts_if_needed(source_forms, types, TypeSpec("uint"), pool, env));
case IR2_Condition::Kind::GEQ_SIGNED:
return pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::GEQ),
make_casts_if_needed(source_forms, types, TypeSpec("int"), pool, env));
case IR2_Condition::Kind::GEQ_UNSIGNED:
return pool.alloc_element<GenericElement>(
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<SimpleAtomElement>(SimpleAtom::make_int_constant(0));
casted.push_back(zero);
return pool.alloc_element<GenericElement>(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<SimpleAtomElement>(SimpleAtom::make_int_constant(0));
casted.push_back(zero);
return pool.alloc_element<GenericElement>(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<SimpleAtomElement>(SimpleAtom::make_int_constant(0));
casted.push_back(zero);
return pool.alloc_element<GenericElement>(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<SimpleAtomElement>(SimpleAtom::make_int_constant(0));
casted.push_back(zero);
return pool.alloc_element<GenericElement>(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<GenericElement>(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<GenericElement>(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<GenericElement>(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<GenericElement>(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<GenericElement>(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<GenericElement>(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<Form*> source_forms, popped_forms;
std::vector<TypeSpec> source_types;
std::vector<RegisterAccess> 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<SimpleAtomElement>(*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<FormElement*>* result,
bool allow_side_effects) {
mark_popped();
std::vector<Form*> source_forms, popped_forms;
std::vector<TypeSpec> source_types;
std::vector<RegisterAccess> 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<SimpleAtomElement>(*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<FormElement*> new_entries;
std::optional<RegisterAccess> 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<CastElement>();
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<BitFieldType*>(type_info);
if (bitfield_info) {
auto base = pop_to_forms({*var}, env, pool, stack, true).at(0);
auto read_elt = pool.alloc_element<BitfieldAccessElement>(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<BitfieldAccessElement>();
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<BitFieldType*>(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<BitfieldAccessElement>(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<BitfieldAccessElement>();
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<GenericElement>(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<BitFieldType*>(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<BitfieldAccessElement>(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<BitFieldType*>(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<BitfieldAccessElement>(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<GenericElement>(
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<const FunctionEndOp*>(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<const SpecialOp*>(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<const AsmOp*>(m_op);
if (as_asm) {
push_asm_to_stack(as_asm, this, env, pool, stack);
return;
}
auto as_branch = dynamic_cast<AsmBranchOp*>(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<AtomicOpElement>(delay);
auto be = pool.alloc_element<AsmBranchElement>(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<FormElement*>* 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<SimpleAtom> 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<ConditionElement>(m_branch_op->condition().kind(), vars[0], vars[1],
consumed, false);
// and update it from the stack.
std::vector<FormElement*> 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<TranslatedAsmBranch>(
branch_condition, m_branch_delay, m_branch_op->label_id(), m_branch_op->is_likely());
// fmt::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<SimpleAtom> 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<ConditionElement>(m_op->condition().kind(), vars[0], vars[1],
consumed, false);
// and update it from the stack.
std::vector<FormElement*> 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<SimpleAtomElement>(SimpleAtom::make_var(src));
branch_delay =
pool.form<SetVarElement>(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<SimpleAtomElement>(SimpleAtom::make_sym_val("#f"));
branch_delay = pool.form<SetVarElement>(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<SimpleAtomElement>(SimpleAtom::make_sym_val("#t"));
branch_delay = pool.form<SetVarElement>(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<TranslatedAsmBranch>(branch_condition, branch_delay,
m_op->label_id(), m_op->likely());
// fmt::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<FormElement*>* 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<DerefElement>(
var_to_form(base.value(), pool), false,
std::vector<DerefToken>{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<FormElement*>* 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 <idx>)
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<DerefToken> 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<DerefElement>(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;
fmt::print("power {}\n", 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<DerefToken> 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<DerefElement>(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<DerefToken> 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<DerefElement>(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<DerefToken> 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<DerefElement>(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<DerefToken> 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<DerefElement>(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<DerefToken> 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<DerefElement>(base, false, tokens);
result->push_back(deref);
}
}
}
void ArrayFieldAccess::update_from_stack(const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* 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<FormElement*>* 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<FormElement*>* 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<SimpleExpressionElement*>(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 (!val && 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 as_cpad_macro = try_make_logtest_cpad_macro(logtest_form, pool);
if (as_cpad_macro) {
val = pool.alloc_single_form(nullptr, as_cpad_macro);
} else {
val = pool.alloc_single_form(nullptr, as_logtest);
}
}
}
if (!val) {
val = pool.form<GenericElement>(
GenericOperator::make_compare(on_zero ? IR2_Condition::Kind::NONZERO
: IR2_Condition::Kind::ZERO),
std::vector<Form*>{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<SimpleAtomElement>(m_value);
}
auto dst = pool.form<ConstantTokenElement>(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<SetFormFormElement>(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<VectorFloatLoadStoreElement*>(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<RegisterAccess> 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*>(&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);
// create the actual form
Form* new_thing = pool.form<GenericElement>(
GenericOperator::make_function(pool.form<ConstantTokenElement>("vector-reset!")),
std::vector<Form*>{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<DerefElement>();
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<FormElement*>* 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<DerefElement>();
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<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::METHOD_OF_OBJECT),
std::vector<Form*>{type, pool.form<ConstantTokenElement>(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<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::METHOD_OF_OBJECT),
std::vector<Form*>{type_as_deref->base(),
pool.form<ConstantTokenElement>(m_method_info.name)}));
return;
}
}
result->push_back(pool.alloc_element<GenericElement>(
GenericOperator::make_fixed(FixedOperatorKind::METHOD_OF_TYPE),
std::vector<Form*>{type, pool.form<ConstantTokenElement>(m_method_info.name)}));
}
void SimpleAtomElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
void StringConstantElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
void GetMethodElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
void CondNoElseElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
void ConstantTokenElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
void ConstantFloatElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
void StackStructureDefElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
void StackSpillValueElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
void GetSymbolStringPointer::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
void DefstateElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
void DefskelgroupElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
void DefpartgroupElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
void DefpartElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
void ResLumpMacroElement::update_from_stack(const Env&,
FormPool&,
FormStack&,
std::vector<FormElement*>* result,
bool) {
mark_popped();
result->push_back(this);
}
void LabelDerefElement::update_from_stack(const Env& env,
FormPool& pool,
FormStack& stack,
std::vector<FormElement*>* 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<FormElement*> 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<EmptyElement>());
stack.push_form_element(this, true);
}
} // namespace decompiler