Skip to content

[Clang] Allow the value of unroll count to be zero in #pragma GCC unroll and #pragma unroll #88666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 19, 2024

Conversation

yronglin
Copy link
Contributor

Fixes #88624

GCC allows the value of loop unroll count to be zero, and the values of 0 and 1 block any unrolling of the loop. This PR aims to make clang keeps the same behavior with GCC.
https://gcc.gnu.org/onlinedocs/gcc/Loop-Specific-Pragmas.html

@yronglin yronglin requested a review from Endilll as a code owner April 14, 2024 16:39
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. labels Apr 14, 2024
@llvmbot
Copy link
Member

llvmbot commented Apr 14, 2024

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: None (yronglin)

Changes

Fixes #88624

GCC allows the value of loop unroll count to be zero, and the values of 0 and 1 block any unrolling of the loop. This PR aims to make clang keeps the same behavior with GCC.
https://gcc.gnu.org/onlinedocs/gcc/Loop-Specific-Pragmas.html


Full diff: https://github.com/llvm/llvm-project/pull/88666.diff

9 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+4)
  • (modified) clang/include/clang/Sema/Sema.h (+2-1)
  • (modified) clang/lib/CodeGen/CGLoopInfo.cpp (+2)
  • (modified) clang/lib/Parse/ParsePragma.cpp (+4-3)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+10-2)
  • (modified) clang/lib/Sema/SemaStmtAttr.cpp (+14-5)
  • (modified) clang/lib/Sema/SemaTemplateInstantiate.cpp (+2-1)
  • (modified) clang/test/CodeGenCXX/pragma-gcc-unroll.cpp (+22)
  • (modified) clang/test/Parser/pragma-unroll.cpp (+6-2)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 45a9a79739a4eb..26119e0a788a24 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -420,6 +420,10 @@ Bug Fixes in This Version
 - Fixed a regression in CTAD that a friend declaration that befriends itself may cause
   incorrect constraint substitution. (#GH86769).
 
+- Clang now allowed the value of unroll count to be zero in ``#pragma GCC unroll`` and ``#pragma unroll``. 
+  The values of 0 and 1 block any unrolling of the loop. This keeps the same behavior with GCC.
+  Fixes (`#88624 <https://github.com/llvm/llvm-project/issues/88624>`_).
+
 Bug Fixes to Compiler Builtins
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index c4a603cc5a4a74..d7447470112186 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -5449,7 +5449,8 @@ class Sema final : public SemaBase {
   ExprResult ActOnPredefinedExpr(SourceLocation Loc, tok::TokenKind Kind);
   ExprResult ActOnIntegerConstant(SourceLocation Loc, uint64_t Val);
 
-  bool CheckLoopHintExpr(Expr *E, SourceLocation Loc);
+  bool CheckLoopHintExpr(Expr *E, SourceLocation Loc,
+                         const IdentifierInfo *PragmaNameInfo);
 
   ExprResult ActOnNumericConstant(const Token &Tok, Scope *UDLScope = nullptr);
   ExprResult ActOnCharacterConstant(const Token &Tok,
diff --git a/clang/lib/CodeGen/CGLoopInfo.cpp b/clang/lib/CodeGen/CGLoopInfo.cpp
index 0d4800b90a2f26..72d1471021ac02 100644
--- a/clang/lib/CodeGen/CGLoopInfo.cpp
+++ b/clang/lib/CodeGen/CGLoopInfo.cpp
@@ -673,6 +673,8 @@ void LoopInfoStack::push(BasicBlock *Header, clang::ASTContext &Ctx,
         setPipelineDisabled(true);
         break;
       case LoopHintAttr::UnrollCount:
+        setUnrollState(LoopAttributes::Disable);
+        break;
       case LoopHintAttr::UnrollAndJamCount:
       case LoopHintAttr::VectorizeWidth:
       case LoopHintAttr::InterleaveCount:
diff --git a/clang/lib/Parse/ParsePragma.cpp b/clang/lib/Parse/ParsePragma.cpp
index 0f692e2146a490..ee7ff12a6f3a24 100644
--- a/clang/lib/Parse/ParsePragma.cpp
+++ b/clang/lib/Parse/ParsePragma.cpp
@@ -1568,7 +1568,8 @@ bool Parser::HandlePragmaLoopHint(LoopHint &Hint) {
       ConsumeToken(); // Consume the constant expression eof terminator.
 
       if (Arg2Error || R.isInvalid() ||
-          Actions.CheckLoopHintExpr(R.get(), Toks[0].getLocation()))
+          Actions.CheckLoopHintExpr(R.get(), Toks[0].getLocation(),
+                                    PragmaNameInfo))
         return false;
 
       // Argument is a constant expression with an integer type.
@@ -1592,8 +1593,8 @@ bool Parser::HandlePragmaLoopHint(LoopHint &Hint) {
 
     ConsumeToken(); // Consume the constant expression eof terminator.
 
-    if (R.isInvalid() ||
-        Actions.CheckLoopHintExpr(R.get(), Toks[0].getLocation()))
+    if (R.isInvalid() || Actions.CheckLoopHintExpr(
+                             R.get(), Toks[0].getLocation(), PragmaNameInfo))
       return false;
 
     // Argument is a constant expression with an integer type.
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index ec5ca2b9352ed4..8df53cbdaaf1ac 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -3880,7 +3880,8 @@ static Expr *BuildFloatingLiteral(Sema &S, NumericLiteralParser &Literal,
   return FloatingLiteral::Create(S.Context, Val, isExact, Ty, Loc);
 }
 
-bool Sema::CheckLoopHintExpr(Expr *E, SourceLocation Loc) {
+bool Sema::CheckLoopHintExpr(Expr *E, SourceLocation Loc,
+                             const IdentifierInfo *PragmaNameInfo) {
   assert(E && "Invalid expression");
 
   if (E->isValueDependent())
@@ -3898,7 +3899,14 @@ bool Sema::CheckLoopHintExpr(Expr *E, SourceLocation Loc) {
   if (R.isInvalid())
     return true;
 
-  bool ValueIsPositive = ValueAPS.isStrictlyPositive();
+  // GCC allows the value of unroll count to be 0.
+  // https://gcc.gnu.org/onlinedocs/gcc/Loop-Specific-Pragmas.html says
+  // "The values of 0 and 1 block any unrolling of the loop."
+  // The values doesn't have to be strictly positive in '#pragma GCC unroll' and
+  // '#pragma unroll' cases.
+  bool ValueIsPositive = PragmaNameInfo->isStr("unroll")
+                             ? ValueAPS.isNonNegative()
+                             : ValueAPS.isStrictlyPositive();
   if (!ValueIsPositive || ValueAPS.getActiveBits() > 31) {
     Diag(E->getExprLoc(), diag::err_pragma_loop_invalid_argument_value)
         << toString(ValueAPS, 10) << ValueIsPositive;
diff --git a/clang/lib/Sema/SemaStmtAttr.cpp b/clang/lib/Sema/SemaStmtAttr.cpp
index a0339273a0ba35..403843daa4616e 100644
--- a/clang/lib/Sema/SemaStmtAttr.cpp
+++ b/clang/lib/Sema/SemaStmtAttr.cpp
@@ -109,9 +109,17 @@ static Attr *handleLoopHintAttr(Sema &S, Stmt *St, const ParsedAttr &A,
     SetHints(LoopHintAttr::Unroll, LoopHintAttr::Disable);
   } else if (PragmaName == "unroll") {
     // #pragma unroll N
-    if (ValueExpr)
-      SetHints(LoopHintAttr::UnrollCount, LoopHintAttr::Numeric);
-    else
+    if (ValueExpr) {
+      llvm::APSInt ValueAPS;
+      ExprResult R = S.VerifyIntegerConstantExpression(ValueExpr, &ValueAPS);
+      assert(!R.isInvalid() && "unroll count value must be a valid value, it's "
+                               "should be checked in Sema::CheckLoopHintExpr");
+      // The values of 0 and 1 block any unrolling of the loop.
+      if (ValueAPS.isZero() || ValueAPS.isOne())
+        SetHints(LoopHintAttr::UnrollCount, LoopHintAttr::Disable);
+      else
+        SetHints(LoopHintAttr::UnrollCount, LoopHintAttr::Numeric);
+    } else
       SetHints(LoopHintAttr::Unroll, LoopHintAttr::Enable);
   } else if (PragmaName == "nounroll_and_jam") {
     SetHints(LoopHintAttr::UnrollAndJam, LoopHintAttr::Disable);
@@ -142,7 +150,8 @@ static Attr *handleLoopHintAttr(Sema &S, Stmt *St, const ParsedAttr &A,
     if (Option == LoopHintAttr::VectorizeWidth) {
       assert((ValueExpr || (StateLoc && StateLoc->Ident)) &&
              "Attribute must have a valid value expression or argument.");
-      if (ValueExpr && S.CheckLoopHintExpr(ValueExpr, St->getBeginLoc()))
+      if (ValueExpr &&
+          S.CheckLoopHintExpr(ValueExpr, St->getBeginLoc(), OptionLoc->Ident))
         return nullptr;
       if (StateLoc && StateLoc->Ident && StateLoc->Ident->isStr("scalable"))
         State = LoopHintAttr::ScalableWidth;
@@ -152,7 +161,7 @@ static Attr *handleLoopHintAttr(Sema &S, Stmt *St, const ParsedAttr &A,
                Option == LoopHintAttr::UnrollCount ||
                Option == LoopHintAttr::PipelineInitiationInterval) {
       assert(ValueExpr && "Attribute must have a valid value expression.");
-      if (S.CheckLoopHintExpr(ValueExpr, St->getBeginLoc()))
+      if (S.CheckLoopHintExpr(ValueExpr, St->getBeginLoc(), OptionLoc->Ident))
         return nullptr;
       State = LoopHintAttr::Numeric;
     } else if (Option == LoopHintAttr::Vectorize ||
diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp
index a265fd1c46a63e..e6499ef8c23468 100644
--- a/clang/lib/Sema/SemaTemplateInstantiate.cpp
+++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp
@@ -2147,7 +2147,8 @@ TemplateInstantiator::TransformLoopHintAttr(const LoopHintAttr *LH) {
     return LH;
 
   // Generate error if there is a problem with the value.
-  if (getSema().CheckLoopHintExpr(TransformedExpr, LH->getLocation()))
+  if (getSema().CheckLoopHintExpr(TransformedExpr, LH->getLocation(),
+                                  LH->getAttrName()))
     return LH;
 
   // Create new LoopHintValueAttr with integral expression in place of the
diff --git a/clang/test/CodeGenCXX/pragma-gcc-unroll.cpp b/clang/test/CodeGenCXX/pragma-gcc-unroll.cpp
index ed75e0b6e3c364..8a94a5cc91e239 100644
--- a/clang/test/CodeGenCXX/pragma-gcc-unroll.cpp
+++ b/clang/test/CodeGenCXX/pragma-gcc-unroll.cpp
@@ -96,6 +96,26 @@ void template_test(double *List, int Length) {
   for_template_define_test<double>(List, Length, Value);
 }
 
+void for_unroll_zero_test(int *List, int Length) {
+  // CHECK: define {{.*}} @_Z20for_unroll_zero_testPii
+  #pragma GCC unroll 0
+  for (int i = 0; i < Length; i++) {
+    // CHECK: br label {{.*}}, !llvm.loop ![[LOOP_14:.*]]
+    List[i] = i * 2;
+  }
+}
+
+void while_unroll_zero_test(int *List, int Length) {
+  // CHECK: define {{.*}} @_Z22while_unroll_zero_testPii
+  int i = 0;
+#pragma GCC unroll(0)
+  while (i < Length) {
+    // CHECK: br label {{.*}}, !llvm.loop ![[LOOP_15:.*]]
+    List[i] = i * 2;
+    i++;
+  }
+}
+
 // CHECK: ![[LOOP_1]] = distinct !{![[LOOP_1]], [[MP:![0-9]+]], ![[UNROLL_ENABLE:.*]]}
 // CHECK: ![[UNROLL_ENABLE]] = !{!"llvm.loop.unroll.enable"}
 // CHECK: ![[LOOP_2]] = distinct !{![[LOOP_2:.*]], ![[UNROLL_DISABLE:.*]]}
@@ -107,3 +127,5 @@ void template_test(double *List, int Length) {
 // CHECK: ![[LOOP_5]] = distinct !{![[LOOP_5]], ![[UNROLL_8:.*]]}
 // CHECK: ![[LOOP_6]] = distinct !{![[LOOP_6]], ![[UNROLL_8:.*]]}
 // CHECK: ![[LOOP_7]] = distinct !{![[LOOP_7]], ![[UNROLL_8:.*]]}
+// CHECK: ![[LOOP_14]] = distinct !{![[LOOP_14]], [[MP]], ![[UNROLL_DISABLE:.*]]}
+// CHECK: ![[LOOP_15]] = distinct !{![[LOOP_15]], [[MP]], ![[UNROLL_DISABLE:.*]]}
diff --git a/clang/test/Parser/pragma-unroll.cpp b/clang/test/Parser/pragma-unroll.cpp
index c89cf49a002065..f41bd7a18d5a41 100644
--- a/clang/test/Parser/pragma-unroll.cpp
+++ b/clang/test/Parser/pragma-unroll.cpp
@@ -40,14 +40,18 @@ void test(int *List, int Length) {
 
 /* expected-error {{expected ')'}} */ #pragma unroll(()
 /* expected-error {{expected expression}} */ #pragma unroll -
-/* expected-error {{invalid value '0'; must be positive}} */ #pragma unroll(0)
-/* expected-error {{invalid value '0'; must be positive}} */ #pragma unroll 0
+/* The values of 0 and 1 block any unrolling of the loop. */ #pragma unroll 0
 /* expected-error {{value '3000000000' is too large}} */ #pragma unroll(3000000000)
 /* expected-error {{value '3000000000' is too large}} */ #pragma unroll 3000000000
   while (i-8 < Length) {
     List[i] = i;
   }
 
+/* The values of 0 and 1 block any unrolling of the loop. */ #pragma unroll(0)
+  while (i-8 < Length) {
+    List[i] = i;
+  }
+
 #pragma unroll
 /* expected-error {{expected a for, while, or do-while loop to follow '#pragma unroll'}} */ int j = Length;
 #pragma unroll 4

@yronglin yronglin changed the title [Clang] Allow the value of unroll count to be zero in '#pragma GCC unroll' and '#pragma unroll' [Clang] Allow the value of unroll count to be zero in #pragma GCC unroll and #pragma unroll Apr 14, 2024
@Endilll
Copy link
Contributor

Endilll commented Apr 14, 2024

Sema.h changes look good.

@yronglin
Copy link
Contributor Author

Thanks for your review! @Endilll @tbaederr

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 nit, else this lgtm.

// "The values of 0 and 1 block any unrolling of the loop."
// The values doesn't have to be strictly positive in '#pragma GCC unroll' and
// '#pragma unroll' cases.
bool ValueIsPositive = PragmaNameInfo->isStr("unroll")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd very much like to keep this checking of the name OUT of Sema here, and have the function just take a bool for IsPragmaUnroll or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your review! I've add a bool parameter AllowZero here. I want to go further, should we move all the string checks in Sema::handleLoopHintAttr to Parser::HandlePragmaLoopHint? (

if (PragmaName == "nounroll") {
) , these checks used to classify LoopHintAttr::OptionType and LoopHintAttr::LoopHintState, I'd like to classification in Parser::HandlePragmaLoopHint and pass the result into Sema::handleLoopHintAttr through ParsedAttr.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I don't think we should do those changes in THIS patch, but I'd be ok with a followup patch to do that movement.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'll do it in a separate patch!

@yronglin
Copy link
Contributor Author

Wait for CI green, but seems windows bots fall into a trouble.

@erichkeane
Copy link
Collaborator

Wait for CI green, but seems windows bots fall into a trouble.

Windows bot is typically pretty far behind(usually 4-5 hrs during the day), but 13 hours is a heck of a delay! I think all we can do is wait :/

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM assuming precommit CI on Windows eventually comes back green.

@yronglin
Copy link
Contributor Author

LGTM assuming precommit CI on Windows eventually comes back green.

Thanks for your review!

@yronglin
Copy link
Contributor Author

Hope CI can be come back soon

@yronglin
Copy link
Contributor Author

yronglin commented Apr 19, 2024

Thanks for all the reviewers! The last 833f2df CI was green, but there was a conflict in ReleaseNotes. After I merged the main branch and resolved the conflict, there was a build issue with CI, but it was not caused by this PR(Seems the root cause is flang). therefor, I'll merge this PR.

@yronglin yronglin merged commit f4bbcac into llvm:main Apr 19, 2024
@alexfh
Copy link
Contributor

alexfh commented Apr 20, 2024

After this patch clang doesn't accept integer template argument as a parameter of #pragma unroll (https://gcc.godbolt.org/z/Woc7zs3sK):

template<int Unroll>
void test(int *List, int Length) {
  int i = 0;
#pragma unroll Unroll
  while (i + 1 < Length) {
    List[i] = i;
  }
}
<source>:4:16: error: expression is not an integral constant expression
    4 | #pragma unroll Unroll
      |                ^~~~~~

Please fix soon or revert. Thanks!

@ronlieb
Copy link
Contributor

ronlieb commented Apr 21, 2024

our downstream CI for amdgpu build of rccl, rocblas and rocSolver are seeing this assertions due to this patch
[2024-04-21T03:28:05.859Z] clang-19: /jenkins/workspace/compiler-psdb-amd-staging/repos/external/llvm-project/clang/lib/AST/ExprConstant.cpp:15721: bool clang::Expr::EvaluateAsRValue(clang::Expr::EvalResult&, const clang::ASTContext&, bool) const: Assertion `!isValueDependent() && "Expression evaluator can't be called on a dependent expression."' failed.

if a reproducer is needed, we can try and get one , bit of effort. let me know.

@Endilll
Copy link
Contributor

Endilll commented Apr 21, 2024

@ronlieb nice and small reproducer would definitely help resolving this.

@yronglin yronglin deleted the loop_unroll_zero branch April 22, 2024 03:38
@yronglin yronglin restored the loop_unroll_zero branch April 22, 2024 03:38
@yronglin yronglin deleted the loop_unroll_zero branch April 22, 2024 03:39
@shiltian
Copy link
Contributor

@alexfh @ronlieb @Endilll fix in #89567.

@yronglin
Copy link
Contributor Author

Thank you report this issue! @ronlieb Could you please provide a simple reproducer, we can use it to strengthen clang's test.

@alexfh
Copy link
Contributor

alexfh commented Apr 22, 2024

Thanks for the prompt fix!

searlmc1 pushed a commit to ROCm/llvm-project that referenced this pull request Apr 25, 2024
…roll` and `#pragma unroll` (llvm#88666)

Fixes llvm#88624

GCC allows the value of loop unroll count to be zero, and the values of
0 and 1 block any unrolling of the loop. This PR aims to make clang
keeps the same behavior with GCC.
https://gcc.gnu.org/onlinedocs/gcc/Loop-Specific-Pragmas.html

---------

Signed-off-by: yronglin <[email protected]>
Change-Id: I7974f28337c270c8239f0e24ea16c09bc3c21c80
@alexfh
Copy link
Contributor

alexfh commented Apr 26, 2024

There's one more questionable effect of this commit (together with the fix in #89567). Some of our code started triggering "loop not unrolled" warnings. A reduced example is at https://godbolt.org/z/jG4xsGY7s

#include <cstddef>
#include <bit>

template <bool Flag>
int FailToBuild(int n) {
  constexpr int N = 100;
  auto init = [=]() { return Flag ? n : 0UL; };
  auto cond = [=](size_t ix) { return Flag ? ix != 0 : ix < 10; };
  auto iter = [=](size_t ix) {
    return Flag ? ix & ~(1ULL << std::countr_zero(ix)) : ix + 1;
  };
#pragma unroll Flag ? 1 : N
  for (size_t ix = init(); cond(ix); ix = iter(ix)) {
    n *= n;
  }
  return n;
}

int foo(int n) {
    return FailToBuild<true>(n);
}

int bar(int n) {
    return FailToBuild<false>(n);
}

clang -std=c++20 -O3 started generating the following warning on this code after the commit:

<source>:13:3: warning: loop not unrolled: the optimizer was unable to perform the requested transformation; the transformation might be disabled or specified as part of an unsupported transformation ordering [-Wpass-failed=transform-warning]
   13 |   for (size_t ix = init(); cond(ix); ix = iter(ix)) {

It doesn't seem the generated code has actually changed, thus, the question is whether these are legit warnings that used to be missed, or vice versa?

@yronglin
Copy link
Contributor Author

yronglin commented Apr 26, 2024

Thank you for your report, I will find out the cause as soon as possible.

@yronglin
Copy link
Contributor Author

Sorry, I didn't find this problem last time I reviewed #89567. @alexfh Seems this issue introduced by #89567, I've a PR to fix it #90240.

@yronglin
Copy link
Contributor Author

@alexfh fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

#pragma GCC unroll 0 not allowed: invalid value '0'; must be positive
9 participants