[llvm] [EarlyCSE] Remove void return restriction for call CSE (PR #145320)
Nikita Popov via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 23 05:45:52 PDT 2025
https://github.com/nikic created https://github.com/llvm/llvm-project/pull/145320
For readonly/readnone calls returning void we can't CSE the return
value. However, making these participate in CSE is still useful,
because it allows DCE of calls that are not willreturn/nounwind
(something no other part of LLVM is capable of removing).
The more interesting use-case is CSE for writeonly calls (not
yet supported), but I figured this change makes sense independently.
There is no impact on compile-time.
>From 7177cd1a8e6e86d8e1cab7a1384c3a98eb4d23bc Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Mon, 23 Jun 2025 14:36:53 +0200
Subject: [PATCH 1/2] Add test with readonly/readnone void function
---
llvm/test/Transforms/EarlyCSE/basic.ll | 28 ++++++++++++++++++++++++--
1 file changed, 26 insertions(+), 2 deletions(-)
diff --git a/llvm/test/Transforms/EarlyCSE/basic.ll b/llvm/test/Transforms/EarlyCSE/basic.ll
index 2c6b2a9613924..d7c6bd3fa9079 100644
--- a/llvm/test/Transforms/EarlyCSE/basic.ll
+++ b/llvm/test/Transforms/EarlyCSE/basic.ll
@@ -146,6 +146,30 @@ define i32 @test5(ptr%P) {
ret i32 %Diff
}
+declare void @void_func()
+
+define void @void_func_cse_readonly(ptr %P) {
+; CHECK-LABEL: @void_func_cse_readonly(
+; CHECK-NEXT: call void @void_func(ptr [[P:%.*]]) #[[ATTR1:[0-9]+]]
+; CHECK-NEXT: call void @void_func(ptr [[P]]) #[[ATTR1]]
+; CHECK-NEXT: ret void
+;
+ call void @void_func(ptr %P) memory(read)
+ call void @void_func(ptr %P) memory(read)
+ ret void
+}
+
+define void @void_func_cse_readnone(ptr %P) {
+; CHECK-LABEL: @void_func_cse_readnone(
+; CHECK-NEXT: call void @void_func(ptr [[P:%.*]]) #[[ATTR2:[0-9]+]]
+; CHECK-NEXT: call void @void_func(ptr [[P]]) #[[ATTR2]]
+; CHECK-NEXT: ret void
+;
+ call void @void_func(ptr %P) memory(none)
+ call void @void_func(ptr %P) memory(none)
+ ret void
+}
+
!0 = !{!"branch_weights", i32 95}
!1 = !{!"branch_weights", i32 95}
@@ -186,7 +210,7 @@ define void @test7(ptr%P) {
;; Readnone functions aren't invalidated by stores.
define i32 @test8(ptr%P) {
; CHECK-LABEL: @test8(
-; CHECK-NEXT: [[V1:%.*]] = call i32 @func(ptr [[P:%.*]]) #[[ATTR2:[0-9]+]]
+; CHECK-NEXT: [[V1:%.*]] = call i32 @func(ptr [[P:%.*]]) #[[ATTR2]]
; CHECK-NEXT: store i32 4, ptr [[P]], align 4
; CHECK-NEXT: ret i32 0
;
@@ -202,7 +226,7 @@ define i32 @test8(ptr%P) {
define i32 @test9(ptr%P) {
; CHECK-LABEL: @test9(
; CHECK-NEXT: store i32 4, ptr [[P:%.*]], align 4
-; CHECK-NEXT: [[V1:%.*]] = call i32 @func(ptr [[P]]) #[[ATTR1:[0-9]+]]
+; CHECK-NEXT: [[V1:%.*]] = call i32 @func(ptr [[P]]) #[[ATTR1]]
; CHECK-NEXT: store i32 5, ptr [[P]], align 4
; CHECK-NEXT: ret i32 [[V1]]
;
>From ebcc8a0a15d662a4593e67ee8c9fe87e8bae1687 Mon Sep 17 00:00:00 2001
From: Nikita Popov <npopov at redhat.com>
Date: Mon, 23 Jun 2025 10:01:38 +0200
Subject: [PATCH 2/2] [EarlyCSE] Remove void return restriction for call CSE
For readonly/readnone calls returns void we can't CSE the return
value. However, making these participate in CSE is still useful,
because we *can* CSE calls that are not willreturn/nounwind
(which otherwise cannot be DCEd).
The more interesting use-case is CSE for writeonly calls (not
yet supported), but I figured this change makes sense independently.
There is no impact on compile-time.
---
llvm/lib/Transforms/Scalar/EarlyCSE.cpp | 6 +---
llvm/test/Transforms/EarlyCSE/basic.ll | 2 --
llvm/test/Transforms/EarlyCSE/flags.ll | 26 +++++++-------
.../Transforms/EarlyCSE/noalias-addrspace.ll | 34 +++++++++----------
4 files changed, 31 insertions(+), 37 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/EarlyCSE.cpp b/llvm/lib/Transforms/Scalar/EarlyCSE.cpp
index e1e283f171d38..381de60fcface 100644
--- a/llvm/lib/Transforms/Scalar/EarlyCSE.cpp
+++ b/llvm/lib/Transforms/Scalar/EarlyCSE.cpp
@@ -133,7 +133,7 @@ struct SimpleValue {
}
}
}
- return CI->doesNotAccessMemory() && !CI->getType()->isVoidTy() &&
+ return CI->doesNotAccessMemory() &&
// FIXME: Currently the calls which may access the thread id may
// be considered as not accessing the memory. But this is
// problematic for coroutines, since coroutines may resume in a
@@ -492,10 +492,6 @@ struct CallValue {
}
static bool canHandle(Instruction *Inst) {
- // Don't value number anything that returns void.
- if (Inst->getType()->isVoidTy())
- return false;
-
CallInst *CI = dyn_cast<CallInst>(Inst);
if (!CI || !CI->onlyReadsMemory() ||
// FIXME: Currently the calls which may access the thread id may
diff --git a/llvm/test/Transforms/EarlyCSE/basic.ll b/llvm/test/Transforms/EarlyCSE/basic.ll
index d7c6bd3fa9079..f877235ed9787 100644
--- a/llvm/test/Transforms/EarlyCSE/basic.ll
+++ b/llvm/test/Transforms/EarlyCSE/basic.ll
@@ -151,7 +151,6 @@ declare void @void_func()
define void @void_func_cse_readonly(ptr %P) {
; CHECK-LABEL: @void_func_cse_readonly(
; CHECK-NEXT: call void @void_func(ptr [[P:%.*]]) #[[ATTR1:[0-9]+]]
-; CHECK-NEXT: call void @void_func(ptr [[P]]) #[[ATTR1]]
; CHECK-NEXT: ret void
;
call void @void_func(ptr %P) memory(read)
@@ -162,7 +161,6 @@ define void @void_func_cse_readonly(ptr %P) {
define void @void_func_cse_readnone(ptr %P) {
; CHECK-LABEL: @void_func_cse_readnone(
; CHECK-NEXT: call void @void_func(ptr [[P:%.*]]) #[[ATTR2:[0-9]+]]
-; CHECK-NEXT: call void @void_func(ptr [[P]]) #[[ATTR2]]
; CHECK-NEXT: ret void
;
call void @void_func(ptr %P) memory(none)
diff --git a/llvm/test/Transforms/EarlyCSE/flags.ll b/llvm/test/Transforms/EarlyCSE/flags.ll
index 78b3818b211da..dcaaacbac639f 100644
--- a/llvm/test/Transforms/EarlyCSE/flags.ll
+++ b/llvm/test/Transforms/EarlyCSE/flags.ll
@@ -3,7 +3,7 @@
; RUN: opt -passes='early-cse<memssa>' -S < %s | FileCheck %s
declare void @use(i1)
-declare void @use.ptr(ptr) memory(read)
+declare void @use.ptr(i32, ptr) memory(read)
define void @test1(float %x, float %y) {
; CHECK-LABEL: @test1(
@@ -52,42 +52,42 @@ define void @test_inbounds_program_not_ub_if_first_gep_poison(ptr %ptr, i64 %n)
define void @load_both_nonnull(ptr %p) {
; CHECK-LABEL: @load_both_nonnull(
; CHECK-NEXT: [[V1:%.*]] = load ptr, ptr [[P:%.*]], align 8, !nonnull [[META0:![0-9]+]]
-; CHECK-NEXT: call void @use.ptr(ptr [[V1]])
-; CHECK-NEXT: call void @use.ptr(ptr [[V1]])
+; CHECK-NEXT: call void @use.ptr(i32 0, ptr [[V1]])
+; CHECK-NEXT: call void @use.ptr(i32 1, ptr [[V1]])
; CHECK-NEXT: ret void
;
%v1 = load ptr, ptr %p, !nonnull !{}
- call void @use.ptr(ptr %v1)
+ call void @use.ptr(i32 0, ptr %v1)
%v2 = load ptr, ptr %p, !nonnull !{}
- call void @use.ptr(ptr %v2)
+ call void @use.ptr(i32 1, ptr %v2)
ret void
}
define void @load_first_nonnull(ptr %p) {
; CHECK-LABEL: @load_first_nonnull(
; CHECK-NEXT: [[V1:%.*]] = load ptr, ptr [[P:%.*]], align 8
-; CHECK-NEXT: call void @use.ptr(ptr [[V1]])
-; CHECK-NEXT: call void @use.ptr(ptr [[V1]])
+; CHECK-NEXT: call void @use.ptr(i32 0, ptr [[V1]])
+; CHECK-NEXT: call void @use.ptr(i32 1, ptr [[V1]])
; CHECK-NEXT: ret void
;
%v1 = load ptr, ptr %p, !nonnull !{}
- call void @use.ptr(ptr %v1)
+ call void @use.ptr(i32 0, ptr %v1)
%v2 = load ptr, ptr %p
- call void @use.ptr(ptr %v2)
+ call void @use.ptr(i32 1, ptr %v2)
ret void
}
define void @load_first_nonnull_noundef(ptr %p) {
; CHECK-LABEL: @load_first_nonnull_noundef(
; CHECK-NEXT: [[V1:%.*]] = load ptr, ptr [[P:%.*]], align 8, !nonnull [[META0]], !noundef [[META0]]
-; CHECK-NEXT: call void @use.ptr(ptr [[V1]])
-; CHECK-NEXT: call void @use.ptr(ptr [[V1]])
+; CHECK-NEXT: call void @use.ptr(i32 0, ptr [[V1]])
+; CHECK-NEXT: call void @use.ptr(i32 1, ptr [[V1]])
; CHECK-NEXT: ret void
;
%v1 = load ptr, ptr %p, !nonnull !{}, !noundef !{}
- call void @use.ptr(ptr %v1)
+ call void @use.ptr(i32 0, ptr %v1)
%v2 = load ptr, ptr %p
- call void @use.ptr(ptr %v2)
+ call void @use.ptr(i32 1, ptr %v2)
ret void
}
diff --git a/llvm/test/Transforms/EarlyCSE/noalias-addrspace.ll b/llvm/test/Transforms/EarlyCSE/noalias-addrspace.ll
index 0a001b55f684c..2d67186531481 100644
--- a/llvm/test/Transforms/EarlyCSE/noalias-addrspace.ll
+++ b/llvm/test/Transforms/EarlyCSE/noalias-addrspace.ll
@@ -2,20 +2,20 @@
; RUN: opt -passes='early-cse<memssa>' -S < %s | FileCheck %s
declare void @use(i1)
-declare void @use.ptr(ptr) memory(read)
+declare void @use.ptr(i32, ptr) memory(read)
define void @load_first_noalias_addrspace(ptr %p) {
; CHECK-LABEL: define void @load_first_noalias_addrspace(
; CHECK-SAME: ptr [[P:%.*]]) {
; CHECK-NEXT: [[V1:%.*]] = load ptr, ptr [[P]], align 8, !nonnull [[META0:![0-9]+]], !noundef [[META0]], !noalias.addrspace [[META1:![0-9]+]]
-; CHECK-NEXT: call void @use.ptr(ptr [[V1]])
-; CHECK-NEXT: call void @use.ptr(ptr [[V1]])
+; CHECK-NEXT: call void @use.ptr(i32 0, ptr [[V1]])
+; CHECK-NEXT: call void @use.ptr(i32 1, ptr [[V1]])
; CHECK-NEXT: ret void
;
%v1 = load ptr, ptr %p, !nonnull !{}, !noundef !{}, !noalias.addrspace !0
- call void @use.ptr(ptr %v1)
+ call void @use.ptr(i32 0, ptr %v1)
%v2 = load ptr, ptr %p
- call void @use.ptr(ptr %v2)
+ call void @use.ptr(i32 1, ptr %v2)
ret void
}
@@ -23,14 +23,14 @@ define void @load_both_same_noalias_addrspace(ptr %p) {
; CHECK-LABEL: define void @load_both_same_noalias_addrspace(
; CHECK-SAME: ptr [[P:%.*]]) {
; CHECK-NEXT: [[V1:%.*]] = load ptr, ptr [[P]], align 8, !nonnull [[META0]], !noundef [[META0]], !noalias.addrspace [[META1]]
-; CHECK-NEXT: call void @use.ptr(ptr [[V1]])
-; CHECK-NEXT: call void @use.ptr(ptr [[V1]])
+; CHECK-NEXT: call void @use.ptr(i32 0, ptr [[V1]])
+; CHECK-NEXT: call void @use.ptr(i32 1, ptr [[V1]])
; CHECK-NEXT: ret void
;
%v1 = load ptr, ptr %p, !nonnull !{}, !noundef !{}, !noalias.addrspace !0
- call void @use.ptr(ptr %v1)
+ call void @use.ptr(i32 0, ptr %v1)
%v2 = load ptr, ptr %p, !noalias.addrspace !0
- call void @use.ptr(ptr %v2)
+ call void @use.ptr(i32 1, ptr %v2)
ret void
}
@@ -38,14 +38,14 @@ define void @load_both_disjoint_noalias_addrspace(ptr %p) {
; CHECK-LABEL: define void @load_both_disjoint_noalias_addrspace(
; CHECK-SAME: ptr [[P:%.*]]) {
; CHECK-NEXT: [[V1:%.*]] = load ptr, ptr [[P]], align 8, !nonnull [[META0]], !noundef [[META0]], !noalias.addrspace [[META1]]
-; CHECK-NEXT: call void @use.ptr(ptr [[V1]])
-; CHECK-NEXT: call void @use.ptr(ptr [[V1]])
+; CHECK-NEXT: call void @use.ptr(i32 0, ptr [[V1]])
+; CHECK-NEXT: call void @use.ptr(i32 1, ptr [[V1]])
; CHECK-NEXT: ret void
;
%v1 = load ptr, ptr %p, !nonnull !{}, !noundef !{}, !noalias.addrspace !0
- call void @use.ptr(ptr %v1)
+ call void @use.ptr(i32 0, ptr %v1)
%v2 = load ptr, ptr %p, !noalias.addrspace !1
- call void @use.ptr(ptr %v2)
+ call void @use.ptr(i32 1, ptr %v2)
ret void
}
@@ -53,14 +53,14 @@ define void @load_both_overlap_noalias_addrspace(ptr %p) {
; CHECK-LABEL: define void @load_both_overlap_noalias_addrspace(
; CHECK-SAME: ptr [[P:%.*]]) {
; CHECK-NEXT: [[V1:%.*]] = load ptr, ptr [[P]], align 8, !nonnull [[META0]], !noundef [[META0]], !noalias.addrspace [[META1]]
-; CHECK-NEXT: call void @use.ptr(ptr [[V1]])
-; CHECK-NEXT: call void @use.ptr(ptr [[V1]])
+; CHECK-NEXT: call void @use.ptr(i32 0, ptr [[V1]])
+; CHECK-NEXT: call void @use.ptr(i32 1, ptr [[V1]])
; CHECK-NEXT: ret void
;
%v1 = load ptr, ptr %p, !nonnull !{}, !noundef !{}, !noalias.addrspace !0
- call void @use.ptr(ptr %v1)
+ call void @use.ptr(i32 0, ptr %v1)
%v2 = load ptr, ptr %p, !noalias.addrspace !2
- call void @use.ptr(ptr %v2)
+ call void @use.ptr(i32 1, ptr %v2)
ret void
}
More information about the llvm-commits
mailing list