Skip to content

Commit

Permalink
[Clang] noinline call site attribute
Browse files Browse the repository at this point in the history
Motivation:

```
int foo(int x, int y) { // any compiler will happily inline this function
    return x / y;
}

int test(int x, int y) {
    int r = 0;
    [[clang::noinline]] r += foo(x, y); // for some reason we don't want any inlining 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. The implementation is "smaller" wrt approach which uses new intrinsics and thanks to https://reviews.llvm.org/D79121 (Add nomerge statement attribute to clang), we have got some basic infrastructure to deal with attrs on statements with call expressions.

GCC devs are more inclined to call attribute solution as well, as builtins are problematic for them - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=104187. But they have no patch proposal yet so..  We have free hands here.

If this approach makes sense, next future steps would be support for call site attributes for always_inline / flatten.

Reviewed By: aaron.ballman, kuhar

Differential Revision: https://reviews.llvm.org/D119061
  • Loading branch information
davidbolvansky committed Feb 28, 2022
1 parent 96dea20 commit 223b824
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 13 deletions.
12 changes: 8 additions & 4 deletions clang/include/clang/Basic/Attr.td
Expand Up @@ -1760,10 +1760,14 @@ def Convergent : InheritableAttr {
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;
}

Expand Down
23 changes: 23 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Expand Up @@ -527,6 +527,29 @@ calls.
}];
}

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 = [{
Expand Down
11 changes: 10 additions & 1 deletion clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -2891,11 +2891,16 @@ def warn_auto_var_is_id : Warning<
InGroup<DiagGroup<"auto-var-id">>;

// 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<IgnoredAttributes>;

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<IgnoredAttributes>;

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">;
Expand Down Expand Up @@ -4033,6 +4038,10 @@ def warn_attribute_nonnull_no_pointers : Warning<
def warn_attribute_nonnull_parm_no_args : Warning<
"'nonnull' attribute when used on parameters takes no arguments">,
InGroup<IgnoredAttributes>;
def warn_function_stmt_attribute_precedence : Warning<
"statement attribute %0 has higher precedence than function attribute "
"'%select{always_inline|flatten}1'">,
InGroup<IgnoredAttributes>;
def note_declared_nonnull : Note<
"declared %select{'returns_nonnull'|'nonnull'}0 here">;
def warn_attribute_sentinel_named_arguments : Warning<
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/CodeGen/CGCall.cpp
Expand Up @@ -5244,12 +5244,17 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
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<FlattenAttr>() &&
!InNoInlineAttributedStmt &&
!(TargetDecl && TargetDecl->hasAttr<NoInlineAttr>())) {
Attrs =
Attrs.addFnAttribute(getLLVMContext(), llvm::Attribute::AlwaysInline);
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/CodeGen/CGStmt.cpp
Expand Up @@ -666,19 +666,24 @@ void CodeGenFunction::EmitLabelStmt(const LabelStmt &S) {

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<ReturnStmt>(Sub);
musttail = cast<CallExpr>(R->getRetValue()->IgnoreParens());
}
}
SaveAndRestore<bool> save_nomerge(InNoMergeAttributedStmt, nomerge);
SaveAndRestore<bool> save_noinline(InNoInlineAttributedStmt, noinline);
SaveAndRestore<const CallExpr *> save_musttail(MustTailCall, musttail);
EmitStmt(S.getSubStmt(), S.getAttrs());
}
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/CodeGen/CodeGenFunction.h
Expand Up @@ -551,6 +551,9 @@ class CodeGenFunction : public CodeGenTypeCache {
/// 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;
Expand Down
46 changes: 40 additions & 6 deletions clang/lib/Sema/SemaStmtAttr.cpp
Expand Up @@ -175,17 +175,22 @@ static Attr *handleLoopHintAttr(Sema &S, Stmt *St, const ParsedAttr &A,

namespace {
class CallExprFinder : public ConstEvaluatedExprVisitor<CallExprFinder> {
bool FoundCallExpr = false;
bool FoundAsmStmt = false;
std::vector<const CallExpr *> CallExprs;

public:
typedef ConstEvaluatedExprVisitor<CallExprFinder> Inherited;

CallExprFinder(Sema &S, const Stmt *St) : Inherited(S.Context) { Visit(St); }

bool foundCallExpr() { return FoundCallExpr; }
bool foundCallExpr() { return !CallExprs.empty(); }
const std::vector<const CallExpr *> &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)
Expand All @@ -200,15 +205,42 @@ static Attr *handleNoMergeAttr(Sema &S, Stmt *St, const ParsedAttr &A,
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;
}

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<AlwaysInlineAttr>() || Decl->hasAttr<FlattenAttr>())
S.Diag(St->getBeginLoc(), diag::warn_function_stmt_attribute_precedence)
<< A << (Decl->hasAttr<AlwaysInlineAttr>() ? 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().
Expand Down Expand Up @@ -418,6 +450,8 @@ static Attr *ProcessStmtAttribute(Sema &S, Stmt *St, const ParsedAttr &A,
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:
Expand Down
55 changes: 55 additions & 0 deletions 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 }
2 changes: 1 addition & 1 deletion clang/test/Parser/stmt-attributes.c
Expand Up @@ -45,7 +45,7 @@ void foo(int i) {
}

__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}}

Expand Down
2 changes: 1 addition & 1 deletion 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));

Expand Down
27 changes: 27 additions & 0 deletions 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}}

0 comments on commit 223b824

Please sign in to comment.