Skip to content

Commit 9e0c06d

Browse files
[clang][CodeGen] Set dead_on_return when passing arguments indirectly
Let Clang emit `dead_on_return` attribute on pointer arguments that are passed indirectly, namely, large aggregates that the ABI mandates be passed by value; thus, the parameter is destroyed within the callee. Writes to such arguments are not observable by the caller after the callee returns. This should desirably enable further MemCpyOpt/DSE optimizations. Previous discussion: https://discourse.llvm.org/t/rfc-add-dead-on-return-attribute/86871.
1 parent ee8756e commit 9e0c06d

File tree

92 files changed

+887
-873
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+887
-873
lines changed

clang/lib/CodeGen/CGCall.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2852,8 +2852,21 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
28522852
if (AI.getInReg())
28532853
Attrs.addAttribute(llvm::Attribute::InReg);
28542854

2855-
if (AI.getIndirectByVal())
2855+
// Depending on the ABI, this may be either a byval or a dead_on_return
2856+
// argument.
2857+
if (AI.getIndirectByVal()) {
28562858
Attrs.addByValAttr(getTypes().ConvertTypeForMem(ParamType));
2859+
} else {
2860+
// Add dead_on_return when the object's lifetime ends in the callee.
2861+
// This includes trivially-destructible objects, as well as objects
2862+
// whose destruction / clean-up is carried out within the callee (e.g.,
2863+
// Obj-C ARC-managed structs, MSVC callee-destroyed objects).
2864+
if (!ParamType.isDestructedType() || !ParamType->isRecordType() ||
2865+
ParamType->castAs<RecordType>()
2866+
->getDecl()
2867+
->isParamDestroyedInCallee())
2868+
Attrs.addAttribute(llvm::Attribute::DeadOnReturn);
2869+
}
28572870

28582871
auto *Decl = ParamType->getAsRecordDecl();
28592872
if (CodeGenOpts.PassByValueIsNoAlias && Decl &&

clang/test/CodeGen/64bit-swiftcall.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ TEST(struct_big_1)
239239
// CHECK-LABEL: define {{.*}} void @return_struct_big_1(ptr dead_on_unwind noalias writable sret
240240

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

244244
/*****************************************************************************/
245245
/********************************* TYPE MERGING ******************************/

clang/test/CodeGen/AArch64/byval-temp.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ void example(void) {
3030
// Then, memcpy `l` to the temporary stack space.
3131
// CHECK-O0-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %[[byvaltemp]], ptr align 8 %[[l]], i64 64, i1 false)
3232
// Finally, call using a pointer to the temporary stack space.
33-
// CHECK-O0-NEXT: call void @pass_large(ptr noundef %[[byvaltemp]])
33+
// CHECK-O0-NEXT: call void @pass_large(ptr dead_on_return noundef %[[byvaltemp]])
3434
// Now, do the same for the second call, using the second temporary alloca.
3535
// CHECK-O0-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %[[byvaltemp1]], ptr align 8 %[[l]], i64 64, i1 false)
36-
// CHECK-O0-NEXT: call void @pass_large(ptr noundef %[[byvaltemp1]])
36+
// CHECK-O0-NEXT: call void @pass_large(ptr dead_on_return noundef %[[byvaltemp1]])
3737
// CHECK-O0-NEXT: ret void
3838
//
3939
// At O3, we should have lifetime markers to help the optimizer re-use the temporary allocas.
@@ -58,15 +58,15 @@ void example(void) {
5858
// Then, memcpy `l` to the temporary stack space.
5959
// CHECK-O3-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %[[byvaltemp]], ptr align 8 %[[l]], i64 64, i1 false)
6060
// Finally, call using a pointer to the temporary stack space.
61-
// CHECK-O3-NEXT: call void @pass_large(ptr noundef %[[byvaltemp]])
61+
// CHECK-O3-NEXT: call void @pass_large(ptr dead_on_return noundef %[[byvaltemp]])
6262
//
6363
// The lifetime of the temporary used to pass a pointer to the struct ends here.
6464
// CHECK-O3-NEXT: call void @llvm.lifetime.end.p0(i64 64, ptr %[[byvaltemp]])
6565
//
6666
// Now, do the same for the second call, using the second temporary alloca.
6767
// CHECK-O3-NEXT: call void @llvm.lifetime.start.p0(i64 64, ptr %[[byvaltemp1]])
6868
// CHECK-O3-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %[[byvaltemp1]], ptr align 8 %[[l]], i64 64, i1 false)
69-
// CHECK-O3-NEXT: call void @pass_large(ptr noundef %[[byvaltemp1]])
69+
// CHECK-O3-NEXT: call void @pass_large(ptr dead_on_return noundef %[[byvaltemp1]])
7070
// CHECK-O3-NEXT: call void @llvm.lifetime.end.p0(i64 64, ptr %[[byvaltemp1]])
7171
//
7272
// Mark the end of the lifetime of `l`.
@@ -88,12 +88,12 @@ void example_BitInt(void) {
8888
// CHECK-O0-NEXT: [[LOADEDV:%.*]] = trunc i256 [[TMP0]] to i129
8989
// CHECK-O0-NEXT: [[STOREDV:%.*]] = sext i129 [[LOADEDV]] to i256
9090
// CHECK-O0-NEXT: store i256 [[STOREDV]], ptr [[INDIRECT_ARG_TEMP]], align 16
91-
// CHECK-O0-NEXT: call void @pass_large_BitInt(ptr noundef [[INDIRECT_ARG_TEMP]])
91+
// CHECK-O0-NEXT: call void @pass_large_BitInt(ptr dead_on_return noundef [[INDIRECT_ARG_TEMP]])
9292
// CHECK-O0-NEXT: [[TMP1:%.*]] = load i256, ptr [[L]], align 16
9393
// CHECK-O0-NEXT: [[LOADEDV1:%.*]] = trunc i256 [[TMP1]] to i129
9494
// CHECK-O0-NEXT: [[STOREDV1:%.*]] = sext i129 [[LOADEDV1]] to i256
9595
// CHECK-O0-NEXT: store i256 [[STOREDV1]], ptr [[INDIRECT_ARG_TEMP1]], align 16
96-
// CHECK-O0-NEXT: call void @pass_large_BitInt(ptr noundef [[INDIRECT_ARG_TEMP1]])
96+
// CHECK-O0-NEXT: call void @pass_large_BitInt(ptr dead_on_return noundef [[INDIRECT_ARG_TEMP1]])
9797
// CHECK-O0-NEXT: ret void
9898
//
9999
// CHECK-O3-LABEL: define dso_local void @example_BitInt(
@@ -108,13 +108,13 @@ void example_BitInt(void) {
108108
// CHECK-O3-NEXT: call void @llvm.lifetime.start.p0(i64 32, ptr [[INDIRECT_ARG_TEMP]])
109109
// CHECK-O3-NEXT: [[STOREDV:%.*]] = sext i129 [[LOADEDV]] to i256
110110
// CHECK-O3-NEXT: store i256 [[STOREDV]], ptr [[INDIRECT_ARG_TEMP]], align 16, !tbaa [[TBAA6]]
111-
// CHECK-O3-NEXT: call void @pass_large_BitInt(ptr noundef [[INDIRECT_ARG_TEMP]])
111+
// CHECK-O3-NEXT: call void @pass_large_BitInt(ptr dead_on_return noundef [[INDIRECT_ARG_TEMP]])
112112
// CHECK-O3-NEXT: call void @llvm.lifetime.end.p0(i64 32, ptr [[INDIRECT_ARG_TEMP]])
113113
// CHECK-O3-NEXT: [[TMP1:%.*]] = load i256, ptr [[L]], align 16, !tbaa [[TBAA6]]
114114
// CHECK-O3-NEXT: [[LOADEDV1:%.*]] = trunc i256 [[TMP1]] to i129
115115
// CHECK-O3-NEXT: call void @llvm.lifetime.start.p0(i64 32, ptr [[INDIRECT_ARG_TEMP1]])
116116
// CHECK-O3-NEXT: [[STOREDV1:%.*]] = sext i129 [[LOADEDV1]] to i256
117117
// CHECK-O3-NEXT: store i256 [[STOREDV1]], ptr [[INDIRECT_ARG_TEMP1]], align 16, !tbaa [[TBAA6]]
118-
// CHECK-O3-NEXT: call void @pass_large_BitInt(ptr noundef [[INDIRECT_ARG_TEMP1]])
118+
// CHECK-O3-NEXT: call void @pass_large_BitInt(ptr dead_on_return noundef [[INDIRECT_ARG_TEMP1]])
119119
// CHECK-O3-NEXT: call void @llvm.lifetime.end.p0(i64 32, ptr [[INDIRECT_ARG_TEMP1]])
120120
// CHECK-O3-NEXT: call void @llvm.lifetime.end.p0(i64 32, ptr [[L]])

clang/test/CodeGen/AArch64/pure-scalable-args-empty-union.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ void f0(S0 *p) {
1919
use0(*p);
2020
}
2121
// 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>)
22-
// CHECK-CXX: declare void @use0(ptr noundef)
22+
// CHECK-CXX: declare void @use0(ptr dead_on_return noundef)
2323

2424
#ifdef __cplusplus
2525

0 commit comments

Comments
 (0)