diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1352,6 +1352,12 @@ let SimpleHandler = 1; } +def MustTail : StmtAttr { + let Spellings = [Clang<"musttail">]; + let Documentation = [MustTailDocs]; + let Subjects = SubjectList<[ReturnStmt], ErrorDiag, "return statements">; +} + def FastCall : DeclOrTypeAttr { let Spellings = [GCC<"fastcall">, Keyword<"__fastcall">, Keyword<"_fastcall">]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -443,6 +443,26 @@ }]; } +def MustTailDocs : Documentation { + let Category = DocCatStmt; + let Content = [{ +If a ``return`` statement is marked ``musttail``, this indicates that the +compiler must generate a tail call for the program to be correct, even when +optimizations are disabled. This guarantees that the call will not cause +unbounded stack growth if it is part of a recursive cycle in the call graph. + +``clang::musttail`` can only be applied to a ``return`` statement whose value +is the result of a function call (even functions returning void must use +``return``, although no value is returned). The target function must have the +same number of arguments as the caller. The types of the return value and all +arguments must be similar, including the implicit "this" argument, if any. +Any variables in scope, including all arguments to the function and the +return value must be trivially destructible. The calling convention of the +caller and callee must match, and they must not have old style K&R C function +declarations. + }]; +} + def AssertCapabilityDocs : Documentation { let Category = DocCatFunction; let Heading = "assert_capability, assert_shared_capability"; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2823,6 +2823,39 @@ "%0 attribute is ignored because there exists no call expression inside the " "statement">, InGroup; +def err_musttail_needs_trivial_args : Error< + "%0 attribute requires that the return type and all arguments are trivially " + "destructible">; +def err_musttail_needs_call : Error< + "%0 attribute requires that the return value is the result of a function call" + >; +def err_musttail_needs_prototype : Error< + "%0 attribute requires that both caller and callee functions have a " + "prototype">; +def note_musttail_fix_non_prototype : Note< + "add 'void' to the parameter list to turn a old-style K&R function " + "declaration into a prototype">; +def err_musttail_only_from_function : Error< + "%0 attribute can only be used from a regular function.">; +def err_musttail_mismatch : Error< + "%0 attribute requires that caller and callee have compatible function " + "signatures">; +def note_musttail_mismatch : Note< + "target function " + "%select{has different class%diff{ (expected $ but has $)|}1,2" + "|has different number of parameters (expected %1 but has %2)" + "|has type mismatch at %ordinal3 parameter" + "%diff{ (expected $ but has $)|}1,2" + "|has different return type%diff{ ($ expected but has $)|}1,2" + "|has different calling convention (expected %1 but found %2)}0">; +def err_musttail_callconv_mismatch : Error< + "%0 attribute requires that caller and callee use the same calling convention" + >; +def err_musttail_no_destruction : Error< + "%0 attribute requires that all variables in scope are trivially destructible" + >; +def err_musttail_no_variadic : Error< + "%0 attribute may not be used with variadic functions">; def err_nsobject_attribute : Error< "'NSObject' attribute is for pointer types only">; def err_attributes_are_not_compatible : Error< diff --git a/clang/include/clang/Sema/ScopeInfo.h b/clang/include/clang/Sema/ScopeInfo.h --- a/clang/include/clang/Sema/ScopeInfo.h +++ b/clang/include/clang/Sema/ScopeInfo.h @@ -118,6 +118,10 @@ /// Whether this function contains any indirect gotos. bool HasIndirectGoto : 1; + /// Whether this function contains any statement marked with + /// \c [[clang::musttail]]. + bool HasMustTail : 1; + /// Whether a statement was dropped because it was invalid. bool HasDroppedStmt : 1; @@ -370,14 +374,13 @@ public: FunctionScopeInfo(DiagnosticsEngine &Diag) : Kind(SK_Function), HasBranchProtectedScope(false), - HasBranchIntoScope(false), HasIndirectGoto(false), + HasBranchIntoScope(false), HasIndirectGoto(false), HasMustTail(false), HasDroppedStmt(false), HasOMPDeclareReductionCombiner(false), HasFallthroughStmt(false), UsesFPIntrin(false), - HasPotentialAvailabilityViolations(false), - ObjCShouldCallSuper(false), ObjCIsDesignatedInit(false), - ObjCWarnForNoDesignatedInitChain(false), ObjCIsSecondaryInit(false), - ObjCWarnForNoInitDelegation(false), NeedsCoroutineSuspends(true), - ErrorTrap(Diag) {} + HasPotentialAvailabilityViolations(false), ObjCShouldCallSuper(false), + ObjCIsDesignatedInit(false), ObjCWarnForNoDesignatedInitChain(false), + ObjCIsSecondaryInit(false), ObjCWarnForNoInitDelegation(false), + NeedsCoroutineSuspends(true), ErrorTrap(Diag) {} virtual ~FunctionScopeInfo(); @@ -423,6 +426,8 @@ HasIndirectGoto = true; } + void setHasMustTail() { HasMustTail = true; } + void setHasDroppedStmt() { HasDroppedStmt = true; } @@ -450,9 +455,8 @@ } bool NeedsScopeChecking() const { - return !HasDroppedStmt && - (HasIndirectGoto || - (HasBranchProtectedScope && HasBranchIntoScope)); + return !HasDroppedStmt && (HasIndirectGoto || HasMustTail || + (HasBranchProtectedScope && HasBranchIntoScope)); } // Add a block introduced in this function. diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -1848,6 +1848,7 @@ void setFunctionHasBranchIntoScope(); void setFunctionHasBranchProtectedScope(); void setFunctionHasIndirectGoto(); + void setFunctionHasMustTail(); void PushCompoundScope(bool IsStmtExpr); void PopCompoundScope(); @@ -11338,6 +11339,10 @@ /// function, issuing a diagnostic if not. void checkVariadicArgument(const Expr *E, VariadicCallType CT); + /// Check whether the given expression can have musttail applied to it, + /// issuing a diagnostic and returning false if not. + bool checkMustTailAttr(const Stmt *St, const Attr &MTA); + /// Check to see if a given expression could have '.c_str()' called on it. bool hasCStrMethod(const Expr *E); diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -5252,10 +5252,12 @@ if (CGM.getLangOpts().ObjCAutoRefCount) AddObjCARCExceptionMetadata(CI); - // Suppress tail calls if requested. + // Set tail call kind if necessary. if (llvm::CallInst *Call = dyn_cast(CI)) { if (TargetDecl && TargetDecl->hasAttr()) Call->setTailCallKind(llvm::CallInst::TCK_NoTail); + else if (InMustTailCallExpr) + Call->setTailCallKind(llvm::CallInst::TCK_MustTail); } // Add metadata for calls to MSAllocator functions @@ -5307,6 +5309,21 @@ return GetUndefRValue(RetTy); } + // If this is a musttail call, return immediately. We do not branch to the + // epilogue in this case. + if (InMustTailCallExpr) { + // FIXME: insert checks/assertions to verify that this early exit + // is safe. We tried to verify this in Sema but we should double-check + // here. + if (RetTy->isVoidType()) + Builder.CreateRetVoid(); + else + Builder.CreateRet(CI); + Builder.ClearInsertionPoint(); + EnsureInsertPoint(); + return GetUndefRValue(RetTy); + } + // Perform the swifterror writeback. if (swiftErrorTemp.isValid()) { llvm::Value *errorResult = Builder.CreateLoad(swiftErrorTemp); diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -38,6 +38,7 @@ #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/MathExtras.h" #include "llvm/Support/Path.h" +#include "llvm/Support/SaveAndRestore.h" #include "llvm/Transforms/Utils/SanitizerStats.h" #include @@ -4824,6 +4825,8 @@ RValue CodeGenFunction::EmitCallExpr(const CallExpr *E, ReturnValueSlot ReturnValue) { + SaveAndRestore SaveMustTail(InMustTailCallExpr, E == MustTailCall); + // Builtins never have block type. if (E->getCallee()->getType()->isBlockPointerType()) return EmitBlockCallExpr(E, ReturnValue); diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -16,6 +16,8 @@ #include "CodeGenModule.h" #include "TargetInfo.h" #include "clang/AST/Attr.h" +#include "clang/AST/Expr.h" +#include "clang/AST/Stmt.h" #include "clang/AST/StmtVisitor.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/DiagnosticSema.h" @@ -643,12 +645,19 @@ void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) { bool nomerge = false; - for (const auto *A : S.getAttrs()) + const CallExpr *musttail = nullptr; + for (const auto *A : S.getAttrs()) { if (A->getKind() == attr::NoMerge) { nomerge = true; - break; } + if (A->getKind() == attr::MustTail) { + const Stmt *Sub = S.getSubStmt(); + const ReturnStmt *R = cast(Sub); + musttail = cast(R->getRetValue()->IgnoreParenImpCasts()); + } + } SaveAndRestore save_nomerge(InNoMergeAttributedStmt, nomerge); + SaveAndRestore save_musttail(MustTailCall, musttail); EmitStmt(S.getSubStmt(), S.getAttrs()); } diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -517,6 +517,13 @@ /// True if the current statement has nomerge attribute. bool InNoMergeAttributedStmt = false; + /// True if the current statement has musttail attribute. + bool InMustTailCallExpr = false; + + // The CallExpr within the current statement that the musttail attribute + // applies to. This is non-nullptr iff InMustTailCallExpr == true. + const CallExpr *MustTailCall = nullptr; + /// True if the current function should be marked mustprogress. bool FnIsMustProgress = false; diff --git a/clang/lib/Sema/JumpDiagnostics.cpp b/clang/lib/Sema/JumpDiagnostics.cpp --- a/clang/lib/Sema/JumpDiagnostics.cpp +++ b/clang/lib/Sema/JumpDiagnostics.cpp @@ -11,13 +11,14 @@ // //===----------------------------------------------------------------------===// -#include "clang/Sema/SemaInternal.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/StmtCXX.h" #include "clang/AST/StmtObjC.h" #include "clang/AST/StmtOpenMP.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/Sema/SemaInternal.h" #include "llvm/ADT/BitVector.h" using namespace clang; @@ -29,6 +30,10 @@ /// int a[n]; /// L: /// +/// We also detect jumps out of protected scopes when it's not possible to do +/// cleanups properly. Indirect jumps and ASM jumps can't do cleanups because the +/// target is unknown. Return statements with \c [[clang::musttail]] cannot +/// handle any cleanups due to the nature of a tail call. class JumpScopeChecker { Sema &S; @@ -68,6 +73,7 @@ SmallVector IndirectJumps; SmallVector AsmJumps; + SmallVector MustTailStmts; SmallVector IndirectJumpTargets; SmallVector AsmJumpTargets; public: @@ -81,6 +87,7 @@ void VerifyJumps(); void VerifyIndirectOrAsmJumps(bool IsAsmGoto); + void VerifyMustTailStmts(); void NoteJumpIntoScopes(ArrayRef ToScopes); void DiagnoseIndirectOrAsmJump(Stmt *IG, unsigned IGScope, LabelDecl *Target, unsigned TargetScope); @@ -88,6 +95,7 @@ unsigned JumpDiag, unsigned JumpDiagWarning, unsigned JumpDiagCXX98Compat); void CheckGotoStmt(GotoStmt *GS); + const Attr *GetMustTailAttr(AttributedStmt *AS); unsigned GetDeepestCommonScope(unsigned A, unsigned B); }; @@ -109,6 +117,7 @@ VerifyJumps(); VerifyIndirectOrAsmJumps(false); VerifyIndirectOrAsmJumps(true); + VerifyMustTailStmts(); } /// GetDeepestCommonScope - Finds the innermost scope enclosing the @@ -580,6 +589,15 @@ LabelAndGotoScopes[S] = ParentScope; break; + case Stmt::AttributedStmtClass: { + AttributedStmt *AS = cast(S); + if (GetMustTailAttr(AS)) { + LabelAndGotoScopes[AS] = ParentScope; + MustTailStmts.push_back(AS); + } + break; + } + default: if (auto *ED = dyn_cast(S)) { if (!ED->isStandaloneDirective()) { @@ -971,6 +989,25 @@ } } +void JumpScopeChecker::VerifyMustTailStmts() { + for (AttributedStmt *AS : MustTailStmts) { + for (unsigned I = LabelAndGotoScopes[AS]; I; I = Scopes[I].ParentScope) { + if (Scopes[I].OutDiag) { + S.Diag(AS->getBeginLoc(), diag::err_musttail_no_destruction) + << GetMustTailAttr(AS); + S.Diag(Scopes[I].Loc, Scopes[I].OutDiag); + } + } + } +} + +const Attr *JumpScopeChecker::GetMustTailAttr(AttributedStmt *AS) { + ArrayRef Attrs = AS->getAttrs(); + auto Iter = + llvm::find_if(Attrs, [](const Attr *A) { return isa(A); }); + return Iter != Attrs.end() ? *Iter : nullptr; +} + void Sema::DiagnoseInvalidJumps(Stmt *Body) { (void)JumpScopeChecker(Body, *this); } diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -2079,6 +2079,11 @@ FunctionScopes.back()->setHasIndirectGoto(); } +void Sema::setFunctionHasMustTail() { + if (!FunctionScopes.empty()) + FunctionScopes.back()->setHasMustTail(); +} + BlockScopeInfo *Sema::getCurBlock() { if (FunctionScopes.empty()) return nullptr; diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp --- a/clang/lib/Sema/SemaStmt.cpp +++ b/clang/lib/Sema/SemaStmt.cpp @@ -558,11 +558,173 @@ StmtResult Sema::ActOnAttributedStmt(SourceLocation AttrLoc, ArrayRef Attrs, Stmt *SubStmt) { + for (const auto *A : Attrs) { + if (A->getKind() == attr::MustTail) { + if (!checkMustTailAttr(SubStmt, *A)) { + return SubStmt; + } + setFunctionHasMustTail(); + } + } + // Fill in the declaration and return it. AttributedStmt *LS = AttributedStmt::Create(Context, AttrLoc, Attrs, SubStmt); return LS; } +bool Sema::checkMustTailAttr(const Stmt *St, const Attr &MTA) { + const ReturnStmt *R = cast(St); + const Expr *Ex = R->getRetValue(); + + if (const ExprWithCleanups *EWC = dyn_cast(Ex)) { + if (EWC->cleanupsHaveSideEffects()) { + Diag(St->getBeginLoc(), diag::err_musttail_needs_trivial_args) << &MTA; + return false; + } + } + + const CallExpr *CE = dyn_cast(Ex->IgnoreParenImpCasts()); + + if (!CE) { + Diag(St->getBeginLoc(), diag::err_musttail_needs_call) << &MTA; + return false; + } + + const Expr *Callee = CE->getCallee()->IgnoreParens(); + const FunctionProtoType *CalleeType; + QualType CalleeThis; + + const FunctionDecl *CallerDecl = dyn_cast(CurContext); + if (!CallerDecl) { + Diag(St->getBeginLoc(), diag::err_musttail_only_from_function) << &MTA; + return false; + } + + if (CallerDecl->isDependentContext()) + // We have to suspend our check until template instantiation time. + return true; + + // Detect member function calls, inspired by Expr::findBoundMemberType(). + // We can't call Expr::findBoundMemberType() directly because we also need the + // type of "this". + if (const MemberExpr *Mem = dyn_cast(Callee)) { + // Call is: obj.method() or obj->method() + const CXXMethodDecl *CMD = dyn_cast(Mem->getMemberDecl()); + assert(CMD && !CMD->isStatic()); + CalleeThis = CMD->getThisType()->getPointeeType(); + CalleeType = CMD->getType()->castAs(); + } else if (const BinaryOperator *op = dyn_cast(Callee)) { + // Call is: obj->*method_ptr or obj.*method_ptr + const MemberPointerType *MPT = + op->getRHS()->getType()->castAs(); + CalleeThis = QualType(MPT->getClass(), 0); + CalleeType = MPT->getPointeeType()->castAs(); + } else { + // Regular non-member function call. + assert(!Callee->isBoundMemberFunction((Context))); + QualType FunctionType = Callee->getType()->getPointeeType(); + if (FunctionType.isNull()) { + // This call is ill-formed, for example (obj->*method_ptr)() where the + // method pointer type doesn't match. There has already been a + // diagnostic so we don't emit one here. + assert(hasUncompilableErrorOccurred() && "expected previous error"); + return false; + } + CalleeType = dyn_cast( + FunctionType->getUnqualifiedDesugaredType()); + const FunctionProtoType *CallerType = + dyn_cast(CallerDecl->getType()); + if (!CalleeType || !CallerType) { + Diag(St->getBeginLoc(), diag::err_musttail_needs_prototype) << &MTA; + if (!CalleeType && CE->getDirectCallee()) { + Diag(CE->getDirectCallee()->getBeginLoc(), + diag::note_musttail_fix_non_prototype); + } + if (!CallerType) + Diag(CallerDecl->getBeginLoc(), diag::note_musttail_fix_non_prototype); + return false; + } + } + + if (CalleeType->isVariadic() || CallerDecl->isVariadic()) { + Diag(St->getBeginLoc(), diag::err_musttail_no_variadic) << &MTA; + return false; + } + + auto CheckTypesMatch = [this, CallerDecl, CalleeThis, + CalleeType](PartialDiagnostic &PD) -> bool { + auto GetThisType = [](const FunctionDecl *FD) -> QualType { + const CXXMethodDecl *CMD = dyn_cast(FD); + return CMD && !CMD->isStatic() ? CMD->getThisType()->getPointeeType() + : QualType(); + }; + + enum { + ft_different_class, + ft_parameter_arity, + ft_parameter_mismatch, + ft_return_type, + ft_callconv_mismatch + }; + + auto DoTypesMatch = [this, &PD](QualType A, QualType B, + unsigned Select) -> bool { + if (A.isNull()) A = Context.VoidTy; + if (B.isNull()) B = Context.VoidTy; + if (!Context.hasSimilarType(A, B)) { + PD << Select << A << B; + return false; + } + return true; + }; + + const FunctionProtoType *CallerType = + CallerDecl->getType()->castAs(); + + if (!DoTypesMatch(CallerType->getReturnType(), CalleeType->getReturnType(), + ft_return_type) || + !DoTypesMatch(GetThisType(CallerDecl), CalleeThis, ft_different_class)) + return false; + + if (CallerType->getNumParams() != CalleeType->getNumParams()) { + PD << ft_parameter_arity << CallerType->getNumParams() + << CalleeType->getNumParams(); + return false; + } + + ArrayRef CalleeParams = CalleeType->getParamTypes(); + ArrayRef CallerParams = CallerType->getParamTypes(); + size_t N = CallerType->getNumParams(); + for (size_t I = 0; I < N; I++) { + if (!DoTypesMatch(CalleeParams[I], CallerParams[I], ft_parameter_mismatch)) { + PD << static_cast(I) + 1; + return false; + } + } + + if (CallerType->getCallConv() != CalleeType->getCallConv()) { + PD << ft_callconv_mismatch + << FunctionType::getNameForCallConv(CallerType->getCallConv()) + << FunctionType::getNameForCallConv(CalleeType->getCallConv()); + return false; + } + + return true; + }; + + PartialDiagnostic PD = PDiag(diag::note_musttail_mismatch); + if (!CheckTypesMatch(PD)) { + SourceLocation CalleeLoc = CE->getDirectCallee() + ? CE->getDirectCallee()->getBeginLoc() + : St->getBeginLoc(); + Diag(St->getBeginLoc(), diag::err_musttail_mismatch) << &MTA; + Diag(CalleeLoc, PD); + return false; + } + + return true; +} + namespace { class CommaVisitor : public EvaluatedExprVisitor { typedef EvaluatedExprVisitor Inherited; diff --git a/clang/lib/Sema/SemaStmtAttr.cpp b/clang/lib/Sema/SemaStmtAttr.cpp --- a/clang/lib/Sema/SemaStmtAttr.cpp +++ b/clang/lib/Sema/SemaStmtAttr.cpp @@ -209,6 +209,14 @@ return ::new (S.Context) NoMergeAttr(S.Context, A); } +static Attr *handleMustTailAttr(Sema &S, Stmt *St, const ParsedAttr &A, + SourceRange Range) { + MustTailAttr MTA(S.Context, A); + + // Validation is in Sema::ActOnAttributedStmt(). + return ::new (S.Context) MustTailAttr(S.Context, A); +} + static Attr *handleLikely(Sema &S, Stmt *St, const ParsedAttr &A, SourceRange Range) { @@ -425,6 +433,8 @@ return handleSuppressAttr(S, St, A, Range); case ParsedAttr::AT_NoMerge: return handleNoMergeAttr(S, St, A, Range); + case ParsedAttr::AT_MustTail: + return handleMustTailAttr(S, St, A, Range); case ParsedAttr::AT_Likely: return handleLikely(S, St, A, Range); case ParsedAttr::AT_Unlikely: diff --git a/clang/test/CodeGen/attr-musttail.cpp b/clang/test/CodeGen/attr-musttail.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/attr-musttail.cpp @@ -0,0 +1,159 @@ +// RUN: %clang_cc1 -S -emit-llvm %s -triple x86_64-unknown-linux-gnu -o - | FileCheck %s + +int Bar(int); +int Baz(int); + +int Func1(int x) { + if (x) { + // CHECK: %call = musttail call i32 @_Z3Bari(i32 %1) + // CHECK-NEXT: ret i32 %call + [[clang::musttail]] return Bar(x); + } else { + [[clang::musttail]] return Baz(x); // CHECK: %call1 = musttail call i32 @_Z3Bazi(i32 %3) + } +} + +int Func2(int x) { + { + [[clang::musttail]] return Bar(Bar(x)); + } +} + +// CHECK: %call1 = musttail call i32 @_Z3Bari(i32 %call) + +class Foo { +public: + static int StaticMethod(int x); + int MemberFunction(int x); + int TailFrom(int x); + int TailFrom2(int x); + int TailFrom3(int x); +}; + +int Foo::TailFrom(int x) { + [[clang::musttail]] return MemberFunction(x); +} + +// CHECK: %call = musttail call i32 @_ZN3Foo14MemberFunctionEi(%class.Foo* nonnull dereferenceable(1) %this1, i32 %0) + +int Func3(int x) { + [[clang::musttail]] return Foo::StaticMethod(x); +} + +// CHECK: %call = musttail call i32 @_ZN3Foo12StaticMethodEi(i32 %0) + +int Func4(int x) { + Foo foo; // Object with trivial destructor. + [[clang::musttail]] return foo.StaticMethod(x); +} + +// CHECK: %call = musttail call i32 @_ZN3Foo12StaticMethodEi(i32 %0) + +int (Foo::*pmf)(int); + +int Foo::TailFrom2(int x) { + [[clang::musttail]] return ((*this).*pmf)(x); +} + +// CHECK: %call = musttail call i32 %8(%class.Foo* nonnull dereferenceable(1) %this.adjusted, i32 %9) + +int Foo::TailFrom3(int x) { + [[clang::musttail]] return (this->*pmf)(x); +} + +// CHECK: %call = musttail call i32 %8(%class.Foo* nonnull dereferenceable(1) %this.adjusted, i32 %9) + +void ReturnsVoid(); + +void Func5() { + [[clang::musttail]] return ReturnsVoid(); +} + +// CHECK: musttail call void @_Z11ReturnsVoidv() + +class HasTrivialDestructor {}; + +int ReturnsInt(int x); + +int Func6(int x) { + HasTrivialDestructor foo; + [[clang::musttail]] return ReturnsInt(x); +} + +// CHECK: %call = musttail call i32 @_Z10ReturnsInti(i32 %0) + +struct Data { + int (*fptr)(Data *); +}; + +int Func7(Data *data) { + [[clang::musttail]] return data->fptr(data); +} + +// CHECK: %call = musttail call i32 %1(%struct.Data* %2) + +template +T TemplateFunc(T) { + return 5; +} + +int Func9(int x) { + [[clang::musttail]] return TemplateFunc(x); +} + +// CHECK: %call = musttail call i32 @_Z12TemplateFuncIiET_S0_(i32 %0) + +template +int Func10(int x) { + T t; + [[clang::musttail]] return Bar(x); +} + +int Func11(int x) { + return Func10(x); +} + +// CHECK: %call = musttail call i32 @_Z3Bari(i32 %0) + +template +T Func12(T x) { + [[clang::musttail]] return ::Bar(x); +} + +int Func13(int x) { + return Func12(x); +} + +// CHECK: %call = musttail call i32 @_Z3Bari(i32 %0) + +int Func14(int x) { + int vla[x]; + [[clang::musttail]] return Bar(x); +} + +// CHECK: %call = musttail call i32 @_Z3Bari(i32 %3) + +void TrivialDestructorParam(HasTrivialDestructor obj); + +void Func14(HasTrivialDestructor obj) { + [[clang::musttail]] return TrivialDestructorParam(obj); +} + +// CHECK: musttail call void @_Z22TrivialDestructorParam20HasTrivialDestructor() + +int Func15() { + [[clang::musttail]] return []() { return 12; }(); +} + +// CHECK: %call = musttail call i32 @"_ZZ6Func15vENK3$_0clEv"(%class.anon* nonnull dereferenceable(1) %ref.tmp) + +struct Struct3 { + void ConstMemberFunction(const int*) const; + void NonConstMemberFunction(int* i); +}; +void Struct3::NonConstMemberFunction(int* i) { + // The parameters are not identical, but they are compatible. + [[clang::musttail]] return ConstMemberFunction(i); +} + +// CHECK: musttail call void @_ZNK7Struct319ConstMemberFunctionEPKi(%struct.Struct3* nonnull dereferenceable(1) %this1, i32* %0) diff --git a/clang/test/Sema/attr-musttail.c b/clang/test/Sema/attr-musttail.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-musttail.c @@ -0,0 +1,15 @@ +// RUN: %clang_cc1 -verify -fsyntax-only %s + +int NotAProtoType(); // expected-note{{add 'void' to the parameter list to turn a old-style K&R function declaration into a prototype}} +int TestCalleeNotProtoType(void) { + __attribute__((musttail)) return NotAProtoType(); // expected-error{{'musttail' attribute requires that both caller and callee functions have a prototype}} +} + +int ProtoType(void); +int TestCallerNotProtoType() { // expected-note{{add 'void' to the parameter list to turn a old-style K&R function declaration into a prototype}} + __attribute__((musttail)) return ProtoType(); // expected-error{{'musttail' attribute requires that both caller and callee functions have a prototype}} +} + +int TestProtoType(void) { + __attribute__((musttail)) return ProtoType(); +} diff --git a/clang/test/Sema/attr-musttail.cpp b/clang/test/Sema/attr-musttail.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-musttail.cpp @@ -0,0 +1,129 @@ +// RUN: %clang_cc1 -verify -fsyntax-only %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(); +} + +[[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]] return NoParams(); // expected-error {{'musttail' attribute requires that caller and callee have compatible function signatures}} +} + +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]] return LongParam(x); // expected-error {{'musttail' attribute requires that caller and callee have compatible function signatures}} +} + +long ReturnsLong(); // expected-note {{target function has different return type ('int' expected but has 'long')}} +int TestReturnTypeMismatch() { + [[clang::musttail]] return ReturnsLong(); // expected-error {{'musttail' attribute requires that caller and callee have compatible function signatures}} +} + +struct Struct1 { + void MemberFunction(); // expected-note {{target function has different class (expected 'void' but has 'Struct1')}} +}; +void TestNonMemberToMember() { + Struct1 st; + [[clang::musttail]] return st.MemberFunction(); // expected-error {{'musttail' attribute requires that caller and callee have compatible function signatures}} +} + +void ReturnsVoid(); // expected-note {{target function has different class (expected 'Struct2' but has 'void')}} +struct Struct2 { + void TestMemberToNonMember() { + [[clang::musttail]] return ReturnsVoid(); // expected-error{{'musttail' attribute requires that caller and callee have compatible function signatures}} + } +}; + +class HasNonTrivialDestructor { +public: + ~HasNonTrivialDestructor() {} +}; + +void ReturnsVoid2(); +void TestNonTrivialDestructorInScope() { + HasNonTrivialDestructor foo; // expected-note {{jump exits scope of variable with non-trivial destructor}} + [[clang::musttail]] return ReturnsVoid(); // expected-error {{'musttail' attribute requires that all variables in scope are trivially destructible}} +} + +int NonTrivialParam(HasNonTrivialDestructor x); +int TestNonTrivialParam(HasNonTrivialDestructor x) { + [[clang::musttail]] return NonTrivialParam(x); // expected-error {{'musttail' attribute requires that the return type and all arguments are trivially destructible}} +} + +HasNonTrivialDestructor ReturnsNonTrivialValue(); +HasNonTrivialDestructor TestReturnsNonTrivialValue() { + [[clang::musttail]] return ReturnsNonTrivialValue(); // expected-error {{'musttail' attribute requires that the return type and all arguments are trivially destructible}} +} + +struct UsesPointerToMember { + void (UsesPointerToMember::*p_mem)(); +}; +void TestUsesPointerToMember(UsesPointerToMember *foo) { + // "this" pointer cannot double as first parameter. + [[clang::musttail]] return (foo->*(foo->p_mem))(); // expected-error {{'musttail' attribute requires that caller and callee have compatible function signatures}} expected-note{{target function has different class (expected 'void' but has 'UsesPointerToMember')}} +} + +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(); + } + }; +} + +struct TestBadPMF { + int (TestBadPMF::*pmf)(); + void BadPMF() { + // We need to specially handle this, otherwise it can crash the compiler. + [[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'}} + } +}; + +template +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(x); +} +template +T BadTemplateFunc(T x) { + [[clang::musttail]] return TemplateFunc(x); // expected-error {{'musttail' attribute requires that caller and callee have compatible function signatures}} +} +long TestBadTemplateFunc(long x) { + return BadTemplateFunc(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 {{'musttail' attribute requires that all variables in scope 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]] return TakesShortParam(x); // expected-error {{'musttail' attribute requires that caller and callee have compatible function signatures}} +} +int TestIntParamMismatch2(short x) { + [[clang::musttail]] return TakesIntParam(x); // expected-error {{'musttail' attribute requires that caller and callee have compatible function signatures}} +} + +__regcall int RegCallReturnsInt(); // expected-note {{target function has different calling convention (expected cdecl but found regcall)}} +int TestMismatchCallingConvention() { + [[clang::musttail]] return RegCallReturnsInt(); // expected-error {{'musttail' attribute requires that caller and callee have compatible function signatures}} +}