Skip to content

[clang][CodeGen] Set dead_on_return on indirect pointer arguments #148159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 10 additions & 1 deletion clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2852,8 +2852,17 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
if (AI.getInReg())
Attrs.addAttribute(llvm::Attribute::InReg);

if (AI.getIndirectByVal())
// Depending on the ABI, this may be either a byval or a dead_on_return
// argument.
if (AI.getIndirectByVal()) {
Attrs.addByValAttr(getTypes().ConvertTypeForMem(ParamType));
} else {
// If the argument type has a non-trivial destructor that the caller has
// to invoke, this cannot be a dead_on_return argument.
const auto *RD = ParamType->getAsCXXRecordDecl();
if (!RD || (RD && RD->hasTrivialDestructor()))
Attrs.addAttribute(llvm::Attribute::DeadOnReturn);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Please use:

  if (!ParamType.isDestructedType() || !ParamType->isRecordType() ||
      ParamType->castAs<RecordType>()->getDecl()->isParamDestroyedInCallee())

We do destroy parameters in the callee in several situations. The most important example is the MSVC C++ ABI, but I believe that coincidentally never uses indirect argument passing, so it wouldn't be testable. The testable example would be something like this in Objective-C++:

struct HasWeakReference {
  __weak id ref;
};
void test(HasWeakReference) {} // should still be dead_on_return because the caller is not responsible to destroy it

__weak helpfully forces the struct to be passed indirectly, but you could also just use a __strong id ref; in a struct that's big enough to be passed indirectly on the target.


auto *Decl = ParamType->getAsRecordDecl();
if (CodeGenOpts.PassByValueIsNoAlias && Decl &&
Expand Down
2 changes: 1 addition & 1 deletion clang/test/CodeGen/64bit-swiftcall.c
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ TEST(struct_big_1)
// CHECK-LABEL: define {{.*}} void @return_struct_big_1(ptr dead_on_unwind noalias writable sret

// Should not be byval.
// CHECK-LABEL: define {{.*}} void @take_struct_big_1(ptr{{( %.*)?}})
// CHECK-LABEL: define {{.*}} void @take_struct_big_1(ptr dead_on_return{{( %.*)?}})

/*****************************************************************************/
/********************************* TYPE MERGING ******************************/
Expand Down
16 changes: 8 additions & 8 deletions clang/test/CodeGen/AArch64/byval-temp.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ void example(void) {
// Then, memcpy `l` to the temporary stack space.
// CHECK-O0-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %[[byvaltemp]], ptr align 8 %[[l]], i64 64, i1 false)
// Finally, call using a pointer to the temporary stack space.
// CHECK-O0-NEXT: call void @pass_large(ptr noundef %[[byvaltemp]])
// CHECK-O0-NEXT: call void @pass_large(ptr dead_on_return noundef %[[byvaltemp]])
// Now, do the same for the second call, using the second temporary alloca.
// CHECK-O0-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %[[byvaltemp1]], ptr align 8 %[[l]], i64 64, i1 false)
// CHECK-O0-NEXT: call void @pass_large(ptr noundef %[[byvaltemp1]])
// CHECK-O0-NEXT: call void @pass_large(ptr dead_on_return noundef %[[byvaltemp1]])
// CHECK-O0-NEXT: ret void
//
// At O3, we should have lifetime markers to help the optimizer re-use the temporary allocas.
Expand All @@ -58,15 +58,15 @@ void example(void) {
// Then, memcpy `l` to the temporary stack space.
// CHECK-O3-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %[[byvaltemp]], ptr align 8 %[[l]], i64 64, i1 false)
// Finally, call using a pointer to the temporary stack space.
// CHECK-O3-NEXT: call void @pass_large(ptr noundef %[[byvaltemp]])
// CHECK-O3-NEXT: call void @pass_large(ptr dead_on_return noundef %[[byvaltemp]])
//
// The lifetime of the temporary used to pass a pointer to the struct ends here.
// CHECK-O3-NEXT: call void @llvm.lifetime.end.p0(i64 64, ptr %[[byvaltemp]])
//
// Now, do the same for the second call, using the second temporary alloca.
// CHECK-O3-NEXT: call void @llvm.lifetime.start.p0(i64 64, ptr %[[byvaltemp1]])
// CHECK-O3-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %[[byvaltemp1]], ptr align 8 %[[l]], i64 64, i1 false)
// CHECK-O3-NEXT: call void @pass_large(ptr noundef %[[byvaltemp1]])
// CHECK-O3-NEXT: call void @pass_large(ptr dead_on_return noundef %[[byvaltemp1]])
// CHECK-O3-NEXT: call void @llvm.lifetime.end.p0(i64 64, ptr %[[byvaltemp1]])
//
// Mark the end of the lifetime of `l`.
Expand All @@ -88,12 +88,12 @@ void example_BitInt(void) {
// CHECK-O0-NEXT: [[LOADEDV:%.*]] = trunc i256 [[TMP0]] to i129
// CHECK-O0-NEXT: [[STOREDV:%.*]] = sext i129 [[LOADEDV]] to i256
// CHECK-O0-NEXT: store i256 [[STOREDV]], ptr [[INDIRECT_ARG_TEMP]], align 16
// CHECK-O0-NEXT: call void @pass_large_BitInt(ptr noundef [[INDIRECT_ARG_TEMP]])
// CHECK-O0-NEXT: call void @pass_large_BitInt(ptr dead_on_return noundef [[INDIRECT_ARG_TEMP]])
// CHECK-O0-NEXT: [[TMP1:%.*]] = load i256, ptr [[L]], align 16
// CHECK-O0-NEXT: [[LOADEDV1:%.*]] = trunc i256 [[TMP1]] to i129
// CHECK-O0-NEXT: [[STOREDV1:%.*]] = sext i129 [[LOADEDV1]] to i256
// CHECK-O0-NEXT: store i256 [[STOREDV1]], ptr [[INDIRECT_ARG_TEMP1]], align 16
// CHECK-O0-NEXT: call void @pass_large_BitInt(ptr noundef [[INDIRECT_ARG_TEMP1]])
// CHECK-O0-NEXT: call void @pass_large_BitInt(ptr dead_on_return noundef [[INDIRECT_ARG_TEMP1]])
// CHECK-O0-NEXT: ret void
//
// CHECK-O3-LABEL: define dso_local void @example_BitInt(
Expand All @@ -108,13 +108,13 @@ void example_BitInt(void) {
// CHECK-O3-NEXT: call void @llvm.lifetime.start.p0(i64 32, ptr [[INDIRECT_ARG_TEMP]])
// CHECK-O3-NEXT: [[STOREDV:%.*]] = sext i129 [[LOADEDV]] to i256
// CHECK-O3-NEXT: store i256 [[STOREDV]], ptr [[INDIRECT_ARG_TEMP]], align 16, !tbaa [[TBAA6]]
// CHECK-O3-NEXT: call void @pass_large_BitInt(ptr noundef [[INDIRECT_ARG_TEMP]])
// CHECK-O3-NEXT: call void @pass_large_BitInt(ptr dead_on_return noundef [[INDIRECT_ARG_TEMP]])
// CHECK-O3-NEXT: call void @llvm.lifetime.end.p0(i64 32, ptr [[INDIRECT_ARG_TEMP]])
// CHECK-O3-NEXT: [[TMP1:%.*]] = load i256, ptr [[L]], align 16, !tbaa [[TBAA6]]
// CHECK-O3-NEXT: [[LOADEDV1:%.*]] = trunc i256 [[TMP1]] to i129
// CHECK-O3-NEXT: call void @llvm.lifetime.start.p0(i64 32, ptr [[INDIRECT_ARG_TEMP1]])
// CHECK-O3-NEXT: [[STOREDV1:%.*]] = sext i129 [[LOADEDV1]] to i256
// CHECK-O3-NEXT: store i256 [[STOREDV1]], ptr [[INDIRECT_ARG_TEMP1]], align 16, !tbaa [[TBAA6]]
// CHECK-O3-NEXT: call void @pass_large_BitInt(ptr noundef [[INDIRECT_ARG_TEMP1]])
// CHECK-O3-NEXT: call void @pass_large_BitInt(ptr dead_on_return noundef [[INDIRECT_ARG_TEMP1]])
// CHECK-O3-NEXT: call void @llvm.lifetime.end.p0(i64 32, ptr [[INDIRECT_ARG_TEMP1]])
// CHECK-O3-NEXT: call void @llvm.lifetime.end.p0(i64 32, ptr [[L]])
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ void f0(S0 *p) {
use0(*p);
}
// CHECK-C: declare void @use0(<vscale x 4 x float>, <vscale x 4 x float>, <vscale x 4 x float>, <vscale x 4 x float>)
// CHECK-CXX: declare void @use0(ptr noundef)
// CHECK-CXX: declare void @use0(ptr dead_on_return noundef)

#ifdef __cplusplus

Expand Down
Loading
Loading