diff --git a/gcc/config/arm/arm.h b/gcc/config/arm/arm.h index e07cf03538c5..1269f40bd77c 100644 --- a/gcc/config/arm/arm.h +++ b/gcc/config/arm/arm.h @@ -586,6 +586,9 @@ extern int arm_arch_bf16; /* Target machine storage Layout. */ +/* Nonzero if this chip provides Armv8.1-M Mainline + LOB (low overhead branch features) extension instructions. */ +#define TARGET_HAVE_LOB (arm_arch8_1m_main) /* Define this macro if it is advisable to hold scalars in registers in a wider mode than that declared by the program. In such cases, diff --git a/gcc/config/arm/arm.c b/gcc/config/arm/arm.c index 9cc7bc0e5621..7c2a7b7e9e97 100644 --- a/gcc/config/arm/arm.c +++ b/gcc/config/arm/arm.c @@ -833,6 +833,9 @@ static const struct attribute_spec arm_attribute_table[] = #undef TARGET_CONSTANT_ALIGNMENT #define TARGET_CONSTANT_ALIGNMENT arm_constant_alignment +#undef TARGET_INVALID_WITHIN_DOLOOP +#define TARGET_INVALID_WITHIN_DOLOOP arm_invalid_within_doloop + #undef TARGET_MD_ASM_ADJUST #define TARGET_MD_ASM_ADJUST arm_md_asm_adjust @@ -32937,6 +32940,27 @@ arm_ge_bits_access (void) return true; } +/* NULL if INSN insn is valid within a low-overhead loop. + Otherwise return why doloop cannot be applied. */ + +static const char * +arm_invalid_within_doloop (const rtx_insn *insn) +{ + if (!TARGET_HAVE_LOB) + return default_invalid_within_doloop (insn); + + if (CALL_P (insn)) + return "Function call in the loop."; + + if (tablejump_p (insn, NULL, NULL) || computed_jump_p (insn)) + return "Computed branch in the loop."; + + if (reg_mentioned_p (gen_rtx_REG (SImode, LR_REGNUM), insn)) + return "LR is used inside loop."; + + return NULL; +} + #if CHECKING_P namespace selftest { diff --git a/gcc/config/arm/thumb2.md b/gcc/config/arm/thumb2.md index b0d3bd1cf1c4..fab993a8079f 100644 --- a/gcc/config/arm/thumb2.md +++ b/gcc/config/arm/thumb2.md @@ -1555,8 +1555,11 @@ using a certain 'count' register and (2) the loop count can be adjusted by modifying this register prior to the loop. ??? The possible introduction of a new block to initialize the - new IV can potentially affect branch optimizations. */ - if (optimize > 0 && flag_modulo_sched) + new IV can potentially affect branch optimizations. + + Also used to implement the low over head loops feature, which is part of + the Armv8.1-M Mainline Low Overhead Branch (LOB) extension. */ + if (optimize > 0 && (flag_modulo_sched || TARGET_HAVE_LOB)) { rtx s0; rtx bcomp; @@ -1569,6 +1572,11 @@ FAIL; s0 = operands [0]; + + /* Low over head loop instructions require the first operand to be LR. */ + if (TARGET_HAVE_LOB) + s0 = gen_rtx_REG (SImode, LR_REGNUM); + if (TARGET_THUMB2) insn = emit_insn (gen_thumb2_addsi3_compare0 (s0, s0, GEN_INT (-1))); else @@ -1650,3 +1658,30 @@ "TARGET_HAVE_MVE" "lsrl%?\\t%Q0, %R0, %1" [(set_attr "predicable" "yes")]) + +;; Originally expanded by 'doloop_end'. +(define_insn "*doloop_end_internal" + [(parallel [(set (pc) + (if_then_else + (ne (reg:SI LR_REGNUM) (const_int 1)) + (label_ref (match_operand 0 "" "")) + (pc))) + (set (reg:SI LR_REGNUM) + (plus:SI (reg:SI LR_REGNUM) (const_int -1)))])] + "TARGET_32BIT && TARGET_HAVE_LOB" + "le\t%|lr, %l0") + +(define_expand "doloop_begin" + [(match_operand 0 "" "") + (match_operand 1 "" "")] + "TARGET_32BIT && TARGET_HAVE_LOB" + { + emit_insn (gen_dls_insn (operands[0])); + DONE; + }) + +(define_insn "dls_insn" + [(set (reg:SI LR_REGNUM) + (unspec:SI [(match_operand:SI 0 "s_register_operand" "r")] UNSPEC_DLS))] + "TARGET_32BIT && TARGET_HAVE_LOB" + "dls\t%|lr, %0") diff --git a/gcc/config/arm/unspecs.md b/gcc/config/arm/unspecs.md index 8f4a705f43ef..df5ecb731925 100644 --- a/gcc/config/arm/unspecs.md +++ b/gcc/config/arm/unspecs.md @@ -154,6 +154,7 @@ UNSPEC_SMUADX ; Represent the SMUADX operation. UNSPEC_SSAT16 ; Represent the SSAT16 operation. UNSPEC_USAT16 ; Represent the USAT16 operation. + UNSPEC_DLS ; Used for DLS (Do Loop Start), Armv8.1-M Mainline instruction ]) diff --git a/gcc/doc/sourcebuild.texi b/gcc/doc/sourcebuild.texi index 1f412ded2bb7..5b9e0399ebc1 100644 --- a/gcc/doc/sourcebuild.texi +++ b/gcc/doc/sourcebuild.texi @@ -1945,6 +1945,12 @@ ARM Target supports options suitable for accessing the Q-bit manipulation intrinsics from @code{arm_acle.h}. Some multilibs may be incompatible with these options. +@item arm_v8_1_lob_ok +@anchor{arm_v8_1_lob_ok} +ARM Target supports executing the Armv8.1-M Mainline Low Overhead Loop +instructions @code{DLS} and @code{LE}. +Some multilibs may be incompatible with these options. + @end table @subsubsection AArch64-specific attributes diff --git a/gcc/testsuite/gcc.target/arm/lob.h b/gcc/testsuite/gcc.target/arm/lob.h new file mode 100644 index 000000000000..feaae7cc8995 --- /dev/null +++ b/gcc/testsuite/gcc.target/arm/lob.h @@ -0,0 +1,15 @@ +#include + +/* Common code for lob tests. */ + +#define NO_LOB asm volatile ("@ clobber lr" : : : "lr" ) + +#define N 10000 + +static void +reset_data (int *a, int *b, int *c) +{ + memset (a, -1, N * sizeof (*a)); + memset (b, -1, N * sizeof (*b)); + memset (c, -1, N * sizeof (*c)); +} diff --git a/gcc/testsuite/gcc.target/arm/lob1.c b/gcc/testsuite/gcc.target/arm/lob1.c new file mode 100644 index 000000000000..b92dc551d50b --- /dev/null +++ b/gcc/testsuite/gcc.target/arm/lob1.c @@ -0,0 +1,85 @@ +/* Check that GCC generates Armv8.1-M low over head loop instructions + for some simple loops. */ +/* { dg-do run } */ +/* { dg-require-effective-target arm_v8_1_lob_ok } */ +/* { dg-skip-if "avoid conflicting multilib options" { *-*-* } { "-marm" "-mcpu=*" } } */ +/* { dg-options "-march=armv8.1-m.main -O3 --save-temps" } */ +#include +#include "lob.h" + +int a[N]; +int b[N]; +int c[N]; + +int +foo (int a, int b) +{ + return a + b; +} + +void __attribute__((noinline)) +loop1 (int *a, int *b, int *c) +{ + for (int i = 0; i < N; i++) + { + a[i] = i; + b[i] = i * 2; + c[i] = a[i] + b[i]; + } +} + +void __attribute__((noinline)) +loop2 (int *a, int *b, int *c) +{ + int i = 0; + while (i < N) + { + a[i] = i - 2; + b[i] = i * 5; + c[i] = a[i] + b[i]; + i++; + } +} + +void __attribute__((noinline)) +loop3 (int *a, int *b, int *c) +{ + int i = 0; + do + { + a[i] = i - 4; + b[i] = i * 3; + c[i] = a[i] + b[i]; + i++; + } while (i < N); +} + +void +check (int *a, int *b, int *c) +{ + for (int i = 0; i < N; i++) + { + NO_LOB; + if (c[i] != a[i] + b[i]) + abort (); + } +} + +int +main (void) +{ + reset_data (a, b, c); + loop1 (a, b ,c); + check (a, b ,c); + reset_data (a, b, c); + loop2 (a, b ,c); + check (a, b ,c); + reset_data (a, b, c); + loop3 (a, b ,c); + check (a, b ,c); + + return 0; +} + +/* { dg-final { scan-assembler-times {dls\s\S*,\s\S*} 3 } } */ +/* { dg-final { scan-assembler-times {le\slr,\s\S*} 3 } } */ diff --git a/gcc/testsuite/gcc.target/arm/lob2.c b/gcc/testsuite/gcc.target/arm/lob2.c new file mode 100644 index 000000000000..1fe9a9d82bb9 --- /dev/null +++ b/gcc/testsuite/gcc.target/arm/lob2.c @@ -0,0 +1,32 @@ +/* Check that GCC does not generate Armv8.1-M low over head loop instructions + if a non-inlineable function call takes place inside the loop. */ +/* { dg-do compile } */ +/* { dg-skip-if "avoid conflicting multilib options" { *-*-* } { "-marm" "-mcpu=*" } } */ +/* { dg-options "-march=armv8.1-m.main -O3 --save-temps" } */ +#include +#include "lob.h" + +int a[N]; +int b[N]; +int c[N]; + +int __attribute__ ((noinline)) +foo (int a, int b) +{ + return a + b; +} + +int +main (void) +{ + for (int i = 0; i < N; i++) + { + a[i] = i; + b[i] = i * 2; + c[i] = foo (a[i], b[i]); + } + + return 0; +} +/* { dg-final { scan-assembler-not {dls\s\S*,\s\S*} } } */ +/* { dg-final { scan-assembler-not {le\slr,\s\S*} } } */ diff --git a/gcc/testsuite/gcc.target/arm/lob3.c b/gcc/testsuite/gcc.target/arm/lob3.c new file mode 100644 index 000000000000..17cba007ccb6 --- /dev/null +++ b/gcc/testsuite/gcc.target/arm/lob3.c @@ -0,0 +1,27 @@ +/* Check that GCC does not generate Armv8.1-M low over head loop instructions + if causes VFP emulation library calls to happen inside the loop. */ +/* { dg-do compile } */ +/* { dg-skip-if "avoid conflicting multilib options" { *-*-* } { "-marm" "-mcpu=*" } } */ +/* { dg-options "-march=armv8.1-m.main -O3 --save-temps -mfloat-abi=soft" } */ +/* { dg-require-effective-target arm_softfloat } */ +#include +#include "lob.h" + +double a[N]; +double b[N]; +double c[N]; + +int +main (void) +{ + for (int i = 0; i < N; i++) + { + a[i] = i; + b[i] = i * 2; + c[i] = a[i] + b[i]; + } + + return 0; +} +/* { dg-final { scan-assembler-not {dls\s\S*,\s\S*} } } */ +/* { dg-final { scan-assembler-not {le\slr,\s\S*} } } */ diff --git a/gcc/testsuite/gcc.target/arm/lob4.c b/gcc/testsuite/gcc.target/arm/lob4.c new file mode 100644 index 000000000000..444a2c7b4bfd --- /dev/null +++ b/gcc/testsuite/gcc.target/arm/lob4.c @@ -0,0 +1,34 @@ +/* Check that GCC does not generate Armv8.1-M low over head loop instructions + if LR is modified within the loop. */ +/* { dg-do compile } */ +/* { dg-skip-if "avoid conflicting multilib options" { *-*-* } { "-marm" "-mcpu=*" } } */ +/* { dg-options "-march=armv8.1-m.main -O3 --save-temps -mfloat-abi=soft" } */ +/* { dg-require-effective-target arm_softfloat } */ +#include +#include "lob.h" + +int a[N]; +int b[N]; +int c[N]; + +static __attribute__ ((always_inline)) inline int +foo (int a, int b) +{ + NO_LOB; + return a + b; +} + +int +main (void) +{ + for (int i = 0; i < N; i++) + { + a[i] = i; + b[i] = i * 2; + c[i] = foo(a[i], b[i]); + } + + return 0; +} +/* { dg-final { scan-assembler-not {dls\s\S*,\s\S*} } } */ +/* { dg-final { scan-assembler-not {le\slr,\s\S*} } } */ diff --git a/gcc/testsuite/gcc.target/arm/lob5.c b/gcc/testsuite/gcc.target/arm/lob5.c new file mode 100644 index 000000000000..c4f46e41532b --- /dev/null +++ b/gcc/testsuite/gcc.target/arm/lob5.c @@ -0,0 +1,35 @@ +/* Check that GCC does not generates Armv8.1-M low over head loop + instructions. Innermost loop has no fixed number of iterations + therefore is not optimizable. Outer loops are not optimized. */ +/* { dg-do compile } */ +/* { dg-skip-if "avoid conflicting multilib options" { *-*-* } { "-marm" "-mcpu=*" } } */ +/* { dg-options "-march=armv8.1-m.main -O3 --save-temps" } */ +#include +#include "lob.h" + +int a[N]; +int b[N]; +int c[N]; + +int +main (void) +{ + for (int i = 0; i < N; i++) + { + a[i] = i; + b[i] = i * 2; + + int k = b[i]; + while (k != 0) + { + if (k % 2 == 0) + c[i - 1] = k % 2; + k /= 2; + } + c[i] = a[i] - b[i]; + } + + return 0; +} +/* { dg-final { scan-assembler-not {dls\s\S*,\s\S*} } } */ +/* { dg-final { scan-assembler-not {le\slr,\s\S*} } } */ diff --git a/gcc/testsuite/gcc.target/arm/lob6.c b/gcc/testsuite/gcc.target/arm/lob6.c new file mode 100644 index 000000000000..95db2f47efe9 --- /dev/null +++ b/gcc/testsuite/gcc.target/arm/lob6.c @@ -0,0 +1,97 @@ +/* Check that GCC generates Armv8.1-M low over head loop instructions + with some less trivial loops and the result is correct. */ +/* { dg-do run } */ +/* { dg-require-effective-target arm_v8_1_lob_ok } */ +/* { dg-skip-if "avoid conflicting multilib options" { *-*-* } { "-marm" "-mcpu=*" } } */ +/* { dg-options "-march=armv8.1-m.main -O3 --save-temps" } */ +#include +#include "lob.h" + +#define TEST_CODE1 \ + { \ + for (int i = 0; i < N; i++) \ + { \ + a[i] = i; \ + b[i] = i * 2; \ + \ + for (int k = 0; k < N; k++) \ + { \ + MAYBE_LOB; \ + c[k] = k / 2; \ + } \ + c[i] = a[i] - b[i]; \ + } \ + } + +#define TEST_CODE2 \ + { \ + for (int i = 0; i < N / 2; i++) \ + { \ + MAYBE_LOB; \ + if (c[i] % 2 == 0) \ + break; \ + a[i]++; \ + b[i]++; \ + } \ + } + +int a1[N]; +int b1[N]; +int c1[N]; + +int a2[N]; +int b2[N]; +int c2[N]; + +#define MAYBE_LOB +void __attribute__((noinline)) +loop1 (int *a, int *b, int *c) + TEST_CODE1; + +void __attribute__((noinline)) +loop2 (int *a, int *b, int *c) + TEST_CODE2; + +#undef MAYBE_LOB +#define MAYBE_LOB NO_LOB + +void +ref1 (int *a, int *b, int *c) + TEST_CODE1; + +void +ref2 (int *a, int *b, int *c) + TEST_CODE2; + +void +check (void) +{ + for (int i = 0; i < N; i++) + { + NO_LOB; + if (a1[i] != a2[i] + && b1[i] != b2[i] + && c1[i] != c2[i]) + abort (); + } +} + +int +main (void) +{ + reset_data (a1, b1, c1); + reset_data (a2, b2, c2); + loop1 (a1, b1, c1); + ref1 (a2, b2, c2); + check (); + + reset_data (a1, b1, c1); + reset_data (a2, b2, c2); + loop2 (a1, b1, c1); + ref2 (a2, b2, c2); + check (); + + return 0; +} +/* { dg-final { scan-assembler-times {dls\s\S*,\s\S*} 2 } } */ +/* { dg-final { scan-assembler-times {le\slr,\s\S*} 2 } } */ diff --git a/gcc/testsuite/lib/target-supports.exp b/gcc/testsuite/lib/target-supports.exp index d3b2798df3e8..51669aac1327 100644 --- a/gcc/testsuite/lib/target-supports.exp +++ b/gcc/testsuite/lib/target-supports.exp @@ -9807,6 +9807,28 @@ proc check_effective_target_arm_v8_3a_bkey_directive { } { }] } +# Return 1 if the target supports executing the Armv8.1-M Mainline Low +# Overhead Loop, 0 otherwise. The test is valid for ARM. + +proc check_effective_target_arm_v8_1_lob_ok { } { + if { ![istarget arm*-*-*] } { + return 0; + } else { + return [check_runtime arm_v8_1_lob_hw_available { + int + main (void) + { int i = 0; + asm ("movw r3, #10\n\t" /* movs? */ + "dls lr, r3" : : : "r3", "lr"); + loop: + i++; + asm goto ("le lr, %l0" : : : "lr" : loop); + return i != 10; + } + } "-march=armv8.1-m.main" ] + } +} + # Returns 1 if the target is using glibc, 0 otherwise. proc check_effective_target_glibc { } {