Reverse rotations with masked negative counts in the generic RTL
simplifier. Keep the count mask so the reversed count stays in range.
gcc/ChangeLog:
* simplify-rtx.cc (simplify_context::simplify_binary_operation_1):
Reverse rotations with masked negative counts.
(selftest::test_masked_negative_rotate): New function.
(selftest::test_scalar_int_ops): Call it.
gcc/testsuite/ChangeLog:
* gcc.target/riscv/zbb-rol-ror-10.c: New test.
* gcc.target/aarch64/rotate-neg-mask.c: Ditto.
---
Requires patch 1/2 of this series.
diff --git a/gcc/simplify-rtx.cc b/gcc/simplify-rtx.cc
index
e979d4c296411170d3de0ffa5863df7cd07c0b33..1711b55f7376c33fb145d952c1d250e88d61740b
100644
--- a/gcc/simplify-rtx.cc
+++ b/gcc/simplify-rtx.cc
@@ -4858,6 +4858,44 @@
case ROTATE:
if (trueop1 == CONST0_RTX (mode))
return op0;
+
+ /* Reversing the direction removes a negated count modulo the
+ precision. Keep the mask so that the new count is in range even
+ on targets that do not truncate rotate counts. */
+ if (is_a <scalar_int_mode> (mode, &int_mode)
+ && exact_log2 (GET_MODE_PRECISION (int_mode)) >= 0)
+ {
+ rtx count = op1;
+ if ((SUBREG_P (count)
+ && subreg_lowpart_p (count)
+ && !paradoxical_subreg_p (count))
+ || GET_CODE (count) == TRUNCATE)
+ count = XEXP (count, 0);
+
+ scalar_int_mode count_mode;
+ if (GET_CODE (count) == AND
+ && GET_CODE (XEXP (count, 0)) == NEG
+ && CONST_INT_P (XEXP (count, 1))
+ && INTVAL (XEXP (count, 1))
+ == GET_MODE_PRECISION (int_mode) - 1
+ && is_a <scalar_int_mode> (GET_MODE (count), &inner_mode)
+ && is_a <scalar_int_mode> (GET_MODE (op1), &count_mode)
+ && GET_MODE_PRECISION (count_mode)
+ > exact_log2 (GET_MODE_PRECISION (int_mode))
+ && GET_MODE_PRECISION (inner_mode)
+ >= GET_MODE_PRECISION (count_mode))
+ {
+ tem = simplify_gen_binary (AND, inner_mode,
+ XEXP (XEXP (count, 0), 0),
+ XEXP (count, 1));
+ if (count != op1)
+ tem = simplify_gen_unary (TRUNCATE, count_mode, tem,
+ inner_mode);
+ return simplify_gen_binary (code == ROTATE ? ROTATERT : ROTATE,
+ mode, op0, tem);
+ }
+ }
+
/* Canonicalize rotates by constant amount. If the condition of
reversing direction is met, then reverse the direction. */
#if defined(HAVE_rotate) && defined(HAVE_rotatert)
@@ -9352,6 +9390,65 @@
return gen_rtx_REG (mode, test_reg_num++);
}
+/* Test reversing rotations with a masked negative count. */
+
+static void
+test_masked_negative_rotate (machine_mode mode)
+{
+ rtx value = make_test_reg (mode);
+ unsigned int precision = GET_MODE_PRECISION (mode).to_constant ();
+ int count_bits = exact_log2 (precision);
+ static const machine_mode count_modes[] = { QImode, SImode, DImode };
+ static const rtx_code codes[] = { ROTATE, ROTATERT };
+
+ for (auto count_mode : count_modes)
+ {
+ rtx reg = make_test_reg (count_mode);
+ rtx neg = gen_rtx_NEG (count_mode, reg);
+ rtx mask = gen_int_mode (precision - 1, count_mode);
+ rtx count = gen_rtx_AND (count_mode, neg, mask);
+ rtx positive = simplify_gen_binary (AND, count_mode, reg, mask);
+
+ for (auto code : codes)
+ {
+ rtx_code reverse = code == ROTATE ? ROTATERT : ROTATE;
+ rtx expected = NULL_RTX;
+ if (count_bits >= 0
+ && GET_MODE_PRECISION (count_mode).to_constant () > count_bits)
+ expected = simplify_gen_binary (reverse, mode, value, positive);
+ ASSERT_RTX_EQ (expected,
+ simplify_binary_operation (code, mode, value, count));
+
+ /* A bare negative count or a narrower mask must not be folded. */
+ ASSERT_FALSE (simplify_binary_operation (code, mode, value, neg));
+ rtx narrow = gen_rtx_AND (count_mode, neg,
+ gen_int_mode (precision / 2 - 1,
count_mode));
+ ASSERT_FALSE (simplify_binary_operation (code, mode, value, narrow));
+
+ if (count_mode == QImode)
+ continue;
+ expected = NULL_RTX;
+ if (count_bits >= 0 && GET_MODE_PRECISION (QImode) > count_bits)
+ expected = simplify_gen_binary
+ (reverse, mode, value,
+ simplify_gen_unary (TRUNCATE, QImode, positive, count_mode));
+ rtx low = gen_rtx_SUBREG (QImode, count,
+ subreg_lowpart_offset (QImode, count_mode));
+ ASSERT_RTX_EQ (expected,
+ simplify_binary_operation (code, mode, value, low));
+ rtx trunc = gen_rtx_TRUNCATE (QImode, count);
+ ASSERT_RTX_EQ (expected,
+ simplify_binary_operation (code, mode, value, trunc));
+ poly_uint64 offset = subreg_highpart_offset (QImode, count_mode);
+ if (validate_subreg (QImode, count_mode, count, offset))
+ {
+ rtx high = gen_rtx_SUBREG (QImode, count, offset);
+ ASSERT_FALSE (simplify_binary_operation (code, mode, value,
high));
+ }
+ }
+ }
+}
+
static void
test_scalar_int_ops (machine_mode mode)
{
@@ -9418,6 +9515,8 @@
ASSERT_RTX_EQ (op0, simplify_gen_unary (ZERO_EXTEND, mode, op0, mode));
ASSERT_RTX_EQ (op0, simplify_gen_unary (SIGN_EXTEND, mode, op0, mode));
ASSERT_RTX_EQ (op0, lowpart_subreg (mode, op0, mode));
+
+ test_masked_negative_rotate (mode);
}
/* Verify some simplifications of integer extension/truncation.
diff --git a/gcc/testsuite/gcc.target/riscv/zbb-rol-ror-10.c
b/gcc/testsuite/gcc.target/riscv/zbb-rol-ror-10.c
new file mode 100644
index
0000000000000000000000000000000000000000..2db53c6887fdf719dbcb0f0c2e52ae3691e4d704
--- /dev/null
+++ b/gcc/testsuite/gcc.target/riscv/zbb-rol-ror-10.c
@@ -0,0 +1,44 @@
+/* { dg-do compile } */
+/* { dg-options "-march=rv64gc_zbb -mabi=lp64" { target { rv64 } } } */
+/* { dg-options "-march=rv32gc_zbb -mabi=ilp32" { target { rv32 } } } */
+/* { dg-skip-if "" { *-*-* } { "-O0" "-Og" } } */
+
+unsigned long
+rol_neg (unsigned long x, unsigned long n)
+{
+ return __builtin_stdc_rotate_left (x, 0UL - (n & 255UL));
+}
+
+unsigned long
+ror_neg (unsigned long x, unsigned long n)
+{
+ return __builtin_stdc_rotate_right (x, 0UL - (n & 255UL));
+}
+
+/* This mask changes the effective count and must be retained. */
+unsigned long
+ror_neg_mask7 (unsigned long x, unsigned long n)
+{
+ return __builtin_stdc_rotate_right (x, 0UL - (n & 7UL));
+}
+
+#if __riscv_xlen == 64
+unsigned int
+rolw_neg (unsigned int x, unsigned int n)
+{
+ return __builtin_stdc_rotate_left (x, 0U - (n & 31U));
+}
+
+unsigned int
+rorw_neg (unsigned int x, unsigned int n)
+{
+ return __builtin_stdc_rotate_right (x, 0U - (n & 31U));
+}
+#endif
+
+/* { dg-final { scan-assembler-times "rol\t" 2 } } */
+/* { dg-final { scan-assembler-times "ror\t" 1 } } */
+/* { dg-final { scan-assembler-times "rolw\t" 1 { target { rv64 } } } } */
+/* { dg-final { scan-assembler-times "rorw\t" 1 { target { rv64 } } } } */
+/* { dg-final { scan-assembler-times "andi\t" 1 } } */
+/* { dg-final { scan-assembler-not "negw?\t" } } */
diff --git a/gcc/testsuite/gcc.target/aarch64/rotate-neg-mask.c
b/gcc/testsuite/gcc.target/aarch64/rotate-neg-mask.c
new file mode 100644
index
0000000000000000000000000000000000000000..68c63c2768aa8fc57c894c76fd532aa7b63cbaf8
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/rotate-neg-mask.c
@@ -0,0 +1,39 @@
+/* { dg-do run } */
+/* { dg-options "-O2" } */
+/* { dg-skip-if "" { *-*-* } { "-mgeneral-regs-only" } } */
+
+/* Keep SIMD enabled so that SHIFT_COUNT_TRUNCATED is false. */
+__attribute__((noipa)) unsigned int
+rotate32 (unsigned int x, unsigned int n)
+{
+ return __builtin_stdc_rotate_right (x, 0U - (n & 31U));
+}
+
+__attribute__((noipa)) unsigned long long
+rotate64 (unsigned long long x, unsigned long long n)
+{
+ return __builtin_stdc_rotate_right (x, 0ULL - (n & 63ULL));
+}
+
+__attribute__((noipa)) unsigned long long
+rotate64_count32 (unsigned long long x, unsigned int n)
+{
+ return __builtin_stdc_rotate_right (x, 0U - (n & 63U));
+}
+
+int
+main (void)
+{
+ unsigned int expected32 = 1;
+ unsigned long long expected64 = 1;
+ for (unsigned int n = 0; n < 128; ++n)
+ {
+ if (rotate32 (1, n) != expected32
+ || rotate64 (1, n) != expected64
+ || rotate64_count32 (1, n) != expected64)
+ __builtin_abort ();
+ expected32 = (expected32 << 1) | (expected32 >> 31);
+ expected64 = (expected64 << 1) | (expected64 >> 63);
+ }
+ return 0;
+}