Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
cf30821
[Clang] Forward incoming Indirect parameters across musttail calls
xroche May 23, 2026
a1c3fd3
[Clang][NFC] Trim comments on musttail Indirect forwarding helper
xroche May 23, 2026
11cdbbf
[Clang] Address review on musttail Indirect forwarding
xroche May 23, 2026
4359083
[Clang] Extend musttail Indirect forwarding to C++ trivial-copy args
xroche May 23, 2026
9b1aa8b
[Clang][test] Mirror C test cases in musttail-indirect-arg.cpp
xroche May 23, 2026
99dfa79
[Clang] Switch musttail Indirect to a two-phase general algorithm
xroche May 26, 2026
88607e8
[Clang][test] Strengthen P3 and P17 to catch in-place-write regression
xroche May 26, 2026
2dd4ce8
Merge branch 'main' into musttail-byval-temp-fix
xroche May 26, 2026
a27213a
Merge branch 'main' into musttail-byval-temp-fix
xroche May 27, 2026
d12920e
[Clang] Diagnose musttail Indirect args with no addressable source
xroche Jun 5, 2026
7231b68
Merge remote-tracking branch 'origin/main' into musttail-byval-temp-fix
xroche Jun 5, 2026
f008bf5
[Clang][test] Make musttail-indirect-arg scratch checks SROA-robust
xroche Jun 6, 2026
1248313
Merge remote-tracking branch 'origin/main' into musttail-byval-temp-fix
xroche Jun 7, 2026
40a7edf
Merge branch 'main' into musttail-byval-temp-fix
xroche Jun 22, 2026
6964a03
Merge branch 'main' into musttail-byval-temp-fix
xroche Jun 25, 2026
3a58836
[Clang][test] Pin slot operands and swap data-flow in musttail-indire…
xroche Jun 26, 2026
6f56dba
[Clang] Drop redundant freeze of forwarded musttail Indirect pointer
xroche Jun 27, 2026
cb7efcb
[Clang] Forward trivially-copyable by-value args beyond musttail
xroche Jun 27, 2026
3a8db70
[Clang][test] Cover musttail Indirect arg with no in-memory source
xroche Jun 27, 2026
2a770e5
[Clang] Exclude CUDA surface/texture types from by-value arg forwarding
xroche Jun 27, 2026
5be3d32
[Clang] Exclude ObjC GC object-member records from by-value arg forwa…
xroche Jun 27, 2026
9905d11
Merge remote-tracking branch 'origin/main' into musttail-byval-temp-fix
xroche Jul 2, 2026
8c5c3be
[Clang] Re-gate trivial-copy arg forwarding to musttail calls
xroche Jul 3, 2026
beda9ba
[Clang] Accept any same-type glvalue as musttail forwarding source
xroche Jul 3, 2026
d6fe44f
[Clang][test] Note the unsupported musttail Indirect case is liftable
xroche Jul 3, 2026
c3ba863
[Clang] Restrict musttail forwarding sources to pure lvalue chains
xroche Jul 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticCommonKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,9 @@ def err_aix_musttail_unsupported: Error<
"'musttail' attribute is not supported on AIX">;
def err_musttail_noexcept_mismatch: Error<
"'musttail' in a noexcept function requires a noexcept callee">;
def err_musttail_unsupported_indirect_arg: Error<
"'musttail' call requires passing an argument by reference, but the source "
"does not have an addressable storage and would alias the caller's frame">;

// Source manager
def err_cannot_open_file : Error<"cannot open file '%0': %1">, DefaultFatal;
Expand Down
110 changes: 110 additions & 0 deletions clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5253,6 +5253,42 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
}
}

// C++ analog of the CK_LValueToRValue case above: a trivial copy/move

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should split this off into a followup patch, so it's easier to bisect if there are any issues.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Humm yes. I could keep the musttail-gated forwarding here (the musttail fix needs it for C++ arguments) and move the "forward for all calls" generalization, with its test churn and the exclusion-list question, to a follow-up PR. WHat do you think ?

// constructor from a variable forwards the source LValue instead of
// materializing an agg.tmp. EmitCall makes the real copy at the
// Indirect/byval boundary. Under musttail this also keeps the value in
// storage that survives the tail call. Types whose copy is not a plain
// memcpy in EmitAggregateCopy are excluded, since forwarding would bypass
// that special copy: CUDA surface/texture (lowered to a handle) and, under
// ObjC GC, records with object members (need a write barrier).
if (HasAggregateEvalKind && type->isRecordType() &&
type.isTriviallyCopyableType(getContext()) &&
!type->isCUDADeviceBuiltinSurfaceType() &&
!type->isCUDADeviceBuiltinTextureType() &&
!(getLangOpts().getGC() != LangOptions::NonGC &&
type->getAsRecordDecl()->hasObjectMember())) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little worried this is going to fall out of date... does this list just come from digging through CodeGenFunction::EmitAggregateCopy? Is there anywhere else that uses a similar check?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I copied EmitAggregateCopy's special cases, and I found no existing shared check for "this copy is a plain memcpy". I will factorize this, the risks of drift is concerning.

A "cleaner" alternative would be to route the argument copy through EmitAggregateCopy itself so no list is needed, but might be more complex ?

if (const auto *CCE = dyn_cast<CXXConstructExpr>(E)) {
const CXXConstructorDecl *Ctor = CCE->getConstructor();
if (Ctor->isCopyOrMoveConstructor() && Ctor->isTrivial() &&
CCE->getNumArgs() == 1) {
const Expr *Source = CCE->getArg(0)->IgnoreParenImpCasts();
Comment thread
xroche marked this conversation as resolved.
Outdated
// Same-type guard: a stripped derived-to-base cast would forward a
// derived lvalue into a base-typed slot and slice at a wrong offset.
// Exclude hlsl_constant sources, as the CK_LValueToRValue path does.
if (const auto *DRE = dyn_cast<DeclRefExpr>(Source);
DRE && isa<VarDecl>(DRE->getDecl()) &&
Source->getType().getAddressSpace() != LangAS::hlsl_constant &&
getContext().hasSameUnqualifiedType(Source->getType(), type)) {
LValue L = EmitLValue(DRE);
if (L.isSimple()) {
args.addUncopiedAggregate(L, type);
return;
}
}
}
}
}

args.add(EmitAnyExprToTemp(E), type);
}

Expand Down Expand Up @@ -5562,6 +5598,17 @@ static unsigned getMaxVectorWidth(const llvm::Type *Ty) {
return MaxVectorWidth;
}

/// Peel one AddrSpaceCastInst from \p SrcPtr. EmitParmDecl wraps incoming
/// Indirect params via address-space cast on NVPTX/AMDGPU/SPIR, so peeling
/// exposes the underlying llvm::Argument when the source IS a forwarded
/// incoming parameter. Loads are NOT unwrapped: a load through a local
/// alloca means the source is a local.
static llvm::Value *peelAddrSpaceCast(llvm::Value *SrcPtr) {
if (auto *ASC = llvm::dyn_cast<llvm::AddrSpaceCastInst>(SrcPtr))
return ASC->getOperand(0);
return SrcPtr;
}

RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
const CGCallee &Callee,
ReturnValueSlot ReturnValue,
Expand Down Expand Up @@ -5685,6 +5732,17 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
// markers that need to be ended right after the call.
SmallVector<CallLifetimeEnd, 2> CallLifetimeEndAfterCall;

// Deferred Phase-2 writes for musttail Indirect args. Splitting reads
// (in the per-arg loop) from writes (after the loop) lets permutations
// like C(b, a) land correctly: all sources are captured into scratches
// before any incoming-param destination is overwritten.
struct MustTailIndirectCopy {
LValue Scratch;
LValue Dst;
QualType Ty;
};
llvm::SmallVector<MustTailIndirectCopy, 4> MustTailIndirectCopies;

// Translate all of the arguments as necessary to match the IR lowering.
assert(CallInfo.arg_size() == CallArgs.size() &&
"Mismatch between function signature & arguments.");
Expand Down Expand Up @@ -5757,6 +5815,51 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
case ABIArgInfo::Indirect:
case ABIArgInfo::IndirectAliased: {
assert(NumIRArgs == 1);

// Musttail Indirect: route via the matching incoming parameter.
// Prototype-match (Verifier V5/V6/V7) makes CurFn->arg_begin()+
// FirstIRArg a distinct destination for this slot that lives in the
// caller's caller's frame and survives the tail call. To handle
// permutations safely, the source value is captured into a scratch
// alloca here (Phase 1); the write to the incoming-param destination
// is deferred until after all sources have been read (Phase 2 below).
// IndirectAliased uses the existing fallback (different source-AS).
if (IsMustTail && ArgInfo.isIndirect()) {
llvm::Argument *IncomingArg = CurFn->arg_begin() + FirstIRArg;
llvm::Value *Dst = IncomingArg;
Address SrcAddr = Address::invalid();
if (I->hasLValue())
SrcAddr = I->getKnownLValue().getAddress();
else if (I->getKnownRValue().isAggregate())
SrcAddr = I->getKnownRValue().getAggregateAddress();
if (SrcAddr.isValid()) {
llvm::Value *Src = peelAddrSpaceCast(SrcAddr.emitRawPointer(*this));
if (Src != Dst) {
CharUnits Align = ArgInfo.getIndirectAlign();
QualType Ty = I->Ty;
llvm::Type *ElemTy = ConvertTypeForMem(Ty);
RawAddress Scratch =
CreateMemTempWithoutCast(Ty, Align, "musttail.copy");
LValue ScratchLV = MakeAddrLValue(Scratch, Ty);
LValue SrcLV = MakeAddrLValue(SrcAddr, Ty);
EmitAggregateCopy(ScratchLV, SrcLV, Ty,
AggValueSlot::DoesNotOverlap);
LValue DstLV = MakeAddrLValue(Address(Dst, ElemTy, Align), Ty);
MustTailIndirectCopies.push_back({ScratchLV, DstLV, Ty});
}
// No freeze: Dst is an incoming parameter pointer, never poison.
IRCallArgs[FirstIRArg] = Dst;
break;
}
// No addressable source for this Indirect arg (rare; e.g. a scalar
// RValue the ABI classifies as Indirect). The fall-through below
// would create a current-frame byval-temp that dangles past the
// tail-call teardown. Refuse cleanly; codegen continues producing
// IR but the error prevents it from reaching the backend.
Comment thread
xroche marked this conversation as resolved.
CGM.getDiags().Report(MustTailCall->getBeginLoc(),
diag::err_musttail_unsupported_indirect_arg);
}

if (I->isAggregate()) {
// We want to avoid creating an unnecessary temporary+copy here;
// however, we need one in three cases:
Expand Down Expand Up @@ -6083,6 +6186,13 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
}
}

// Phase 2 of the musttail Indirect-arg copy: flush each captured scratch
// into its incoming-param destination. Phase 1 has read every source, so
// permutations like C(b, a) land the right value in each slot.
for (const auto &Copy : MustTailIndirectCopies)
EmitAggregateCopy(Copy.Dst, Copy.Scratch, Copy.Ty,
AggValueSlot::DoesNotOverlap);

const CGCallee &ConcreteCallee = Callee.prepareConcreteCallee(*this);
llvm::Value *CalleePtr = ConcreteCallee.getFunctionPointer();

Expand Down
24 changes: 6 additions & 18 deletions clang/test/CodeGen/AArch64/struct-coerce-using-ptr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -654,9 +654,7 @@ void Tpaddrspace(Spaddrspace s) { *s.x = 1; }
// CHECK-A64-SAME: ) #[[ATTR0]] {
// CHECK-A64-NEXT: [[ENTRY:.*:]]
// CHECK-A64-NEXT: [[S:%.*]] = alloca [[STRUCT_SPADDRSPACE:%.*]], align 8
// CHECK-A64-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_SPADDRSPACE]], align 8
// CHECK-A64-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[AGG_TMP]], ptr align 8 [[S]], i64 8, i1 false)
// CHECK-A64-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_SPADDRSPACE]], ptr [[AGG_TMP]], i32 0, i32 0
// CHECK-A64-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_SPADDRSPACE]], ptr [[S]], i32 0, i32 0
// CHECK-A64-NEXT: [[TMP0:%.*]] = load ptr addrspace(100), ptr [[COERCE_DIVE]], align 8
// CHECK-A64-NEXT: [[COERCE_VAL_PI:%.*]] = ptrtoint ptr addrspace(100) [[TMP0]] to i64
// CHECK-A64-NEXT: call void @_Z11Tpaddrspace11Spaddrspace(i64 [[COERCE_VAL_PI]])
Expand All @@ -666,9 +664,7 @@ void Tpaddrspace(Spaddrspace s) { *s.x = 1; }
// CHECK-A64_32-SAME: ) #[[ATTR0]] {
// CHECK-A64_32-NEXT: [[ENTRY:.*:]]
// CHECK-A64_32-NEXT: [[S:%.*]] = alloca [[STRUCT_SPADDRSPACE:%.*]], align 4
// CHECK-A64_32-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_SPADDRSPACE]], align 4
// CHECK-A64_32-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[AGG_TMP]], ptr align 4 [[S]], i32 4, i1 false)
// CHECK-A64_32-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_SPADDRSPACE]], ptr [[AGG_TMP]], i32 0, i32 0
// CHECK-A64_32-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_SPADDRSPACE]], ptr [[S]], i32 0, i32 0
// CHECK-A64_32-NEXT: [[TMP0:%.*]] = load ptr addrspace(100), ptr [[COERCE_DIVE]], align 4
// CHECK-A64_32-NEXT: [[COERCE_VAL_PI:%.*]] = ptrtoint ptr addrspace(100) [[TMP0]] to i32
// CHECK-A64_32-NEXT: [[COERCE_VAL_II:%.*]] = zext i32 [[COERCE_VAL_PI]] to i64
Expand Down Expand Up @@ -709,9 +705,7 @@ void Tp2addrspace(Sp2addrspace s) { *s.x[0] = 1; }
// CHECK-A64-SAME: ) #[[ATTR0]] {
// CHECK-A64-NEXT: [[ENTRY:.*:]]
// CHECK-A64-NEXT: [[S:%.*]] = alloca [[STRUCT_SP2ADDRSPACE:%.*]], align 8
// CHECK-A64-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_SP2ADDRSPACE]], align 8
// CHECK-A64-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[AGG_TMP]], ptr align 8 [[S]], i64 16, i1 false)
// CHECK-A64-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_SP2ADDRSPACE]], ptr [[AGG_TMP]], i32 0, i32 0
// CHECK-A64-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_SP2ADDRSPACE]], ptr [[S]], i32 0, i32 0
// CHECK-A64-NEXT: [[TMP0:%.*]] = load [2 x i64], ptr [[COERCE_DIVE]], align 8
// CHECK-A64-NEXT: call void @_Z12Tp2addrspace12Sp2addrspace([2 x i64] [[TMP0]])
// CHECK-A64-NEXT: ret void
Expand All @@ -720,9 +714,7 @@ void Tp2addrspace(Sp2addrspace s) { *s.x[0] = 1; }
// CHECK-A64_32-SAME: ) #[[ATTR0]] {
// CHECK-A64_32-NEXT: [[ENTRY:.*:]]
// CHECK-A64_32-NEXT: [[S:%.*]] = alloca [[STRUCT_SP2ADDRSPACE:%.*]], align 4
// CHECK-A64_32-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_SP2ADDRSPACE]], align 4
// CHECK-A64_32-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[AGG_TMP]], ptr align 4 [[S]], i32 8, i1 false)
// CHECK-A64_32-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_SP2ADDRSPACE]], ptr [[AGG_TMP]], i32 0, i32 0
// CHECK-A64_32-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_SP2ADDRSPACE]], ptr [[S]], i32 0, i32 0
// CHECK-A64_32-NEXT: [[TMP0:%.*]] = load i64, ptr [[COERCE_DIVE]], align 4
// CHECK-A64_32-NEXT: call void @_Z12Tp2addrspace12Sp2addrspace(i64 [[TMP0]])
// CHECK-A64_32-NEXT: ret void
Expand Down Expand Up @@ -761,12 +753,10 @@ void Traddrspace(Sraddrspace s) { s.x = 1; }
// CHECK-A64-SAME: i64 [[S_COERCE:%.*]]) #[[ATTR0]] {
// CHECK-A64-NEXT: [[ENTRY:.*:]]
// CHECK-A64-NEXT: [[S:%.*]] = alloca [[STRUCT_SRADDRSPACE:%.*]], align 8
// CHECK-A64-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_SRADDRSPACE]], align 8
// CHECK-A64-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_SRADDRSPACE]], ptr [[S]], i32 0, i32 0
// CHECK-A64-NEXT: [[COERCE_VAL_IP:%.*]] = inttoptr i64 [[S_COERCE]] to ptr addrspace(100)
// CHECK-A64-NEXT: store ptr addrspace(100) [[COERCE_VAL_IP]], ptr [[COERCE_DIVE]], align 8
// CHECK-A64-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 [[AGG_TMP]], ptr align 8 [[S]], i64 8, i1 false)
// CHECK-A64-NEXT: [[COERCE_DIVE1:%.*]] = getelementptr inbounds nuw [[STRUCT_SRADDRSPACE]], ptr [[AGG_TMP]], i32 0, i32 0
// CHECK-A64-NEXT: [[COERCE_DIVE1:%.*]] = getelementptr inbounds nuw [[STRUCT_SRADDRSPACE]], ptr [[S]], i32 0, i32 0
// CHECK-A64-NEXT: [[TMP0:%.*]] = load ptr addrspace(100), ptr [[COERCE_DIVE1]], align 8
// CHECK-A64-NEXT: [[COERCE_VAL_PI:%.*]] = ptrtoint ptr addrspace(100) [[TMP0]] to i64
// CHECK-A64-NEXT: call void @_Z11Traddrspace11Sraddrspace(i64 [[COERCE_VAL_PI]])
Expand All @@ -776,12 +766,10 @@ void Traddrspace(Sraddrspace s) { s.x = 1; }
// CHECK-A64_32-SAME: i64 [[S_COERCE:%.*]]) #[[ATTR0]] {
// CHECK-A64_32-NEXT: [[ENTRY:.*:]]
// CHECK-A64_32-NEXT: [[S:%.*]] = alloca [[STRUCT_SRADDRSPACE:%.*]], align 4
// CHECK-A64_32-NEXT: [[AGG_TMP:%.*]] = alloca [[STRUCT_SRADDRSPACE]], align 4
// CHECK-A64_32-NEXT: [[COERCE_DIVE:%.*]] = getelementptr inbounds nuw [[STRUCT_SRADDRSPACE]], ptr [[S]], i32 0, i32 0
// CHECK-A64_32-NEXT: [[COERCE_VAL_II:%.*]] = trunc i64 [[S_COERCE]] to i32
// CHECK-A64_32-NEXT: store i32 [[COERCE_VAL_II]], ptr [[COERCE_DIVE]], align 4
// CHECK-A64_32-NEXT: call void @llvm.memcpy.p0.p0.i32(ptr align 4 [[AGG_TMP]], ptr align 4 [[S]], i32 4, i1 false)
// CHECK-A64_32-NEXT: [[COERCE_DIVE1:%.*]] = getelementptr inbounds nuw [[STRUCT_SRADDRSPACE]], ptr [[AGG_TMP]], i32 0, i32 0
// CHECK-A64_32-NEXT: [[COERCE_DIVE1:%.*]] = getelementptr inbounds nuw [[STRUCT_SRADDRSPACE]], ptr [[S]], i32 0, i32 0
// CHECK-A64_32-NEXT: [[TMP0:%.*]] = load ptr addrspace(100), ptr [[COERCE_DIVE1]], align 4
// CHECK-A64_32-NEXT: [[COERCE_VAL_PI:%.*]] = ptrtoint ptr addrspace(100) [[TMP0]] to i32
// CHECK-A64_32-NEXT: [[COERCE_VAL_II2:%.*]] = zext i32 [[COERCE_VAL_PI]] to i64
Expand Down
2 changes: 1 addition & 1 deletion clang/test/CodeGen/aapcs64-align.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ unsigned sizeof_RidiculouslyOverSizedBitfield = sizeof(RidiculouslyOverSizedBitf
unsigned alignof_RidiculouslyOverSizedBitfield = alignof(RidiculouslyOverSizedBitfield);

// CHECK: define{{.*}} void @g9
// CHECK: call void @f9(i32 noundef 1, ptr noundef nonnull align 16 dead_on_return %agg.tmp)
// CHECK: call void @f9(i32 noundef 1, ptr noundef nonnull align 16 dead_on_return %byval-temp)
// CHECK: declare void @f9(i32 noundef, ptr noundef align 16 dead_on_return)
void f9(int a, RidiculouslyOverSizedBitfield b);
void g9() {
Expand Down
25 changes: 25 additions & 0 deletions clang/test/CodeGen/musttail-indirect-arg-unsupported.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// RUN: %clang_cc1 -triple=x86_64-linux-gnu -verify -emit-llvm-only %s
// RUN: %clang_cc1 -triple=riscv64-linux-gnu -verify -emit-llvm-only %s

// A musttail Indirect argument is forwarded through the matching incoming
// parameter, which requires an in-memory source. A wide _BitInt has scalar
// evaluation kind, so the argument is a scalar value with no source storage
// to forward, regardless of value category. Such a call is rejected rather
// than routed through a caller-frame temp that dangles past the tail call.
Comment thread
xroche marked this conversation as resolved.

typedef _BitInt(256) BI;
BI cee(BI x);
BI pee(BI a) {
// expected-error@+1 {{'musttail' call requires passing an argument by reference, but the source does not have an addressable storage and would alias the caller's frame}}
__attribute__((musttail)) return cee(a);
}

// An aggregate lvalue has addressable storage to forward, so it is accepted.
// Confirms the diagnostic is specific to the no-source case.
struct Big {
unsigned long long a, b, c, d;
};
struct Big cee_ok(struct Big x);
struct Big pee_ok(struct Big a) {
__attribute__((musttail)) return cee_ok(a);
}
Loading