diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -133,6 +133,8 @@ C++2b Feature Support ^^^^^^^^^^^^^^^^^^^^^ +- Support the ``[[assume(expression)]]`` attribute (`P1774R8 `_). + CUDA/HIP Language Changes in Clang ---------------------------------- diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1444,6 +1444,13 @@ } def : MutualExclusions<[Likely, Unlikely]>; +def Assume : StmtAttr { + let Spellings = [CXX11<"", "assume", 202207>]; + let Documentation = [AssumeDocs]; + let Subjects = SubjectList<[NullStmt], ErrorDiag, "empty statements">; + let Args = [ExprArgument<"Cond">]; +} + def NoMerge : DeclOrStmtAttr { let Spellings = [Clang<"nomerge">]; let Documentation = [NoMergeDocs]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -2100,6 +2100,36 @@ }]; } +def AssumeDocs : Documentation { + let Category = DocCatStmt; + let Heading = "assume"; + let Content = [{ +The ``assume`` attribute used to provide the optimizer with an expression that +is defined to evaluate to ``true`` at the place the assumption occurs. The +optimizer may analyze the form of the expression provided as the argument and +deduce from that information used to optimize the program. If the condition is +violated during execution, the behavior is undefined. The argument itself is +never evaluated, so any side effects of the expression will be discarded. + +Example usage: + +.. code-block:: c++ + + int divide_by_32(int x) { + [[assume(x >= 0)]]; + return x/32; // is equivalent to `return x>>5;` + } + + int increment(int y) { + [[assume(++y == 43)]]; + return y; // is equivalent to `return 42;` + } + +``[[assume(expression)]]`` is a standard C++23 attribute. Clang supports its +use in C++11 onwards. + }]; +} + def ARMInterruptDocs : Documentation { let Category = DocCatFunction; let Heading = "interrupt (ARM)"; diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1087,9 +1087,11 @@ def CXX14Attrs : DiagGroup<"c++14-attribute-extensions">; def CXX17Attrs : DiagGroup<"c++17-attribute-extensions">; def CXX20Attrs : DiagGroup<"c++20-attribute-extensions">; +def CXX2bAttrs : DiagGroup<"c++2b-attribute-extensions">; def FutureAttrs : DiagGroup<"future-attribute-extensions", [CXX14Attrs, CXX17Attrs, - CXX20Attrs]>; + CXX20Attrs, + CXX2bAttrs]>; // A warning group for warnings about using C++11 features as extensions in // earlier C++ versions. diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -8796,6 +8796,8 @@ "use of the %0 attribute is a C++17 extension">, InGroup; def ext_cxx20_attr : Extension< "use of the %0 attribute is a C++20 extension">, InGroup; +def ext_cxx2b_attr : Extension< + "use of the %0 attribute is a C++2b extension">, InGroup; def warn_unused_comparison : Warning< "%select{equality|inequality|relational|three-way}0 comparison result unused">, diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -714,12 +714,19 @@ case attr::AlwaysInline: alwaysinline = true; break; - case attr::MustTail: + case attr::MustTail: { const Stmt *Sub = S.getSubStmt(); const ReturnStmt *R = cast(Sub); musttail = cast(R->getRetValue()->IgnoreParens()); break; } + case attr::Assume: { + llvm::Value *ArgValue = EmitScalarExpr(cast(A)->getCond()); + llvm::Function *FnAssume = CGM.getIntrinsic(llvm::Intrinsic::assume); + Builder.CreateCall(FnAssume, ArgValue); + break; + } + } } SaveAndRestore save_nomerge(InNoMergeAttributedStmt, nomerge); SaveAndRestore save_noinline(InNoInlineAttributedStmt, noinline); diff --git a/clang/lib/Sema/SemaStmtAttr.cpp b/clang/lib/Sema/SemaStmtAttr.cpp --- a/clang/lib/Sema/SemaStmtAttr.cpp +++ b/clang/lib/Sema/SemaStmtAttr.cpp @@ -291,6 +291,15 @@ return ::new (S.Context) UnlikelyAttr(S.Context, A); } +static Attr *handleAssume(Sema &S, Stmt *St, const ParsedAttr &A, + SourceRange Range) { + + if (!S.getLangOpts().CPlusPlus2b) + S.Diag(A.getLoc(), diag::ext_cxx2b_attr) << A << Range; + + return ::new (S.Context) AssumeAttr(S.Context, A, A.getArgAsExpr(0)); +} + #define WANT_STMT_MERGE_LOGIC #include "clang/Sema/AttrParsedAttrImpl.inc" #undef WANT_STMT_MERGE_LOGIC @@ -490,6 +499,8 @@ return handleLikely(S, St, A, Range); case ParsedAttr::AT_Unlikely: return handleUnlikely(S, St, A, Range); + case ParsedAttr::AT_Assume: + return handleAssume(S, St, A, Range); default: // N.B., ClangAttrEmitter.cpp emits a diagnostic helper that ensures a // declaration attribute is not written on a statement, but this code is diff --git a/clang/test/AST/ast-dump-attr.cpp b/clang/test/AST/ast-dump-attr.cpp --- a/clang/test/AST/ast-dump-attr.cpp +++ b/clang/test/AST/ast-dump-attr.cpp @@ -259,3 +259,18 @@ // CHECK-NEXT: AnnotateAttr{{.*}} Inherited // CHECK-NEXT: UnusedAttr // CHECK-NEXT: NoThreadSafetyAnalysisAttr + +namespace TestAssume { + int DivideBy32(int x) { + [[assume(x >= 0)]]; + return x/32; + } + // CHECK: FunctionDecl{{.*}}DivideBy32 + // CHECK: |-AttributedStmt + // CHECK-NEXT: | |-AssumeAttr + // CHECK-NEXT: | | `-BinaryOperator + // CHECK-NEXT: | | |-ImplicitCastExpr + // CHECK-NEXT: | | | `-DeclRefExpr + // CHECK-NEXT: | | `-IntegerLiteral + // CHECK-NEXT: | `-NullStmt +} // namespace TestAssume diff --git a/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.assume/p1.cpp b/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.assume/p1.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.assume/p1.cpp @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -fsyntax-only -Wunused -std=c++2b -verify %s + +int divide_by_32(int x) { + [[assume(x >= 0)]]; + return x/32; +} + +int increment(int y) { + [[assume(++y == 43)]]; + return y; +} + +[[assume(true)]] int incorrects(int y) { // expected-error {{'assume' attribute cannot be applied to a declaration}} + [[assume(++y == 43)]] int x = 0; // expected-error {{'assume' attribute cannot be applied to a declaration}} + [[assume(++y == 43)]] x = 0; // expected-error {{'assume' attribute only applies to empty statements}} + [[assume(++y == 43 && ++y == 44)]]; + [[assume(++y == 43, ++y == 43)]]; // expected-error {{'assume' attribute takes one argument}} + [[assume]]; // expected-error {{'assume' attribute takes one argument}} + return y; +} diff --git a/clang/test/CodeGenCXX/cxx2b-assume.cpp b/clang/test/CodeGenCXX/cxx2b-assume.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/cxx2b-assume.cpp @@ -0,0 +1,62 @@ +// RUN: %clang_cc1 -std=c++2b %s -emit-llvm -o - -triple x86_64-linux-gnu -disable-llvm-passes -DUSE_ASSUME | FileCheck %s +// RUN: %clang_cc1 -std=c++2b %s -emit-llvm -o - -triple x86_64-linux-gnu -O3 | FileCheck --check-prefixes=CHECK-OPT-NOASSUME %s +// RUN: %clang_cc1 -std=c++2b %s -emit-llvm -o - -triple x86_64-linux-gnu -O3 -DUSE_ASSUME | FileCheck --check-prefixes=CHECK-OPT %s + +// CHECK-LABEL: @_Z12divide_by_32i +// CHECK-NEXT: entry: +// CHECK-NEXT: %[[VAR:.*]] = alloca i32, align 4 +// CHECK-NEXT: store i32 %x, ptr %[[VAR]], align 4 +// CHECK-NEXT: %0 = load i32, ptr %[[VAR]], align 4 +// CHECK-NEXT: %[[CMP:.*]] = icmp sge i32 %0, 0 +// CHECK-NEXT: call void @llvm.assume(i1 %[[CMP]]) +// CHECK-NEXT: %1 = load i32, ptr %[[VAR]], align 4 +// CHECK-NEXT: %[[DIV:.*]] = sdiv i32 %1, 32 +// CHECK-NEXT: ret i32 %[[DIV]] +// +// CHECK-OPT-NOASSUME-LABEL: @_Z12divide_by_32i +// CHECK-OPT-NOASSUME-NEXT: entry: +// CHECK-OPT-NOASSUME-NEXT: %[[DIV:.*]] = sdiv i32 %x, 32 +// CHECK-OPT-NOASSUME-NEXT: ret i32 %[[DIV]] +// +// CHECK-OPT-LABEL: @_Z12divide_by_32i +// CHECK-OPT-NEXT: entry: +// CHECK-OPT-NEXT: %[[CMP:.*]] = icmp sgt i32 %x, -1 +// CHECK-OPT-NEXT: tail call void @llvm.assume(i1 %[[CMP]]) +// CHECK-OPT-NEXT: %[[DIV:.*]] = lshr i32 %x, 5 +// CHECK-OPT-NEXT: ret i32 %[[DIV]] +// +int divide_by_32(int x) { +#ifdef USE_ASSUME + [[assume(x >= 0)]]; +#endif + return x/32; +} + +// CHECK-LABEL: @_Z9incrementi +// CHECK-NEXT: entry: +// CHECK-NEXT: %[[VAR:.*]] = alloca i32, align 4 +// CHECK-NEXT: store i32 %y, ptr %[[VAR]], align 4 +// CHECK-NEXT: %0 = load i32, ptr %[[VAR]], align 4 +// CHECK-NEXT: %[[VARP1:.*]] = add nsw i32 %0, 1 +// CHECK-NEXT: store i32 %[[VARP1]], ptr %[[VAR]], align 4 +// CHECK-NEXT: %[[CMP:.*]] = icmp eq i32 %[[VARP1]], 43 +// CHECK-NEXT: call void @llvm.assume(i1 %[[CMP]]) +// CHECK-NEXT: %1 = load i32, ptr %[[VAR]], align 4 +// CHECK-NEXT: ret i32 %1 +// +// CHECK-OPT-NOASSUME-LABEL: @_Z9incrementi +// CHECK-OPT-NOASSUME-NEXT: entry: +// CHECK-OPT-NOASSUME-NEXT: ret i32 %y +// +// CHECK-OPT-LABEL: @_Z9incrementi +// CHECK-OPT-NEXT: entry: +// CHECK-OPT-NEXT: %[[CMP:.*]] = icmp eq i32 %y, 42 +// CHECK-OPT-NEXT: tail call void @llvm.assume(i1 %[[CMP]]) +// CHECK-OPT-NEXT: ret i32 43 +// +int increment(int y) { +#ifdef USE_ASSUME + [[assume(++y == 43)]]; +#endif + return y; +} diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -1501,7 +1501,7 @@ Portable assumptions P1774R8 - No + Clang 17 Support for UTF-8 as a portable source file encoding