[qfcc] Implement support for the swizzle operator

The destination operand must be a full four component vector, but the
source can be smaller and small sources do not need to be aligned: the
offset of the source operand and the swizzle indices are adjusted. The
adjustments are done during final statement emission in order to avoid
confusing the data flow analyser (and that's when def offsets are known).
This commit is contained in:
Bill Currie 2022-05-01 10:02:26 +09:00
parent 3c20dd515e
commit ef9960c6f9
11 changed files with 347 additions and 6 deletions

View file

@ -258,6 +258,17 @@ typedef struct {
struct type_s *type; ///< result type struct type_s *type; ///< result type
} ex_horizontal_t; } ex_horizontal_t;
//NOTE always operates on vec4 or dvec4, so needs a suitable destination and
//care must be taken when working with smaller source operands (check aligmnet
//and adjust swizzle operation as needed)
typedef struct {
struct expr_s *src; ///< source expression
unsigned source[4]; ///< src component indices
unsigned neg; ///< bitmask of dst components to negate
unsigned zero; ///< bitmask of dst components to 0
struct type_s *type; ///< result type
} ex_swizzle_t;
#define POINTER_VAL(p) (((p).def ? (p).def->offset : 0) + (p).val) #define POINTER_VAL(p) (((p).def ? (p).def->offset : 0) + (p).val)
typedef struct expr_s { typedef struct expr_s {
@ -293,6 +304,7 @@ typedef struct expr_s {
ex_with_t with; ///< with expr param ex_with_t with; ///< with expr param
struct type_s *nil; ///< type for nil if known struct type_s *nil; ///< type for nil if known
ex_horizontal_t hop; ///< horizontal vector operation ex_horizontal_t hop; ///< horizontal vector operation
ex_swizzle_t swizzle; ///< vector swizzle operation
} e; } e;
} expr_t; } expr_t;
@ -480,6 +492,8 @@ expr_t *new_unary_expr (int op, expr_t *e1);
*/ */
expr_t *new_horizontal_expr (int op, expr_t *vec, struct type_s *type); expr_t *new_horizontal_expr (int op, expr_t *vec, struct type_s *type);
expr_t *new_swizzle_expr (expr_t *src, const char *swizzle);
/** Create a new def reference (non-temporary variable) expression node. /** Create a new def reference (non-temporary variable) expression node.
\return The new def reference expression node (::def_t). \return The new def reference expression node (::def_t).

View file

@ -63,5 +63,6 @@ EX_EXPR(adjstk) ///< stack adjust expression (::ex_adjstk_t)
EX_EXPR(with) ///< with expression (::ex_with_t) EX_EXPR(with) ///< with expression (::ex_with_t)
EX_EXPR(args) ///< @args marker in parameter list. no data EX_EXPR(args) ///< @args marker in parameter list. no data
EX_EXPR(horizontal) ///< horizontal vector operation (::ex_horzontal_t) EX_EXPR(horizontal) ///< horizontal vector operation (::ex_horzontal_t)
EX_EXPR(swizzle) ///< vector swizzle operation (::ex_swizzle_t)
///@} ///@}

View file

@ -162,6 +162,36 @@ type_t *field_type (type_t *aux);
type_t *pointer_type (type_t *aux); type_t *pointer_type (type_t *aux);
type_t *vector_type (const type_t *ele_type, int width) __attribute__((pure)); type_t *vector_type (const type_t *ele_type, int width) __attribute__((pure));
type_t *base_type (const type_t *vec_type) __attribute__((pure)); type_t *base_type (const type_t *vec_type) __attribute__((pure));
/** Return an integral type of same size as the provided type.
Any 32-bit type will produce type_int (or one of ivec2, ivec3 or ivec4).
Any 64-bit type will produce type_long (lor one of lvec2, lvec3, or lvec4).
Both type_width() and type_size() of the returned type will match the
provided type.
\param base Type on which the return type will be based.
\return Matching integral type (int, long, or a vector form), or
null if no such match can be made.
*/
type_t *int_type (const type_t *base) __attribute__((pure));
/** Return a floating point type of same size as the provided type.
Any 32-bit type will produce type_float (or one of vec2, vec3 or vec4).
Any 64-bit type will produce type_double (lor one of dvec2, dvec3, or
dvec4).
Both type_width() and type_size() of the returned type will match the
provided type.
\param base Type on which the return type will be based.
\return Matching floating point type (float, double, or a vector
form), or null if no such match can be made.
*/
type_t *float_type (const type_t *base) __attribute__((pure));
type_t *array_type (type_t *aux, int size); type_t *array_type (type_t *aux, int size);
type_t *based_array_type (type_t *aux, int base, int top); type_t *based_array_type (type_t *aux, int base, int top);
type_t *alias_type (type_t *type, type_t *alias_chain, const char *name); type_t *alias_type (type_t *type, type_t *alias_chain, const char *name);

View file

@ -555,6 +555,41 @@ print_args (dstring_t *dstr, expr_t *e, int level, int id, expr_t *next)
e->line); e->line);
} }
static void
print_horizontal (dstring_t *dstr, expr_t *e, int level, int id, expr_t *next)
{
int indent = level * 2 + 2;
_print_expr (dstr, e->e.hop.vec, level, id, next);
dasprintf (dstr, "%*se_%p -> \"e_%p\";\n", indent, "", e, e->e.hop.vec);
dasprintf (dstr, "%*se_%p [label=\"hop %s\\n%d\"];\n", indent, "", e,
get_op_string (e->e.hop.op), e->line);
}
static void
print_swizzle (dstring_t *dstr, expr_t *e, int level, int id, expr_t *next)
{
static char swizzle_components[] = "xyzw";
int indent = level * 2 + 2;
ex_swizzle_t swiz = e->e.swizzle;
const char *swizzle = "";
for (int i = 0; i < 4; i++) {
if (swiz.zero & (1 << i)) {
swizzle = va (0, "%s0", swizzle);
} else {
swizzle = va (0, "%s%s%c", swizzle,
swiz.neg & (1 << i) ? "-" : "",
swizzle_components[swiz.source[i]]);
}
}
_print_expr (dstr, swiz.src, level, id, next);
dasprintf (dstr, "%*se_%p -> \"e_%p\";\n", indent, "", e, swiz.src);
dasprintf (dstr, "%*se_%p [label=\"swizzle %s\\n%d\"];\n", indent, "", e,
swizzle, e->line);
}
static void static void
_print_expr (dstring_t *dstr, expr_t *e, int level, int id, expr_t *next) _print_expr (dstring_t *dstr, expr_t *e, int level, int id, expr_t *next)
{ {
@ -584,6 +619,8 @@ _print_expr (dstring_t *dstr, expr_t *e, int level, int id, expr_t *next)
[ex_adjstk] = print_adjstk, [ex_adjstk] = print_adjstk,
[ex_with] = print_with, [ex_with] = print_with,
[ex_args] = print_args, [ex_args] = print_args,
[ex_horizontal] = print_horizontal,
[ex_swizzle] = print_swizzle,
}; };
int indent = level * 2 + 2; int indent = level * 2 + 2;

View file

@ -179,6 +179,74 @@ use_tempop (operand_t *op, expr_t *expr)
bug (expr, "temp users went negative: %s", operand_string (op)); bug (expr, "temp users went negative: %s", operand_string (op));
} }
static def_t *
cover_def_32 (def_t *def, int *adj)
{
int offset = def->offset;
def_t *cover = def;
if (def->alias) {
offset += def->alias->offset;
}
*adj = offset & 3;
if (offset & 3) {
if (def->alias) {
cover = alias_def (def->alias, def->type, def->offset);
} else {
cover = alias_def (def, def->type, 0);
}
cover->offset -= offset & 3;
}
return cover;
}
static def_t *
cover_def_64 (def_t *def, int *adj)
{
int offset = def->offset;
def_t *cover = def;
if (def->alias) {
offset += def->alias->offset;
}
if (offset & 1) {
internal_error (0, "misaligned 64-bit swizzle source");
}
*adj = (offset & 6) >> 1;
if (offset & 6) {
if (def->alias) {
cover = alias_def (def->alias, def->type, def->offset);
} else {
cover = alias_def (def, def->type, 0);
}
cover->offset -= offset & 6;
}
return cover;
}
static def_t *
cover_def (def_t *def, int *adj)
{
if (type_size (base_type (def->type)) == 1) {
return cover_def_32 (def, adj);
} else {
return cover_def_64 (def, adj);
}
}
static void
adjust_swizzle (def_t *def, int adj)
{
pr_ushort_t swiz = def->offset;
for (int i = 0; i < 8; i += 2) {
pr_ushort_t mask = 3 << i;
pr_ushort_t ind = swiz & mask;
swiz &= ~mask;
swiz |= (ind + (adj << i)) & mask;
}
def->offset = swiz;
}
static void static void
emit_statement (statement_t *statement) emit_statement (statement_t *statement)
{ {
@ -202,12 +270,28 @@ emit_statement (statement_t *statement)
op_b = statement->opb; op_b = statement->opb;
op_c = statement->opc; op_c = statement->opc;
} }
def_a = get_operand_def (statement->expr, op_a); def_a = get_operand_def (statement->expr, op_a);
use_tempop (op_a, statement->expr); use_tempop (op_a, statement->expr);
def_b = get_operand_def (statement->expr, op_b); def_b = get_operand_def (statement->expr, op_b);
use_tempop (op_b, statement->expr); use_tempop (op_b, statement->expr);
def_c = get_operand_def (statement->expr, op_c); def_c = get_operand_def (statement->expr, op_c);
use_tempop (op_c, statement->expr); use_tempop (op_c, statement->expr);
if (strcmp (opcode, "swizzle") == 0) {
op_c->type = float_type (op_c->type);
op_a->type = float_type (op_a->type);
if (!op_c->type || !op_a->type) {
internal_error (statement->expr, "invalid types in swizzle");
}
if (op_a->width < 4) {
int adj;
def_a = cover_def (def_a, &adj);
adjust_swizzle (def_b, adj);
op_a->width = 4;
}
}
inst = opcode_find (opcode, op_a, op_b, op_c); inst = opcode_find (opcode, op_a, op_b, op_c);
if (!inst) { if (!inst) {

View file

@ -195,6 +195,8 @@ get_type (expr_t *e)
return &type_va_list; return &type_va_list;
case ex_horizontal: case ex_horizontal:
return e->e.hop.type; return e->e.hop.type;
case ex_swizzle:
return e->e.swizzle.type;
case ex_count: case ex_count:
internal_error (e, "invalid expression"); internal_error (e, "invalid expression");
} }
@ -425,6 +427,11 @@ copy_expr (expr_t *e)
*n = *e; *n = *e;
e->e.hop.vec = copy_expr (e->e.hop.vec); e->e.hop.vec = copy_expr (e->e.hop.vec);
return n; return n;
case ex_swizzle:
n = new_expr ();
*n = *e;
e->e.swizzle.src = copy_expr (e->e.swizzle.src);
return n;
case ex_count: case ex_count:
break; break;
} }
@ -611,6 +618,74 @@ new_horizontal_expr (int op, expr_t *vec, type_t *type)
return e; return e;
} }
expr_t *
new_swizzle_expr (expr_t *src, const char *swizzle)
{
type_t *src_type = get_type (src);
if (!src_type) {
return src;
}
int src_width = type_width (src_type);
// swizzle always generates a *vec4
ex_swizzle_t swiz = {};
#define m(x) (1 << ((x) - 'a'))
#define v(x, mask) (((x) & 0x60) == 0x60 && (m(x) & (mask)))
#define vind(x) ((x) & 3)
#define cind(x) (-(((x) >> 3) ^ (x)) & 3)
#define tind(x) ((((~(x+1)>>2)&1) + x + 1) & 3)
const int color = m('r') | m('g') | m('b') | m('a');
const int vector = m('x') | m('y') | m('z') | m('w');
const int texture = m('s') | m('t') | m('p') | m('q');
int type_mask = 0;
int comp_count = 0;
for (const char *s = swizzle; *s; s++) {
if (comp_count >= 4) {
return error (src, "too many components in swizzle");
}
if (*s == '0') {
swiz.zero |= 1 << comp_count;
comp_count++;
} else if (*s == '-') {
swiz.neg |= 1 << comp_count;
} else {
int ind = 0;
int mask = 0;
if (v (*s, vector)) {
ind = vind (*s);
mask = 1;
} else if (v (*s, color)) {
ind = cind (*s);
mask = 2;
} else if (v (*s, texture)) {
ind = tind (*s);
mask = 4;
}
if (!mask) {
return error (src, "invalid component in swizzle");
}
if (type_mask & ~mask) {
return error (src, "mixed components in swizzle");
}
if (ind >= src_width) {
return error (src, "swizzle component out of bounds");
}
type_mask |= mask;
swiz.source[comp_count++] = ind;
}
}
swiz.zero |= (0xf << comp_count) & 0xf;
swiz.src = new_alias_expr (vector_type (&type_float, src_width), src);
swiz.type = vector_type (base_type (src_type), 4);
expr_t *expr = new_expr ();
expr->type = ex_swizzle;
expr->e.swizzle = swiz;
return expr;
}
expr_t * expr_t *
new_def_expr (def_t *def) new_def_expr (def_t *def)
{ {
@ -1547,6 +1622,8 @@ has_function_call (expr_t *e)
return has_function_call (e->e.retrn.ret_val); return has_function_call (e->e.retrn.ret_val);
case ex_horizontal: case ex_horizontal:
return has_function_call (e->e.hop.vec); return has_function_call (e->e.hop.vec);
case ex_swizzle:
return has_function_call (e->e.swizzle.src);
case ex_error: case ex_error:
case ex_state: case ex_state:
case ex_label: case ex_label:
@ -1692,6 +1769,7 @@ unary_expr (int op, expr_t *e)
case ex_alias: case ex_alias:
case ex_assign: case ex_assign:
case ex_horizontal: case ex_horizontal:
case ex_swizzle:
{ {
expr_t *n = new_unary_expr (op, e); expr_t *n = new_unary_expr (op, e);
@ -1785,6 +1863,7 @@ unary_expr (int op, expr_t *e)
case ex_address: case ex_address:
case ex_assign: case ex_assign:
case ex_horizontal: case ex_horizontal:
case ex_swizzle:
if (options.code.progsversion == PROG_VERSION) { if (options.code.progsversion == PROG_VERSION) {
return binary_expr (EQ, e, new_nil_expr ()); return binary_expr (EQ, e, new_nil_expr ());
} else { } else {
@ -1874,6 +1953,7 @@ unary_expr (int op, expr_t *e)
case ex_alias: case ex_alias:
case ex_assign: case ex_assign:
case ex_horizontal: case ex_horizontal:
case ex_swizzle:
bitnot_expr: bitnot_expr:
if (options.code.progsversion == PROG_ID_VERSION) { if (options.code.progsversion == PROG_ID_VERSION) {
expr_t *n1 = new_int_expr (-1); expr_t *n1 = new_int_expr (-1);

View file

@ -142,6 +142,7 @@ is_lvalue (const expr_t *expr)
case ex_with: case ex_with:
case ex_args: case ex_args:
case ex_horizontal: case ex_horizontal:
case ex_swizzle:
break; break;
case ex_count: case ex_count:
internal_error (expr, "invalid expression"); internal_error (expr, "invalid expression");

View file

@ -654,13 +654,28 @@ convert_scalar (expr_t *scalar, int op, expr_t *vec)
if (!*s_op) { if (!*s_op) {
return 0; return 0;
} }
// expand the scalar to a vector of the same width as vec // expand the scalar to a vector of the same width as vec
for (int i = 1; i < type_width (get_type (vec)); i++) { type_t *vec_type = get_type (vec);
expr_t *s = copy_expr (scalar);
s->next = scalar; if (scalar->type == ex_symbol || scalar->type == ex_def
scalar = s; || is_constant (scalar)) {
for (int i = 1; i < type_width (get_type (vec)); i++) {
expr_t *s = copy_expr (scalar);
s->next = scalar;
scalar = s;
}
return new_vector_list (scalar);
} }
return new_vector_list (scalar);
char swizzle[] = "xxxx";
type_t *vec_base = base_type (vec_type);
expr_t *tmp = new_temp_def_expr (vector_type (vec_base, 4));
expr_t *block = new_block_expr ();
swizzle[type_width (vec_type)] = 0;
append_expr (block, assign_expr (tmp, new_swizzle_expr (scalar, swizzle)));
block->e.block.result = new_alias_expr (vec_type, tmp);
return block;
} }
static expr_t * static expr_t *

View file

@ -41,6 +41,7 @@
#include <QF/hash.h> #include <QF/hash.h>
#include "tools/qfcc/include/diagnostic.h"
#include "tools/qfcc/include/opcodes.h" #include "tools/qfcc/include/opcodes.h"
#include "tools/qfcc/include/options.h" #include "tools/qfcc/include/options.h"
#include "tools/qfcc/include/qfcc.h" #include "tools/qfcc/include/qfcc.h"
@ -302,7 +303,26 @@ operand_width (const char *opname, operand_t *op)
} }
return op->width; return op->width;
} }
#if 0
if (!strcmp (name, "swizzle")) {
adjust_swizzle_op (&search_op, 0);
adjust_swizzle_op (&search_op, 2);
}
static void
adjust_swizzle_op (opcode_t *op, int opind)
{
// swizzle instructions require both operands to be 4 components (4 or 8
// words) in size with the same alignment.
op->widths[opind] = 4;
if (pr_type_size[op->types[opind]] == 1) {
op->types[opind] = ev_float;
} else if (pr_type_size[op->types[opind]] == 2) {
op->types[opind] = ev_double;
} else {
internal_error (0, "unexpected swizzle op size");
}
}
#endif
static opcode_t * static opcode_t *
rua_opcode_find (const char *name, operand_t *op_a, operand_t *op_b, rua_opcode_find (const char *name, operand_t *op_a, operand_t *op_b,
operand_t *op_c) operand_t *op_c)

View file

@ -1783,6 +1783,32 @@ expr_horizontal (sblock_t *sblock, expr_t *e, operand_t **op)
return sblock; return sblock;
} }
static sblock_t *
expr_swizzle (sblock_t *sblock, expr_t *e, operand_t **op)
{
const char *opcode = "swizzle";
statement_t *s;
int swiz = 0;
type_t *res_type = e->e.swizzle.type;
for (int i = 0; i < 4; i++) {
swiz |= e->e.swizzle.source[i] & 3;
}
swiz |= (e->e.swizzle.neg & 0xf) << 8;
swiz |= (e->e.swizzle.zero & 0xf) << 12;
s = new_statement (st_expr, opcode, e);
sblock = statement_subexpr (sblock, e->e.swizzle.src, &s->opa);
s->opb = short_operand (swiz, e);
if (!*op) {
*op = temp_operand (res_type, e);
}
s->opc = *op;
sblock_add_statement (sblock, s);
return sblock;
}
static sblock_t * static sblock_t *
expr_def (sblock_t *sblock, expr_t *e, operand_t **op) expr_def (sblock_t *sblock, expr_t *e, operand_t **op)
{ {
@ -1915,6 +1941,7 @@ statement_subexpr (sblock_t *sblock, expr_t *e, operand_t **op)
[ex_expr] = expr_expr, [ex_expr] = expr_expr,
[ex_uexpr] = expr_uexpr, [ex_uexpr] = expr_uexpr,
[ex_horizontal] = expr_horizontal, [ex_horizontal] = expr_horizontal,
[ex_swizzle] = expr_swizzle,
[ex_def] = expr_def, [ex_def] = expr_def,
[ex_symbol] = expr_symbol, [ex_symbol] = expr_symbol,
[ex_temp] = expr_temp, [ex_temp] = expr_temp,

View file

@ -586,6 +586,38 @@ base_type (const type_t *vec_type)
return ev_types[vec_type->type]; return ev_types[vec_type->type];
} }
type_t *
int_type (const type_t *base)
{
int width = type_width (base);
base = base_type (base);
if (!base) {
return 0;
}
if (type_size (base) == 1) {
base = &type_int;
} else if (type_size (base) == 2) {
base = &type_long;
}
return vector_type (base, width);
}
type_t *
float_type (const type_t *base)
{
int width = type_width (base);
base = base_type (base);
if (!base) {
return 0;
}
if (type_size (base) == 1) {
base = &type_float;
} else if (type_size (base) == 2) {
base = &type_double;
}
return vector_type (base, width);
}
type_t * type_t *
array_type (type_t *aux, int size) array_type (type_t *aux, int size)
{ {