diff options
author | Sirraide <aeternalmail@gmail.com> | 2024-03-09 12:07:16 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-09 12:07:16 +0100 |
commit | 2b5f68a5f63d2342a056bf9f86bd116c100fd81a (patch) | |
tree | 2831699172fa824863d5d321e9054fa7816bc960 | |
parent | 9df719407f808d71d3bf02867da2b01617ef3d55 (diff) | |
download | llvm-2b5f68a5f63d2342a056bf9f86bd116c100fd81a.zip llvm-2b5f68a5f63d2342a056bf9f86bd116c100fd81a.tar.gz llvm-2b5f68a5f63d2342a056bf9f86bd116c100fd81a.tar.bz2 |
[Clang][C++23] Implement P1774R8: Portable assumptions (#81014)
This implements the C++23 `[[assume]]` attribute.
Assumption information is lowered to a call to `@llvm.assume`, unless the expression has side-effects, in which case it is discarded and a warning is issued to tell the user that the assumption doesn’t do anything. A failed assumption at compile time is an error (unless we are in `MSVCCompat` mode, in which case we don’t check assumptions at compile time).
Due to performance regressions in LLVM, assumptions can be disabled with the `-fno-assumptions` flag. With it, assumptions will still be parsed and checked, but no calls to `@llvm.assume` will be emitted and assumptions will not be checked at compile time.
28 files changed, 502 insertions, 29 deletions
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 690fc7e..f61dca9 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -100,6 +100,7 @@ C++23 Feature Support - Implemented `P2718R0: Lifetime extension in range-based for loops <https://wg21.link/P2718R0>`_. Also materialize temporary object which is a prvalue in discarded-value expression. +- Implemented `P1774R8: Portable assumptions <https://wg21.link/P1774R8>`_. - Implemented `P2448R2: Relaxing some constexpr restrictions <https://wg21.link/P2448R2>`_. diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index ebb616f..fd7970d0 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1580,6 +1580,13 @@ def Unlikely : StmtAttr { } def : MutualExclusions<[Likely, Unlikely]>; +def CXXAssume : StmtAttr { + let Spellings = [CXX11<"", "assume", 202207>]; + let Subjects = SubjectList<[NullStmt], ErrorDiag, "empty statements">; + let Args = [ExprArgument<"Assumption">]; + let Documentation = [CXXAssumeDocs]; +} + def NoMerge : DeclOrStmtAttr { let Spellings = [Clang<"nomerge">]; let Documentation = [NoMergeDocs]; @@ -4151,11 +4158,11 @@ def OMPDeclareVariant : InheritableAttr { }]; } -def Assumption : InheritableAttr { +def OMPAssume : InheritableAttr { let Spellings = [Clang<"assume">]; let Subjects = SubjectList<[Function, ObjCMethod]>; let InheritEvenIfAlreadyPresent = 1; - let Documentation = [AssumptionDocs]; + let Documentation = [OMPAssumeDocs]; let Args = [StringArgument<"Assumption">]; } diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index b96fbdd..2c07cd0 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -1996,6 +1996,34 @@ Here is an example: }]; } +def CXXAssumeDocs : Documentation { + let Category = DocCatStmt; + let Heading = "assume"; + let Content = [{ +The ``assume`` attribute is used to indicate to the optimizer that a +certain condition is assumed to be true at a certain point in the +program. If this condition is violated at runtime, the behavior is +undefined. ``assume`` can only be applied to a null statement. + +Different optimisers are likely to react differently to the presence of +this attribute; in some cases, adding ``assume`` may affect performance +negatively. It should be used with parsimony and care. + +Note that `clang::assume` is a different attribute. Always write ``assume`` +without a namespace if you intend to use the standard C++ attribute. + +Example: + +.. code-block:: c++ + + int f(int x, int y) { + [[assume(x == 27)]]; + [[assume(x == y)]]; + return y + 1; // May be optimised to `return 28`. + } + }]; +} + def LikelihoodDocs : Documentation { let Category = DocCatStmt; let Heading = "likely and unlikely"; @@ -4629,7 +4657,7 @@ For more information see }]; } -def AssumptionDocs : Documentation { +def OMPAssumeDocs : Documentation { let Category = DocCatFunction; let Heading = "assume"; let Content = [{ diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index c81d17e..a024f9b 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -399,6 +399,8 @@ def note_constexpr_unsupported_flexible_array : Note< "flexible array initialization is not yet supported">; def note_constexpr_non_const_vectorelements : Note< "cannot determine number of elements for sizeless vectors in a constant expression">; +def note_constexpr_assumption_failed : Note< + "assumption evaluated to false">; def err_experimental_clang_interp_failed : Error< "the experimental clang interpreter failed to evaluate an expression">; diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 0791a00..ba1d4b2 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1133,9 +1133,11 @@ def NonGCC : DiagGroup<"non-gcc", def CXX14Attrs : DiagGroup<"c++14-attribute-extensions">; def CXX17Attrs : DiagGroup<"c++17-attribute-extensions">; def CXX20Attrs : DiagGroup<"c++20-attribute-extensions">; +def CXX23Attrs : DiagGroup<"c++23-attribute-extensions">; def FutureAttrs : DiagGroup<"future-attribute-extensions", [CXX14Attrs, CXX17Attrs, - CXX20Attrs]>; + CXX20Attrs, + CXX23Attrs]>; def CXX23AttrsOnLambda : DiagGroup<"c++23-lambda-attributes">; diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index c0dbc25..816c3ff 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -786,6 +786,9 @@ def err_ms_property_expected_comma_or_rparen : Error< def err_ms_property_initializer : Error< "property declaration cannot have a default member initializer">; +def err_assume_attr_expects_cond_expr : Error< + "use of this expression in an %0 attribute requires parentheses">; + def warn_cxx20_compat_explicit_bool : Warning< "this expression will be parsed as explicit(bool) in C++20">, InGroup<CXX20Compat>, DefaultIgnore; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 6da49fa..9b52456 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -855,10 +855,10 @@ def note_strncat_wrong_size : Note< def warn_assume_side_effects : Warning< "the argument to %0 has side effects that will be discarded">, InGroup<DiagGroup<"assume">>; -def warn_assume_attribute_string_unknown : Warning< +def warn_omp_assume_attribute_string_unknown : Warning< "unknown assumption string '%0'; attribute is potentially ignored">, InGroup<UnknownAssumption>; -def warn_assume_attribute_string_unknown_suggested : Warning< +def warn_omp_assume_attribute_string_unknown_suggested : Warning< "unknown assumption string '%0' may be misspelled; attribute is potentially " "ignored, did you mean '%1'?">, InGroup<MisspelledAssumption>; @@ -9115,6 +9115,8 @@ def ext_cxx17_attr : Extension< "use of the %0 attribute is a C++17 extension">, InGroup<CXX17Attrs>; def ext_cxx20_attr : Extension< "use of the %0 attribute is a C++20 extension">, InGroup<CXX20Attrs>; +def ext_cxx23_attr : Extension< + "use of the %0 attribute is a C++23 extension">, InGroup<CXX23Attrs>; def warn_unused_comparison : Warning< "%select{equality|inequality|relational|three-way}0 comparison result unused">, @@ -10169,6 +10171,9 @@ def err_fallthrough_attr_outside_switch : Error< def err_fallthrough_attr_invalid_placement : Error< "fallthrough annotation does not directly precede switch label">; +def err_assume_attr_args : Error< + "attribute '%0' requires a single expression argument">; + def warn_unreachable_default : Warning< "default label in switch which covers all enumeration values">, InGroup<CoveredSwitchDefault>, DefaultIgnore; diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index 2b42b52..472fd9f 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -450,6 +450,8 @@ LANGOPT(RegCall4, 1, 0, "Set __regcall4 as a default calling convention to respe LANGOPT(MatrixTypes, 1, 0, "Enable or disable the builtin matrix type") +LANGOPT(CXXAssumptions, 1, 1, "Enable or disable codegen and compile-time checks for C++23's [[assume]] attribute") + ENUM_LANGOPT(StrictFlexArraysLevel, StrictFlexArraysLevelKind, 2, StrictFlexArraysLevelKind::Default, "Rely on strict definition of flexible arrays") diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index 5b3d366..d5eed15 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -3789,6 +3789,12 @@ def foptimization_record_passes_EQ : Joined<["-"], "foptimization-record-passes= HelpText<"Only include passes which match a specified regular expression in the generated optimization record (by default, include all passes)">, MetaVarName<"<regex>">; +defm assumptions : BoolFOption<"assumptions", + LangOpts<"CXXAssumptions">, DefaultTrue, + NegFlag<SetFalse, [], [ClangOption, CC1Option], + "Disable codegen and compile-time checks for C++23's [[assume]] attribute">, + PosFlag<SetTrue>>; + def fvectorize : Flag<["-"], "fvectorize">, Group<f_Group>, HelpText<"Enable the loop vectorization passes">; def fno_vectorize : Flag<["-"], "fno-vectorize">, Group<f_Group>; diff --git a/clang/include/clang/Parse/Parser.h b/clang/include/clang/Parse/Parser.h index 071520f..64e031d 100644 --- a/clang/include/clang/Parse/Parser.h +++ b/clang/include/clang/Parse/Parser.h @@ -1803,6 +1803,7 @@ public: ExprResult ParseConstraintLogicalOrExpression(bool IsTrailingRequiresClause); // Expr that doesn't include commas. ExprResult ParseAssignmentExpression(TypeCastState isTypeCast = NotTypeCast); + ExprResult ParseConditionalExpression(); ExprResult ParseMSAsmIdentifier(llvm::SmallVectorImpl<Token> &LineToks, unsigned &NumLineToksConsumed, @@ -2955,6 +2956,12 @@ private: SourceLocation ScopeLoc, CachedTokens &OpenMPTokens); + /// Parse a C++23 assume() attribute. Returns true on error. + bool ParseCXXAssumeAttributeArg(ParsedAttributes &Attrs, + IdentifierInfo *AttrName, + SourceLocation AttrNameLoc, + SourceLocation *EndLoc); + IdentifierInfo *TryParseCXX11AttributeIdentifier( SourceLocation &Loc, Sema::AttributeCompletion Completion = Sema::AttributeCompletion::None, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index cfc1c3b..00b3f53 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -9011,6 +9011,12 @@ public: void ProcessStmtAttributes(Stmt *Stmt, const ParsedAttributes &InAttrs, SmallVectorImpl<const Attr *> &OutAttrs); + ExprResult ActOnCXXAssumeAttr(Stmt *St, const ParsedAttr &A, + SourceRange Range); + ExprResult BuildCXXAssumeExpr(Expr *Assumption, + const IdentifierInfo *AttrName, + SourceRange Range); + ///@} // @@ -14716,10 +14722,10 @@ private: SmallVector<OMPDeclareVariantScope, 4> OMPDeclareVariantScopes; /// The current `omp begin/end assumes` scopes. - SmallVector<AssumptionAttr *, 4> OMPAssumeScoped; + SmallVector<OMPAssumeAttr *, 4> OMPAssumeScoped; /// All `omp assumes` we encountered so far. - SmallVector<AssumptionAttr *, 4> OMPAssumeGlobal; + SmallVector<OMPAssumeAttr *, 4> OMPAssumeGlobal; /// OMPD_loop is mapped to OMPD_for, OMPD_distribute or OMPD_simd depending /// on the parameter of the bind clause. In the methods for the diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 4a7c775..726415c 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -5582,6 +5582,29 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info, MSConstexprContextRAII ConstexprContext( *Info.CurrentCall, hasSpecificAttr<MSConstexprAttr>(AS->getAttrs()) && isa<ReturnStmt>(SS)); + + auto LO = Info.getCtx().getLangOpts(); + if (LO.CXXAssumptions && !LO.MSVCCompat) { + for (auto *Attr : AS->getAttrs()) { + auto *AA = dyn_cast<CXXAssumeAttr>(Attr); + if (!AA) + continue; + + auto *Assumption = AA->getAssumption(); + if (Assumption->isValueDependent()) + return ESR_Failed; + + bool Value; + if (!EvaluateAsBooleanCondition(Assumption, Value, Info)) + return ESR_Failed; + if (!Value) { + Info.CCEDiag(Assumption->getExprLoc(), + diag::note_constexpr_assumption_failed); + return ESR_Failed; + } + } + } + return EvaluateStmt(Result, Info, SS, Case); } diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 13f6823..a28d788 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -1796,14 +1796,14 @@ static void AddAttributesFromFunctionProtoType(ASTContext &Ctx, FuncAttrs.addAttribute("aarch64_inout_zt0"); } -static void AddAttributesFromAssumes(llvm::AttrBuilder &FuncAttrs, - const Decl *Callee) { +static void AddAttributesFromOMPAssumes(llvm::AttrBuilder &FuncAttrs, + const Decl *Callee) { if (!Callee) return; SmallVector<StringRef, 4> Attrs; - for (const AssumptionAttr *AA : Callee->specific_attrs<AssumptionAttr>()) + for (const OMPAssumeAttr *AA : Callee->specific_attrs<OMPAssumeAttr>()) AA->getAssumption().split(Attrs, ","); if (!Attrs.empty()) @@ -2344,7 +2344,7 @@ void CodeGenModule::ConstructAttributeList(StringRef Name, // Attach assumption attributes to the declaration. If this is a call // site, attach assumptions from the caller to the call as well. - AddAttributesFromAssumes(FuncAttrs, TargetDecl); + AddAttributesFromOMPAssumes(FuncAttrs, TargetDecl); bool HasOptnone = false; // The NoBuiltinAttr attached to the target FunctionDecl. diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index d0a3a71..8898e3f 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -728,11 +728,19 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) { case attr::AlwaysInline: alwaysinline = true; break; - case attr::MustTail: + case attr::MustTail: { const Stmt *Sub = S.getSubStmt(); const ReturnStmt *R = cast<ReturnStmt>(Sub); musttail = cast<CallExpr>(R->getRetValue()->IgnoreParens()); - break; + } break; + case attr::CXXAssume: { + const Expr *Assumption = cast<CXXAssumeAttr>(A)->getAssumption(); + if (getLangOpts().CXXAssumptions && + !Assumption->HasSideEffects(getContext())) { + llvm::Value *AssumptionVal = EvaluateExprAsBool(Assumption); + Builder.CreateAssumption(AssumptionVal); + } + } break; } } SaveAndRestore save_nomerge(InNoMergeAttributedStmt, nomerge); diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index fa17f62..678e24e 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -6982,6 +6982,11 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, (!IsWindowsMSVC || IsMSVC2015Compatible))) CmdArgs.push_back("-fno-threadsafe-statics"); + // Add -fno-assumptions, if it was specified. + if (!Args.hasFlag(options::OPT_fassumptions, options::OPT_fno_assumptions, + true)) + CmdArgs.push_back("-fno-assumptions"); + // -fgnu-keywords default varies depending on language; only pass if // specified. Args.AddLastArg(CmdArgs, options::OPT_fgnu_keywords, diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index 62632b2..bdca10c 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -4528,6 +4528,61 @@ static bool IsBuiltInOrStandardCXX11Attribute(IdentifierInfo *AttrName, } } +/// Parse the argument to C++23's [[assume()]] attribute. +bool Parser::ParseCXXAssumeAttributeArg(ParsedAttributes &Attrs, + IdentifierInfo *AttrName, + SourceLocation AttrNameLoc, + SourceLocation *EndLoc) { + assert(Tok.is(tok::l_paren) && "Not a C++11 attribute argument list"); + BalancedDelimiterTracker T(*this, tok::l_paren); + T.consumeOpen(); + + // [dcl.attr.assume]: The expression is potentially evaluated. + EnterExpressionEvaluationContext Unevaluated( + Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated); + + TentativeParsingAction TPA(*this); + ExprResult Res( + Actions.CorrectDelayedTyposInExpr(ParseConditionalExpression())); + if (Res.isInvalid()) { + TPA.Commit(); + SkipUntil(tok::r_paren, tok::r_square, StopAtSemi | StopBeforeMatch); + if (Tok.is(tok::r_paren)) + T.consumeClose(); + return true; + } + + if (!Tok.isOneOf(tok::r_paren, tok::r_square)) { + // Emit a better diagnostic if this is an otherwise valid expression that + // is not allowed here. + TPA.Revert(); + Res = ParseExpression(); + if (!Res.isInvalid()) { + auto *E = Res.get(); + Diag(E->getExprLoc(), diag::err_assume_attr_expects_cond_expr) + << AttrName << FixItHint::CreateInsertion(E->getBeginLoc(), "(") + << FixItHint::CreateInsertion(PP.getLocForEndOfToken(E->getEndLoc()), + ")") + << E->getSourceRange(); + } + + T.consumeClose(); + return true; + } + + TPA.Commit(); + ArgsUnion Assumption = Res.get(); + auto RParen = Tok.getLocation(); + T.consumeClose(); + Attrs.addNew(AttrName, SourceRange(AttrNameLoc, RParen), nullptr, + SourceLocation(), &Assumption, 1, ParsedAttr::Form::CXX11()); + + if (EndLoc) + *EndLoc = RParen; + + return false; +} + /// ParseCXX11AttributeArgs -- Parse a C++11 attribute-argument-clause. /// /// [C++11] attribute-argument-clause: @@ -4596,7 +4651,12 @@ bool Parser::ParseCXX11AttributeArgs( if (ScopeName && (ScopeName->isStr("clang") || ScopeName->isStr("_Clang"))) NumArgs = ParseClangAttributeArgs(AttrName, AttrNameLoc, Attrs, EndLoc, ScopeName, ScopeLoc, Form); - else + // So does C++23's assume() attribute. + else if (!ScopeName && AttrName->isStr("assume")) { + if (ParseCXXAssumeAttributeArg(Attrs, AttrName, AttrNameLoc, EndLoc)) + return true; + NumArgs = 1; + } else NumArgs = ParseAttributeArgsCommon(AttrName, AttrNameLoc, Attrs, EndLoc, ScopeName, ScopeLoc, Form); diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 1f07edd..88c3a14 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -179,6 +179,19 @@ ExprResult Parser::ParseAssignmentExpression(TypeCastState isTypeCast) { return ParseRHSOfBinaryExpression(LHS, prec::Assignment); } +ExprResult Parser::ParseConditionalExpression() { + if (Tok.is(tok::code_completion)) { + cutOffParsing(); + Actions.CodeCompleteExpression(getCurScope(), + PreferredType.get(Tok.getLocation())); + return ExprError(); + } + + ExprResult LHS = ParseCastExpression( + AnyCastExpr, /*isAddressOfOperand=*/false, NotTypeCast); + return ParseRHSOfBinaryExpression(LHS, prec::Conditional); +} + /// Parse an assignment expression where part of an Objective-C message /// send has already been parsed. /// diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index e6943ef..c00120b 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -1771,8 +1771,8 @@ void Sema::AddAllocAlignAttr(Decl *D, const AttributeCommonInfo &CI, } /// Check if \p AssumptionStr is a known assumption and warn if not. -static void checkAssumptionAttr(Sema &S, SourceLocation Loc, - StringRef AssumptionStr) { +static void checkOMPAssumeAttr(Sema &S, SourceLocation Loc, + StringRef AssumptionStr) { if (llvm::KnownAssumptionStrings.count(AssumptionStr)) return; @@ -1788,22 +1788,23 @@ static void checkAssumptionAttr(Sema &S, SourceLocation Loc, } if (!Suggestion.empty()) - S.Diag(Loc, diag::warn_assume_attribute_string_unknown_suggested) + S.Diag(Loc, diag::warn_omp_assume_attribute_string_unknown_suggested) << AssumptionStr << Suggestion; else - S.Diag(Loc, diag::warn_assume_attribute_string_unknown) << AssumptionStr; + S.Diag(Loc, diag::warn_omp_assume_attribute_string_unknown) + << AssumptionStr; } -static void handleAssumumptionAttr(Sema &S, Decl *D, const ParsedAttr &AL) { +static void handleOMPAssumeAttr(Sema &S, Decl *D, const ParsedAttr &AL) { // Handle the case where the attribute has a text message. StringRef Str; SourceLocation AttrStrLoc; if (!S.checkStringLiteralArgumentAttr(AL, 0, Str, &AttrStrLoc)) return; - checkAssumptionAttr(S, AttrStrLoc, Str); + checkOMPAssumeAttr(S, AttrStrLoc, Str); - D->addAttr(::new (S.Context) AssumptionAttr(S.Context, AL, Str)); + D->addAttr(::new (S.Context) OMPAssumeAttr(S.Context, AL, Str)); } /// Normalize the attribute, __foo__ becomes foo. @@ -9491,8 +9492,8 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_Unavailable: handleAttrWithMessage<UnavailableAttr>(S, D, AL); break; - case ParsedAttr::AT_Assumption: - handleAssumumptionAttr(S, D, AL); + case ParsedAttr::AT_OMPAssume: + handleOMPAssumeAttr(S, D, AL); break; case ParsedAttr::AT_ObjCDirect: handleObjCDirectAttr(S, D, AL); diff --git a/clang/lib/Sema/SemaOpenMP.cpp b/clang/lib/Sema/SemaOpenMP.cpp index afffd37..0cc0cba 100644 --- a/clang/lib/Sema/SemaOpenMP.cpp +++ b/clang/lib/Sema/SemaOpenMP.cpp @@ -3496,7 +3496,7 @@ void Sema::ActOnOpenMPAssumesDirective(SourceLocation Loc, << llvm::omp::getAllAssumeClauseOptions() << llvm::omp::getOpenMPDirectiveName(DKind); - auto *AA = AssumptionAttr::Create(Context, llvm::join(Assumptions, ","), Loc); + auto *AA = OMPAssumeAttr::Create(Context, llvm::join(Assumptions, ","), Loc); if (DKind == llvm::omp::Directive::OMPD_begin_assumes) { OMPAssumeScoped.push_back(AA); return; @@ -7275,10 +7275,10 @@ void Sema::ActOnFinishedFunctionDefinitionInOpenMPAssumeScope(Decl *D) { // only global ones. We apply scoped assumption to the template definition // though. if (!inTemplateInstantiation()) { - for (AssumptionAttr *AA : OMPAssumeScoped) + for (OMPAssumeAttr *AA : OMPAssumeScoped) FD->addAttr(AA); } - for (AssumptionAttr *AA : OMPAssumeGlobal) + for (OMPAssumeAttr *AA : OMPAssumeGlobal) FD->addAttr(AA); } diff --git a/clang/lib/Sema/SemaStmtAttr.cpp b/clang/lib/Sema/SemaStmtAttr.cpp index e6a4d3e..691857e 100644 --- a/clang/lib/Sema/SemaStmtAttr.cpp +++ b/clang/lib/Sema/SemaStmtAttr.cpp @@ -303,6 +303,15 @@ static Attr *handleAlwaysInlineAttr(Sema &S, Stmt *St, const ParsedAttr &A, return ::new (S.Context) AlwaysInlineAttr(S.Context, A); } +static Attr *handleCXXAssumeAttr(Sema &S, Stmt *St, const ParsedAttr &A, + SourceRange Range) { + ExprResult Res = S.ActOnCXXAssumeAttr(St, A, Range); + if (!Res.isUsable()) + return nullptr; + + return ::new (S.Context) CXXAssumeAttr(S.Context, A, Res.get()); +} + static Attr *handleMustTailAttr(Sema &S, Stmt *St, const ParsedAttr &A, SourceRange Range) { // Validation is in Sema::ActOnAttributedStmt(). @@ -594,6 +603,8 @@ static Attr *ProcessStmtAttribute(Sema &S, Stmt *St, const ParsedAttr &A, switch (A.getKind()) { case ParsedAttr::AT_AlwaysInline: return handleAlwaysInlineAttr(S, St, A, Range); + case ParsedAttr::AT_CXXAssume: + return handleCXXAssumeAttr(S, St, A, Range); case ParsedAttr::AT_FallThrough: return handleFallThroughAttr(S, St, A, Range); case ParsedAttr::AT_LoopHint: @@ -641,3 +652,47 @@ bool Sema::CheckRebuiltStmtAttributes(ArrayRef<const Attr *> Attrs) { CheckForDuplicateLoopAttrs<CodeAlignAttr>(*this, Attrs); return false; } + +ExprResult Sema::ActOnCXXAssumeAttr(Stmt *St, const ParsedAttr &A, + SourceRange Range) { + if (A.getNumArgs() != 1 || !A.getArgAsExpr(0)) { + Diag(A.getLoc(), diag::err_assume_attr_args) << A.getAttrName() << Range; + return ExprError(); + } + + auto *Assumption = A.getArgAsExpr(0); + if (Assumption->getDependence() == ExprDependence::None) { + ExprResult Res = BuildCXXAssumeExpr(Assumption, A.getAttrName(), Range); + if (Res.isInvalid()) + return ExprError(); + Assumption = Res.get(); + } + + if (!getLangOpts().CPlusPlus23) + Diag(A.getLoc(), diag::ext_cxx23_attr) << A << Range; + + return Assumption; +} + +ExprResult Sema::BuildCXXAssumeExpr(Expr *Assumption, + const IdentifierInfo *AttrName, + SourceRange Range) { + ExprResult Res = CorrectDelayedTyposInExpr(Assumption); + if (Res.isInvalid()) + return ExprError(); + + Res = CheckPlaceholderExpr(Res.get()); + if (Res.isInvalid()) + return ExprError(); + + Res = PerformContextuallyConvertToBool(Res.get()); + if (Res.isInvalid()) + return ExprError(); + + Assumption = Res.get(); + if (Assumption->HasSideEffects(Context)) + Diag(Assumption->getBeginLoc(), diag::warn_assume_side_effects) + << AttrName << Range; + + return Assumption; +} diff --git a/clang/lib/Sema/SemaTemplateInstantiate.cpp b/clang/lib/Sema/SemaTemplateInstantiate.cpp index d9994d7..1a0c887 100644 --- a/clang/lib/Sema/SemaTemplateInstantiate.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -1411,6 +1411,7 @@ namespace { NamedDecl *FirstQualifierInScope = nullptr, bool AllowInjectedClassName = false); + const CXXAssumeAttr *TransformCXXAssumeAttr(const CXXAssumeAttr *AA); const LoopHintAttr *TransformLoopHintAttr(const LoopHintAttr *LH); const NoInlineAttr *TransformStmtNoInlineAttr(const Stmt *OrigS, const Stmt *InstS, @@ -1980,6 +1981,21 @@ TemplateInstantiator::TransformTemplateParmRefExpr(DeclRefExpr *E, Arg, PackIndex); } +const CXXAssumeAttr * +TemplateInstantiator::TransformCXXAssumeAttr(const CXXAssumeAttr *AA) { + ExprResult Res = getDerived().TransformExpr(AA->getAssumption()); + if (!Res.isUsable()) + return AA; + + Res = getSema().BuildCXXAssumeExpr(Res.get(), AA->getAttrName(), + AA->getRange()); + if (!Res.isUsable()) + return AA; + + return CXXAssumeAttr::CreateImplicit(getSema().Context, Res.get(), + AA->getRange()); +} + const LoopHintAttr * TemplateInstantiator::TransformLoopHintAttr(const LoopHintAttr *LH) { Expr *TransformedExpr = getDerived().TransformExpr(LH->getValue()).get(); diff --git a/clang/test/CodeGenCXX/cxx23-assume.cpp b/clang/test/CodeGenCXX/cxx23-assume.cpp new file mode 100644 index 0000000..a1fa6b3 --- /dev/null +++ b/clang/test/CodeGenCXX/cxx23-assume.cpp @@ -0,0 +1,50 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++23 %s -emit-llvm -o - | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++23 -fno-assumptions %s -emit-llvm -o - | FileCheck %s --check-prefix=DISABLED + +// DISABLED-NOT: @llvm.assume + +bool f(); + +template <typename T> +void f2() { + [[assume(sizeof(T) == sizeof(int))]]; +} + +// CHECK: @_Z1gii(i32 noundef [[X:%.*]], i32 noundef [[Y:%.*]]) +// CHECK-NEXT: entry: +// CHECK-NEXT: [[X_ADDR:%.*]] = alloca i32 +// CHECK-NEXT: [[Y_ADDR:%.*]] = alloca i32 +// CHECK-NEXT: store i32 [[X]], ptr [[X_ADDR]] +// CHECK-NEXT: store i32 [[Y]], ptr [[Y_ADDR]] +void g(int x, int y) { + // Not emitted because it has side-effects. + [[assume(f())]]; + + // CHECK-NEXT: call void @llvm.assume(i1 true) + [[assume((1, 2))]]; + + // CHECK-NEXT: [[X1:%.*]] = load i32, ptr [[X_ADDR]] + // CHECK-NEXT: [[CMP1:%.*]] = icmp ne i32 [[X1]], 27 + // CHECK-NEXT: call void @llvm.assume(i1 [[CMP1]]) + [[assume(x != 27)]]; + + // CHECK-NEXT: [[X2:%.*]] = load i32, ptr [[X_ADDR]] + // CHECK-NEXT: [[Y2:%.*]] = load i32, ptr [[Y_ADDR]] + // CHECK-NEXT: [[CMP2:%.*]] = icmp eq i32 [[X2]], [[Y2]] + // CHECK-NEXT: call void @llvm.assume(i1 [[CMP2]]) + [[assume(x == y)]]; + + // CHECK-NEXT: call void @_Z2f2IiEvv() + f2<int>(); + + // CHECK-NEXT: call void @_Z2f2IdEvv() + f2<double>(); +} + +// CHECK: void @_Z2f2IiEvv() +// CHECK-NEXT: entry: +// CHECK-NEXT: call void @llvm.assume(i1 true) + +// CHECK: void @_Z2f2IdEvv() +// CHECK-NEXT: entry: +// CHECK-NEXT: call void @llvm.assume(i1 false) diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test index 1528388..ec84ebd 100644 --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -19,7 +19,6 @@ // CHECK-NEXT: ArcWeakrefUnavailable (SubjectMatchRule_objc_interface) // CHECK-NEXT: ArmBuiltinAlias (SubjectMatchRule_function) // CHECK-NEXT: AssumeAligned (SubjectMatchRule_objc_method, SubjectMatchRule_function) -// CHECK-NEXT: Assumption (SubjectMatchRule_function, SubjectMatchRule_objc_method) // CHECK-NEXT: Availability ((SubjectMatchRule_record, SubjectMatchRule_enum, SubjectMatchRule_enum_constant, SubjectMatchRule_field, SubjectMatchRule_function, SubjectMatchRule_namespace, SubjectMatchRule_objc_category, SubjectMatchRule_objc_implementation, SubjectMatchRule_objc_interface, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_objc_protocol, SubjectMatchRule_record, SubjectMatchRule_type_alias, SubjectMatchRule_variable)) // CHECK-NEXT: AvailableOnlyInDefaultEvalMethod (SubjectMatchRule_type_alias) // CHECK-NEXT: BPFPreserveAccessIndex (SubjectMatchRule_record) @@ -127,6 +126,7 @@ // CHECK-NEXT: NoThrow (SubjectMatchRule_hasType_functionType) // CHECK-NEXT: NoUwtable (SubjectMatchRule_hasType_functionType) // CHECK-NEXT: NotTailCalled (SubjectMatchRule_function) +// CHECK-NEXT: OMPAssume (SubjectMatchRule_function, SubjectMatchRule_objc_method) // CHECK-NEXT: OSConsumed (SubjectMatchRule_variable_is_parameter) // CHECK-NEXT: OSReturnsNotRetained (SubjectMatchRule_function, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_variable_is_parameter) // CHECK-NEXT: OSReturnsRetained (SubjectMatchRule_function, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_variable_is_parameter) diff --git a/clang/test/Parser/cxx23-assume.cpp b/clang/test/Parser/cxx23-assume.cpp new file mode 100644 index 0000000..269fb7e --- /dev/null +++ b/clang/test/Parser/cxx23-assume.cpp @@ -0,0 +1,18 @@ +// RUN: %clang_cc1 -std=c++23 -x c++ %s -verify + +void f(int x, int y) { + [[assume(true)]]; + [[assume(1)]]; + [[assume(1.0)]]; + [[assume(1 + 2 == 3)]]; + [[assume(x ? 1 : 2)]]; + [[assume(x && y)]]; + [[assume(true)]] [[assume(true)]]; + + [[assume]]; // expected-error {{takes one argument}} + [[assume(]]; // expected-error {{expected expression}} + [[assume()]]; // expected-error {{expected expression}} + [[assume(2]]; // expected-error {{expected ')'}} expected-note {{to match this '('}} + [[assume(x = 2)]]; // expected-error {{requires parentheses}} + [[assume(2, 3)]]; // expected-error {{requires parentheses}} expected-warning {{has no effect}} +} diff --git a/clang/test/SemaCXX/cxx23-assume-disabled.cpp b/clang/test/SemaCXX/cxx23-assume-disabled.cpp new file mode 100644 index 0000000..4233a2f --- /dev/null +++ b/clang/test/SemaCXX/cxx23-assume-disabled.cpp @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -std=c++23 -x c++ %s -fno-assumptions -verify +// RUN: %clang_cc1 -std=c++23 -x c++ %s -fms-compatibility -verify +// expected-no-diagnostics + +// We don't check assumptions at compile time if '-fno-assumptions' is passed, +// or if we're in MSVCCompat mode + +constexpr bool f(bool x) { + [[assume(x)]]; + return true; +} + +static_assert(f(false)); + diff --git a/clang/test/SemaCXX/cxx23-assume-print.cpp b/clang/test/SemaCXX/cxx23-assume-print.cpp new file mode 100644 index 0000000..37db015 --- /dev/null +++ b/clang/test/SemaCXX/cxx23-assume-print.cpp @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -std=c++23 -ast-print %s | FileCheck %s + +// CHECK: void f(int x, int y) { +void f(int x, int y) { + // CHECK-NEXT: {{\[}}[assume(true)]] + [[assume(true)]]; + + // CHECK-NEXT: {{\[}}[assume(2 + 4)]] + [[assume(2 + 4)]]; + + // CHECK-NEXT: {{\[}}[assume(x == y)]] + [[assume(x == y)]]; +} diff --git a/clang/test/SemaCXX/cxx23-assume.cpp b/clang/test/SemaCXX/cxx23-assume.cpp new file mode 100644 index 0000000..2b99cbd --- /dev/null +++ b/clang/test/SemaCXX/cxx23-assume.cpp @@ -0,0 +1,128 @@ +// RUN: %clang_cc1 -std=c++23 -x c++ %s -verify +// RUN: %clang_cc1 -std=c++20 -pedantic -x c++ %s -verify=ext,expected + +struct A{}; +struct B{ explicit operator bool() { return true; } }; + +template <bool cond> +void f() { + [[assume(cond)]]; // ext-warning {{C++23 extension}} +} + +template <bool cond> +struct S { + void f() { + [[assume(cond)]]; // ext-warning {{C++23 extension}} + } + + template <typename T> + constexpr bool g() { + [[assume(cond == sizeof(T))]]; // expected-note {{assumption evaluated to false}} ext-warning {{C++23 extension}} + return true; + } +}; + +bool f2(); + +template <typename T> +constexpr void f3() { + [[assume(T{})]]; // expected-error {{not contextually convertible to 'bool'}} expected-warning {{has side effects that will be discarded}} ext-warning {{C++23 extension}} +} + +void g(int x) { + f<true>(); + f<false>(); + S<true>{}.f(); + S<false>{}.f(); + S<true>{}.g<char>(); + S<true>{}.g<int>(); + [[assume(f2())]]; // expected-warning {{side effects that will be discarded}} ext-warning {{C++23 extension}} + + [[assume((x = 3))]]; // expected-warning {{has side effects that will be discarded}} // ext-warning {{C++23 extension}} + [[assume(x++)]]; // expected-warning {{has side effects that will be discarded}} // ext-warning {{C++23 extension}} + [[assume(++x)]]; // expected-warning {{has side effects that will be discarded}} // ext-warning {{C++23 extension}} + [[assume([]{ return true; }())]]; // expected-warning {{has side effects that will be discarded}} // ext-warning {{C++23 extension}} + [[assume(B{})]]; // expected-warning {{has side effects that will be discarded}} // ext-warning {{C++23 extension}} + [[assume((1, 2))]]; // expected-warning {{has no effect}} // ext-warning {{C++23 extension}} + + f3<A>(); // expected-note {{in instantiation of}} + f3<B>(); // expected-note {{in instantiation of}} + [[assume]]; // expected-error {{takes one argument}} + [[assume(z)]]; // expected-error {{undeclared identifier}} + [[assume(A{})]]; // expected-error {{not contextually convertible to 'bool'}} + [[assume(true)]] if (true) {} // expected-error {{only applies to empty statements}} + [[assume(true)]] {} // expected-error {{only applies to empty statements}} + [[assume(true)]] for (;false;) {} // expected-error {{only applies to empty statements}} + [[assume(true)]] while (false) {} // expected-error {{only applies to empty statements}} + [[assume(true)]] label:; // expected-error {{cannot be applied to a declaration}} + [[assume(true)]] goto label; // expected-error {{only applies to empty statements}} +} + +// Check that 'x' is ODR-used here. +constexpr int h(int x) { return sizeof([=] { [[assume(x)]]; }); } // ext-warning {{C++23 extension}} +static_assert(h(4) == sizeof(int)); + +static_assert(__has_cpp_attribute(assume) == 202207L); +static_assert(__has_attribute(assume)); + +constexpr bool i() { // expected-error {{never produces a constant expression}} + [[assume(false)]]; // expected-note {{assumption evaluated to false}} expected-note {{assumption evaluated to false}} ext-warning {{C++23 extension}} + return true; +} + +constexpr bool j(bool b) { + [[assume(b)]]; // expected-note {{assumption evaluated to false}} ext-warning {{C++23 extension}} + return true; +} + +static_assert(i()); // expected-error {{not an integral constant expression}} expected-note {{in call to}} +static_assert(j(true)); +static_assert(j(false)); // expected-error {{not an integral constant expression}} expected-note {{in call to}} +static_assert(S<true>{}.g<char>()); +static_assert(S<false>{}.g<A>()); // expected-error {{not an integral constant expression}} expected-note {{in call to}} + + +template <typename T> +constexpr bool f4() { + [[assume(!T{})]]; // expected-error {{invalid argument type 'D'}} // expected-warning 2 {{side effects}} ext-warning {{C++23 extension}} + return sizeof(T) == sizeof(int); +} + +template <typename T> +concept C = f4<T>(); // expected-note 3 {{in instantiation of}} + // expected-note@-1 3 {{while substituting}} + // expected-error@-2 2 {{resulted in a non-constant expression}} + +struct D { + int x; +}; + +struct E { + int x; + constexpr explicit operator bool() { return false; } +}; + +struct F { + int x; + int y; + constexpr explicit operator bool() { return false; } +}; + +template <typename T> +constexpr int f5() requires C<T> { return 1; } // expected-note {{while checking the satisfaction}} + // expected-note@-1 {{while substituting template arguments}} + // expected-note@-2 {{candidate template ignored}} + +template <typename T> +constexpr int f5() requires (!C<T>) { return 2; } // expected-note 4 {{while checking the satisfaction}} + // expected-note@-1 4 {{while substituting template arguments}} + // expected-note@-2 {{candidate template ignored}} + +static_assert(f5<int>() == 1); +static_assert(f5<D>() == 1); // expected-note 3 {{while checking constraint satisfaction}} + // expected-note@-1 3 {{in instantiation of}} + // expected-error@-2 {{no matching function for call}} + +static_assert(f5<double>() == 2); +static_assert(f5<E>() == 1); // expected-note {{while checking constraint satisfaction}} expected-note {{in instantiation of}} +static_assert(f5<F>() == 2); // expected-note {{while checking constraint satisfaction}} expected-note {{in instantiation of}} diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index a3090ad..66a2b11 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -381,7 +381,7 @@ C++23, informally referred to as C++26.</p> <tr> <td>Portable assumptions</td> <td><a href="https://wg21.link/P1774R8">P1774R8</a></td> - <td class="none" align="center">No</td> + <td class="full" align="center">Clang 19</td> </tr> <tr> <td>Support for UTF-8 as a portable source file encoding</td> |