From: Rudolf Polzer Date: Mon, 26 Aug 2013 08:25:29 +0000 (+0200) Subject: Support vector bitor/bitand/bitxor. X-Git-Tag: 0.3.5~147^2 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=refs%2Fheads%2FdivVerent%2Fsubmit%2Fvector-bitops;p=xonotic%2Fgmqcc.git Support vector bitor/bitand/bitxor. Signed-off-by: Rudolf Polzer --- diff --git a/fold.c b/fold.c index b1e89c3..4019794 100644 --- a/fold.c +++ b/fold.c @@ -73,6 +73,38 @@ static GMQCC_INLINE vec3_t vec3_neg(vec3_t a) { return out; } +static GMQCC_INLINE vec3_t vec3_or(vec3_t a, vec3_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b.x)); + out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b.y)); + out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b.z)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_orvf(vec3_t a, qcfloat_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) | ((qcint_t)b)); + out.y = (qcfloat_t)(((qcint_t)a.y) | ((qcint_t)b)); + out.z = (qcfloat_t)(((qcint_t)a.z) | ((qcint_t)b)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_and(vec3_t a, vec3_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b.x)); + out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b.y)); + out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b.z)); + return out; +} + +static GMQCC_INLINE vec3_t vec3_andvf(vec3_t a, qcfloat_t b) { + vec3_t out; + out.x = (qcfloat_t)(((qcint_t)a.x) & ((qcint_t)b)); + out.y = (qcfloat_t)(((qcint_t)a.y) & ((qcint_t)b)); + out.z = (qcfloat_t)(((qcint_t)a.z) & ((qcint_t)b)); + return out; +} + static GMQCC_INLINE vec3_t vec3_xor(vec3_t a, vec3_t b) { vec3_t out; out.x = (qcfloat_t)(((qcint_t)a.x) ^ ((qcint_t)b.x)); @@ -89,6 +121,14 @@ static GMQCC_INLINE vec3_t vec3_xorvf(vec3_t a, qcfloat_t b) { return out; } +static GMQCC_INLINE vec3_t vec3_not(vec3_t a) { + vec3_t out; + out.x = (qcfloat_t)(~((qcint_t)a.x)); + out.y = (qcfloat_t)(~((qcint_t)a.y)); + out.z = (qcfloat_t)(~((qcint_t)a.z)); + return out; +} + static GMQCC_INLINE qcfloat_t vec3_mulvv(vec3_t a, vec3_t b) { return (a.x * b.x + a.y * b.y + a.z * b.z); } @@ -184,6 +224,7 @@ fold_t *fold_init(parser_t *parser) { (void)fold_constgen_float (fold, -1.0f); (void)fold_constgen_vector(fold, vec3_create(0.0f, 0.0f, 0.0f)); + (void)fold_constgen_vector(fold, vec3_create(-1.0f, -1.0f, -1.0f)); return fold; } @@ -444,14 +485,34 @@ static GMQCC_INLINE ast_expression *fold_op_mod(fold_t *fold, ast_value *a, ast_ } static GMQCC_INLINE ast_expression *fold_op_bor(fold_t *fold, ast_value *a, ast_value *b) { - if (fold_can_2(a, b)) - return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) | ((qcint_t)fold_immvalue_float(b)))); + if (isfloat(a)) { + if (fold_can_2(a, b)) + return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) | ((qcint_t)fold_immvalue_float(b)))); + } else { + if (isvector(b)) { + if (fold_can_2(a, b)) + return fold_constgen_vector(fold, vec3_or(fold_immvalue_vector(a), fold_immvalue_vector(b))); + } else { + if (fold_can_2(a, b)) + return fold_constgen_vector(fold, vec3_orvf(fold_immvalue_vector(a), fold_immvalue_float(b))); + } + } return NULL; } static GMQCC_INLINE ast_expression *fold_op_band(fold_t *fold, ast_value *a, ast_value *b) { - if (fold_can_2(a, b)) - return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) & ((qcint_t)fold_immvalue_float(b)))); + if (isfloat(a)) { + if (fold_can_2(a, b)) + return fold_constgen_float(fold, (qcfloat_t)(((qcint_t)fold_immvalue_float(a)) & ((qcint_t)fold_immvalue_float(b)))); + } else { + if (isvector(b)) { + if (fold_can_2(a, b)) + return fold_constgen_vector(fold, vec3_and(fold_immvalue_vector(a), fold_immvalue_vector(b))); + } else { + if (fold_can_2(a, b)) + return fold_constgen_vector(fold, vec3_andvf(fold_immvalue_vector(a), fold_immvalue_float(b))); + } + } return NULL; } @@ -537,8 +598,15 @@ static GMQCC_INLINE ast_expression *fold_op_cmp(fold_t *fold, ast_value *a, ast_ } static GMQCC_INLINE ast_expression *fold_op_bnot(fold_t *fold, ast_value *a) { - if (fold_can_1(a)) - return fold_constgen_float(fold, ~((qcint_t)fold_immvalue_float(a))); + if (isfloat(a)) { + if (fold_can_1(a)) + return fold_constgen_float(fold, ~((qcint_t)fold_immvalue_float(a))); + } else { + if (isvector(a)) { + if (fold_can_1(a)) + return fold_constgen_vector(fold, vec3_not(fold_immvalue_vector(a))); + } + } return NULL; } diff --git a/gmqcc.h b/gmqcc.h index 36406a2..5ac1935 100644 --- a/gmqcc.h +++ b/gmqcc.h @@ -717,7 +717,16 @@ enum { * Creating this causes IR blocks to be marked as 'final'. * No-Return-Call */ - VINSTR_NRCALL + VINSTR_NRCALL, + + /* Emulated instructions. */ + VINSTR_BITAND_V, /* BITAND_V must be the first emulated bitop */ + VINSTR_BITAND_VF, + VINSTR_BITOR_V, + VINSTR_BITOR_VF, + VINSTR_BITXOR, + VINSTR_BITXOR_V, + VINSTR_BITXOR_VF /* BITXOR_VF must be the last emulated bitop */ }; /* TODO: elide */ diff --git a/ir.c b/ir.c index ee07a44..d77f011 100644 --- a/ir.c +++ b/ir.c @@ -312,6 +312,7 @@ static void ir_function_delete_quick(ir_function *self); ir_builder* ir_builder_new(const char *modulename) { ir_builder* self; + size_t i; self = (ir_builder*)mem_a(sizeof(*self)); if (!self) @@ -344,6 +345,15 @@ ir_builder* ir_builder_new(const char *modulename) self->nil = ir_value_var("nil", store_value, TYPE_NIL); self->nil->cvq = CV_CONST; + for (i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) { + /* we write to them, but they're not supposed to be used outside the IR, so + * let's not allow the generation of ir_instrs which use these. + * So it's a constant noexpr. + */ + self->vinstr_temp[i] = ir_value_var("vinstr_temp", store_value, TYPE_NOEXPR); + self->vinstr_temp[i]->cvq = CV_CONST; + } + self->reserved_va_count = NULL; self->code = code_init(); @@ -374,6 +384,9 @@ void ir_builder_delete(ir_builder* self) ir_value_delete(self->fields[i]); } ir_value_delete(self->nil); + for (i = 0; i != IR_MAX_VINSTR_TEMPS; ++i) { + ir_value_delete(self->vinstr_temp[i]); + } vec_free(self->fields); vec_free(self->filenames); vec_free(self->filestrings); @@ -599,7 +612,8 @@ static bool instr_is_operation(uint16_t op) (op == INSTR_ADDRESS) || (op >= INSTR_NOT_F && op <= INSTR_NOT_FNC) || (op >= INSTR_AND && op <= INSTR_BITOR) || - (op >= INSTR_CALL0 && op <= INSTR_CALL8) ); + (op >= INSTR_CALL0 && op <= INSTR_CALL8) || + (op >= VINSTR_BITAND_V && op <= VINSTR_BITXOR_VF) ); } static bool ir_function_pass_peephole(ir_function *self) @@ -628,6 +642,7 @@ static bool ir_function_pass_peephole(ir_function *self) if (!instr_is_operation(oper->opcode)) continue; + /* Old engine's mul for vector+float cannot deal with aliased inputs. */ if (OPTS_FLAG(LEGACY_VECTOR_MATHS)) { if (oper->opcode == INSTR_MUL_VF && oper->_ops[2]->memberof == oper->_ops[1]) continue; @@ -635,6 +650,18 @@ static bool ir_function_pass_peephole(ir_function *self) continue; } + /* Emulated bitxor cannot deal with aliased inputs. */ + if (oper->opcode == VINSTR_BITXOR && oper->_ops[2]->memberof == oper->_ops[1]) + continue; + + /* Emulated bitand/bitor for vector+float cannot deal with aliased inputs. */ + if (oper->opcode == VINSTR_BITAND_VF && oper->_ops[2]->memberof == oper->_ops[1]) + continue; + if (oper->opcode == VINSTR_BITOR_VF && oper->_ops[2]->memberof == oper->_ops[1]) + continue; + if (oper->opcode == VINSTR_BITXOR_VF && oper->_ops[2]->memberof == oper->_ops[1]) + continue; + value = oper->_ops[0]; /* only do it for SSA values */ @@ -1019,6 +1046,11 @@ static void ir_instr_delete(ir_instr *self) static bool ir_instr_op(ir_instr *self, int op, ir_value *v, bool writing) { + if (v && v->vtype == TYPE_NOEXPR) { + irerror(self->context, "tried to use a NOEXPR value"); + return false; + } + if (self->_ops[op]) { size_t idx; if (writing && vec_ir_instr_find(self->_ops[op]->writes, self, &idx)) @@ -1752,6 +1784,7 @@ ir_value* ir_block_create_binop(ir_block *self, lex_ctx_t ctx, #endif case INSTR_BITAND: case INSTR_BITOR: + case VINSTR_BITXOR: #if 0 case INSTR_SUB_S: /* -- offset of string as float */ case INSTR_MUL_IF: @@ -1788,6 +1821,12 @@ ir_value* ir_block_create_binop(ir_block *self, lex_ctx_t ctx, case INSTR_SUB_V: case INSTR_MUL_VF: case INSTR_MUL_FV: + case VINSTR_BITAND_V: + case VINSTR_BITOR_V: + case VINSTR_BITXOR_V: + case VINSTR_BITAND_VF: + case VINSTR_BITOR_VF: + case VINSTR_BITXOR_VF: #if 0 case INSTR_DIV_VF: case INSTR_MUL_IV: @@ -2483,7 +2522,15 @@ static bool ir_block_life_propagate(ir_block *self, bool *changed) } } - if (instr->opcode == INSTR_MUL_VF) + /* These operations need a special case as they can break when using + * same source and destination operand otherwise, as the engine may + * read the source multiple times. */ + if (instr->opcode == INSTR_MUL_VF || + instr->opcode == VINSTR_BITXOR || + instr->opcode == VINSTR_BITAND_VF || + instr->opcode == VINSTR_BITOR_VF || + instr->opcode == VINSTR_BITXOR_VF || + instr->opcode == VINSTR_BITXOR_V) { value = instr->_ops[2]; /* the float source will get an additional lifetime */ @@ -2748,6 +2795,7 @@ static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *bloc ir_block *onfalse; size_t stidx; size_t i; + int j; block->generated = true; block->code_start = vec_size(code->statements); @@ -2780,6 +2828,145 @@ static bool gen_blocks_recursive(code_t *code, ir_function *func, ir_block *bloc return true; } + if (instr->opcode == VINSTR_BITXOR) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context.line); + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); + code_push_statement(code, &stmt, instr->context.line); + stmt.opcode = INSTR_SUB_F; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); + stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context.line); + + /* instruction generated */ + continue; + } + + if (instr->opcode == VINSTR_BITAND_V) { + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context.line); + ++stmt.o1.s1; + ++stmt.o2.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context.line); + ++stmt.o1.s1; + ++stmt.o2.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context.line); + + /* instruction generated */ + continue; + } + + if (instr->opcode == VINSTR_BITOR_V) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context.line); + ++stmt.o1.s1; + ++stmt.o2.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context.line); + ++stmt.o1.s1; + ++stmt.o2.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context.line); + + /* instruction generated */ + continue; + } + + if (instr->opcode == VINSTR_BITXOR_V) { + for (j = 0; j < 3; ++j) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + j; + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j; + code_push_statement(code, &stmt, instr->context.line); + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]) + j; + stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j; + code_push_statement(code, &stmt, instr->context.line); + } + stmt.opcode = INSTR_SUB_V; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); + stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context.line); + + /* instruction generated */ + continue; + } + + if (instr->opcode == VINSTR_BITAND_VF) { + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context.line); + ++stmt.o1.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context.line); + ++stmt.o1.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context.line); + + /* instruction generated */ + continue; + } + + if (instr->opcode == VINSTR_BITOR_VF) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]); + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context.line); + ++stmt.o1.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context.line); + ++stmt.o1.s1; + ++stmt.o3.s1; + code_push_statement(code, &stmt, instr->context.line); + + /* instruction generated */ + continue; + } + + if (instr->opcode == VINSTR_BITXOR_VF) { + for (j = 0; j < 3; ++j) { + stmt.opcode = INSTR_BITOR; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]) + j; + code_push_statement(code, &stmt, instr->context.line); + stmt.opcode = INSTR_BITAND; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[1]) + j; + stmt.o2.s1 = ir_value_code_addr(instr->_ops[2]); + stmt.o3.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]) + j; + code_push_statement(code, &stmt, instr->context.line); + } + stmt.opcode = INSTR_SUB_V; + stmt.o1.s1 = ir_value_code_addr(instr->_ops[0]); + stmt.o2.s1 = ir_value_code_addr(func->owner->vinstr_temp[0]); + stmt.o3.s1 = ir_value_code_addr(instr->_ops[0]); + code_push_statement(code, &stmt, instr->context.line); + + /* instruction generated */ + continue; + } + if (instr->opcode == VINSTR_COND) { ontrue = instr->bops[0]; onfalse = instr->bops[1]; @@ -3652,6 +3839,14 @@ bool ir_builder_generate(ir_builder *self, const char *filename) vec_push(self->code->globals, 0); vec_push(self->code->globals, 0); + /* generate virtual-instruction temps */ + for (i = 0; i < IR_MAX_VINSTR_TEMPS; ++i) { + ir_value_code_setaddr(self->vinstr_temp[i], vec_size(self->code->globals)); + vec_push(self->code->globals, 0); + vec_push(self->code->globals, 0); + vec_push(self->code->globals, 0); + } + /* generate global temps */ self->first_common_globaltemp = vec_size(self->code->globals); for (i = 0; i < self->max_globaltemps; ++i) { @@ -3734,11 +3929,18 @@ static const char *qc_opname(int op) if (op < VINSTR_END) return util_instr_str[op]; switch (op) { - case VINSTR_END: return "END"; - case VINSTR_PHI: return "PHI"; - case VINSTR_JUMP: return "JUMP"; - case VINSTR_COND: return "COND"; - default: return ""; + case VINSTR_END: return "END"; + case VINSTR_PHI: return "PHI"; + case VINSTR_JUMP: return "JUMP"; + case VINSTR_COND: return "COND"; + case VINSTR_BITXOR: return "BITXOR"; + case VINSTR_BITAND_V: return "BITAND_V"; + case VINSTR_BITOR_V: return "BITOR_V"; + case VINSTR_BITXOR_V: return "BITXOR_V"; + case VINSTR_BITAND_VF: return "BITAND_VF"; + case VINSTR_BITOR_VF: return "BITOR_VF"; + case VINSTR_BITXOR_VF: return "BITXOR_VF"; + default: return ""; } } diff --git a/ir.h b/ir.h index b62b09d..46ac464 100644 --- a/ir.h +++ b/ir.h @@ -240,6 +240,7 @@ ir_block* ir_function_create_block(lex_ctx_t ctx, ir_function*, const char /* builder */ #define IR_HT_SIZE 1024 +#define IR_MAX_VINSTR_TEMPS 1 typedef struct ir_builder_s { char *name; @@ -261,12 +262,16 @@ typedef struct ir_builder_s uint32_t first_common_globaltemp; const char **filenames; - qcint_t *filestrings; + qcint_t *filestrings; /* we cache the #IMMEDIATE string here */ - qcint_t str_immediate; + qcint_t str_immediate; /* there should just be this one nil */ ir_value *nil; ir_value *reserved_va_count; + /* some virtual instructions require temps, and their code is isolated + * so that we don't need to keep track of their liveness. + */ + ir_value *vinstr_temp[IR_MAX_VINSTR_TEMPS]; /* code generator */ code_t *code; diff --git a/parser.c b/parser.c index a2ab3ca..9ed8043 100644 --- a/parser.c +++ b/parser.c @@ -623,55 +623,7 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy) case opid1('|'): case opid1('&'): - if (NotSameType(TYPE_FLOAT)) { - compile_error(ctx, "invalid types used in expression: cannot perform bit operations between types %s and %s", - type_name[exprs[0]->vtype], - type_name[exprs[1]->vtype]); - return false; - } - if (!(out = fold_op(parser->fold, op, exprs))) - out = (ast_expression*)ast_binary_new(ctx, - (op->id == opid1('|') ? INSTR_BITOR : INSTR_BITAND), - exprs[0], exprs[1]); - break; case opid1('^'): - /* - * Okay lets designate what the hell is an acceptable use - * of the ^ operator. In many vector processing units, XOR - * is allowed to be used on vectors, but only if the first - * operand is a vector, the second operand can be a float - * or vector. It's never legal for the first operand to be - * a float, and then the following operand to be a vector. - * Further more, the only time it is legal to do XOR otherwise - * is when both operand are floats. This nicely crafted if - * statement catches them all. - * - * In the event that the first operand is a vector, two - * possible situations can arise, thus, each element of - * vector A (operand A) is exclusive-ORed with the corresponding - * element of vector B (operand B), If B is scalar, the - * scalar value is first replicated for each element. - * - * The QCVM itself lacks a BITXOR instruction. Thus emulating - * the mathematics of it is required. The following equation - * is used: (LHS | RHS) & ~(LHS & RHS). However, due to the - * QCVM also lacking a BITNEG instruction, we need to emulate - * ~FOO with -1 - FOO, the whole process becoming this nicely - * crafted expression: (LHS | RHS) & (-1 - (LHS & RHS)). - * - * When A is not scalar, this process is repeated for all - * components of vector A with the value in operand B, - * only if operand B is scalar. When A is not scalar, and B - * is also not scalar, this process is repeated for all - * components of the vector A with the components of vector B. - * Finally when A is scalar and B is scalar, this process is - * simply used once for A and B being LHS and RHS respectfully. - * - * Yes the semantics are a bit strange (no pun intended). - * But then again BITXOR is strange itself, consdering it's - * commutative, assocative, and elements of the BITXOR operation - * are their own inverse. - */ if ( !(exprs[0]->vtype == TYPE_FLOAT && exprs[1]->vtype == TYPE_FLOAT) && !(exprs[0]->vtype == TYPE_VECTOR && exprs[1]->vtype == TYPE_FLOAT) && !(exprs[0]->vtype == TYPE_VECTOR && exprs[1]->vtype == TYPE_VECTOR)) @@ -688,46 +640,26 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy) * since scalar ^ vector is not allowed. */ if (exprs[0]->vtype == TYPE_FLOAT) { - ast_binary *expr = ast_binary_new( - ctx, - INSTR_SUB_F, - (ast_expression*)parser->fold->imm_float[2], - (ast_expression*)ast_binary_new( - ctx, - INSTR_BITAND, - exprs[0], - exprs[1] - ) - ); - expr->refs = AST_REF_NONE; - - out = (ast_expression*) - ast_binary_new( - ctx, - INSTR_BITAND, - (ast_expression*)ast_binary_new( - ctx, - INSTR_BITOR, - exprs[0], - exprs[1] - ), - (ast_expression*)expr - ); + out = (ast_expression*)ast_binary_new(ctx, + (op->id == opid1('^') ? VINSTR_BITXOR : op->id == opid1('|') ? INSTR_BITOR : INSTR_BITAND), + exprs[0], exprs[1]); } else { /* - * The first is a vector: vector is allowed to xor with vector and + * The first is a vector: vector is allowed to bitop with vector and * with scalar, branch here for the second operand. */ if (exprs[1]->vtype == TYPE_VECTOR) { /* - * Xor all the values of the vector components against the + * Bitop all the values of the vector components against the * vectors components in question. */ - compile_error(ast_ctx(exprs[0]), "Not Yet Implemented: bit-xor for vector against vector"); - return false; + out = (ast_expression*)ast_binary_new(ctx, + (op->id == opid1('^') ? VINSTR_BITXOR_V : op->id == opid1('|') ? VINSTR_BITOR_V : VINSTR_BITAND_V), + exprs[0], exprs[1]); } else { - compile_error(ast_ctx(exprs[0]), "Not Yet Implemented: bit-xor for vector against float"); - return false; + out = (ast_expression*)ast_binary_new(ctx, + (op->id == opid1('^') ? VINSTR_BITXOR_VF : op->id == opid1('|') ? VINSTR_BITOR_VF : VINSTR_BITAND_VF), + exprs[0], exprs[1]); } } } @@ -1096,7 +1028,8 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy) break; case opid2('&','='): case opid2('|','='): - if (NotSameType(TYPE_FLOAT)) { + case opid2('^','='): + if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) { ast_type_to_string(exprs[0], ty1, sizeof(ty1)); ast_type_to_string(exprs[1], ty2, sizeof(ty2)); compile_error(ctx, "invalid types used in expression: %s and %s", @@ -1110,16 +1043,21 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy) assignop = type_storep_instr[exprs[0]->vtype]; else assignop = type_store_instr[exprs[0]->vtype]; - out = (ast_expression*)ast_binstore_new(ctx, assignop, - (op->id == opid2('&','=') ? INSTR_BITAND : INSTR_BITOR), - exprs[0], exprs[1]); + if (exprs[0]->vtype == TYPE_FLOAT) + out = (ast_expression*)ast_binstore_new(ctx, assignop, + (op->id == opid2('^','=') ? VINSTR_BITXOR : op->id == opid2('&','=') ? INSTR_BITAND : INSTR_BITOR), + exprs[0], exprs[1]); + else + out = (ast_expression*)ast_binstore_new(ctx, assignop, + (op->id == opid2('^','=') ? VINSTR_BITXOR_V : op->id == opid2('&','=') ? VINSTR_BITAND_V : VINSTR_BITOR_V), + exprs[0], exprs[1]); break; case opid3('&','~','='): /* This is like: a &= ~(b); * But QC has no bitwise-not, so we implement it as * a -= a & (b); */ - if (NotSameType(TYPE_FLOAT)) { + if (NotSameType(TYPE_FLOAT) && NotSameType(TYPE_VECTOR)) { ast_type_to_string(exprs[0], ty1, sizeof(ty1)); ast_type_to_string(exprs[1], ty2, sizeof(ty2)); compile_error(ctx, "invalid types used in expression: %s and %s", @@ -1130,25 +1068,36 @@ static bool parser_sy_apply_operator(parser_t *parser, shunt *sy) assignop = type_storep_instr[exprs[0]->vtype]; else assignop = type_store_instr[exprs[0]->vtype]; - out = (ast_expression*)ast_binary_new(ctx, INSTR_BITAND, exprs[0], exprs[1]); + if (exprs[0]->vtype == TYPE_FLOAT) + out = (ast_expression*)ast_binary_new(ctx, INSTR_BITAND, exprs[0], exprs[1]); + else + out = (ast_expression*)ast_binary_new(ctx, VINSTR_BITAND_V, exprs[0], exprs[1]); if (!out) return false; if (ast_istype(exprs[0], ast_value) && asvalue[0]->cvq == CV_CONST) { compile_error(ctx, "assignment to constant `%s`", asvalue[0]->name); } - asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_F, exprs[0], out); + if (exprs[0]->vtype == TYPE_FLOAT) + asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_F, exprs[0], out); + else + asbinstore = ast_binstore_new(ctx, assignop, INSTR_SUB_V, exprs[0], out); asbinstore->keep_dest = true; out = (ast_expression*)asbinstore; break; case opid2('~', 'P'): - if (exprs[0]->vtype != TYPE_FLOAT) { + if (exprs[0]->vtype != TYPE_FLOAT && exprs[0]->vtype != TYPE_VECTOR) { ast_type_to_string(exprs[0], ty1, sizeof(ty1)); compile_error(ast_ctx(exprs[0]), "invalid type for bit not: %s", ty1); return false; } - if (!(out = fold_op(parser->fold, op, exprs))) - out = (ast_expression*)ast_binary_new(ctx, INSTR_SUB_F, (ast_expression*)parser->fold->imm_float[2], exprs[0]); + if (!(out = fold_op(parser->fold, op, exprs))) { + if (exprs[0]->vtype == TYPE_FLOAT) { + out = (ast_expression*)ast_binary_new(ctx, INSTR_SUB_F, (ast_expression*)parser->fold->imm_float[2], exprs[0]); + } else { + out = (ast_expression*)ast_binary_new(ctx, INSTR_SUB_V, (ast_expression*)parser->fold->imm_vector[1], exprs[0]); + } + } break; } #undef NotSameType diff --git a/tests/bitnot.qc b/tests/bitnot.qc index c6e875d..17ba6cd 100644 --- a/tests/bitnot.qc +++ b/tests/bitnot.qc @@ -3,12 +3,18 @@ void main() { float b; b = 1; float c; c = 1; float d; d = 1; + vector e; e = '1 1 1'; + vector f; f = '1 1 1'; a &~= 1; // 0 b &= ~1; // 0 c &= ~d; // 0 + f &~= e; // '0 0 0' + e &= ~e; // '0 0 0' print("a: ", ftos(a), "\nb: ", ftos(b), "\nc: ", ftos(c), "\n"); + print("e: ", vtos(e), "\n"); + print("f: ", vtos(f), "\n"); } diff --git a/tests/bitnot.tmpl b/tests/bitnot.tmpl index 75ae64a..b6a1eaa 100644 --- a/tests/bitnot.tmpl +++ b/tests/bitnot.tmpl @@ -7,3 +7,5 @@ E: $null M: a: 0 M: b: 0 M: c: 0 +M: e: '0 0 0' +M: f: '0 0 0' diff --git a/tests/vec_ops.qc b/tests/vec_ops.qc index ac84ffc..509dc3a 100644 --- a/tests/vec_ops.qc +++ b/tests/vec_ops.qc @@ -4,4 +4,8 @@ void main(vector v) { print(vtos(v), "\n"); print(vtos(v / 2), "\n"); print(vtos(v), "\n"); + print(vtos(v | 16), "\n"); + print(vtos(v & 16), "\n"); + print(vtos(v | '25 42 51'), "\n"); + print(vtos(v & '25 42 51'), "\n"); } diff --git a/tests/vec_ops.tmpl b/tests/vec_ops.tmpl index 7d8a501..7943232 100644 --- a/tests/vec_ops.tmpl +++ b/tests/vec_ops.tmpl @@ -7,3 +7,7 @@ M: '8 16 32' M: '4 8 16' M: '2 4 8' M: '4 8 16' +M: '20 24 16' +M: '0 0 16' +M: '29 42 51' +M: '0 8 16' diff --git a/tests/xor.qc b/tests/xor.qc index 41d02e9..531a5fb 100644 --- a/tests/xor.qc +++ b/tests/xor.qc @@ -38,6 +38,12 @@ void main() { // vector ^ vector // vector ^ float // are legal in constant expressions (currently) + vector v1 = '5 2 5'; + vector v2 = '3 10 3'; + + print("vv: ", vtos(v1 ^ v2), "\n"); + print("vf: ", vtos(v1 ^ 10), "\n"); + const vector v3 = '5 2 5' ^ '3 10 3'; const vector v4 = '5 2 5' ^ 10; @@ -49,4 +55,12 @@ void main() { float swap_y = 200; vector swaps = swap(swap_x, swap_y); print("100:200 swapped is: ", ftos(swaps.x), ":", ftos(swaps.y), "\n"); + + // good olde xor swap test too + vector swap_u = '1 2 3'; + vector swap_v = '4 5 6'; + swap_u ^= swap_v; + swap_v ^= swap_u; + swap_u ^= swap_v; + print("'1 2 3':'4 5 6' swapped is: ", vtos(swap_u), ":", vtos(swap_v), "\n"); } diff --git a/tests/xor.tmpl b/tests/xor.tmpl index 5394f3d..a08d824 100644 --- a/tests/xor.tmpl +++ b/tests/xor.tmpl @@ -10,4 +10,7 @@ M: assocative M: inverse M: vv: '6 8 6' M: vf: '15 8 15' +M: vv: '6 8 6' +M: vf: '15 8 15' M: 100:200 swapped is: 200:100 +M: '1 2 3':'4 5 6' swapped is: '4 5 6':'1 2 3'