[3/5,RISC-V] Generate Zicond instruction for select pattern with condition eq or neq to 0
Checks
Commit Message
This patch completes the recognition of Zicond when the select pattern
with condition eq or neq to 0 (using equality as an example), namely:
1 rd = (rs2 == 0) ? non-imm : 0
2 rd = (rs2 == 0) ? non-imm : non-imm
3 rd = (rs2 == 0) ? reg : non-imm
4 rd = (rs2 == 0) ? reg : reg
gcc/ChangeLog:
* config/riscv/riscv.cc (riscv_rtx_costs): IF_THEN_ELSE costs in Zicond.
(riscv_expand_conditional_move): Recognize Zicond.
* config/riscv/riscv.md: Zicond patterns.
gcc/testsuite/ChangeLog:
* gcc.target/riscv/zicond-primitiveSemantics_return_0_imm.c: New test.
* gcc.target/riscv/zicond-primitiveSemantics_return_imm_imm.c: New test.
* gcc.target/riscv/zicond-primitiveSemantics_return_imm_reg.c: New test.
* gcc.target/riscv/zicond-primitiveSemantics_return_reg_reg.c: New test.
---
gcc/config/riscv/riscv.cc | 125 ++++++++++++++++++
gcc/config/riscv/riscv.md | 2 +-
.../zicond-primitiveSemantics_return_0_imm.c | 65 +++++++++
...zicond-primitiveSemantics_return_imm_imm.c | 73 ++++++++++
...zicond-primitiveSemantics_return_imm_reg.c | 65 +++++++++
...zicond-primitiveSemantics_return_reg_reg.c | 65 +++++++++
6 files changed, 394 insertions(+), 1 deletion(-)
create mode 100644 gcc/testsuite/gcc.target/riscv/zicond-primitiveSemantics_return_0_imm.c
create mode 100644 gcc/testsuite/gcc.target/riscv/zicond-primitiveSemantics_return_imm_imm.c
create mode 100644 gcc/testsuite/gcc.target/riscv/zicond-primitiveSemantics_return_imm_reg.c
create mode 100644 gcc/testsuite/gcc.target/riscv/zicond-primitiveSemantics_return_reg_reg.c
Comments
On 7/19/23 04:11, Xiao Zeng wrote:
> This patch completes the recognition of Zicond when the select pattern
> with condition eq or neq to 0 (using equality as an example), namely:
>
> 1 rd = (rs2 == 0) ? non-imm : 0
> 2 rd = (rs2 == 0) ? non-imm : non-imm
> 3 rd = (rs2 == 0) ? reg : non-imm
> 4 rd = (rs2 == 0) ? reg : reg
>
> gcc/ChangeLog:
>
> * config/riscv/riscv.cc (riscv_rtx_costs): IF_THEN_ELSE costs in Zicond.
> (riscv_expand_conditional_move): Recognize Zicond.
> * config/riscv/riscv.md: Zicond patterns.
>
> gcc/testsuite/ChangeLog:
>
> * gcc.target/riscv/zicond-primitiveSemantics_return_0_imm.c: New test.
> * gcc.target/riscv/zicond-primitiveSemantics_return_imm_imm.c: New test.
> * gcc.target/riscv/zicond-primitiveSemantics_return_imm_reg.c: New test.
> * gcc.target/riscv/zicond-primitiveSemantics_return_reg_reg.c: New test.
> ---
> gcc/config/riscv/riscv.cc | 125 ++++++++++++++++++
> gcc/config/riscv/riscv.md | 2 +-
> .../zicond-primitiveSemantics_return_0_imm.c | 65 +++++++++
> ...zicond-primitiveSemantics_return_imm_imm.c | 73 ++++++++++
> ...zicond-primitiveSemantics_return_imm_reg.c | 65 +++++++++
> ...zicond-primitiveSemantics_return_reg_reg.c | 65 +++++++++
> 6 files changed, 394 insertions(+), 1 deletion(-)
> create mode 100644 gcc/testsuite/gcc.target/riscv/zicond-primitiveSemantics_return_0_imm.c
> create mode 100644 gcc/testsuite/gcc.target/riscv/zicond-primitiveSemantics_return_imm_imm.c
> create mode 100644 gcc/testsuite/gcc.target/riscv/zicond-primitiveSemantics_return_imm_reg.c
> create mode 100644 gcc/testsuite/gcc.target/riscv/zicond-primitiveSemantics_return_reg_reg.c
>
> diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
> index 38d8eb2fcf5..7e6b24bd232 100644
> --- a/gcc/config/riscv/riscv.cc
> +++ b/gcc/config/riscv/riscv.cc
> @@ -2448,6 +2448,17 @@ riscv_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno ATTRIBUTE_UN
> *total = COSTS_N_INSNS (1);
> return true;
> }
> + else if (TARGET_ZICOND && outer_code == SET &&
> + ((GET_CODE (XEXP (x, 1)) == REG && XEXP (x, 2) == const0_rtx) ||
> + (GET_CODE (XEXP (x, 2)) == REG && XEXP (x, 1) == const0_rtx) ||
> + (GET_CODE (XEXP (x, 1)) == REG && GET_CODE (XEXP (x, 2)) &&
> + XEXP (x, 1) == XEXP (XEXP (x, 0), 0)) ||
> + (GET_CODE (XEXP (x, 1)) == REG && GET_CODE (XEXP (x, 2)) &&
> + XEXP (x, 2) == XEXP (XEXP (x, 0), 0))))
> + {
> + *total = 0;
> + return true;
> + }
So why *total = 0. I would have expected *total = COSTS_N_INSNS (1).
I'm not entirely sure the changes to riscv_expand_conditional_move are
desirable -- these are likely better done in the generic if-conversion
pass.
> diff --git a/gcc/config/riscv/riscv.md b/gcc/config/riscv/riscv.md
> index 6b8c2e8e268..b4147c7a79c 100644
> --- a/gcc/config/riscv/riscv.md
> +++ b/gcc/config/riscv/riscv.md
> @@ -2484,7 +2484,7 @@
> (if_then_else:GPR (match_operand 1 "comparison_operator")
> (match_operand:GPR 2 "reg_or_0_operand")
> (match_operand:GPR 3 "sfb_alu_operand")))]
> - "TARGET_SFB_ALU || TARGET_XTHEADCONDMOV"
> + "TARGET_SFB_ALU || TARGET_XTHEADCONDMOV || TARGET_ZICOND"
> {
> if (riscv_expand_conditional_move (operands[0], operands[1],
> operands[2], operands[3]))
We had to do more than just slap on a TARGET_ZICOND. I'm a bit
surprised this worked as-is. Though we also have bits to handle
conditions other than eq/ne by first emitting an sCC style insn which
might be adding complication or cases you hadn't encountered.
Jeff
On Jul 19 2023, Xiao Zeng wrote:
> diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
> index 38d8eb2fcf5..7e6b24bd232 100644
> --- a/gcc/config/riscv/riscv.cc
> +++ b/gcc/config/riscv/riscv.cc
> @@ -2448,6 +2448,17 @@ riscv_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno ATTRIBUTE_UN
> *total = COSTS_N_INSNS (1);
> return true;
> }
> + else if (TARGET_ZICOND && outer_code == SET &&
> + ((GET_CODE (XEXP (x, 1)) == REG && XEXP (x, 2) == const0_rtx) ||
> + (GET_CODE (XEXP (x, 2)) == REG && XEXP (x, 1) == const0_rtx) ||
> + (GET_CODE (XEXP (x, 1)) == REG && GET_CODE (XEXP (x, 2)) &&
> + XEXP (x, 1) == XEXP (XEXP (x, 0), 0)) ||
> + (GET_CODE (XEXP (x, 1)) == REG && GET_CODE (XEXP (x, 2)) &&
> + XEXP (x, 2) == XEXP (XEXP (x, 0), 0))))
Line breaks before the operator, not after.
On Wed, Jul 26, 2023 at 01:55:00 AM Andreas Schwab <schwab@linux-m68k.org> wrote:
>
>On Jul 19 2023, Xiao Zeng wrote:
>
>> diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
>> index 38d8eb2fcf5..7e6b24bd232 100644
>> --- a/gcc/config/riscv/riscv.cc
>> +++ b/gcc/config/riscv/riscv.cc
>> @@ -2448,6 +2448,17 @@ riscv_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno ATTRIBUTE_UN
>> *total = COSTS_N_INSNS (1);
>> return true;
>> }
>> + else if (TARGET_ZICOND && outer_code == SET &&
>> + ((GET_CODE (XEXP (x, 1)) == REG && XEXP (x, 2) == const0_rtx) ||
>> + (GET_CODE (XEXP (x, 2)) == REG && XEXP (x, 1) == const0_rtx) ||
>> + (GET_CODE (XEXP (x, 1)) == REG && GET_CODE (XEXP (x, 2)) &&
>> + XEXP (x, 1) == XEXP (XEXP (x, 0), 0)) ||
>> + (GET_CODE (XEXP (x, 1)) == REG && GET_CODE (XEXP (x, 2)) &&
>> + XEXP (x, 2) == XEXP (XEXP (x, 0), 0))))
>
>Line breaks before the operator, not after.
>
>--
>Andreas Schwab, schwab@linux-m68k.org
>GPG Key fingerprint = 7578 EB47 D4E5 4D69 2510 2552 DF73 E780 A9DA AEC1
>"And now for something completely different."
Thank you for pointing out the code format issue. I will fix it in the future patch.
On 7/25/23 11:55, Andreas Schwab wrote:
> On Jul 19 2023, Xiao Zeng wrote:
>
>> diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
>> index 38d8eb2fcf5..7e6b24bd232 100644
>> --- a/gcc/config/riscv/riscv.cc
>> +++ b/gcc/config/riscv/riscv.cc
>> @@ -2448,6 +2448,17 @@ riscv_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno ATTRIBUTE_UN
>> *total = COSTS_N_INSNS (1);
>> return true;
>> }
>> + else if (TARGET_ZICOND && outer_code == SET &&
>> + ((GET_CODE (XEXP (x, 1)) == REG && XEXP (x, 2) == const0_rtx) ||
>> + (GET_CODE (XEXP (x, 2)) == REG && XEXP (x, 1) == const0_rtx) ||
>> + (GET_CODE (XEXP (x, 1)) == REG && GET_CODE (XEXP (x, 2)) &&
>> + XEXP (x, 1) == XEXP (XEXP (x, 0), 0)) ||
>> + (GET_CODE (XEXP (x, 1)) == REG && GET_CODE (XEXP (x, 2)) &&
>> + XEXP (x, 2) == XEXP (XEXP (x, 0), 0))))
>
> Line breaks before the operator, not after.
Also note that && GET_CODE (XEXP (x, 2)) && that appears twice.
That just verifies the code isn't RTX_UNKNOWN which I suspect isn't what
the author intended. It probably needs to be adjusted for SUBREGs and
the pointer equality issues with REGs after reload.
I'll take care of these goofs since the costing ought to be able to move
forward independently of the improvements Xiao made to generating
conditional move sequences.
Jeff
On 7/19/23 04:11, Xiao Zeng wrote:
> + else if (TARGET_ZICOND
> + && (code == EQ || code == NE)
> + && GET_MODE_CLASS (mode) == MODE_INT)
> + {
> + need_eq_ne_p = true;
> + /* 0 + imm */
> + if (GET_CODE (cons) == CONST_INT && cons == const0_rtx
> + && GET_CODE (alt) == CONST_INT && alt != const0_rtx)
> + {
> + riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
> + rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
> + alt = force_reg (mode, alt);
> + emit_insn (gen_rtx_SET (dest,
> + gen_rtx_IF_THEN_ELSE (mode, cond,
> + cons, alt)));
> + return true;
> + }
> + /* imm + imm */
> + else if (GET_CODE (cons) == CONST_INT && cons != const0_rtx
> + && GET_CODE (alt) == CONST_INT && alt != const0_rtx)
> + {
> + riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
> + rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
> + alt = force_reg (mode, alt);
> + rtx temp1 = gen_reg_rtx (mode);
> + rtx temp2 = GEN_INT(-1 * INTVAL (cons));
> + riscv_emit_binary(PLUS, temp1, alt, temp2);
So in this sequence you're just computing a constant since both ALT and
CONS are constants. It's better to just form the constant directly,
then force that into a register because it'll make the costing more
correct, particularly if the resulting constant needs more than one
instruction to synthesize.
And a nit. There should always be a space between a function name and
its argument list.
> + emit_insn (gen_rtx_SET (dest,
> + gen_rtx_IF_THEN_ELSE (mode, cond,
> + const0_rtx, alt)));
> + riscv_emit_binary(PLUS, dest, dest, cons);
> + return true;
I don't see how this can be correct from a code generation standpoint.
You compute ALT-CONS into TEMP1 earlier. But you never use TEMP1 after
that. I think you meant to use TEMP1 instead of ALT as the false arm if
the IF-THEN-ELSE you constructed.
In general you should be using CONST0_RTX (mode) rather than const0_rtx.
> + }
> + /* imm + reg */
> + else if (GET_CODE (cons) == CONST_INT && cons != const0_rtx
> + && GET_CODE (alt) == REG)
> + {
> + /* Optimize for register value of 0. */
> + if (op0 == alt && op1 == const0_rtx)
> + {
> + rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
> + cons = force_reg (mode, cons);
> + emit_insn (gen_rtx_SET (dest,
> + gen_rtx_IF_THEN_ELSE (mode, cond,
> + cons, alt)));
> + return true;
> + }
> + riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
> + rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
> + rtx temp1 = gen_reg_rtx (mode);
> + rtx temp2 = GEN_INT(-1 * INTVAL (cons));
> + riscv_emit_binary(PLUS, temp1, alt, temp2);
Here you have to be careful if CONS is -2048. You negate it resulting
in +2048 which can't be used in an addi. This will cause the entire
sequence to fail due to an unrecognized insn. It would be better to
handle that scenario directly so the generated sequence is still valid.
By generating recognizable code in that case we let the costing model
determine if the conditional move sequence is better than the branching
sequence.
> + emit_insn (gen_rtx_SET (dest,
> + gen_rtx_IF_THEN_ELSE (mode, cond,
> + const0_rtx, alt)));
I think we have the same problem with the use of ALT here rather than
TEMP1 that we had in the previous case.
> + /* reg + imm */
> + else if (GET_CODE (cons) == REG
> + && GET_CODE (alt) == CONST_INT && alt != const0_rtx)
> + {
> + /* Optimize for register value of 0. */
> + if (op0 == cons && op1 == const0_rtx)
> + {
> + rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
> + alt = force_reg (mode, alt);
> + emit_insn (gen_rtx_SET (dest,
> + gen_rtx_IF_THEN_ELSE (mode, cond,
> + cons, alt)));
> + return true;
> + }
> + riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
> + rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
> + rtx temp1 = gen_reg_rtx (mode);
> + rtx temp2 = GEN_INT(-1 * INTVAL (alt));
> + riscv_emit_binary(PLUS, temp1, cons, temp2);
> + emit_insn (gen_rtx_SET (dest,
> + gen_rtx_IF_THEN_ELSE (mode, cond,
> + temp1, const0_rtx)));
> + riscv_emit_binary(PLUS, dest, dest, alt);
> + return true;
This has basically the same issues as the imm + reg case.
> + }
> + /* reg + reg */
> + else if (GET_CODE (cons) == REG && GET_CODE (alt) == REG)
> + {
> + rtx reg1 = gen_reg_rtx (mode);
> + rtx reg2 = gen_reg_rtx (mode);
> + riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
> + rtx cond1 = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
> + rtx cond2 = gen_rtx_fmt_ee (code == NE ? EQ : NE,
> + GET_MODE (op0), op0, op1);
> + emit_insn (gen_rtx_SET (reg2,
> + gen_rtx_IF_THEN_ELSE (mode, cond2,
> + const0_rtx, cons)));
> + emit_insn (gen_rtx_SET (reg1,
> + gen_rtx_IF_THEN_ELSE (mode, cond1,
> + const0_rtx, alt)));
> + riscv_emit_binary(IOR, dest, reg1, reg2);
> + return true;
This probably should detect the case where alt or cons is the same as
op0. Otherwise I would expect the costing model to reject some cases
that are actually profitable.
> diff --git a/gcc/config/riscv/riscv.md b/gcc/config/riscv/riscv.md
> index 6b8c2e8e268..b4147c7a79c 100644
> --- a/gcc/config/riscv/riscv.md
> +++ b/gcc/config/riscv/riscv.md
> @@ -2484,7 +2484,7 @@
> (if_then_else:GPR (match_operand 1 "comparison_operator")
> (match_operand:GPR 2 "reg_or_0_operand")
> (match_operand:GPR 3 "sfb_alu_operand")))
> - "TARGET_SFB_ALU || TARGET_XTHEADCONDMOV"
> + "TARGET_SFB_ALU || TARGET_XTHEADCONDMOV || TARGET_ZICOND"
Note the predicate for operand2 will reject all constants except 0.
Anyway, I'm still cleaning this up and adding the bits from Ventana
which handle the relational conditions as well as FP conditionals.
Jeff
On Sat, Jul 29, 2023 at 04:59:00 AM Jeff Law <jeffreyalaw@gmail.com> wrote:
>
>
>
>On 7/19/23 04:11, Xiao Zeng wrote:
>
>> + else if (TARGET_ZICOND
>> + && (code == EQ || code == NE)
>> + && GET_MODE_CLASS (mode) == MODE_INT)
>> + {
>> + need_eq_ne_p = true;
>> + /* 0 + imm */
>> + if (GET_CODE (cons) == CONST_INT && cons == const0_rtx
>> + && GET_CODE (alt) == CONST_INT && alt != const0_rtx)
>> + {
>> + riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
>> + rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
>> + alt = force_reg (mode, alt);
>> + emit_insn (gen_rtx_SET (dest,
>> + gen_rtx_IF_THEN_ELSE (mode, cond,
>> + cons, alt)));
>> + return true;
>> + }
>> + /* imm + imm */
>> + else if (GET_CODE (cons) == CONST_INT && cons != const0_rtx
>> + && GET_CODE (alt) == CONST_INT && alt != const0_rtx)
>> + {
>> + riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
>> + rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
>> + alt = force_reg (mode, alt);
>> + rtx temp1 = gen_reg_rtx (mode);
>> + rtx temp2 = GEN_INT(-1 * INTVAL (cons));
>> + riscv_emit_binary(PLUS, temp1, alt, temp2);
>So in this sequence you're just computing a constant since both ALT and
>CONS are constants. It's better to just form the constant directly,
>then force that into a register because it'll make the costing more
>correct, particularly if the resulting constant needs more than one
>instruction to synthesize.
Fixed
>
>And a nit. There should always be a space between a function name and
>its argument list.
Fixed
>
>
>
>> + emit_insn (gen_rtx_SET (dest,
>> + gen_rtx_IF_THEN_ELSE (mode, cond,
>> + const0_rtx, alt)));
>> + riscv_emit_binary(PLUS, dest, dest, cons);
>> + return true;
>I don't see how this can be correct from a code generation standpoint.
>You compute ALT-CONS into TEMP1 earlier. But you never use TEMP1 after
>that. I think you meant to use TEMP1 instead of ALT as the false arm if
>the IF-THEN-ELSE you constructed.
Fixed
>
>In general you should be using CONST0_RTX (mode) rather than const0_rtx.
>
Fixed
>> + }
>> + /* imm + reg */
>> + else if (GET_CODE (cons) == CONST_INT && cons != const0_rtx
>> + && GET_CODE (alt) == REG)
>> + {
>> + /* Optimize for register value of 0. */
>> + if (op0 == alt && op1 == const0_rtx)
>> + {
>> + rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
>> + cons = force_reg (mode, cons);
>> + emit_insn (gen_rtx_SET (dest,
>> + gen_rtx_IF_THEN_ELSE (mode, cond,
>> + cons, alt)));
>> + return true;
>> + }
>> + riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
>> + rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
>> + rtx temp1 = gen_reg_rtx (mode);
>> + rtx temp2 = GEN_INT(-1 * INTVAL (cons));
>> + riscv_emit_binary(PLUS, temp1, alt, temp2);
>Here you have to be careful if CONS is -2048. You negate it resulting
>in +2048 which can't be used in an addi. This will cause the entire
>sequence to fail due to an unrecognized insn. It would be better to
>handle that scenario directly so the generated sequence is still valid.
>
>By generating recognizable code in that case we let the costing model
>determine if the conditional move sequence is better than the branching
>sequence.
Thank you for pointing out this special situation, it has been fixed
>
>
>> + emit_insn (gen_rtx_SET (dest,
>> + gen_rtx_IF_THEN_ELSE (mode, cond,
>> + const0_rtx, alt)));
>I think we have the same problem with the use of ALT here rather than
>TEMP1 that we had in the previous case.
Fixed
>
>
>
>> + /* reg + imm */
>> + else if (GET_CODE (cons) == REG
>> + && GET_CODE (alt) == CONST_INT && alt != const0_rtx)
>> + {
>> + /* Optimize for register value of 0. */
>> + if (op0 == cons && op1 == const0_rtx)
>> + {
>> + rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
>> + alt = force_reg (mode, alt);
>> + emit_insn (gen_rtx_SET (dest,
>> + gen_rtx_IF_THEN_ELSE (mode, cond,
>> + cons, alt)));
>> + return true;
>> + }
>> + riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
>> + rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
>> + rtx temp1 = gen_reg_rtx (mode);
>> + rtx temp2 = GEN_INT(-1 * INTVAL (alt));
>> + riscv_emit_binary(PLUS, temp1, cons, temp2);
>> + emit_insn (gen_rtx_SET (dest,
>> + gen_rtx_IF_THEN_ELSE (mode, cond,
>> + temp1, const0_rtx)));
>> + riscv_emit_binary(PLUS, dest, dest, alt);
>> + return true;
>This has basically the same issues as the imm + reg case.
Fixed
>
>
>> + }
>> + /* reg + reg */
>> + else if (GET_CODE (cons) == REG && GET_CODE (alt) == REG)
>> + {
>> + rtx reg1 = gen_reg_rtx (mode);
>> + rtx reg2 = gen_reg_rtx (mode);
>> + riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
>> + rtx cond1 = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
>> + rtx cond2 = gen_rtx_fmt_ee (code == NE ? EQ : NE,
>> + GET_MODE (op0), op0, op1);
>> + emit_insn (gen_rtx_SET (reg2,
>> + gen_rtx_IF_THEN_ELSE (mode, cond2,
>> + const0_rtx, cons)));
>> + emit_insn (gen_rtx_SET (reg1,
>> + gen_rtx_IF_THEN_ELSE (mode, cond1,
>> + const0_rtx, alt)));
>> + riscv_emit_binary(IOR, dest, reg1, reg2);
>> + return true;
>This probably should detect the case where alt or cons is the same as
>op0. Otherwise I would expect the costing model to reject some cases
>that are actually profitable.
>
Fixed
>
>> diff --git a/gcc/config/riscv/riscv.md b/gcc/config/riscv/riscv.md
>> index 6b8c2e8e268..b4147c7a79c 100644
>> --- a/gcc/config/riscv/riscv.md
>> +++ b/gcc/config/riscv/riscv.md
>> @@ -2484,7 +2484,7 @@
>> (if_then_else:GPR (match_operand 1 "comparison_operator")
>> (match_operand:GPR 2 "reg_or_0_operand")
>> (match_operand:GPR 3 "sfb_alu_operand")))
>> - "TARGET_SFB_ALU || TARGET_XTHEADCONDMOV"
>> + "TARGET_SFB_ALU || TARGET_XTHEADCONDMOV || TARGET_ZICOND"
>Note the predicate for operand2 will reject all constants except 0.
>
>Anyway, I'm still cleaning this up and adding the bits from Ventana
>which handle the relational conditions as well as FP conditionals.
>
>Jeff
1 Thank you for Jeff's code review comments. I have made the modifications
and submitted the V2-patch[3/5].
2 For the calculation method of cost, I hope to submit a separate patch[cost]
after the V2-patch[3/5] merged into master, which will focus on explaining
the reasons for calculating cost in the same way as in patch[4/5].
3 At the same time, I realized that for Zicond's series of patches, it would be
better to split them into separate patches and submit them to the community
code review. Therefore, I plan to submit:
V2-patch[3/5]
patch[cost]
V2-patch[4/5]
V2-patch[5/5]
I will only submit subsequent patches after the previous patch enters the master.
4. In V2-patch[3/5], Zicond's cost calculation is not involved, therefore, all test
cases are skipped with "- O0" and "- Os". I will remove the "- Os" constraint from
the test case in patch[cost].
5 The address for V2-patch[3/5] is: https://gcc.gnu.org/pipermail/gcc-patches/2023-July/625781.html
Thanks
Xiao Zeng
On Fri, Jul 28, 2023 at 11:09:00 PM Jeff Law <jeffreyalaw@gmail.com> wrote:
>
>
>
>On 7/25/23 11:55, Andreas Schwab wrote:
>> On Jul 19 2023, Xiao Zeng wrote:
>>
>>> diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
>>> index 38d8eb2fcf5..7e6b24bd232 100644
>>> --- a/gcc/config/riscv/riscv.cc
>>> +++ b/gcc/config/riscv/riscv.cc
>>> @@ -2448,6 +2448,17 @@ riscv_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno ATTRIBUTE_UN
>>> *total = COSTS_N_INSNS (1);
>>> return true;
>>> }
>>> + else if (TARGET_ZICOND && outer_code == SET &&
>>> + ((GET_CODE (XEXP (x, 1)) == REG && XEXP (x, 2) == const0_rtx) ||
>>> + (GET_CODE (XEXP (x, 2)) == REG && XEXP (x, 1) == const0_rtx) ||
>>> + (GET_CODE (XEXP (x, 1)) == REG && GET_CODE (XEXP (x, 2)) &&
>>> + XEXP (x, 1) == XEXP (XEXP (x, 0), 0)) ||
>>> + (GET_CODE (XEXP (x, 1)) == REG && GET_CODE (XEXP (x, 2)) &&
>>> + XEXP (x, 2) == XEXP (XEXP (x, 0), 0))))
>>
>> Line breaks before the operator, not after.
>Also note that && GET_CODE (XEXP (x, 2)) && that appears twice.
This is an error that I will fix in patch[cost] and provide a detailed explanation.
>
>That just verifies the code isn't RTX_UNKNOWN which I suspect isn't what
>the author intended. It probably needs to be adjusted for SUBREGs and
>the pointer equality issues with REGs after reload.
>
>I'll take care of these goofs since the costing ought to be able to move
>forward independently of the improvements Xiao made to generating
>conditional move sequences.
>
>Jeff
After V2-patch[3/5] is accepted, a patch[cost] will be submitted to provide detailed
explanation of this issue. Of course, as Jeff mentioned, some issues will also be fixed.
Thanks
Xiao Zeng
On 7/19/23 04:11, Xiao Zeng wrote:
> This patch completes the recognition of Zicond when the select pattern
> with condition eq or neq to 0 (using equality as an example), namely:
[ ... ]
I've committed the attached patch which implements a simple cost model
for using Zicond to implement conditional moves.
I've changed it to give the proper cost (COSTS_N_INSNS (1) and removed
the extraneous GET_CODE (object) tests adjusted the ChangeLog a bit and
pushed it to the trunk.
Thanks!
Jeff
commit 5b501863ac7da57858fdd464dfb7a776143f22a2
Author: Xiao Zeng <zengxiao@eswincomputing.com>
Date: Wed Aug 2 00:17:12 2023 -0600
[PATCH 3/5] [RISC-V] Cost model for Zicond.
This patch implements a reasonable cost model for using Zicond to
implement conditional moves. Essentially the Zicond insns are always
COSTS_N_INSNS (1).
Note there is still a problem with the costing model in general that
results in failure to if-convert as often as we should. In simplest
terms the insn costing model sums the cost of the SET_SRC and the
cost of the SET_DEST. Thus the conditional move is considered twice
as costly as it should be. That will have to be addressed separately.
gcc/
* config/riscv/riscv.cc (riscv_rtx_costs): Add costing for
using Zicond to implement some conditional moves.
diff --git a/gcc/config/riscv/riscv.cc b/gcc/config/riscv/riscv.cc
index 8c474503080..785e09c76ce 100644
--- a/gcc/config/riscv/riscv.cc
+++ b/gcc/config/riscv/riscv.cc
@@ -2518,6 +2518,20 @@ riscv_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno ATTRIBUTE_UN
*total = COSTS_N_INSNS (1);
return true;
}
+ else if (TARGET_ZICOND
+ && outer_code == SET
+ && ((GET_CODE (XEXP (x, 1)) == REG
+ && XEXP (x, 2) == CONST0_RTX (GET_MODE (XEXP (x, 1))))
+ || (GET_CODE (XEXP (x, 2)) == REG
+ && XEXP (x, 1) == CONST0_RTX (GET_MODE (XEXP (x, 2))))
+ || (GET_CODE (XEXP (x, 1)) == REG
+ && rtx_equal_p (XEXP (x, 1), XEXP (XEXP (x, 0), 0)))
+ || (GET_CODE (XEXP (x, 1)) == REG
+ && rtx_equal_p (XEXP (x, 2), XEXP (XEXP (x, 0), 0)))))
+ {
+ *total = COSTS_N_INSNS (1);
+ return true;
+ }
else if (LABEL_REF_P (XEXP (x, 1)) && XEXP (x, 2) == pc_rtx)
{
if (equality_operator (XEXP (x, 0), mode)
On 7/29/23 03:14, Xiao Zeng wrote:
>
> 1 Thank you for Jeff's code review comments. I have made the modifications
> and submitted the V2-patch[3/5].
Yea. I'm adjusting my tree based on those updates. For testing I've
actually got my compiler generating zicond by default and qemu allowing
zicond by default. I can then run the execute.exp tests which validate
code correctness to a reasonable degree.
>
> 2 For the calculation method of cost, I hope to submit a separate patch[cost]
> after the V2-patch[3/5] merged into master, which will focus on explaining
> the reasons for calculating cost in the same way as in patch[4/5].
I think the costing problem is going to require its own little
subproject. GCC's approach to costing is a bit crazy with multiple APIs
that behave differently and in some cases do some rather surprising
things. It's a long standing design flaw.
The point being that I think we'll probably move forward with the
functional bits, perhaps initially without the basic functionality
tests. That allows folks to start utilizing the core functionality
while we audit and likely adjust the risc-v cost hook implementation.
>
> 4. In V2-patch[3/5], Zicond's cost calculation is not involved, therefore, all test
> cases are skipped with "- O0" and "- Os". I will remove the "- Os" constraint from
> the test case in patch[cost].
We may need to avoid for -Og as well. I've got that change here
locally, but I wanted to go back and review that as well.
jeff
@@ -2448,6 +2448,17 @@ riscv_rtx_costs (rtx x, machine_mode mode, int outer_code, int opno ATTRIBUTE_UN
*total = COSTS_N_INSNS (1);
return true;
}
+ else if (TARGET_ZICOND && outer_code == SET &&
+ ((GET_CODE (XEXP (x, 1)) == REG && XEXP (x, 2) == const0_rtx) ||
+ (GET_CODE (XEXP (x, 2)) == REG && XEXP (x, 1) == const0_rtx) ||
+ (GET_CODE (XEXP (x, 1)) == REG && GET_CODE (XEXP (x, 2)) &&
+ XEXP (x, 1) == XEXP (XEXP (x, 0), 0)) ||
+ (GET_CODE (XEXP (x, 1)) == REG && GET_CODE (XEXP (x, 2)) &&
+ XEXP (x, 2) == XEXP (XEXP (x, 0), 0))))
+ {
+ *total = 0;
+ return true;
+ }
else if (LABEL_REF_P (XEXP (x, 1)) && XEXP (x, 2) == pc_rtx)
{
if (equality_operator (XEXP (x, 0), mode)
@@ -3501,6 +3512,120 @@ riscv_expand_conditional_move (rtx dest, rtx op, rtx cons, rtx alt)
cond, cons, alt)));
return true;
}
+ else if (TARGET_ZICOND
+ && (code == EQ || code == NE)
+ && GET_MODE_CLASS (mode) == MODE_INT)
+ {
+ need_eq_ne_p = true;
+ /* 0 + imm */
+ if (GET_CODE (cons) == CONST_INT && cons == const0_rtx
+ && GET_CODE (alt) == CONST_INT && alt != const0_rtx)
+ {
+ riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
+ rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
+ alt = force_reg (mode, alt);
+ emit_insn (gen_rtx_SET (dest,
+ gen_rtx_IF_THEN_ELSE (mode, cond,
+ cons, alt)));
+ return true;
+ }
+ /* imm + imm */
+ else if (GET_CODE (cons) == CONST_INT && cons != const0_rtx
+ && GET_CODE (alt) == CONST_INT && alt != const0_rtx)
+ {
+ riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
+ rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
+ alt = force_reg (mode, alt);
+ rtx temp1 = gen_reg_rtx (mode);
+ rtx temp2 = GEN_INT(-1 * INTVAL (cons));
+ riscv_emit_binary(PLUS, temp1, alt, temp2);
+ emit_insn (gen_rtx_SET (dest,
+ gen_rtx_IF_THEN_ELSE (mode, cond,
+ const0_rtx, alt)));
+ riscv_emit_binary(PLUS, dest, dest, cons);
+ return true;
+ }
+ /* imm + reg */
+ else if (GET_CODE (cons) == CONST_INT && cons != const0_rtx
+ && GET_CODE (alt) == REG)
+ {
+ /* Optimize for register value of 0. */
+ if (op0 == alt && op1 == const0_rtx)
+ {
+ rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
+ cons = force_reg (mode, cons);
+ emit_insn (gen_rtx_SET (dest,
+ gen_rtx_IF_THEN_ELSE (mode, cond,
+ cons, alt)));
+ return true;
+ }
+ riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
+ rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
+ rtx temp1 = gen_reg_rtx (mode);
+ rtx temp2 = GEN_INT(-1 * INTVAL (cons));
+ riscv_emit_binary(PLUS, temp1, alt, temp2);
+ emit_insn (gen_rtx_SET (dest,
+ gen_rtx_IF_THEN_ELSE (mode, cond,
+ const0_rtx, alt)));
+ riscv_emit_binary(PLUS, dest, dest, cons);
+ return true;
+ }
+ /* imm + 0 */
+ else if (GET_CODE (cons) == CONST_INT && cons != const0_rtx
+ && GET_CODE (alt) == CONST_INT && alt == const0_rtx)
+ {
+ riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
+ rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
+ cons = force_reg (mode, cons);
+ emit_insn (gen_rtx_SET (dest,
+ gen_rtx_IF_THEN_ELSE (mode, cond,
+ cons, alt)));
+ return true;
+ }
+ /* reg + imm */
+ else if (GET_CODE (cons) == REG
+ && GET_CODE (alt) == CONST_INT && alt != const0_rtx)
+ {
+ /* Optimize for register value of 0. */
+ if (op0 == cons && op1 == const0_rtx)
+ {
+ rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
+ alt = force_reg (mode, alt);
+ emit_insn (gen_rtx_SET (dest,
+ gen_rtx_IF_THEN_ELSE (mode, cond,
+ cons, alt)));
+ return true;
+ }
+ riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
+ rtx cond = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
+ rtx temp1 = gen_reg_rtx (mode);
+ rtx temp2 = GEN_INT(-1 * INTVAL (alt));
+ riscv_emit_binary(PLUS, temp1, cons, temp2);
+ emit_insn (gen_rtx_SET (dest,
+ gen_rtx_IF_THEN_ELSE (mode, cond,
+ temp1, const0_rtx)));
+ riscv_emit_binary(PLUS, dest, dest, alt);
+ return true;
+ }
+ /* reg + reg */
+ else if (GET_CODE (cons) == REG && GET_CODE (alt) == REG)
+ {
+ rtx reg1 = gen_reg_rtx (mode);
+ rtx reg2 = gen_reg_rtx (mode);
+ riscv_emit_int_compare (&code, &op0, &op1, need_eq_ne_p);
+ rtx cond1 = gen_rtx_fmt_ee (code, GET_MODE (op0), op0, op1);
+ rtx cond2 = gen_rtx_fmt_ee (code == NE ? EQ : NE,
+ GET_MODE (op0), op0, op1);
+ emit_insn (gen_rtx_SET (reg2,
+ gen_rtx_IF_THEN_ELSE (mode, cond2,
+ const0_rtx, cons)));
+ emit_insn (gen_rtx_SET (reg1,
+ gen_rtx_IF_THEN_ELSE (mode, cond1,
+ const0_rtx, alt)));
+ riscv_emit_binary(IOR, dest, reg1, reg2);
+ return true;
+ }
+ }
return false;
}
@@ -2484,7 +2484,7 @@
(if_then_else:GPR (match_operand 1 "comparison_operator")
(match_operand:GPR 2 "reg_or_0_operand")
(match_operand:GPR 3 "sfb_alu_operand")))]
- "TARGET_SFB_ALU || TARGET_XTHEADCONDMOV"
+ "TARGET_SFB_ALU || TARGET_XTHEADCONDMOV || TARGET_ZICOND"
{
if (riscv_expand_conditional_move (operands[0], operands[1],
operands[2], operands[3]))
new file mode 100644
@@ -0,0 +1,65 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc_zicond -mabi=lp64d" { target { rv64 } } } */
+/* { dg-options "-march=rv32gc_zicond -mabi=ilp32f" { target { rv32 } } } */
+/* { dg-skip-if "" { *-*-* } {"-O0"} } */
+
+long primitiveSemantics_return_0_imm_00(long a, long b) {
+ return a == 0 ? 0 : 3;
+}
+
+long primitiveSemantics_return_0_imm_01(long a, long b) {
+ return a != 0 ? 0 : 3;
+}
+
+long primitiveSemantics_return_0_imm_02(long a, long b) {
+ return a == 0 ? 3 : 0;
+}
+
+long primitiveSemantics_return_0_imm_03(long a, long b) {
+ return a != 0 ? 3 : 0;
+}
+
+long primitiveSemantics_return_0_imm_04(long a, long b) {
+ if (a)
+ b = 0;
+ else
+ b = 3;
+ return b;
+}
+
+long primitiveSemantics_return_0_imm_05(long a, long b) {
+ if (!a)
+ b = 0;
+ else
+ b = 3;
+ return b;
+}
+
+int primitiveSemantics_return_0_imm_06(int a, int b) { return a == 0 ? 0 : 3; }
+
+int primitiveSemantics_return_0_imm_07(int a, int b) { return a != 0 ? 0 : 3; }
+
+int primitiveSemantics_return_0_imm_08(int a, int b) { return a == 0 ? 3 : 0; }
+
+int primitiveSemantics_return_0_imm_09(int a, int b) { return a != 0 ? 3 : 0; }
+
+int primitiveSemantics_return_0_imm_10(int a, int b) {
+ if (a)
+ b = 0;
+ else
+ b = 3;
+ return b;
+}
+
+int primitiveSemantics_return_0_imm_11(int a, int b) {
+ if (!a)
+ b = 0;
+ else
+ b = 3;
+ return b;
+}
+
+/* { dg-final { scan-assembler-times "czero.eqz" 6 } } */
+/* { dg-final { scan-assembler-times "czero.nez" 6 } } */
+/* { dg-final { scan-assembler-not "beq" } } */
+/* { dg-final { scan-assembler-not "bne" } } */
new file mode 100644
@@ -0,0 +1,73 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc_zicond -mabi=lp64d" { target { rv64 } } } */
+/* { dg-options "-march=rv32gc_zicond -mabi=ilp32f" { target { rv32 } } } */
+/* { dg-skip-if "" { *-*-* } {"-O0" "-Os"} } */
+
+long primitiveSemantics_return_imm_imm_00(long a, long b) {
+ return a == 0 ? 4 : 6;
+}
+
+long primitiveSemantics_return_imm_imm_01(long a, long b) {
+ return a != 0 ? 4 : 6;
+}
+
+long primitiveSemantics_return_imm_imm_02(long a, long b) {
+ return a == 0 ? 6 : 4;
+}
+
+long primitiveSemantics_return_imm_imm_03(long a, long b) {
+ return a != 0 ? 6 : 4;
+}
+
+long primitiveSemantics_return_imm_imm_04(long a, long b) {
+ if (a)
+ b = 4;
+ else
+ b = 6;
+ return b;
+}
+
+long primitiveSemantics_return_imm_imm_05(long a, long b) {
+ if (!a)
+ b = 4;
+ else
+ b = 6;
+ return b;
+}
+
+int primitiveSemantics_return_imm_imm_06(int a, int b) {
+ return a == 0 ? 4 : 6;
+}
+
+int primitiveSemantics_return_imm_imm_07(int a, int b) {
+ return a != 0 ? 4 : 6;
+}
+
+int primitiveSemantics_return_imm_imm_08(int a, int b) {
+ return a == 0 ? 6 : 4;
+}
+
+int primitiveSemantics_return_imm_imm_09(int a, int b) {
+ return a != 0 ? 6 : 4;
+}
+
+int primitiveSemantics_return_imm_imm_10(int a, int b) {
+ if (a)
+ b = 4;
+ else
+ b = 6;
+ return b;
+}
+
+int primitiveSemantics_return_imm_imm_11(int a, int b) {
+ if (!a)
+ b = 4;
+ else
+ b = 6;
+ return b;
+}
+
+/* { dg-final { scan-assembler-times "czero.eqz" 6 } } */
+/* { dg-final { scan-assembler-times "czero.nez" 6 } } */
+/* { dg-final { scan-assembler-not "beq" } } */
+/* { dg-final { scan-assembler-not "bne" } } */
new file mode 100644
@@ -0,0 +1,65 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc_zicond -mabi=lp64d" { target { rv64 } } } */
+/* { dg-options "-march=rv32gc_zicond -mabi=ilp32f" { target { rv32 } } } */
+/* { dg-skip-if "" { *-*-* } {"-O0" "-Os"} } */
+
+long primitiveSemantics_return_imm_reg_00(long a, long b) {
+ return a == 0 ? 1 : b;
+}
+
+long primitiveSemantics_return_imm_reg_01(long a, long b) {
+ return a != 0 ? 1 : b;
+}
+
+long primitiveSemantics_return_imm_reg_02(long a, long b) {
+ return a == 0 ? b : 1;
+}
+
+long primitiveSemantics_return_imm_reg_03(long a, long b) {
+ return a != 0 ? b : 1;
+}
+
+long primitiveSemantics_return_imm_reg_04(long a, long b) {
+ if (a)
+ b = 1;
+ return b;
+}
+
+long primitiveSemantics_return_imm_reg_05(long a, long b) {
+ if (!a)
+ b = 1;
+ return b;
+}
+
+int primitiveSemantics_return_imm_reg_06(int a, int b) {
+ return a == 0 ? 1 : b;
+}
+
+int primitiveSemantics_return_imm_reg_07(int a, int b) {
+ return a != 0 ? 1 : b;
+}
+
+int primitiveSemantics_return_imm_reg_08(int a, int b) {
+ return a == 0 ? b : 1;
+}
+
+int primitiveSemantics_return_imm_reg_09(int a, int b) {
+ return a != 0 ? b : 1;
+}
+
+int primitiveSemantics_return_imm_reg_10(int a, int b) {
+ if (a)
+ b = 1;
+ return b;
+}
+
+int primitiveSemantics_return_imm_reg_11(int a, int b) {
+ if (!a)
+ b = 1;
+ return b;
+}
+
+/* { dg-final { scan-assembler-times "czero.eqz" 6 } } */
+/* { dg-final { scan-assembler-times "czero.nez" 6 } } */
+/* { dg-final { scan-assembler-not "beq" } } */
+/* { dg-final { scan-assembler-not "bne" } } */
new file mode 100644
@@ -0,0 +1,65 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc_zicond -mabi=lp64d" { target { rv64 } } } */
+/* { dg-options "-march=rv32gc_zicond -mabi=ilp32f" { target { rv32 } } } */
+/* { dg-skip-if "" { *-*-* } {"-O0" "-Os"} } */
+
+long primitiveSemantics_return_reg_reg_00(long a, long b, long c) {
+ return a == 0 ? c : b;
+}
+
+long primitiveSemantics_return_reg_reg_01(long a, long b, long c) {
+ return a != 0 ? c : b;
+}
+
+long primitiveSemantics_return_reg_reg_02(long a, long b, long c) {
+ return a == 0 ? b : c;
+}
+
+long primitiveSemantics_return_reg_reg_03(long a, long b, long c) {
+ return a != 0 ? b : c;
+}
+
+long primitiveSemantics_return_reg_reg_04(long a, long b, long c) {
+ if (a)
+ b = c;
+ return b;
+}
+
+long primitiveSemantics_return_reg_reg_05(long a, long b, long c) {
+ if (!a)
+ b = c;
+ return b;
+}
+
+int primitiveSemantics_return_reg_reg_06(int a, int b, int c) {
+ return a == 0 ? c : b;
+}
+
+int primitiveSemantics_return_reg_reg_07(int a, int b, int c) {
+ return a != 0 ? c : b;
+}
+
+int primitiveSemantics_return_reg_reg_08(int a, int b, int c) {
+ return a == 0 ? b : c;
+}
+
+int primitiveSemantics_return_reg_reg_09(int a, int b, int c) {
+ return a != 0 ? b : c;
+}
+
+int primitiveSemantics_return_reg_reg_10(int a, int b, int c) {
+ if (a)
+ b = c;
+ return b;
+}
+
+int primitiveSemantics_return_reg_reg_11(int a, int b, int c) {
+ if (!a)
+ b = c;
+ return b;
+}
+
+/* { dg-final { scan-assembler-times "czero.eqz" 12 } } */
+/* { dg-final { scan-assembler-times "czero.nez" 12 } } */
+/* { dg-final { scan-assembler-not "beq" } } */
+/* { dg-final { scan-assembler-not "bne" } } */