[2/3] c-family: support #pragma unroll N [PR91840]

Message ID 20261001182331.62094-3-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

Commit Message

Yueh-Shun Li Oct. 1, 2026, 6:24 p.m. UTC
  Support preprocessing directive "#pragma unroll N" as an alias for
"#pragma GCC unroll N".

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]
only with slightly different limitations:
* The upper limit of GCC is (USHRT_MAX - 1).
  This is decided in the implementation prior to these changes.
* Clang skips all limitation regarding size or trip counts. [2]
  The only limitation left is asserting
  (AllowRemainder || trip_count % N == 0) [2]
* The upper limit of NVCC is (INT_MAX). [3]
* The upper limit of IBM XL C/C++ is not documented publicly.

[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

gcc/ChangeLog:

	PR c++/91840
	* doc/extend.texi: document #pragma unroll (n)

gcc/c-family/ChangeLog:

	PR c++/91840
	* c-pragma.cc
	(init_pragma): Register the unqualified "<pragma unroll N>".
	* c-pragma.h
	(pragma_kind): Add "PRAGMA_UNROLL_UNQUALIFIED".

gcc/c/ChangeLog:

	PR c++/91840
	* c-parser.cc (c_parser_pragma): Handle
	new case PRAGMA_UNROLL_UNQUALIFIED for #pragma unroll.
	#pragma unroll (n) is now an alias for #pragma GCC unroll (n).

gcc/cp/ChangeLog:

	PR c++/91840
	* parser.cc (cp_parser_pragma): Handle
	new case PRAGMA_UNROLL_UNQUALIFIED for #pragma unroll.
	#pragma unroll (n) is now an alias for #pragma GCC unroll (n).

gcc/testsuite/ChangeLog:

	PR c++/91840
	* c-c++-common/unroll-1-unqualified-constant.c: New test
	like unroll-1.c (basic unrolling) but for #pragma unroll N
	* c-c++-common/unroll-4-unqualified-constant.c: New test
	like unroll-4.c (stacked pragmas and no unrolling)
	but for #pragma unroll N
	* c-c++-common/unroll-5-unqualified-constant.c: New test
	like unroll-5.c (rolling factor syntax) but for #pragma unroll N
	* g++.dg/parse/pr126323-unroll-unqualified-constant.C: New test
	like pr126323.C (stacked pragmas outside function)
	but for #pragma unroll N

Signed-off-by: Yueh-Shun Li <shamrocklee@posteo.net>
---
 gcc/c-family/c-pragma.cc                      |  4 ++
 gcc/c-family/c-pragma.h                       |  1 +
 gcc/c/c-parser.cc                             |  9 +++-
 gcc/cp/parser.cc                              |  3 ++
 gcc/doc/extend.texi                           |  6 +++
 .../unroll-1-unqualified-constant.c           | 46 +++++++++++++++++++
 .../unroll-4-unqualified-constant.c           | 31 +++++++++++++
 .../unroll-5-unqualified-constant.c           | 35 ++++++++++++++
 .../pr126323-unroll-unqualified-constant.C    | 12 +++++
 9 files changed, 146 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/c-c++-common/unroll-1-unqualified-constant.c
 create mode 100644 gcc/testsuite/c-c++-common/unroll-4-unqualified-constant.c
 create mode 100644 gcc/testsuite/c-c++-common/unroll-5-unqualified-constant.c
 create mode 100644 gcc/testsuite/g++.dg/parse/pr126323-unroll-unqualified-constant.C
  

Patch

diff --git a/gcc/c-family/c-pragma.cc b/gcc/c-family/c-pragma.cc
index d0d4ff69fd7..bcdc81fd0a4 100644
--- a/gcc/c-family/c-pragma.cc
+++ b/gcc/c-family/c-pragma.cc
@@ -1862,6 +1862,10 @@  init_pragma (void)
     cpp_register_deferred_pragma (parse_in, "GCC", "unroll", PRAGMA_UNROLL,
 				  false, false);
 
+  if (!flag_preprocess_only)
+    cpp_register_deferred_pragma (parse_in, NULL, "unroll",
+				  PRAGMA_UNROLL_UNQUALIFIED, false, false);
+
   if (!flag_preprocess_only)
     cpp_register_deferred_pragma (parse_in, "GCC", "novector", PRAGMA_NOVECTOR,
 				  false, false);
diff --git a/gcc/c-family/c-pragma.h b/gcc/c-family/c-pragma.h
index 4d1afe93959..b158ee86347 100644
--- a/gcc/c-family/c-pragma.h
+++ b/gcc/c-family/c-pragma.h
@@ -93,6 +93,7 @@  enum pragma_kind {
   PRAGMA_GCC_PCH_PREPROCESS,
   PRAGMA_IVDEP,
   PRAGMA_UNROLL,
+  PRAGMA_UNROLL_UNQUALIFIED,
   PRAGMA_NOVECTOR,
 
   PRAGMA_FIRST_EXTERNAL
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 927aff6d5b8..28f098e2c33 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -16027,7 +16027,7 @@  c_parse_pragma_novector (c_parser *parser)
   return true;
 }
 
-/* Parse a pragma GCC unroll.  */
+/* Parse a pragma GCC unroll or a pragma unroll.  */
 
 static unsigned short
 c_parser_pragma_unroll (c_parser *parser)
@@ -16291,6 +16291,7 @@  c_parser_pragma (c_parser *parser, enum pragma_context context, bool *if_p,
 
     case PRAGMA_NOVECTOR:
     case PRAGMA_UNROLL:
+    case PRAGMA_UNROLL_UNQUALIFIED:
     case PRAGMA_IVDEP:
       {
 	bool novector = false;
@@ -16305,6 +16306,9 @@  c_parser_pragma (c_parser *parser, enum pragma_context context, bool *if_p,
 	  case PRAGMA_UNROLL:
 	    unroll = c_parser_pragma_unroll (parser);
 	    break;
+	  case PRAGMA_UNROLL_UNQUALIFIED:
+	    unroll = c_parser_pragma_unroll (parser);
+	    break;
 	  case PRAGMA_IVDEP:
 	    ivdep = c_parse_pragma_ivdep (parser);
 	    break;
@@ -16324,6 +16328,9 @@  c_parser_pragma (c_parser *parser, enum pragma_context context, bool *if_p,
 	      case PRAGMA_UNROLL:
 		unroll = c_parser_pragma_unroll (parser);
 		break;
+	      case PRAGMA_UNROLL_UNQUALIFIED:
+		unroll = c_parser_pragma_unroll (parser);
+		break;
 	      case PRAGMA_NOVECTOR:
 		novector = c_parse_pragma_novector (parser);
 		break;
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 373d3358c25..bcdc422fd1c 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -57771,6 +57771,7 @@  cp_parser_pragma (cp_parser *parser, enum pragma_context context, bool *if_p)
 
     case PRAGMA_IVDEP:
     case PRAGMA_UNROLL:
+    case PRAGMA_UNROLL_UNQUALIFIED:
     case PRAGMA_NOVECTOR:
       {
 	bool ivdep = false;
@@ -57783,6 +57784,7 @@  cp_parser_pragma (cp_parser *parser, enum pragma_context context, bool *if_p)
 	  case PRAGMA_IVDEP:
 	    pragma_str = "ivdep";
 	    break;
+	  case PRAGMA_UNROLL_UNQUALIFIED:
 	  case PRAGMA_UNROLL:
 	    pragma_str = "unroll";
 	    break;
@@ -57814,6 +57816,7 @@  cp_parser_pragma (cp_parser *parser, enum pragma_context context, bool *if_p)
 		    ivdep = cp_parser_pragma_ivdep (parser, tok);
 		    break;
 		  }
+		case PRAGMA_UNROLL_UNQUALIFIED:
 		case PRAGMA_UNROLL:
 		  {
 		    if (tok != pragma_tok)
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 88213d25a89..a380bb70ac2 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -10889,6 +10889,12 @@  loop is unrolled or not will be determined by target costing.  The resulting
 vectorized loop may still be unrolled more in later passes depending on the
 target costing.
 
+@cindex pragma, unroll
+@item #pragma unroll @var{n}
+
+This pragma is an alias for @code{#pragma GCC unroll @var{n}} for
+inter-compiler compatibility.
+
 @end table
 
 @node Thread-Local
diff --git a/gcc/testsuite/c-c++-common/unroll-1-unqualified-constant.c b/gcc/testsuite/c-c++-common/unroll-1-unqualified-constant.c
new file mode 100644
index 00000000000..e963e8a6d31
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/unroll-1-unqualified-constant.c
@@ -0,0 +1,46 @@ 
+/*
+  <#pragma unroll N> 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 8
+  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 8
+  for (unsigned long i = 1; i <= 7; ++i)
+    bar(i);
+  /* { dg-final { scan-tree-dump "21:.*: loop with 7 iterations completely unrolled" "cunrolli" } } */
+
+  #pragma unroll 8
+  for (unsigned long i = 1; i <= 15; ++i)
+    bar(i);
+  /* { dg-final { scan-rtl-dump "26:.*: optimized: loop unrolled 7 times" "loop2_unroll" } } */
+
+  #pragma unroll 8
+  for (unsigned long i = 1; i <= j; ++i)
+    bar(i);
+  /* { dg-final { scan-rtl-dump "31:.*: optimized: loop unrolled 7 times" "loop2_unroll" } } */
+
+  #pragma unroll 7
+  for (unsigned long i = 1; i <= j; ++i)
+    bar(i);
+  /* { dg-final { scan-rtl-dump "36:.*: optimized: loop unrolled 3 times" "loop2_unroll" } } */
+
+  unsigned long i = 0;
+  #pragma unroll 3
+  do {
+    bar(i);
+  } while (++i < 9);
+  /* { dg-final { scan-rtl-dump "4\[24\]:.*: optimized: loop unrolled 2 times" "loop2_unroll" } } */
+}
diff --git a/gcc/testsuite/c-c++-common/unroll-4-unqualified-constant.c b/gcc/testsuite/c-c++-common/unroll-4-unqualified-constant.c
new file mode 100644
index 00000000000..58922cdd9ab
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/unroll-4-unqualified-constant.c
@@ -0,0 +1,31 @@ 
+/*
+  <#pragma unroll N> no-unrolling tests
+  taken from unroll-4.c
+ */
+
+/* { dg-do compile } */
+/* { dg-options "-O2 -funroll-all-loops -fdump-rtl-loop2_unroll-details -fdump-tree-cunrolli-details" } */
+
+extern void bar (int);
+
+int j;
+
+void test (void)
+{
+  #pragma unroll 0
+  #pragma GCC ivdep
+  for (unsigned long i = 1; i <= 3; ++i)
+    bar(i);
+
+  #pragma GCC ivdep
+  #pragma unroll 0
+  for (unsigned long i = 1; i <= j; ++i)
+    bar(i);
+
+  #pragma unroll 1
+  for (unsigned long i = 1; i <= j; ++i)
+    bar(i);
+
+  /* { dg-final { scan-tree-dump "Not unrolling loop .: user didn't want it unrolled completely" "cunrolli" } } */
+  /* { dg-final { scan-rtl-dump-times "Not unrolling loop, user didn't want it unrolled" 3 "loop2_unroll" } } */
+}
diff --git a/gcc/testsuite/c-c++-common/unroll-5-unqualified-constant.c b/gcc/testsuite/c-c++-common/unroll-5-unqualified-constant.c
new file mode 100644
index 00000000000..f7240a50d3a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/unroll-5-unqualified-constant.c
@@ -0,0 +1,35 @@ 
+/*
+  <#pragma unroll N> syntax tests
+  taken from unroll-5.c
+ */
+
+/* { dg-do compile } */
+
+extern void bar (int);
+
+int j;
+
+void test (void)
+{
+  #pragma unroll 4+4
+  for (unsigned long i = 1; i <= 8; ++i)
+    bar(i);
+
+  #pragma unroll -1	/* { dg-error "requires an assignment-expression that evaluates to a non-negative integral constant less than" } */
+  for (unsigned long i = 1; i <= 8; ++i)
+    bar(i);
+
+  #pragma unroll 20000000000	/* { dg-error "requires an assignment-expression that evaluates to a non-negative integral constant less than" } */
+  for (unsigned long i = 1; i <= 8; ++i)
+    bar(i);
+
+  #pragma unroll j	/* { dg-error "requires an assignment-expression that evaluates to a non-negative integral constant less than" } */
+                        /* { dg-error "cannot appear in a constant-expression|is not usable in a constant expression" "" { target c++ } .-1 } */
+  for (unsigned long i = 1; i <= 8; ++i)
+    bar(i);
+
+  #pragma unroll  4.2	/* { dg-error "requires an assignment-expression that evaluates to a non-negative integral constant less than" } */
+  for (unsigned long i = 1; i <= 8; ++i)
+    bar(i);
+}
+
diff --git a/gcc/testsuite/g++.dg/parse/pr126323-unroll-unqualified-constant.C b/gcc/testsuite/g++.dg/parse/pr126323-unroll-unqualified-constant.C
new file mode 100644
index 00000000000..d9fc5aff379
--- /dev/null
+++ b/gcc/testsuite/g++.dg/parse/pr126323-unroll-unqualified-constant.C
@@ -0,0 +1,12 @@ 
+/*
+  Derived from pr126323.C but tests `#pragma unroll N`
+*/
+
+class C {
+#pragma GCC novector	// { dg-error "must be inside a function" }
+#pragma unroll(0)	// { 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 }
+    ;
+};