Skip to content

Commit d1c4e37

Browse files
nikicrlavaee
authored andcommitted
[EarlyCSE] Add support for writeonly call CSE (llvm#145474)
Add support for CSE of writeonly calls, similar to the existing support for readonly calls.
1 parent a54a5be commit d1c4e37

File tree

3 files changed

+162
-5
lines changed

3 files changed

+162
-5
lines changed

clang/test/CodeGenCXX/auto-var-init.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1342,6 +1342,7 @@ TEST_UNINIT(base, base);
13421342
// ZERO-O1-NOT: !annotation
13431343

13441344
TEST_BRACES(base, base);
1345+
// ZERO-LABEL: @test_base_braces()
13451346
// CHECK-LABEL: @test_base_braces()
13461347
// CHECK: %braces = alloca %struct.base, align [[ALIGN:[0-9]*]]
13471348
// CHECK-NEXT: call void @llvm.memset{{.*}}(ptr align [[ALIGN]] %{{.*}}, i8 0, i64 8, i1 false)

llvm/lib/Transforms/Scalar/EarlyCSE.cpp

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ struct CallValue {
493493

494494
static bool canHandle(Instruction *Inst) {
495495
CallInst *CI = dyn_cast<CallInst>(Inst);
496-
if (!CI || !CI->onlyReadsMemory() ||
496+
if (!CI || (!CI->onlyReadsMemory() && !CI->onlyWritesMemory()) ||
497497
// FIXME: Currently the calls which may access the thread id may
498498
// be considered as not accessing the memory. But this is
499499
// problematic for coroutines, since coroutines may resume in a
@@ -1626,14 +1626,19 @@ bool EarlyCSE::processNode(DomTreeNode *Node) {
16261626
!(MemInst.isValid() && !MemInst.mayReadFromMemory()))
16271627
LastStore = nullptr;
16281628

1629-
// If this is a read-only call, process it.
1630-
if (CallValue::canHandle(&Inst)) {
1629+
// If this is a read-only or write-only call, process it. Skip store
1630+
// MemInsts, as they will be more precisely handled later on. Also skip
1631+
// memsets, as DSE may be able to optimize them better by removing the
1632+
// earlier rather than later store.
1633+
if (CallValue::canHandle(&Inst) &&
1634+
(!MemInst.isValid() || !MemInst.isStore()) && !isa<MemSetInst>(&Inst)) {
16311635
// If we have an available version of this call, and if it is the right
16321636
// generation, replace this instruction.
16331637
std::pair<Instruction *, unsigned> InVal = AvailableCalls.lookup(&Inst);
16341638
if (InVal.first != nullptr &&
16351639
isSameMemGeneration(InVal.second, CurrentGeneration, InVal.first,
1636-
&Inst)) {
1640+
&Inst) &&
1641+
InVal.first->mayReadFromMemory() == Inst.mayReadFromMemory()) {
16371642
LLVM_DEBUG(dbgs() << "EarlyCSE CSE CALL: " << Inst
16381643
<< " to: " << *InVal.first << '\n');
16391644
if (!DebugCounter::shouldExecute(CSECounter)) {
@@ -1651,6 +1656,11 @@ bool EarlyCSE::processNode(DomTreeNode *Node) {
16511656
continue;
16521657
}
16531658

1659+
// Increase memory generation for writes. Do this before inserting
1660+
// the call, so it has the generation after the write occurred.
1661+
if (Inst.mayWriteToMemory())
1662+
++CurrentGeneration;
1663+
16541664
// Otherwise, remember that we have this instruction.
16551665
AvailableCalls.insert(&Inst, std::make_pair(&Inst, CurrentGeneration));
16561666
continue;

llvm/test/Transforms/EarlyCSE/writeonly.ll

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
2-
; RUN: opt -S -passes=early-cse -earlycse-debug-hash < %s | FileCheck %s
2+
; RUN: opt -S -passes=early-cse -earlycse-debug-hash < %s | FileCheck %s --check-prefixes=CHECK,NO-MSSA
3+
; RUN: opt -S -passes='early-cse<memssa>' < %s | FileCheck %s --check-prefixes=CHECK,MSSA
34

45
@var = global i32 undef
56
declare void @foo() nounwind
@@ -15,3 +16,148 @@ define void @test() {
1516
store i32 2, ptr @var
1617
ret void
1718
}
19+
20+
declare void @writeonly_void() memory(write)
21+
22+
; Can CSE writeonly calls, including non-nounwind/willreturn.
23+
define void @writeonly_cse() {
24+
; CHECK-LABEL: @writeonly_cse(
25+
; CHECK-NEXT: call void @writeonly_void()
26+
; CHECK-NEXT: ret void
27+
;
28+
call void @writeonly_void()
29+
call void @writeonly_void()
30+
ret void
31+
}
32+
33+
; Can CSE, loads do not matter.
34+
define i32 @writeonly_cse_intervening_load(ptr %p) {
35+
; CHECK-LABEL: @writeonly_cse_intervening_load(
36+
; CHECK-NEXT: call void @writeonly_void()
37+
; CHECK-NEXT: [[V:%.*]] = load i32, ptr [[P:%.*]], align 4
38+
; CHECK-NEXT: ret i32 [[V]]
39+
;
40+
call void @writeonly_void()
41+
%v = load i32, ptr %p
42+
call void @writeonly_void()
43+
ret i32 %v
44+
}
45+
46+
; Cannot CSE, the store may be to the same memory.
47+
define void @writeonly_cse_intervening_store(ptr %p) {
48+
; CHECK-LABEL: @writeonly_cse_intervening_store(
49+
; CHECK-NEXT: call void @writeonly_void()
50+
; CHECK-NEXT: store i32 0, ptr [[P:%.*]], align 4
51+
; CHECK-NEXT: call void @writeonly_void()
52+
; CHECK-NEXT: ret void
53+
;
54+
call void @writeonly_void()
55+
store i32 0, ptr %p
56+
call void @writeonly_void()
57+
ret void
58+
}
59+
60+
; Can CSE, the store does not alias the writeonly call.
61+
define void @writeonly_cse_intervening_noalias_store(ptr noalias %p) {
62+
; NO-MSSA-LABEL: @writeonly_cse_intervening_noalias_store(
63+
; NO-MSSA-NEXT: call void @writeonly_void()
64+
; NO-MSSA-NEXT: store i32 0, ptr [[P:%.*]], align 4
65+
; NO-MSSA-NEXT: call void @writeonly_void()
66+
; NO-MSSA-NEXT: ret void
67+
;
68+
; MSSA-LABEL: @writeonly_cse_intervening_noalias_store(
69+
; MSSA-NEXT: call void @writeonly_void()
70+
; MSSA-NEXT: store i32 0, ptr [[P:%.*]], align 4
71+
; MSSA-NEXT: ret void
72+
;
73+
call void @writeonly_void()
74+
store i32 0, ptr %p
75+
call void @writeonly_void()
76+
ret void
77+
}
78+
79+
; Cannot CSE loads across writeonly call.
80+
define i32 @load_cse_across_writeonly(ptr %p) {
81+
; CHECK-LABEL: @load_cse_across_writeonly(
82+
; CHECK-NEXT: [[V1:%.*]] = load i32, ptr [[P:%.*]], align 4
83+
; CHECK-NEXT: call void @writeonly_void()
84+
; CHECK-NEXT: [[V2:%.*]] = load i32, ptr [[P]], align 4
85+
; CHECK-NEXT: [[RES:%.*]] = sub i32 [[V1]], [[V2]]
86+
; CHECK-NEXT: ret i32 [[RES]]
87+
;
88+
%v1 = load i32, ptr %p
89+
call void @writeonly_void()
90+
%v2 = load i32, ptr %p
91+
%res = sub i32 %v1, %v2
92+
ret i32 %res
93+
}
94+
95+
; Can CSE loads across eliminated writeonly call.
96+
define i32 @load_cse_across_csed_writeonly(ptr %p) {
97+
; CHECK-LABEL: @load_cse_across_csed_writeonly(
98+
; CHECK-NEXT: call void @writeonly_void()
99+
; CHECK-NEXT: [[V2:%.*]] = load i32, ptr [[P:%.*]], align 4
100+
; CHECK-NEXT: ret i32 0
101+
;
102+
call void @writeonly_void()
103+
%v1 = load i32, ptr %p
104+
call void @writeonly_void()
105+
%v2 = load i32, ptr %p
106+
%res = sub i32 %v1, %v2
107+
ret i32 %res
108+
}
109+
110+
declare i32 @writeonly(ptr %p) memory(write)
111+
112+
; Can CSE writeonly calls with arg and return.
113+
define i32 @writeonly_ret_cse(ptr %p) {
114+
; CHECK-LABEL: @writeonly_ret_cse(
115+
; CHECK-NEXT: [[V2:%.*]] = call i32 @writeonly(ptr [[P:%.*]])
116+
; CHECK-NEXT: ret i32 0
117+
;
118+
%v1 = call i32 @writeonly(ptr %p)
119+
%v2 = call i32 @writeonly(ptr %p)
120+
%res = sub i32 %v1, %v2
121+
ret i32 %res
122+
}
123+
124+
; Cannot CSE writeonly calls with different arguments.
125+
define i32 @writeonly_different_args(ptr %p1, ptr %p2) {
126+
; CHECK-LABEL: @writeonly_different_args(
127+
; CHECK-NEXT: [[V1:%.*]] = call i32 @writeonly(ptr [[P1:%.*]])
128+
; CHECK-NEXT: [[V2:%.*]] = call i32 @writeonly(ptr [[P2:%.*]])
129+
; CHECK-NEXT: [[RES:%.*]] = sub i32 [[V1]], [[V2]]
130+
; CHECK-NEXT: ret i32 [[RES]]
131+
;
132+
%v1 = call i32 @writeonly(ptr %p1)
133+
%v2 = call i32 @writeonly(ptr %p2)
134+
%res = sub i32 %v1, %v2
135+
ret i32 %res
136+
}
137+
138+
declare void @callee()
139+
140+
; These are weird cases where the same call is both readonly and writeonly
141+
; based on call-site attributes. I believe this implies that both calls are
142+
; actually readnone and safe to CSE, but leave them alone to be conservative.
143+
define void @readonly_and_writeonly() {
144+
; CHECK-LABEL: @readonly_and_writeonly(
145+
; CHECK-NEXT: call void @callee() #[[ATTR2:[0-9]+]]
146+
; CHECK-NEXT: call void @callee() #[[ATTR1]]
147+
; CHECK-NEXT: ret void
148+
;
149+
call void @callee() memory(read)
150+
call void @callee() memory(write)
151+
ret void
152+
}
153+
154+
define void @writeonly_and_readonly() {
155+
; CHECK-LABEL: @writeonly_and_readonly(
156+
; CHECK-NEXT: call void @callee() #[[ATTR1]]
157+
; CHECK-NEXT: call void @callee() #[[ATTR2]]
158+
; CHECK-NEXT: ret void
159+
;
160+
call void @callee() memory(write)
161+
call void @callee() memory(read)
162+
ret void
163+
}

0 commit comments

Comments
 (0)