[clang] [CIR] Implement most of 'musttail' statement attr (PR #213154)

Erich Keane via cfe-commits cfe-commits at lists.llvm.org
Thu Jul 30 14:48:04 PDT 2026


https://github.com/erichkeane updated https://github.com/llvm/llvm-project/pull/213154

>From f688d1a9a794122c1e013695501c82bdf4ca5bf6 Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Thu, 30 Jul 2026 09:23:29 -0700
Subject: [PATCH] [CIR] Implement most of 'musttail' statement attr

This implements the basics, and leaves NYI in a few places (particularly
those that require cleanup calculations).  There IS some additional work
that needs to be done when the ABI work is put in place as well, and the
thunk 'tail' hint isn't implemented either, and is left as a
missing-feature.

This is necessary for compiling the bytecode interpreter in Clang in a
few places during self-build.
---
 clang/include/clang/CIR/MissingFeatures.h |   2 +-
 clang/lib/CIR/CodeGen/CIRGenAtomic.cpp    |   3 +-
 clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp    |   2 +-
 clang/lib/CIR/CodeGen/CIRGenCall.cpp      |  38 +++-
 clang/lib/CIR/CodeGen/CIRGenClass.cpp     |   6 +-
 clang/lib/CIR/CodeGen/CIRGenExpr.cpp      |   3 +-
 clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp   |  17 +-
 clang/lib/CIR/CodeGen/CIRGenFunction.h    |  10 +-
 clang/lib/CIR/CodeGen/CIRGenStmt.cpp      |  13 +-
 clang/lib/CIR/CodeGen/CIRGenVTables.cpp   |   4 +-
 clang/test/CIR/CodeGen/attr-musttail.cpp  | 238 ++++++++++++++++++++++
 11 files changed, 312 insertions(+), 24 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/attr-musttail.cpp

diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index b7d545317037f..02475b70c5dcd 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -91,7 +91,7 @@ struct MissingFeatures {
   static bool opCallReturn() { return false; }
   static bool opCallArgEvaluationOrder() { return false; }
   static bool opCallCallConv() { return false; }
-  static bool opCallMustTail() { return false; }
+  static bool opCallThunkTailHint() { return false; }
   static bool opCallInAlloca() { return false; }
   static bool opCallAttrs() { return false; }
   static bool opCallSurroundingTry() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenAtomic.cpp b/clang/lib/CIR/CodeGen/CIRGenAtomic.cpp
index a305c135539e9..4a028e0be34f7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenAtomic.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenAtomic.cpp
@@ -1170,7 +1170,8 @@ static RValue emitAtomicLibCall(CIRGenFunction &cgf, llvm::StringRef funcName,
 
   cir::FuncOp fn = cgf.cgm.createRuntimeFunction(fnTy, funcName, fnAttrs);
   auto callee = CIRGenCallee::forDirect(fn);
-  return cgf.emitCall(fnInfo, callee, ReturnValueSlot(), args);
+  return cgf.emitCall(fnInfo, callee, ReturnValueSlot(), args,
+                      /*isMustTail=*/false);
 }
 
 static RValue emitLibCallForAtomicExpr(CIRGenFunction &cgf, AtomicExpr *e,
diff --git a/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp b/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp
index 9aa6bffbd4db7..2288c8bf5a818 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCUDANV.cpp
@@ -312,7 +312,7 @@ void CIRGenNVCUDARuntime::emitDeviceStubBodyNew(CIRGenFunction &cgf,
   const CIRGenFunctionInfo &callInfo =
       cgm.getTypes().arrangeFunctionDeclaration(cudaLaunchKernelFD);
   cgf.emitCall(callInfo, CIRGenCallee::forDirect(cudaKernelLauncherFn),
-               ReturnValueSlot(), launchArgs);
+               ReturnValueSlot(), launchArgs, /*isMustTail=*/false);
 
   if (cgm.getASTContext().getTargetInfo().getCXXABI().isMicrosoft() &&
       !cgf.getLangOpts().HIP)
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index c3065c8917924..28670cf31694a 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -1173,7 +1173,7 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
                                 ReturnValueSlot returnValue,
                                 const CallArgList &args,
                                 cir::CIRCallOpInterface *callOp,
-                                mlir::Location loc) {
+                                bool isMustTail, mlir::Location loc) {
   QualType retTy = funcInfo.getReturnType();
   cir::FuncType cirFuncTy = getTypes().getFunctionType(funcInfo);
 
@@ -1350,7 +1350,41 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
   if (callOp)
     *callOp = theCall;
 
-  assert(!cir::MissingFeatures::opCallMustTail());
+  if (isMustTail) {
+    // PPC/MIPS have some diagnostics for classic-codegen, but we don't support
+    // them yet.
+    const llvm::Triple &triple = getTarget().getTriple();
+    if (triple.isPPC() || triple.isMIPS()) {
+      cgm.errorNYI(mustTailCall->getBeginLoc(),
+                   "musttail call target legality checks");
+      return getUndefRValue(retTy);
+    }
+
+    // Musttail is required to return immediately. Classic codegen does some
+    // work here to go through the exception handling scopes to put them before
+    // the call (it seems?) since musttail must be the last op before the
+    // return.  For now, skip this so we an do it later.
+    if (ehStack.stable_begin() != prologueCleanupDepth) {
+      cgm.errorNYI(mustTailCall->getBeginLoc(),
+                   "musttail call that skips cleanups");
+      return getUndefRValue(retTy);
+    }
+
+    theCall->setAttr(cir::CIRDialect::getMustTailAttrName(),
+                     builder.getUnitAttr());
+
+    if (isa<cir::VoidType>(convertType(retTy)))
+      cir::ReturnOp::create(builder, loc);
+    else
+      cir::ReturnOp::create(builder, loc, theCall->getResult(0));
+
+    // Musttail must return immediately, so we just do that.  All of the below
+    // stuff is effectively UB if this is a musttail, so just do a return
+    // immediately.
+    builder.createBlock(builder.getBlock()->getParent());
+    return getUndefRValue(retTy);
+  }
+
   assert(!cir::MissingFeatures::opCallReturn());
 
   mlir::Type retCIRTy = convertType(retTy);
diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
index 2e50956e1c2ee..31e09320a43d6 100644
--- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp
@@ -958,7 +958,8 @@ void CIRGenFunction::emitForwardingCallToLambda(
   // Now emit our call.
   CIRGenCallee callee =
       CIRGenCallee::forDirect(calleePtr, GlobalDecl(callOperator));
-  RValue rv = emitCall(calleeFnInfo, callee, returnSlot, callArgs);
+  RValue rv = emitCall(calleeFnInfo, callee, returnSlot, callArgs,
+                       /*isMustTail=*/false);
 
   // Forward the returned value through the function's return slot.
   if (!resultType->isVoidType()) {
@@ -1555,7 +1556,8 @@ void CIRGenFunction::emitCXXConstructorCall(
       args, d, type, extraArgs.prefix, extraArgs.suffix, passPrototypeArgs);
   CIRGenCallee callee = CIRGenCallee::forDirect(calleePtr, GlobalDecl(d, type));
   cir::CIRCallOpInterface c;
-  emitCall(info, callee, ReturnValueSlot(), args, &c, getLoc(loc));
+  emitCall(info, callee, ReturnValueSlot(), args, &c, /*isMustTail=*/false,
+           getLoc(loc));
 
   if (cgm.getCodeGenOpts().OptimizationLevel != 0 && !crd->isDynamicClass() &&
       type != Ctor_Base && cgm.getCodeGenOpts().StrictVTablePointers)
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 1517de5200734..eef1cda1c90f9 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -2426,11 +2426,10 @@ RValue CIRGenFunction::emitCall(clang::QualType calleeTy,
 
   assert(!cir::MissingFeatures::opCallFnInfoOpts());
   assert(!cir::MissingFeatures::hip());
-  assert(!cir::MissingFeatures::opCallMustTail());
 
   cir::CIRCallOpInterface callOp;
   RValue callResult = emitCall(funcInfo, callee, returnValue, args, &callOp,
-                               getLoc(e->getExprLoc()));
+                               e == mustTailCall, getLoc(e->getExprLoc()));
 
   assert(!cir::MissingFeatures::generateDebugInfo());
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 0f0c5e6be13e9..b46fb8a93da42 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -118,10 +118,10 @@ CIRGenFunction::emitCXXMemberPointerCallExpr(const CXXMemberCallExpr *ce,
 
   // Build the call.
   CIRGenCallee callee(fpt, calleePtr.getDefiningOp());
-  assert(!cir::MissingFeatures::opCallMustTail());
   return emitCall(cgm.getTypes().arrangeCXXMethodCall(argsList, fpt, required,
                                                       /*PrefixSize=*/0),
-                  callee, returnValue, argsList, nullptr, loc);
+                  callee, returnValue, argsList, nullptr, ce == mustTailCall,
+                  loc);
 }
 
 RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
@@ -322,8 +322,8 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorCall(
       args, fpt, callInfo.reqArgs, callInfo.prefixSize);
   assert((ce || currSrcLoc) && "expected source location");
   mlir::Location loc = ce ? getLoc(ce->getExprLoc()) : *currSrcLoc;
-  assert(!cir::MissingFeatures::opCallMustTail());
-  return emitCall(fnInfo, callee, returnValue, args, nullptr, loc);
+  return emitCall(fnInfo, callee, returnValue, args, nullptr,
+                  ce && ce == mustTailCall, loc);
 }
 
 static void emitNullBaseClassInitialization(CIRGenFunction &cgf,
@@ -756,9 +756,9 @@ static RValue emitNewDeleteCall(CIRGenFunction &cgf,
   cir::FuncOp calleePtr = cgf.cgm.getAddrOfFunction(calleeDecl);
   CIRGenCallee callee =
       CIRGenCallee::forDirect(calleePtr, GlobalDecl(calleeDecl));
-  RValue rv =
-      cgf.emitCall(cgf.cgm.getTypes().arrangeFreeFunctionCall(args, calleeType),
-                   callee, ReturnValueSlot(), args, &callOrTryCall);
+  RValue rv = cgf.emitCall(
+      cgf.cgm.getTypes().arrangeFreeFunctionCall(args, calleeType), callee,
+      ReturnValueSlot(), args, /*isMustTail=*/false, &callOrTryCall);
 
   /// C++1y [expr.new]p10:
   ///   [In a new-expression,] an implementation is allowed to omit a call
@@ -1367,9 +1367,8 @@ RValue CIRGenFunction::emitCXXDestructorCall(
   commonBuildCXXMemberOrOperatorCall(*this, dtorDecl, thisVal, implicitParam,
                                      implicitParamTy, ce, args, nullptr);
   assert((ce || dtor.getDecl()) && "expected source location provider");
-  assert(!cir::MissingFeatures::opCallMustTail());
   return emitCall(cgm.getTypes().arrangeCXXStructorDeclaration(dtor), callee,
-                  ReturnValueSlot(), args, nullptr,
+                  ReturnValueSlot(), args, nullptr, ce && ce == mustTailCall,
                   ce ? getLoc(ce->getExprLoc())
                      : getLoc(dtor.getDecl()->getSourceRange()));
 }
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index ed53fc38f6930..4ba3ee59f49b0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -586,6 +586,10 @@ class CIRGenFunction : public CIRGenTypeCache {
     }
   };
 
+  // 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;
+
   struct VlaSizePair {
     mlir::Value numElts;
     QualType type;
@@ -1786,14 +1790,14 @@ class CIRGenFunction : public CIRGenTypeCache {
   RValue emitCall(const CIRGenFunctionInfo &funcInfo,
                   const CIRGenCallee &callee, ReturnValueSlot returnValue,
                   const CallArgList &args, cir::CIRCallOpInterface *callOp,
-                  mlir::Location loc);
+                  bool isMustTail, mlir::Location loc);
   RValue emitCall(const CIRGenFunctionInfo &funcInfo,
                   const CIRGenCallee &callee, ReturnValueSlot returnValue,
-                  const CallArgList &args,
+                  const CallArgList &args, bool isMustTail,
                   cir::CIRCallOpInterface *callOrTryCall = nullptr) {
     assert(currSrcLoc && "source location must have been set");
     return emitCall(funcInfo, callee, returnValue, args, callOrTryCall,
-                    *currSrcLoc);
+                    isMustTail, *currSrcLoc);
   }
 
   RValue emitCall(clang::QualType calleeTy, const CIRGenCallee &callee,
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index 21fb88b667e9b..d34769200dbfd 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -21,6 +21,7 @@
 #include "clang/AST/StmtOpenACC.h"
 #include "clang/AST/StmtOpenMP.h"
 #include "clang/CIR/MissingFeatures.h"
+#include "llvm/Support/SaveAndRestore.h"
 
 using namespace clang;
 using namespace clang::CIRGen;
@@ -87,6 +88,9 @@ mlir::LogicalResult CIRGenFunction::emitCompoundStmtWithoutScope(
 
 mlir::LogicalResult
 CIRGenFunction::emitAttributedStmt(const AttributedStmt &s) {
+
+  const CallExpr *musttail = nullptr;
+
   for (const Attr *attr : s.getAttrs()) {
     switch (attr->getKind()) {
     default:
@@ -95,12 +99,17 @@ CIRGenFunction::emitAttributedStmt(const AttributedStmt &s) {
     case attr::NoInline:
     case attr::AlwaysInline:
     case attr::NoConvergent:
-    case attr::MustTail:
     case attr::Atomic:
     case attr::HLSLControlFlowHint:
       cgm.errorNYI(s.getSourceRange(),
                    "Unimplemented statement attribute: ", attr->getKind());
       break;
+    case attr::MustTail: {
+      const Stmt *sub = s.getSubStmt();
+      const ReturnStmt *ret = cast<ReturnStmt>(sub);
+      musttail = cast<CallExpr>(ret->getRetValue()->IgnoreParens());
+      break;
+    }
     case attr::CXXAssume: {
       const Expr *assumptionExpr = cast<CXXAssumeAttr>(attr)->getAssumption();
       if (getLangOpts().CXXAssumptions && builder.getInsertionBlock() &&
@@ -114,6 +123,8 @@ CIRGenFunction::emitAttributedStmt(const AttributedStmt &s) {
     }
   }
 
+  SaveAndRestore save_musttail(mustTailCall, musttail);
+
   return emitStmt(s.getSubStmt(), /*useCurrentScope=*/true, s.getAttrs());
 }
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
index 03d777cf7363a..a4742cb239f21 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
@@ -744,13 +744,13 @@ void CIRGenFunction::emitCallAndReturnForThunk(cir::FuncOp callee,
   CIRGenCallee cirCallee = CIRGenCallee::forDirect(callee, curGD);
   mlir::Location loc = builder.getUnknownLoc();
   RValue rv = emitCall(*curFnInfo, cirCallee, slot, callArgs,
-                       /*callOrTryCall=*/nullptr, loc);
+                       /*callOrTryCall=*/nullptr, /*isMustTail=*/false, loc);
 
   // Consider return adjustment if we have ThunkInfo.
   if (thunk && !thunk->Return.isEmpty())
     rv = performReturnAdjustment(*this, resultType, rv, *thunk);
   else
-    assert(!cir::MissingFeatures::opCallMustTail());
+    assert(!cir::MissingFeatures::opCallThunkTailHint());
 
   // Emit return.  For aggregate returns the call has already written the
   // result through the slot bound to returnValue above; emit the
diff --git a/clang/test/CIR/CodeGen/attr-musttail.cpp b/clang/test/CIR/CodeGen/attr-musttail.cpp
new file mode 100644
index 0000000000000..0054dec34c1bf
--- /dev/null
+++ b/clang/test/CIR/CodeGen/attr-musttail.cpp
@@ -0,0 +1,238 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
+
+int Bar(int);
+int Baz(int);
+
+int Func1(int x) {
+  if (x)
+    [[clang::musttail]] return Bar(x);
+  else
+    [[clang::musttail]] return Baz(x);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z5Func1i
+// CIR:         %[[R1:.+]] = cir.call @_Z3Bari(%{{.+}}) musttail : (!s32i{{.*}}) -> (!s32i{{.*}})
+// CIR-NEXT:    cir.return %[[R1]] : !s32i
+// CIR:         %[[R2:.+]] = cir.call @_Z3Bazi(%{{.+}}) musttail : (!s32i{{.*}}) -> (!s32i{{.*}})
+// CIR-NEXT:    cir.return %[[R2]] : !s32i
+
+// LLVM-LABEL: define{{.*}} i32 @_Z5Func1i
+// LLVM:         %[[C1:.+]] = musttail call{{.*}} i32 @_Z3Bari(i32{{.*}})
+// LLVM-NEXT:    ret i32 %[[C1]]
+// LLVM:         %[[C2:.+]] = musttail call{{.*}} i32 @_Z3Bazi(i32{{.*}})
+// LLVM-NEXT:    ret i32 %[[C2]]
+
+int Nested(int x) {
+  [[clang::musttail]] return Bar(Bar(x));
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z6Nestedi
+// CIR:         %[[INNER:.+]] = cir.call @_Z3Bari(%{{.+}}) : (!s32i{{.*}}) -> (!s32i{{.*}})
+// CIR-NEXT:    %[[OUTER:.+]] = cir.call @_Z3Bari(%[[INNER]]) musttail : (!s32i{{.*}}) -> (!s32i{{.*}})
+// CIR-NEXT:    cir.return %[[OUTER]] : !s32i
+
+// LLVM-LABEL: define{{.*}} i32 @_Z6Nestedi
+// LLVM:         %[[INNER:.+]] = call{{.*}} i32 @_Z3Bari
+// LLVM:         %[[OUTER:.+]] = musttail call{{.*}} i32 @_Z3Bari(i32{{.*}} %[[INNER]])
+// LLVM-NEXT:    ret i32 %[[OUTER]]
+
+int Scoped(int x) {
+  { [[clang::musttail]] return Bar(x); }
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z6Scopedi
+// CIR:         %[[R:.+]] = cir.call @_Z3Bari(%{{.+}}) musttail : (!s32i{{.*}}) -> (!s32i{{.*}})
+// CIR-NEXT:    cir.return %[[R]] : !s32i
+
+// LLVM-LABEL: define{{.*}} i32 @_Z6Scopedi
+// LLVM:         %[[R:.+]] = musttail call{{.*}} i32 @_Z3Bari
+// LLVM-NEXT:    ret i32 %[[R]]
+
+void ReturnsVoid();
+void VoidTail() {
+  [[clang::musttail]] return ReturnsVoid();
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z8VoidTailv
+// CIR:         cir.call @_Z11ReturnsVoidv() musttail : () -> ()
+// CIR-NEXT:    cir.return
+
+// LLVM-LABEL: define{{.*}} void @_Z8VoidTailv
+// LLVM:         musttail call void @_Z11ReturnsVoidv()
+// LLVM-NEXT:    ret void
+
+void IndirectLocal(int x) {
+  void (*p)(int) = nullptr;
+  [[clang::musttail]] return p(x);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z13IndirectLocali
+// CIR:         cir.call %{{.+}}(%{{.+}}) musttail : (!cir.ptr<!cir.func<(!s32i)>>, !s32i{{.*}}) -> ()
+// CIR-NEXT:    cir.return
+
+// LLVM-LABEL: define{{.*}} void @_Z13IndirectLocali
+// LLVM:         musttail call void %{{.+}}(i32{{.*}})
+// LLVM-NEXT:    ret void
+
+struct Data {
+  int (*fptr)(Data *);
+};
+int IndirectField(Data *data) {
+  [[clang::musttail]] return data->fptr(data);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z13IndirectFieldP4Data
+// CIR:         %[[R:.+]] = cir.call %{{.+}}(%{{.+}}) musttail : (!cir.ptr<{{.*}}>, !cir.ptr<!rec_Data>{{.*}}) -> (!s32i{{.*}})
+// CIR-NEXT:    cir.return %[[R]] : !s32i
+
+// LLVM-LABEL: define{{.*}} i32 @_Z13IndirectFieldP4Data
+// LLVM:         %[[R:.+]] = musttail call{{.*}} i32 %{{.+}}(ptr{{.*}})
+// LLVM-NEXT:    ret i32 %[[R]]
+
+struct Foo {
+  int MemberFunction(int x);
+  static int StaticMethod(int x);
+  int TailFrom(int x);
+  int TailFrom2(int x);
+};
+
+int Foo::TailFrom(int x) {
+  [[clang::musttail]] return MemberFunction(x);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_ZN3Foo8TailFromEi
+// CIR:         %[[R:.+]] = cir.call @_ZN3Foo14MemberFunctionEi(%{{.+}}, %{{.+}}) musttail : (!cir.ptr<!rec_Foo>{{.*}}, !s32i{{.*}}) -> (!s32i{{.*}})
+// CIR-NEXT:    cir.return %[[R]] : !s32i
+
+// LLVM-LABEL: define{{.*}} i32 @_ZN3Foo8TailFromEi
+// LLVM:         %[[R:.+]] = musttail call{{.*}} i32 @_ZN3Foo14MemberFunctionEi(ptr{{.*}}, i32{{.*}})
+// LLVM-NEXT:    ret i32 %[[R]]
+
+int StaticViaClass(int x) {
+  [[clang::musttail]] return Foo::StaticMethod(x);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z14StaticViaClassi
+// CIR:         %[[R:.+]] = cir.call @_ZN3Foo12StaticMethodEi(%{{.+}}) musttail : (!s32i{{.*}}) -> (!s32i{{.*}})
+// CIR-NEXT:    cir.return %[[R]] : !s32i
+
+// LLVM-LABEL: define{{.*}} i32 @_Z14StaticViaClassi
+// LLVM:         %[[R:.+]] = musttail call{{.*}} i32 @_ZN3Foo12StaticMethodEi(i32{{.*}})
+// LLVM-NEXT:    ret i32 %[[R]]
+
+int StaticViaObject(int x) {
+  Foo foo;
+  [[clang::musttail]] return foo.StaticMethod(x);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z15StaticViaObjecti
+// CIR:         %[[R:.+]] = cir.call @_ZN3Foo12StaticMethodEi(%{{.+}}) musttail : (!s32i{{.*}}) -> (!s32i{{.*}})
+// CIR-NEXT:    cir.return %[[R]] : !s32i
+
+// LLVM-LABEL: define{{.*}} i32 @_Z15StaticViaObjecti
+// LLVM:         %[[R:.+]] = musttail call{{.*}} i32 @_ZN3Foo12StaticMethodEi(i32{{.*}})
+// LLVM-NEXT:    ret i32 %[[R]]
+
+template <class T> int TplBody(int x) {
+  T t;
+  [[clang::musttail]] return Bar(x);
+}
+int InstBody(int x) { return TplBody<int>(x); }
+
+// CIR-LABEL: cir.func{{.*}} @_Z7TplBodyIiEii
+// CIR:         %[[R:.+]] = cir.call @_Z3Bari(%{{.+}}) musttail : (!s32i{{.*}}) -> (!s32i{{.*}})
+// CIR-NEXT:    cir.return %[[R]] : !s32i
+
+// LLVM-LABEL: define{{.*}} i32 @_Z7TplBodyIiEii
+// LLVM:         %[[R:.+]] = musttail call{{.*}} i32 @_Z3Bari
+// LLVM-NEXT:    ret i32 %[[R]]
+
+template <class T> T TplQualified(T x) {
+  [[clang::musttail]] return ::Bar(x);
+}
+int InstQualified(int x) { return TplQualified<int>(x); }
+
+// CIR-LABEL: cir.func{{.*}} @_Z12TplQualifiedIiET_S0_
+// CIR:         %[[R:.+]] = cir.call @_Z3Bari(%{{.+}}) musttail : (!s32i{{.*}}) -> (!s32i{{.*}})
+// CIR-NEXT:    cir.return %[[R]] : !s32i
+
+// LLVM-LABEL: define{{.*}} i32 @_Z12TplQualifiedIiET_S0_
+// LLVM:         %[[R:.+]] = musttail call{{.*}} i32 @_Z3Bari
+// LLVM-NEXT:    ret i32 %[[R]]
+
+struct TrivialDtor {};
+int ReturnsInt(int x);
+int TrivialLocal(int x) {
+  TrivialDtor foo;
+  [[clang::musttail]] return ReturnsInt(x);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z12TrivialLocali
+// CIR:         %[[R:.+]] = cir.call @_Z10ReturnsInti(%{{.+}}) musttail : (!s32i{{.*}}) -> (!s32i{{.*}})
+// CIR-NEXT:    cir.return %[[R]] : !s32i
+
+// LLVM-LABEL: define{{.*}} i32 @_Z12TrivialLocali
+// LLVM:         %[[R:.+]] = musttail call{{.*}} i32 @_Z10ReturnsInti
+// LLVM-NEXT:    ret i32 %[[R]]
+
+// FIXME(cir): This requires a cleanup scope of some sort, but is NYI.
+//   int VlaScope(int x) {
+//     int vla[x];
+//     [[clang::musttail]] return Bar(x);
+//   }
+
+int (Foo::*pmf)(int);
+int Foo::TailFrom2(int x) {
+  [[clang::musttail]] return (this->*pmf)(x);
+}
+
+// CIR-LABEL: cir.func{{.*}} @_ZN3Foo9TailFrom2Ei
+// CIR:         %[[R:.+]] = cir.call %{{.+}}(%{{.+}}, %{{.+}}) musttail : (!cir.ptr<{{.*}}>, !cir.ptr<!void>{{.*}}, !s32i{{.*}}) -> (!s32i{{.*}})
+// CIR-NEXT:    cir.return %[[R]] : !s32i
+
+// LLVM-LABEL: define{{.*}} i32 @_ZN3Foo9TailFrom2Ei
+// LLVM:         %[[R:.+]] = musttail call{{.*}} i32 %{{.+}}(ptr{{.*}}, i32{{.*}})
+// LLVM-NEXT:    ret i32 %[[R]]
+
+struct V {
+  virtual void t();
+  virtual void f();
+};
+void V::f() {
+  [[clang::musttail]] return t();
+}
+
+// CIR-LABEL: cir.func{{.*}} @_ZN1V1fEv
+// CIR:         cir.call %{{.+}}(%{{.+}}) musttail : (!cir.ptr<{{.*}}>, !cir.ptr<!rec_V>{{.*}}) -> ()
+// CIR-NEXT:    cir.return
+
+// LLVM-LABEL: define{{.*}} void @_ZN1V1fEv
+// LLVM:         musttail call void %{{.+}}(ptr{{.*}})
+// LLVM-NEXT:    ret void
+
+// FIXME(cir): Another one that requires cleanups.
+//   struct NT { NT(const NT &); char d[32]; };
+//   NT RCBV();
+//   NT SretTail() {
+//     [[clang::musttail]] return RCBV();
+//   }
+
+int Lam() {
+  auto l = []() { return 12; };
+  [[clang::musttail]] return (+l)();
+}
+
+// CIR-LABEL: cir.func{{.*}} @_Z3Lamv
+// CIR:         %{{.+}} = cir.call @_ZZ3LamvENK3$_0cvPFivEEv(%{{.+}}){{.*}} : (!cir.ptr<{{.*}}>{{.*}}) -> (!cir.ptr<!cir.func<() -> !s32i>>{{.*}})
+// CIR:         %[[R:.+]] = cir.call %{{.+}}() musttail : (!cir.ptr<!cir.func<() -> !s32i>>) -> (!s32i{{.*}})
+// CIR-NEXT:    cir.return %[[R]] : !s32i
+
+// LLVM-LABEL: define{{.*}} i32 @_Z3Lamv
+// LLVM:         %[[R:.+]] = musttail call{{.*}} i32 %{{.+}}()
+// LLVM-NEXT:    ret i32 %[[R]]
+



More information about the cfe-commits mailing list