Index: clang/include/clang/Basic/Attr.td =================================================================== --- clang/include/clang/Basic/Attr.td +++ clang/include/clang/Basic/Attr.td @@ -706,9 +706,13 @@ let Documentation = [Undocumented]; } -def AlwaysInline : InheritableAttr { - let Spellings = [GCC<"always_inline">, Keyword<"__forceinline">]; - let Subjects = SubjectList<[Function]>; +def AlwaysInline : DeclOrStmtAttr { + let Spellings = [GCC<"always_inline">, CXX11<"clang", "always_inline">, + C2x<"clang", "always_inline">, Keyword<"__forceinline">]; + let Accessors = [Accessor<"isClangAlwaysInline", [CXX11<"clang", "always_inline">, + C2x<"clang", "always_inline">]>]; + let Subjects = SubjectList<[Function, Stmt], WarnDiag, + "functions and statements">; let Documentation = [AlwaysInlineDocs]; } Index: clang/include/clang/Basic/AttrDocs.td =================================================================== --- clang/include/clang/Basic/AttrDocs.td +++ clang/include/clang/Basic/AttrDocs.td @@ -6241,7 +6241,28 @@ Inlining heuristics are disabled and inlining is always attempted regardless of optimization level. -Does not guarantee that inline substitution actually occurs. +``[[clang::always_inline]]`` spelling can be used as a statement attribute; other +spellings of the attribute are not supported on statements. If a statement is +marked ``[[clang::always_inline]]`` and contains calls, the compiler attempts +to inline those calls. + +.. code-block:: c + + int example(void) { + int i; + [[clang::always_inline]] foo(); // attempts to inline foo + [[clang::always_inline]] i = bar(); // attempts to inline bar + [[clang::always_inline]] return f(42, baz(bar())); // attempts to inline everything + } + +A declaration statement, which is a statement, is not a statement that can have an +attribute associated with it (the attribute applies to the declaration, not the +statement in that case). So this use case will not work: + +.. code-block:: c + [[clang::always_inline]] int i = bar(); + +This attribute does not guarantee that inline substitution actually occurs. Note: applying this attribute to a coroutine at the `-O0` optimization level has no effect; other optimization levels may only partially inline and result in a Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -4040,7 +4040,7 @@ InGroup; def warn_function_stmt_attribute_precedence : Warning< "statement attribute %0 has higher precedence than function attribute " - "'%select{always_inline|flatten}1'">, + "'%select{always_inline|flatten|noinline}1'">, InGroup; def note_declared_nonnull : Note< "declared %select{'returns_nonnull'|'nonnull'}0 here">; Index: clang/lib/CodeGen/CGCall.cpp =================================================================== --- clang/lib/CodeGen/CGCall.cpp +++ clang/lib/CodeGen/CGCall.cpp @@ -5248,6 +5248,11 @@ if (InNoInlineAttributedStmt) Attrs = Attrs.addFnAttribute(getLLVMContext(), llvm::Attribute::NoInline); + // Add call-site always_inline attribute if exists. + if (InAlwaysInlineAttributedStmt) + Attrs = + Attrs.addFnAttribute(getLLVMContext(), llvm::Attribute::AlwaysInline); + // Apply some call-site-specific attributes. // TODO: work this into building the attribute set. Index: clang/lib/CodeGen/CGStmt.cpp =================================================================== --- clang/lib/CodeGen/CGStmt.cpp +++ clang/lib/CodeGen/CGStmt.cpp @@ -667,23 +667,33 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) { bool nomerge = false; bool noinline = false; + bool alwaysinline = false; const CallExpr *musttail = nullptr; for (const auto *A : S.getAttrs()) { - if (A->getKind() == attr::NoMerge) { + switch (A->getKind()) { + default: + break; + case attr::NoMerge: nomerge = true; - } - if (A->getKind() == attr::NoInline) { + break; + case attr::NoInline: noinline = true; - } - if (A->getKind() == attr::MustTail) { + break; + case attr::AlwaysInline: + alwaysinline = true; + break; + case attr::MustTail: const Stmt *Sub = S.getSubStmt(); const ReturnStmt *R = cast(Sub); musttail = cast(R->getRetValue()->IgnoreParens()); + break; } } SaveAndRestore save_nomerge(InNoMergeAttributedStmt, nomerge); SaveAndRestore save_noinline(InNoInlineAttributedStmt, noinline); + SaveAndRestore save_alwaysinline(InAlwaysInlineAttributedStmt, + alwaysinline); SaveAndRestore save_musttail(MustTailCall, musttail); EmitStmt(S.getSubStmt(), S.getAttrs()); } Index: clang/lib/CodeGen/CodeGenFunction.h =================================================================== --- clang/lib/CodeGen/CodeGenFunction.h +++ clang/lib/CodeGen/CodeGenFunction.h @@ -554,6 +554,9 @@ /// True if the current statement has noinline attribute. bool InNoInlineAttributedStmt = false; + /// True if the current statement has always_inline attribute. + bool InAlwaysInlineAttributedStmt = 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; Index: clang/lib/Sema/SemaStmtAttr.cpp =================================================================== --- clang/lib/Sema/SemaStmtAttr.cpp +++ clang/lib/Sema/SemaStmtAttr.cpp @@ -241,6 +241,32 @@ return ::new (S.Context) NoInlineAttr(S.Context, A); } +static Attr *handleAlwaysInlineAttr(Sema &S, Stmt *St, const ParsedAttr &A, + SourceRange Range) { + AlwaysInlineAttr AIA(S.Context, A); + if (!AIA.isClangAlwaysInline()) { + S.Diag(St->getBeginLoc(), diag::warn_function_attribute_ignored_in_stmt) + << "[[clang::always_inline]]"; + return nullptr; + } + + CallExprFinder CEF(S, St); + 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() ? 2 : 1); + } + + return ::new (S.Context) AlwaysInlineAttr(S.Context, A); +} + static Attr *handleMustTailAttr(Sema &S, Stmt *St, const ParsedAttr &A, SourceRange Range) { // Validation is in Sema::ActOnAttributedStmt(). @@ -440,6 +466,8 @@ return nullptr; switch (A.getKind()) { + case ParsedAttr::AT_AlwaysInline: + return handleAlwaysInlineAttr(S, St, A, Range); case ParsedAttr::AT_FallThrough: return handleFallThroughAttr(S, St, A, Range); case ParsedAttr::AT_LoopHint: Index: clang/test/CodeGen/attr-alwaysinline.cpp =================================================================== --- /dev/null +++ clang/test/CodeGen/attr-alwaysinline.cpp @@ -0,0 +1,41 @@ +// 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); + +void foo(int i) { + [[clang::always_inline]] bar(); +// CHECK: call noundef zeroext i1 @_Z3barv() #[[ALWAYSINLINEATTR:[0-9]+]] + [[clang::always_inline]] (i = 4, bar()); +// CHECK: call noundef zeroext i1 @_Z3barv() #[[ALWAYSINLINEATTR]] + [[clang::always_inline]] (void)(bar()); +// CHECK: call noundef zeroext i1 @_Z3barv() #[[ALWAYSINLINEATTR]] + [[clang::always_inline]] f(bar(), bar()); +// CHECK: call noundef zeroext i1 @_Z3barv() #[[ALWAYSINLINEATTR]] +// CHECK: call noundef zeroext i1 @_Z3barv() #[[ALWAYSINLINEATTR]] +// CHECK: call void @_Z1fbb({{.*}}) #[[ALWAYSINLINEATTR]] + [[clang::always_inline]] for (bar(); bar(); bar()) {} +// CHECK: call noundef zeroext i1 @_Z3barv() #[[ALWAYSINLINEATTR]] +// CHECK: call noundef zeroext i1 @_Z3barv() #[[ALWAYSINLINEATTR]] +// CHECK: call noundef zeroext i1 @_Z3barv() #[[ALWAYSINLINEATTR]] + bar(); +// CHECK: call noundef zeroext i1 @_Z3barv() + [[gnu::always_inline]] 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::always_inline]]g(s1 == s2); +// CHECK: call noundef zeroext i1 @_ZeqRK1SS1_({{.*}}) #[[ALWAYSINLINEATTR]] +// CHECK: call void @_Z1gb({{.*}}) #[[ALWAYSINLINEATTR]] + bool b; + [[clang::always_inline]] b = s1 == s2; +// CHECK: call noundef zeroext i1 @_ZeqRK1SS1_({{.*}}) #[[ALWAYSINLINEATTR]] +} + +// CHECK: attributes #[[ALWAYSINLINEATTR]] = { alwaysinline } Index: clang/test/Parser/objc-implementation-attrs.m =================================================================== --- clang/test/Parser/objc-implementation-attrs.m +++ clang/test/Parser/objc-implementation-attrs.m @@ -2,15 +2,15 @@ @interface I1 @end -// expected-warning@+1 {{'always_inline' attribute only applies to functions}} +// expected-warning@+1 {{'always_inline' attribute only applies to functions and statements}} __attribute__((always_inline)) @implementation I1 @end -// expected-warning@+1 {{'always_inline' attribute only applies to functions}} +// expected-warning@+1 {{'always_inline' attribute only applies to functions and statements}} __attribute__((always_inline)) @implementation I1 (MyCat) @end -// expected-warning@+1 {{'always_inline' attribute only applies to functions}} +// expected-warning@+1 {{'always_inline' attribute only applies to functions and statements}} __attribute__((always_inline)) // expected-warning@+1 {{cannot find interface declaration for 'I2'}} @implementation I2 @end Index: clang/test/Sema/attr-alwaysinline.cpp =================================================================== --- /dev/null +++ clang/test/Sema/attr-alwaysinline.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::always_inline]] bar(); + [[clang::always_inline(0)]] bar(); // expected-error {{'always_inline' attribute takes no arguments}} + int x; + [[clang::always_inline]] int i = bar(); // expected-warning {{'always_inline' attribute only applies to functions and statements}} + [[clang::always_inline]] x = 0; // expected-warning {{'always_inline' attribute is ignored because there exists no call expression inside the statement}} + [[clang::always_inline]] { asm("nop"); } // expected-warning {{'always_inline' attribute is ignored because there exists no call expression inside the statement}} + [[clang::always_inline]] label : x = 1; // expected-warning {{'always_inline' attribute only applies to functions and statements}} + + [[clang::always_inline]] always_inline_fn(); + [[clang::always_inline]] noinline_fn(); // expected-warning {{statement attribute 'always_inline' has higher precedence than function attribute 'noinline'}} + [[clang::always_inline]] flatten_fn(); // expected-warning {{statement attribute 'always_inline' has higher precedence than function attribute 'flatten'}} + + [[gnu::always_inline]] bar(); // expected-warning {{attribute is ignored on this statement as it only applies to functions; use '[[clang::always_inline]]' on statements}} + __attribute__((always_inline)) bar(); // expected-warning {{attribute is ignored on this statement as it only applies to functions; use '[[clang::always_inline]]' on statements}} +} + +[[clang::always_inline]] static int i = bar(); // expected-warning {{'always_inline' attribute only applies to functions and statements}}