[llvm] [Inliner] Improve attribute propagation to callsites when inlining. (PR #66036)

via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 26 08:48:43 PDT 2023


https://github.com/goldsteinn updated https://github.com/llvm/llvm-project/pull/66036

>From 35a06fb659247bee2741414382993930e0c412b6 Mon Sep 17 00:00:00 2001
From: Noah Goldstein <goldstein.w.n at gmail.com>
Date: Mon, 4 Sep 2023 16:31:47 -0500
Subject: [PATCH 1/2] [Inliner] Add some additional tests for progagating
 attributes before inlining; NFC

---
 .../Inline/access-attributes-prop.ll          | 498 ++++++++++++++++++
 .../Inline/ret_attr_align_and_noundef.ll      | 268 ++++++++++
 2 files changed, 766 insertions(+)
 create mode 100644 llvm/test/Transforms/Inline/access-attributes-prop.ll
 create mode 100644 llvm/test/Transforms/Inline/ret_attr_align_and_noundef.ll

diff --git a/llvm/test/Transforms/Inline/access-attributes-prop.ll b/llvm/test/Transforms/Inline/access-attributes-prop.ll
new file mode 100644
index 000000000000000..3b4a59897c5694a
--- /dev/null
+++ b/llvm/test/Transforms/Inline/access-attributes-prop.ll
@@ -0,0 +1,498 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --function-signature --check-attributes
+; RUN: opt -S -passes=inline %s | FileCheck %s
+; RUN: opt -S -passes='cgscc(inline)' %s | FileCheck %s
+; RUN: opt -S -passes='module-inline' %s | FileCheck %s
+
+declare void @bar1(ptr %p)
+declare void @bar2(ptr %p, ptr %p2)
+
+define dso_local void @foo1_rdonly(ptr readonly %p) {
+; CHECK-LABEL: define {{[^@]+}}@foo1_rdonly
+; CHECK-SAME: (ptr readonly [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @bar1(ptr %p)
+  ret void
+}
+
+define dso_local void @foo1(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@foo1
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @bar1(ptr %p)
+  ret void
+}
+
+define dso_local void @foo1_bar_aligned64_deref512(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@foo1_bar_aligned64_deref512
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr align 64 dereferenceable(512) [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @bar1(ptr align 64 dereferenceable(512) %p)
+  ret void
+}
+
+define dso_local void @foo1_bar_aligned512_deref_or_null512(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@foo1_bar_aligned512_deref_or_null512
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr align 512 dereferenceable_or_null(512) [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @bar1(ptr align 512 dereferenceable_or_null(512) %p)
+  ret void
+}
+
+define dso_local void @foo2(ptr %p, ptr %p2) {
+; CHECK-LABEL: define {{[^@]+}}@foo2
+; CHECK-SAME: (ptr [[P:%.*]], ptr [[P2:%.*]]) {
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @bar2(ptr %p, ptr %p)
+  ret void
+}
+
+define dso_local void @foo2_2(ptr %p, ptr %p2) {
+; CHECK-LABEL: define {{[^@]+}}@foo2_2
+; CHECK-SAME: (ptr [[P:%.*]], ptr [[P2:%.*]]) {
+; CHECK-NEXT:    call void @bar2(ptr [[P2]], ptr [[P2]])
+; CHECK-NEXT:    ret void
+;
+  call void @bar2(ptr %p2, ptr %p2)
+  ret void
+}
+
+define dso_local void @foo2_3(ptr %p, ptr readnone %p2) {
+; CHECK-LABEL: define {{[^@]+}}@foo2_3
+; CHECK-SAME: (ptr [[P:%.*]], ptr readnone [[P2:%.*]]) {
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[P2]])
+; CHECK-NEXT:    ret void
+;
+  call void @bar2(ptr %p, ptr %p2)
+  ret void
+}
+
+define dso_local void @buz1_wronly(ptr %p) writeonly {
+; CHECK: Function Attrs: memory(write)
+; CHECK-LABEL: define {{[^@]+}}@buz1_wronly
+; CHECK-SAME: (ptr [[P:%.*]]) #[[ATTR0:[0-9]+]] {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @bar1(ptr %p)
+  ret void
+}
+
+define dso_local void @buz1(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@buz1
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @bar1(ptr %p)
+  ret void
+}
+
+define dso_local void @buz1_wronly_fail_alloca(ptr %p) writeonly {
+; CHECK: Function Attrs: memory(write)
+; CHECK-LABEL: define {{[^@]+}}@buz1_wronly_fail_alloca
+; CHECK-SAME: (ptr [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    [[A:%.*]] = alloca i32, align 4
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[A]])
+; CHECK-NEXT:    ret void
+;
+  %a = alloca i32, align 4
+  call void @bar2(ptr %p, ptr %a)
+  ret void
+}
+
+define dso_local void @buz1_fail_alloca(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@buz1_fail_alloca
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[A:%.*]] = alloca i32, align 4
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[A]])
+; CHECK-NEXT:    ret void
+;
+  %a = alloca i32, align 4
+  call void @bar2(ptr %p, ptr %a)
+  ret void
+}
+
+define dso_local void @buz1_wronly_partially_okay_alloca(ptr %p) writeonly {
+; CHECK: Function Attrs: memory(write)
+; CHECK-LABEL: define {{[^@]+}}@buz1_wronly_partially_okay_alloca
+; CHECK-SAME: (ptr [[P:%.*]]) #[[ATTR0]] {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    [[A:%.*]] = alloca i32, align 4
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[A]])
+; CHECK-NEXT:    ret void
+;
+  call void @bar1(ptr %p)
+  %a = alloca i32, align 4
+  call void @bar2(ptr %p, ptr %a)
+  ret void
+}
+
+define dso_local void @buz1_partially_okay_alloca(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@buz1_partially_okay_alloca
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    [[A:%.*]] = alloca i32, align 4
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[A]])
+; CHECK-NEXT:    ret void
+;
+  call void @bar1(ptr %p)
+  %a = alloca i32, align 4
+  call void @bar2(ptr %p, ptr %a)
+  ret void
+}
+
+define dso_local void @foo2_through_obj(ptr %p, ptr %p2) {
+; CHECK-LABEL: define {{[^@]+}}@foo2_through_obj
+; CHECK-SAME: (ptr [[P:%.*]], ptr [[P2:%.*]]) {
+; CHECK-NEXT:    [[PP:%.*]] = getelementptr i8, ptr [[P]], i64 9
+; CHECK-NEXT:    [[P2P:%.*]] = getelementptr i8, ptr [[P2]], i64 123
+; CHECK-NEXT:    call void @bar2(ptr [[P2P]], ptr [[PP]])
+; CHECK-NEXT:    ret void
+;
+  %pp = getelementptr i8, ptr %p, i64 9
+  %p2p = getelementptr i8, ptr %p2, i64 123
+  call void @bar2(ptr %p2p, ptr %pp)
+  ret void
+}
+
+define void @prop_param_func_decl(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_func_decl
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1_rdonly(ptr %p)
+  ret void
+}
+
+define void @prop_param_callbase_def(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_callbase_def
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1(ptr readonly %p)
+  call void @bar1(ptr %p)
+  ret void
+}
+
+define void @prop_param_callbase_def_2x(ptr %p, ptr %p2) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_callbase_def_2x
+; CHECK-SAME: (ptr [[P:%.*]], ptr [[P2:%.*]]) {
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo2(ptr readonly %p, ptr %p)
+  ret void
+}
+
+define void @prop_param_callbase_def_2x_2(ptr %p, ptr %p2) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_callbase_def_2x_2
+; CHECK-SAME: (ptr [[P:%.*]], ptr [[P2:%.*]]) {
+; CHECK-NEXT:    [[PP_I:%.*]] = getelementptr i8, ptr [[P]], i64 9
+; CHECK-NEXT:    [[P2P_I:%.*]] = getelementptr i8, ptr [[P2]], i64 123
+; CHECK-NEXT:    call void @bar2(ptr [[P2P_I]], ptr [[PP_I]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo2_through_obj(ptr readonly %p, ptr writeonly %p2)
+  ret void
+}
+
+define void @prop_param_callbase_def_2x_incompat(ptr %p, ptr %p2) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_callbase_def_2x_incompat
+; CHECK-SAME: (ptr [[P:%.*]], ptr [[P2:%.*]]) {
+; CHECK-NEXT:    [[PP_I:%.*]] = getelementptr i8, ptr [[P]], i64 9
+; CHECK-NEXT:    [[P2P_I:%.*]] = getelementptr i8, ptr [[P]], i64 123
+; CHECK-NEXT:    call void @bar2(ptr [[P2P_I]], ptr [[PP_I]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo2_through_obj(ptr readnone %p, ptr readonly %p)
+  ret void
+}
+
+define void @prop_param_callbase_def_2x_incompat_2(ptr %p, ptr %p2) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_callbase_def_2x_incompat_2
+; CHECK-SAME: (ptr [[P:%.*]], ptr [[P2:%.*]]) {
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo2(ptr readonly %p, ptr readnone %p)
+  ret void
+}
+
+define void @prop_param_callbase_def_2x_incompat_3(ptr %p, ptr %p2) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_callbase_def_2x_incompat_3
+; CHECK-SAME: (ptr [[P:%.*]], ptr [[P2:%.*]]) {
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo2_2(ptr readonly %p, ptr readnone %p)
+  ret void
+}
+
+define void @prop_param_callbase_def_1x_partial(ptr %p, ptr %p2) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_callbase_def_1x_partial
+; CHECK-SAME: (ptr [[P:%.*]], ptr [[P2:%.*]]) {
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo2(ptr readonly %p, ptr %p)
+  ret void
+}
+
+define void @prop_param_callbase_def_1x_partial_2(ptr %p, ptr %p2) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_callbase_def_1x_partial_2
+; CHECK-SAME: (ptr [[P:%.*]], ptr [[P2:%.*]]) {
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo2_2(ptr readonly %p, ptr %p)
+  ret void
+}
+
+define void @prop_param_callbase_def_1x_partial_3(ptr %p, ptr %p2) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_callbase_def_1x_partial_3
+; CHECK-SAME: (ptr [[P:%.*]], ptr [[P2:%.*]]) {
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo2_3(ptr readonly %p, ptr %p)
+  ret void
+}
+
+define void @prop_deref(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_deref
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1(ptr dereferenceable(16) %p)
+  ret void
+}
+
+define void @prop_deref_or_null(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_deref_or_null
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1(ptr dereferenceable_or_null(256) %p)
+  ret void
+}
+
+define void @prop_param_nonnull_and_align(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_nonnull_and_align
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1(ptr nonnull align 32 %p)
+  ret void
+}
+
+define void @prop_param_deref_align_no_update(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_deref_align_no_update
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr align 64 dereferenceable(512) [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1_bar_aligned64_deref512(ptr align 4 dereferenceable(64) %p)
+  ret void
+}
+
+define void @prop_param_deref_align_update(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_deref_align_update
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr align 64 dereferenceable(512) [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1_bar_aligned64_deref512(ptr align 128 dereferenceable(1024) %p)
+  ret void
+}
+
+define void @prop_param_deref_or_null_update(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_deref_or_null_update
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr align 512 dereferenceable_or_null(512) [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1_bar_aligned512_deref_or_null512(ptr dereferenceable_or_null(1024) %p)
+  ret void
+}
+
+define void @prop_param_deref_or_null_no_update(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_param_deref_or_null_no_update
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr align 512 dereferenceable_or_null(512) [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1_bar_aligned512_deref_or_null512(ptr dereferenceable_or_null(32) %p)
+  ret void
+}
+
+define void @prop_fn_decl(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_fn_decl
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @buz1_wronly(ptr %p)
+  call void @bar1(ptr %p)
+  ret void
+}
+
+define void @prop_cb_def_wr(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_cb_def_wr
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @buz1(ptr %p) writeonly
+  call void @bar1(ptr %p)
+  ret void
+}
+
+define void @prop_fn_decl_fail_alloca(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_fn_decl_fail_alloca
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[A_I:%.*]] = alloca i32, align 4
+; CHECK-NEXT:    call void @llvm.lifetime.start.p0(i64 4, ptr [[A_I]])
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[A_I]])
+; CHECK-NEXT:    call void @llvm.lifetime.end.p0(i64 4, ptr [[A_I]])
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @buz1_wronly_fail_alloca(ptr %p)
+  call void @bar1(ptr %p)
+  ret void
+}
+
+define void @prop_cb_def_wr_fail_alloca(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_cb_def_wr_fail_alloca
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[A_I:%.*]] = alloca i32, align 4
+; CHECK-NEXT:    call void @llvm.lifetime.start.p0(i64 4, ptr [[A_I]])
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[A_I]])
+; CHECK-NEXT:    call void @llvm.lifetime.end.p0(i64 4, ptr [[A_I]])
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @buz1_fail_alloca(ptr %p) writeonly
+  call void @bar1(ptr %p)
+  ret void
+}
+
+define void @prop_fn_decl_partially_okay_alloca(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_fn_decl_partially_okay_alloca
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[A_I:%.*]] = alloca i32, align 4
+; CHECK-NEXT:    call void @llvm.lifetime.start.p0(i64 4, ptr [[A_I]])
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[A_I]])
+; CHECK-NEXT:    call void @llvm.lifetime.end.p0(i64 4, ptr [[A_I]])
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @buz1_wronly_partially_okay_alloca(ptr %p)
+  call void @bar1(ptr %p)
+  ret void
+}
+
+define void @prop_cb_def_wr_partially_okay_alloca(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_cb_def_wr_partially_okay_alloca
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    [[A_I:%.*]] = alloca i32, align 4
+; CHECK-NEXT:    call void @llvm.lifetime.start.p0(i64 4, ptr [[A_I]])
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    call void @bar2(ptr [[P]], ptr [[A_I]])
+; CHECK-NEXT:    call void @llvm.lifetime.end.p0(i64 4, ptr [[A_I]])
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @buz1_partially_okay_alloca(ptr %p) writeonly
+  call void @bar1(ptr %p)
+  ret void
+}
+
+define void @prop_cb_def_readonly(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_cb_def_readonly
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1(ptr %p) readonly
+  ret void
+}
+
+define void @prop_cb_def_readnone(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_cb_def_readnone
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1(ptr %p) readnone
+  ret void
+}
+
+define void @prop_cb_def_argmem_readonly_fail(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_cb_def_argmem_readonly_fail
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1(ptr %p) memory(argmem:read)
+  ret void
+}
+
+define void @prop_cb_def_inaccessible_none(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_cb_def_inaccessible_none
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1(ptr %p) memory(inaccessiblemem:none)
+  ret void
+}
+
+define void @prop_cb_def_inaccessible_none_argmem_none(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_cb_def_inaccessible_none_argmem_none
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1(ptr %p) memory(inaccessiblemem:none, argmem:none)
+  ret void
+}
+
+define void @prop_cb_def_willreturn(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_cb_def_willreturn
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1(ptr %p) willreturn
+  ret void
+}
+
+define void @prop_cb_def_mustprogress(ptr %p) {
+; CHECK-LABEL: define {{[^@]+}}@prop_cb_def_mustprogress
+; CHECK-SAME: (ptr [[P:%.*]]) {
+; CHECK-NEXT:    call void @bar1(ptr [[P]])
+; CHECK-NEXT:    ret void
+;
+  call void @foo1(ptr %p) mustprogress
+  ret void
+}
diff --git a/llvm/test/Transforms/Inline/ret_attr_align_and_noundef.ll b/llvm/test/Transforms/Inline/ret_attr_align_and_noundef.ll
new file mode 100644
index 000000000000000..d66a3f3adcb1be4
--- /dev/null
+++ b/llvm/test/Transforms/Inline/ret_attr_align_and_noundef.ll
@@ -0,0 +1,268 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 2
+; RUN: opt -S -passes=inline %s | FileCheck %s
+; RUN: opt -S -passes='cgscc(inline)' %s | FileCheck %s
+; RUN: opt -S -passes='module-inline' %s | FileCheck %s
+
+declare ptr @foo()
+declare void @use.ptr(ptr) willreturn nounwind
+declare void @bar()
+define ptr @callee0123() {
+; CHECK-LABEL: define ptr @callee0123() {
+; CHECK-NEXT:    [[R:%.*]] = call ptr @foo()
+; CHECK-NEXT:    ret ptr [[R]]
+;
+  %r = call ptr @foo()
+  ret ptr %r
+}
+
+define ptr @caller0() {
+; CHECK-LABEL: define ptr @caller0() {
+; CHECK-NEXT:    [[R_I:%.*]] = call dereferenceable(16) ptr @foo()
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call dereferenceable(16) ptr @callee0123()
+  ret ptr %r
+}
+
+define ptr @caller1() {
+; CHECK-LABEL: define ptr @caller1() {
+; CHECK-NEXT:    [[R_I:%.*]] = call ptr @foo()
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call align(16) ptr @callee0123()
+  ret ptr %r
+}
+
+define ptr @caller2() {
+; CHECK-LABEL: define ptr @caller2() {
+; CHECK-NEXT:    [[R_I:%.*]] = call ptr @foo()
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call noundef ptr @callee0123()
+  ret ptr %r
+}
+
+define ptr @caller3() {
+; CHECK-LABEL: define ptr @caller3() {
+; CHECK-NEXT:    [[R_I:%.*]] = call dereferenceable_or_null(32) ptr @foo()
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call dereferenceable_or_null(32) ptr @callee0123()
+  ret ptr %r
+}
+
+define ptr @caller_0123_dornull() {
+; CHECK-LABEL: define ptr @caller_0123_dornull() {
+; CHECK-NEXT:    [[R_I:%.*]] = call dereferenceable_or_null(16) ptr @foo()
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call noundef align(32) dereferenceable_or_null(16) ptr @callee0123()
+  ret ptr %r
+}
+
+define ptr @caller_0123_d() {
+; CHECK-LABEL: define ptr @caller_0123_d() {
+; CHECK-NEXT:    [[R_I:%.*]] = call dereferenceable(16) ptr @foo()
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call noundef align(32) dereferenceable(16) ptr @callee0123()
+  ret ptr %r
+}
+
+define ptr @callee4() {
+; CHECK-LABEL: define ptr @callee4() {
+; CHECK-NEXT:    [[R:%.*]] = call ptr @foo()
+; CHECK-NEXT:    call void @bar()
+; CHECK-NEXT:    ret ptr [[R]]
+;
+  %r = call ptr @foo()
+  call void @bar()
+  ret ptr %r
+}
+
+define ptr @caller4_fail() {
+; CHECK-LABEL: define ptr @caller4_fail() {
+; CHECK-NEXT:    [[R_I:%.*]] = call ptr @foo()
+; CHECK-NEXT:    call void @bar()
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call noundef align(256) ptr @callee4()
+  ret ptr %r
+}
+
+define ptr @callee5() {
+; CHECK-LABEL: define ptr @callee5() {
+; CHECK-NEXT:    [[R:%.*]] = call align 64 ptr @foo()
+; CHECK-NEXT:    ret ptr [[R]]
+;
+  %r = call align(64) ptr @foo()
+  ret ptr %r
+}
+
+define ptr @caller5_fail() {
+; CHECK-LABEL: define ptr @caller5_fail() {
+; CHECK-NEXT:    [[R_I:%.*]] = call align 64 ptr @foo()
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call noundef align(32) ptr @callee5()
+  ret ptr %r
+}
+
+define ptr @caller5_okay() {
+; CHECK-LABEL: define ptr @caller5_okay() {
+; CHECK-NEXT:    [[R_I:%.*]] = call align 64 ptr @foo()
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call noundef align(128) ptr @callee5()
+  ret ptr %r
+}
+
+define ptr @callee6() {
+; CHECK-LABEL: define ptr @callee6() {
+; CHECK-NEXT:    [[R:%.*]] = call dereferenceable(16) ptr @foo()
+; CHECK-NEXT:    ret ptr [[R]]
+;
+  %r = call dereferenceable(16) ptr @foo()
+  ret ptr %r
+}
+
+define ptr @caller6_fail() {
+; CHECK-LABEL: define ptr @caller6_fail() {
+; CHECK-NEXT:    [[R_I:%.*]] = call dereferenceable(8) ptr @foo()
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call dereferenceable(8) ptr @callee6()
+  ret ptr %r
+}
+
+define ptr @caller6_okay() {
+; CHECK-LABEL: define ptr @caller6_okay() {
+; CHECK-NEXT:    [[R_I:%.*]] = call dereferenceable(32) ptr @foo()
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call dereferenceable(32) ptr @callee6()
+  ret ptr %r
+}
+
+define ptr @callee7() {
+; CHECK-LABEL: define ptr @callee7() {
+; CHECK-NEXT:    [[R:%.*]] = call dereferenceable_or_null(16) ptr @foo()
+; CHECK-NEXT:    ret ptr [[R]]
+;
+  %r = call dereferenceable_or_null(16) ptr @foo()
+  ret ptr %r
+}
+
+define ptr @caller7_fail() {
+; CHECK-LABEL: define ptr @caller7_fail() {
+; CHECK-NEXT:    [[R_I:%.*]] = call dereferenceable_or_null(8) ptr @foo()
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call dereferenceable_or_null(8) ptr @callee7()
+  ret ptr %r
+}
+
+define ptr @caller7_okay() {
+; CHECK-LABEL: define ptr @caller7_okay() {
+; CHECK-NEXT:    [[R_I:%.*]] = call dereferenceable_or_null(32) ptr @foo()
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call dereferenceable_or_null(32) ptr @callee7()
+  ret ptr %r
+}
+
+define ptr @callee8() {
+; CHECK-LABEL: define ptr @callee8() {
+; CHECK-NEXT:    [[R:%.*]] = call ptr @foo()
+; CHECK-NEXT:    ret ptr [[R]]
+;
+  %r = call ptr @foo()
+  ret ptr %r
+}
+
+define ptr @caller8_okay_use_after_poison_anyways() {
+; CHECK-LABEL: define ptr @caller8_okay_use_after_poison_anyways() {
+; CHECK-NEXT:    [[R_I:%.*]] = call nonnull ptr @foo()
+; CHECK-NEXT:    call void @use.ptr(ptr [[R_I]])
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call nonnull ptr @callee8()
+  call void @use.ptr(ptr %r)
+  ret ptr %r
+}
+
+
+define ptr @callee9() {
+; CHECK-LABEL: define ptr @callee9() {
+; CHECK-NEXT:    [[R:%.*]] = call noundef ptr @foo()
+; CHECK-NEXT:    ret ptr [[R]]
+;
+  %r = call noundef ptr @foo()
+  ret ptr %r
+}
+
+define ptr @caller9_fail_creates_ub() {
+; CHECK-LABEL: define ptr @caller9_fail_creates_ub() {
+; CHECK-NEXT:    [[R_I:%.*]] = call noundef ptr @foo()
+; CHECK-NEXT:    call void @use.ptr(ptr [[R_I]])
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call nonnull ptr @callee9()
+  call void @use.ptr(ptr %r)
+  ret ptr %r
+}
+
+define ptr @caller9_okay_is_ub_anyways() {
+; CHECK-LABEL: define ptr @caller9_okay_is_ub_anyways() {
+; CHECK-NEXT:    [[R_I:%.*]] = call noundef nonnull ptr @foo()
+; CHECK-NEXT:    call void @use.ptr(ptr [[R_I]])
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call noundef nonnull ptr @callee9()
+  call void @use.ptr(ptr %r)
+  ret ptr %r
+}
+
+
+define ptr @callee10() {
+; CHECK-LABEL: define ptr @callee10() {
+; CHECK-NEXT:    [[R:%.*]] = call ptr @foo()
+; CHECK-NEXT:    call void @use.ptr(ptr [[R]])
+; CHECK-NEXT:    ret ptr [[R]]
+;
+  %r = call ptr @foo()
+  call void @use.ptr(ptr %r)
+  ret ptr %r
+}
+
+define ptr @caller10_fail_maybe_poison() {
+; CHECK-LABEL: define ptr @caller10_fail_maybe_poison() {
+; CHECK-NEXT:    [[R_I:%.*]] = call ptr @foo()
+; CHECK-NEXT:    call void @use.ptr(ptr [[R_I]])
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call nonnull ptr @callee10()
+  ret ptr %r
+}
+
+
+define ptr @caller10_okay_will_be_ub() {
+; CHECK-LABEL: define ptr @caller10_okay_will_be_ub() {
+; CHECK-NEXT:    [[R_I:%.*]] = call nonnull ptr @foo()
+; CHECK-NEXT:    call void @use.ptr(ptr [[R_I]])
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call noundef nonnull ptr @callee10()
+  ret ptr %r
+}
+
+
+define noundef ptr @caller10_okay_will_be_ub_todo() {
+; CHECK-LABEL: define noundef ptr @caller10_okay_will_be_ub_todo() {
+; CHECK-NEXT:    [[R_I:%.*]] = call ptr @foo()
+; CHECK-NEXT:    call void @use.ptr(ptr [[R_I]])
+; CHECK-NEXT:    ret ptr [[R_I]]
+;
+  %r = call nonnull ptr @callee10()
+  ret ptr %r
+}

>From dcf3aa02360c09ea3b0c22cba77bb81b8706cd9d Mon Sep 17 00:00:00 2001
From: Noah Goldstein <goldstein.w.n at gmail.com>
Date: Tue, 19 Sep 2023 17:59:54 -0500
Subject: [PATCH 2/2] [Inliner] Fix bug when propagating poison generating
 return attributes

Poison generating return attributes can't be propagated the same as
others, as they can change the behavior of other uses and/or create UB
where it otherwise wouldn't have occurred.

For example:
```
define nonnull ptr @foo() {
    %p = call ptr @bar()
    call void @use(ptr %p)
    ret ptr %p
}
```

If we inline `@foo` and propagate `nonnull` to `@bar`, it could change
the behavior of `@use` as instead of taking `null`, `@use` will
now be passed `poison`.

This can be even worth in a case like:
```
define nonnull ptr @foo() {
    %p = call noundef ptr @bar()
    ret ptr %p
}
```

Where propagating `nonnull` to `@bar` will cause UB on `null` return
of `@bar` (`noundef` + `poison`) where it previously wouldn't
have occurred.

To fix this, we only propagate poison generating return attributes if
either 1) The only use of the callsite to propagate too is return and
the callsite to propagate too doesn't have `noundef`. Or 2) the
callsite to be be inlined has `noundef`.

The former case ensures no new UB or `poison` values will be
added. The latter is UB anyways if the value is `poison` so we can go
ahead without worrying about behavior changes.
---
 llvm/lib/Transforms/Utils/InlineFunction.cpp | 63 +++++++++++++++++++-
 1 file changed, 62 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp
index 56bc90a7c935292..7ee2cb8e78e6fbd 100644
--- a/llvm/lib/Transforms/Utils/InlineFunction.cpp
+++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp
@@ -1355,6 +1355,14 @@ static AttrBuilder IdentifyValidAttributes(CallBase &CB) {
     Valid.addDereferenceableOrNullAttr(DerefOrNullBytes);
   if (CB.hasRetAttr(Attribute::NoAlias))
     Valid.addAttribute(Attribute::NoAlias);
+  return Valid;
+}
+
+static AttrBuilder IdentifyValidPoisonGeneratingAttributes(CallBase &CB) {
+  AttrBuilder Valid(CB.getContext());
+  // Only allow these white listed attributes to be propagated back to the
+  // callee. This is because other attributes may only be valid on the call
+  // itself, i.e. attributes such as signext and zeroext.
   if (CB.hasRetAttr(Attribute::NonNull))
     Valid.addAttribute(Attribute::NonNull);
   return Valid;
@@ -1362,7 +1370,8 @@ static AttrBuilder IdentifyValidAttributes(CallBase &CB) {
 
 static void AddReturnAttributes(CallBase &CB, ValueToValueMapTy &VMap) {
   AttrBuilder Valid = IdentifyValidAttributes(CB);
-  if (!Valid.hasAttributes())
+  AttrBuilder ValidPG = IdentifyValidPoisonGeneratingAttributes(CB);
+  if (!Valid.hasAttributes() && !ValidPG.hasAttributes())
     return;
   auto *CalledFunction = CB.getCalledFunction();
   auto &Context = CalledFunction->getContext();
@@ -1407,6 +1416,58 @@ static void AddReturnAttributes(CallBase &CB, ValueToValueMapTy &VMap) {
     // dereferenceable_or_null etc). See AttrBuilder::merge for more details.
     AttributeList AL = NewRetVal->getAttributes();
     AttributeList NewAL = AL.addRetAttributes(Context, Valid);
+    // Attributes that may generate poison returns are a bit tricky. If we
+    // propagate them, other uses of the callsite might have there behavior
+    // change/or cause UB (if they have noundef) b.c of the new potential
+    // poison.
+    // Take the following three cases:
+    //
+    // 1)
+    // define nonnull ptr @foo() {
+    //   %p = call ptr @bar()
+    //   call void @use(ptr %p) willreturn nounwind
+    //   ret ptr %p
+    // }
+    //
+    // 2)
+    // define noundef nonnull ptr @foo() {
+    //   %p = call ptr @bar()
+    //   call void @use(ptr %p) willreturn nounwind
+    //   ret ptr %p
+    // }
+    //
+    // 3)
+    // define nonnull ptr @foo() {
+    //   %p = call noundef ptr @bar()
+    //   ret ptr %p
+    // }
+    //
+    // In case 1, we can't propagate nonnull because poison value in @use may
+    // change behavior or trigger UB.
+    // In case 2, we don't need to be concerned about propagating nonnull, as
+    // any new poison at @use will trigger UB anyways.
+    // In case 3, we can never propagate nonnull because it may create UB due to
+    // the noundef on @bar.
+    if (ValidPG.hasAttributes()) {
+      bool Okay = true;
+      if (!CB.hasRetAttr(Attribute::NoUndef)) {
+        Okay = !RetVal->hasRetAttr(Attribute::NoUndef);
+        // See if we use this outside of return context. In that case since we
+        // don't have noundef on the to-be-inlined callsite, we won't
+        // necessarily have UB so don't propagate. TODO: This is overly
+        // conservative, we could check the uses to see if poison would change
+        // behavior / trigger UB.
+        for (const llvm::User *U : RetVal->users()) {
+          Okay &= isa<ReturnInst>(U);
+          // Have a non-return user.
+          if (!Okay)
+            break;
+        }
+      }
+
+      if (Okay)
+        NewAL = NewAL.addRetAttributes(Context, ValidPG);
+    }
     NewRetVal->setAttributes(NewAL);
   }
 }



More information about the llvm-commits mailing list