/
trivial-dse-calls.ll
324 lines (300 loc) · 12.2 KB
/
trivial-dse-calls.ll
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt -dse -S < %s | FileCheck %s
declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture)
declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture)
declare void @unknown()
declare void @f(i8*)
declare void @f2(i8*, i8*)
; Basic case for DSEing a trivially dead writing call
define void @test_dead() {
; CHECK-LABEL: @test_dead(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: call void @f(i8* nocapture writeonly [[BITCAST]]) #[[ATTR1:[0-9]+]]
; CHECK-NEXT: ret void
;
%a = alloca i32, align 4
%bitcast = bitcast i32* %a to i8*
call void @f(i8* writeonly nocapture %bitcast) argmemonly nounwind willreturn
ret void
}
; Add in canonical lifetime intrinsics
define void @test_lifetime() {
; CHECK-LABEL: @test_lifetime(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 4, i8* [[BITCAST]])
; CHECK-NEXT: call void @f(i8* nocapture writeonly [[BITCAST]]) #[[ATTR1]]
; CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 4, i8* [[BITCAST]])
; CHECK-NEXT: ret void
;
%a = alloca i32, align 4
%bitcast = bitcast i32* %a to i8*
call void @llvm.lifetime.start.p0i8(i64 4, i8* %bitcast)
call void @f(i8* writeonly nocapture %bitcast) argmemonly nounwind willreturn
call void @llvm.lifetime.end.p0i8(i64 4, i8* %bitcast)
ret void
}
; Add some unknown calls just to point out that this is use based, not
; instruction order sensitive
define void @test_lifetime2() {
; CHECK-LABEL: @test_lifetime2(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 4, i8* [[BITCAST]])
; CHECK-NEXT: call void @unknown()
; CHECK-NEXT: call void @f(i8* nocapture writeonly [[BITCAST]]) #[[ATTR1]]
; CHECK-NEXT: call void @unknown()
; CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 4, i8* [[BITCAST]])
; CHECK-NEXT: ret void
;
%a = alloca i32, align 4
%bitcast = bitcast i32* %a to i8*
call void @llvm.lifetime.start.p0i8(i64 4, i8* %bitcast)
call void @unknown()
call void @f(i8* writeonly nocapture %bitcast) argmemonly nounwind willreturn
call void @unknown()
call void @llvm.lifetime.end.p0i8(i64 4, i8* %bitcast)
ret void
}
; As long as the result is unused, we can even remove reads of the alloca
; itself since the write will be dropped.
define void @test_dead_readwrite() {
; CHECK-LABEL: @test_dead_readwrite(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: call void @f(i8* nocapture [[BITCAST]]) #[[ATTR1]]
; CHECK-NEXT: ret void
;
%a = alloca i32, align 4
%bitcast = bitcast i32* %a to i8*
call void @f(i8* nocapture %bitcast) argmemonly nounwind willreturn
ret void
}
define i32 @test_neg_read_after() {
; CHECK-LABEL: @test_neg_read_after(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: call void @f(i8* nocapture writeonly [[BITCAST]]) #[[ATTR1]]
; CHECK-NEXT: [[RES:%.*]] = load i32, i32* [[A]], align 4
; CHECK-NEXT: ret i32 [[RES]]
;
%a = alloca i32, align 4
%bitcast = bitcast i32* %a to i8*
call void @f(i8* writeonly nocapture %bitcast) argmemonly nounwind willreturn
%res = load i32, i32* %a
ret i32 %res
}
define void @test_neg_infinite_loop() {
; CHECK-LABEL: @test_neg_infinite_loop(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: call void @f(i8* nocapture writeonly [[BITCAST]]) #[[ATTR2:[0-9]+]]
; CHECK-NEXT: ret void
;
%a = alloca i32, align 4
%bitcast = bitcast i32* %a to i8*
call void @f(i8* writeonly nocapture %bitcast) argmemonly nounwind
ret void
}
define void @test_neg_throw() {
; CHECK-LABEL: @test_neg_throw(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: call void @f(i8* nocapture writeonly [[BITCAST]]) #[[ATTR3:[0-9]+]]
; CHECK-NEXT: ret void
;
%a = alloca i32, align 4
%bitcast = bitcast i32* %a to i8*
call void @f(i8* writeonly nocapture %bitcast) argmemonly willreturn
ret void
}
define void @test_neg_extra_write() {
; CHECK-LABEL: @test_neg_extra_write(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: call void @f(i8* nocapture writeonly [[BITCAST]]) #[[ATTR4:[0-9]+]]
; CHECK-NEXT: ret void
;
%a = alloca i32, align 4
%bitcast = bitcast i32* %a to i8*
call void @f(i8* writeonly nocapture %bitcast) nounwind willreturn
ret void
}
; In this case, we can't remove a1 because we need to preserve the write to
; a2, and if we leave the call around, we need memory to pass to the first arg.
define void @test_neg_unmodeled_write() {
; CHECK-LABEL: @test_neg_unmodeled_write(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[A2:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: [[BITCAST2:%.*]] = bitcast i32* [[A2]] to i8*
; CHECK-NEXT: call void @f2(i8* nocapture writeonly [[BITCAST]], i8* [[BITCAST2]]) #[[ATTR1]]
; CHECK-NEXT: ret void
;
%a = alloca i32, align 4
%a2 = alloca i32, align 4
%bitcast = bitcast i32* %a to i8*
%bitcast2 = bitcast i32* %a2 to i8*
call void @f2(i8* nocapture writeonly %bitcast, i8* %bitcast2) argmemonly nounwind willreturn
ret void
}
define i32 @test_neg_captured_by_call() {
; CHECK-LABEL: @test_neg_captured_by_call(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[A2:%.*]] = alloca i8*, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: [[BITCAST2:%.*]] = bitcast i8** [[A2]] to i8*
; CHECK-NEXT: call void @f2(i8* writeonly [[BITCAST]], i8* [[BITCAST2]]) #[[ATTR1]]
; CHECK-NEXT: [[A_COPY_CAST:%.*]] = load i8*, i8** [[A2]], align 8
; CHECK-NEXT: [[A_COPY:%.*]] = bitcast i8* [[A_COPY_CAST]] to i32*
; CHECK-NEXT: [[RES:%.*]] = load i32, i32* [[A_COPY]], align 4
; CHECK-NEXT: ret i32 [[RES]]
;
%a = alloca i32, align 4
%a2 = alloca i8*, align 4
%bitcast = bitcast i32* %a to i8*
%bitcast2 = bitcast i8** %a2 to i8*
call void @f2(i8* writeonly %bitcast, i8* %bitcast2) argmemonly nounwind willreturn
%a_copy_cast = load i8*, i8** %a2
%a_copy = bitcast i8* %a_copy_cast to i32*
%res = load i32, i32* %a_copy
ret i32 %res
}
define i32 @test_neg_captured_before() {
; CHECK-LABEL: @test_neg_captured_before(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[A2:%.*]] = alloca i8*, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: [[BITCAST2:%.*]] = bitcast i8** [[A2]] to i8*
; CHECK-NEXT: store i8* [[BITCAST]], i8** [[A2]], align 8
; CHECK-NEXT: call void @f(i8* nocapture writeonly [[BITCAST]]) #[[ATTR1]]
; CHECK-NEXT: [[A_COPY_CAST:%.*]] = load i8*, i8** [[A2]], align 8
; CHECK-NEXT: [[A_COPY:%.*]] = bitcast i8* [[A_COPY_CAST]] to i32*
; CHECK-NEXT: [[RES:%.*]] = load i32, i32* [[A_COPY]], align 4
; CHECK-NEXT: ret i32 [[RES]]
;
%a = alloca i32, align 4
%a2 = alloca i8*, align 4
%bitcast = bitcast i32* %a to i8*
%bitcast2 = bitcast i8** %a2 to i8*
store i8* %bitcast, i8** %a2
call void @f(i8* writeonly nocapture %bitcast) argmemonly nounwind willreturn
%a_copy_cast = load i8*, i8** %a2
%a_copy = bitcast i8* %a_copy_cast to i32*
%res = load i32, i32* %a_copy
ret i32 %res
}
; Show that reading from unrelated memory is okay
define void @test_unreleated_read() {
; CHECK-LABEL: @test_unreleated_read(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[A2:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: [[BITCAST2:%.*]] = bitcast i32* [[A2]] to i8*
; CHECK-NEXT: call void @f2(i8* nocapture writeonly [[BITCAST]], i8* nocapture readonly [[BITCAST2]]) #[[ATTR1]]
; CHECK-NEXT: ret void
;
%a = alloca i32, align 4
%a2 = alloca i32, align 4
%bitcast = bitcast i32* %a to i8*
%bitcast2 = bitcast i32* %a2 to i8*
call void @f2(i8* nocapture writeonly %bitcast, i8* nocapture readonly %bitcast2) argmemonly nounwind willreturn
ret void
}
; But that removing a capture of an unrelated pointer isn't okay.
define void @test_neg_unreleated_capture() {
; CHECK-LABEL: @test_neg_unreleated_capture(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[A2:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: [[BITCAST2:%.*]] = bitcast i32* [[A2]] to i8*
; CHECK-NEXT: call void @f2(i8* nocapture writeonly [[BITCAST]], i8* readonly [[BITCAST2]]) #[[ATTR1]]
; CHECK-NEXT: ret void
;
%a = alloca i32, align 4
%a2 = alloca i32, align 4
%bitcast = bitcast i32* %a to i8*
%bitcast2 = bitcast i32* %a2 to i8*
call void @f2(i8* nocapture writeonly %bitcast, i8* readonly %bitcast2) argmemonly nounwind willreturn
ret void
}
; As long as the result is unused, we can even remove reads of the alloca
; itself since the write will be dropped.
define void @test_self_read() {
; CHECK-LABEL: @test_self_read(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: call void @f2(i8* nocapture writeonly [[BITCAST]], i8* nocapture readonly [[BITCAST]]) #[[ATTR1]]
; CHECK-NEXT: ret void
;
%a = alloca i32, align 4
%bitcast = bitcast i32* %a to i8*
call void @f2(i8* nocapture writeonly %bitcast, i8* nocapture readonly %bitcast) argmemonly nounwind willreturn
ret void
}
; TODO: We should be able to remove the call because while we don't know
; the size of the write done by the call, we do know the following store
; writes to the entire contents of the alloca.
define i32 @test_dse_overwrite() {
; CHECK-LABEL: @test_dse_overwrite(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: call void @f(i8* nocapture writeonly [[BITCAST]]) #[[ATTR1]]
; CHECK-NEXT: store i32 0, i32* [[A]], align 4
; CHECK-NEXT: [[V:%.*]] = load i32, i32* [[A]], align 4
; CHECK-NEXT: ret i32 [[V]]
;
%a = alloca i32, align 4
%bitcast = bitcast i32* %a to i8*
call void @f(i8* writeonly nocapture %bitcast) argmemonly nounwind willreturn
store i32 0, i32* %a
%v = load i32, i32* %a
ret i32 %v
}
; Negative case where we can read part of the value written by @f.
define i32 @test_neg_dse_partial_overwrite() {
; CHECK-LABEL: @test_neg_dse_partial_overwrite(
; CHECK-NEXT: [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A]] to i8*
; CHECK-NEXT: call void @f(i8* nocapture writeonly [[BITCAST]]) #[[ATTR1]]
; CHECK-NEXT: store i8 0, i8* [[BITCAST]], align 1
; CHECK-NEXT: [[V:%.*]] = load i32, i32* [[A]], align 4
; CHECK-NEXT: ret i32 [[V]]
;
%a = alloca i32, align 4
%bitcast = bitcast i32* %a to i8*
call void @f(i8* writeonly nocapture %bitcast) argmemonly nounwind willreturn
store i8 0, i8* %bitcast
%v = load i32, i32* %a
ret i32 %v
}
; Negative case where we don't know the size of a, and thus can't use the
; full overwrite reasoning
define i32 @test_neg_dse_unsized(i32* %a) {
; CHECK-LABEL: @test_neg_dse_unsized(
; CHECK-NEXT: [[BITCAST:%.*]] = bitcast i32* [[A:%.*]] to i8*
; CHECK-NEXT: call void @f(i8* nocapture writeonly [[BITCAST]]) #[[ATTR1]]
; CHECK-NEXT: store i32 0, i32* [[A]], align 4
; CHECK-NEXT: [[V:%.*]] = load i32, i32* [[A]], align 4
; CHECK-NEXT: ret i32 [[V]]
;
%bitcast = bitcast i32* %a to i8*
call void @f(i8* writeonly nocapture %bitcast) argmemonly nounwind willreturn
store i32 0, i32* %a
%v = load i32, i32* %a
ret i32 %v
}
@G = external global i8
; TODO: Should be able to kill call in analogous manner to test_dse_overwrite.
; Difference is non-alloca object.
define void @test_dse_non_alloca() {
; CHECK-LABEL: @test_dse_non_alloca(
; CHECK-NEXT: call void @f(i8* nocapture writeonly @G) #[[ATTR1]]
; CHECK-NEXT: store i8 0, i8* @G, align 1
; CHECK-NEXT: ret void
;
call void @f(i8* writeonly nocapture @G) argmemonly nounwind willreturn
store i8 0, i8* @G
ret void
}