| 1 | //===--- SemaStmtAttr.cpp - Statement Attribute Handling ------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file implements stmt-related attribute processing. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "clang/AST/ASTContext.h" |
| 14 | #include "clang/AST/EvaluatedExprVisitor.h" |
| 15 | #include "clang/Basic/TargetInfo.h" |
| 16 | #include "clang/Sema/DelayedDiagnostic.h" |
| 17 | #include "clang/Sema/ParsedAttr.h" |
| 18 | #include "clang/Sema/ScopeInfo.h" |
| 19 | #include <optional> |
| 20 | |
| 21 | using namespace clang; |
| 22 | using namespace sema; |
| 23 | |
| 24 | static Attr *handleFallThroughAttr(Sema &S, Stmt *St, const ParsedAttr &A, |
| 25 | SourceRange Range) { |
| 26 | FallThroughAttr Attr(S.Context, A); |
| 27 | if (isa<SwitchCase>(Val: St)) { |
| 28 | S.Diag(Loc: A.getRange().getBegin(), DiagID: diag::err_fallthrough_attr_wrong_target) |
| 29 | << A << St->getBeginLoc(); |
| 30 | SourceLocation L = S.getLocForEndOfToken(Loc: Range.getEnd()); |
| 31 | S.Diag(Loc: L, DiagID: diag::note_fallthrough_insert_semi_fixit) |
| 32 | << FixItHint::CreateInsertion(InsertionLoc: L, Code: ";" ); |
| 33 | return nullptr; |
| 34 | } |
| 35 | auto *FnScope = S.getCurFunction(); |
| 36 | if (FnScope->SwitchStack.empty()) { |
| 37 | S.Diag(Loc: A.getRange().getBegin(), DiagID: diag::err_fallthrough_attr_outside_switch); |
| 38 | return nullptr; |
| 39 | } |
| 40 | |
| 41 | // If this is spelled as the standard C++17 attribute, but not in C++17, warn |
| 42 | // about using it as an extension. |
| 43 | if (!S.getLangOpts().CPlusPlus17 && A.isCXX11Attribute() && |
| 44 | !A.getScopeName()) |
| 45 | S.Diag(Loc: A.getLoc(), DiagID: diag::ext_cxx17_attr) << A; |
| 46 | |
| 47 | FnScope->setHasFallthroughStmt(); |
| 48 | return ::new (S.Context) FallThroughAttr(S.Context, A); |
| 49 | } |
| 50 | |
| 51 | static Attr *handleSuppressAttr(Sema &S, Stmt *St, const ParsedAttr &A, |
| 52 | SourceRange Range) { |
| 53 | if (A.getAttributeSpellingListIndex() == SuppressAttr::CXX11_gsl_suppress && |
| 54 | A.getNumArgs() < 1) { |
| 55 | // Suppression attribute with GSL spelling requires at least 1 argument. |
| 56 | S.Diag(Loc: A.getLoc(), DiagID: diag::err_attribute_too_few_arguments) << A << 1; |
| 57 | return nullptr; |
| 58 | } |
| 59 | |
| 60 | std::vector<StringRef> DiagnosticIdentifiers; |
| 61 | for (unsigned I = 0, E = A.getNumArgs(); I != E; ++I) { |
| 62 | StringRef RuleName; |
| 63 | |
| 64 | if (!S.checkStringLiteralArgumentAttr(Attr: A, ArgNum: I, Str&: RuleName, ArgLocation: nullptr)) |
| 65 | return nullptr; |
| 66 | |
| 67 | DiagnosticIdentifiers.push_back(x: RuleName); |
| 68 | } |
| 69 | |
| 70 | return ::new (S.Context) SuppressAttr( |
| 71 | S.Context, A, DiagnosticIdentifiers.data(), DiagnosticIdentifiers.size()); |
| 72 | } |
| 73 | |
| 74 | static Attr *handleLoopHintAttr(Sema &S, Stmt *St, const ParsedAttr &A, |
| 75 | SourceRange) { |
| 76 | IdentifierLoc *PragmaNameLoc = A.getArgAsIdent(Arg: 0); |
| 77 | IdentifierLoc *OptionLoc = A.getArgAsIdent(Arg: 1); |
| 78 | IdentifierLoc *StateLoc = A.getArgAsIdent(Arg: 2); |
| 79 | Expr *ValueExpr = A.getArgAsExpr(Arg: 3); |
| 80 | |
| 81 | StringRef PragmaName = |
| 82 | llvm::StringSwitch<StringRef>( |
| 83 | PragmaNameLoc->getIdentifierInfo()->getName()) |
| 84 | .Cases(S0: "unroll" , S1: "nounroll" , S2: "unroll_and_jam" , S3: "nounroll_and_jam" , |
| 85 | Value: PragmaNameLoc->getIdentifierInfo()->getName()) |
| 86 | .Default(Value: "clang loop" ); |
| 87 | |
| 88 | // This could be handled automatically by adding a Subjects definition in |
| 89 | // Attr.td, but that would make the diagnostic behavior worse in this case |
| 90 | // because the user spells this attribute as a pragma. |
| 91 | if (!isa<DoStmt, ForStmt, CXXForRangeStmt, WhileStmt>(Val: St)) { |
| 92 | std::string Pragma = "#pragma " + std::string(PragmaName); |
| 93 | S.Diag(Loc: St->getBeginLoc(), DiagID: diag::err_pragma_loop_precedes_nonloop) << Pragma; |
| 94 | return nullptr; |
| 95 | } |
| 96 | |
| 97 | LoopHintAttr::OptionType Option; |
| 98 | LoopHintAttr::LoopHintState State; |
| 99 | |
| 100 | auto SetHints = [&Option, &State](LoopHintAttr::OptionType O, |
| 101 | LoopHintAttr::LoopHintState S) { |
| 102 | Option = O; |
| 103 | State = S; |
| 104 | }; |
| 105 | |
| 106 | if (PragmaName == "nounroll" ) { |
| 107 | SetHints(LoopHintAttr::Unroll, LoopHintAttr::Disable); |
| 108 | } else if (PragmaName == "unroll" ) { |
| 109 | // #pragma unroll N |
| 110 | if (ValueExpr) { |
| 111 | if (!ValueExpr->isValueDependent()) { |
| 112 | auto Value = ValueExpr->EvaluateKnownConstInt(Ctx: S.getASTContext()); |
| 113 | if (Value.isZero() || Value.isOne()) |
| 114 | SetHints(LoopHintAttr::Unroll, LoopHintAttr::Disable); |
| 115 | else |
| 116 | SetHints(LoopHintAttr::UnrollCount, LoopHintAttr::Numeric); |
| 117 | } else |
| 118 | SetHints(LoopHintAttr::UnrollCount, LoopHintAttr::Numeric); |
| 119 | } else |
| 120 | SetHints(LoopHintAttr::Unroll, LoopHintAttr::Enable); |
| 121 | } else if (PragmaName == "nounroll_and_jam" ) { |
| 122 | SetHints(LoopHintAttr::UnrollAndJam, LoopHintAttr::Disable); |
| 123 | } else if (PragmaName == "unroll_and_jam" ) { |
| 124 | // #pragma unroll_and_jam N |
| 125 | if (ValueExpr) |
| 126 | SetHints(LoopHintAttr::UnrollAndJamCount, LoopHintAttr::Numeric); |
| 127 | else |
| 128 | SetHints(LoopHintAttr::UnrollAndJam, LoopHintAttr::Enable); |
| 129 | } else { |
| 130 | // #pragma clang loop ... |
| 131 | assert(OptionLoc && OptionLoc->getIdentifierInfo() && |
| 132 | "Attribute must have valid option info." ); |
| 133 | Option = llvm::StringSwitch<LoopHintAttr::OptionType>( |
| 134 | OptionLoc->getIdentifierInfo()->getName()) |
| 135 | .Case(S: "vectorize" , Value: LoopHintAttr::Vectorize) |
| 136 | .Case(S: "vectorize_width" , Value: LoopHintAttr::VectorizeWidth) |
| 137 | .Case(S: "interleave" , Value: LoopHintAttr::Interleave) |
| 138 | .Case(S: "vectorize_predicate" , Value: LoopHintAttr::VectorizePredicate) |
| 139 | .Case(S: "interleave_count" , Value: LoopHintAttr::InterleaveCount) |
| 140 | .Case(S: "unroll" , Value: LoopHintAttr::Unroll) |
| 141 | .Case(S: "unroll_count" , Value: LoopHintAttr::UnrollCount) |
| 142 | .Case(S: "pipeline" , Value: LoopHintAttr::PipelineDisabled) |
| 143 | .Case(S: "pipeline_initiation_interval" , |
| 144 | Value: LoopHintAttr::PipelineInitiationInterval) |
| 145 | .Case(S: "distribute" , Value: LoopHintAttr::Distribute) |
| 146 | .Default(Value: LoopHintAttr::Vectorize); |
| 147 | if (Option == LoopHintAttr::VectorizeWidth) { |
| 148 | assert((ValueExpr || (StateLoc && StateLoc->getIdentifierInfo())) && |
| 149 | "Attribute must have a valid value expression or argument." ); |
| 150 | if (ValueExpr && S.CheckLoopHintExpr(E: ValueExpr, Loc: St->getBeginLoc(), |
| 151 | /*AllowZero=*/false)) |
| 152 | return nullptr; |
| 153 | if (StateLoc && StateLoc->getIdentifierInfo() && |
| 154 | StateLoc->getIdentifierInfo()->isStr(Str: "scalable" )) |
| 155 | State = LoopHintAttr::ScalableWidth; |
| 156 | else |
| 157 | State = LoopHintAttr::FixedWidth; |
| 158 | } else if (Option == LoopHintAttr::InterleaveCount || |
| 159 | Option == LoopHintAttr::UnrollCount || |
| 160 | Option == LoopHintAttr::PipelineInitiationInterval) { |
| 161 | assert(ValueExpr && "Attribute must have a valid value expression." ); |
| 162 | if (S.CheckLoopHintExpr(E: ValueExpr, Loc: St->getBeginLoc(), |
| 163 | /*AllowZero=*/false)) |
| 164 | return nullptr; |
| 165 | State = LoopHintAttr::Numeric; |
| 166 | } else if (Option == LoopHintAttr::Vectorize || |
| 167 | Option == LoopHintAttr::Interleave || |
| 168 | Option == LoopHintAttr::VectorizePredicate || |
| 169 | Option == LoopHintAttr::Unroll || |
| 170 | Option == LoopHintAttr::Distribute || |
| 171 | Option == LoopHintAttr::PipelineDisabled) { |
| 172 | assert(StateLoc && StateLoc->getIdentifierInfo() && |
| 173 | "Loop hint must have an argument" ); |
| 174 | if (StateLoc->getIdentifierInfo()->isStr(Str: "disable" )) |
| 175 | State = LoopHintAttr::Disable; |
| 176 | else if (StateLoc->getIdentifierInfo()->isStr(Str: "assume_safety" )) |
| 177 | State = LoopHintAttr::AssumeSafety; |
| 178 | else if (StateLoc->getIdentifierInfo()->isStr(Str: "full" )) |
| 179 | State = LoopHintAttr::Full; |
| 180 | else if (StateLoc->getIdentifierInfo()->isStr(Str: "enable" )) |
| 181 | State = LoopHintAttr::Enable; |
| 182 | else |
| 183 | llvm_unreachable("bad loop hint argument" ); |
| 184 | } else |
| 185 | llvm_unreachable("bad loop hint" ); |
| 186 | } |
| 187 | |
| 188 | return LoopHintAttr::CreateImplicit(Ctx&: S.Context, Option, State, Value: ValueExpr, CommonInfo: A); |
| 189 | } |
| 190 | |
| 191 | namespace { |
| 192 | class CallExprFinder : public ConstEvaluatedExprVisitor<CallExprFinder> { |
| 193 | bool FoundAsmStmt = false; |
| 194 | std::vector<const CallExpr *> CallExprs; |
| 195 | |
| 196 | public: |
| 197 | typedef ConstEvaluatedExprVisitor<CallExprFinder> Inherited; |
| 198 | |
| 199 | CallExprFinder(Sema &S, const Stmt *St) : Inherited(S.Context) { Visit(St); } |
| 200 | |
| 201 | bool foundCallExpr() { return !CallExprs.empty(); } |
| 202 | const std::vector<const CallExpr *> &getCallExprs() { return CallExprs; } |
| 203 | |
| 204 | bool foundAsmStmt() { return FoundAsmStmt; } |
| 205 | |
| 206 | void VisitCallExpr(const CallExpr *E) { CallExprs.push_back(x: E); } |
| 207 | |
| 208 | void VisitAsmStmt(const AsmStmt *S) { FoundAsmStmt = true; } |
| 209 | |
| 210 | void Visit(const Stmt *St) { |
| 211 | if (!St) |
| 212 | return; |
| 213 | ConstEvaluatedExprVisitor<CallExprFinder>::Visit(S: St); |
| 214 | } |
| 215 | }; |
| 216 | } // namespace |
| 217 | |
| 218 | static Attr *handleNoMergeAttr(Sema &S, Stmt *St, const ParsedAttr &A, |
| 219 | SourceRange Range) { |
| 220 | CallExprFinder CEF(S, St); |
| 221 | |
| 222 | if (!CEF.foundCallExpr() && !CEF.foundAsmStmt()) { |
| 223 | S.Diag(Loc: St->getBeginLoc(), DiagID: diag::warn_attribute_ignored_no_calls_in_stmt) |
| 224 | << A; |
| 225 | return nullptr; |
| 226 | } |
| 227 | |
| 228 | return ::new (S.Context) NoMergeAttr(S.Context, A); |
| 229 | } |
| 230 | |
| 231 | static Attr *handleNoConvergentAttr(Sema &S, Stmt *St, const ParsedAttr &A, |
| 232 | SourceRange Range) { |
| 233 | CallExprFinder CEF(S, St); |
| 234 | |
| 235 | if (!CEF.foundCallExpr() && !CEF.foundAsmStmt()) { |
| 236 | S.Diag(Loc: St->getBeginLoc(), DiagID: diag::warn_attribute_ignored_no_calls_in_stmt) |
| 237 | << A; |
| 238 | return nullptr; |
| 239 | } |
| 240 | |
| 241 | return ::new (S.Context) NoConvergentAttr(S.Context, A); |
| 242 | } |
| 243 | |
| 244 | template <typename OtherAttr, int DiagIdx> |
| 245 | static bool CheckStmtInlineAttr(Sema &SemaRef, const Stmt *OrigSt, |
| 246 | const Stmt *CurSt, |
| 247 | const AttributeCommonInfo &A) { |
| 248 | CallExprFinder OrigCEF(SemaRef, OrigSt); |
| 249 | CallExprFinder CEF(SemaRef, CurSt); |
| 250 | |
| 251 | // If the call expressions lists are equal in size, we can skip |
| 252 | // previously emitted diagnostics. However, if the statement has a pack |
| 253 | // expansion, we have no way of telling which CallExpr is the instantiated |
| 254 | // version of the other. In this case, we will end up re-diagnosing in the |
| 255 | // instantiation. |
| 256 | // ie: [[clang::always_inline]] non_dependent(), (other_call<Pack>()...) |
| 257 | // will diagnose nondependent again. |
| 258 | bool CanSuppressDiag = |
| 259 | OrigSt && CEF.getCallExprs().size() == OrigCEF.getCallExprs().size(); |
| 260 | |
| 261 | if (!CEF.foundCallExpr()) { |
| 262 | return SemaRef.Diag(Loc: CurSt->getBeginLoc(), |
| 263 | DiagID: diag::warn_attribute_ignored_no_calls_in_stmt) |
| 264 | << A; |
| 265 | } |
| 266 | |
| 267 | for (const auto &Tup : |
| 268 | llvm::zip_longest(t: OrigCEF.getCallExprs(), u: CEF.getCallExprs())) { |
| 269 | // If the original call expression already had a callee, we already |
| 270 | // diagnosed this, so skip it here. We can't skip if there isn't a 1:1 |
| 271 | // relationship between the two lists of call expressions. |
| 272 | if (!CanSuppressDiag || !(*std::get<0>(t: Tup))->getCalleeDecl()) { |
| 273 | const Decl *Callee = (*std::get<1>(t: Tup))->getCalleeDecl(); |
| 274 | if (Callee && |
| 275 | (Callee->hasAttr<OtherAttr>() || Callee->hasAttr<FlattenAttr>())) { |
| 276 | SemaRef.Diag(Loc: CurSt->getBeginLoc(), |
| 277 | DiagID: diag::warn_function_stmt_attribute_precedence) |
| 278 | << A << (Callee->hasAttr<OtherAttr>() ? DiagIdx : 1); |
| 279 | SemaRef.Diag(Loc: Callee->getBeginLoc(), DiagID: diag::note_conflicting_attribute); |
| 280 | } |
| 281 | } |
| 282 | } |
| 283 | |
| 284 | return false; |
| 285 | } |
| 286 | |
| 287 | bool Sema::CheckNoInlineAttr(const Stmt *OrigSt, const Stmt *CurSt, |
| 288 | const AttributeCommonInfo &A) { |
| 289 | return CheckStmtInlineAttr<AlwaysInlineAttr, 0>(SemaRef&: *this, OrigSt, CurSt, A); |
| 290 | } |
| 291 | |
| 292 | bool Sema::CheckAlwaysInlineAttr(const Stmt *OrigSt, const Stmt *CurSt, |
| 293 | const AttributeCommonInfo &A) { |
| 294 | return CheckStmtInlineAttr<NoInlineAttr, 2>(SemaRef&: *this, OrigSt, CurSt, A); |
| 295 | } |
| 296 | |
| 297 | static Attr *handleNoInlineAttr(Sema &S, Stmt *St, const ParsedAttr &A, |
| 298 | SourceRange Range) { |
| 299 | NoInlineAttr NIA(S.Context, A); |
| 300 | if (!NIA.isStmtNoInline()) { |
| 301 | S.Diag(Loc: St->getBeginLoc(), DiagID: diag::warn_function_attribute_ignored_in_stmt) |
| 302 | << "[[clang::noinline]]" ; |
| 303 | return nullptr; |
| 304 | } |
| 305 | |
| 306 | if (S.CheckNoInlineAttr(/*OrigSt=*/nullptr, CurSt: St, A)) |
| 307 | return nullptr; |
| 308 | |
| 309 | return ::new (S.Context) NoInlineAttr(S.Context, A); |
| 310 | } |
| 311 | |
| 312 | static Attr *handleAlwaysInlineAttr(Sema &S, Stmt *St, const ParsedAttr &A, |
| 313 | SourceRange Range) { |
| 314 | AlwaysInlineAttr AIA(S.Context, A); |
| 315 | if (!AIA.isClangAlwaysInline()) { |
| 316 | S.Diag(Loc: St->getBeginLoc(), DiagID: diag::warn_function_attribute_ignored_in_stmt) |
| 317 | << "[[clang::always_inline]]" ; |
| 318 | return nullptr; |
| 319 | } |
| 320 | |
| 321 | if (S.CheckAlwaysInlineAttr(/*OrigSt=*/nullptr, CurSt: St, A)) |
| 322 | return nullptr; |
| 323 | |
| 324 | return ::new (S.Context) AlwaysInlineAttr(S.Context, A); |
| 325 | } |
| 326 | |
| 327 | static Attr *handleCXXAssumeAttr(Sema &S, Stmt *St, const ParsedAttr &A, |
| 328 | SourceRange Range) { |
| 329 | ExprResult Res = S.ActOnCXXAssumeAttr(St, A, Range); |
| 330 | if (!Res.isUsable()) |
| 331 | return nullptr; |
| 332 | |
| 333 | return ::new (S.Context) CXXAssumeAttr(S.Context, A, Res.get()); |
| 334 | } |
| 335 | |
| 336 | static Attr *handleMustTailAttr(Sema &S, Stmt *St, const ParsedAttr &A, |
| 337 | SourceRange Range) { |
| 338 | // Validation is in Sema::ActOnAttributedStmt(). |
| 339 | return ::new (S.Context) MustTailAttr(S.Context, A); |
| 340 | } |
| 341 | |
| 342 | static Attr *handleLikely(Sema &S, Stmt *St, const ParsedAttr &A, |
| 343 | SourceRange Range) { |
| 344 | |
| 345 | if (!S.getLangOpts().CPlusPlus20 && A.isCXX11Attribute() && !A.getScopeName()) |
| 346 | S.Diag(Loc: A.getLoc(), DiagID: diag::ext_cxx20_attr) << A << Range; |
| 347 | |
| 348 | return ::new (S.Context) LikelyAttr(S.Context, A); |
| 349 | } |
| 350 | |
| 351 | static Attr *handleUnlikely(Sema &S, Stmt *St, const ParsedAttr &A, |
| 352 | SourceRange Range) { |
| 353 | |
| 354 | if (!S.getLangOpts().CPlusPlus20 && A.isCXX11Attribute() && !A.getScopeName()) |
| 355 | S.Diag(Loc: A.getLoc(), DiagID: diag::ext_cxx20_attr) << A << Range; |
| 356 | |
| 357 | return ::new (S.Context) UnlikelyAttr(S.Context, A); |
| 358 | } |
| 359 | |
| 360 | CodeAlignAttr *Sema::BuildCodeAlignAttr(const AttributeCommonInfo &CI, |
| 361 | Expr *E) { |
| 362 | if (!E->isValueDependent()) { |
| 363 | llvm::APSInt ArgVal; |
| 364 | ExprResult Res = VerifyIntegerConstantExpression(E, Result: &ArgVal); |
| 365 | if (Res.isInvalid()) |
| 366 | return nullptr; |
| 367 | E = Res.get(); |
| 368 | |
| 369 | // This attribute requires an integer argument which is a constant power of |
| 370 | // two between 1 and 4096 inclusive. |
| 371 | if (ArgVal < CodeAlignAttr::MinimumAlignment || |
| 372 | ArgVal > CodeAlignAttr::MaximumAlignment || !ArgVal.isPowerOf2()) { |
| 373 | if (std::optional<int64_t> Value = ArgVal.trySExtValue()) |
| 374 | Diag(Loc: CI.getLoc(), DiagID: diag::err_attribute_power_of_two_in_range) |
| 375 | << CI << CodeAlignAttr::MinimumAlignment |
| 376 | << CodeAlignAttr::MaximumAlignment << Value.value(); |
| 377 | else |
| 378 | Diag(Loc: CI.getLoc(), DiagID: diag::err_attribute_power_of_two_in_range) |
| 379 | << CI << CodeAlignAttr::MinimumAlignment |
| 380 | << CodeAlignAttr::MaximumAlignment << E; |
| 381 | return nullptr; |
| 382 | } |
| 383 | } |
| 384 | return new (Context) CodeAlignAttr(Context, CI, E); |
| 385 | } |
| 386 | |
| 387 | static Attr *handleCodeAlignAttr(Sema &S, Stmt *St, const ParsedAttr &A) { |
| 388 | |
| 389 | Expr *E = A.getArgAsExpr(Arg: 0); |
| 390 | return S.BuildCodeAlignAttr(CI: A, E); |
| 391 | } |
| 392 | |
| 393 | // Diagnose non-identical duplicates as a 'conflicting' loop attributes |
| 394 | // and suppress duplicate errors in cases where the two match. |
| 395 | template <typename LoopAttrT> |
| 396 | static void CheckForDuplicateLoopAttrs(Sema &S, ArrayRef<const Attr *> Attrs) { |
| 397 | auto FindFunc = [](const Attr *A) { return isa<const LoopAttrT>(A); }; |
| 398 | const auto *FirstItr = llvm::find_if(Attrs, FindFunc); |
| 399 | |
| 400 | if (FirstItr == Attrs.end()) // no attributes found |
| 401 | return; |
| 402 | |
| 403 | const auto *LastFoundItr = FirstItr; |
| 404 | std::optional<llvm::APSInt> FirstValue; |
| 405 | |
| 406 | const auto *CAFA = |
| 407 | dyn_cast<ConstantExpr>(cast<LoopAttrT>(*FirstItr)->getAlignment()); |
| 408 | // Return early if first alignment expression is dependent (since we don't |
| 409 | // know what the effective size will be), and skip the loop entirely. |
| 410 | if (!CAFA) |
| 411 | return; |
| 412 | |
| 413 | while (Attrs.end() != (LastFoundItr = std::find_if(LastFoundItr + 1, |
| 414 | Attrs.end(), FindFunc))) { |
| 415 | const auto *CASA = |
| 416 | dyn_cast<ConstantExpr>(cast<LoopAttrT>(*LastFoundItr)->getAlignment()); |
| 417 | // If the value is dependent, we can not test anything. |
| 418 | if (!CASA) |
| 419 | return; |
| 420 | // Test the attribute values. |
| 421 | llvm::APSInt SecondValue = CASA->getResultAsAPSInt(); |
| 422 | if (!FirstValue) |
| 423 | FirstValue = CAFA->getResultAsAPSInt(); |
| 424 | |
| 425 | if (FirstValue != SecondValue) { |
| 426 | S.Diag((*LastFoundItr)->getLocation(), diag::err_loop_attr_conflict) |
| 427 | << *FirstItr; |
| 428 | S.Diag((*FirstItr)->getLocation(), diag::note_previous_attribute); |
| 429 | } |
| 430 | } |
| 431 | } |
| 432 | |
| 433 | static Attr *handleMSConstexprAttr(Sema &S, Stmt *St, const ParsedAttr &A, |
| 434 | SourceRange Range) { |
| 435 | if (!S.getLangOpts().isCompatibleWithMSVC(MajorVersion: LangOptions::MSVC2022_3)) { |
| 436 | S.Diag(Loc: A.getLoc(), DiagID: diag::warn_unknown_attribute_ignored) |
| 437 | << A << A.getRange(); |
| 438 | return nullptr; |
| 439 | } |
| 440 | return ::new (S.Context) MSConstexprAttr(S.Context, A); |
| 441 | } |
| 442 | |
| 443 | #define WANT_STMT_MERGE_LOGIC |
| 444 | #include "clang/Sema/AttrParsedAttrImpl.inc" |
| 445 | #undef WANT_STMT_MERGE_LOGIC |
| 446 | |
| 447 | static void |
| 448 | CheckForIncompatibleAttributes(Sema &S, |
| 449 | const SmallVectorImpl<const Attr *> &Attrs) { |
| 450 | // The vast majority of attributed statements will only have one attribute |
| 451 | // on them, so skip all of the checking in the common case. |
| 452 | if (Attrs.size() < 2) |
| 453 | return; |
| 454 | |
| 455 | // First, check for the easy cases that are table-generated for us. |
| 456 | if (!DiagnoseMutualExclusions(S, C: Attrs)) |
| 457 | return; |
| 458 | |
| 459 | enum CategoryType { |
| 460 | // For the following categories, they come in two variants: a state form and |
| 461 | // a numeric form. The state form may be one of default, enable, and |
| 462 | // disable. The numeric form provides an integer hint (for example, unroll |
| 463 | // count) to the transformer. |
| 464 | Vectorize, |
| 465 | Interleave, |
| 466 | UnrollAndJam, |
| 467 | Pipeline, |
| 468 | // For unroll, default indicates full unrolling rather than enabling the |
| 469 | // transformation. |
| 470 | Unroll, |
| 471 | // The loop distribution transformation only has a state form that is |
| 472 | // exposed by #pragma clang loop distribute (enable | disable). |
| 473 | Distribute, |
| 474 | // The vector predication only has a state form that is exposed by |
| 475 | // #pragma clang loop vectorize_predicate (enable | disable). |
| 476 | VectorizePredicate, |
| 477 | // This serves as a indicator to how many category are listed in this enum. |
| 478 | NumberOfCategories |
| 479 | }; |
| 480 | // The following array accumulates the hints encountered while iterating |
| 481 | // through the attributes to check for compatibility. |
| 482 | struct { |
| 483 | const LoopHintAttr *StateAttr; |
| 484 | const LoopHintAttr *NumericAttr; |
| 485 | } HintAttrs[CategoryType::NumberOfCategories] = {}; |
| 486 | |
| 487 | for (const auto *I : Attrs) { |
| 488 | const LoopHintAttr *LH = dyn_cast<LoopHintAttr>(Val: I); |
| 489 | |
| 490 | // Skip non loop hint attributes |
| 491 | if (!LH) |
| 492 | continue; |
| 493 | |
| 494 | CategoryType Category = CategoryType::NumberOfCategories; |
| 495 | LoopHintAttr::OptionType Option = LH->getOption(); |
| 496 | switch (Option) { |
| 497 | case LoopHintAttr::Vectorize: |
| 498 | case LoopHintAttr::VectorizeWidth: |
| 499 | Category = Vectorize; |
| 500 | break; |
| 501 | case LoopHintAttr::Interleave: |
| 502 | case LoopHintAttr::InterleaveCount: |
| 503 | Category = Interleave; |
| 504 | break; |
| 505 | case LoopHintAttr::Unroll: |
| 506 | case LoopHintAttr::UnrollCount: |
| 507 | Category = Unroll; |
| 508 | break; |
| 509 | case LoopHintAttr::UnrollAndJam: |
| 510 | case LoopHintAttr::UnrollAndJamCount: |
| 511 | Category = UnrollAndJam; |
| 512 | break; |
| 513 | case LoopHintAttr::Distribute: |
| 514 | // Perform the check for duplicated 'distribute' hints. |
| 515 | Category = Distribute; |
| 516 | break; |
| 517 | case LoopHintAttr::PipelineDisabled: |
| 518 | case LoopHintAttr::PipelineInitiationInterval: |
| 519 | Category = Pipeline; |
| 520 | break; |
| 521 | case LoopHintAttr::VectorizePredicate: |
| 522 | Category = VectorizePredicate; |
| 523 | break; |
| 524 | }; |
| 525 | |
| 526 | assert(Category != NumberOfCategories && "Unhandled loop hint option" ); |
| 527 | auto &CategoryState = HintAttrs[Category]; |
| 528 | const LoopHintAttr *PrevAttr; |
| 529 | if (Option == LoopHintAttr::Vectorize || |
| 530 | Option == LoopHintAttr::Interleave || Option == LoopHintAttr::Unroll || |
| 531 | Option == LoopHintAttr::UnrollAndJam || |
| 532 | Option == LoopHintAttr::VectorizePredicate || |
| 533 | Option == LoopHintAttr::PipelineDisabled || |
| 534 | Option == LoopHintAttr::Distribute) { |
| 535 | // Enable|Disable|AssumeSafety hint. For example, vectorize(enable). |
| 536 | PrevAttr = CategoryState.StateAttr; |
| 537 | CategoryState.StateAttr = LH; |
| 538 | } else { |
| 539 | // Numeric hint. For example, vectorize_width(8). |
| 540 | PrevAttr = CategoryState.NumericAttr; |
| 541 | CategoryState.NumericAttr = LH; |
| 542 | } |
| 543 | |
| 544 | PrintingPolicy Policy(S.Context.getLangOpts()); |
| 545 | SourceLocation OptionLoc = LH->getRange().getBegin(); |
| 546 | if (PrevAttr) |
| 547 | // Cannot specify same type of attribute twice. |
| 548 | S.Diag(Loc: OptionLoc, DiagID: diag::err_pragma_loop_compatibility) |
| 549 | << /*Duplicate=*/true << PrevAttr->getDiagnosticName(Policy) |
| 550 | << LH->getDiagnosticName(Policy); |
| 551 | |
| 552 | if (CategoryState.StateAttr && CategoryState.NumericAttr && |
| 553 | (Category == Unroll || Category == UnrollAndJam || |
| 554 | CategoryState.StateAttr->getState() == LoopHintAttr::Disable)) { |
| 555 | // Disable hints are not compatible with numeric hints of the same |
| 556 | // category. As a special case, numeric unroll hints are also not |
| 557 | // compatible with enable or full form of the unroll pragma because these |
| 558 | // directives indicate full unrolling. |
| 559 | S.Diag(Loc: OptionLoc, DiagID: diag::err_pragma_loop_compatibility) |
| 560 | << /*Duplicate=*/false |
| 561 | << CategoryState.StateAttr->getDiagnosticName(Policy) |
| 562 | << CategoryState.NumericAttr->getDiagnosticName(Policy); |
| 563 | } |
| 564 | } |
| 565 | } |
| 566 | |
| 567 | static Attr *handleOpenCLUnrollHint(Sema &S, Stmt *St, const ParsedAttr &A, |
| 568 | SourceRange Range) { |
| 569 | // Although the feature was introduced only in OpenCL C v2.0 s6.11.5, it's |
| 570 | // useful for OpenCL 1.x too and doesn't require HW support. |
| 571 | // opencl_unroll_hint can have 0 arguments (compiler |
| 572 | // determines unrolling factor) or 1 argument (the unroll factor provided |
| 573 | // by the user). |
| 574 | unsigned UnrollFactor = 0; |
| 575 | if (A.getNumArgs() == 1) { |
| 576 | Expr *E = A.getArgAsExpr(Arg: 0); |
| 577 | std::optional<llvm::APSInt> ArgVal; |
| 578 | |
| 579 | if (!(ArgVal = E->getIntegerConstantExpr(Ctx: S.Context))) { |
| 580 | S.Diag(Loc: A.getLoc(), DiagID: diag::err_attribute_argument_type) |
| 581 | << A << AANT_ArgumentIntegerConstant << E->getSourceRange(); |
| 582 | return nullptr; |
| 583 | } |
| 584 | |
| 585 | int Val = ArgVal->getSExtValue(); |
| 586 | if (Val <= 0) { |
| 587 | S.Diag(Loc: A.getRange().getBegin(), |
| 588 | DiagID: diag::err_attribute_requires_positive_integer) |
| 589 | << A << /* positive */ 0; |
| 590 | return nullptr; |
| 591 | } |
| 592 | UnrollFactor = static_cast<unsigned>(Val); |
| 593 | } |
| 594 | |
| 595 | return ::new (S.Context) OpenCLUnrollHintAttr(S.Context, A, UnrollFactor); |
| 596 | } |
| 597 | |
| 598 | static Attr *handleHLSLLoopHintAttr(Sema &S, Stmt *St, const ParsedAttr &A, |
| 599 | SourceRange Range) { |
| 600 | |
| 601 | if (A.getSemanticSpelling() == HLSLLoopHintAttr::Spelling::Microsoft_loop && |
| 602 | !A.checkAtMostNumArgs(S, Num: 0)) |
| 603 | return nullptr; |
| 604 | |
| 605 | unsigned UnrollFactor = 0; |
| 606 | if (A.getNumArgs() == 1) { |
| 607 | Expr *E = A.getArgAsExpr(Arg: 0); |
| 608 | |
| 609 | if (S.CheckLoopHintExpr(E, Loc: St->getBeginLoc(), |
| 610 | /*AllowZero=*/false)) |
| 611 | return nullptr; |
| 612 | |
| 613 | std::optional<llvm::APSInt> ArgVal = E->getIntegerConstantExpr(Ctx: S.Context); |
| 614 | // CheckLoopHintExpr handles non int const cases |
| 615 | assert(ArgVal != std::nullopt && "ArgVal should be an integer constant." ); |
| 616 | int Val = ArgVal->getSExtValue(); |
| 617 | // CheckLoopHintExpr handles negative and zero cases |
| 618 | assert(Val > 0 && "Val should be a positive integer greater than zero." ); |
| 619 | UnrollFactor = static_cast<unsigned>(Val); |
| 620 | } |
| 621 | return ::new (S.Context) HLSLLoopHintAttr(S.Context, A, UnrollFactor); |
| 622 | } |
| 623 | |
| 624 | static Attr *handleHLSLControlFlowHint(Sema &S, Stmt *St, const ParsedAttr &A, |
| 625 | SourceRange Range) { |
| 626 | |
| 627 | return ::new (S.Context) HLSLControlFlowHintAttr(S.Context, A); |
| 628 | } |
| 629 | |
| 630 | static Attr *handleAtomicAttr(Sema &S, Stmt *St, const ParsedAttr &AL, |
| 631 | SourceRange Range) { |
| 632 | if (!AL.checkAtLeastNumArgs(S, Num: 1)) |
| 633 | return nullptr; |
| 634 | |
| 635 | SmallVector<AtomicAttr::ConsumedOption, 6> Options; |
| 636 | for (unsigned ArgIndex = 0; ArgIndex < AL.getNumArgs(); ++ArgIndex) { |
| 637 | AtomicAttr::ConsumedOption Option; |
| 638 | StringRef OptionString; |
| 639 | SourceLocation Loc; |
| 640 | |
| 641 | if (!AL.isArgIdent(Arg: ArgIndex)) { |
| 642 | S.Diag(Loc: AL.getArgAsExpr(Arg: ArgIndex)->getBeginLoc(), |
| 643 | DiagID: diag::err_attribute_argument_type) |
| 644 | << AL << AANT_ArgumentIdentifier; |
| 645 | return nullptr; |
| 646 | } |
| 647 | |
| 648 | IdentifierLoc *Ident = AL.getArgAsIdent(Arg: ArgIndex); |
| 649 | OptionString = Ident->getIdentifierInfo()->getName(); |
| 650 | Loc = Ident->getLoc(); |
| 651 | if (!AtomicAttr::ConvertStrToConsumedOption(Val: OptionString, Out&: Option)) { |
| 652 | S.Diag(Loc, DiagID: diag::err_attribute_invalid_atomic_argument) << OptionString; |
| 653 | return nullptr; |
| 654 | } |
| 655 | Options.push_back(Elt: Option); |
| 656 | } |
| 657 | |
| 658 | return ::new (S.Context) |
| 659 | AtomicAttr(S.Context, AL, Options.data(), Options.size()); |
| 660 | } |
| 661 | |
| 662 | static Attr *ProcessStmtAttribute(Sema &S, Stmt *St, const ParsedAttr &A, |
| 663 | SourceRange Range) { |
| 664 | if (A.isInvalid() || A.getKind() == ParsedAttr::IgnoredAttribute) |
| 665 | return nullptr; |
| 666 | |
| 667 | // Unknown attributes are automatically warned on. Target-specific attributes |
| 668 | // which do not apply to the current target architecture are treated as |
| 669 | // though they were unknown attributes. |
| 670 | const TargetInfo *Aux = S.Context.getAuxTargetInfo(); |
| 671 | if (A.getKind() == ParsedAttr::UnknownAttribute || |
| 672 | !(A.existsInTarget(Target: S.Context.getTargetInfo()) || |
| 673 | (S.Context.getLangOpts().SYCLIsDevice && Aux && |
| 674 | A.existsInTarget(Target: *Aux)))) { |
| 675 | if (A.isRegularKeywordAttribute() || A.isDeclspecAttribute()) { |
| 676 | S.Diag(Loc: A.getLoc(), DiagID: A.isRegularKeywordAttribute() |
| 677 | ? diag::err_keyword_not_supported_on_target |
| 678 | : diag::warn_unhandled_ms_attribute_ignored) |
| 679 | << A << A.getRange(); |
| 680 | } else { |
| 681 | S.DiagnoseUnknownAttribute(AL: A); |
| 682 | } |
| 683 | return nullptr; |
| 684 | } |
| 685 | |
| 686 | if (S.checkCommonAttributeFeatures(S: St, A)) |
| 687 | return nullptr; |
| 688 | |
| 689 | switch (A.getKind()) { |
| 690 | case ParsedAttr::AT_AlwaysInline: |
| 691 | return handleAlwaysInlineAttr(S, St, A, Range); |
| 692 | case ParsedAttr::AT_CXXAssume: |
| 693 | return handleCXXAssumeAttr(S, St, A, Range); |
| 694 | case ParsedAttr::AT_FallThrough: |
| 695 | return handleFallThroughAttr(S, St, A, Range); |
| 696 | case ParsedAttr::AT_LoopHint: |
| 697 | return handleLoopHintAttr(S, St, A, Range); |
| 698 | case ParsedAttr::AT_HLSLLoopHint: |
| 699 | return handleHLSLLoopHintAttr(S, St, A, Range); |
| 700 | case ParsedAttr::AT_HLSLControlFlowHint: |
| 701 | return handleHLSLControlFlowHint(S, St, A, Range); |
| 702 | case ParsedAttr::AT_OpenCLUnrollHint: |
| 703 | return handleOpenCLUnrollHint(S, St, A, Range); |
| 704 | case ParsedAttr::AT_Suppress: |
| 705 | return handleSuppressAttr(S, St, A, Range); |
| 706 | case ParsedAttr::AT_NoMerge: |
| 707 | return handleNoMergeAttr(S, St, A, Range); |
| 708 | case ParsedAttr::AT_NoInline: |
| 709 | return handleNoInlineAttr(S, St, A, Range); |
| 710 | case ParsedAttr::AT_MustTail: |
| 711 | return handleMustTailAttr(S, St, A, Range); |
| 712 | case ParsedAttr::AT_Likely: |
| 713 | return handleLikely(S, St, A, Range); |
| 714 | case ParsedAttr::AT_Unlikely: |
| 715 | return handleUnlikely(S, St, A, Range); |
| 716 | case ParsedAttr::AT_CodeAlign: |
| 717 | return handleCodeAlignAttr(S, St, A); |
| 718 | case ParsedAttr::AT_MSConstexpr: |
| 719 | return handleMSConstexprAttr(S, St, A, Range); |
| 720 | case ParsedAttr::AT_NoConvergent: |
| 721 | return handleNoConvergentAttr(S, St, A, Range); |
| 722 | case ParsedAttr::AT_Annotate: |
| 723 | return S.CreateAnnotationAttr(AL: A); |
| 724 | case ParsedAttr::AT_Atomic: |
| 725 | return handleAtomicAttr(S, St, AL: A, Range); |
| 726 | default: |
| 727 | if (Attr *AT = nullptr; A.getInfo().handleStmtAttribute(S, St, Attr: A, Result&: AT) != |
| 728 | ParsedAttrInfo::NotHandled) { |
| 729 | return AT; |
| 730 | } |
| 731 | // N.B., ClangAttrEmitter.cpp emits a diagnostic helper that ensures a |
| 732 | // declaration attribute is not written on a statement, but this code is |
| 733 | // needed for attributes in Attr.td that do not list any subjects. |
| 734 | S.Diag(Loc: A.getRange().getBegin(), DiagID: diag::err_decl_attribute_invalid_on_stmt) |
| 735 | << A << A.isRegularKeywordAttribute() << St->getBeginLoc(); |
| 736 | return nullptr; |
| 737 | } |
| 738 | } |
| 739 | |
| 740 | void Sema::ProcessStmtAttributes(Stmt *S, const ParsedAttributes &InAttrs, |
| 741 | SmallVectorImpl<const Attr *> &OutAttrs) { |
| 742 | for (const ParsedAttr &AL : InAttrs) { |
| 743 | if (const Attr *A = ProcessStmtAttribute(S&: *this, St: S, A: AL, Range: InAttrs.Range)) |
| 744 | OutAttrs.push_back(Elt: A); |
| 745 | } |
| 746 | |
| 747 | CheckForIncompatibleAttributes(S&: *this, Attrs: OutAttrs); |
| 748 | CheckForDuplicateLoopAttrs<CodeAlignAttr>(S&: *this, Attrs: OutAttrs); |
| 749 | } |
| 750 | |
| 751 | bool Sema::CheckRebuiltStmtAttributes(ArrayRef<const Attr *> Attrs) { |
| 752 | CheckForDuplicateLoopAttrs<CodeAlignAttr>(S&: *this, Attrs); |
| 753 | return false; |
| 754 | } |
| 755 | |
| 756 | ExprResult Sema::ActOnCXXAssumeAttr(Stmt *St, const ParsedAttr &A, |
| 757 | SourceRange Range) { |
| 758 | if (A.getNumArgs() != 1 || !A.getArgAsExpr(Arg: 0)) { |
| 759 | Diag(Loc: A.getLoc(), DiagID: diag::err_attribute_wrong_number_arguments) |
| 760 | << A.getAttrName() << 1 << Range; |
| 761 | return ExprError(); |
| 762 | } |
| 763 | |
| 764 | auto *Assumption = A.getArgAsExpr(Arg: 0); |
| 765 | |
| 766 | if (DiagnoseUnexpandedParameterPack(E: Assumption)) { |
| 767 | return ExprError(); |
| 768 | } |
| 769 | |
| 770 | if (Assumption->getDependence() == ExprDependence::None) { |
| 771 | ExprResult Res = BuildCXXAssumeExpr(Assumption, AttrName: A.getAttrName(), Range); |
| 772 | if (Res.isInvalid()) |
| 773 | return ExprError(); |
| 774 | Assumption = Res.get(); |
| 775 | } |
| 776 | |
| 777 | if (!getLangOpts().CPlusPlus23 && |
| 778 | A.getSyntax() == AttributeCommonInfo::AS_CXX11) |
| 779 | Diag(Loc: A.getLoc(), DiagID: diag::ext_cxx23_attr) << A << Range; |
| 780 | |
| 781 | return Assumption; |
| 782 | } |
| 783 | |
| 784 | ExprResult Sema::BuildCXXAssumeExpr(Expr *Assumption, |
| 785 | const IdentifierInfo *AttrName, |
| 786 | SourceRange Range) { |
| 787 | if (!Assumption) |
| 788 | return ExprError(); |
| 789 | |
| 790 | ExprResult Res = CheckPlaceholderExpr(E: Assumption); |
| 791 | if (Res.isInvalid()) |
| 792 | return ExprError(); |
| 793 | |
| 794 | Res = PerformContextuallyConvertToBool(From: Res.get()); |
| 795 | if (Res.isInvalid()) |
| 796 | return ExprError(); |
| 797 | |
| 798 | Assumption = Res.get(); |
| 799 | if (Assumption->HasSideEffects(Ctx: Context)) |
| 800 | Diag(Loc: Assumption->getBeginLoc(), DiagID: diag::warn_assume_side_effects) |
| 801 | << AttrName << Range; |
| 802 | |
| 803 | return Assumption; |
| 804 | } |
| 805 | |