[backport gcc-8,9][arm] Thumb2 out of range conditional branch fix [PR91816]

Stam Markianos-Wright stam.markianos-wright@arm.com
Wed Nov 25 13:48:48 GMT 2020


Hi all,

Now that I have pushed the entirety of this patch to gcc-10 and gcc-11, 
I would like to backport it to gcc-8 and gcc-9.

PR link: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91816

This patch had originally been approved here:

https://gcc.gnu.org/legacy-ml/gcc-patches/2020-01/msg02010.html

See the attached diffs that have been rebased and apply cleanly.

Tested on a cross arm-none-eabi and also in a Cortex A-15 bootstrap with 
no regressions.

Ok to backport?

Thanks,
Stam Markianos-Wright
-------------- next part --------------
diff --git a/gcc/config/arm/arm-protos.h b/gcc/config/arm/arm-protos.h
index 9d0acde7a39..87e01e35221 100644
--- a/gcc/config/arm/arm-protos.h
+++ b/gcc/config/arm/arm-protos.h
@@ -553,4 +553,6 @@ void arm_parse_option_features (sbitmap, const cpu_arch_option *,
 
 void arm_initialize_isa (sbitmap, const enum isa_feature *);
 
+const char * arm_gen_far_branch (rtx *, int, const char * , const char *);
+
 #endif /* ! GCC_ARM_PROTOS_H */
diff --git a/gcc/config/arm/arm.c b/gcc/config/arm/arm.c
index f990ca11bcb..eefe3d99548 100644
--- a/gcc/config/arm/arm.c
+++ b/gcc/config/arm/arm.c
@@ -31629,6 +31629,39 @@ arm_constant_alignment (const_tree exp, HOST_WIDE_INT align)
   return align;
 }
 
+/* Generate code to enable conditional branches in functions over 1 MiB.
+   Parameters are:
+     operands: is the operands list of the asm insn (see arm_cond_branch or
+       arm_cond_branch_reversed).
+     pos_label: is an index into the operands array where operands[pos_label] is
+       the asm label of the final jump destination.
+     dest: is a string which is used to generate the asm label of the intermediate
+       destination
+   branch_format: is a string denoting the intermediate branch format, e.g.
+     "beq", "bne", etc.  */
+
+const char *
+arm_gen_far_branch (rtx * operands, int pos_label, const char * dest,
+		    const char * branch_format)
+{
+  rtx_code_label * tmp_label = gen_label_rtx ();
+  char label_buf[256];
+  char buffer[128];
+  ASM_GENERATE_INTERNAL_LABEL (label_buf, dest , \
+			CODE_LABEL_NUMBER (tmp_label));
+  const char *label_ptr = arm_strip_name_encoding (label_buf);
+  rtx dest_label = operands[pos_label];
+  operands[pos_label] = tmp_label;
+
+  snprintf (buffer, sizeof (buffer), "%s%s", branch_format , label_ptr);
+  output_asm_insn (buffer, operands);
+
+  snprintf (buffer, sizeof (buffer), "b\t%%l0%d\n%s:", pos_label, label_ptr);
+  operands[pos_label] = dest_label;
+  output_asm_insn (buffer, operands);
+  return "";
+}
+
 #if CHECKING_P
 namespace selftest {
 
diff --git a/gcc/config/arm/arm.md b/gcc/config/arm/arm.md
index 6d6b37719e0..81c96658d95 100644
--- a/gcc/config/arm/arm.md
+++ b/gcc/config/arm/arm.md
@@ -7187,9 +7187,15 @@
 ;; And for backward branches we have 
 ;;   (neg_range - neg_base_offs + pc_offs) = (neg_range - (-2 or -4) + 4).
 ;;
+;; In 16-bit Thumb these ranges are:
 ;; For a 'b'       pos_range = 2046, neg_range = -2048 giving (-2040->2048).
 ;; For a 'b<cond>' pos_range = 254,  neg_range = -256  giving (-250 ->256).
 
+;; In 32-bit Thumb these ranges are:
+;; For a 'b'       +/- 16MB is not checked for.
+;; For a 'b<cond>' pos_range = 1048574,  neg_range = -1048576  giving
+;; (-1048568 -> 1048576).
+
 (define_expand "cbranchsi4"
   [(set (pc) (if_then_else
 	      (match_operator 0 "expandable_comparison_operator"
@@ -7444,23 +7450,50 @@
 		      (label_ref (match_operand 0 "" ""))
 		      (pc)))]
   "TARGET_32BIT"
-  "*
-  if (arm_ccfsm_state == 1 || arm_ccfsm_state == 2)
+  {
+    if (arm_ccfsm_state == 1 || arm_ccfsm_state == 2)
     {
       arm_ccfsm_state += 2;
-      return \"\";
+      return "";
     }
-  return \"b%d1\\t%l0\";
-  "
+    switch (get_attr_length (insn))
+      {
+	case 2: /* Thumb2 16-bit b{cond}.  */
+	case 4: /* Thumb2 32-bit b{cond} or A32 b{cond}.  */
+	  return "b%d1\t%l0";
+	  break;
+
+	/* Thumb2 b{cond} out of range.  Use 16-bit b{cond} and
+	   unconditional branch b.  */
+	default: return arm_gen_far_branch (operands, 0, "Lbcond", "b%D1\t");
+      }
+  }
   [(set_attr "conds" "use")
    (set_attr "type" "branch")
    (set (attr "length")
-	(if_then_else
-	   (and (match_test "TARGET_THUMB2")
-		(and (ge (minus (match_dup 0) (pc)) (const_int -250))
-		     (le (minus (match_dup 0) (pc)) (const_int 256))))
-	   (const_int 2)
-	   (const_int 4)))]
+    (if_then_else (match_test "!TARGET_THUMB2")
+
+      ;;Target is not Thumb2, therefore is A32.  Generate b{cond}.
+      (const_int 4)
+
+      ;; Check if target is within 16-bit Thumb2 b{cond} range.
+      (if_then_else (and (ge (minus (match_dup 0) (pc)) (const_int -250))
+		         (le (minus (match_dup 0) (pc)) (const_int 256)))
+
+	;; Target is Thumb2, within narrow range.
+	;; Generate b{cond}.
+	(const_int 2)
+
+	;; Check if target is within 32-bit Thumb2 b{cond} range.
+	(if_then_else (and (ge (minus (match_dup 0) (pc))(const_int -1048568))
+			   (le (minus (match_dup 0) (pc)) (const_int 1048576)))
+
+	  ;; Target is Thumb2, within wide range.
+	  ;; Generate b{cond}
+	  (const_int 4)
+	  ;; Target is Thumb2, out of range.
+	  ;; Generate narrow b{cond} and unconditional branch b.
+	  (const_int 6)))))]
 )
 
 (define_insn "*arm_cond_branch_reversed"
@@ -7470,23 +7503,50 @@
 		      (pc)
 		      (label_ref (match_operand 0 "" ""))))]
   "TARGET_32BIT"
-  "*
-  if (arm_ccfsm_state == 1 || arm_ccfsm_state == 2)
-    {
-      arm_ccfsm_state += 2;
-      return \"\";
-    }
-  return \"b%D1\\t%l0\";
-  "
-  [(set_attr "conds" "use")
+  {
+    if (arm_ccfsm_state == 1 || arm_ccfsm_state == 2)
+      {
+	arm_ccfsm_state += 2;
+	return "";
+      }
+    switch (get_attr_length (insn))
+      {
+	case 2: /* Thumb2 16-bit b{cond}.  */
+	case 4: /* Thumb2 32-bit b{cond} or A32 b{cond}.  */
+	  return "b%D1\t%l0";
+	  break;
+
+	/* Thumb2 b{cond} out of range.  Use 16-bit b{cond} and
+	   unconditional branch b.  */
+	default: return arm_gen_far_branch (operands, 0, "Lbcond", "b%d1\t");
+      }
+  }
+[(set_attr "conds" "use")
    (set_attr "type" "branch")
    (set (attr "length")
-	(if_then_else
-	   (and (match_test "TARGET_THUMB2")
-		(and (ge (minus (match_dup 0) (pc)) (const_int -250))
-		     (le (minus (match_dup 0) (pc)) (const_int 256))))
-	   (const_int 2)
-	   (const_int 4)))]
+    (if_then_else (match_test "!TARGET_THUMB2")
+
+      ;;Target is not Thumb2, therefore is A32.  Generate b{cond}.
+      (const_int 4)
+
+      ;; Check if target is within 16-bit Thumb2 b{cond} range.
+      (if_then_else (and (ge (minus (match_dup 0) (pc)) (const_int -250))
+			 (le (minus (match_dup 0) (pc)) (const_int 256)))
+
+	;; Target is Thumb2, within narrow range.
+	;; Generate b{cond}.
+	(const_int 2)
+
+	;; Check if target is within 32-bit Thumb2 b{cond} range.
+	(if_then_else (and (ge (minus (match_dup 0) (pc))(const_int -1048568))
+			   (le (minus (match_dup 0) (pc)) (const_int 1048576)))
+
+	  ;; Target is Thumb2, within wide range.
+	  ;; Generate b{cond}.
+	  (const_int 4)
+	  ;; Target is Thumb2, out of range.
+	  ;; Generate narrow b{cond} and unconditional branch b.
+	  (const_int 6)))))]
 )
 
 

diff --git a/gcc/testsuite/gcc.target/arm/pr91816.c b/gcc/testsuite/gcc.target/arm/pr91816.c
new file mode 100644
index 00000000000..75b938a6aad
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr91816.c
@@ -0,0 +1,63 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_thumb2_ok } */
+/* { dg-additional-options "-mthumb" }  */
+/* { dg-timeout-factor 4.0 } */
+
+int printf(const char *, ...);
+
+#define HW0	printf("Hello World!\n");
+#define HW1	HW0 HW0 HW0 HW0 HW0 HW0 HW0 HW0 HW0 HW0
+#define HW2	HW1 HW1 HW1 HW1 HW1 HW1 HW1 HW1 HW1 HW1
+#define HW3	HW2 HW2 HW2 HW2 HW2 HW2 HW2 HW2 HW2 HW2
+#define HW4	HW3 HW3 HW3 HW3 HW3 HW3 HW3 HW3 HW3 HW3
+#define HW5	HW4 HW4 HW4 HW4 HW4 HW4 HW4 HW4 HW4 HW4
+
+__attribute__((noinline,noclone)) void f1 (int a)
+{
+  if (a) { HW0 }
+}
+
+__attribute__((noinline,noclone)) void f2 (int a)
+{
+  if (a) { HW3 }
+}
+
+
+__attribute__((noinline,noclone)) void f3 (int a)
+{
+  if (a) { HW5 }
+}
+
+__attribute__((noinline,noclone)) void f4 (int a)
+{
+  if (a == 1) { HW0 }
+}
+
+__attribute__((noinline,noclone)) void f5 (int a)
+{
+  if (a == 1) { HW3 }
+}
+
+
+__attribute__((noinline,noclone)) void f6 (int a)
+{
+  if (a == 1) { HW5 }
+}
+
+
+int main(void)
+{
+	f1(0);
+	f2(0);
+	f3(0);
+	f4(0);
+	f5(0);
+	f6(0);
+	return 0;
+}
+
+
+/* { dg-final { scan-assembler-times "beq\\t.L\[0-9\]" 2 } } */
+/* { dg-final { scan-assembler-times "beq\\t.Lbcond\[0-9\]" 1 } } */
+/* { dg-final { scan-assembler-times "bne\\t.L\[0-9\]" 2 } } */
+/* { dg-final { scan-assembler-times "bne\\t.Lbcond\[0-9\]" 1 } } */
-------------- next part --------------
diff --git a/gcc/config/arm/arm-protos.h b/gcc/config/arm/arm-protos.h
index 98beb6109b9..3e8bba5caea 100644
--- a/gcc/config/arm/arm-protos.h
+++ b/gcc/config/arm/arm-protos.h
@@ -575,4 +575,6 @@ void arm_parse_option_features (sbitmap, const cpu_arch_option *,
 
 void arm_initialize_isa (sbitmap, const enum isa_feature *);
 
+const char * arm_gen_far_branch (rtx *, int, const char * , const char *);
+
 #endif /* ! GCC_ARM_PROTOS_H */
diff --git a/gcc/config/arm/arm.c b/gcc/config/arm/arm.c
index d4e2ad2a317..04edd637d43 100644
--- a/gcc/config/arm/arm.c
+++ b/gcc/config/arm/arm.c
@@ -31807,6 +31807,39 @@ arm_emit_speculation_barrier_function ()
   emit_library_call (speculation_barrier_libfunc, LCT_NORMAL, VOIDmode);
 }
 
+/* Generate code to enable conditional branches in functions over 1 MiB.
+   Parameters are:
+     operands: is the operands list of the asm insn (see arm_cond_branch or
+       arm_cond_branch_reversed).
+     pos_label: is an index into the operands array where operands[pos_label] is
+       the asm label of the final jump destination.
+     dest: is a string which is used to generate the asm label of the intermediate
+       destination
+   branch_format: is a string denoting the intermediate branch format, e.g.
+     "beq", "bne", etc.  */
+
+const char *
+arm_gen_far_branch (rtx * operands, int pos_label, const char * dest,
+		    const char * branch_format)
+{
+  rtx_code_label * tmp_label = gen_label_rtx ();
+  char label_buf[256];
+  char buffer[128];
+  ASM_GENERATE_INTERNAL_LABEL (label_buf, dest , \
+			CODE_LABEL_NUMBER (tmp_label));
+  const char *label_ptr = arm_strip_name_encoding (label_buf);
+  rtx dest_label = operands[pos_label];
+  operands[pos_label] = tmp_label;
+
+  snprintf (buffer, sizeof (buffer), "%s%s", branch_format , label_ptr);
+  output_asm_insn (buffer, operands);
+
+  snprintf (buffer, sizeof (buffer), "b\t%%l0%d\n%s:", pos_label, label_ptr);
+  operands[pos_label] = dest_label;
+  output_asm_insn (buffer, operands);
+  return "";
+}
+
 #if CHECKING_P
 namespace selftest {
 
diff --git a/gcc/config/arm/arm.md b/gcc/config/arm/arm.md
index aa1f23c8e9e..b5df489dca4 100644
--- a/gcc/config/arm/arm.md
+++ b/gcc/config/arm/arm.md
@@ -7313,9 +7313,15 @@
 ;; And for backward branches we have 
 ;;   (neg_range - neg_base_offs + pc_offs) = (neg_range - (-2 or -4) + 4).
 ;;
+;; In 16-bit Thumb these ranges are:
 ;; For a 'b'       pos_range = 2046, neg_range = -2048 giving (-2040->2048).
 ;; For a 'b<cond>' pos_range = 254,  neg_range = -256  giving (-250 ->256).
 
+;; In 32-bit Thumb these ranges are:
+;; For a 'b'       +/- 16MB is not checked for.
+;; For a 'b<cond>' pos_range = 1048574,  neg_range = -1048576  giving
+;; (-1048568 -> 1048576).
+
 (define_expand "cbranchsi4"
   [(set (pc) (if_then_else
 	      (match_operator 0 "expandable_comparison_operator"
@@ -7573,23 +7579,50 @@
 		      (label_ref (match_operand 0 "" ""))
 		      (pc)))]
   "TARGET_32BIT"
-  "*
-  if (arm_ccfsm_state == 1 || arm_ccfsm_state == 2)
+  {
+    if (arm_ccfsm_state == 1 || arm_ccfsm_state == 2)
     {
       arm_ccfsm_state += 2;
-      return \"\";
+      return "";
     }
-  return \"b%d1\\t%l0\";
-  "
+    switch (get_attr_length (insn))
+      {
+	case 2: /* Thumb2 16-bit b{cond}.  */
+	case 4: /* Thumb2 32-bit b{cond} or A32 b{cond}.  */
+	  return "b%d1\t%l0";
+	  break;
+
+	/* Thumb2 b{cond} out of range.  Use 16-bit b{cond} and
+	   unconditional branch b.  */
+	default: return arm_gen_far_branch (operands, 0, "Lbcond", "b%D1\t");
+      }
+  }
   [(set_attr "conds" "use")
    (set_attr "type" "branch")
    (set (attr "length")
-	(if_then_else
-	   (and (match_test "TARGET_THUMB2")
-		(and (ge (minus (match_dup 0) (pc)) (const_int -250))
-		     (le (minus (match_dup 0) (pc)) (const_int 256))))
-	   (const_int 2)
-	   (const_int 4)))]
+    (if_then_else (match_test "!TARGET_THUMB2")
+
+      ;;Target is not Thumb2, therefore is A32.  Generate b{cond}.
+      (const_int 4)
+
+      ;; Check if target is within 16-bit Thumb2 b{cond} range.
+      (if_then_else (and (ge (minus (match_dup 0) (pc)) (const_int -250))
+		         (le (minus (match_dup 0) (pc)) (const_int 256)))
+
+	;; Target is Thumb2, within narrow range.
+	;; Generate b{cond}.
+	(const_int 2)
+
+	;; Check if target is within 32-bit Thumb2 b{cond} range.
+	(if_then_else (and (ge (minus (match_dup 0) (pc))(const_int -1048568))
+			   (le (minus (match_dup 0) (pc)) (const_int 1048576)))
+
+	  ;; Target is Thumb2, within wide range.
+	  ;; Generate b{cond}
+	  (const_int 4)
+	  ;; Target is Thumb2, out of range.
+	  ;; Generate narrow b{cond} and unconditional branch b.
+	  (const_int 6)))))]
 )
 
 (define_insn "*arm_cond_branch_reversed"
@@ -7599,23 +7632,50 @@
 		      (pc)
 		      (label_ref (match_operand 0 "" ""))))]
   "TARGET_32BIT"
-  "*
-  if (arm_ccfsm_state == 1 || arm_ccfsm_state == 2)
-    {
-      arm_ccfsm_state += 2;
-      return \"\";
-    }
-  return \"b%D1\\t%l0\";
-  "
-  [(set_attr "conds" "use")
+  {
+    if (arm_ccfsm_state == 1 || arm_ccfsm_state == 2)
+      {
+	arm_ccfsm_state += 2;
+	return "";
+      }
+    switch (get_attr_length (insn))
+      {
+	case 2: /* Thumb2 16-bit b{cond}.  */
+	case 4: /* Thumb2 32-bit b{cond} or A32 b{cond}.  */
+	  return "b%D1\t%l0";
+	  break;
+
+	/* Thumb2 b{cond} out of range.  Use 16-bit b{cond} and
+	   unconditional branch b.  */
+	default: return arm_gen_far_branch (operands, 0, "Lbcond", "b%d1\t");
+      }
+  }
+[(set_attr "conds" "use")
    (set_attr "type" "branch")
    (set (attr "length")
-	(if_then_else
-	   (and (match_test "TARGET_THUMB2")
-		(and (ge (minus (match_dup 0) (pc)) (const_int -250))
-		     (le (minus (match_dup 0) (pc)) (const_int 256))))
-	   (const_int 2)
-	   (const_int 4)))]
+    (if_then_else (match_test "!TARGET_THUMB2")
+
+      ;;Target is not Thumb2, therefore is A32.  Generate b{cond}.
+      (const_int 4)
+
+      ;; Check if target is within 16-bit Thumb2 b{cond} range.
+      (if_then_else (and (ge (minus (match_dup 0) (pc)) (const_int -250))
+			 (le (minus (match_dup 0) (pc)) (const_int 256)))
+
+	;; Target is Thumb2, within narrow range.
+	;; Generate b{cond}.
+	(const_int 2)
+
+	;; Check if target is within 32-bit Thumb2 b{cond} range.
+	(if_then_else (and (ge (minus (match_dup 0) (pc))(const_int -1048568))
+			   (le (minus (match_dup 0) (pc)) (const_int 1048576)))
+
+	  ;; Target is Thumb2, within wide range.
+	  ;; Generate b{cond}.
+	  (const_int 4)
+	  ;; Target is Thumb2, out of range.
+	  ;; Generate narrow b{cond} and unconditional branch b.
+	  (const_int 6)))))]
 )
 
 

diff --git a/gcc/testsuite/gcc.target/arm/pr91816.c b/gcc/testsuite/gcc.target/arm/pr91816.c
new file mode 100644
index 00000000000..75b938a6aad
--- /dev/null
+++ b/gcc/testsuite/gcc.target/arm/pr91816.c
@@ -0,0 +1,63 @@
+/* { dg-do compile } */
+/* { dg-require-effective-target arm_thumb2_ok } */
+/* { dg-additional-options "-mthumb" }  */
+/* { dg-timeout-factor 4.0 } */
+
+int printf(const char *, ...);
+
+#define HW0	printf("Hello World!\n");
+#define HW1	HW0 HW0 HW0 HW0 HW0 HW0 HW0 HW0 HW0 HW0
+#define HW2	HW1 HW1 HW1 HW1 HW1 HW1 HW1 HW1 HW1 HW1
+#define HW3	HW2 HW2 HW2 HW2 HW2 HW2 HW2 HW2 HW2 HW2
+#define HW4	HW3 HW3 HW3 HW3 HW3 HW3 HW3 HW3 HW3 HW3
+#define HW5	HW4 HW4 HW4 HW4 HW4 HW4 HW4 HW4 HW4 HW4
+
+__attribute__((noinline,noclone)) void f1 (int a)
+{
+  if (a) { HW0 }
+}
+
+__attribute__((noinline,noclone)) void f2 (int a)
+{
+  if (a) { HW3 }
+}
+
+
+__attribute__((noinline,noclone)) void f3 (int a)
+{
+  if (a) { HW5 }
+}
+
+__attribute__((noinline,noclone)) void f4 (int a)
+{
+  if (a == 1) { HW0 }
+}
+
+__attribute__((noinline,noclone)) void f5 (int a)
+{
+  if (a == 1) { HW3 }
+}
+
+
+__attribute__((noinline,noclone)) void f6 (int a)
+{
+  if (a == 1) { HW5 }
+}
+
+
+int main(void)
+{
+	f1(0);
+	f2(0);
+	f3(0);
+	f4(0);
+	f5(0);
+	f6(0);
+	return 0;
+}
+
+
+/* { dg-final { scan-assembler-times "beq\\t.L\[0-9\]" 2 } } */
+/* { dg-final { scan-assembler-times "beq\\t.Lbcond\[0-9\]" 1 } } */
+/* { dg-final { scan-assembler-times "bne\\t.L\[0-9\]" 2 } } */
+/* { dg-final { scan-assembler-times "bne\\t.Lbcond\[0-9\]" 1 } } */


More information about the Gcc-patches mailing list