Skip to content

Commit 2b5f68a

Browse files
authored
[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.
1 parent 9df7194 commit 2b5f68a

28 files changed

+502
-29
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ C++23 Feature Support
100100

101101
- Implemented `P2718R0: Lifetime extension in range-based for loops <https://wg21.link/P2718R0>`_. Also
102102
materialize temporary object which is a prvalue in discarded-value expression.
103+
- Implemented `P1774R8: Portable assumptions <https://wg21.link/P1774R8>`_.
103104

104105
- Implemented `P2448R2: Relaxing some constexpr restrictions <https://wg21.link/P2448R2>`_.
105106

clang/include/clang/Basic/Attr.td

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,6 +1580,13 @@ def Unlikely : StmtAttr {
15801580
}
15811581
def : MutualExclusions<[Likely, Unlikely]>;
15821582

1583+
def CXXAssume : StmtAttr {
1584+
let Spellings = [CXX11<"", "assume", 202207>];
1585+
let Subjects = SubjectList<[NullStmt], ErrorDiag, "empty statements">;
1586+
let Args = [ExprArgument<"Assumption">];
1587+
let Documentation = [CXXAssumeDocs];
1588+
}
1589+
15831590
def NoMerge : DeclOrStmtAttr {
15841591
let Spellings = [Clang<"nomerge">];
15851592
let Documentation = [NoMergeDocs];
@@ -4151,11 +4158,11 @@ def OMPDeclareVariant : InheritableAttr {
41514158
}];
41524159
}
41534160

4154-
def Assumption : InheritableAttr {
4161+
def OMPAssume : InheritableAttr {
41554162
let Spellings = [Clang<"assume">];
41564163
let Subjects = SubjectList<[Function, ObjCMethod]>;
41574164
let InheritEvenIfAlreadyPresent = 1;
4158-
let Documentation = [AssumptionDocs];
4165+
let Documentation = [OMPAssumeDocs];
41594166
let Args = [StringArgument<"Assumption">];
41604167
}
41614168

clang/include/clang/Basic/AttrDocs.td

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1996,6 +1996,34 @@ Here is an example:
19961996
}];
19971997
}
19981998

1999+
def CXXAssumeDocs : Documentation {
2000+
let Category = DocCatStmt;
2001+
let Heading = "assume";
2002+
let Content = [{
2003+
The ``assume`` attribute is used to indicate to the optimizer that a
2004+
certain condition is assumed to be true at a certain point in the
2005+
program. If this condition is violated at runtime, the behavior is
2006+
undefined. ``assume`` can only be applied to a null statement.
2007+
2008+
Different optimisers are likely to react differently to the presence of
2009+
this attribute; in some cases, adding ``assume`` may affect performance
2010+
negatively. It should be used with parsimony and care.
2011+
2012+
Note that `clang::assume` is a different attribute. Always write ``assume``
2013+
without a namespace if you intend to use the standard C++ attribute.
2014+
2015+
Example:
2016+
2017+
.. code-block:: c++
2018+
2019+
int f(int x, int y) {
2020+
[[assume(x == 27)]];
2021+
[[assume(x == y)]];
2022+
return y + 1; // May be optimised to `return 28`.
2023+
}
2024+
}];
2025+
}
2026+
19992027
def LikelihoodDocs : Documentation {
20002028
let Category = DocCatStmt;
20012029
let Heading = "likely and unlikely";
@@ -4629,7 +4657,7 @@ For more information see
46294657
}];
46304658
}
46314659

4632-
def AssumptionDocs : Documentation {
4660+
def OMPAssumeDocs : Documentation {
46334661
let Category = DocCatFunction;
46344662
let Heading = "assume";
46354663
let Content = [{

clang/include/clang/Basic/DiagnosticASTKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@ def note_constexpr_unsupported_flexible_array : Note<
399399
"flexible array initialization is not yet supported">;
400400
def note_constexpr_non_const_vectorelements : Note<
401401
"cannot determine number of elements for sizeless vectors in a constant expression">;
402+
def note_constexpr_assumption_failed : Note<
403+
"assumption evaluated to false">;
402404
def err_experimental_clang_interp_failed : Error<
403405
"the experimental clang interpreter failed to evaluate an expression">;
404406

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1133,9 +1133,11 @@ def NonGCC : DiagGroup<"non-gcc",
11331133
def CXX14Attrs : DiagGroup<"c++14-attribute-extensions">;
11341134
def CXX17Attrs : DiagGroup<"c++17-attribute-extensions">;
11351135
def CXX20Attrs : DiagGroup<"c++20-attribute-extensions">;
1136+
def CXX23Attrs : DiagGroup<"c++23-attribute-extensions">;
11361137
def FutureAttrs : DiagGroup<"future-attribute-extensions", [CXX14Attrs,
11371138
CXX17Attrs,
1138-
CXX20Attrs]>;
1139+
CXX20Attrs,
1140+
CXX23Attrs]>;
11391141

11401142
def CXX23AttrsOnLambda : DiagGroup<"c++23-lambda-attributes">;
11411143

clang/include/clang/Basic/DiagnosticParseKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,9 @@ def err_ms_property_expected_comma_or_rparen : Error<
786786
def err_ms_property_initializer : Error<
787787
"property declaration cannot have a default member initializer">;
788788

789+
def err_assume_attr_expects_cond_expr : Error<
790+
"use of this expression in an %0 attribute requires parentheses">;
791+
789792
def warn_cxx20_compat_explicit_bool : Warning<
790793
"this expression will be parsed as explicit(bool) in C++20">,
791794
InGroup<CXX20Compat>, DefaultIgnore;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -855,10 +855,10 @@ def note_strncat_wrong_size : Note<
855855
def warn_assume_side_effects : Warning<
856856
"the argument to %0 has side effects that will be discarded">,
857857
InGroup<DiagGroup<"assume">>;
858-
def warn_assume_attribute_string_unknown : Warning<
858+
def warn_omp_assume_attribute_string_unknown : Warning<
859859
"unknown assumption string '%0'; attribute is potentially ignored">,
860860
InGroup<UnknownAssumption>;
861-
def warn_assume_attribute_string_unknown_suggested : Warning<
861+
def warn_omp_assume_attribute_string_unknown_suggested : Warning<
862862
"unknown assumption string '%0' may be misspelled; attribute is potentially "
863863
"ignored, did you mean '%1'?">,
864864
InGroup<MisspelledAssumption>;
@@ -9115,6 +9115,8 @@ def ext_cxx17_attr : Extension<
91159115
"use of the %0 attribute is a C++17 extension">, InGroup<CXX17Attrs>;
91169116
def ext_cxx20_attr : Extension<
91179117
"use of the %0 attribute is a C++20 extension">, InGroup<CXX20Attrs>;
9118+
def ext_cxx23_attr : Extension<
9119+
"use of the %0 attribute is a C++23 extension">, InGroup<CXX23Attrs>;
91189120

91199121
def warn_unused_comparison : Warning<
91209122
"%select{equality|inequality|relational|three-way}0 comparison result unused">,
@@ -10169,6 +10171,9 @@ def err_fallthrough_attr_outside_switch : Error<
1016910171
def err_fallthrough_attr_invalid_placement : Error<
1017010172
"fallthrough annotation does not directly precede switch label">;
1017110173

10174+
def err_assume_attr_args : Error<
10175+
"attribute '%0' requires a single expression argument">;
10176+
1017210177
def warn_unreachable_default : Warning<
1017310178
"default label in switch which covers all enumeration values">,
1017410179
InGroup<CoveredSwitchDefault>, DefaultIgnore;

clang/include/clang/Basic/LangOptions.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,8 @@ LANGOPT(RegCall4, 1, 0, "Set __regcall4 as a default calling convention to respe
450450

451451
LANGOPT(MatrixTypes, 1, 0, "Enable or disable the builtin matrix type")
452452

453+
LANGOPT(CXXAssumptions, 1, 1, "Enable or disable codegen and compile-time checks for C++23's [[assume]] attribute")
454+
453455
ENUM_LANGOPT(StrictFlexArraysLevel, StrictFlexArraysLevelKind, 2,
454456
StrictFlexArraysLevelKind::Default,
455457
"Rely on strict definition of flexible arrays")

clang/include/clang/Driver/Options.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3789,6 +3789,12 @@ def foptimization_record_passes_EQ : Joined<["-"], "foptimization-record-passes=
37893789
HelpText<"Only include passes which match a specified regular expression in the generated optimization record (by default, include all passes)">,
37903790
MetaVarName<"<regex>">;
37913791

3792+
defm assumptions : BoolFOption<"assumptions",
3793+
LangOpts<"CXXAssumptions">, DefaultTrue,
3794+
NegFlag<SetFalse, [], [ClangOption, CC1Option],
3795+
"Disable codegen and compile-time checks for C++23's [[assume]] attribute">,
3796+
PosFlag<SetTrue>>;
3797+
37923798
def fvectorize : Flag<["-"], "fvectorize">, Group<f_Group>,
37933799
HelpText<"Enable the loop vectorization passes">;
37943800
def fno_vectorize : Flag<["-"], "fno-vectorize">, Group<f_Group>;

clang/include/clang/Parse/Parser.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1803,6 +1803,7 @@ class Parser : public CodeCompletionHandler {
18031803
ExprResult ParseConstraintLogicalOrExpression(bool IsTrailingRequiresClause);
18041804
// Expr that doesn't include commas.
18051805
ExprResult ParseAssignmentExpression(TypeCastState isTypeCast = NotTypeCast);
1806+
ExprResult ParseConditionalExpression();
18061807

18071808
ExprResult ParseMSAsmIdentifier(llvm::SmallVectorImpl<Token> &LineToks,
18081809
unsigned &NumLineToksConsumed,
@@ -2955,6 +2956,12 @@ class Parser : public CodeCompletionHandler {
29552956
SourceLocation ScopeLoc,
29562957
CachedTokens &OpenMPTokens);
29572958

2959+
/// Parse a C++23 assume() attribute. Returns true on error.
2960+
bool ParseCXXAssumeAttributeArg(ParsedAttributes &Attrs,
2961+
IdentifierInfo *AttrName,
2962+
SourceLocation AttrNameLoc,
2963+
SourceLocation *EndLoc);
2964+
29582965
IdentifierInfo *TryParseCXX11AttributeIdentifier(
29592966
SourceLocation &Loc,
29602967
Sema::AttributeCompletion Completion = Sema::AttributeCompletion::None,

clang/include/clang/Sema/Sema.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9011,6 +9011,12 @@ class Sema final {
90119011
void ProcessStmtAttributes(Stmt *Stmt, const ParsedAttributes &InAttrs,
90129012
SmallVectorImpl<const Attr *> &OutAttrs);
90139013

9014+
ExprResult ActOnCXXAssumeAttr(Stmt *St, const ParsedAttr &A,
9015+
SourceRange Range);
9016+
ExprResult BuildCXXAssumeExpr(Expr *Assumption,
9017+
const IdentifierInfo *AttrName,
9018+
SourceRange Range);
9019+
90149020
///@}
90159021

90169022
//
@@ -14716,10 +14722,10 @@ class Sema final {
1471614722
SmallVector<OMPDeclareVariantScope, 4> OMPDeclareVariantScopes;
1471714723

1471814724
/// The current `omp begin/end assumes` scopes.
14719-
SmallVector<AssumptionAttr *, 4> OMPAssumeScoped;
14725+
SmallVector<OMPAssumeAttr *, 4> OMPAssumeScoped;
1472014726

1472114727
/// All `omp assumes` we encountered so far.
14722-
SmallVector<AssumptionAttr *, 4> OMPAssumeGlobal;
14728+
SmallVector<OMPAssumeAttr *, 4> OMPAssumeGlobal;
1472314729

1472414730
/// OMPD_loop is mapped to OMPD_for, OMPD_distribute or OMPD_simd depending
1472514731
/// on the parameter of the bind clause. In the methods for the

clang/lib/AST/ExprConstant.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5582,6 +5582,29 @@ static EvalStmtResult EvaluateStmt(StmtResult &Result, EvalInfo &Info,
55825582
MSConstexprContextRAII ConstexprContext(
55835583
*Info.CurrentCall, hasSpecificAttr<MSConstexprAttr>(AS->getAttrs()) &&
55845584
isa<ReturnStmt>(SS));
5585+
5586+
auto LO = Info.getCtx().getLangOpts();
5587+
if (LO.CXXAssumptions && !LO.MSVCCompat) {
5588+
for (auto *Attr : AS->getAttrs()) {
5589+
auto *AA = dyn_cast<CXXAssumeAttr>(Attr);
5590+
if (!AA)
5591+
continue;
5592+
5593+
auto *Assumption = AA->getAssumption();
5594+
if (Assumption->isValueDependent())
5595+
return ESR_Failed;
5596+
5597+
bool Value;
5598+
if (!EvaluateAsBooleanCondition(Assumption, Value, Info))
5599+
return ESR_Failed;
5600+
if (!Value) {
5601+
Info.CCEDiag(Assumption->getExprLoc(),
5602+
diag::note_constexpr_assumption_failed);
5603+
return ESR_Failed;
5604+
}
5605+
}
5606+
}
5607+
55855608
return EvaluateStmt(Result, Info, SS, Case);
55865609
}
55875610

clang/lib/CodeGen/CGCall.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1796,14 +1796,14 @@ static void AddAttributesFromFunctionProtoType(ASTContext &Ctx,
17961796
FuncAttrs.addAttribute("aarch64_inout_zt0");
17971797
}
17981798

1799-
static void AddAttributesFromAssumes(llvm::AttrBuilder &FuncAttrs,
1800-
const Decl *Callee) {
1799+
static void AddAttributesFromOMPAssumes(llvm::AttrBuilder &FuncAttrs,
1800+
const Decl *Callee) {
18011801
if (!Callee)
18021802
return;
18031803

18041804
SmallVector<StringRef, 4> Attrs;
18051805

1806-
for (const AssumptionAttr *AA : Callee->specific_attrs<AssumptionAttr>())
1806+
for (const OMPAssumeAttr *AA : Callee->specific_attrs<OMPAssumeAttr>())
18071807
AA->getAssumption().split(Attrs, ",");
18081808

18091809
if (!Attrs.empty())
@@ -2344,7 +2344,7 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
23442344

23452345
// Attach assumption attributes to the declaration. If this is a call
23462346
// site, attach assumptions from the caller to the call as well.
2347-
AddAttributesFromAssumes(FuncAttrs, TargetDecl);
2347+
AddAttributesFromOMPAssumes(FuncAttrs, TargetDecl);
23482348

23492349
bool HasOptnone = false;
23502350
// The NoBuiltinAttr attached to the target FunctionDecl.

clang/lib/CodeGen/CGStmt.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -728,11 +728,19 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) {
728728
case attr::AlwaysInline:
729729
alwaysinline = true;
730730
break;
731-
case attr::MustTail:
731+
case attr::MustTail: {
732732
const Stmt *Sub = S.getSubStmt();
733733
const ReturnStmt *R = cast<ReturnStmt>(Sub);
734734
musttail = cast<CallExpr>(R->getRetValue()->IgnoreParens());
735-
break;
735+
} break;
736+
case attr::CXXAssume: {
737+
const Expr *Assumption = cast<CXXAssumeAttr>(A)->getAssumption();
738+
if (getLangOpts().CXXAssumptions &&
739+
!Assumption->HasSideEffects(getContext())) {
740+
llvm::Value *AssumptionVal = EvaluateExprAsBool(Assumption);
741+
Builder.CreateAssumption(AssumptionVal);
742+
}
743+
} break;
736744
}
737745
}
738746
SaveAndRestore save_nomerge(InNoMergeAttributedStmt, nomerge);

clang/lib/Driver/ToolChains/Clang.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6982,6 +6982,11 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
69826982
(!IsWindowsMSVC || IsMSVC2015Compatible)))
69836983
CmdArgs.push_back("-fno-threadsafe-statics");
69846984

6985+
// Add -fno-assumptions, if it was specified.
6986+
if (!Args.hasFlag(options::OPT_fassumptions, options::OPT_fno_assumptions,
6987+
true))
6988+
CmdArgs.push_back("-fno-assumptions");
6989+
69856990
// -fgnu-keywords default varies depending on language; only pass if
69866991
// specified.
69876992
Args.AddLastArg(CmdArgs, options::OPT_fgnu_keywords,

clang/lib/Parse/ParseDeclCXX.cpp

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4528,6 +4528,61 @@ static bool IsBuiltInOrStandardCXX11Attribute(IdentifierInfo *AttrName,
45284528
}
45294529
}
45304530

4531+
/// Parse the argument to C++23's [[assume()]] attribute.
4532+
bool Parser::ParseCXXAssumeAttributeArg(ParsedAttributes &Attrs,
4533+
IdentifierInfo *AttrName,
4534+
SourceLocation AttrNameLoc,
4535+
SourceLocation *EndLoc) {
4536+
assert(Tok.is(tok::l_paren) && "Not a C++11 attribute argument list");
4537+
BalancedDelimiterTracker T(*this, tok::l_paren);
4538+
T.consumeOpen();
4539+
4540+
// [dcl.attr.assume]: The expression is potentially evaluated.
4541+
EnterExpressionEvaluationContext Unevaluated(
4542+
Actions, Sema::ExpressionEvaluationContext::PotentiallyEvaluated);
4543+
4544+
TentativeParsingAction TPA(*this);
4545+
ExprResult Res(
4546+
Actions.CorrectDelayedTyposInExpr(ParseConditionalExpression()));
4547+
if (Res.isInvalid()) {
4548+
TPA.Commit();
4549+
SkipUntil(tok::r_paren, tok::r_square, StopAtSemi | StopBeforeMatch);
4550+
if (Tok.is(tok::r_paren))
4551+
T.consumeClose();
4552+
return true;
4553+
}
4554+
4555+
if (!Tok.isOneOf(tok::r_paren, tok::r_square)) {
4556+
// Emit a better diagnostic if this is an otherwise valid expression that
4557+
// is not allowed here.
4558+
TPA.Revert();
4559+
Res = ParseExpression();
4560+
if (!Res.isInvalid()) {
4561+
auto *E = Res.get();
4562+
Diag(E->getExprLoc(), diag::err_assume_attr_expects_cond_expr)
4563+
<< AttrName << FixItHint::CreateInsertion(E->getBeginLoc(), "(")
4564+
<< FixItHint::CreateInsertion(PP.getLocForEndOfToken(E->getEndLoc()),
4565+
")")
4566+
<< E->getSourceRange();
4567+
}
4568+
4569+
T.consumeClose();
4570+
return true;
4571+
}
4572+
4573+
TPA.Commit();
4574+
ArgsUnion Assumption = Res.get();
4575+
auto RParen = Tok.getLocation();
4576+
T.consumeClose();
4577+
Attrs.addNew(AttrName, SourceRange(AttrNameLoc, RParen), nullptr,
4578+
SourceLocation(), &Assumption, 1, ParsedAttr::Form::CXX11());
4579+
4580+
if (EndLoc)
4581+
*EndLoc = RParen;
4582+
4583+
return false;
4584+
}
4585+
45314586
/// ParseCXX11AttributeArgs -- Parse a C++11 attribute-argument-clause.
45324587
///
45334588
/// [C++11] attribute-argument-clause:
@@ -4596,7 +4651,12 @@ bool Parser::ParseCXX11AttributeArgs(
45964651
if (ScopeName && (ScopeName->isStr("clang") || ScopeName->isStr("_Clang")))
45974652
NumArgs = ParseClangAttributeArgs(AttrName, AttrNameLoc, Attrs, EndLoc,
45984653
ScopeName, ScopeLoc, Form);
4599-
else
4654+
// So does C++23's assume() attribute.
4655+
else if (!ScopeName && AttrName->isStr("assume")) {
4656+
if (ParseCXXAssumeAttributeArg(Attrs, AttrName, AttrNameLoc, EndLoc))
4657+
return true;
4658+
NumArgs = 1;
4659+
} else
46004660
NumArgs = ParseAttributeArgsCommon(AttrName, AttrNameLoc, Attrs, EndLoc,
46014661
ScopeName, ScopeLoc, Form);
46024662

clang/lib/Parse/ParseExpr.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,19 @@ ExprResult Parser::ParseAssignmentExpression(TypeCastState isTypeCast) {
179179
return ParseRHSOfBinaryExpression(LHS, prec::Assignment);
180180
}
181181

182+
ExprResult Parser::ParseConditionalExpression() {
183+
if (Tok.is(tok::code_completion)) {
184+
cutOffParsing();
185+
Actions.CodeCompleteExpression(getCurScope(),
186+
PreferredType.get(Tok.getLocation()));
187+
return ExprError();
188+
}
189+
190+
ExprResult LHS = ParseCastExpression(
191+
AnyCastExpr, /*isAddressOfOperand=*/false, NotTypeCast);
192+
return ParseRHSOfBinaryExpression(LHS, prec::Conditional);
193+
}
194+
182195
/// Parse an assignment expression where part of an Objective-C message
183196
/// send has already been parsed.
184197
///

0 commit comments

Comments
 (0)