Skip to content

Commit 8344675

Browse files
habermanzygoloid
authored andcommitted
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
1 parent f280505 commit 8344675

23 files changed

+1035
-25
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,13 @@ sections with improvements to Clang's support for those languages.
4646
Major New Features
4747
------------------
4848

49-
- ...
49+
- Guaranteed tail calls are now supported with statement attributes
50+
``[[clang::musttail]]`` in C++ and ``__attribute__((musttail))`` in C. The
51+
attribute is applied to a return statement (not a function declaration),
52+
and an error is emitted if a tail call cannot be guaranteed, for example if
53+
the function signatures of caller and callee are not compatible. Guaranteed
54+
tail calls enable a class of algorithms that would otherwise use an
55+
arbitrary amount of stack space.
5056

5157
Improvements to Clang's diagnostics
5258
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/AST/IgnoreExpr.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,18 @@ inline Expr *IgnoreImplicitSingleStep(Expr *E) {
121121
return E;
122122
}
123123

124+
inline Expr *IgnoreElidableImplicitConstructorSingleStep(Expr *E) {
125+
auto *CCE = dyn_cast<CXXConstructExpr>(E);
126+
if (CCE && CCE->isElidable() && !isa<CXXTemporaryObjectExpr>(CCE)) {
127+
unsigned NumArgs = CCE->getNumArgs();
128+
if ((NumArgs == 1 ||
129+
(NumArgs > 1 && CCE->getArg(1)->isDefaultArgument())) &&
130+
!CCE->getArg(0)->isDefaultArgument() && !CCE->isListInitialization())
131+
return CCE->getArg(0);
132+
}
133+
return E;
134+
}
135+
124136
inline Expr *IgnoreImplicitAsWrittenSingleStep(Expr *E) {
125137
if (auto *ICE = dyn_cast<ImplicitCastExpr>(E))
126138
return ICE->getSubExprAsWritten();

clang/include/clang/Basic/Attr.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,6 +1370,12 @@ def NoMerge : DeclOrStmtAttr {
13701370
let SimpleHandler = 1;
13711371
}
13721372

1373+
def MustTail : StmtAttr {
1374+
let Spellings = [Clang<"musttail">];
1375+
let Documentation = [MustTailDocs];
1376+
let Subjects = SubjectList<[ReturnStmt], ErrorDiag, "return statements">;
1377+
}
1378+
13731379
def FastCall : DeclOrTypeAttr {
13741380
let Spellings = [GCC<"fastcall">, Keyword<"__fastcall">,
13751381
Keyword<"_fastcall">];

clang/include/clang/Basic/AttrDocs.td

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,32 @@ calls.
443443
}];
444444
}
445445

446+
def MustTailDocs : Documentation {
447+
let Category = DocCatStmt;
448+
let Content = [{
449+
If a ``return`` statement is marked ``musttail``, this indicates that the
450+
compiler must generate a tail call for the program to be correct, even when
451+
optimizations are disabled. This guarantees that the call will not cause
452+
unbounded stack growth if it is part of a recursive cycle in the call graph.
453+
454+
If the callee is a virtual function that is implemented by a thunk, there is
455+
no guarantee in general that the thunk tail-calls the implementation of the
456+
virtual function, so such a call in a recursive cycle can still result in
457+
unbounded stack growth.
458+
459+
``clang::musttail`` can only be applied to a ``return`` statement whose value
460+
is the result of a function call (even functions returning void must use
461+
``return``, although no value is returned). The target function must have the
462+
same number of arguments as the caller. The types of the return value and all
463+
arguments must be similar according to C++ rules (differing only in cv
464+
qualifiers or array size), including the implicit "this" argument, if any.
465+
Any variables in scope, including all arguments to the function and the
466+
return value must be trivially destructible. The calling convention of the
467+
caller and callee must match, and they must not be variadic functions or have
468+
old style K&R C function declarations.
469+
}];
470+
}
471+
446472
def AssertCapabilityDocs : Documentation {
447473
let Category = DocCatFunction;
448474
let Heading = "assert_capability, assert_shared_capability";

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2824,6 +2824,53 @@ def warn_nomerge_attribute_ignored_in_stmt: Warning<
28242824
"%0 attribute is ignored because there exists no call expression inside the "
28252825
"statement">,
28262826
InGroup<IgnoredAttributes>;
2827+
2828+
def err_musttail_needs_trivial_args : Error<
2829+
"tail call requires that the return value, all parameters, and any "
2830+
"temporaries created by the expression are trivially destructible">;
2831+
def err_musttail_needs_call : Error<
2832+
"%0 attribute requires that the return value is the result of a function call"
2833+
>;
2834+
def err_musttail_needs_prototype : Error<
2835+
"%0 attribute requires that both caller and callee functions have a "
2836+
"prototype">;
2837+
def note_musttail_fix_non_prototype : Note<
2838+
"add 'void' to the parameter list to turn an old-style K&R function "
2839+
"declaration into a prototype">;
2840+
def err_musttail_structors_forbidden : Error<"cannot perform a tail call "
2841+
"%select{from|to}0 a %select{constructor|destructor}1">;
2842+
def note_musttail_structors_forbidden : Note<"target "
2843+
"%select{constructor|destructor}0 is declared here">;
2844+
def err_musttail_forbidden_from_this_context : Error<
2845+
"%0 attribute cannot be used from "
2846+
"%select{a block|an Objective-C function|this context}1">;
2847+
def err_musttail_member_mismatch : Error<
2848+
"%select{non-member|static member|non-static member}0 "
2849+
"function cannot perform a tail call to "
2850+
"%select{non-member|static member|non-static member|pointer-to-member}1 "
2851+
"function%select{| %3}2">;
2852+
def note_musttail_callee_defined_here : Note<"%0 declared here">;
2853+
def note_tail_call_required : Note<"tail call required by %0 attribute here">;
2854+
def err_musttail_mismatch : Error<
2855+
"cannot perform a tail call to function%select{| %1}0 because its signature "
2856+
"is incompatible with the calling function">;
2857+
def note_musttail_mismatch : Note<
2858+
"target function "
2859+
"%select{is a member of different class%diff{ (expected $ but has $)|}1,2"
2860+
"|has different number of parameters (expected %1 but has %2)"
2861+
"|has type mismatch at %ordinal3 parameter"
2862+
"%diff{ (expected $ but has $)|}1,2"
2863+
"|has different return type%diff{ ($ expected but has $)|}1,2}0">;
2864+
def err_musttail_callconv_mismatch : Error<
2865+
"cannot perform a tail call to function%select{| %1}0 because it uses an "
2866+
"incompatible calling convention">;
2867+
def note_musttail_callconv_mismatch : Note<
2868+
"target function has calling convention %1 (expected %0)">;
2869+
def err_musttail_scope : Error<
2870+
"cannot perform a tail call from this return statement">;
2871+
def err_musttail_no_variadic : Error<
2872+
"%0 attribute may not be used with variadic functions">;
2873+
28272874
def err_nsobject_attribute : Error<
28282875
"'NSObject' attribute is for pointer types only">;
28292876
def err_attributes_are_not_compatible : Error<

clang/include/clang/Sema/ScopeInfo.h

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ class FunctionScopeInfo {
118118
/// Whether this function contains any indirect gotos.
119119
bool HasIndirectGoto : 1;
120120

121+
/// Whether this function contains any statement marked with
122+
/// \c [[clang::musttail]].
123+
bool HasMustTail : 1;
124+
121125
/// Whether a statement was dropped because it was invalid.
122126
bool HasDroppedStmt : 1;
123127

@@ -370,14 +374,13 @@ class FunctionScopeInfo {
370374
public:
371375
FunctionScopeInfo(DiagnosticsEngine &Diag)
372376
: Kind(SK_Function), HasBranchProtectedScope(false),
373-
HasBranchIntoScope(false), HasIndirectGoto(false),
377+
HasBranchIntoScope(false), HasIndirectGoto(false), HasMustTail(false),
374378
HasDroppedStmt(false), HasOMPDeclareReductionCombiner(false),
375379
HasFallthroughStmt(false), UsesFPIntrin(false),
376-
HasPotentialAvailabilityViolations(false),
377-
ObjCShouldCallSuper(false), ObjCIsDesignatedInit(false),
378-
ObjCWarnForNoDesignatedInitChain(false), ObjCIsSecondaryInit(false),
379-
ObjCWarnForNoInitDelegation(false), NeedsCoroutineSuspends(true),
380-
ErrorTrap(Diag) {}
380+
HasPotentialAvailabilityViolations(false), ObjCShouldCallSuper(false),
381+
ObjCIsDesignatedInit(false), ObjCWarnForNoDesignatedInitChain(false),
382+
ObjCIsSecondaryInit(false), ObjCWarnForNoInitDelegation(false),
383+
NeedsCoroutineSuspends(true), ErrorTrap(Diag) {}
381384

382385
virtual ~FunctionScopeInfo();
383386

@@ -423,6 +426,8 @@ class FunctionScopeInfo {
423426
HasIndirectGoto = true;
424427
}
425428

429+
void setHasMustTail() { HasMustTail = true; }
430+
426431
void setHasDroppedStmt() {
427432
HasDroppedStmt = true;
428433
}
@@ -450,9 +455,8 @@ class FunctionScopeInfo {
450455
}
451456

452457
bool NeedsScopeChecking() const {
453-
return !HasDroppedStmt &&
454-
(HasIndirectGoto ||
455-
(HasBranchProtectedScope && HasBranchIntoScope));
458+
return !HasDroppedStmt && (HasIndirectGoto || HasMustTail ||
459+
(HasBranchProtectedScope && HasBranchIntoScope));
456460
}
457461

458462
// Add a block introduced in this function.

clang/include/clang/Sema/Sema.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,6 +1848,7 @@ class Sema final {
18481848
void setFunctionHasBranchIntoScope();
18491849
void setFunctionHasBranchProtectedScope();
18501850
void setFunctionHasIndirectGoto();
1851+
void setFunctionHasMustTail();
18511852

18521853
void PushCompoundScope(bool IsStmtExpr);
18531854
void PopCompoundScope();
@@ -11359,6 +11360,18 @@ class Sema final {
1135911360
/// function, issuing a diagnostic if not.
1136011361
void checkVariadicArgument(const Expr *E, VariadicCallType CT);
1136111362

11363+
/// Check whether the given statement can have musttail applied to it,
11364+
/// issuing a diagnostic and returning false if not. In the success case,
11365+
/// the statement is rewritten to remove implicit nodes from the return
11366+
/// value.
11367+
bool checkAndRewriteMustTailAttr(Stmt *St, const Attr &MTA);
11368+
11369+
private:
11370+
/// Check whether the given statement can have musttail applied to it,
11371+
/// issuing a diagnostic and returning false if not.
11372+
bool checkMustTailAttr(const Stmt *St, const Attr &MTA);
11373+
11374+
public:
1136211375
/// Check to see if a given expression could have '.c_str()' called on it.
1136311376
bool hasCStrMethod(const Expr *E);
1136411377

clang/lib/CodeGen/CGCall.cpp

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4560,7 +4560,7 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
45604560
const CGCallee &Callee,
45614561
ReturnValueSlot ReturnValue,
45624562
const CallArgList &CallArgs,
4563-
llvm::CallBase **callOrInvoke,
4563+
llvm::CallBase **callOrInvoke, bool IsMustTail,
45644564
SourceLocation Loc) {
45654565
// FIXME: We no longer need the types from CallArgs; lift up and simplify.
45664566

@@ -5253,10 +5253,12 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
52535253
if (CGM.getLangOpts().ObjCAutoRefCount)
52545254
AddObjCARCExceptionMetadata(CI);
52555255

5256-
// Suppress tail calls if requested.
5256+
// Set tail call kind if necessary.
52575257
if (llvm::CallInst *Call = dyn_cast<llvm::CallInst>(CI)) {
52585258
if (TargetDecl && TargetDecl->hasAttr<NotTailCalledAttr>())
52595259
Call->setTailCallKind(llvm::CallInst::TCK_NoTail);
5260+
else if (IsMustTail)
5261+
Call->setTailCallKind(llvm::CallInst::TCK_MustTail);
52605262
}
52615263

52625264
// Add metadata for calls to MSAllocator functions
@@ -5308,6 +5310,24 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
53085310
return GetUndefRValue(RetTy);
53095311
}
53105312

5313+
// If this is a musttail call, return immediately. We do not branch to the
5314+
// epilogue in this case.
5315+
if (IsMustTail) {
5316+
for (auto it = EHStack.find(CurrentCleanupScopeDepth); it != EHStack.end();
5317+
++it) {
5318+
EHCleanupScope *Cleanup = dyn_cast<EHCleanupScope>(&*it);
5319+
if (!(Cleanup && Cleanup->getCleanup()->isRedundantBeforeReturn()))
5320+
CGM.ErrorUnsupported(MustTailCall, "tail call skipping over cleanups");
5321+
}
5322+
if (CI->getType()->isVoidTy())
5323+
Builder.CreateRetVoid();
5324+
else
5325+
Builder.CreateRet(CI);
5326+
Builder.ClearInsertionPoint();
5327+
EnsureInsertPoint();
5328+
return GetUndefRValue(RetTy);
5329+
}
5330+
53115331
// Perform the swifterror writeback.
53125332
if (swiftErrorTemp.isValid()) {
53135333
llvm::Value *errorResult = Builder.CreateLoad(swiftErrorTemp);

clang/lib/CodeGen/CGClass.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2182,7 +2182,7 @@ void CodeGenFunction::EmitCXXConstructorCall(const CXXConstructorDecl *D,
21822182
const CGFunctionInfo &Info = CGM.getTypes().arrangeCXXConstructorCall(
21832183
Args, D, Type, ExtraArgs.Prefix, ExtraArgs.Suffix, PassPrototypeArgs);
21842184
CGCallee Callee = CGCallee::forDirect(CalleePtr, GlobalDecl(D, Type));
2185-
EmitCall(Info, Callee, ReturnValueSlot(), Args, nullptr, Loc);
2185+
EmitCall(Info, Callee, ReturnValueSlot(), Args, nullptr, false, Loc);
21862186

21872187
// Generate vtable assumptions if we're constructing a complete object
21882188
// with a vtable. We don't do this for base subobjects for two reasons:

clang/lib/CodeGen/CGDecl.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,7 @@ namespace {
550550
struct CallStackRestore final : EHScopeStack::Cleanup {
551551
Address Stack;
552552
CallStackRestore(Address Stack) : Stack(Stack) {}
553+
bool isRedundantBeforeReturn() override { return true; }
553554
void Emit(CodeGenFunction &CGF, Flags flags) override {
554555
llvm::Value *V = CGF.Builder.CreateLoad(Stack);
555556
llvm::Function *F = CGF.CGM.getIntrinsic(llvm::Intrinsic::stackrestore);

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "llvm/Support/ConvertUTF.h"
3939
#include "llvm/Support/MathExtras.h"
4040
#include "llvm/Support/Path.h"
41+
#include "llvm/Support/SaveAndRestore.h"
4142
#include "llvm/Transforms/Utils/SanitizerStats.h"
4243

4344
#include <string>
@@ -5287,7 +5288,7 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType, const CGCallee &OrigCallee
52875288
}
52885289
llvm::CallBase *CallOrInvoke = nullptr;
52895290
RValue Call = EmitCall(FnInfo, Callee, ReturnValue, Args, &CallOrInvoke,
5290-
E->getExprLoc());
5291+
E == MustTailCall, E->getExprLoc());
52915292

52925293
// Generate function declaration DISuprogram in order to be used
52935294
// in debug info about call sites.

clang/lib/CodeGen/CGExprCXX.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ RValue CodeGenFunction::EmitCXXMemberOrOperatorCall(
8787
auto &FnInfo = CGM.getTypes().arrangeCXXMethodCall(
8888
Args, FPT, CallInfo.ReqArgs, CallInfo.PrefixSize);
8989
return EmitCall(FnInfo, Callee, ReturnValue, Args, nullptr,
90+
CE && CE == MustTailCall,
9091
CE ? CE->getExprLoc() : SourceLocation());
9192
}
9293

@@ -112,7 +113,7 @@ RValue CodeGenFunction::EmitCXXDestructorCall(
112113
commonEmitCXXMemberOrOperatorCall(*this, DtorDecl, This, ImplicitParam,
113114
ImplicitParamTy, CE, Args, nullptr);
114115
return EmitCall(CGM.getTypes().arrangeCXXStructorDeclaration(Dtor), Callee,
115-
ReturnValueSlot(), Args, nullptr,
116+
ReturnValueSlot(), Args, nullptr, CE && CE == MustTailCall,
116117
CE ? CE->getExprLoc() : SourceLocation{});
117118
}
118119

@@ -472,7 +473,8 @@ CodeGenFunction::EmitCXXMemberPointerCallExpr(const CXXMemberCallExpr *E,
472473
EmitCallArgs(Args, FPT, E->arguments());
473474
return EmitCall(CGM.getTypes().arrangeCXXMethodCall(Args, FPT, required,
474475
/*PrefixSize=*/0),
475-
Callee, ReturnValue, Args, nullptr, E->getExprLoc());
476+
Callee, ReturnValue, Args, nullptr, E == MustTailCall,
477+
E->getExprLoc());
476478
}
477479

478480
RValue

clang/lib/CodeGen/CGStmt.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include "CodeGenModule.h"
1717
#include "TargetInfo.h"
1818
#include "clang/AST/Attr.h"
19+
#include "clang/AST/Expr.h"
20+
#include "clang/AST/Stmt.h"
1921
#include "clang/AST/StmtVisitor.h"
2022
#include "clang/Basic/Builtins.h"
2123
#include "clang/Basic/DiagnosticSema.h"
@@ -649,12 +651,20 @@ void CodeGenFunction::EmitLabelStmt(const LabelStmt &S) {
649651

650652
void CodeGenFunction::EmitAttributedStmt(const AttributedStmt &S) {
651653
bool nomerge = false;
652-
for (const auto *A : S.getAttrs())
654+
const CallExpr *musttail = nullptr;
655+
656+
for (const auto *A : S.getAttrs()) {
653657
if (A->getKind() == attr::NoMerge) {
654658
nomerge = true;
655-
break;
656659
}
660+
if (A->getKind() == attr::MustTail) {
661+
const Stmt *Sub = S.getSubStmt();
662+
const ReturnStmt *R = cast<ReturnStmt>(Sub);
663+
musttail = cast<CallExpr>(R->getRetValue()->IgnoreParens());
664+
}
665+
}
657666
SaveAndRestore<bool> save_nomerge(InNoMergeAttributedStmt, nomerge);
667+
SaveAndRestore<const CallExpr *> save_musttail(MustTailCall, musttail);
658668
EmitStmt(S.getSubStmt(), S.getAttrs());
659669
}
660670

clang/lib/CodeGen/CodeGenFunction.h

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,10 @@ class CodeGenFunction : public CodeGenTypeCache {
520520
/// True if the current statement has nomerge attribute.
521521
bool InNoMergeAttributedStmt = false;
522522

523+
// The CallExpr within the current statement that the musttail attribute
524+
// applies to. nullptr if there is no 'musttail' on the current statement.
525+
const CallExpr *MustTailCall = nullptr;
526+
523527
/// True if the current function should be marked mustprogress.
524528
bool FnIsMustProgress = false;
525529

@@ -568,6 +572,8 @@ class CodeGenFunction : public CodeGenTypeCache {
568572
llvm::Instruction *CurrentFuncletPad = nullptr;
569573

570574
class CallLifetimeEnd final : public EHScopeStack::Cleanup {
575+
bool isRedundantBeforeReturn() override { return true; }
576+
571577
llvm::Value *Addr;
572578
llvm::Value *Size;
573579

@@ -3913,12 +3919,14 @@ class CodeGenFunction : public CodeGenTypeCache {
39133919
/// LLVM arguments and the types they were derived from.
39143920
RValue EmitCall(const CGFunctionInfo &CallInfo, const CGCallee &Callee,
39153921
ReturnValueSlot ReturnValue, const CallArgList &Args,
3916-
llvm::CallBase **callOrInvoke, SourceLocation Loc);
3922+
llvm::CallBase **callOrInvoke, bool IsMustTail,
3923+
SourceLocation Loc);
39173924
RValue EmitCall(const CGFunctionInfo &CallInfo, const CGCallee &Callee,
39183925
ReturnValueSlot ReturnValue, const CallArgList &Args,
3919-
llvm::CallBase **callOrInvoke = nullptr) {
3926+
llvm::CallBase **callOrInvoke = nullptr,
3927+
bool IsMustTail = false) {
39203928
return EmitCall(CallInfo, Callee, ReturnValue, Args, callOrInvoke,
3921-
SourceLocation());
3929+
IsMustTail, SourceLocation());
39223930
}
39233931
RValue EmitCall(QualType FnType, const CGCallee &Callee, const CallExpr *E,
39243932
ReturnValueSlot ReturnValue, llvm::Value *Chain = nullptr);

0 commit comments

Comments
 (0)