[decompiler] clean up vector detection and add vector float product detection (#638)

* change

* recognize vector float product and update tests
This commit is contained in:
water111
2021-06-27 17:24:35 -04:00
committed by GitHub
parent bfb1fbe1fc
commit a6d5c4eda3
146 changed files with 1085 additions and 1653 deletions
+57 -228
View File
@@ -950,6 +950,54 @@ void SimpleExpressionElement::update_from_stack_pcypld(const Env& env,
result->push_back(new_form);
}
void SimpleExpressionElement::update_from_stack_vector_plus_minus(bool is_add,
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(is_add ? FixedOperatorKind::VECTOR_PLUS
: FixedOperatorKind::VECTOR_MINUS),
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_copy_first_int_2(const Env& env,
FixedOperatorKind kind,
FormPool& pool,
@@ -1559,6 +1607,15 @@ void SimpleExpressionElement::update_from_stack(const Env& env,
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(true, env, pool, stack, result, allow_side_effects);
break;
case SimpleExpression::Kind::VECTOR_MINUS:
update_from_stack_vector_plus_minus(false, 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;
default:
throw std::runtime_error(
fmt::format("SimpleExpressionElement::update_from_stack NYI for {}", to_string(env)));
@@ -3786,110 +3843,6 @@ Form* is_load_store_vector_to_reg(const Register& reg,
return mr.maps.forms.at(0);
}
/*!
* try to convert to an assembly op, return nullptr if we can't.
*/
const AsmOp* get_asm_op(FormElement* form) {
auto as_asm = dynamic_cast<OpenGoalAsmOpElement*>(form);
if (as_asm) {
return as_asm->op();
}
auto two = dynamic_cast<AsmOpElement*>(form);
if (two) {
return two->op();
}
return nullptr;
}
/*!
* Is this vmove.w vfX, vf0? This is a common trick to set the w field.
*/
bool is_set_w_1(const Register& reg, FormElement* form, const Env&) {
auto as_asm = get_asm_op(form);
if (!as_asm) {
return false;
}
auto instr = as_asm->instruction();
if (instr.kind != InstructionKind::VMOVE) {
return false;
}
if (instr.cop2_dest != 1) {
return false;
}
if (!instr.get_src(0).is_reg(Register(Reg::VF, 0))) {
return false;
}
if (!instr.get_dst(0).is_reg(reg)) {
return false;
}
return true;
}
/*!
* Is this a COP2 op in the form vblah.mask vfX, vfY, vfZ?
*/
bool is_vf_3op_dst(InstructionKind kind,
u8 dest_mask,
const Register& dst,
const Register& src0,
const Register& src1,
FormElement* form) {
auto as_asm = get_asm_op(form);
if (!as_asm) {
return false;
}
auto instr = as_asm->instruction();
if (instr.kind != kind) {
return false;
}
if (instr.cop2_dest != dest_mask) {
return false;
}
if (!instr.get_src(0).is_reg(src0)) {
return false;
}
if (!instr.get_src(1).is_reg(src1)) {
return false;
}
if (!instr.get_dst(0).is_reg(dst)) {
return false;
}
return true;
}
/*!
* Make a vf register.
*/
Register vfr(int idx) {
return Register(Reg::VF, idx);
}
/*!
* Try to pop a variable from the stack again. If we are detecting a macro that flips argument
* evaluation order, we can use this to fix it up and remove temporaries.
* If the previous pop succeeded, this does nothing.
*/
Form* repop_arg(Form* in, FormStack& stack, const Env& env, FormPool& pool) {
auto as_atom = form_as_atom(in);
if (as_atom && as_atom->is_var()) {
return pop_to_forms({as_atom->var()}, env, pool, stack, true).at(0);
}
return in;
}
/*!
* Imagine:
* x = foo
@@ -3934,122 +3887,6 @@ std::optional<RegisterAccess> form_as_ra(Form* form) {
return {};
}
/*!
* Handle an inlined call to vector-!
*/
bool try_vector_add_sub_inline(const Env& env,
FormPool& pool,
FormStack& stack,
bool is_add,
FormElement* store_element) {
// we are looking for 5 ops, none are sets, the store element is passed in separately, before
// propagating
auto elts = stack.try_getting_active_stack_entries({false, false, false, false});
if (!elts) {
return false;
}
int idx = 0;
if (is_add) {
// third (.vmove.w vf6 vf0)
if (!is_set_w_1(Register(Reg::VF, 6), elts->at(idx++).elt, env)) {
return false;
}
}
// check first: (.lvf vf4 (&-> arg1 quad))
auto first =
is_load_store_vector_to_reg(Register(Reg::VF, 4), elts->at(idx++).elt, true, nullptr);
if (!first) {
return false;
}
// second (.lvf vf5 (&-> a0-1 quad))
auto second =
is_load_store_vector_to_reg(Register(Reg::VF, 5), elts->at(idx++).elt, true, nullptr);
if (!second) {
return false;
}
if (!is_add) {
// third (.vmove.w vf6 vf0)
if (!is_set_w_1(Register(Reg::VF, 6), elts->at(idx++).elt, env)) {
return false;
}
}
// 4th (.vsub.xyz vf6 vf4 vf5)
if (!is_vf_3op_dst(is_add ? InstructionKind::VADD : InstructionKind::VSUB, 14, vfr(6), vfr(4),
vfr(5), elts->at(idx++).elt)) {
return false;
}
// 5th (and remember the index)
int store_idx = -1;
auto store = is_load_store_vector_to_reg(Register(Reg::VF, 6), store_element, false, &store_idx);
if (!store) {
return false;
}
// 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 add/sub, but couldn't get store var.");
return false;
}
// remove these from the stack.
stack.pop(4);
// 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);
}
// repop the arguments in the opposite order. this can eliminate temporaries as this will
// use the opposite order of the original attempt.
second = repop_arg(second, stack, env, pool);
first = repop_arg(first, stack, env, pool);
// 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 vector-! form
Form* new_thing = pool.alloc_single_element_form<GenericElement>(
nullptr,
GenericOperator::make_function(pool.alloc_single_element_form<ConstantTokenElement>(
nullptr, is_add ? "vector+!" : "vector-!")),
std::vector<Form*>{store, first, second});
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;
}
bool try_vector_reset_inline(const Env& env,
FormPool& pool,
FormStack& stack,
@@ -4132,14 +3969,6 @@ void VectorFloatLoadStoreElement::push_to_stack(const Env& env, FormPool& pool,
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_add_sub_inline(env, pool, stack, true, this)) {
return;
}
if (try_vector_add_sub_inline(env, pool, stack, false, this)) {
return;
}
if (try_vector_reset_inline(env, pool, stack, this)) {
return;
}