Add support for function attribute 'not_tail_called'.

This attribute is used to prevent tail-call optimizations to the marked
function. For example, in the following piece of code, foo1 will not be
tail-call optimized: 

int __attribute__((not_tail_called)) foo1(int);

int foo2(int a) {
  return foo1(a); // Tail-call optimization is not performed.
}

The attribute has effect only on statically bound calls. It has no
effect on indirect calls. Also, virtual functions and objective-c
methods cannot be marked as 'not_tail_called'.

rdar://problem/22667622

Differential Revision: http://reviews.llvm.org/D12922


git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@252369 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Akira Hatanaka 2015-11-06 23:56:15 +00:00
parent 67fafbb08d
commit 08bdfb571a
10 changed files with 164 additions and 0 deletions

View File

@ -1029,6 +1029,12 @@ def NoInstrumentFunction : InheritableAttr {
let Documentation = [Undocumented];
}
def NotTailCalled : InheritableAttr {
let Spellings = [GNU<"not_tail_called">, CXX11<"clang", "not_tail_called">];
let Subjects = SubjectList<[Function]>;
let Documentation = [NotTailCalledDocs];
}
def NoThrow : InheritableAttr {
let Spellings = [GCC<"nothrow">, Declspec<"nothrow">];
let Documentation = [Undocumented];

View File

@ -1620,3 +1620,52 @@ function are loads and stores from objects pointed to by its pointer-typed
arguments, with arbitrary offsets.
}];
}
def NotTailCalledDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
The ``not_tail_called`` attribute prevents tail-call optimization on statically bound calls. It has no effect on indirect calls. Virtual functions, objective-c methods, and functions marked as ``always_inline`` cannot be marked as ``not_tail_called``.
For example, it prevents tail-call optimization in the following case:
.. code-block: c
int __attribute__((not_tail_called)) foo1(int);
int foo2(int a) {
return foo1(a); // No tail-call optimization on direct calls.
}
However, it doesn't prevent tail-call optimization in this case:
.. code-block: c
int __attribute__((not_tail_called)) foo1(int);
int foo2(int a) {
int (*fn)(int) = &foo1;
// not_tail_called has no effect on an indirect call even if the call can be
// resolved at compile time.
return (*fn)(a);
}
Marking virtual functions as ``not_tail_called`` is an error:
.. code-block: c++
class Base {
public:
// not_tail_called on a virtual function is an error.
[[clang::not_tail_called]] virtual int foo1();
virtual int foo2();
// Non-virtual functions can be marked ``not_tail_called``.
[[clang::not_tail_called]] int foo3();
};
class Derived1 : public Base {
public:
int foo1() override;
// not_tail_called on a virtual function is an error.
[[clang::not_tail_called]] int foo2() override;
};
}];
}

View File

@ -2409,6 +2409,8 @@ def warn_attribute_not_on_decl : Warning<
"%0 attribute ignored when parsing type">, InGroup<IgnoredAttributes>;
def err_base_specifier_attribute : Error<
"%0 attribute cannot be applied to a base specifier">;
def err_invalid_attribute_on_virtual_function : Error<
"%0 attribute cannot be applied to virtual functions">;
// Availability attribute
def warn_availability_unknown_platform : Warning<

View File

@ -3493,6 +3493,10 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
// lexical order, so deactivate it and run it manually here.
CallArgs.freeArgumentMemory(*this);
if (llvm::CallInst *Call = dyn_cast<llvm::CallInst>(CI))
if (TargetDecl && TargetDecl->hasAttr<NotTailCalledAttr>())
Call->setTailCallKind(llvm::CallInst::TCK_NoTail);
RValue Ret = [&] {
switch (RetAI.getKind()) {
case ABIArgInfo::InAlloca:

View File

@ -5373,6 +5373,16 @@ static void checkAttributesAfterMerging(Sema &S, NamedDecl &ND) {
ND.setInvalidDecl();
}
}
// Virtual functions cannot be marked as 'notail'.
if (auto *Attr = ND.getAttr<NotTailCalledAttr>())
if (auto *MD = dyn_cast<CXXMethodDecl>(&ND))
if (MD->isVirtual()) {
S.Diag(ND.getLocation(),
diag::err_invalid_attribute_on_virtual_function)
<< Attr;
ND.dropAttr<NotTailCalledAttr>();
}
}
static void checkDLLAttributeRedeclaration(Sema &S, NamedDecl *OldDecl,

View File

@ -1701,6 +1701,15 @@ static void handleDependencyAttr(Sema &S, Scope *Scope, Decl *D,
Attr.getAttributeSpellingListIndex()));
}
static void handleNotTailCalledAttr(Sema &S, Decl *D,
const AttributeList &Attr) {
if (checkAttrMutualExclusion<AlwaysInlineAttr>(S, D, Attr))
return;
D->addAttr(::new (S.Context) NotTailCalledAttr(
Attr.getRange(), S.Context, Attr.getAttributeSpellingListIndex()));
}
static void handleUsedAttr(Sema &S, Decl *D, const AttributeList &Attr) {
if (const VarDecl *VD = dyn_cast<VarDecl>(D)) {
if (VD->hasLocalStorage()) {
@ -3419,6 +3428,9 @@ OptimizeNoneAttr *Sema::mergeOptimizeNoneAttr(Decl *D, SourceRange Range,
static void handleAlwaysInlineAttr(Sema &S, Decl *D,
const AttributeList &Attr) {
if (checkAttrMutualExclusion<NotTailCalledAttr>(S, D, Attr))
return;
if (AlwaysInlineAttr *Inline = S.mergeAlwaysInlineAttr(
D, Attr.getRange(), Attr.getName(),
Attr.getAttributeSpellingListIndex()))
@ -4991,6 +5003,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
case AttributeList::AT_ReturnsTwice:
handleSimpleAttribute<ReturnsTwiceAttr>(S, D, Attr);
break;
case AttributeList::AT_NotTailCalled:
handleNotTailCalledAttr(S, D, Attr);
break;
case AttributeList::AT_Used:
handleUsedAttr(S, D, Attr);
break;

View File

@ -0,0 +1,33 @@
// RUN: %clang_cc1 -triple x86_64-apple-macosx10.7.0 %s -emit-llvm -o - | FileCheck %s
// CHECK: %{{[a-z0-9]+}} = notail call i32 @callee0(i32 %
// CHECK: %{{[a-z0-9]+}} = notail call i32 @callee1(i32 %
// Check that indirect calls do not have the notail marker.
// CHECK: store i32 (i32)* @callee1, i32 (i32)** [[ALLOCA1:%[A-Za-z0-9]+]], align 8
// CHECK: [[INDIRFUNC:%[0-9]+]] = load i32 (i32)*, i32 (i32)** [[ALLOCA1]], align 8
// CHECK: %{{[a-z0-9]+}} = call i32 [[INDIRFUNC]](i32 %6)
// CHECK: %{{[a-z0-9]+}} = call i32 @callee2(i32 %
int callee0(int a) __attribute__((not_tail_called)) {
return a + 1;
}
int callee1(int) __attribute__((not_tail_called));
int callee2(int);
typedef int (*FuncTy)(int);
int foo0(int a) {
if (a > 1)
return callee0(a);
if (a == 1)
return callee1(a);
if (a < 0) {
FuncTy F = callee1;
return (*F)(a);
}
return callee2(a);
}

View File

@ -0,0 +1,17 @@
// RUN: %clang_cc1 -triple=x86_64-apple-darwin -std=c++11 -emit-llvm -o - %s | FileCheck %s
class Class1 {
public:
[[clang::not_tail_called]] int m1();
int m2();
};
int foo1(int a, Class1 *c1) {
if (a)
return c1->m1();
return c1->m2();
}
// CHECK-LABEL: define i32 @_Z4foo1iP6Class1(
// CHECK: %{{[a-z0-9]+}} = notail call i32 @_ZN6Class12m1Ev(%class.Class1*
// CHECK: %{{[a-z0-9]+}} = call i32 @_ZN6Class12m2Ev(%class.Class1*

12
test/Sema/attr-notail.c Normal file
View File

@ -0,0 +1,12 @@
// RUN: %clang_cc1 -fsyntax-only -verify %s
int callee0() __attribute__((not_tail_called,always_inline)); // expected-error{{'not_tail_called' and 'always_inline' attributes are not compatible}}
int callee1() __attribute__((always_inline,not_tail_called)); // expected-error{{'always_inline' and 'not_tail_called' attributes are not compatible}}
int foo(int a) {
return a ? callee0() : callee1();
}
int g0 __attribute__((not_tail_called)); // expected-warning {{'not_tail_called' attribute only applies to functions}}
int foo2(int a) __attribute__((not_tail_called("abc"))); // expected-error {{'not_tail_called' attribute takes no arguments}}

View File

@ -0,0 +1,16 @@
// RUN: %clang_cc1 -std=c++11 -fsyntax-only -verify %s
class Base {
public:
[[clang::not_tail_called]] virtual int foo1(); // expected-error {{'not_tail_called' attribute cannot be applied to virtual functions}}
virtual int foo2();
[[clang::not_tail_called]] int foo3();
virtual ~Base() {}
};
class Derived1 : public Base {
public:
int foo1() override;
[[clang::not_tail_called]] int foo2() override; // expected-error {{'not_tail_called' attribute cannot be applied to virtual functions}}
[[clang::not_tail_called]] int foo4();
};