This is Shreya's next chunk of work. When I was looking for good bugs
for her to chase down I cam across PR114512. While the bug isn't
necessarily a RISC-V specific bug, its testcases did show how we were
failing to recognize certain bit extraction idioms and how the lispy
nature of RTL allows us to tackle these issues in the combiner.
First, the bit position may be masked. The RISC-V port does not define
SHIFT_COUNT_TRUNCATED for valid reasons. So if we want to optimize away
a mask that matches what the hardware will do, we need suitable insns
that include that explicit masking.
In addition to needing to incorporate masking, the masking may happen in
a subword mode. So we need to recognize the mask wrapped in a zero
extension.
Those two captured the most common cases.
We can also have a single bit extraction implemented as a left shift of
the bit into the sign bit, then a right shift by the size of a word - 1.
These are less common, but we did cover the case derived from the
upstream bug report as well as one class seen reviewing the instruction
stream for spec2017.
Finally, extracting a single bit at a variable position from a constant
as seen with some regularity in spec2017. In that scenario, combine's
chosen split point wasn't ideal (I forget what it selected, but it
definitely wasn't helpful). So we've got a new splitter for this case
as well.
Earlier versions of this have gone through my tester as well as a
bootstrap and regression cycle. This version has just gone through a
cycle in my tester (but missed today's bootstrap cycle).
Waiting on the upstream pre-commit tester to render its verdict, but the
plan is to commit on Shreya's behalf once that's clean.
Jeff
PR middle-end/114512
gcc/
* config/riscv/bitmanip.md (bext* patterns): New patterns for
bext recognition plus splitter for extracting variable bit from
a constant.
* config/riscv/predicates.md (bitpos_mask_operand): New predicate.
gcc/testsuite/
* gcc.target/riscv/pr114512.c: New test.
diff --git a/gcc/config/riscv/bitmanip.md b/gcc/config/riscv/bitmanip.md
index 20d03dc8792..ca03dcaecf8 100644
--- a/gcc/config/riscv/bitmanip.md
+++ b/gcc/config/riscv/bitmanip.md
@@ -1302,3 +1302,76 @@ (define_split
}
DONE;
})
+
+;; More forms of single bit extraction. The RISC-V port does not
+;; define SHIFT_COUNT_TRUNCATED so we need forms where the bit position
+;; is masked.
+;;
+;; We could in theory use this for rv32 as well, but it probably does
+;; not occur in practice. The bit position would need to be QI/HI mode,
+;; otherwise we would not need the zero extension.
+;;
+;; One could also argue that the zero extension is redundant and should
+;; have been optimized away during RTL simplification.
+(define_insn "*bextdi_position_ze_masked"
+ [(set (match_operand:DI 0 "register_operand" "=r")
+ (zero_extract:DI (match_operand:DI 1 "register_operand" "r")
+ (const_int 1)
+ (zero_extend:DI
+ (and:SI (match_operand:SI 2 "register_operand" "r")
+ (const_int 63)))))]
+ "TARGET_64BIT && TARGET_ZBS"
+ "bext\t%0,%1,%2"
+ [(set_attr "type" "bitmanip")])
+
+;; Same as above, but without the extraneous zero_extend.
+(define_insn "*bextdi_position_ze_masked"
+ [(set (match_operand:X 0 "register_operand" "=r")
+ (zero_extract:X (match_operand:X 1 "register_operand" "r")
+ (const_int 1)
+ (and:X (match_operand:SI 2 "register_operand" "r")
+ (match_operand:SI 3 "bitpos_mask_operand"
"n"))))]
+ "TARGET_64BIT && TARGET_ZBS"
+ "bext\t%0,%1,%2"
+ [(set_attr "type" "bitmanip")])
+
+
+;; Single bit extraction by first shifting it into the sign bit, then
+;; shifting it down to the low bit.
+(define_insn "*bext<mode>_position_masked"
+ [(set (match_operand:X 0 "register_operand" "=r")
+ (lshiftrt:X (ashift:X (match_operand:X 1 "register_operand" "r")
+ (match_operand:QI 2 "register_operand" "r"))
+ (match_operand:X 3 "bitpos_mask_operand" "n")))]
+ "TARGET_ZBS"
+ "bext\t%0,%1,%2"
+ [(set_attr "type" "bitmanip")])
+
+;; Single bit extraction by shifting into the low bit, but with the
+;; position formed with a subreg of a mask.
+(define_insn "*bext<mode>_position_masked_subreg"
+ [(set (match_operand:X 0 "register_operand" "=r")
+ (lshiftrt:X
+ (ashift:X (match_operand:X 1 "register_operand" "r")
+ (subreg:QI
+ (and:X (match_operand:X 2 "register_operand" "r")
+ (match_operand:X 3 "bitpos_mask_operand" "n")) 0))
+ (match_operand:X 4 "bitpos_mask_operand" "n")))]
+ "TARGET_ZBS"
+ "bext\t%0,%1,%2"
+ [(set_attr "type" "bitmanip")])
+
+;; This has shown up in testing. In particular we end up with an
+;; immediate input. We can load that into a register and target
+;; one of the above bext patterns.
+(define_split
+ [(set (match_operand:X 0 "register_operand")
+ (and:X (lshiftrt:X (match_operand 1 "immediate_operand")
+ (match_operand:QI 2 "register_operand"))
+ (const_int 1)))
+ (clobber (match_operand:X 3 "register_operand"))]
+ ""
+ [(set (match_dup 3) (match_dup 1))
+ (set (match_dup 0) (zero_extract:X (match_dup 3)
+ (const_int 1)
+ (zero_extend:X (match_dup 2))))])
diff --git a/gcc/config/riscv/predicates.md b/gcc/config/riscv/predicates.md
index f26bafcc688..c9a638cd103 100644
--- a/gcc/config/riscv/predicates.md
+++ b/gcc/config/riscv/predicates.md
@@ -685,3 +685,7 @@ (define_predicate "x1x5_operand"
(and (match_operand 0 "register_operand")
(match_test "REGNO (op) == RETURN_ADDR_REGNUM
|| REGNO (op) == T0_REGNUM")))
+
+(define_predicate "bitpos_mask_operand"
+ (and (match_code "const_int")
+ (match_test "TARGET_64BIT ? INTVAL (op) == 63 : INTVAL (op) == 31")))
diff --git a/gcc/testsuite/gcc.target/riscv/pr114512.c
b/gcc/testsuite/gcc.target/riscv/pr114512.c
new file mode 100644
index 00000000000..205071c0883
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/pr114512.c
@@ -0,0 +1,109 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gcb -mabi=lp64d" { target { rv64 } } } */
+/* { dg-options "-march=rv32gcb -mabi=ilp32" { target { rv32 } } } */
+/* { dg-skip-if "" { *-*-* } { "-O0" "-Og" "-Os" "-Oz" } } */
+
+/* We need to adjust the constant so this works for rv32 and rv64. */
+#if __riscv_xlen == 32
+#define ONE 1U
+#define MASK 0x1f
+typedef unsigned int utype;
+#else
+#define ONE 1ULL
+#define MASK 0x3f
+typedef unsigned long utype;
+#endif
+
+
+_Bool my_isxdigit_1(unsigned char ch) {
+ utype mask1 = 0x03FF007E;
+ if (!((mask1 >> (ch & MASK)) & 1))
+ return 0;
+
+ return 1;
+}
+
+_Bool my_isxdigit_1a(unsigned char ch) {
+ utype mask2 = 0x58;
+ if (!((mask2 >> (ch >> 4)) & 1))
+ return 0;
+
+ return 1;
+}
+
+_Bool my_isxdigit_2(unsigned char ch) {
+ utype mask1 = 0x03FF007E;
+ if (!(mask1 & (ONE << (ch & MASK))))
+ return 0;
+
+ return 1;
+}
+
+_Bool my_isxdigit_2a(unsigned char ch) {
+ utype mask2 = 0x58;
+ if (!(mask2 & (ONE << (ch >> 4))))
+ return 0;
+
+ return 1;
+}
+
+_Bool my_isxdigit_3(unsigned char ch) {
+ utype mask1 = 0x7E00FFC0;
+ if (!((mask1 << (MASK - (ch & MASK))) >> MASK))
+ return 0;
+
+ return 1;
+}
+
+_Bool my_isxdigit_3a(unsigned char ch) {
+ utype mask2 = 0x7E00FFC0;
+ if (!((mask2 << (MASK - ((ch >> 4) & MASK))) >> MASK))
+ return 0;
+
+ return 1;
+}
+
+_Bool my_isxdigit_1_parm(unsigned char ch, utype mask1) {
+ if (!((mask1 >> (ch & MASK)) & 1))
+ return 0;
+
+ return 1;
+}
+
+_Bool my_isxdigit_1a_parm(unsigned char ch, utype mask2) {
+ if (!((mask2 >> (ch >> 4)) & 1))
+ return 0;
+
+ return 1;
+}
+
+_Bool my_isxdigit_2_parm(unsigned char ch, utype mask1) {
+ if (!(mask1 & (ONE << (ch & MASK))))
+ return 0;
+
+ return 1;
+}
+
+_Bool my_isxdigit_2a_parm(unsigned char ch, utype mask2) {
+ if (!(mask2 & (ONE << (ch >> 4))))
+ return 0;
+
+ return 1;
+}
+
+_Bool my_isxdigit_3_parm(unsigned char ch, utype mask1) {
+ if (!((mask1 << (MASK - (ch & MASK))) >> MASK))
+ return 0;
+
+ return 1;
+}
+
+_Bool my_isxdigit_3a_parm(unsigned char ch, utype mask2) {
+ if (!((mask2 << (MASK - ((ch >> 4) & MASK))) >> MASK))
+ return 0;
+
+ return 1;
+}
+
+/* Each test should generate a single bext. */
+/* { dg-final { scan-assembler-times "bext\t" 12 } } */