[clang] 8344675 - Implemented [[clang::musttail]] attribute for guaranteed tail calls.
Richard Smith via cfe-commits
cfe-commits at lists.llvm.org
Thu Apr 15 17:13:39 PDT 2021
Author: Joshua Haberman
Date: 2021-04-15T17:12:21-07:00
New Revision: 8344675908424ee532d4ae30e5043c5a5834e02c
URL: https://github.com/llvm/llvm-project/commit/8344675908424ee532d4ae30e5043c5a5834e02c
DIFF: https://github.com/llvm/llvm-project/commit/8344675908424ee532d4ae30e5043c5a5834e02c.diff
LOG: Implemented [[clang::musttail]] attribute for guaranteed tail calls.
This is a Clang-only change and depends on the existing "musttail"
support already implemented in LLVM.
The [[clang::musttail]] attribute goes on a return statement, not
a function definition. There are several constraints that the user
must follow when using [[clang::musttail]], and these constraints
are verified by Sema.
Tail calls are supported on regular function calls, calls through a
function pointer, member function calls, and even pointer to member.
Future work would be to throw a warning if a users tries to pass
a pointer or reference to a local variable through a musttail call.
Reviewed By: rsmith
Differential Revision: https://reviews.llvm.org/D99517
Added:
clang/test/CodeGenCXX/attr-musttail.cpp
clang/test/Sema/attr-musttail.c
clang/test/Sema/attr-musttail.m
clang/test/SemaCXX/attr-musttail.cpp
Modified:
clang/docs/ReleaseNotes.rst
clang/include/clang/AST/IgnoreExpr.h
clang/include/clang/Basic/Attr.td
clang/include/clang/Basic/AttrDocs.td
clang/include/clang/Basic/DiagnosticSemaKinds.td
clang/include/clang/Sema/ScopeInfo.h
clang/include/clang/Sema/Sema.h
clang/lib/CodeGen/CGCall.cpp
clang/lib/CodeGen/CGClass.cpp
clang/lib/CodeGen/CGDecl.cpp
clang/lib/CodeGen/CGExpr.cpp
clang/lib/CodeGen/CGExprCXX.cpp
clang/lib/CodeGen/CGStmt.cpp
clang/lib/CodeGen/CodeGenFunction.h
clang/lib/CodeGen/EHScopeStack.h
clang/lib/Sema/JumpDiagnostics.cpp
clang/lib/Sema/Sema.cpp
clang/lib/Sema/SemaStmt.cpp
clang/lib/Sema/SemaStmtAttr.cpp
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 9d7333660290..ddf048c2dd33 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -46,7 +46,13 @@ sections with improvements to Clang's support for those languages.
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
index b2f53d1d2a79..a7e9b07bef6c 100644
--- a/clang/include/clang/AST/IgnoreExpr.h
+++ b/clang/include/clang/AST/IgnoreExpr.h
@@ -121,6 +121,18 @@ inline Expr *IgnoreImplicitSingleStep(Expr *E) {
return E;
}
+inline Expr *IgnoreElidableImplicitConstructorSingleStep(Expr *E) {
+ auto *CCE = dyn_cast<CXXConstructExpr>(E);
+ if (CCE && CCE->isElidable() && !isa<CXXTemporaryObjectExpr>(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<ImplicitCastExpr>(E))
return ICE->getSubExprAsWritten();
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 400dfe84b7d9..fcad24b83a05 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1370,6 +1370,12 @@ def NoMerge : DeclOrStmtAttr {
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
index eabf30eda8cc..867865e91056 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -443,6 +443,32 @@ calls.
}];
}
+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 (
diff ering 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
index c84fd4da0fd4..aeeed560b514 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -2824,6 +2824,53 @@ def warn_nomerge_attribute_ignored_in_stmt: Warning<
"%0 attribute is ignored because there exists no call expression inside the "
"statement">,
InGroup<IgnoredAttributes>;
+
+def err_musttail_needs_trivial_args : Error<
+ "tail call requires that the return value, all parameters, and any "
+ "temporaries created by the expression are trivially destructible">;
+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
diff erent class%
diff { (expected $ but has $)|}1,2"
+ "|has
diff erent number of parameters (expected %1 but has %2)"
+ "|has type mismatch at %ordinal3 parameter"
+ "%
diff { (expected $ but has $)|}1,2"
+ "|has
diff erent 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
index 8ec74cafeeca..98ed75acd9d2 100644
--- a/clang/include/clang/Sema/ScopeInfo.h
+++ b/clang/include/clang/Sema/ScopeInfo.h
@@ -118,6 +118,10 @@ class FunctionScopeInfo {
/// 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 @@ class FunctionScopeInfo {
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 @@ class FunctionScopeInfo {
HasIndirectGoto = true;
}
+ void setHasMustTail() { HasMustTail = true; }
+
void setHasDroppedStmt() {
HasDroppedStmt = true;
}
@@ -450,9 +455,8 @@ class FunctionScopeInfo {
}
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
index b3407639b40e..cc008e9869d2 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1848,6 +1848,7 @@ class Sema final {
void setFunctionHasBranchIntoScope();
void setFunctionHasBranchProtectedScope();
void setFunctionHasIndirectGoto();
+ void setFunctionHasMustTail();
void PushCompoundScope(bool IsStmtExpr);
void PopCompoundScope();
@@ -11359,6 +11360,18 @@ class Sema final {
/// 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
index 0474ddb03428..366c83eeb8e8 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -4560,7 +4560,7 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
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 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
if (CGM.getLangOpts().ObjCAutoRefCount)
AddObjCARCExceptionMetadata(CI);
- // Suppress tail calls if requested.
+ // Set tail call kind if necessary.
if (llvm::CallInst *Call = dyn_cast<llvm::CallInst>(CI)) {
if (TargetDecl && TargetDecl->hasAttr<NotTailCalledAttr>())
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 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
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<EHCleanupScope>(&*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
index 7fcc84bc7a09..50681da6608d 100644
--- a/clang/lib/CodeGen/CGClass.cpp
+++ b/clang/lib/CodeGen/CGClass.cpp
@@ -2182,7 +2182,7 @@ void CodeGenFunction::EmitCXXConstructorCall(const CXXConstructorDecl *D,
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
index d72b9c4b6e15..fda78f8dd59c 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -550,6 +550,7 @@ namespace {
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
index b5437e497164..a35bd7dd56b7 100644
--- 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 <string>
@@ -5287,7 +5288,7 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType, const CGCallee &OrigCallee
}
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
index b8608911edb2..96cf977ca290 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -87,6 +87,7 @@ RValue CodeGenFunction::EmitCXXMemberOrOperatorCall(
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 @@ RValue CodeGenFunction::EmitCXXDestructorCall(
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 @@ CodeGenFunction::EmitCXXMemberPointerCallExpr(const CXXMemberCallExpr *E,
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
index c2b64c899eeb..b6f4f98fbd2e 100644
--- 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::EmitLabelStmt(const LabelStmt &S) {
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<ReturnStmt>(Sub);
+ musttail = cast<CallExpr>(R->getRetValue()->IgnoreParens());
+ }
+ }
SaveAndRestore<bool> save_nomerge(InNoMergeAttributedStmt, nomerge);
+ SaveAndRestore<const CallExpr *> save_musttail(MustTailCall, musttail);
EmitStmt(S.getSubStmt(), S.getAttrs());
}
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 0b3da9e088d0..7723382c9cb4 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -520,6 +520,10 @@ class CodeGenFunction : public CodeGenTypeCache {
/// 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 @@ class CodeGenFunction : public CodeGenTypeCache {
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 @@ class CodeGenFunction : public CodeGenTypeCache {
/// 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
index 3a640d6117d6..994d3db555aa 100644
--- a/clang/lib/CodeGen/EHScopeStack.h
+++ b/clang/lib/CodeGen/EHScopeStack.h
@@ -150,6 +150,8 @@ class EHScopeStack {
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
index d33b14a79dc1..9d73881d66d7 100644
--- 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 @@ namespace {
/// 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 @@ class JumpScopeChecker {
SmallVector<Stmt*, 4> IndirectJumps;
SmallVector<Stmt*, 4> AsmJumps;
+ SmallVector<AttributedStmt *, 4> MustTailStmts;
SmallVector<LabelDecl*, 4> IndirectJumpTargets;
SmallVector<LabelDecl*, 4> AsmJumpTargets;
public:
@@ -81,6 +87,7 @@ class JumpScopeChecker {
void VerifyJumps();
void VerifyIndirectOrAsmJumps(bool IsAsmGoto);
+ void VerifyMustTailStmts();
void NoteJumpIntoScopes(ArrayRef<unsigned> ToScopes);
void DiagnoseIndirectOrAsmJump(Stmt *IG, unsigned IGScope, LabelDecl *Target,
unsigned TargetScope);
@@ -88,6 +95,7 @@ class JumpScopeChecker {
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 @@ JumpScopeChecker::JumpScopeChecker(Stmt *Body, Sema &s)
VerifyJumps();
VerifyIndirectOrAsmJumps(false);
VerifyIndirectOrAsmJumps(true);
+ VerifyMustTailStmts();
}
/// GetDeepestCommonScope - Finds the innermost scope enclosing the
@@ -580,6 +589,15 @@ void JumpScopeChecker::BuildScopeInformation(Stmt *S,
LabelAndGotoScopes[S] = ParentScope;
break;
+ case Stmt::AttributedStmtClass: {
+ AttributedStmt *AS = cast<AttributedStmt>(S);
+ if (GetMustTailAttr(AS)) {
+ LabelAndGotoScopes[AS] = ParentScope;
+ MustTailStmts.push_back(AS);
+ }
+ break;
+ }
+
default:
if (auto *ED = dyn_cast<OMPExecutableDirective>(S)) {
if (!ED->isStandaloneDirective()) {
@@ -971,6 +989,24 @@ void JumpScopeChecker::CheckGotoStmt(GotoStmt *GS) {
}
}
+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<const Attr *> Attrs = AS->getAttrs();
+ const auto *Iter =
+ llvm::find_if(Attrs, [](const Attr *A) { return isa<MustTailAttr>(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
index 2c99a35e700b..40f11e40882c 100644
--- a/clang/lib/Sema/Sema.cpp
+++ b/clang/lib/Sema/Sema.cpp
@@ -2079,6 +2079,11 @@ void Sema::setFunctionHasIndirectGoto() {
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
index 174679a14f24..a3b1fa4ab534 100644
--- 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 @@ Sema::ActOnLabelStmt(SourceLocation IdentLoc, LabelDecl *TheDecl,
StmtResult Sema::BuildAttributedStmt(SourceLocation AttrsLoc,
ArrayRef<const Attr *> 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 @@ StmtResult Sema::ActOnAttributedStmt(const ParsedAttributesWithRange &Attrs,
return SubStmt;
}
+bool Sema::checkAndRewriteMustTailAttr(Stmt *St, const Attr &MTA) {
+ ReturnStmt *R = cast<ReturnStmt>(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<Expr *>(E), IgnoreParensSingleStep,
+ IgnoreImplicitAsWrittenSingleStep,
+ IgnoreElidableImplicitConstructorSingleStep);
+ };
+
+ const Expr *E = cast<ReturnStmt>(St)->getRetValue();
+ const auto *CE = dyn_cast_or_null<CallExpr>(IgnoreParenImplicitAsWritten(E));
+
+ if (!CE) {
+ Diag(St->getBeginLoc(), diag::err_musttail_needs_call) << &MTA;
+ return false;
+ }
+
+ if (const auto *EWC = dyn_cast<ExprWithCleanups>(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<CXXConstructorDecl, CXXDestructorDecl>(CMD)) {
+ Diag(St->getBeginLoc(), diag::err_musttail_structors_forbidden)
+ << IsCallee << isa<CXXDestructorDecl>(CMD);
+ if (IsCallee)
+ Diag(CMD->getBeginLoc(), diag::note_musttail_structors_forbidden)
+ << isa<CXXDestructorDecl>(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<FunctionProtoType>();
+ return true;
+ };
+
+ const auto *CallerDecl = dyn_cast<FunctionDecl>(CurContext);
+
+ // Find caller function signature.
+ if (!CallerDecl) {
+ int ContextType;
+ if (isa<BlockDecl>(CurContext))
+ ContextType = 0;
+ else if (isa<ObjCMethodDecl>(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<CXXMethodDecl>(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<FunctionProtoType>();
+ }
+
+ const Expr *CalleeExpr = CE->getCallee()->IgnoreParens();
+ const auto *CalleeBinOp = dyn_cast<BinaryOperator>(CalleeExpr);
+ SourceLocation CalleeLoc = CE->getCalleeDecl()
+ ? CE->getCalleeDecl()->getBeginLoc()
+ : St->getBeginLoc();
+
+ // Find callee function signature.
+ if (const CXXMethodDecl *CMD =
+ dyn_cast_or_null<CXXMethodDecl>(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<MemberPointerType>();
+ CalleeType.This = QualType(MPT->getClass(), 0);
+ CalleeType.Func = MPT->getPointeeType()->castAs<FunctionProtoType>();
+ CalleeType.MemberType = FuncType::ft_pointer_to_member;
+ } else if (isa<CXXPseudoDestructorExpr>(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<FunctionProtoType>();
+ }
+
+ // 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<NamedDecl>(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<NamedDecl>(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_
diff erent_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_
diff erent_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<QualType> CalleeParams = CalleeType.Func->getParamTypes();
+ ArrayRef<QualType> 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<int>(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<NamedDecl>(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<CommaVisitor> {
typedef EvaluatedExprVisitor<CommaVisitor> Inherited;
diff --git a/clang/lib/Sema/SemaStmtAttr.cpp b/clang/lib/Sema/SemaStmtAttr.cpp
index 763c6b61fbd9..4f2977f89ce1 100644
--- a/clang/lib/Sema/SemaStmtAttr.cpp
+++ b/clang/lib/Sema/SemaStmtAttr.cpp
@@ -209,6 +209,12 @@ static Attr *handleNoMergeAttr(Sema &S, Stmt *St, const ParsedAttr &A,
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 @@ static Attr *ProcessStmtAttribute(Sema &S, Stmt *St, const ParsedAttr &A,
return handleSuppressAttr(S, St, A, Range);
case ParsedAttr::AT_NoMerge:
return handleNoMergeAttr(S, St, A, Range);
+ case ParsedAttr::AT_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
index 000000000000..1d02d03ec578
--- /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 <class T>
+T TemplateFunc(T) {
+ return 5;
+}
+
+int Func9(int x) {
+ [[clang::musttail]] return TemplateFunc<int>(x);
+}
+
+// CHECK: %call = musttail call i32 @_Z12TemplateFuncIiET_S0_(i32 %0)
+
+template <class T>
+int Func10(int x) {
+ T t;
+ [[clang::musttail]] return Bar(x);
+}
+
+int Func11(int x) {
+ return Func10<int>(x);
+}
+
+// CHECK: %call = musttail call i32 @_Z3Bari(i32 %0)
+
+template <class T>
+T Func12(T x) {
+ [[clang::musttail]] return ::Bar(x);
+}
+
+int Func13(int x) {
+ return Func12<int>(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
index 000000000000..b17947c68ba9
--- /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
index 000000000000..6549232d107a
--- /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))
+ at interface TestObjcClass
+ at end
+
+ at implementation TestObjcClass
+
+- (void)testObjCMethod {
+ __attribute__((musttail)) return ReturnsVoid(); // expected-error{{'musttail' attribute cannot be used from an Objective-C function}}
+}
+
+ at end
diff --git a/clang/test/SemaCXX/attr-musttail.cpp b/clang/test/SemaCXX/attr-musttail.cpp
new file mode 100644
index 000000000000..55faf5a4f5ac
--- /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
diff erent 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
diff erent 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
diff erent 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
diff erent 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
diff erent 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}}
+}
More information about the cfe-commits
mailing list