Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
126 changes: 126 additions & 0 deletions clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5173,6 +5173,26 @@ void CodeGenFunction::EmitWritebacks(const CallArgList &args) {
emitWriteback(*this, I);
}

/// Whether emitting this glvalue neither has side effects nor reads mutable
/// state, so deferring its byte read to the call boundary is equivalent to
/// initializing the argument last, a sequencing C++17 [expr.call]/8 allows.
/// A dereference or call in the address computation would instead split the
/// argument's evaluation around the other arguments'.
static bool isPureForwardableLValue(const Expr *E) {
E = E->IgnoreParens();
if (const auto *DRE = dyn_cast<DeclRefExpr>(E))
return isa<VarDecl>(DRE->getDecl());
if (const auto *ME = dyn_cast<MemberExpr>(E))
return !ME->isArrow() && isa<FieldDecl>(ME->getMemberDecl()) &&
isPureForwardableLValue(ME->getBase());
if (const auto *ICE = dyn_cast<ImplicitCastExpr>(E))
if (ICE->getCastKind() == CK_DerivedToBase ||
ICE->getCastKind() == CK_UncheckedDerivedToBase ||
ICE->getCastKind() == CK_NoOp)
return isPureForwardableLValue(ICE->getSubExpr());
return false;
}

void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
QualType type) {
std::optional<DisableDebugLocationUpdates> Dis;
Expand Down Expand Up @@ -5253,6 +5273,38 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
}
}

// Under musttail, hand a trivially-copyable record source's LValue to
// EmitCall rather than materializing an agg.tmp. EmitCall's Indirect path
// copies it into the matching incoming parameter, which survives the tail
// call. The byte read is deferred to the call boundary, so the source is
// restricted to pure lvalue chains (see isPureForwardableLValue); casts
// are not stripped, so a derived-to-base source keeps its adjusted
// address. On the device side CUDA surface/texture types are excluded:
// they classify as Direct and forwarding would load raw record bytes
// instead of the handle that EmitAggregateCopy materializes.
if (HasAggregateEvalKind && MustTailCall && type->isRecordType() &&
type.isTriviallyCopyableType(getContext()) &&
!(getLangOpts().CUDAIsDevice &&
(type->isCUDADeviceBuiltinSurfaceType() ||
type->isCUDADeviceBuiltinTextureType()))) {
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);
if (Source->isGLValue() && isPureForwardableLValue(Source) &&
Source->getType().getAddressSpace() != LangAS::hlsl_constant &&
getContext().hasSameUnqualifiedType(Source->getType(), type)) {
LValue L = EmitLValue(Source);
if (L.isSimple()) {
args.addUncopiedAggregate(L, type);
return;
}
}
}
}
}

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

Expand Down Expand Up @@ -5562,6 +5614,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 +5748,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 +5831,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 +6202,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
16 changes: 8 additions & 8 deletions clang/test/CodeGen/AArch64/struct-coerce-using-ptr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ struct Srp {
// CHECK-A64-NEXT: [[S:%.*]] = alloca [[STRUCT_SRP:%.*]], align 8
// CHECK-A64-NEXT: store [2 x ptr] [[S_COERCE]], ptr [[S]], align 8
// CHECK-A64-NEXT: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_SRP]], ptr [[S]], i32 0, i32 0
// CHECK-A64-NEXT: [[TMP0:%.*]] = load ptr, ptr [[X]], align 8, !nonnull [[META1:![0-9]+]], !align [[META2:![0-9]+]]
// CHECK-A64-NEXT: [[TMP0:%.*]] = load ptr, ptr [[X]], align 8, !nonnull [[META2:![0-9]+]], !align [[META3:![0-9]+]]
// CHECK-A64-NEXT: store i32 1, ptr [[TMP0]], align 4
// CHECK-A64-NEXT: ret void
//
Expand All @@ -149,7 +149,7 @@ struct Srp {
// CHECK-A64_32-NEXT: [[S:%.*]] = alloca [[STRUCT_SRP:%.*]], align 4
// CHECK-A64_32-NEXT: store i64 [[S_COERCE]], ptr [[S]], align 4
// CHECK-A64_32-NEXT: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_SRP]], ptr [[S]], i32 0, i32 0
// CHECK-A64_32-NEXT: [[TMP0:%.*]] = load ptr, ptr [[X]], align 4, !nonnull [[META1:![0-9]+]], !align [[META2:![0-9]+]]
// CHECK-A64_32-NEXT: [[TMP0:%.*]] = load ptr, ptr [[X]], align 4, !nonnull [[META2:![0-9]+]], !align [[META3:![0-9]+]]
// CHECK-A64_32-NEXT: store i32 1, ptr [[TMP0]], align 4
// CHECK-A64_32-NEXT: ret void
//
Expand Down Expand Up @@ -740,7 +740,7 @@ struct Sraddrspace {
// 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: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_SRADDRSPACE]], ptr [[S]], i32 0, i32 0
// CHECK-A64-NEXT: [[TMP0:%.*]] = load ptr addrspace(100), ptr [[X]], align 8, !align [[META2]]
// CHECK-A64-NEXT: [[TMP0:%.*]] = load ptr addrspace(100), ptr [[X]], align 8, !align [[META3]]
// CHECK-A64-NEXT: store i32 1, ptr addrspace(100) [[TMP0]], align 4
// CHECK-A64-NEXT: ret void
//
Expand All @@ -752,7 +752,7 @@ struct Sraddrspace {
// 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: [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_SRADDRSPACE]], ptr [[S]], i32 0, i32 0
// CHECK-A64_32-NEXT: [[TMP0:%.*]] = load ptr addrspace(100), ptr [[X]], align 4, !align [[META2]]
// CHECK-A64_32-NEXT: [[TMP0:%.*]] = load ptr addrspace(100), ptr [[X]], align 4, !align [[META3]]
// CHECK-A64_32-NEXT: store i32 1, ptr addrspace(100) [[TMP0]], align 4
// CHECK-A64_32-NEXT: ret void
//
Expand Down Expand Up @@ -791,9 +791,9 @@ void Traddrspace(Sraddrspace s) { s.x = 1; }
void Craddrspace(Sraddrspace s) { Traddrspace(s); }

//.
// CHECK-A64: [[META1]] = !{}
// CHECK-A64: [[META2]] = !{i64 4}
// CHECK-A64: [[META2]] = !{}
// CHECK-A64: [[META3]] = !{i64 4}
//.
// CHECK-A64_32: [[META1]] = !{}
// CHECK-A64_32: [[META2]] = !{i64 4}
// CHECK-A64_32: [[META2]] = !{}
// CHECK-A64_32: [[META3]] = !{i64 4}
//.
27 changes: 27 additions & 0 deletions clang/test/CodeGen/musttail-indirect-arg-unsupported.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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.
// This is an implementation limit, not a fundamental one: it could be lifted
// by storing the value through the incoming parameter's own slot.

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