mirror of
https://github.com/open-goal/jak-project.git
synced 2024-10-20 11:26:18 -04:00
d9f9e076af
* improve label system * clean up menu * debug menu working, still need to fix tests * fix tests and clean up
5252 lines
204 KiB
C++
5252 lines
204 KiB
C++
#include "Form.h"
|
|
#include "FormStack.h"
|
|
#include "GenericElementMatcher.h"
|
|
#include "common/goos/PrettyPrinter.h"
|
|
#include "decompiler/util/DecompilerTypeSystem.h"
|
|
#include "decompiler/ObjectFile/LinkedObjectFile.h"
|
|
#include "decompiler/util/data_decompile.h"
|
|
#include "decompiler/IR2/bitfields.h"
|
|
#include "common/util/BitUtils.h"
|
|
#include "common/type_system/state.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(Form* in) {
|
|
auto as_fc = in->try_as_element<ConstantFloatElement>();
|
|
if (as_fc) {
|
|
return as_fc->value();
|
|
}
|
|
return {};
|
|
}
|
|
} // 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!
|
|
}
|
|
|
|
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.alloc_single_element_form<GenericElement>(
|
|
nullptr,
|
|
GenericOperator::make_function(
|
|
pool.alloc_single_element_form<ConstantTokenElement>(nullptr, "meters")),
|
|
pool.alloc_single_element_form<ConstantFloatElement>(nullptr, div));
|
|
} else {
|
|
lg::error("Floating point value {} could not be converted to meters.", *fc);
|
|
}
|
|
}
|
|
}
|
|
|
|
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.alloc_single_element_form<GenericElement>(
|
|
nullptr,
|
|
GenericOperator::make_function(
|
|
pool.alloc_single_element_form<ConstantTokenElement>(nullptr, "degrees")),
|
|
pool.alloc_single_element_form<ConstantFloatElement>(nullptr, div));
|
|
} else {
|
|
lg::error("Floating point value {} could not be converted to degrees.", *fc);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
auto type_info = env.dts->ts.lookup_type(new_type);
|
|
auto bitfield_info = dynamic_cast<BitFieldType*>(type_info);
|
|
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);
|
|
}
|
|
|
|
auto enum_info = dynamic_cast<EnumType*>(type_info);
|
|
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);
|
|
}
|
|
}
|
|
|
|
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.alloc_single_element_form<SimpleAtomElement>(nullptr, 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.alloc_single_element_form<CastElement>(nullptr, 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.alloc_single_element_form<CastElement>(nullptr, *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)?
|
|
*/
|
|
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 type == TypeSpec("int");
|
|
}
|
|
|
|
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 Env& env, int my_idx, RegisterAccess var) {
|
|
auto type = env.get_types_before_op(my_idx).get(var.reg()).typespec();
|
|
return type == TypeSpec("uint");
|
|
}
|
|
|
|
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 {
|
|
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;
|
|
}
|
|
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;
|
|
assert(in.size() == in_types.size());
|
|
for (size_t i = 0; i < in_types.size(); i++) {
|
|
out.push_back(make_cast_if_needed(in.at(i), in_types.at(i), out_type, 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:
|
|
/*
|
|
auto kv = env.label_types().find(lab.name);
|
|
if (kv != env.label_types().end()) {
|
|
auto type_name = kv->second.type_name;
|
|
// the actual decompilation is deferred until later, once static lambdas are done.
|
|
if (type_name == "_auto_") {
|
|
result->push_back(pool.alloc_element<DecompiledDataElement>(lab));
|
|
} else if (type_name == "_lambda_") {
|
|
result->push_back(this);
|
|
} else {
|
|
result->push_back(pool.alloc_element<DecompiledDataElement>(lab, kv->second));
|
|
}
|
|
} else {
|
|
result->push_back(this);
|
|
}
|
|
*/
|
|
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)));
|
|
}
|
|
}
|
|
|
|
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 {
|
|
// 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),
|
|
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 {}", src_type.print(), to_string(env)));
|
|
}
|
|
}
|
|
|
|
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."));
|
|
}
|
|
}
|
|
|
|
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())) {
|
|
// 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(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)));
|
|
}
|
|
}
|
|
|
|
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."));
|
|
}
|
|
}
|
|
|
|
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)));
|
|
}
|
|
|
|
namespace {
|
|
std::vector<Form*> get_addition_elements(Form* in) {
|
|
auto gen_elt = in->try_as_element<GenericElement>();
|
|
if (gen_elt && gen_elt->op().is_fixed(FixedOperatorKind::ADDITION)) {
|
|
return gen_elt->elts();
|
|
} else {
|
|
return {in};
|
|
}
|
|
}
|
|
|
|
FormElement* make_and_compact_addition(Form* arg0,
|
|
Form* arg1,
|
|
const std::optional<TypeSpec>& arg0_cast,
|
|
const std::optional<TypeSpec>& arg1_cast,
|
|
FormPool& pool,
|
|
const Env& env) {
|
|
if (!arg1_cast) {
|
|
auto arg0_elts = get_addition_elements(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.
|
|
auto arg1_elts = get_addition_elements(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(FixedOperatorKind::ADDITION), 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(FixedOperatorKind::ADDITION), arg0, arg1);
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
void SimpleExpressionElement::update_from_stack_add_i(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 arg0_u = is_uint_type(env, m_my_idx, m_expr.get_arg(0).var());
|
|
|
|
bool arg1_reg = m_expr.get_arg(1).is_var();
|
|
bool arg1_i = true;
|
|
bool arg1_u = true;
|
|
if (arg1_reg) {
|
|
arg1_i = is_int_type(env, m_my_idx, m_expr.get_arg(1).var());
|
|
arg1_u = is_uint_type(env, m_my_idx, m_expr.get_arg(1).var());
|
|
}
|
|
|
|
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.alloc_single_element_form<SimpleAtomElement>(nullptr, 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());
|
|
auto arg0_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(0).var().reg());
|
|
arg1_ptr = is_ptr_or_child(env, m_my_idx, m_expr.get_arg(1).var(), true);
|
|
|
|
// try to find symbol to string stuff
|
|
auto arg0_int = get_goal_integer_constant(args.at(0), env);
|
|
if (arg0_int && (*arg0_int == SYM_INFO_OFFSET + 4) &&
|
|
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 {
|
|
lg::error("Bad is {}\n", args.at(0)->to_string(env));
|
|
throw std::runtime_error("Failed to match product_with_constant inline array access 2.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto arg0_type = env.get_types_before_op(m_my_idx).get(m_expr.get_arg(0).var().reg());
|
|
|
|
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()) && (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 (false && ((arg0_i && arg1_i) || (arg0_u && arg1_u))) {
|
|
auto new_form = pool.alloc_element<GenericElement>(
|
|
GenericOperator::make_fixed(FixedOperatorKind::ADDITION), args.at(0), args.at(1));
|
|
result->push_back(new_form);
|
|
} else 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 (!arg1_i && !arg1_u) {
|
|
arg1_cast = TypeSpec(arg0_i ? "int" : "uint");
|
|
}
|
|
|
|
result->push_back(
|
|
make_and_compact_addition(args.at(0), args.at(1), arg0_cast, arg1_cast, pool, env));
|
|
}
|
|
}
|
|
|
|
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.alloc_single_element_form<CastElement>(nullptr, TypeSpec("int"), args.at(0));
|
|
}
|
|
|
|
if (!arg1_i) {
|
|
args.at(1) = pool.alloc_single_element_form<CastElement>(nullptr, 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_i = is_int_type(env, m_my_idx, m_expr.get_arg(0).var());
|
|
bool arg1_i = true;
|
|
bool arg1_reg = m_expr.get_arg(1).is_var();
|
|
if (arg1_reg) {
|
|
arg1_i = is_int_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) {
|
|
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.alloc_single_element_form<SimpleAtomElement>(nullptr, m_expr.get_arg(1)));
|
|
}
|
|
|
|
if (!arg0_i) {
|
|
args.at(0) = pool.alloc_single_element_form<CastElement>(nullptr, TypeSpec("int"), args.at(0));
|
|
}
|
|
|
|
if (!arg1_i) {
|
|
args.at(1) = pool.alloc_single_element_form<CastElement>(nullptr, TypeSpec("int"), 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_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.alloc_single_element_form<SimpleAtomElement>(nullptr, m_expr.get_arg(1)));
|
|
}
|
|
|
|
if (!arg0_u) {
|
|
args.at(0) = pool.alloc_single_element_form<CastElement>(nullptr, TypeSpec("uint"), args.at(0));
|
|
}
|
|
|
|
if (!arg1_u) {
|
|
args.at(1) = pool.alloc_single_element_form<CastElement>(nullptr, 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.alloc_single_element_form<SimpleAtomElement>(nullptr, 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_vector_3_dot(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()},
|
|
env, pool, stack, allow_side_effects);
|
|
|
|
for (int i = 0; i < 2; 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(FixedOperatorKind::VECTOR_3_DOT),
|
|
std::vector<Form*>{popped_args.at(0), popped_args.at(1)});
|
|
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(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_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.alloc_single_element_form<SimpleAtomElement>(nullptr, 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.alloc_single_element_form<CastElement>(nullptr, TypeSpec("int"), args.at(0)),
|
|
pool.alloc_single_element_form<SimpleAtomElement>(nullptr, 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.alloc_single_element_form<SimpleAtomElement>(nullptr, m_expr.get_arg(1)));
|
|
result->push_back(new_form);
|
|
}
|
|
|
|
return;
|
|
}
|
|
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 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)) {
|
|
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.alloc_single_element_form<CastElement>(
|
|
nullptr, 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
|
|
auto arg0_type = env.get_variable_type(m_expr.get_arg(0).var(), true);
|
|
|
|
// 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) {
|
|
// 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 && 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 = 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 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;
|
|
|
|
auto args = pop_to_forms({m_expr.get_arg(0).var(), m_expr.get_arg(1).var()}, env, pool, stack,
|
|
allow_side_effects);
|
|
|
|
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(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.alloc_single_element_form<ConstantTokenElement>(
|
|
nullptr, arg1_type.base_type()));
|
|
std::vector<Form*> form_elts;
|
|
for (auto& x : elts) {
|
|
form_elts.push_back(pool.alloc_single_element_form<ConstantTokenElement>(nullptr, x));
|
|
}
|
|
auto inverted =
|
|
pool.alloc_single_element_form<GenericElement>(nullptr, oper, form_elts);
|
|
auto normal = pool.alloc_single_element_form<GenericElement>(
|
|
nullptr, 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)) {
|
|
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.alloc_single_element_form<CastElement>(
|
|
nullptr, 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})});
|
|
|
|
Form hack_form;
|
|
hack_form.elts().push_back(element);
|
|
|
|
auto mr = match(logclear_matcher, &hack_form);
|
|
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;
|
|
}
|
|
|
|
auto make_handle_matcher = Matcher::op_fixed(
|
|
FixedOperatorKind::LOGIOR,
|
|
{Matcher::op_fixed(
|
|
FixedOperatorKind::SHL,
|
|
{Matcher::deref(Matcher::any_reg(0), false,
|
|
{DerefTokenMatcher::integer(0), DerefTokenMatcher::string("pid")}),
|
|
Matcher::integer(32)}),
|
|
Matcher::op_fixed(FixedOperatorKind::ASM_SLLV_R0, {Matcher::any_reg(1)})});
|
|
|
|
auto handle_mr = match(make_handle_matcher, &hack_form);
|
|
if (handle_mr.matched) {
|
|
auto var_a = handle_mr.maps.regs.at(0).value();
|
|
auto var_b = handle_mr.maps.regs.at(1).value();
|
|
if (env.get_variable_name(var_a) == env.get_variable_name(var_b) &&
|
|
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 (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.alloc_single_element_form<SimpleAtomElement>(
|
|
nullptr, 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.alloc_single_element_form<CastElement>(nullptr, TypeSpec("int"), args.at(0)),
|
|
pool.alloc_single_element_form<SimpleAtomElement>(nullptr, 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.alloc_single_element_form<SimpleAtomElement>(nullptr, 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.alloc_single_element_form<CastElement>(nullptr, TypeSpec("int"), arg),
|
|
pool.alloc_single_element_form<SimpleAtomElement>(nullptr, 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.alloc_single_element_form<SimpleAtomElement>(nullptr, 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 && 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.alloc_single_element_form<SimpleAtomElement>(
|
|
nullptr, 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.alloc_single_element_form<CastElement>(nullptr, TypeSpec("int"), args.at(0)),
|
|
pool.alloc_single_element_form<SimpleAtomElement>(nullptr, 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.alloc_single_element_form<SimpleAtomElement>(nullptr, 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();
|
|
if (type == TypeSpec("int") || type == TypeSpec("uint") || 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("Used int to float on a {} from {}: {}", 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();
|
|
if (type == TypeSpec("float")) {
|
|
result->push_back(pool.alloc_element<CastElement>(TypeSpec("int"), arg, 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.general_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(env, FixedOperatorKind::MULTIPLICATION, pool, stack, result,
|
|
allow_side_effects);
|
|
break;
|
|
case SimpleExpression::Kind::ADD_S:
|
|
update_from_stack_float_2(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_vector_3_dot(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;
|
|
}
|
|
|
|
// bool skip = false;
|
|
// auto dst_type = env.get_variable_type(m_dst, false);
|
|
// auto as_bitfield = dynamic_cast<BitFieldType*>(env.dts->ts.lookup_type(dst_type));
|
|
// if (as_bitfield && as_bitfield->get_load_size() == 16) {
|
|
// skip = true;
|
|
// }
|
|
|
|
// 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 (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);
|
|
|
|
/*
|
|
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, RegisterAccess::Print::AS_VARIABLE_NO_CAST);
|
|
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 dst_dform = pool.alloc_single_element_form<SimpleAtomElement>(nullptr,
|
|
SimpleAtom::make_var(m_dst)); auto loc_elt = pool.alloc_element<DerefElement>(dst_dform, false,
|
|
field_token); loc_elt->inline_nested(); auto loc = pool.alloc_single_form(nullptr, loc_elt); auto
|
|
new_form_el = pool.alloc_element<SetFormFormElement>(loc, value);
|
|
stack.push_form_element(new_form_el, true);
|
|
return;
|
|
}
|
|
}
|
|
*/
|
|
|
|
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.alloc_single_element_form<ConstantFloatElement>(nullptr, 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.alloc_single_element_form<SimpleAtomElement>(nullptr, zero);
|
|
m_src = zero_form;
|
|
}
|
|
}
|
|
|
|
const std::pair<FixedOperatorKind, FixedOperatorKind> in_place_ops[] = {
|
|
{FixedOperatorKind::ADDITION, FixedOperatorKind::ADDITION_IN_PLACE},
|
|
{FixedOperatorKind::ADDITION_PTR, FixedOperatorKind::ADDITION_PTR_IN_PLACE},
|
|
{FixedOperatorKind::LOGAND, FixedOperatorKind::LOGAND_IN_PLACE},
|
|
{FixedOperatorKind::LOGIOR, FixedOperatorKind::LOGIOR_IN_PLACE},
|
|
{FixedOperatorKind::LOGCLEAR, FixedOperatorKind::LOGCLEAR_IN_PLACE}};
|
|
|
|
auto src_as_generic = m_src->try_as_element<GenericElement>();
|
|
if (src_as_generic) {
|
|
for (auto& op_pair : in_place_ops) {
|
|
if (src_as_generic->op().is_fixed(op_pair.first)) {
|
|
auto dst_form = m_dst->to_form(env);
|
|
auto add_form_0 = src_as_generic->elts().at(0)->to_form(env);
|
|
|
|
if (dst_form == add_form_0) {
|
|
src_as_generic->op() = GenericOperator::make_fixed(op_pair.second);
|
|
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.alloc_single_element_form<SimpleExpressionElement>(
|
|
nullptr, SimpleAtom::make_sym_val(m_sym_name).as_expr(), m_my_idx);
|
|
auto val = pool.alloc_single_element_form<SimpleExpressionElement>(nullptr, m_value, m_my_idx);
|
|
val->update_children_from_stack(env, pool, stack, true);
|
|
|
|
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.alloc_single_element_form<GenericElement>(
|
|
nullptr, 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.alloc_single_element_form<SimpleExpressionElement>(nullptr, m_value, m_my_idx);
|
|
val->mark_popped();
|
|
auto addr = pool.alloc_single_element_form<GenericElement>(
|
|
nullptr, 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)));
|
|
|
|
Form hack;
|
|
hack.elts().push_back(elt);
|
|
auto mr = match(matcher, &hack);
|
|
|
|
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.alloc_single_element_form<GenericElement>(
|
|
nullptr,
|
|
GenericOperator::make_function(pool.alloc_single_element_form<ConstantTokenElement>(
|
|
nullptr, 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;
|
|
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)));
|
|
|
|
Form hack;
|
|
hack.elts().push_back(elt);
|
|
auto mr = match(matcher, &hack);
|
|
|
|
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.alloc_single_element_form<GenericElement>(
|
|
nullptr, GenericOperator::make_function(
|
|
pool.alloc_single_element_form<ConstantTokenElement>(
|
|
nullptr, "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.alloc_single_element_form<SimpleExpressionElement>(nullptr, 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.alloc_single_element_form<SimpleExpressionElement>(nullptr, 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
|
|
///////////////////
|
|
|
|
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());
|
|
|
|
// 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));
|
|
}
|
|
|
|
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();
|
|
auto val_atom = form_as_atom(val);
|
|
if (val_atom && val_atom->is_var()) {
|
|
actual_arg_type = env.get_variable_type(val_atom->var(), true);
|
|
}
|
|
|
|
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.alloc_single_element_form<ConstantTokenElement>(
|
|
nullptr, virtual_go_mr.maps.strings.at(1)));
|
|
auto go_form = pool.alloc_element<GenericElement>(
|
|
GenericOperator::make_function(
|
|
pool.alloc_single_element_form<ConstantTokenElement>(nullptr, "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.alloc_single_element_form<ConstantTokenElement>(nullptr, "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) {
|
|
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;
|
|
|
|
new_form = pool.alloc_element<GenericElement>(
|
|
GenericOperator::make_function(mr.maps.forms.at(1)), arg_forms);
|
|
result->push_back(new_form);
|
|
assert(!go_next_state);
|
|
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.alloc_single_element_form<CastElement>(
|
|
nullptr, 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.alloc_single_element_form<SimpleAtomElement>(
|
|
nullptr, 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 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.alloc_single_element_form<ConstantTokenElement>(nullptr, 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.alloc_single_element_form<ConstantTokenElement>(nullptr, "'stack"));
|
|
stack_new_args.push_back(pool.alloc_single_element_form<ConstantTokenElement>(
|
|
nullptr, 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.alloc_single_element_form<GetMethodElement>(nullptr, 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
|
|
///////////////////
|
|
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();
|
|
|
|
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);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
return pool.alloc_single_element_form<GenericElement>(
|
|
nullptr, 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.alloc_single_element_form<GenericElement>(
|
|
nullptr, GenericOperator::make_fixed(FixedOperatorKind::PPOINTER_TO_PROCESS), repopped);
|
|
}
|
|
} // 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 {
|
|
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;
|
|
}
|
|
source_types.push_back(last_in_body->src_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("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.alloc_single_element_form<CastElement>(nullptr, expected_type, result_value);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// fmt::print("candidate: {}\n", elt->to_string(env));
|
|
|
|
constexpr int reg_input_1 = 0;
|
|
constexpr int reg_input_2 = 1;
|
|
constexpr int reg_input_3 = 2;
|
|
constexpr int reg_temp_1 = 10;
|
|
constexpr int reg_temp_2 = 11;
|
|
constexpr int reg_temp_3 = 12;
|
|
|
|
// check first.
|
|
auto first_matcher =
|
|
Matcher::op(GenericOpMatcher::condition(IR2_Condition::Kind::NONZERO),
|
|
{Matcher::op(GenericOpMatcher::fixed(FixedOperatorKind::L32_NOT_FALSE_CBOOL),
|
|
{Matcher::any_reg(reg_input_1)})});
|
|
|
|
auto first_result = match(first_matcher, elt->entries.at(0).condition);
|
|
if (!first_result.matched) {
|
|
return nullptr;
|
|
}
|
|
|
|
// auto first_use_of_in = *first_result.maps.regs.at(reg_input_1);
|
|
// fmt::print("reg1: {}\n", first_use_of_in.to_string(env));
|
|
|
|
auto setup_matcher = Matcher::set_var(
|
|
Matcher::deref(Matcher::any_reg(reg_input_2), false,
|
|
{DerefTokenMatcher::string("process"), DerefTokenMatcher::integer(0)}),
|
|
reg_temp_1);
|
|
|
|
auto if_matcher = Matcher::if_no_else(
|
|
Matcher::op(
|
|
GenericOpMatcher::fixed(FixedOperatorKind::EQ),
|
|
{Matcher::deref(Matcher::any_reg(reg_input_3), false, {DerefTokenMatcher::string("pid")}),
|
|
Matcher::deref(Matcher::any_reg(reg_temp_2), false,
|
|
{DerefTokenMatcher::string("pid")})}),
|
|
Matcher::any_reg(reg_temp_3));
|
|
|
|
auto second_matcher = Matcher::begin({setup_matcher, if_matcher});
|
|
|
|
auto second_result = match(second_matcher, elt->entries.at(1).condition);
|
|
if (!second_result.matched) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto in1 = *first_result.maps.regs.at(reg_input_1);
|
|
auto in2 = *second_result.maps.regs.at(reg_input_2);
|
|
auto in3 = *second_result.maps.regs.at(reg_input_3);
|
|
|
|
auto in_name = in1.to_string(env);
|
|
if (in_name != in2.to_string(env)) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (in_name != in3.to_string(env)) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto temp_name = second_result.maps.regs.at(reg_temp_1)->to_string(env);
|
|
if (temp_name != second_result.maps.regs.at(reg_temp_2)->to_string(env)) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (temp_name != second_result.maps.regs.at(reg_temp_3)->to_string(env)) {
|
|
return nullptr;
|
|
}
|
|
|
|
const auto& temp_use_def = env.get_use_def_info(*second_result.maps.regs.at(reg_temp_1));
|
|
if (temp_use_def.use_count() != 2 || temp_use_def.def_count() != 1) {
|
|
return nullptr;
|
|
}
|
|
|
|
// modify use def:
|
|
auto* menv = const_cast<Env*>(&env);
|
|
menv->disable_use(in2);
|
|
menv->disable_use(in3);
|
|
|
|
auto repopped = stack.pop_reg(in1, {}, env, true);
|
|
// fmt::print("repopped: {}\n", repopped->to_string(env));
|
|
|
|
if (!repopped) {
|
|
repopped = var_to_form(in1, pool);
|
|
}
|
|
|
|
return pool.alloc_element<GenericElement>(
|
|
GenericOperator::make_function(
|
|
pool.alloc_single_element_form<ConstantTokenElement>(nullptr, "handle->process")),
|
|
repopped);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void ShortCircuitElement::push_to_stack(const Env& env, FormPool& pool, FormStack& stack) {
|
|
mark_popped();
|
|
if (!used_as_value.value_or(false)) {
|
|
throw std::runtime_error(
|
|
"ShortCircuitElement::push_to_stack not implemented for result not used case.");
|
|
|
|
stack.push_form_element(this, true);
|
|
} else {
|
|
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 {
|
|
/*!
|
|
* 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)) {
|
|
return cast_form(value, type, pool, env);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
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;
|
|
}
|
|
} // 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.alloc_single_element_form<SimpleAtomElement>(
|
|
nullptr, 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});
|
|
}
|
|
|
|
return pool.alloc_element<GenericElement>(GenericOperator::make_compare(m_kind), source_forms);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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.alloc_single_element_form<SimpleAtomElement>(
|
|
nullptr, 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_logand = try_make_nonzero_logtest(source_forms.at(0), pool);
|
|
if (as_logand) {
|
|
return as_logand;
|
|
}
|
|
|
|
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 {
|
|
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.alloc_single_element_form<GenericElement>(
|
|
nullptr, GenericOperator::make_fixed(FixedOperatorKind::NULLP), source_forms.at(0)));
|
|
} 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.alloc_single_element_form<SimpleAtomElement>(nullptr,
|
|
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.alloc_single_element_form<GenericElement>(
|
|
nullptr, 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.alloc_single_element_form<SimpleAtomElement>(nullptr,
|
|
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_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::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::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.alloc_single_element_form<SimpleAtomElement>(
|
|
nullptr, 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.alloc_single_element_form<SimpleAtomElement>(
|
|
nullptr, 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::GREATER_THAN_ZERO_UNSIGNED: {
|
|
auto casted = make_casts_if_needed(source_forms, types, TypeSpec("uint"), pool, env);
|
|
auto zero = pool.alloc_single_element_form<SimpleAtomElement>(
|
|
nullptr, 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.alloc_single_element_form<SimpleAtomElement>(
|
|
nullptr, 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.alloc_single_element_form<SimpleAtomElement>(nullptr, *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.alloc_single_element_form<SimpleAtomElement>(nullptr, *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 {
|
|
// stack.push_form_element(form_elt, true);
|
|
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 {
|
|
throw std::runtime_error(fmt::format("Got invalid bitfield manip for srl: {} type was {}",
|
|
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 sav = op->src(1);
|
|
assert(sav.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 (sav->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.alloc_single_element_form<GenericElement>(
|
|
nullptr, 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.alloc_single_element_form<AtomicOpElement>(nullptr, 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.alloc_single_element_form<SimpleAtomElement>(nullptr, SimpleAtom::make_var(src));
|
|
|
|
branch_delay = pool.alloc_single_element_form<SetVarElement>(
|
|
nullptr, 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.alloc_single_element_form<SimpleAtomElement>(
|
|
nullptr, SimpleAtom::make_sym_val("#f"));
|
|
|
|
branch_delay = pool.alloc_single_element_form<SetVarElement>(nullptr, 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.alloc_single_element_form<SimpleAtomElement>(
|
|
nullptr, SimpleAtom::make_sym_val("#t"));
|
|
|
|
branch_delay = pool.alloc_single_element_form<SetVarElement>(nullptr, 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::fixed_op(FixedOperatorKind::MULTIPLICATION, {reg0_matcher, Matcher::integer(4)});
|
|
auto matcher = Matcher::fixed_op(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::fixed_op(FixedOperatorKind::ADDITION, {base_matcher, offset_matcher}),
|
|
Matcher::fixed_op(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::fixed_op(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::fixed_op(FixedOperatorKind::ADDITION, {reg0_matcher, mult_matcher}),
|
|
Matcher::fixed_op(FixedOperatorKind::ADDITION_PTR, {reg0_matcher, mult_matcher})});
|
|
auto match_result = match(matcher, new_val);
|
|
if (!match_result.matched) {
|
|
matcher = Matcher::match_or(
|
|
{Matcher::fixed_op(FixedOperatorKind::ADDITION, {mult_matcher, reg0_matcher}),
|
|
Matcher::fixed_op(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 {
|
|
throw std::runtime_error("Not power of two case, not yet implemented (no offset)");
|
|
}
|
|
} 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::fixed_op(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::fixed_op(FixedOperatorKind::MULTIPLICATION,
|
|
{reg0_matcher, Matcher::integer(m_expected_stride)});
|
|
mult_matcher = Matcher::match_or({Matcher::cast("uint", mult_matcher), mult_matcher});
|
|
auto matcher = Matcher::fixed_op(FixedOperatorKind::ADDITION, {mult_matcher, reg1_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::fixed_op(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");
|
|
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) {
|
|
val = pool.alloc_single_form(nullptr, as_logtest);
|
|
}
|
|
}
|
|
|
|
if (!val) {
|
|
val = pool.alloc_single_element_form<GenericElement>(
|
|
nullptr,
|
|
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.alloc_single_element_form<SimpleAtomElement>(nullptr, m_value);
|
|
}
|
|
|
|
auto dst = pool.alloc_single_element_form<ConstantTokenElement>(
|
|
nullptr, 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.general_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.alloc_single_element_form<GenericElement>(
|
|
nullptr,
|
|
GenericOperator::make_function(
|
|
pool.alloc_single_element_form<ConstantTokenElement>(nullptr, "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->guessed_name.to_string();
|
|
// 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.alloc_single_element_form<ConstantTokenElement>(
|
|
nullptr, 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.alloc_single_element_form<ConstantTokenElement>(nullptr, m_method_info.name)}));
|
|
return;
|
|
}
|
|
}
|
|
|
|
result->push_back(pool.alloc_element<GenericElement>(
|
|
GenericOperator::make_fixed(FixedOperatorKind::METHOD_OF_TYPE),
|
|
std::vector<Form*>{type, pool.alloc_single_element_form<ConstantTokenElement>(
|
|
nullptr, 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 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 {}\n", 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
|