From ef9960c6f905f65400a75160cfec00620212e08f Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Sun, 1 May 2022 10:02:26 +0900 Subject: [PATCH] [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). --- tools/qfcc/include/expr.h | 14 ++++++ tools/qfcc/include/expr_names.h | 1 + tools/qfcc/include/type.h | 30 ++++++++++++ tools/qfcc/source/dot_expr.c | 37 +++++++++++++++ tools/qfcc/source/emit.c | 84 +++++++++++++++++++++++++++++++++ tools/qfcc/source/expr.c | 80 +++++++++++++++++++++++++++++++ tools/qfcc/source/expr_assign.c | 1 + tools/qfcc/source/expr_binary.c | 25 ++++++++-- tools/qfcc/source/opcodes.c | 22 ++++++++- tools/qfcc/source/statements.c | 27 +++++++++++ tools/qfcc/source/type.c | 32 +++++++++++++ 11 files changed, 347 insertions(+), 6 deletions(-) diff --git a/tools/qfcc/include/expr.h b/tools/qfcc/include/expr.h index 0f460445d..b9534086e 100644 --- a/tools/qfcc/include/expr.h +++ b/tools/qfcc/include/expr.h @@ -258,6 +258,17 @@ typedef struct { struct type_s *type; ///< result type } 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) typedef struct expr_s { @@ -293,6 +304,7 @@ typedef struct expr_s { ex_with_t with; ///< with expr param struct type_s *nil; ///< type for nil if known ex_horizontal_t hop; ///< horizontal vector operation + ex_swizzle_t swizzle; ///< vector swizzle operation } e; } 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_swizzle_expr (expr_t *src, const char *swizzle); + /** Create a new def reference (non-temporary variable) expression node. \return The new def reference expression node (::def_t). diff --git a/tools/qfcc/include/expr_names.h b/tools/qfcc/include/expr_names.h index 05d1f197a..8c6db70b6 100644 --- a/tools/qfcc/include/expr_names.h +++ b/tools/qfcc/include/expr_names.h @@ -63,5 +63,6 @@ EX_EXPR(adjstk) ///< stack adjust expression (::ex_adjstk_t) EX_EXPR(with) ///< with expression (::ex_with_t) EX_EXPR(args) ///< @args marker in parameter list. no data EX_EXPR(horizontal) ///< horizontal vector operation (::ex_horzontal_t) +EX_EXPR(swizzle) ///< vector swizzle operation (::ex_swizzle_t) ///@} diff --git a/tools/qfcc/include/type.h b/tools/qfcc/include/type.h index 69ca4bc4b..6dde9d55b 100644 --- a/tools/qfcc/include/type.h +++ b/tools/qfcc/include/type.h @@ -162,6 +162,36 @@ type_t *field_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 *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 *based_array_type (type_t *aux, int base, int top); type_t *alias_type (type_t *type, type_t *alias_chain, const char *name); diff --git a/tools/qfcc/source/dot_expr.c b/tools/qfcc/source/dot_expr.c index fdf8238e0..664796915 100644 --- a/tools/qfcc/source/dot_expr.c +++ b/tools/qfcc/source/dot_expr.c @@ -555,6 +555,41 @@ print_args (dstring_t *dstr, expr_t *e, int level, int id, expr_t *next) 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 _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_with] = print_with, [ex_args] = print_args, + [ex_horizontal] = print_horizontal, + [ex_swizzle] = print_swizzle, }; int indent = level * 2 + 2; diff --git a/tools/qfcc/source/emit.c b/tools/qfcc/source/emit.c index 30b2d1b4b..d5615a794 100644 --- a/tools/qfcc/source/emit.c +++ b/tools/qfcc/source/emit.c @@ -179,6 +179,74 @@ use_tempop (operand_t *op, expr_t *expr) 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 emit_statement (statement_t *statement) { @@ -202,12 +270,28 @@ emit_statement (statement_t *statement) op_b = statement->opb; op_c = statement->opc; } + def_a = get_operand_def (statement->expr, op_a); use_tempop (op_a, statement->expr); def_b = get_operand_def (statement->expr, op_b); use_tempop (op_b, statement->expr); def_c = get_operand_def (statement->expr, op_c); 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); if (!inst) { diff --git a/tools/qfcc/source/expr.c b/tools/qfcc/source/expr.c index db2305e83..595eb1e87 100644 --- a/tools/qfcc/source/expr.c +++ b/tools/qfcc/source/expr.c @@ -195,6 +195,8 @@ get_type (expr_t *e) return &type_va_list; case ex_horizontal: return e->e.hop.type; + case ex_swizzle: + return e->e.swizzle.type; case ex_count: internal_error (e, "invalid expression"); } @@ -425,6 +427,11 @@ copy_expr (expr_t *e) *n = *e; e->e.hop.vec = copy_expr (e->e.hop.vec); 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: break; } @@ -611,6 +618,74 @@ new_horizontal_expr (int op, expr_t *vec, type_t *type) 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 * 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); case ex_horizontal: return has_function_call (e->e.hop.vec); + case ex_swizzle: + return has_function_call (e->e.swizzle.src); case ex_error: case ex_state: case ex_label: @@ -1692,6 +1769,7 @@ unary_expr (int op, expr_t *e) case ex_alias: case ex_assign: case ex_horizontal: + case ex_swizzle: { expr_t *n = new_unary_expr (op, e); @@ -1785,6 +1863,7 @@ unary_expr (int op, expr_t *e) case ex_address: case ex_assign: case ex_horizontal: + case ex_swizzle: if (options.code.progsversion == PROG_VERSION) { return binary_expr (EQ, e, new_nil_expr ()); } else { @@ -1874,6 +1953,7 @@ unary_expr (int op, expr_t *e) case ex_alias: case ex_assign: case ex_horizontal: + case ex_swizzle: bitnot_expr: if (options.code.progsversion == PROG_ID_VERSION) { expr_t *n1 = new_int_expr (-1); diff --git a/tools/qfcc/source/expr_assign.c b/tools/qfcc/source/expr_assign.c index 1bc88faf7..b04a98b36 100644 --- a/tools/qfcc/source/expr_assign.c +++ b/tools/qfcc/source/expr_assign.c @@ -142,6 +142,7 @@ is_lvalue (const expr_t *expr) case ex_with: case ex_args: case ex_horizontal: + case ex_swizzle: break; case ex_count: internal_error (expr, "invalid expression"); diff --git a/tools/qfcc/source/expr_binary.c b/tools/qfcc/source/expr_binary.c index 454f01fdf..d2e8e59c5 100644 --- a/tools/qfcc/source/expr_binary.c +++ b/tools/qfcc/source/expr_binary.c @@ -654,13 +654,28 @@ convert_scalar (expr_t *scalar, int op, expr_t *vec) if (!*s_op) { return 0; } + // expand the scalar to a vector of the same width as vec - for (int i = 1; i < type_width (get_type (vec)); i++) { - expr_t *s = copy_expr (scalar); - s->next = scalar; - scalar = s; + type_t *vec_type = get_type (vec); + + if (scalar->type == ex_symbol || scalar->type == ex_def + || 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 * diff --git a/tools/qfcc/source/opcodes.c b/tools/qfcc/source/opcodes.c index 5c97f780c..d4413e744 100644 --- a/tools/qfcc/source/opcodes.c +++ b/tools/qfcc/source/opcodes.c @@ -41,6 +41,7 @@ #include +#include "tools/qfcc/include/diagnostic.h" #include "tools/qfcc/include/opcodes.h" #include "tools/qfcc/include/options.h" #include "tools/qfcc/include/qfcc.h" @@ -302,7 +303,26 @@ operand_width (const char *opname, operand_t *op) } 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 * rua_opcode_find (const char *name, operand_t *op_a, operand_t *op_b, operand_t *op_c) diff --git a/tools/qfcc/source/statements.c b/tools/qfcc/source/statements.c index 7d15854f6..4e5010427 100644 --- a/tools/qfcc/source/statements.c +++ b/tools/qfcc/source/statements.c @@ -1783,6 +1783,32 @@ expr_horizontal (sblock_t *sblock, expr_t *e, operand_t **op) 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 * 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_uexpr] = expr_uexpr, [ex_horizontal] = expr_horizontal, + [ex_swizzle] = expr_swizzle, [ex_def] = expr_def, [ex_symbol] = expr_symbol, [ex_temp] = expr_temp, diff --git a/tools/qfcc/source/type.c b/tools/qfcc/source/type.c index bd5d93e62..8b0901339 100644 --- a/tools/qfcc/source/type.c +++ b/tools/qfcc/source/type.c @@ -586,6 +586,38 @@ base_type (const type_t *vec_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 * array_type (type_t *aux, int size) {