mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-17 03:56:42 +00:00

The lifetimes of local variables and function parameters must end before the call to a [[clang::musttail]] function, instead of before the return, because we will not have a stack frame to hold them when doing the call. This documents this limitation, and adds diagnostics to warn about some code which is invalid because of it.
301 lines
15 KiB
C++
301 lines
15 KiB
C++
// RUN: %clang_cc1 -verify -fsyntax-only -fms-extensions -fcxx-exceptions -fopenmp -triple x86_64-linux %s
|
|
|
|
int ReturnsInt1();
|
|
int Func1() {
|
|
[[clang::musttail]] ReturnsInt1(); // expected-error {{'musttail' attribute only applies to return statements}}
|
|
[[clang::musttail(1, 2)]] return ReturnsInt1(); // expected-error {{'musttail' attribute takes no arguments}}
|
|
[[clang::musttail]] return 5; // expected-error {{'musttail' attribute requires that the return value is the result of a function call}}
|
|
[[clang::musttail]] return ReturnsInt1();
|
|
}
|
|
|
|
void NoFunctionCall() {
|
|
[[clang::musttail]] return; // expected-error {{'musttail' attribute requires that the return value is the result of a function call}}
|
|
}
|
|
|
|
[[clang::musttail]] static int int_val = ReturnsInt1(); // expected-error {{'musttail' attribute cannot be applied to a declaration}}
|
|
|
|
void NoParams(); // expected-note {{target function has different number of parameters (expected 1 but has 0)}}
|
|
void TestParamArityMismatch(int x) {
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return NoParams(); // expected-error {{cannot perform a tail call to function 'NoParams' because its signature is incompatible with the calling function}}
|
|
}
|
|
|
|
void LongParam(long x); // expected-note {{target function has type mismatch at 1st parameter (expected 'long' but has 'int')}}
|
|
void TestParamTypeMismatch(int x) {
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return LongParam(x); // expected-error {{cannot perform a tail call to function 'LongParam' because its signature is incompatible with the calling function}}
|
|
}
|
|
|
|
long ReturnsLong(); // expected-note {{target function has different return type ('int' expected but has 'long')}}
|
|
int TestReturnTypeMismatch() {
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return ReturnsLong(); // expected-error {{cannot perform a tail call to function 'ReturnsLong' because its signature is incompatible with the calling function}}
|
|
}
|
|
|
|
struct Struct1 {
|
|
void MemberFunction(); // expected-note {{'MemberFunction' declared here}}
|
|
};
|
|
void TestNonMemberToMember() {
|
|
Struct1 st;
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return st.MemberFunction(); // expected-error {{non-member function cannot perform a tail call to non-static member function 'MemberFunction'}}
|
|
}
|
|
|
|
void ReturnsVoid(); // expected-note {{'ReturnsVoid' declared here}}
|
|
struct Struct2 {
|
|
void TestMemberToNonMember() {
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return ReturnsVoid(); // expected-error{{non-static member function cannot perform a tail call to non-member function 'ReturnsVoid'}}
|
|
}
|
|
};
|
|
|
|
class HasNonTrivialDestructor {
|
|
public:
|
|
~HasNonTrivialDestructor() {}
|
|
int ReturnsInt();
|
|
};
|
|
|
|
void ReturnsVoid2();
|
|
void TestNonTrivialDestructorInScope() {
|
|
HasNonTrivialDestructor foo; // expected-note {{jump exits scope of variable with non-trivial destructor}}
|
|
[[clang::musttail]] return ReturnsVoid(); // expected-error {{cannot perform a tail call from this return statement}}
|
|
}
|
|
|
|
int NonTrivialParam(HasNonTrivialDestructor x);
|
|
int TestNonTrivialParam(HasNonTrivialDestructor x) {
|
|
[[clang::musttail]] return NonTrivialParam(x); // expected-error {{tail call requires that the return value, all parameters, and any temporaries created by the expression are trivially destructible}}
|
|
}
|
|
|
|
HasNonTrivialDestructor ReturnsNonTrivialValue();
|
|
HasNonTrivialDestructor TestReturnsNonTrivialValue() {
|
|
// FIXME: the diagnostic cannot currently distinguish between needing to run a
|
|
// destructor for the return value and needing to run a destructor for some
|
|
// other temporary created in the return statement.
|
|
[[clang::musttail]] return (ReturnsNonTrivialValue()); // expected-error {{tail call requires that the return value, all parameters, and any temporaries created by the expression are trivially destructible}}
|
|
}
|
|
|
|
HasNonTrivialDestructor TestReturnsNonTrivialNonFunctionCall() {
|
|
[[clang::musttail]] return HasNonTrivialDestructor(); // expected-error {{'musttail' attribute requires that the return value is the result of a function call}}
|
|
}
|
|
|
|
struct UsesPointerToMember {
|
|
void (UsesPointerToMember::*p_mem)(); // expected-note {{'p_mem' declared here}}
|
|
};
|
|
void TestUsesPointerToMember(UsesPointerToMember *foo) {
|
|
// "this" pointer cannot double as first parameter.
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return (foo->*(foo->p_mem))(); // expected-error {{non-member function cannot perform a tail call to pointer-to-member function 'p_mem'}}
|
|
}
|
|
|
|
void ReturnsVoid2();
|
|
void TestNestedClass() {
|
|
HasNonTrivialDestructor foo;
|
|
class Nested {
|
|
__attribute__((noinline)) static void NestedMethod() {
|
|
// Outer non-trivial destructor does not affect nested class.
|
|
[[clang::musttail]] return ReturnsVoid2();
|
|
}
|
|
};
|
|
}
|
|
|
|
template <class T>
|
|
T TemplateFunc(T x) { // expected-note{{target function has different return type ('long' expected but has 'int')}}
|
|
return x ? 5 : 10;
|
|
}
|
|
int OkTemplateFunc(int x) {
|
|
[[clang::musttail]] return TemplateFunc<int>(x);
|
|
}
|
|
template <class T>
|
|
T BadTemplateFunc(T x) {
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return TemplateFunc<int>(x); // expected-error {{cannot perform a tail call to function 'TemplateFunc' because its signature is incompatible with the calling function}}
|
|
}
|
|
long TestBadTemplateFunc(long x) {
|
|
return BadTemplateFunc<long>(x); // expected-note {{in instantiation of}}
|
|
}
|
|
|
|
void IntParam(int x);
|
|
void TestVLA(int x) {
|
|
HasNonTrivialDestructor vla[x]; // expected-note {{jump exits scope of variable with non-trivial destructor}}
|
|
[[clang::musttail]] return IntParam(x); // expected-error {{cannot perform a tail call from this return statement}}
|
|
}
|
|
|
|
void TestNonTrivialDestructorSubArg(int x) {
|
|
[[clang::musttail]] return IntParam(NonTrivialParam(HasNonTrivialDestructor())); // expected-error {{tail call requires that the return value, all parameters, and any temporaries created by the expression are trivially destructible}}
|
|
}
|
|
|
|
void VariadicFunction(int x, ...);
|
|
void TestVariadicFunction(int x, ...) {
|
|
[[clang::musttail]] return VariadicFunction(x); // expected-error {{'musttail' attribute may not be used with variadic functions}}
|
|
}
|
|
|
|
int TakesIntParam(int x); // expected-note {{target function has type mismatch at 1st parameter (expected 'int' but has 'short')}}
|
|
int TakesShortParam(short x); // expected-note {{target function has type mismatch at 1st parameter (expected 'short' but has 'int')}}
|
|
int TestIntParamMismatch(int x) {
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return TakesShortParam(x); // expected-error {{cannot perform a tail call to function 'TakesShortParam' because its signature is incompatible with the calling function}}
|
|
}
|
|
int TestIntParamMismatch2(short x) {
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return TakesIntParam(x); // expected-error {{cannot perform a tail call to function 'TakesIntParam' because its signature is incompatible with the calling function}}
|
|
}
|
|
|
|
struct TestClassMismatch1 {
|
|
void ToFunction(); // expected-note{{target function is a member of different class (expected 'TestClassMismatch2' but has 'TestClassMismatch1')}}
|
|
};
|
|
TestClassMismatch1 *tcm1;
|
|
struct TestClassMismatch2 {
|
|
void FromFunction() {
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return tcm1->ToFunction(); // expected-error {{cannot perform a tail call to function 'ToFunction' because its signature is incompatible with the calling function}}
|
|
}
|
|
};
|
|
|
|
__regcall int RegCallReturnsInt(); // expected-note {{target function has calling convention regcall (expected cdecl)}}
|
|
int TestMismatchCallingConvention() {
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return RegCallReturnsInt(); // expected-error {{cannot perform a tail call to function 'RegCallReturnsInt' because it uses an incompatible calling convention}}
|
|
}
|
|
|
|
int TestNonCapturingLambda() {
|
|
auto lambda = []() { return 12; }; // expected-note {{'operator()' declared here}}
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return lambda(); // expected-error {{non-member function cannot perform a tail call to non-static member function 'operator()'}}
|
|
|
|
// This works.
|
|
auto lambda_fptr = static_cast<int (*)()>(lambda);
|
|
[[clang::musttail]] return lambda_fptr();
|
|
[[clang::musttail]] return (+lambda)();
|
|
}
|
|
|
|
int TestCapturingLambda() {
|
|
int x;
|
|
auto lambda = [x]() { return 12; }; // expected-note {{'operator()' declared here}}
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return lambda(); // expected-error {{non-member function cannot perform a tail call to non-static member function 'operator()'}}
|
|
}
|
|
|
|
int TestNonTrivialTemporary(int) {
|
|
[[clang::musttail]] return TakesIntParam(HasNonTrivialDestructor().ReturnsInt()); // expected-error {{tail call requires that the return value, all parameters, and any temporaries created by the expression are trivially destructible}}
|
|
}
|
|
|
|
void ReturnsVoid();
|
|
struct TestDestructor {
|
|
~TestDestructor() {
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return ReturnsVoid(); // expected-error {{destructor '~TestDestructor' must not return void expression}} // expected-error {{cannot perform a tail call from a destructor}}
|
|
}
|
|
};
|
|
|
|
struct ClassWithDestructor { // expected-note {{target destructor is declared here}}
|
|
void TestExplicitDestructorCall() {
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return this->~ClassWithDestructor(); // expected-error {{cannot perform a tail call to a destructor}}
|
|
}
|
|
};
|
|
|
|
struct HasNonTrivialCopyConstructor {
|
|
HasNonTrivialCopyConstructor(const HasNonTrivialCopyConstructor &);
|
|
};
|
|
HasNonTrivialCopyConstructor ReturnsClassByValue();
|
|
HasNonTrivialCopyConstructor TestNonElidableCopyConstructor() {
|
|
// This is an elidable constructor, but when it is written explicitly
|
|
// we decline to elide it.
|
|
[[clang::musttail]] return HasNonTrivialCopyConstructor(ReturnsClassByValue()); // expected-error{{'musttail' attribute requires that the return value is the result of a function call}}
|
|
}
|
|
|
|
struct ClassWithConstructor {
|
|
ClassWithConstructor() = default; // expected-note {{target constructor is declared here}}
|
|
};
|
|
void TestExplicitConstructorCall(ClassWithConstructor a) {
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return a.ClassWithConstructor::ClassWithConstructor(); // expected-error{{cannot perform a tail call to a constructor}} expected-warning{{explicit constructor calls are a Microsoft extension}}
|
|
}
|
|
|
|
void TestStatementExpression() {
|
|
({
|
|
HasNonTrivialDestructor foo; // expected-note {{jump exits scope of variable with non-trivial destructor}}
|
|
[[clang::musttail]] return ReturnsVoid2(); // expected-error {{cannot perform a tail call from this return statement}}
|
|
});
|
|
}
|
|
|
|
struct MyException {};
|
|
void TestTryBlock() {
|
|
try { // expected-note {{jump exits try block}}
|
|
[[clang::musttail]] return ReturnsVoid2(); // expected-error {{cannot perform a tail call from this return statement}}
|
|
} catch (MyException &e) {
|
|
}
|
|
}
|
|
|
|
using IntFunctionType = int();
|
|
IntFunctionType *ReturnsIntFunction();
|
|
long TestRValueFunctionPointer() {
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return ReturnsIntFunction()(); // expected-error{{cannot perform a tail call to function because its signature is incompatible with the calling function}} // expected-note{{target function has different return type ('long' expected but has 'int')}}
|
|
}
|
|
|
|
void TestPseudoDestructor() {
|
|
int n;
|
|
using T = int;
|
|
[[clang::musttail]] // expected-note {{tail call required by 'musttail' attribute here}}
|
|
return n.~T(); // expected-error{{cannot perform a tail call to a destructor}}
|
|
}
|
|
|
|
struct StructPMF {
|
|
typedef void (StructPMF::*PMF)();
|
|
static void TestReturnsPMF();
|
|
};
|
|
|
|
StructPMF *St;
|
|
StructPMF::PMF ReturnsPMF();
|
|
void StructPMF::TestReturnsPMF() {
|
|
[[clang::musttail]] // expected-note{{tail call required by 'musttail' attribute here}}
|
|
return (St->*ReturnsPMF())(); // expected-error{{static member function cannot perform a tail call to pointer-to-member function}}
|
|
}
|
|
|
|
// These tests are merely verifying that we don't crash with incomplete or
|
|
// erroneous ASTs. These cases crashed the compiler in early iterations.
|
|
|
|
struct TestBadPMF {
|
|
int (TestBadPMF::*pmf)();
|
|
void BadPMF() {
|
|
[[clang::musttail]] return ((*this)->*pmf)(); // expected-error {{left hand operand to ->* must be a pointer to class compatible with the right hand operand, but is 'TestBadPMF'}}
|
|
}
|
|
};
|
|
|
|
namespace ns {}
|
|
void TestCallNonValue() {
|
|
[[clang::musttail]] return ns; // expected-error {{unexpected namespace name 'ns': expected expression}}
|
|
}
|
|
|
|
// Test diagnostics for lifetimes of local variables, which end earlier for a
|
|
// musttail call than for a nowmal one.
|
|
|
|
void TakesIntAndPtr(int, int *);
|
|
void PassAddressOfLocal(int a, int *b) {
|
|
int c;
|
|
[[clang::musttail]] return TakesIntAndPtr(0, &c); // expected-warning {{address of stack memory associated with local variable 'c' passed to musttail function}}
|
|
}
|
|
void PassAddressOfParam(int a, int *b) {
|
|
[[clang::musttail]] return TakesIntAndPtr(0, &a); // expected-warning {{address of stack memory associated with parameter 'a' passed to musttail function}}
|
|
}
|
|
void PassValues(int a, int *b) {
|
|
[[clang::musttail]] return TakesIntAndPtr(a, b);
|
|
}
|
|
|
|
void TakesIntAndRef(int, const int &);
|
|
void PassRefOfLocal(int a, const int &b) {
|
|
int c;
|
|
[[clang::musttail]] return TakesIntAndRef(0, c); // expected-warning {{address of stack memory associated with local variable 'c' passed to musttail function}}
|
|
}
|
|
void PassRefOfParam(int a, const int &b) {
|
|
[[clang::musttail]] return TakesIntAndRef(0, a); // expected-warning {{address of stack memory associated with parameter 'a' passed to musttail function}}
|
|
}
|
|
int ReturnInt();
|
|
void PassRefOfTemporary(int a, const int &b) {
|
|
[[clang::musttail]] return TakesIntAndRef(0, ReturnInt()); // expected-warning {{passing address of local temporary object to musttail function}}
|
|
}
|
|
void PassValuesRef(int a, const int &b) {
|
|
[[clang::musttail]] return TakesIntAndRef(a, b);
|
|
}
|