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 @@ -1760,10 +1760,14 @@ let SimpleHandler = 1; } -def NoInline : InheritableAttr { - let Spellings = [GCC<"noinline">, Declspec<"noinline">]; - let Subjects = SubjectList<[Function]>; - let Documentation = [Undocumented]; +def NoInline : DeclOrStmtAttr { + let Spellings = [GCC<"noinline">, CXX11<"clang", "noinline">, + C2x<"clang", "noinline">, Declspec<"noinline">]; + let Accessors = [Accessor<"isClangNoInline", [CXX11<"clang", "noinline">, + C2x<"clang", "noinline">]>]; + let Documentation = [NoInlineDocs]; + let Subjects = SubjectList<[Function, Stmt], ErrorDiag, + "functions and statements">; let SimpleHandler = 1; } 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 @@ -527,6 +527,29 @@ }]; } +def NoInlineDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +This function attribute suppresses the inlining of a function at the call sites +of the function. + +``[[clang::noinline]]`` spelling can be used as a statement attribute; other +spellings of the attribute are not supported on statements. If a statement is +marked ``[[clang::noinline]]`` and contains calls, those calls inside the +statement will not be inlined by the compiler. + +.. code-block:: c + + int example(void) { + int r; + [[clang::noinline]] foo(); + [[clang::noinline]] r = bar(); + return r; + } + + }]; +} + def MustTailDocs : Documentation { let Category = DocCatStmt; let Content = [{ 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 @@ -2891,11 +2891,16 @@ InGroup>; // Attributes -def warn_nomerge_attribute_ignored_in_stmt: Warning< +def warn_attribute_ignored_no_calls_in_stmt: Warning< "%0 attribute is ignored because there exists no call expression inside the " "statement">, InGroup; +def warn_function_attribute_ignored_in_stmt : Warning< + "attribute is ignored on this statement as it only applies to functions; " + "use '%0' on statements">, + InGroup; + def err_musttail_needs_trivial_args : Error< "tail call requires that the return value, all parameters, and any " "temporaries created by the expression are trivially destructible">; @@ -4033,6 +4038,10 @@ def warn_attribute_nonnull_parm_no_args : Warning< "'nonnull' attribute when used on parameters takes no arguments">, InGroup; +def warn_function_stmt_attribute_precedence : Warning< + "statement attribute %0 has higher precedence than function attribute " + "'%select{always_inline|flatten}1'">, + InGroup; def note_declared_nonnull : Note< "declared %select{'returns_nonnull'|'nonnull'}0 here">; def warn_attribute_sentinel_named_arguments : Warning< diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -5244,12 +5244,17 @@ if (InNoMergeAttributedStmt) Attrs = Attrs.addFnAttribute(getLLVMContext(), llvm::Attribute::NoMerge); + // Add call-site noinline attribute if exists. + if (InNoInlineAttributedStmt) + Attrs = Attrs.addFnAttribute(getLLVMContext(), llvm::Attribute::NoInline); + // Apply some call-site-specific attributes. // TODO: work this into building the attribute set. // Apply always_inline to all calls within flatten functions. // FIXME: should this really take priority over __try, below? if (CurCodeDecl && CurCodeDecl->hasAttr() && + !InNoInlineAttributedStmt && !(TargetDecl && TargetDecl->hasAttr())) { Attrs = Attrs.addFnAttribute(getLLVMContext(), llvm::Attribute::AlwaysInline); 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 @@ -666,12 +666,16 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) { bool nomerge = false; + bool noinline = false; const CallExpr *musttail = nullptr; for (const auto *A : S.getAttrs()) { if (A->getKind() == attr::NoMerge) { nomerge = true; } + if (A->getKind() == attr::NoInline) { + noinline = true; + } if (A->getKind() == attr::MustTail) { const Stmt *Sub = S.getSubStmt(); const ReturnStmt *R = cast(Sub); @@ -679,6 +683,7 @@ } } SaveAndRestore save_nomerge(InNoMergeAttributedStmt, nomerge); + SaveAndRestore save_noinline(InNoInlineAttributedStmt, noinline); SaveAndRestore save_musttail(MustTailCall, musttail); EmitStmt(S.getSubStmt(), S.getAttrs()); } diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -551,6 +551,9 @@ /// True if the current statement has nomerge attribute. bool InNoMergeAttributedStmt = false; + /// True if the current statement has noinline attribute. + bool InNoInlineAttributedStmt = false; + // The CallExpr within the current statement that the musttail attribute // applies to. nullptr if there is no 'musttail' on the current statement. const CallExpr *MustTailCall = nullptr; 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 @@ -175,17 +175,22 @@ namespace { class CallExprFinder : public ConstEvaluatedExprVisitor { - bool FoundCallExpr = false; + bool FoundAsmStmt = false; + std::vector CallExprs; public: typedef ConstEvaluatedExprVisitor Inherited; CallExprFinder(Sema &S, const Stmt *St) : Inherited(S.Context) { Visit(St); } - bool foundCallExpr() { return FoundCallExpr; } + bool foundCallExpr() { return !CallExprs.empty(); } + const std::vector &getCallExprs() { return CallExprs; } - void VisitCallExpr(const CallExpr *E) { FoundCallExpr = true; } - void VisitAsmStmt(const AsmStmt *S) { FoundCallExpr = true; } + bool foundAsmStmt() { return FoundAsmStmt; } + + void VisitCallExpr(const CallExpr *E) { CallExprs.push_back(E); } + + void VisitAsmStmt(const AsmStmt *S) { FoundAsmStmt = true; } void Visit(const Stmt *St) { if (!St) @@ -200,8 +205,8 @@ NoMergeAttr NMA(S.Context, A); CallExprFinder CEF(S, St); - if (!CEF.foundCallExpr()) { - S.Diag(St->getBeginLoc(), diag::warn_nomerge_attribute_ignored_in_stmt) + if (!CEF.foundCallExpr() && !CEF.foundAsmStmt()) { + S.Diag(St->getBeginLoc(), diag::warn_attribute_ignored_no_calls_in_stmt) << A; return nullptr; } @@ -209,6 +214,33 @@ return ::new (S.Context) NoMergeAttr(S.Context, A); } +static Attr *handleNoInlineAttr(Sema &S, Stmt *St, const ParsedAttr &A, + SourceRange Range) { + NoInlineAttr NIA(S.Context, A); + CallExprFinder CEF(S, St); + + if (!NIA.isClangNoInline()) { + S.Diag(St->getBeginLoc(), diag::warn_function_attribute_ignored_in_stmt) + << "[[clang::noinline]]"; + return nullptr; + } + + if (!CEF.foundCallExpr()) { + S.Diag(St->getBeginLoc(), diag::warn_attribute_ignored_no_calls_in_stmt) + << A; + return nullptr; + } + + for (const auto *CallExpr : CEF.getCallExprs()) { + const Decl *Decl = CallExpr->getCalleeDecl(); + if (Decl->hasAttr() || Decl->hasAttr()) + S.Diag(St->getBeginLoc(), diag::warn_function_stmt_attribute_precedence) + << A << (Decl->hasAttr() ? 0 : 1); + } + + return ::new (S.Context) NoInlineAttr(S.Context, A); +} + static Attr *handleMustTailAttr(Sema &S, Stmt *St, const ParsedAttr &A, SourceRange Range) { // Validation is in Sema::ActOnAttributedStmt(). @@ -418,6 +450,8 @@ return handleSuppressAttr(S, St, A, Range); case ParsedAttr::AT_NoMerge: return handleNoMergeAttr(S, St, A, Range); + case ParsedAttr::AT_NoInline: + return handleNoInlineAttr(S, St, A, Range); case ParsedAttr::AT_MustTail: return handleMustTailAttr(S, St, A, Range); case ParsedAttr::AT_Likely: diff --git a/clang/test/CodeGen/attr-noinline.cpp b/clang/test/CodeGen/attr-noinline.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/attr-noinline.cpp @@ -0,0 +1,55 @@ +// RUN: %clang_cc1 -S -emit-llvm %s -triple x86_64-unknown-linux-gnu -o - | FileCheck %s + +bool bar(); +void f(bool, bool); +void g(bool); + +static int baz(int x) { + return x * 10; +} + +[[clang::noinline]] bool noi() { } + +void foo(int i) { + [[clang::noinline]] bar(); +// CHECK: call noundef zeroext i1 @_Z3barv() #[[NOINLINEATTR:[0-9]+]] + [[clang::noinline]] i = baz(i); +// CHECK: call noundef i32 @_ZL3bazi({{.*}}) #[[NOINLINEATTR]] + [[clang::noinline]] (i = 4, bar()); +// CHECK: call noundef zeroext i1 @_Z3barv() #[[NOINLINEATTR]] + [[clang::noinline]] (void)(bar()); +// CHECK: call noundef zeroext i1 @_Z3barv() #[[NOINLINEATTR]] + [[clang::noinline]] f(bar(), bar()); +// CHECK: call noundef zeroext i1 @_Z3barv() #[[NOINLINEATTR]] +// CHECK: call noundef zeroext i1 @_Z3barv() #[[NOINLINEATTR]] +// CHECK: call void @_Z1fbb({{.*}}) #[[NOINLINEATTR]] + [[clang::noinline]] [] { bar(); bar(); }(); // noinline only applies to the anonymous function call +// CHECK: call void @"_ZZ3fooiENK3$_0clEv"(%class.anon* {{[^,]*}} %ref.tmp) #[[NOINLINEATTR]] + [[clang::noinline]] for (bar(); bar(); bar()) {} +// CHECK: call noundef zeroext i1 @_Z3barv() #[[NOINLINEATTR]] +// CHECK: call noundef zeroext i1 @_Z3barv() #[[NOINLINEATTR]] +// CHECK: call noundef zeroext i1 @_Z3barv() #[[NOINLINEATTR]] + bar(); +// CHECK: call noundef zeroext i1 @_Z3barv() + [[clang::noinline]] noi(); +// CHECK: call noundef zeroext i1 @_Z3noiv() + noi(); +// CHECK: call noundef zeroext i1 @_Z3noiv() + [[gnu::noinline]] bar(); +// CHECK: call noundef zeroext i1 @_Z3barv() +} + +struct S { + friend bool operator==(const S &LHS, const S &RHS); +}; + +void func(const S &s1, const S &s2) { + [[clang::noinline]]g(s1 == s2); +// CHECK: call noundef zeroext i1 @_ZeqRK1SS1_({{.*}}) #[[NOINLINEATTR]] +// CHECK: call void @_Z1gb({{.*}}) #[[NOINLINEATTR]] + bool b; + [[clang::noinline]] b = s1 == s2; +// CHECK: call noundef zeroext i1 @_ZeqRK1SS1_({{.*}}) #[[NOINLINEATTR]] +} + +// CHECK: attributes #[[NOINLINEATTR]] = { noinline } diff --git a/clang/test/Parser/stmt-attributes.c b/clang/test/Parser/stmt-attributes.c --- a/clang/test/Parser/stmt-attributes.c +++ b/clang/test/Parser/stmt-attributes.c @@ -45,7 +45,7 @@ } __attribute__((fastcall)) goto there; // expected-error {{'fastcall' attribute cannot be applied to a statement}} - __attribute__((noinline)) there : // expected-warning {{'noinline' attribute only applies to functions}} + __attribute__((noinline)) there : // expected-error {{'noinline' attribute only applies to functions and statements}} __attribute__((weakref)) return; // expected-error {{'weakref' attribute only applies to variables and functions}} diff --git a/clang/test/Sema/attr-noinline.c b/clang/test/Sema/attr-noinline.c --- a/clang/test/Sema/attr-noinline.c +++ b/clang/test/Sema/attr-noinline.c @@ -1,6 +1,6 @@ // RUN: %clang_cc1 %s -verify -fsyntax-only -int a __attribute__((noinline)); // expected-warning {{'noinline' attribute only applies to functions}} +int a __attribute__((noinline)); // expected-error {{'noinline' attribute only applies to functions and statements}} void t1(void) __attribute__((noinline)); diff --git a/clang/test/Sema/attr-noinline.cpp b/clang/test/Sema/attr-noinline.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-noinline.cpp @@ -0,0 +1,27 @@ +// RUN: %clang_cc1 -verify -fsyntax-only %s + +int bar(); + +[[gnu::always_inline]] void always_inline_fn(void) { } +[[gnu::flatten]] void flatten_fn(void) { } + +[[gnu::noinline]] void noinline_fn(void) { } + +void foo() { + [[clang::noinline]] bar(); + [[clang::noinline(0)]] bar(); // expected-error {{'noinline' attribute takes no arguments}} + int x; + [[clang::noinline]] x = 0; // expected-warning {{'noinline' attribute is ignored because there exists no call expression inside the statement}} + [[clang::noinline]] { asm("nop"); } // expected-warning {{'noinline' attribute is ignored because there exists no call expression inside the statement}} + [[clang::noinline]] label: x = 1; // expected-error {{'noinline' attribute only applies to functions and statements}} + + + [[clang::noinline]] always_inline_fn(); // expected-warning {{statement attribute 'noinline' has higher precedence than function attribute 'always_inline'}} + [[clang::noinline]] flatten_fn(); // expected-warning {{statement attribute 'noinline' has higher precedence than function attribute 'flatten'}} + [[clang::noinline]] noinline_fn(); + + [[gnu::noinline]] bar(); // expected-warning {{attribute is ignored on this statement as it only applies to functions; use '[[clang::noinline]]' on statements}} + __attribute__((noinline)) bar(); // expected-warning {{attribute is ignored on this statement as it only applies to functions; use '[[clang::noinline]]' on statements}} +} + +[[clang::noinline]] static int i = bar(); // expected-error {{'noinline' attribute only applies to functions and statements}}