#include "ir.h"
#include "ir_visitor.h"
#include "ir_optimization.h"
#include "compiler/glsl_types.h"
#include "program/prog_instruction.h"
namespace {
class ir_vectorize_visitor : public ir_hierarchical_visitor {
public:
void clear()
{
assignment[0] = NULL;
assignment[1] = NULL;
assignment[2] = NULL;
assignment[3] = NULL;
current_assignment = NULL;
last_assignment = NULL;
channels = 0;
has_swizzle = false;
}
ir_vectorize_visitor()
{
clear();
progress = false;
}
virtual ir_visitor_status visit_enter(ir_assignment *);
virtual ir_visitor_status visit_enter(ir_swizzle *);
virtual ir_visitor_status visit_enter(ir_dereference_array *);
virtual ir_visitor_status visit_enter(ir_expression *);
virtual ir_visitor_status visit_enter(ir_if *);
virtual ir_visitor_status visit_enter(ir_loop *);
virtual ir_visitor_status visit_enter(ir_texture *);
virtual ir_visitor_status visit_leave(ir_assignment *);
void try_vectorize();
ir_assignment *assignment[4];
ir_assignment *current_assignment, *last_assignment;
unsigned channels;
bool has_swizzle;
bool progress;
};
}
static void
rewrite_swizzle(ir_instruction *ir, void *data)
{
ir_swizzle_mask *mask = (ir_swizzle_mask *)data;
switch (ir->ir_type) {
case ir_type_swizzle: {
ir_swizzle *swz = (ir_swizzle *)ir;
if (swz->val->type->is_vector()) {
swz->mask = *mask;
}
swz->type = glsl_type::get_instance(swz->type->base_type,
mask->num_components, 1);
break;
}
case ir_type_expression: {
ir_expression *expr = (ir_expression *)ir;
expr->type = glsl_type::get_instance(expr->type->base_type,
mask->num_components, 1);
for (unsigned i = 0; i < 4; i++) {
if (expr->operands[i]) {
ir_rvalue *rval = expr->operands[i]->as_rvalue();
if (rval && rval->type->is_scalar() &&
!rval->as_expression() && !rval->as_swizzle()) {
expr->operands[i] = new(ir) ir_swizzle(rval, 0, 0, 0, 0,
mask->num_components);
}
}
}
break;
}
default:
break;
}
}
void
ir_vectorize_visitor::try_vectorize()
{
if (this->last_assignment && this->channels > 1) {
ir_swizzle_mask mask = {0, 0, 0, 0, channels, 0};
this->last_assignment->write_mask = 0;
for (unsigned i = 0, j = 0; i < 4; i++) {
if (this->assignment[i]) {
this->last_assignment->write_mask |= 1 << i;
if (this->assignment[i] != this->last_assignment) {
this->assignment[i]->remove();
}
switch (j) {
case 0: mask.x = i; break;
case 1: mask.y = i; break;
case 2: mask.z = i; break;
case 3: mask.w = i; break;
}
j++;
}
}
visit_tree(this->last_assignment->rhs, rewrite_swizzle, &mask);
this->progress = true;
}
clear();
}
static bool
single_channel_write_mask(unsigned write_mask)
{
return write_mask != 0 && (write_mask & (write_mask - 1)) == 0;
}
static unsigned
write_mask_to_swizzle(unsigned write_mask)
{
switch (write_mask) {
case WRITEMASK_X: return SWIZZLE_X;
case WRITEMASK_Y: return SWIZZLE_Y;
case WRITEMASK_Z: return SWIZZLE_Z;
case WRITEMASK_W: return SWIZZLE_W;
}
UNREACHABLE("not reached");
}
static bool
write_mask_matches_swizzle(unsigned write_mask,
const ir_swizzle *swz)
{
return ((write_mask == WRITEMASK_X && swz->mask.x == SWIZZLE_X) ||
(write_mask == WRITEMASK_Y && swz->mask.x == SWIZZLE_Y) ||
(write_mask == WRITEMASK_Z && swz->mask.x == SWIZZLE_Z) ||
(write_mask == WRITEMASK_W && swz->mask.x == SWIZZLE_W));
}
ir_visitor_status
ir_vectorize_visitor::visit_enter(ir_assignment *ir)
{
ir_dereference *lhs = this->last_assignment != NULL ?
this->last_assignment->lhs : NULL;
ir_rvalue *rhs = this->last_assignment != NULL ?
this->last_assignment->rhs : NULL;
if (ir->condition ||
this->channels >= 4 ||
!single_channel_write_mask(ir->write_mask) ||
this->assignment[write_mask_to_swizzle(ir->write_mask)] != NULL ||
(lhs && !ir->lhs->equals(lhs)) ||
(rhs && !ir->rhs->equals(rhs, ir_type_swizzle))) {
try_vectorize();
}
this->current_assignment = ir;
return visit_continue;
}
ir_visitor_status
ir_vectorize_visitor::visit_enter(ir_swizzle *ir)
{
if (this->current_assignment) {
if (write_mask_matches_swizzle(this->current_assignment->write_mask, ir)) {
this->has_swizzle = true;
} else {
this->current_assignment = NULL;
}
}
return visit_continue;
}
ir_visitor_status
ir_vectorize_visitor::visit_enter(ir_dereference_array *)
{
this->current_assignment = NULL;
return visit_continue_with_parent;
}
ir_visitor_status
ir_vectorize_visitor::visit_enter(ir_expression *ir)
{
if (ir->is_horizontal()) {
this->current_assignment = NULL;
return visit_continue_with_parent;
}
return visit_continue;
}
ir_visitor_status
ir_vectorize_visitor::visit_enter(ir_if *ir)
{
try_vectorize();
visit_list_elements(this, &ir->then_instructions);
try_vectorize();
visit_list_elements(this, &ir->else_instructions);
try_vectorize();
return visit_continue_with_parent;
}
ir_visitor_status
ir_vectorize_visitor::visit_enter(ir_loop *ir)
{
try_vectorize();
visit_list_elements(this, &ir->body_instructions);
try_vectorize();
return visit_continue_with_parent;
}
ir_visitor_status
ir_vectorize_visitor::visit_enter(ir_texture *)
{
this->current_assignment = NULL;
return visit_continue_with_parent;
}
ir_visitor_status
ir_vectorize_visitor::visit_leave(ir_assignment *ir)
{
if (this->has_swizzle && this->current_assignment) {
assert(this->current_assignment == ir);
unsigned channel = write_mask_to_swizzle(this->current_assignment->write_mask);
this->assignment[channel] = ir;
this->channels++;
this->last_assignment = this->current_assignment;
}
this->current_assignment = NULL;
this->has_swizzle = false;
return visit_continue;
}
bool
do_vectorize(exec_list *instructions)
{
ir_vectorize_visitor v;
v.run(instructions);
v.try_vectorize();
return v.progress;
}