https://gcc.gnu.org/g:d88781e6fb9786e7bb0bc8d34e7f15316b450c4b
commit r17-5181-gd88781e6fb9786e7bb0bc8d34e7f15316b450c4b Author: Kishan Parmar <[email protected]> Date: Wed Oct 14 13:49:53 2026 +0530 rs6000: New splitter to combine two rotates into rldic+rldimi sequence GCC currently emits three instructions (rldic/rldic/or) for RTL of the form: (ior (and (ashift S1 sh1) mask1) (and (ashift S2 sh2) mask2)) even when the two masks select disjoint bitfields that can be merged using a single `rldic` (rotate-and-mask) followed by an `rldimi` (rotate-and-insert) instruction. This patch adds a `define_split` pattern that recognizes such cases and rewrites them into a two-instruction sequence that performs the same operation more efficiently: rldic dest, S2, sh2, mb2 rldimi dest, S1, sh1, mb1 The splitter checks that: * both rotated fields have valid insert/shift masks, * the masks are non-overlapping, and * safety guard to check pseudos can be created at this stage (`can_create_pseudo_p()`). This transformation allows combine to generate the higher-level (ior(and(rotate), and(rotate))) form, while the new splitter breaks it down pre-reload into efficient PowerPC field-merge instructions. 2026-10-14 Kishan Parmar <[email protected]> gcc/ChangeLog: PR target/93171 * config/rs6000/rs6000.md (New merge field splitter): New define_split pattern. Split disjoint rotate-and-mask OR sequences into rldic + rldimi when both masks are valid and non-overlapping. gcc/testsuite/ChangeLog: * gcc.target/powerpc/pr93171.c: New testcase. Diff: --- gcc/config/rs6000/rs6000.md | 69 ++++++++++++++++++++++++++++++ gcc/testsuite/gcc.target/powerpc/pr93171.c | 29 +++++++++++++ 2 files changed, 98 insertions(+) diff --git a/gcc/config/rs6000/rs6000.md b/gcc/config/rs6000/rs6000.md index 796aef1eb297..c9727f2e6d8a 100644 --- a/gcc/config/rs6000/rs6000.md +++ b/gcc/config/rs6000/rs6000.md @@ -4493,6 +4493,75 @@ [(set_attr "type" "insert") (set_attr "size" "64")]) +; Two forms for rotate-clear + rotate-insert (the two arms of the IOR are +; not canonicalized, both are an AND so are the same precedence). +; Splitter 1: first arm uses an insert mask, second arm uses a shift mask. +(define_split + [(set (match_operand:GPR 0 "gpc_reg_operand") + (ior:GPR + (and:GPR + (match_operator:GPR 7 "rotate_mask_operator" + [(match_operand:GPR 1 "gpc_reg_operand") + (match_operand:SI 2 "const_int_operand")]) + (match_operand:GPR 3 "const_int_operand")) + (and:GPR + (match_operator:GPR 8 "rotate_mask_operator" + [(match_operand:GPR 4 "gpc_reg_operand") + (match_operand:SI 5 "const_int_operand")]) + (match_operand:GPR 6 "const_int_operand"))))] + "rs6000_is_valid_insert_mask (operands[3], operands[7], <MODE>mode) + && rs6000_is_valid_shift_mask (operands[6], operands[8], <MODE>mode) + && ((UINTVAL (operands[3]) & UINTVAL (operands[6])) == 0) + && can_create_pseudo_p ()" + [(set (match_dup 9) + (and:GPR (match_dup 8) + (match_dup 6))) + (set (match_dup 0) + (ior:GPR + (and:GPR (match_dup 9) + (match_dup 10)) + (and:GPR (match_dup 7) + (match_dup 3))))] +{ + HOST_WIDE_INT m1 = INTVAL (operands[3]); + HOST_WIDE_INT cm = trunc_int_for_mode (~m1, <MODE>mode); + operands[9] = gen_reg_rtx (<MODE>mode); + operands[10] = GEN_INT (cm); +}) + +; Splitter 2: first arm uses a shift mask, second arm uses an insert mask. +(define_split + [(set (match_operand:GPR 0 "gpc_reg_operand") + (ior:GPR + (and:GPR + (match_operator:GPR 7 "rotate_mask_operator" + [(match_operand:GPR 1 "gpc_reg_operand") + (match_operand:SI 2 "const_int_operand")]) + (match_operand:GPR 3 "const_int_operand")) + (and:GPR + (match_operator:GPR 8 "rotate_mask_operator" + [(match_operand:GPR 4 "gpc_reg_operand") + (match_operand:SI 5 "const_int_operand")]) + (match_operand:GPR 6 "const_int_operand"))))] + "rs6000_is_valid_insert_mask (operands[6], operands[8], <MODE>mode) + && rs6000_is_valid_shift_mask (operands[3], operands[7], <MODE>mode) + && ((UINTVAL (operands[3]) & UINTVAL (operands[6])) == 0) + && can_create_pseudo_p ()" + [(set (match_dup 9) + (and:GPR (match_dup 7) + (match_dup 3))) + (set (match_dup 0) + (ior:GPR + (and:GPR (match_dup 9) + (match_dup 10)) + (and:GPR (match_dup 8) + (match_dup 6))))] +{ + HOST_WIDE_INT m1 = INTVAL (operands[6]); + HOST_WIDE_INT cm = trunc_int_for_mode (~m1, <MODE>mode); + operands[9] = gen_reg_rtx (<MODE>mode); + operands[10] = GEN_INT (cm); +}) ; This handles the important case of multiple-precision shifts. There is ; no canonicalization rule for ASHIFT vs. LSHIFTRT, so two patterns. diff --git a/gcc/testsuite/gcc.target/powerpc/pr93171.c b/gcc/testsuite/gcc.target/powerpc/pr93171.c new file mode 100644 index 000000000000..22fc61a0dfc1 --- /dev/null +++ b/gcc/testsuite/gcc.target/powerpc/pr93171.c @@ -0,0 +1,29 @@ +/* { dg-do compile { target powerpc64*-*-* } } */ +/* { dg-options "-O2 -m64 -mcpu=power10" } */ + +typedef unsigned long long uint64_t; + +typedef union { + uint64_t u64; + struct { + uint64_t op : 4; + uint64_t type : 3; + uint64_t tag : 32; + } s; +} TagReq; + +__attribute__((noinline, noclone)) +void foo(uint64_t *a, int type, int tag) +{ + TagReq req; + req.u64 = 0; + + req.s.tag = (unsigned)tag; + req.s.type = (unsigned)type; + + *a = req.u64; +} + +/* Expect exactly one rldic and one rldimi. */ + /* { dg-final { scan-assembler-times {\mrldic(\.)?\M} 1 } } */ + /* { dg-final { scan-assembler-times {\mrldimi(\.)?\M} 1 } } */
