[clang] 003c0b9 - [Clang] always_inline statement attribute

Dávid Bolvanský via cfe-commits cfe-commits at lists.llvm.org
Mon Mar 14 13:45:39 PDT 2022


Author: Dávid Bolvanský
Date: 2022-03-14T21:45:31+01:00
New Revision: 003c0b9307bc52605fc93c599dfe36849839ded5

URL: https://github.com/llvm/llvm-project/commit/003c0b9307bc52605fc93c599dfe36849839ded5
DIFF: https://github.com/llvm/llvm-project/commit/003c0b9307bc52605fc93c599dfe36849839ded5.diff

LOG: [Clang] always_inline statement  attribute

Motivation:

```
int test(int x, int y) {
    int r = 0;
    [[clang::always_inline]] r += foo(x, y); // force compiler to inline this function here
    return r;
}
```

In 2018, @kuhar proposed "Introduce per-callsite inline intrinsics" in https://reviews.llvm.org/D51200 to solve this motivation case (and many others).

This patch solves this problem with call site attribute. "noinline" statement attribute already landed in D119061. Also, some LLVM Inliner fixes landed so call site attribute is stronger than function attribute.

Reviewed By: aaron.ballman

Differential Revision: https://reviews.llvm.org/D120717

Added: 
    clang/test/CodeGen/attr-alwaysinline.cpp
    clang/test/Sema/attr-alwaysinline.cpp

Modified: 
    clang/include/clang/Basic/Attr.td
    clang/include/clang/Basic/AttrDocs.td
    clang/include/clang/Basic/DiagnosticSemaKinds.td
    clang/lib/CodeGen/CGCall.cpp
    clang/lib/CodeGen/CGStmt.cpp
    clang/lib/CodeGen/CodeGenFunction.h
    clang/lib/Sema/SemaStmtAttr.cpp
    clang/test/Parser/objc-implementation-attrs.m

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index cd2e2ca8bbf5a..e28044646080e 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -706,9 +706,13 @@ def AlignNatural : InheritableAttr {
   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];
 }
 

diff  --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 5f50b49ee2373..ee5be4c0bc412 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -6241,7 +6241,28 @@ def AlwaysInlineDocs : Documentation {
 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.
 
 <ins>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

diff  --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index db4adbbb6e1e6..e17b018937fb4 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -4052,7 +4052,7 @@ def warn_attribute_nonnull_parm_no_args : Warning<
   InGroup<IgnoredAttributes>;
 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<IgnoredAttributes>;
 def note_declared_nonnull : Note<
   "declared %select{'returns_nonnull'|'nonnull'}0 here">;

diff  --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index ab84d133de84e..8331d1ba215e9 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5248,6 +5248,11 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
   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.
 

diff  --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 01ccc71463069..7d4762bb0b782 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -667,23 +667,33 @@ void CodeGenFunction::EmitLabelStmt(const LabelStmt &S) {
 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<ReturnStmt>(Sub);
       musttail = cast<CallExpr>(R->getRetValue()->IgnoreParens());
+      break;
     }
   }
   SaveAndRestore<bool> save_nomerge(InNoMergeAttributedStmt, nomerge);
   SaveAndRestore<bool> save_noinline(InNoInlineAttributedStmt, noinline);
+  SaveAndRestore<bool> save_alwaysinline(InAlwaysInlineAttributedStmt,
+                                         alwaysinline);
   SaveAndRestore<const CallExpr *> save_musttail(MustTailCall, musttail);
   EmitStmt(S.getSubStmt(), S.getAttrs());
 }

diff  --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 2636a4db88fdc..598fb7f4a9f38 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -554,6 +554,9 @@ class CodeGenFunction : public CodeGenTypeCache {
   /// 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;

diff  --git a/clang/lib/Sema/SemaStmtAttr.cpp b/clang/lib/Sema/SemaStmtAttr.cpp
index e9a938a6646b0..ebb10da9ab6ea 100644
--- a/clang/lib/Sema/SemaStmtAttr.cpp
+++ b/clang/lib/Sema/SemaStmtAttr.cpp
@@ -241,6 +241,32 @@ static Attr *handleNoInlineAttr(Sema &S, Stmt *St, const ParsedAttr &A,
   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<NoInlineAttr>() || Decl->hasAttr<FlattenAttr>())
+      S.Diag(St->getBeginLoc(), diag::warn_function_stmt_attribute_precedence)
+          << A << (Decl->hasAttr<NoInlineAttr>() ? 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 @@ static Attr *ProcessStmtAttribute(Sema &S, Stmt *St, const ParsedAttr &A,
     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:

diff  --git a/clang/test/CodeGen/attr-alwaysinline.cpp b/clang/test/CodeGen/attr-alwaysinline.cpp
new file mode 100644
index 0000000000000..2873a3f5de969
--- /dev/null
+++ b/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 }

diff  --git a/clang/test/Parser/objc-implementation-attrs.m b/clang/test/Parser/objc-implementation-attrs.m
index 9fc83ef348d6a..4dc34aa79cbdf 100644
--- a/clang/test/Parser/objc-implementation-attrs.m
+++ b/clang/test/Parser/objc-implementation-attrs.m
@@ -2,15 +2,15 @@
 
 @interface I1 @end
 
-// expected-warning at +1 {{'always_inline' attribute only applies to functions}}
+// expected-warning at +1 {{'always_inline' attribute only applies to functions and statements}}
 __attribute__((always_inline))
 @implementation I1 @end
 
-// expected-warning at +1 {{'always_inline' attribute only applies to functions}}
+// expected-warning at +1 {{'always_inline' attribute only applies to functions and statements}}
 __attribute__((always_inline))
 @implementation I1 (MyCat) @end
 
-// expected-warning at +1 {{'always_inline' attribute only applies to functions}}
+// expected-warning at +1 {{'always_inline' attribute only applies to functions and statements}}
 __attribute__((always_inline))
 // expected-warning at +1 {{cannot find interface declaration for 'I2'}}
 @implementation I2 @end

diff  --git a/clang/test/Sema/attr-alwaysinline.cpp b/clang/test/Sema/attr-alwaysinline.cpp
new file mode 100644
index 0000000000000..6b8e8f215a4b6
--- /dev/null
+++ b/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}}


        


More information about the cfe-commits mailing list