[3/3] c-family: support complete unrolling with #pragma unroll [PR91840]

Message ID 20261001182331.62094-4-shamrocklee@posteo.net
State New
Headers
Series c-family: support unqualified #pragma unroll [PR91840] |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gcc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap success Build passed
linaro-tcwg-bot/tcwg_gcc_check--master-arm success Test passed

Commit Message

Yueh-Shun Li Oct. 1, 2026, 6:25 p.m. UTC
  Support preprocessing directive "#pragma unroll" without explicit
n_unroll as an alias for "pragma GCC unroll (USHRT_MAX - 1)",
completely unrolling loops with trip count <= USHRT_MAX - 1 .

Provide an interface for GCC's unrolling pragma
with inter-compiler support.

Compilers like Clang, NVCC, and IBM XL
supports "#pragma unroll N" with the N being the unroll factor
applying to loops with compile-time constant trip counts, [1][3][4]
but with some notable behavioural difference:
* GCC, if this patch is applied,
  will only unroll loops completely if (trip_count <= USHRT_MAX - 1),
  and will otherwise decide on the unroll factor heuristically.
* Clang has upper limits guarding [2]
  * unrolled body size (PragmaUnrollThreshold)
  * trip counts (PragmaUnrollFullMaxIterations (default 1,000,000))
  When exceeded, the loop won't be completely unrolled.
* IBM Open XL C/C++ Compiler will decide on the unroll factor
  instead of unrolling completely when n is not specified. [5]

[1]: https://clang.llvm.org/docs/AttributeReference.html#pragma-unroll-pragma-nounroll
[2]: https://github.com/llvm/llvm-project/blob/afb54cc942d5fccf63f5594a6808f21eb179db6f/llvm/lib/Transforms/Scalar/LoopUnrollPass.cpp
[3]: https://docs.nvidia.com/cuda/cuda-programming-guide/05-appendices/cpp-language-extensions.html#pragma-unroll
[4]: https://www.ibm.com/docs/en/ent-metalc-zos/3.1.0?topic=descriptions-pragma-unroll
[5]: https://www.ibm.com/docs/en/SSRZSMX_17.1.3/pdf/user.pdf

gcc/ChangeLog:

	PR c++/91840
	* doc/extend.texi: document complete-unrolling #pragma unroll

gcc/c/ChangeLog:

	PR c++/91840
	* c-parser.cc (c_parser_pragma_unroll): Add
	bool parameter is_unqualified to differentiate
	PRAGMA_UNROLL and PRAGMA_UNROLL_UNQUALIFIED.
	#pragma unroll without the constant is now an alias for
	#pragma GCC unroll (USHRT_MAX - 1), which completely unrolls any loop
	with a build-time constant trip count less than USHRT_MAX.

gcc/cp/ChangeLog:

	PR c++/91840
	* parser.cc (cp_parser_pragma_unroll): Add
	bool parameter is_unqualified to differentiate
	PRAGMA_UNROLL and PRAGMA_UNROLL_UNQUALIFIED.
	#pragma unroll without the constant is now an alias for
	#pragma GCC unroll (USHRT_MAX - 1), which completely unrolls any loop
	with a build-time constant trip count less than USHRT_MAX.

gcc/testsuite/ChangeLog:

	PR c++/91840
	* c-c++-common/unroll-1-unqualified-complete.c: New test from the
	complete unrolling part of unroll-1.c but for #pragma unroll
	* c-c++-common/unroll-8-unqualified-complete.c: New test
	complete unrolling trip count limit for #pragma unroll
	* g++.dg/parse/pr126323-unroll-unqualified-complete.C: New test
	like pr126323.C (stacked pragmas outside function)
	but for #pragma unroll

Signed-off-by: Yueh-Shun Li <shamrocklee@posteo.net>
---
 gcc/c/c-parser.cc                             | 16 ++++++----
 gcc/cp/parser.cc                              | 17 +++++++++--
 gcc/doc/extend.texi                           | 12 ++++++++
 .../unroll-1-unqualified-complete.c           | 24 +++++++++++++++
 .../unroll-8-unqualified-complete.c           | 29 +++++++++++++++++++
 .../pr126323-unroll-unqualified-complete.C    | 13 +++++++++
 6 files changed, 104 insertions(+), 7 deletions(-)
 create mode 100644 gcc/testsuite/c-c++-common/unroll-1-unqualified-complete.c
 create mode 100644 gcc/testsuite/c-c++-common/unroll-8-unqualified-complete.c
 create mode 100644 gcc/testsuite/g++.dg/parse/pr126323-unroll-unqualified-complete.C
  

Patch

diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 28f098e2c33..360ed8b57d0 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -16030,10 +16030,16 @@  c_parse_pragma_novector (c_parser *parser)
 /* Parse a pragma GCC unroll or a pragma unroll.  */
 
 static unsigned short
-c_parser_pragma_unroll (c_parser *parser)
+c_parser_pragma_unroll (c_parser *parser, bool is_unqualified)
 {
   unsigned short unroll;
   c_parser_consume_pragma (parser);
+  if (is_unqualified && c_parser_peek_token (parser)->type == CPP_PRAGMA_EOL)
+    {
+      c_parser_skip_to_pragma_eol (parser);
+      unroll = USHRT_MAX - 1;
+      return unroll;
+    }
   location_t location = c_parser_peek_token (parser)->location;
   tree expr = c_parser_expr_no_commas (parser, NULL).value;
   mark_exp_read (expr);
@@ -16304,10 +16310,10 @@  c_parser_pragma (c_parser *parser, enum pragma_context context, bool *if_p,
 	    novector = c_parse_pragma_novector (parser);
 	    break;
 	  case PRAGMA_UNROLL:
-	    unroll = c_parser_pragma_unroll (parser);
+	    unroll = c_parser_pragma_unroll (parser, false);
 	    break;
 	  case PRAGMA_UNROLL_UNQUALIFIED:
-	    unroll = c_parser_pragma_unroll (parser);
+	    unroll = c_parser_pragma_unroll (parser, true);
 	    break;
 	  case PRAGMA_IVDEP:
 	    ivdep = c_parse_pragma_ivdep (parser);
@@ -16326,10 +16332,10 @@  c_parser_pragma (c_parser *parser, enum pragma_context context, bool *if_p,
 		ivdep = c_parse_pragma_ivdep (parser);
 		break;
 	      case PRAGMA_UNROLL:
-		unroll = c_parser_pragma_unroll (parser);
+		unroll = c_parser_pragma_unroll (parser, false);
 		break;
 	      case PRAGMA_UNROLL_UNQUALIFIED:
-		unroll = c_parser_pragma_unroll (parser);
+		unroll = c_parser_pragma_unroll (parser, true);
 		break;
 	      case PRAGMA_NOVECTOR:
 		novector = c_parse_pragma_novector (parser);
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index bcdc422fd1c..458fe99fd08 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -57389,8 +57389,15 @@  cp_parser_pragma_ivdep (cp_parser *parser, cp_token *pragma_tok)
 /* Parse a pragma GCC unroll.  */
 
 static tree
-cp_parser_pragma_unroll (cp_parser *parser, cp_token *pragma_tok)
+cp_parser_pragma_unroll (cp_parser *parser, cp_token *pragma_tok,
+			    bool is_unqualified)
 {
+  if (is_unqualified
+      && cp_lexer_peek_token (parser->lexer)->type == CPP_PRAGMA_EOL)
+    {
+      cp_parser_skip_to_pragma_eol (parser, pragma_tok);
+      return build_int_cst (integer_type_node, USHRT_MAX - 1);
+    }
   location_t location = cp_lexer_peek_token (parser->lexer)->location;
   tree unroll = cp_parser_constant_expression (parser);
   unroll = cp_check_pragma_unroll (location, fold_non_dependent_expr (unroll));
@@ -57817,11 +57824,17 @@  cp_parser_pragma (cp_parser *parser, enum pragma_context context, bool *if_p)
 		    break;
 		  }
 		case PRAGMA_UNROLL_UNQUALIFIED:
+		  {
+		    if (tok != pragma_tok)
+		      tok = cp_lexer_consume_token (parser->lexer);
+		    unroll = cp_parser_pragma_unroll (parser, tok, true);
+		    break;
+		  }
 		case PRAGMA_UNROLL:
 		  {
 		    if (tok != pragma_tok)
 		      tok = cp_lexer_consume_token (parser->lexer);
-		    unroll = cp_parser_pragma_unroll (parser, tok);
+		    unroll = cp_parser_pragma_unroll (parser, tok, false);
 		    break;
 		  }
 		case PRAGMA_NOVECTOR:
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index a380bb70ac2..b440e6f5cdb 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -10895,6 +10895,18 @@  target costing.
 This pragma is an alias for @code{#pragma GCC unroll @var{n}} for
 inter-compiler compatibility.
 
+@cindex pragma, unroll
+@item #pragma unroll
+
+This pragma completely unrolls a loop with a compile-time constant trip count
+less than @code{USHRT_MAX - 1}.
+
+Loops with even larger trip counts will be unrolled
+with heuristic unrolling factors instead of complete unrolling.
+
+Note that no code-size limit is applied on the presence of this pragma --
+use with care on loops with large trip counts or loop bodies.
+
 @end table
 
 @node Thread-Local
diff --git a/gcc/testsuite/c-c++-common/unroll-1-unqualified-complete.c b/gcc/testsuite/c-c++-common/unroll-1-unqualified-complete.c
new file mode 100644
index 00000000000..feba40dfd93
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/unroll-1-unqualified-complete.c
@@ -0,0 +1,24 @@ 
+/*
+  <#pragma unroll> basic tests
+  taken from unroll-1.c
+ */
+
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-cunrolli-details -fdump-rtl-loop2_unroll-details" } */
+
+extern void bar (int);
+
+int j;
+
+void test (void)
+{
+  #pragma unroll
+  for (unsigned long i = 1; i <= 8; ++i)
+    bar(i);
+  /* { dg-final { scan-tree-dump "16:.*: loop with 8 iterations completely unrolled" "cunrolli" } } */
+
+  #pragma unroll
+  for (unsigned long i = 1; i <= 7; ++i)
+    bar(i);
+  /* { dg-final { scan-tree-dump "21:.*: loop with 7 iterations completely unrolled" "cunrolli" } } */
+}
diff --git a/gcc/testsuite/c-c++-common/unroll-8-unqualified-complete.c b/gcc/testsuite/c-c++-common/unroll-8-unqualified-complete.c
new file mode 100644
index 00000000000..03bdc98bc8e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/unroll-8-unqualified-complete.c
@@ -0,0 +1,29 @@ 
+/*
+  <#pragma unroll> trip count limit test
+  Ensure that "#pragma unroll" completely unrolls a loop with trip count 0xFFFE
+*/
+/* { dg-do compile } */
+/* { dg-options "-O2 -fdump-tree-cunrolli-details -fdump-rtl-loop2_unroll-details" } */
+
+/*
+  It takes ~5min to run on an Intel N200 processor.
+  Request 4x time in case it runs slow.
+*/
+/* { dg-timeout-factor 4 } */
+/* { dg-require-effective-target run_expensive_tests } */
+
+extern void bar (int);
+
+int j;
+
+void test (void)
+{
+  /*
+    USHRT_MAX - 1 >= 0xFFFE
+    0xFFFE == 65534
+  */
+  #pragma unroll
+  for (unsigned long i = 1; i <= 0xFFFE; ++i)
+    bar(i);
+  /* { dg-final { scan-tree-dump "26:.*: loop with 65534 iterations completely unrolled" "cunrolli" } } */
+}
diff --git a/gcc/testsuite/g++.dg/parse/pr126323-unroll-unqualified-complete.C b/gcc/testsuite/g++.dg/parse/pr126323-unroll-unqualified-complete.C
new file mode 100644
index 00000000000..e0a8ee39013
--- /dev/null
+++ b/gcc/testsuite/g++.dg/parse/pr126323-unroll-unqualified-complete.C
@@ -0,0 +1,13 @@ 
+/*
+  Derived from pr126323.C but tests
+  `#pragma unroll` (complete-unrolling pragma)
+*/
+
+class C {
+#pragma GCC novector	// { dg-error "must be inside a function" }
+#pragma unroll	// { dg-error "must be inside a function" }
+#pragma GCC ivdep	// { dg-error "must be inside a function" }
+  for (int i = 0; i < 2; i++)	// { dg-error "expected unqualified-id" }
+				// { dg-error "does not name a type" "" { target *-*-* } .-1 }
+    ;
+};