[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.
This commit is contained in:
parent
9df719407f
commit
2b5f68a5f6
@ -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>`_.
|
||||
|
||||
|
||||
@ -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">];
|
||||
}
|
||||
|
||||
|
||||
@ -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 = [{
|
||||
|
||||
@ -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">;
|
||||
|
||||
|
||||
@ -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">;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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>;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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.
|
||||
///
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
50
clang/test/CodeGenCXX/cxx23-assume.cpp
Normal file
50
clang/test/CodeGenCXX/cxx23-assume.cpp
Normal file
@ -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)
|
||||
@ -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)
|
||||
|
||||
18
clang/test/Parser/cxx23-assume.cpp
Normal file
18
clang/test/Parser/cxx23-assume.cpp
Normal file
@ -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}}
|
||||
}
|
||||
14
clang/test/SemaCXX/cxx23-assume-disabled.cpp
Normal file
14
clang/test/SemaCXX/cxx23-assume-disabled.cpp
Normal file
@ -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));
|
||||
|
||||
13
clang/test/SemaCXX/cxx23-assume-print.cpp
Normal file
13
clang/test/SemaCXX/cxx23-assume-print.cpp
Normal file
@ -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)]];
|
||||
}
|
||||
128
clang/test/SemaCXX/cxx23-assume.cpp
Normal file
128
clang/test/SemaCXX/cxx23-assume.cpp
Normal file
@ -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}}
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user