Skip to content

Commit

Permalink
[Clang][C++23] Implement P1774R8: Portable assumptions (#81014)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Sirraide committed Mar 9, 2024
1 parent 9df7194 commit 2b5f68a
Show file tree
Hide file tree
Showing 28 changed files with 502 additions and 29 deletions.
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Expand Up @@ -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>`_.

Expand Down
11 changes: 9 additions & 2 deletions clang/include/clang/Basic/Attr.td
Expand Up @@ -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];
Expand Down Expand Up @@ -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">];
}

Expand Down
30 changes: 29 additions & 1 deletion clang/include/clang/Basic/AttrDocs.td
Expand Up @@ -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";
Expand Down Expand Up @@ -4629,7 +4657,7 @@ For more information see
}];
}

def AssumptionDocs : Documentation {
def OMPAssumeDocs : Documentation {
let Category = DocCatFunction;
let Heading = "assume";
let Content = [{
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticASTKinds.td
Expand Up @@ -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">;

Expand Down
4 changes: 3 additions & 1 deletion clang/include/clang/Basic/DiagnosticGroups.td
Expand Up @@ -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">;

Expand Down
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticParseKinds.td
Expand Up @@ -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;
Expand Down
9 changes: 7 additions & 2 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -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>;
Expand Down Expand Up @@ -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">,
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/LangOptions.def
Expand Up @@ -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")
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Driver/Options.td
Expand Up @@ -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>;
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Parse/Parser.h
Expand Up @@ -1803,6 +1803,7 @@ class Parser : public CodeCompletionHandler {
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,
Expand Down Expand Up @@ -2955,6 +2956,12 @@ class Parser : public CodeCompletionHandler {
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,
Expand Down
10 changes: 8 additions & 2 deletions clang/include/clang/Sema/Sema.h
Expand Up @@ -9011,6 +9011,12 @@ class Sema final {
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);

///@}

//
Expand Down Expand Up @@ -14716,10 +14722,10 @@ class Sema final {
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
Expand Down
23 changes: 23 additions & 0 deletions clang/lib/AST/ExprConstant.cpp
Expand Up @@ -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);
}

Expand Down
8 changes: 4 additions & 4 deletions clang/lib/CodeGen/CGCall.cpp
Expand Up @@ -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())
Expand Down Expand Up @@ -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.
Expand Down
12 changes: 10 additions & 2 deletions clang/lib/CodeGen/CGStmt.cpp
Expand Up @@ -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);
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/Driver/ToolChains/Clang.cpp
Expand Up @@ -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,
Expand Down
62 changes: 61 additions & 1 deletion clang/lib/Parse/ParseDeclCXX.cpp
Expand Up @@ -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:
Expand Down Expand Up @@ -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);

Expand Down
13 changes: 13 additions & 0 deletions clang/lib/Parse/ParseExpr.cpp
Expand Up @@ -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.
///
Expand Down

0 comments on commit 2b5f68a

Please sign in to comment.