diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -46,7 +46,13 @@ Major New Features ------------------ -- ... +- Guaranteed tail calls are now supported with statement attributes + ``[[clang::musttail]]`` in C++ and ``__attribute__((musttail))`` in C. The + attribute is applied to a return statement (not a function declaration), + and an error is emitted if a tail call cannot be guaranteed, for example if + the function signatures of caller and callee are not compatible. Guaranteed + tail calls enable a class of algorithms that would otherwise use an + arbitrary amount of stack space. Improvements to Clang's diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/IgnoreExpr.h b/clang/include/clang/AST/IgnoreExpr.h --- a/clang/include/clang/AST/IgnoreExpr.h +++ b/clang/include/clang/AST/IgnoreExpr.h @@ -121,6 +121,18 @@ return E; } +inline Expr *IgnoreElidableImplicitConstructorSingleStep(Expr *E) { + auto *CCE = dyn_cast(E); + if (CCE && CCE->isElidable() && !isa(CCE)) { + unsigned NumArgs = CCE->getNumArgs(); + if ((NumArgs == 1 || + (NumArgs > 1 && CCE->getArg(1)->isDefaultArgument())) && + !CCE->getArg(0)->isDefaultArgument() && !CCE->isListInitialization()) + return CCE->getArg(0); + } + return E; +} + inline Expr *IgnoreImplicitAsWrittenSingleStep(Expr *E) { if (auto *ICE = dyn_cast(E)) return ICE->getSubExprAsWritten(); 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 @@ -1370,6 +1370,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,32 @@ }]; } +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. + +If the callee is a virtual function that is implemented by a thunk, there is +no guarantee in general that the thunk tail-calls the implementation of the +virtual function, so such a call in a recursive cycle can still result in +unbounded stack growth. + +``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 according to C++ rules (differing only in cv +qualifiers or array size), 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 be variadic functions or 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 @@ -2824,6 +2824,53 @@ "%0 attribute is ignored because there exists no call expression inside the " "statement">, InGroup; + +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">; +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 an old-style K&R function " + "declaration into a prototype">; +def err_musttail_structors_forbidden : Error<"cannot perform a tail call " + "%select{from|to}0 a %select{constructor|destructor}1">; +def note_musttail_structors_forbidden : Note<"target " + "%select{constructor|destructor}0 is declared here">; +def err_musttail_forbidden_from_this_context : Error< + "%0 attribute cannot be used from " + "%select{a block|an Objective-C function|this context}1">; +def err_musttail_member_mismatch : Error< + "%select{non-member|static member|non-static member}0 " + "function cannot perform a tail call to " + "%select{non-member|static member|non-static member|pointer-to-member}1 " + "function%select{| %3}2">; +def note_musttail_callee_defined_here : Note<"%0 declared here">; +def note_tail_call_required : Note<"tail call required by %0 attribute here">; +def err_musttail_mismatch : Error< + "cannot perform a tail call to function%select{| %1}0 because its signature " + "is incompatible with the calling function">; +def note_musttail_mismatch : Note< + "target function " + "%select{is a member of 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}0">; +def err_musttail_callconv_mismatch : Error< + "cannot perform a tail call to function%select{| %1}0 because it uses an " + "incompatible calling convention">; +def note_musttail_callconv_mismatch : Note< + "target function has calling convention %1 (expected %0)">; +def err_musttail_scope : Error< + "cannot perform a tail call from this return statement">; +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(); @@ -11359,6 +11360,18 @@ /// function, issuing a diagnostic if not. void checkVariadicArgument(const Expr *E, VariadicCallType CT); + /// Check whether the given statement can have musttail applied to it, + /// issuing a diagnostic and returning false if not. In the success case, + /// the statement is rewritten to remove implicit nodes from the return + /// value. + bool checkAndRewriteMustTailAttr(Stmt *St, const Attr &MTA); + +private: + /// Check whether the given statement can have musttail applied to it, + /// issuing a diagnostic and returning false if not. + bool checkMustTailAttr(const Stmt *St, const Attr &MTA); + +public: /// 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 @@ -4560,7 +4560,7 @@ const CGCallee &Callee, ReturnValueSlot ReturnValue, const CallArgList &CallArgs, - llvm::CallBase **callOrInvoke, + llvm::CallBase **callOrInvoke, bool IsMustTail, SourceLocation Loc) { // FIXME: We no longer need the types from CallArgs; lift up and simplify. @@ -5253,10 +5253,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 (IsMustTail) + Call->setTailCallKind(llvm::CallInst::TCK_MustTail); } // Add metadata for calls to MSAllocator functions @@ -5308,6 +5310,24 @@ return GetUndefRValue(RetTy); } + // If this is a musttail call, return immediately. We do not branch to the + // epilogue in this case. + if (IsMustTail) { + for (auto it = EHStack.find(CurrentCleanupScopeDepth); it != EHStack.end(); + ++it) { + EHCleanupScope *Cleanup = dyn_cast(&*it); + if (!(Cleanup && Cleanup->getCleanup()->isRedundantBeforeReturn())) + CGM.ErrorUnsupported(MustTailCall, "tail call skipping over cleanups"); + } + if (CI->getType()->isVoidTy()) + 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/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp --- a/clang/lib/CodeGen/CGClass.cpp +++ b/clang/lib/CodeGen/CGClass.cpp @@ -2182,7 +2182,7 @@ const CGFunctionInfo &Info = CGM.getTypes().arrangeCXXConstructorCall( Args, D, Type, ExtraArgs.Prefix, ExtraArgs.Suffix, PassPrototypeArgs); CGCallee Callee = CGCallee::forDirect(CalleePtr, GlobalDecl(D, Type)); - EmitCall(Info, Callee, ReturnValueSlot(), Args, nullptr, Loc); + EmitCall(Info, Callee, ReturnValueSlot(), Args, nullptr, false, Loc); // Generate vtable assumptions if we're constructing a complete object // with a vtable. We don't do this for base subobjects for two reasons: diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -550,6 +550,7 @@ struct CallStackRestore final : EHScopeStack::Cleanup { Address Stack; CallStackRestore(Address Stack) : Stack(Stack) {} + bool isRedundantBeforeReturn() override { return true; } void Emit(CodeGenFunction &CGF, Flags flags) override { llvm::Value *V = CGF.Builder.CreateLoad(Stack); llvm::Function *F = CGF.CGM.getIntrinsic(llvm::Intrinsic::stackrestore); 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 @@ -5287,7 +5288,7 @@ } llvm::CallBase *CallOrInvoke = nullptr; RValue Call = EmitCall(FnInfo, Callee, ReturnValue, Args, &CallOrInvoke, - E->getExprLoc()); + E == MustTailCall, E->getExprLoc()); // Generate function declaration DISuprogram in order to be used // in debug info about call sites. diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp --- a/clang/lib/CodeGen/CGExprCXX.cpp +++ b/clang/lib/CodeGen/CGExprCXX.cpp @@ -87,6 +87,7 @@ auto &FnInfo = CGM.getTypes().arrangeCXXMethodCall( Args, FPT, CallInfo.ReqArgs, CallInfo.PrefixSize); return EmitCall(FnInfo, Callee, ReturnValue, Args, nullptr, + CE && CE == MustTailCall, CE ? CE->getExprLoc() : SourceLocation()); } @@ -112,7 +113,7 @@ commonEmitCXXMemberOrOperatorCall(*this, DtorDecl, This, ImplicitParam, ImplicitParamTy, CE, Args, nullptr); return EmitCall(CGM.getTypes().arrangeCXXStructorDeclaration(Dtor), Callee, - ReturnValueSlot(), Args, nullptr, + ReturnValueSlot(), Args, nullptr, CE && CE == MustTailCall, CE ? CE->getExprLoc() : SourceLocation{}); } @@ -472,7 +473,8 @@ EmitCallArgs(Args, FPT, E->arguments()); return EmitCall(CGM.getTypes().arrangeCXXMethodCall(Args, FPT, required, /*PrefixSize=*/0), - Callee, ReturnValue, Args, nullptr, E->getExprLoc()); + Callee, ReturnValue, Args, nullptr, E == MustTailCall, + E->getExprLoc()); } RValue 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" @@ -649,12 +651,20 @@ 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()->IgnoreParens()); + } + } 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 @@ -520,6 +520,10 @@ /// True if the current statement has nomerge attribute. bool InNoMergeAttributedStmt = 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; + /// True if the current function should be marked mustprogress. bool FnIsMustProgress = false; @@ -568,6 +572,8 @@ llvm::Instruction *CurrentFuncletPad = nullptr; class CallLifetimeEnd final : public EHScopeStack::Cleanup { + bool isRedundantBeforeReturn() override { return true; } + llvm::Value *Addr; llvm::Value *Size; @@ -3913,12 +3919,14 @@ /// LLVM arguments and the types they were derived from. RValue EmitCall(const CGFunctionInfo &CallInfo, const CGCallee &Callee, ReturnValueSlot ReturnValue, const CallArgList &Args, - llvm::CallBase **callOrInvoke, SourceLocation Loc); + llvm::CallBase **callOrInvoke, bool IsMustTail, + SourceLocation Loc); RValue EmitCall(const CGFunctionInfo &CallInfo, const CGCallee &Callee, ReturnValueSlot ReturnValue, const CallArgList &Args, - llvm::CallBase **callOrInvoke = nullptr) { + llvm::CallBase **callOrInvoke = nullptr, + bool IsMustTail = false) { return EmitCall(CallInfo, Callee, ReturnValue, Args, callOrInvoke, - SourceLocation()); + IsMustTail, SourceLocation()); } RValue EmitCall(QualType FnType, const CGCallee &Callee, const CallExpr *E, ReturnValueSlot ReturnValue, llvm::Value *Chain = nullptr); diff --git a/clang/lib/CodeGen/EHScopeStack.h b/clang/lib/CodeGen/EHScopeStack.h --- a/clang/lib/CodeGen/EHScopeStack.h +++ b/clang/lib/CodeGen/EHScopeStack.h @@ -150,6 +150,8 @@ Cleanup(Cleanup &&) {} Cleanup() = default; + virtual bool isRedundantBeforeReturn() { return false; } + /// Generation flags. class Flags { enum { 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,24 @@ } } +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_scope); + S.Diag(Scopes[I].Loc, Scopes[I].OutDiag); + } + } + } +} + +const Attr *JumpScopeChecker::GetMustTailAttr(AttributedStmt *AS) { + ArrayRef Attrs = AS->getAttrs(); + const 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 @@ -10,17 +10,16 @@ // //===----------------------------------------------------------------------===// -#include "clang/Sema/Ownership.h" -#include "clang/Sema/SemaInternal.h" #include "clang/AST/ASTContext.h" #include "clang/AST/ASTDiagnostic.h" #include "clang/AST/ASTLambda.h" -#include "clang/AST/CharUnits.h" #include "clang/AST/CXXInheritance.h" +#include "clang/AST/CharUnits.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/EvaluatedExprVisitor.h" #include "clang/AST/ExprCXX.h" #include "clang/AST/ExprObjC.h" +#include "clang/AST/IgnoreExpr.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/StmtCXX.h" #include "clang/AST/StmtObjC.h" @@ -30,8 +29,10 @@ #include "clang/Lex/Preprocessor.h" #include "clang/Sema/Initialization.h" #include "clang/Sema/Lookup.h" +#include "clang/Sema/Ownership.h" #include "clang/Sema/Scope.h" #include "clang/Sema/ScopeInfo.h" +#include "clang/Sema/SemaInternal.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/STLExtras.h" @@ -558,6 +559,17 @@ StmtResult Sema::BuildAttributedStmt(SourceLocation AttrsLoc, ArrayRef Attrs, Stmt *SubStmt) { + // FIXME: this code should move when a planned refactoring around statement + // attributes lands. + for (const auto *A : Attrs) { + if (A->getKind() == attr::MustTail) { + if (!checkAndRewriteMustTailAttr(SubStmt, *A)) { + return SubStmt; + } + setFunctionHasMustTail(); + } + } + return AttributedStmt::Create(Context, AttrsLoc, Attrs, SubStmt); } @@ -573,6 +585,259 @@ return SubStmt; } +bool Sema::checkAndRewriteMustTailAttr(Stmt *St, const Attr &MTA) { + ReturnStmt *R = cast(St); + Expr *E = R->getRetValue(); + + if (CurContext->isDependentContext() || (E && E->isInstantiationDependent())) + // We have to suspend our check until template instantiation time. + return true; + + if (!checkMustTailAttr(St, MTA)) + return false; + + // FIXME: Replace Expr::IgnoreImplicitAsWritten() with this function. + // Currently it does not skip implicit constructors in an initialization + // context. + auto IgnoreImplicitAsWritten = [](Expr *E) -> Expr * { + return IgnoreExprNodes(E, IgnoreImplicitAsWrittenSingleStep, + IgnoreElidableImplicitConstructorSingleStep); + }; + + // Now that we have verified that 'musttail' is valid here, rewrite the + // return value to remove all implicit nodes, but retain parentheses. + R->setRetValue(IgnoreImplicitAsWritten(E)); + return true; +} + +bool Sema::checkMustTailAttr(const Stmt *St, const Attr &MTA) { + assert(!CurContext->isDependentContext() && + "musttail cannot be checked from a dependent context"); + + // FIXME: Add Expr::IgnoreParenImplicitAsWritten() with this definition. + auto IgnoreParenImplicitAsWritten = [](const Expr *E) -> const Expr * { + return IgnoreExprNodes(const_cast(E), IgnoreParensSingleStep, + IgnoreImplicitAsWrittenSingleStep, + IgnoreElidableImplicitConstructorSingleStep); + }; + + const Expr *E = cast(St)->getRetValue(); + const auto *CE = dyn_cast_or_null(IgnoreParenImplicitAsWritten(E)); + + if (!CE) { + Diag(St->getBeginLoc(), diag::err_musttail_needs_call) << &MTA; + return false; + } + + if (const auto *EWC = dyn_cast(E)) { + if (EWC->cleanupsHaveSideEffects()) { + Diag(St->getBeginLoc(), diag::err_musttail_needs_trivial_args) << &MTA; + return false; + } + } + + // We need to determine the full function type (including "this" type, if any) + // for both caller and callee. + struct FuncType { + enum { + ft_non_member, + ft_static_member, + ft_non_static_member, + ft_pointer_to_member, + } MemberType = ft_non_member; + + QualType This; + const FunctionProtoType *Func; + const CXXMethodDecl *Method = nullptr; + } CallerType, CalleeType; + + auto GetMethodType = [this, St, MTA](const CXXMethodDecl *CMD, FuncType &Type, + bool IsCallee) -> bool { + if (isa(CMD)) { + Diag(St->getBeginLoc(), diag::err_musttail_structors_forbidden) + << IsCallee << isa(CMD); + if (IsCallee) + Diag(CMD->getBeginLoc(), diag::note_musttail_structors_forbidden) + << isa(CMD); + Diag(MTA.getLocation(), diag::note_tail_call_required) << &MTA; + return false; + } + if (CMD->isStatic()) + Type.MemberType = FuncType::ft_static_member; + else { + Type.This = CMD->getThisType()->getPointeeType(); + Type.MemberType = FuncType::ft_non_static_member; + } + Type.Func = CMD->getType()->castAs(); + return true; + }; + + const auto *CallerDecl = dyn_cast(CurContext); + + // Find caller function signature. + if (!CallerDecl) { + int ContextType; + if (isa(CurContext)) + ContextType = 0; + else if (isa(CurContext)) + ContextType = 1; + else + ContextType = 2; + Diag(St->getBeginLoc(), diag::err_musttail_forbidden_from_this_context) + << &MTA << ContextType; + return false; + } else if (const auto *CMD = dyn_cast(CurContext)) { + // Caller is a class/struct method. + if (!GetMethodType(CMD, CallerType, false)) + return false; + } else { + // Caller is a non-method function. + CallerType.Func = CallerDecl->getType()->getAs(); + } + + const Expr *CalleeExpr = CE->getCallee()->IgnoreParens(); + const auto *CalleeBinOp = dyn_cast(CalleeExpr); + SourceLocation CalleeLoc = CE->getCalleeDecl() + ? CE->getCalleeDecl()->getBeginLoc() + : St->getBeginLoc(); + + // Find callee function signature. + if (const CXXMethodDecl *CMD = + dyn_cast_or_null(CE->getCalleeDecl())) { + // Call is: obj.method(), obj->method(), functor(), etc. + if (!GetMethodType(CMD, CalleeType, true)) + return false; + } else if (CalleeBinOp && CalleeBinOp->isPtrMemOp()) { + // Call is: obj->*method_ptr or obj.*method_ptr + const auto *MPT = + CalleeBinOp->getRHS()->getType()->castAs(); + CalleeType.This = QualType(MPT->getClass(), 0); + CalleeType.Func = MPT->getPointeeType()->castAs(); + CalleeType.MemberType = FuncType::ft_pointer_to_member; + } else if (isa(CalleeExpr)) { + Diag(St->getBeginLoc(), diag::err_musttail_structors_forbidden) + << /* IsCallee = */ 1 << /* IsDestructor = */ 1; + Diag(MTA.getLocation(), diag::note_tail_call_required) << &MTA; + return false; + } else { + // Non-method function. + CalleeType.Func = + CalleeExpr->getType()->getPointeeType()->getAs(); + } + + // Both caller and callee must have a prototype (no K&R declarations). + if (!CalleeType.Func || !CallerType.Func) { + Diag(St->getBeginLoc(), diag::err_musttail_needs_prototype) << &MTA; + if (!CalleeType.Func && CE->getDirectCallee()) { + Diag(CE->getDirectCallee()->getBeginLoc(), + diag::note_musttail_fix_non_prototype); + } + if (!CallerType.Func) + Diag(CallerDecl->getBeginLoc(), diag::note_musttail_fix_non_prototype); + return false; + } + + // Caller and callee must have matching calling conventions. + // + // Some calling conventions are physically capable of supporting tail calls + // even if the function types don't perfectly match. LLVM is currently too + // strict to allow this, but if LLVM added support for this in the future, we + // could exit early here and skip the remaining checks if the functions are + // using such a calling convention. + if (CallerType.Func->getCallConv() != CalleeType.Func->getCallConv()) { + if (const auto *ND = dyn_cast_or_null(CE->getCalleeDecl())) + Diag(St->getBeginLoc(), diag::err_musttail_callconv_mismatch) + << true << ND->getDeclName(); + else + Diag(St->getBeginLoc(), diag::err_musttail_callconv_mismatch) << false; + Diag(CalleeLoc, diag::note_musttail_callconv_mismatch) + << FunctionType::getNameForCallConv(CallerType.Func->getCallConv()) + << FunctionType::getNameForCallConv(CalleeType.Func->getCallConv()); + Diag(MTA.getLocation(), diag::note_tail_call_required) << &MTA; + return false; + } + + if (CalleeType.Func->isVariadic() || CallerType.Func->isVariadic()) { + Diag(St->getBeginLoc(), diag::err_musttail_no_variadic) << &MTA; + return false; + } + + // Caller and callee must match in whether they have a "this" parameter. + if (CallerType.This.isNull() != CalleeType.This.isNull()) { + if (const auto *ND = dyn_cast_or_null(CE->getCalleeDecl())) { + Diag(St->getBeginLoc(), diag::err_musttail_member_mismatch) + << CallerType.MemberType << CalleeType.MemberType << true + << ND->getDeclName(); + Diag(CalleeLoc, diag::note_musttail_callee_defined_here) + << ND->getDeclName(); + } else + Diag(St->getBeginLoc(), diag::err_musttail_member_mismatch) + << CallerType.MemberType << CalleeType.MemberType << false; + Diag(MTA.getLocation(), diag::note_tail_call_required) << &MTA; + return false; + } + + auto CheckTypesMatch = [this](FuncType CallerType, FuncType CalleeType, + PartialDiagnostic &PD) -> bool { + enum { + ft_different_class, + ft_parameter_arity, + ft_parameter_mismatch, + ft_return_type, + }; + + auto DoTypesMatch = [this, &PD](QualType A, QualType B, + unsigned Select) -> bool { + if (!Context.hasSimilarType(A, B)) { + PD << Select << A.getUnqualifiedType() << B.getUnqualifiedType(); + return false; + } + return true; + }; + + if (!CallerType.This.isNull() && + !DoTypesMatch(CallerType.This, CalleeType.This, ft_different_class)) + return false; + + if (!DoTypesMatch(CallerType.Func->getReturnType(), + CalleeType.Func->getReturnType(), ft_return_type)) + return false; + + if (CallerType.Func->getNumParams() != CalleeType.Func->getNumParams()) { + PD << ft_parameter_arity << CallerType.Func->getNumParams() + << CalleeType.Func->getNumParams(); + return false; + } + + ArrayRef CalleeParams = CalleeType.Func->getParamTypes(); + ArrayRef CallerParams = CallerType.Func->getParamTypes(); + size_t N = CallerType.Func->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; + } + } + + return true; + }; + + PartialDiagnostic PD = PDiag(diag::note_musttail_mismatch); + if (!CheckTypesMatch(CallerType, CalleeType, PD)) { + if (const auto *ND = dyn_cast_or_null(CE->getCalleeDecl())) + Diag(St->getBeginLoc(), diag::err_musttail_mismatch) + << true << ND->getDeclName(); + else + Diag(St->getBeginLoc(), diag::err_musttail_mismatch) << false; + Diag(CalleeLoc, PD); + Diag(MTA.getLocation(), diag::note_tail_call_required) << &MTA; + 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,12 @@ return ::new (S.Context) NoMergeAttr(S.Context, A); } +static Attr *handleMustTailAttr(Sema &S, Stmt *St, const ParsedAttr &A, + SourceRange Range) { + // 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) { @@ -412,6 +418,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/CodeGenCXX/attr-musttail.cpp b/clang/test/CodeGenCXX/attr-musttail.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/attr-musttail.cpp @@ -0,0 +1,228 @@ +// RUN: %clang_cc1 -fno-elide-constructors -S -emit-llvm %s -triple x86_64-unknown-linux-gnu -o - | FileCheck %s +// RUN: %clang_cc1 -fno-elide-constructors -S -emit-llvm %s -triple x86_64-unknown-linux-gnu -o - | opt -verify +// FIXME: remove the call to "opt" once the tests are running the Clang verifier automatically again. + +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() + +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) + +struct HasNonTrivialCopyConstructor { + HasNonTrivialCopyConstructor(const HasNonTrivialCopyConstructor &); +}; +HasNonTrivialCopyConstructor ReturnsClassByValue(); +HasNonTrivialCopyConstructor TestNonElidableCopyConstructor() { + [[clang::musttail]] return (((ReturnsClassByValue()))); +} + +// CHECK: musttail call void @_Z19ReturnsClassByValuev(%struct.HasNonTrivialCopyConstructor* sret(%struct.HasNonTrivialCopyConstructor) align 1 %agg.result) + +struct HasNonTrivialCopyConstructor2 { + // Copy constructor works even if it has extra default params. + HasNonTrivialCopyConstructor2(const HasNonTrivialCopyConstructor &, int DefaultParam = 5); +}; +HasNonTrivialCopyConstructor2 ReturnsClassByValue2(); +HasNonTrivialCopyConstructor2 TestNonElidableCopyConstructor2() { + [[clang::musttail]] return (((ReturnsClassByValue2()))); +} + +// CHECK: musttail call void @_Z20ReturnsClassByValue2v() + +void TestFunctionPointer(int x) { + void (*p)(int) = nullptr; + [[clang::musttail]] return p(x); +} + +// CHECK: musttail call void %0(i32 %1) + +struct LargeWithCopyConstructor { + LargeWithCopyConstructor(const LargeWithCopyConstructor &); + char data[32]; +}; +LargeWithCopyConstructor ReturnsLarge(); +LargeWithCopyConstructor TestLargeWithCopyConstructor() { + [[clang::musttail]] return ReturnsLarge(); +} + +// CHECK: define dso_local void @_Z28TestLargeWithCopyConstructorv(%struct.LargeWithCopyConstructor* noalias sret(%struct.LargeWithCopyConstructor) align 1 %agg.result) +// CHECK: musttail call void @_Z12ReturnsLargev(%struct.LargeWithCopyConstructor* sret(%struct.LargeWithCopyConstructor) align 1 %agg.result) + +using IntFunctionType = int(); +IntFunctionType *ReturnsIntFunction(); +int TestRValueFunctionPointer() { + [[clang::musttail]] return ReturnsIntFunction()(); +} + +// CHECK: musttail call i32 %call() + +void(FuncWithParens)() { + [[clang::musttail]] return FuncWithParens(); +} + +// CHECK: musttail call void @_Z14FuncWithParensv() + +int TestNonCapturingLambda() { + auto lambda = []() { return 12; }; + [[clang::musttail]] return (+lambda)(); +} + +// CHECK: %call = call i32 ()* @"_ZZ22TestNonCapturingLambdavENK3$_0cvPFivEEv"(%class.anon* nonnull dereferenceable(1) %lambda) +// CHECK: musttail call i32 %call() + +class TestVirtual { + virtual void TailTo(); + virtual void TailFrom(); +}; + +void TestVirtual::TailFrom() { + [[clang::musttail]] return TailTo(); +} + +// CHECK: musttail call void %1(%class.TestVirtual* nonnull dereferenceable(8) %this1) 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 an 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 an 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) { + return ProtoType(); +} diff --git a/clang/test/Sema/attr-musttail.m b/clang/test/Sema/attr-musttail.m new file mode 100644 --- /dev/null +++ b/clang/test/Sema/attr-musttail.m @@ -0,0 +1,26 @@ +// RUN: %clang_cc1 -fsyntax-only -fblocks -Wno-objc-root-class -verify %s + +void TestObjcBlock(void) { + void (^x)(void) = ^(void) { + __attribute__((musttail)) return TestObjcBlock(); // expected-error{{'musttail' attribute cannot be used from a block}} + }; + __attribute__((musttail)) return x(); +} + +void ReturnsVoid(void); +void TestObjcBlockVar(void) { + __block int i = 0; // expected-note{{jump exits scope of __block variable}} + __attribute__((musttail)) return ReturnsVoid(); // expected-error{{cannot perform a tail call from this return statement}} +} + +__attribute__((objc_root_class)) +@interface TestObjcClass +@end + +@implementation TestObjcClass + +- (void)testObjCMethod { + __attribute__((musttail)) return ReturnsVoid(); // expected-error{{'musttail' attribute cannot be used from an Objective-C function}} +} + +@end diff --git a/clang/test/SemaCXX/attr-musttail.cpp b/clang/test/SemaCXX/attr-musttail.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/attr-musttail.cpp @@ -0,0 +1,269 @@ +// RUN: %clang_cc1 -verify -fsyntax-only -fms-extensions -fcxx-exceptions -fopenmp %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 +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]] // expected-note {{tail call required by 'musttail' attribute here}} + return TemplateFunc(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(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(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}} +}