File: trivial-dse-calls.ll

package info (click to toggle)
llvm-toolchain-19 1%3A19.1.7-3~deb12u1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm-proposed-updates
  • size: 1,998,492 kB
  • sloc: cpp: 6,951,680; ansic: 1,486,157; asm: 913,598; python: 232,024; f90: 80,126; objc: 75,281; lisp: 37,276; pascal: 16,990; sh: 10,009; ml: 5,058; perl: 4,724; awk: 3,523; makefile: 3,167; javascript: 2,504; xml: 892; fortran: 664; cs: 573
file content (288 lines) | stat: -rw-r--r-- 9,868 bytes parent folder | download | duplicates (9)
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
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt -passes=dse -S < %s | FileCheck %s

declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture)
declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture)

declare void @unknown()
declare void @f(ptr)
declare void @f2(ptr, ptr)
declare ptr @f3(ptr, ptr)

; Basic case for DSEing a trivially dead writing call
define void @test_dead() {
; CHECK-LABEL: @test_dead(
; CHECK-NEXT:    ret void
;
  %a = alloca i32, align 4
  call void @f(ptr writeonly nocapture %a) 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:    call void @llvm.lifetime.start.p0(i64 4, ptr [[A]])
; CHECK-NEXT:    call void @llvm.lifetime.end.p0(i64 4, ptr [[A]])
; CHECK-NEXT:    ret void
;
  %a = alloca i32, align 4
  call void @llvm.lifetime.start.p0(i64 4, ptr %a)
  call void @f(ptr writeonly nocapture %a) argmemonly nounwind willreturn
  call void @llvm.lifetime.end.p0(i64 4, ptr %a)
  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:    call void @llvm.lifetime.start.p0(i64 4, ptr [[A]])
; CHECK-NEXT:    call void @unknown()
; CHECK-NEXT:    call void @unknown()
; CHECK-NEXT:    call void @llvm.lifetime.end.p0(i64 4, ptr [[A]])
; CHECK-NEXT:    ret void
;
  %a = alloca i32, align 4
  call void @llvm.lifetime.start.p0(i64 4, ptr %a)
  call void @unknown()
  call void @f(ptr writeonly nocapture %a) argmemonly nounwind willreturn
  call void @unknown()
  call void @llvm.lifetime.end.p0(i64 4, ptr %a)
  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:    ret void
;
  %a = alloca i32, align 4
  call void @f(ptr nocapture %a) 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:    call void @f(ptr nocapture writeonly [[A]]) #[[ATTR1:[0-9]+]]
; CHECK-NEXT:    [[RES:%.*]] = load i32, ptr [[A]], align 4
; CHECK-NEXT:    ret i32 [[RES]]
;
  %a = alloca i32, align 4
  call void @f(ptr writeonly nocapture %a) argmemonly nounwind willreturn
  %res = load i32, ptr %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:    call void @f(ptr nocapture writeonly [[A]]) #[[ATTR2:[0-9]+]]
; CHECK-NEXT:    ret void
;
  %a = alloca i32, align 4
  call void @f(ptr writeonly nocapture %a) argmemonly nounwind
  ret void
}

define void @test_neg_throw() {
; CHECK-LABEL: @test_neg_throw(
; CHECK-NEXT:    [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT:    call void @f(ptr nocapture writeonly [[A]]) #[[ATTR3:[0-9]+]]
; CHECK-NEXT:    ret void
;
  %a = alloca i32, align 4
  call void @f(ptr writeonly nocapture %a) 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:    call void @f(ptr nocapture writeonly [[A]]) #[[ATTR4:[0-9]+]]
; CHECK-NEXT:    ret void
;
  %a = alloca i32, align 4
  call void @f(ptr writeonly nocapture %a) 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:    call void @f2(ptr nocapture writeonly [[A]], ptr [[A2]]) #[[ATTR1]]
; CHECK-NEXT:    ret void
;
  %a = alloca i32, align 4
  %a2 = alloca i32, align 4
  call void @f2(ptr nocapture writeonly %a, ptr %a2) 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 ptr, align 4
; CHECK-NEXT:    call void @f2(ptr writeonly [[A]], ptr [[A2]]) #[[ATTR1]]
; CHECK-NEXT:    [[A_COPY_CAST:%.*]] = load ptr, ptr [[A2]], align 8
; CHECK-NEXT:    [[RES:%.*]] = load i32, ptr [[A_COPY_CAST]], align 4
; CHECK-NEXT:    ret i32 [[RES]]
;
  %a = alloca i32, align 4
  %a2 = alloca ptr, align 4
  call void @f2(ptr writeonly %a, ptr %a2) argmemonly nounwind willreturn
  %a_copy_cast = load ptr, ptr %a2
  %res = load i32, ptr %a_copy_cast
  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 ptr, align 4
; CHECK-NEXT:    store ptr [[A]], ptr [[A2]], align 8
; CHECK-NEXT:    call void @f(ptr nocapture writeonly [[A]]) #[[ATTR1]]
; CHECK-NEXT:    [[A_COPY_CAST:%.*]] = load ptr, ptr [[A2]], align 8
; CHECK-NEXT:    [[RES:%.*]] = load i32, ptr [[A_COPY_CAST]], align 4
; CHECK-NEXT:    ret i32 [[RES]]
;
  %a = alloca i32, align 4
  %a2 = alloca ptr, align 4
  store ptr %a, ptr %a2
  call void @f(ptr writeonly nocapture %a) argmemonly nounwind willreturn
  %a_copy_cast = load ptr, ptr %a2
  %res = load i32, ptr %a_copy_cast
  ret i32 %res
}

; Callee might be dead, but op bundle has unknown semantics and thus isn't.
define void @test_new_op_bundle() {
; CHECK-LABEL: @test_new_op_bundle(
; CHECK-NEXT:    [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT:    call void @f(ptr nocapture writeonly [[A]]) #[[ATTR1]] [ "unknown"(ptr [[A]]) ]
; CHECK-NEXT:    ret void
;
  %a = alloca i32, align 4
  call void @f(ptr writeonly nocapture %a) argmemonly nounwind willreturn ["unknown" (ptr %a)]
  ret void
}

; Show that reading from unrelated memory is okay
define void @test_unreleated_read() {
; CHECK-LABEL: @test_unreleated_read(
; CHECK-NEXT:    ret void
;
  %a = alloca i32, align 4
  %a2 = alloca i32, align 4
  call void @f2(ptr nocapture writeonly %a, ptr nocapture readonly %a2) argmemonly nounwind willreturn
  ret void
}

; Removing a capture is also okay. The capture can only be in the return value
; (which is unused) or written into the dead out parameter.
define void @test_unrelated_capture() {
; CHECK-LABEL: @test_unrelated_capture(
; CHECK-NEXT:    ret void
;
  %a = alloca i32, align 4
  %a2 = alloca i32, align 4
  call ptr @f3(ptr nocapture writeonly %a, ptr readonly %a2) argmemonly nounwind willreturn
  ret void
}

; Cannot remove call, as %a2 is captured via the return value.
define i8 @test_neg_unrelated_capture_used_via_return() {
; CHECK-LABEL: @test_neg_unrelated_capture_used_via_return(
; CHECK-NEXT:    [[A:%.*]] = alloca i32, align 4
; CHECK-NEXT:    [[A2:%.*]] = alloca i32, align 4
; CHECK-NEXT:    [[CAPTURE:%.*]] = call ptr @f3(ptr nocapture writeonly [[A]], ptr readonly [[A2]]) #[[ATTR1]]
; CHECK-NEXT:    [[V:%.*]] = load i8, ptr [[CAPTURE]], align 1
; CHECK-NEXT:    ret i8 [[V]]
;
  %a = alloca i32, align 4
  %a2 = alloca i32, align 4
  %capture = call ptr @f3(ptr nocapture writeonly %a, ptr readonly %a2) argmemonly nounwind willreturn
  %v = load i8, ptr %capture
  ret i8 %v
}

; 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:    ret void
;
  %a = alloca i32, align 4
  call void @f2(ptr nocapture writeonly %a, ptr nocapture readonly %a) argmemonly nounwind willreturn
  ret void
}

; We can 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:    store i32 0, ptr [[A]], align 4
; CHECK-NEXT:    [[V:%.*]] = load i32, ptr [[A]], align 4
; CHECK-NEXT:    ret i32 [[V]]
;
  %a = alloca i32, align 4
  call void @f(ptr writeonly nocapture %a) argmemonly nounwind willreturn
  store i32 0, ptr %a
  %v = load i32, ptr %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:    call void @f(ptr nocapture writeonly [[A]]) #[[ATTR1]]
; CHECK-NEXT:    store i8 0, ptr [[A]], align 1
; CHECK-NEXT:    [[V:%.*]] = load i32, ptr [[A]], align 4
; CHECK-NEXT:    ret i32 [[V]]
;
  %a = alloca i32, align 4
  call void @f(ptr writeonly nocapture %a) argmemonly nounwind willreturn
  store i8 0, ptr %a
  %v = load i32, ptr %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(ptr %a) {
; CHECK-LABEL: @test_neg_dse_unsized(
; CHECK-NEXT:    call void @f(ptr nocapture writeonly [[A:%.*]]) #[[ATTR1]]
; CHECK-NEXT:    store i32 0, ptr [[A]], align 4
; CHECK-NEXT:    [[V:%.*]] = load i32, ptr [[A]], align 4
; CHECK-NEXT:    ret i32 [[V]]
;
  call void @f(ptr writeonly nocapture %a) argmemonly nounwind willreturn
  store i32 0, ptr %a
  %v = load i32, ptr %a
  ret i32 %v
}

@G = global i8 0

; Same as test_dse_overwrite, but with a non-alloca object.
define void @test_dse_non_alloca() {
; CHECK-LABEL: @test_dse_non_alloca(
; CHECK-NEXT:    store i8 0, ptr @G, align 1
; CHECK-NEXT:    ret void
;
  call void @f(ptr writeonly nocapture @G) argmemonly nounwind willreturn
  store i8 0, ptr @G
  ret void
}