From: Pritam Gharat <[email protected]>
Some logical operations (AND/IOR/XOR) use an immediate that is not a legal
AArch64 bitmask immediate and therefore previously required the constant to
be materialised in a register (MOV/MOVK) followed by a register-form logical
operation.
For values that can be expressed as the combination of exactly two legal
bitmask immediates, emit two immediate-form logical operations instead,
avoiding the constant materialisation. aarch64_split_logical_imm ()
computes the two halves and verifies that both are legal bitmask immediates;
a new define_insn_and_split pattern performs the transformation after reload,
and aarch64_emit_split_logical () emits the two logical instructions.
For AND the two halves overlap and their intersection is the original value;
for IOR/XOR the halves are disjoint and their union/xor is the original
value. In both cases "(src OP p1) OP p2" reproduces "src OP val".
gcc/ChangeLog:
* config/aarch64/aarch64-protos.h (aarch64_split_logical_imm): Declare.
(aarch64_emit_split_logical): Declare.
* config/aarch64/aarch64.cc (aarch64_split_logical_imm): New function.
(aarch64_emit_split_logical): New function.
* config/aarch64/aarch64.md (*aarch64_<optab><mode>3_split_bitmask):
New define_insn_and_split.
gcc/testsuite/ChangeLog:
* gcc.target/aarch64/split_logical_imm_si.c: New test.
* gcc.target/aarch64/split_logical_imm_di.c: New test.
Signed-off-by: Pritam Gharat <[email protected]>
---
gcc/config/aarch64/aarch64-protos.h | 12 ++
gcc/config/aarch64/aarch64.cc | 114 ++++++++++++
gcc/config/aarch64/aarch64.md | 20 ++
.../gcc.target/aarch64/split_logical_imm_di.c | 175 ++++++++++++++++++
.../gcc.target/aarch64/split_logical_imm_si.c | 168 +++++++++++++++++
5 files changed, 489 insertions(+)
create mode 100644 gcc/testsuite/gcc.target/aarch64/split_logical_imm_di.c
create mode 100644 gcc/testsuite/gcc.target/aarch64/split_logical_imm_si.c
diff --git a/gcc/config/aarch64/aarch64-protos.h
b/gcc/config/aarch64/aarch64-protos.h
index bccdbffc194..62260d396d0 100644
--- a/gcc/config/aarch64/aarch64-protos.h
+++ b/gcc/config/aarch64/aarch64-protos.h
@@ -1317,4 +1317,16 @@ extern unsigned aarch64_stack_alignment (const_tree exp,
unsigned align);
extern rtx aarch64_gen_compare_zero_and_branch (rtx_code code, rtx x,
rtx_code_label *label);
+bool
+aarch64_split_logical_imm (unsigned HOST_WIDE_INT val, machine_mode mode,
+ enum rtx_code code,
+ unsigned HOST_WIDE_INT *part1,
+ unsigned HOST_WIDE_INT *part2);
+
+void
+aarch64_emit_split_logical (rtx dst, rtx src, machine_mode mode,
+ enum rtx_code op,
+ unsigned HOST_WIDE_INT p1,
+ unsigned HOST_WIDE_INT p2);
+
#endif /* GCC_AARCH64_PROTOS_H */
diff --git a/gcc/config/aarch64/aarch64.cc b/gcc/config/aarch64/aarch64.cc
index 5b1c78462a3..c06bcfd9007 100644
--- a/gcc/config/aarch64/aarch64.cc
+++ b/gcc/config/aarch64/aarch64.cc
@@ -251,6 +251,120 @@ inline simd_immediate_info
u.pattern = pattern_in;
}
+/* Try to express VAL (interpreted in MODE) as the combination of two
+ AArch64 "logical immediate" (bitmask) values that can each be encoded
+ directly in an ORR/EOR/AND instruction, so that
+
+ ORR: x | VAL == (x | PART1) | PART2
+ EOR: x ^ VAL == (x ^ PART1) ^ PART2 (PART1 & PART2 == 0)
+ AND: x & VAL == (x & ~M1) & ~M2 (VAL == ~(M1 | M2))
+
+ For ORR/EOR we split VAL itself into two disjoint bitmask pieces.
+ For AND we split the *inverse* (~VAL), because AND clears bits; each
+ emitted immediate is then the bitwise-NOT of a bitmask piece and must
+ itself be a valid bitmask.
+
+ On success, store the two immediates to emit in *PART1 and *PART2 and
+ return true. PART1/PART2 may be NULL when the caller only wants a
+ feasibility test (for example, from an insn condition). */
+
+bool
+aarch64_split_logical_imm (unsigned HOST_WIDE_INT val, machine_mode mode,
+ enum rtx_code code,
+ unsigned HOST_WIDE_INT *part1,
+ unsigned HOST_WIDE_INT *part2)
+{
+ gcc_assert (mode == SImode || mode == DImode);
+
+ /* Mask VAL to the mode width so all bit tricks stay within the
+ relevant bits (important for SImode on a 64-bit HOST_WIDE_INT). */
+ unsigned HOST_WIDE_INT mask
+ = (mode == SImode) ? 0xffffffffULL : ~(unsigned HOST_WIDE_INT) 0;
+ val &= mask;
+
+ /* Only worth it when VAL isn't already a single bitmask and isn't a
+ cheap MOV. */
+ if (aarch64_bitmask_imm (val, mode)
+ || aarch64_move_imm (val, mode))
+ return false;
+
+ unsigned HOST_WIDE_INT part_a = 0, part_b = 0;
+
+ if (code == AND)
+ {
+ /* AND clears bits, so decompose the *zero-bit* runs of VAL, i.e.
+ the set-bit runs of the inverse, then invert each half back.
+ The two resulting masks OVERLAP; their intersection is VAL. */
+ unsigned HOST_WIDE_INT inv_val = (~val) & mask;
+
+ /* Isolate the lowest contiguous run of zero-bits. */
+ unsigned HOST_WIDE_INT low_bit = inv_val & -inv_val;
+ unsigned HOST_WIDE_INT low_run = ((inv_val + low_bit) ^ inv_val) & mask;
+ unsigned HOST_WIDE_INT inv_part_a = inv_val & low_run;
+
+ /* All remaining zero-bits. */
+ unsigned HOST_WIDE_INT inv_part_b = inv_val ^ inv_part_a;
+
+ part_a = (~inv_part_a) & mask;
+ part_b = (~inv_part_b) & mask;
+ }
+ else if (code == IOR || code == XOR)
+ {
+ /* OR/XOR set bits, so decompose the set-bit runs of VAL directly.
+ The two halves are DISJOINT, so both '|' and '^' reproduce VAL. */
+ unsigned HOST_WIDE_INT low_bit = val & -val;
+ unsigned HOST_WIDE_INT low_run = ((val + low_bit) ^ val) & mask;
+ part_a = val & low_run;
+
+ /* All remaining set-bits. */
+ part_b = val ^ part_a;
+ }
+ else
+ /* Unsupported bitwise operation. */
+ return false;
+
+ /* Hardware check: BOTH halves must be encodable as logical immediates.
+ part_b may still hold several bit-runs (VAL had >2 runs); in that
+ case it fails this test and we correctly bail out. */
+ if (aarch64_bitmask_imm (part_a, mode)
+ && aarch64_bitmask_imm (part_b, mode))
+ {
+ if (part1 != nullptr && part2 != nullptr)
+ {
+ *part1=part_a;
+ *part2=part_b;
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+void
+aarch64_emit_split_logical (rtx dst, rtx src, machine_mode mode,
+ enum rtx_code op,
+ unsigned HOST_WIDE_INT p1,
+ unsigned HOST_WIDE_INT p2)
+{
+ /* This helper is only valid for the bitwise logical operations that
+ aarch64_split_logical_imm () handles. For AND the two halves overlap
+ and satisfy (p1 & p2) == val; for IOR/XOR they are disjoint and satisfy
+ (p1 | p2) == val / (p1 ^ p2) == val. In every case
+ (src OP p1) OP p2 reproduces src OP val. */
+ gcc_assert (op == AND || op == IOR || op == XOR);
+ gcc_assert (mode == SImode || mode == DImode);
+
+ rtx imm1 = gen_int_mode (p1, mode);
+ rtx imm2 = gen_int_mode (p2, mode);
+
+ /* 1st instruction: dst = src OP p1. */
+ emit_insn (gen_rtx_SET (dst, gen_rtx_fmt_ee (op, mode, src, imm1)));
+
+ /* 2nd instruction: dst = dst OP p2. */
+ emit_insn (gen_rtx_SET (dst, gen_rtx_fmt_ee (op, mode, dst, imm2)));
+}
+
namespace {
/* Describes types that map to Pure Scalable Types (PSTs) in the AAPCS64. */
diff --git a/gcc/config/aarch64/aarch64.md b/gcc/config/aarch64/aarch64.md
index 8d0e50db272..0897af24551 100644
--- a/gcc/config/aarch64/aarch64.md
+++ b/gcc/config/aarch64/aarch64.md
@@ -3030,6 +3030,26 @@ (define_insn "add<mode>3_compare0"
}
)
+(define_insn_and_split "*aarch64_<optab><mode>3_split_bitmask"
+ [(set (match_operand:GPI 0 "register_operand" "=r")
+ (LOGICAL:GPI (match_operand:GPI 1 "register_operand" "r")
+ (match_operand:GPI 2 "const_int_operand" "n")))]
+ "!aarch64_logical_operand (operands[2], <MODE>mode)
+ && aarch64_split_logical_imm (UINTVAL (operands[2]), <MODE>mode,
+ <CODE>, nullptr, nullptr)"
+ "#"
+ "&& reload_completed"
+ [(const_int 0)]
+ {
+ unsigned HOST_WIDE_INT p1=0, p2=0;
+ aarch64_split_logical_imm (UINTVAL (operands[2]), <MODE>mode,
+ <CODE>, &p1, &p2);
+ aarch64_emit_split_logical (operands[0], operands[1], <MODE>mode,
+ <CODE>, p1, p2);
+ DONE;
+ }
+)
+
;; zero_extend version of above
(define_insn "*addsi3_compare0_uxtw"
[(set (reg:CC_NZ CC_REGNUM)
diff --git a/gcc/testsuite/gcc.target/aarch64/split_logical_imm_di.c
b/gcc/testsuite/gcc.target/aarch64/split_logical_imm_di.c
new file mode 100644
index 00000000000..0c0397d9b69
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/split_logical_imm_di.c
@@ -0,0 +1,175 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-schedule-insns -fno-schedule-insns2" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+
+/* =====================================================================
+ aarch64_split_logical_imm () - DImode (64-bit, "x" registers)
+ 4 scenarios x {AND, IOR (|), XOR (^)}.
+
+ The expected code for each function is verified with check-function-bodies
+ (the "** <insn>" block immediately preceding each function): this pins the
+ exact, ordered instruction sequence - order, contents, and the absence of
+ stray instructions - which scan-assembler-times cannot do.
+
+ Representative immediates (verified identical behaviour for AND/IOR/XOR):
+ S1 valid : 0x000000000000ffff (= 65535)
+ S2 -> 2 valid : 0xffff0000000000f0
+ S3 -> >2 valid : 0x00000000000f0f0f
+ S4 unsplittable : 0x123456789abcdef0
+ ===================================================================== */
+
+
+/* =====================================================================
+ SCENARIO 1 (DImode): valid bitmask immediate -> NO split required.
+
+ 0x000000000000ffff is a single contiguous run of 16 ones - a legal
+ AArch64 64-bit logical bitmask immediate. aarch64_split_logical_imm ()
+ returns false early (aarch64_bitmask_imm () is true), so a SINGLE
+ immediate-form logical instruction is emitted and no MOVK is needed.
+ ===================================================================== */
+
+/*
+** s1_di_and:
+** and x0, x0, 65535
+** ret
+*/
+unsigned long s1_di_and (unsigned long x) { return x & 0x000000000000ffffUL; }
+
+/*
+** s1_di_ior:
+** orr x0, x0, 65535
+** ret
+*/
+unsigned long s1_di_ior (unsigned long x) { return x | 0x000000000000ffffUL; }
+
+/*
+** s1_di_xor:
+** eor x0, x0, 65535
+** ret
+*/
+unsigned long s1_di_xor (unsigned long x) { return x ^ 0x000000000000ffffUL; }
+
+
+/* =====================================================================
+ SCENARIO 2 (DImode): invalid bitmask, splits into EXACTLY TWO valid
+ bitmask immediates -> TWO immediate-form logical ops, no MOVK.
+
+ 0xffff0000000000f0 is not a legal bitmask immediate but is the
+ combination of two legal bitmask immediates. The split differs per
+ opcode:
+ IOR/XOR (disjoint): part_a = 0x00000000000000f0 (240),
+ part_b = 0xffff000000000000 (-281474976710656)
+ AND (overlap) : part_a = 0xfffffffffffffff0 (-16),
+ part_b = 0xffff0000000000ff (-281474976710401)
+ In every case (x OP part_a) OP part_b reproduces x OP 0xffff0000000000f0.
+ ===================================================================== */
+
+/*
+** s2_di_and:
+** and x0, x0, -16
+** and x0, x0, -281474976710401
+** ret
+*/
+unsigned long s2_di_and (unsigned long x) { return x & 0xffff0000000000f0UL; }
+
+/*
+** s2_di_ior:
+** orr x0, x0, 240
+** orr x0, x0, -281474976710656
+** ret
+*/
+unsigned long s2_di_ior (unsigned long x) { return x | 0xffff0000000000f0UL; }
+
+/*
+** s2_di_xor:
+** eor x0, x0, 240
+** eor x0, x0, -281474976710656
+** ret
+*/
+unsigned long s2_di_xor (unsigned long x) { return x ^ 0xffff0000000000f0UL; }
+
+
+/* =====================================================================
+ SCENARIO 3 (DImode): invalid bitmask, needs MORE THAN TWO valid
+ bitmask immediates.
+
+ 0x00000000000f0f0f has THREE disjoint bit-runs, each individually a
+ legal bitmask, but it CANNOT be grouped into just two legal bitmask
+ immediates. aarch64_split_logical_imm () performs only a 2-way split;
+ here part_b still contains two runs and fails aarch64_bitmask_imm (), so
+ the function returns FALSE. The constant is therefore materialised
+ (MOV/MOVK) and a REGISTER-form op is used. (The scratch register is
+ RA-chosen; matched with a backreference.)
+ ===================================================================== */
+
+/*
+** s3_di_and:
+** mov w([0-9]+), 3855
+** movk x\1, 0xf, lsl 16
+** and x0, x0, x\1
+** ret
+*/
+unsigned long s3_di_and (unsigned long x) { return x & 0x00000000000f0f0fUL; }
+
+/*
+** s3_di_ior:
+** mov w([0-9]+), 3855
+** movk x\1, 0xf, lsl 16
+** orr x0, x0, x\1
+** ret
+*/
+unsigned long s3_di_ior (unsigned long x) { return x | 0x00000000000f0f0fUL; }
+
+/*
+** s3_di_xor:
+** mov w([0-9]+), 3855
+** movk x\1, 0xf, lsl 16
+** eor x0, x0, x\1
+** ret
+*/
+unsigned long s3_di_xor (unsigned long x) { return x ^ 0x00000000000f0f0fUL; }
+
+
+/* =====================================================================
+ SCENARIO 4 (DImode): invalid bitmask, CANNOT be split into valid
+ bitmask immediates.
+
+ 0x123456789abcdef0 has many scattered runs and cannot be expressed as a
+ combination of two legal bitmask immediates at all.
+ aarch64_split_logical_imm () returns FALSE, so the full 64-bit constant
+ is materialised (MOV + 3x MOVK) and a single REGISTER-form logical op is
+ used. (Same KIND of code as Scenario 3; distinguished by the value.)
+ ===================================================================== */
+
+/*
+** s4_di_and:
+** mov w([0-9]+), 57072
+** movk x\1, 0x9abc, lsl 16
+** movk x\1, 0x5678, lsl 32
+** movk x\1, 0x1234, lsl 48
+** and x0, x0, x\1
+** ret
+*/
+unsigned long s4_di_and (unsigned long x) { return x & 0x123456789abcdef0UL; }
+
+/*
+** s4_di_ior:
+** mov w([0-9]+), 57072
+** movk x\1, 0x9abc, lsl 16
+** movk x\1, 0x5678, lsl 32
+** movk x\1, 0x1234, lsl 48
+** orr x0, x0, x\1
+** ret
+*/
+unsigned long s4_di_ior (unsigned long x) { return x | 0x123456789abcdef0UL; }
+
+/*
+** s4_di_xor:
+** mov w([0-9]+), 57072
+** movk x\1, 0x9abc, lsl 16
+** movk x\1, 0x5678, lsl 32
+** movk x\1, 0x1234, lsl 48
+** eor x0, x0, x\1
+** ret
+*/
+unsigned long s4_di_xor (unsigned long x) { return x ^ 0x123456789abcdef0UL; }
diff --git a/gcc/testsuite/gcc.target/aarch64/split_logical_imm_si.c
b/gcc/testsuite/gcc.target/aarch64/split_logical_imm_si.c
new file mode 100644
index 00000000000..ad41b900f34
--- /dev/null
+++ b/gcc/testsuite/gcc.target/aarch64/split_logical_imm_si.c
@@ -0,0 +1,168 @@
+/* { dg-do compile } */
+/* { dg-options "-O2 -fno-schedule-insns -fno-schedule-insns2" } */
+/* { dg-final { check-function-bodies "**" "" "" } } */
+
+/* =====================================================================
+ aarch64_split_logical_imm () - SImode (32-bit, "w" registers)
+ 4 scenarios x {AND, IOR (|), XOR (^)}.
+
+ The expected code for each function is verified with check-function-bodies
+ (the "** <insn>" block immediately preceding each function): this pins the
+ exact, ordered instruction sequence - order, contents, and the absence of
+ stray instructions - which scan-assembler-times cannot do.
+
+ Representative immediates (verified identical behaviour for AND/IOR/XOR):
+ S1 valid : 0x0f0f0f0f (= 252645135)
+ S2 -> 2 valid : 0x00f0000f
+ S3 -> >2 valid : 0x00f0f00f
+ S4 unsplittable : 0x12345678
+ ===================================================================== */
+
+
+/* =====================================================================
+ SCENARIO 1 (SImode): valid bitmask immediate -> NO split required.
+
+ 0x0f0f0f0f is a legal AArch64 logical bitmask immediate (element 0x0f
+ replicated every 8 bits). aarch64_split_logical_imm () returns false
+ early because aarch64_bitmask_imm () is true, so a SINGLE immediate-form
+ logical instruction is emitted and no constant is materialised.
+ ===================================================================== */
+
+/*
+** s1_si_and:
+** and w0, w0, 252645135
+** ret
+*/
+unsigned int s1_si_and (unsigned int x) { return x & 0x0f0f0f0fU; }
+
+/*
+** s1_si_ior:
+** orr w0, w0, 252645135
+** ret
+*/
+unsigned int s1_si_ior (unsigned int x) { return x | 0x0f0f0f0fU; }
+
+/*
+** s1_si_xor:
+** eor w0, w0, 252645135
+** ret
+*/
+unsigned int s1_si_xor (unsigned int x) { return x ^ 0x0f0f0f0fU; }
+
+
+/* =====================================================================
+ SCENARIO 2 (SImode): invalid bitmask, splits into EXACTLY TWO valid
+ bitmask immediates -> TWO immediate-form logical ops, no MOVK.
+
+ 0x00f0000f is not a legal bitmask immediate but is the combination of
+ two legal bitmask immediates. The split differs per opcode:
+ IOR/XOR (disjoint): part_a = 0x0000000f (15),
+ part_b = 0x00f00000 (15728640)
+ AND (overlap) : part_a = 0xfff0000f (-1048561),
+ part_b = 0x00ffffff (16777215)
+ In every case (x OP part_a) OP part_b reproduces x OP 0x00f0000f.
+ ===================================================================== */
+
+/*
+** s2_si_and:
+** and w0, w0, 16777215
+** and w0, w0, -1048561
+** ret
+*/
+unsigned int s2_si_and (unsigned int x) { return x & 0x00f0000fU; }
+
+/*
+** s2_si_ior:
+** orr w0, w0, 15
+** orr w0, w0, 15728640
+** ret
+*/
+unsigned int s2_si_ior (unsigned int x) { return x | 0x00f0000fU; }
+
+/*
+** s2_si_xor:
+** eor w0, w0, 15
+** eor w0, w0, 15728640
+** ret
+*/
+unsigned int s2_si_xor (unsigned int x) { return x ^ 0x00f0000fU; }
+
+
+/* =====================================================================
+ SCENARIO 3 (SImode): invalid bitmask, needs MORE THAN TWO valid
+ bitmask immediates.
+
+ 0x00f0f00f has THREE disjoint bit-runs, each individually a legal
+ bitmask, but it CANNOT be grouped into just two legal bitmask
+ immediates. aarch64_split_logical_imm () performs only a 2-way split;
+ here part_b (0x00f0f000) still contains two runs and fails
+ aarch64_bitmask_imm (), so the function returns FALSE. The constant is
+ therefore materialised (MOV/MOVK) and a REGISTER-form op is used.
+ (The scratch register is RA-chosen; matched with a backreference.)
+ ===================================================================== */
+
+/*
+** s3_si_and:
+** mov (w[0-9]+), 61455
+** movk \1, 0xf0, lsl 16
+** and w0, w0, \1
+** ret
+*/
+unsigned int s3_si_and (unsigned int x) { return x & 0x00f0f00fU; }
+
+/*
+** s3_si_ior:
+** mov (w[0-9]+), 61455
+** movk \1, 0xf0, lsl 16
+** orr w0, w0, \1
+** ret
+*/
+unsigned int s3_si_ior (unsigned int x) { return x | 0x00f0f00fU; }
+
+/*
+** s3_si_xor:
+** mov (w[0-9]+), 61455
+** movk \1, 0xf0, lsl 16
+** eor w0, w0, \1
+** ret
+*/
+unsigned int s3_si_xor (unsigned int x) { return x ^ 0x00f0f00fU; }
+
+
+/* =====================================================================
+ SCENARIO 4 (SImode): invalid bitmask, CANNOT be split into valid
+ bitmask immediates.
+
+ 0x12345678 has many scattered runs and cannot be expressed as a
+ combination of two legal bitmask immediates at all.
+ aarch64_split_logical_imm () returns FALSE, so the constant is fully
+ materialised (MOV + MOVK) and a single REGISTER-form logical op is used.
+ (Same KIND of code as Scenario 3; distinguished by the input value.)
+ ===================================================================== */
+
+/*
+** s4_si_and:
+** mov (w[0-9]+), 22136
+** movk \1, 0x1234, lsl 16
+** and w0, w0, \1
+** ret
+*/
+unsigned int s4_si_and (unsigned int x) { return x & 0x12345678U; }
+
+/*
+** s4_si_ior:
+** mov (w[0-9]+), 22136
+** movk \1, 0x1234, lsl 16
+** orr w0, w0, \1
+** ret
+*/
+unsigned int s4_si_ior (unsigned int x) { return x | 0x12345678U; }
+
+/*
+** s4_si_xor:
+** mov (w[0-9]+), 22136
+** movk \1, 0x1234, lsl 16
+** eor w0, w0, \1
+** ret
+*/
+unsigned int s4_si_xor (unsigned int x) { return x ^ 0x12345678U; }
--
2.34.1