xref: /llvm-project/llvm/test/CodeGen/AArch64/implicit-null-check.ll (revision 5ddce70ef0e5a641d7fea95e31fc5e2439cb98cb)
1; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
2; RUN: llc < %s -verify-machineinstrs -O3 -mtriple=aarch64-unknown-unknown -enable-implicit-null-checks | FileCheck %s
3
4; Basic test for implicit null check conversion - this is analogous to the
5; file with the same name in the X86 tree, but adjusted to remove patterns
6; related to memory folding of arithmetic (since aarch64 doesn't), and add
7; a couple of aarch64 specific tests.
8
9define i32 @imp_null_check_load_fallthrough(ptr %x) {
10; CHECK-LABEL: imp_null_check_load_fallthrough:
11; CHECK:       // %bb.0: // %entry
12; CHECK-NEXT:  .Ltmp0:
13; CHECK-NEXT:    ldr w0, [x0] // on-fault: .LBB0_2
14; CHECK-NEXT:  // %bb.1: // %not_null
15; CHECK-NEXT:    ret
16; CHECK-NEXT:  .LBB0_2:
17; CHECK-NEXT:    mov w0, #42
18; CHECK-NEXT:    ret
19 entry:
20  %c = icmp eq ptr %x, null
21  br i1 %c, label %is_null, label %not_null, !make.implicit !0
22
23 not_null:
24  %t = load i32, ptr %x
25  ret i32 %t
26
27is_null:
28  ret i32 42
29}
30
31
32define i32 @imp_null_check_load_reorder(ptr %x) {
33; CHECK-LABEL: imp_null_check_load_reorder:
34; CHECK:       // %bb.0: // %entry
35; CHECK-NEXT:  .Ltmp1:
36; CHECK-NEXT:    ldr w0, [x0] // on-fault: .LBB1_2
37; CHECK-NEXT:  // %bb.1: // %not_null
38; CHECK-NEXT:    ret
39; CHECK-NEXT:  .LBB1_2:
40; CHECK-NEXT:    mov w0, #42
41; CHECK-NEXT:    ret
42 entry:
43  %c = icmp eq ptr %x, null
44  br i1 %c, label %is_null, label %not_null, !make.implicit !0
45
46 is_null:
47  ret i32 42
48
49 not_null:
50  %t = load i32, ptr %x
51  ret i32 %t
52}
53
54define i32 @imp_null_check_unordered_load(ptr %x) {
55; CHECK-LABEL: imp_null_check_unordered_load:
56; CHECK:       // %bb.0: // %entry
57; CHECK-NEXT:  .Ltmp2:
58; CHECK-NEXT:    ldr w0, [x0] // on-fault: .LBB2_2
59; CHECK-NEXT:  // %bb.1: // %not_null
60; CHECK-NEXT:    ret
61; CHECK-NEXT:  .LBB2_2:
62; CHECK-NEXT:    mov w0, #42
63; CHECK-NEXT:    ret
64 entry:
65  %c = icmp eq ptr %x, null
66  br i1 %c, label %is_null, label %not_null, !make.implicit !0
67
68 is_null:
69  ret i32 42
70
71 not_null:
72  %t = load atomic i32, ptr %x unordered, align 4
73  ret i32 %t
74}
75
76
77; TODO: Can be converted into implicit check.
78;; Probably could be implicit, but we're conservative for now
79define i32 @imp_null_check_seq_cst_load(ptr %x) {
80; CHECK-LABEL: imp_null_check_seq_cst_load:
81; CHECK:       // %bb.0: // %entry
82; CHECK-NEXT:    cbz x0, .LBB3_2
83; CHECK-NEXT:  // %bb.1: // %not_null
84; CHECK-NEXT:    ldar w0, [x0]
85; CHECK-NEXT:    ret
86; CHECK-NEXT:  .LBB3_2:
87; CHECK-NEXT:    mov w0, #42
88; CHECK-NEXT:    ret
89 entry:
90  %c = icmp eq ptr %x, null
91  br i1 %c, label %is_null, label %not_null, !make.implicit !0
92
93 is_null:
94  ret i32 42
95
96 not_null:
97  %t = load atomic i32, ptr %x seq_cst, align 4
98  ret i32 %t
99}
100
101;; Might be memory mapped IO, so can't rely on fault behavior
102define i32 @imp_null_check_volatile_load(ptr %x) {
103; CHECK-LABEL: imp_null_check_volatile_load:
104; CHECK:       // %bb.0: // %entry
105; CHECK-NEXT:    cbz x0, .LBB4_2
106; CHECK-NEXT:  // %bb.1: // %not_null
107; CHECK-NEXT:    ldr w0, [x0]
108; CHECK-NEXT:    ret
109; CHECK-NEXT:  .LBB4_2:
110; CHECK-NEXT:    mov w0, #42
111; CHECK-NEXT:    ret
112 entry:
113  %c = icmp eq ptr %x, null
114  br i1 %c, label %is_null, label %not_null, !make.implicit !0
115
116 is_null:
117  ret i32 42
118
119 not_null:
120  %t = load volatile i32, ptr %x, align 4
121  ret i32 %t
122}
123
124
125define i8 @imp_null_check_load_i8(ptr %x) {
126; CHECK-LABEL: imp_null_check_load_i8:
127; CHECK:       // %bb.0: // %entry
128; CHECK-NEXT:  .Ltmp3:
129; CHECK-NEXT:    ldrb w0, [x0] // on-fault: .LBB5_2
130; CHECK-NEXT:  // %bb.1: // %not_null
131; CHECK-NEXT:    ret
132; CHECK-NEXT:  .LBB5_2:
133; CHECK-NEXT:    mov w0, #42
134; CHECK-NEXT:    ret
135 entry:
136  %c = icmp eq ptr %x, null
137  br i1 %c, label %is_null, label %not_null, !make.implicit !0
138
139 is_null:
140  ret i8 42
141
142 not_null:
143  %t = load i8, ptr %x
144  ret i8 %t
145}
146
147define i256 @imp_null_check_load_i256(ptr %x) {
148; CHECK-LABEL: imp_null_check_load_i256:
149; CHECK:       // %bb.0: // %entry
150; CHECK-NEXT:    cbz x0, .LBB6_2
151; CHECK-NEXT:  // %bb.1: // %not_null
152; CHECK-NEXT:    ldp x2, x3, [x0, #16]
153; CHECK-NEXT:    ldp x0, x1, [x0]
154; CHECK-NEXT:    ret
155; CHECK-NEXT:  .LBB6_2:
156; CHECK-NEXT:    mov x1, xzr
157; CHECK-NEXT:    mov x2, xzr
158; CHECK-NEXT:    mov x3, xzr
159; CHECK-NEXT:    mov w0, #42
160; CHECK-NEXT:    ret
161 entry:
162  %c = icmp eq ptr %x, null
163  br i1 %c, label %is_null, label %not_null, !make.implicit !0
164
165 is_null:
166  ret i256 42
167
168 not_null:
169  %t = load i256, ptr %x
170  ret i256 %t
171}
172
173
174
175define i32 @imp_null_check_gep_load(ptr %x) {
176; CHECK-LABEL: imp_null_check_gep_load:
177; CHECK:       // %bb.0: // %entry
178; CHECK-NEXT:  .Ltmp4:
179; CHECK-NEXT:    ldr w0, [x0, #128] // on-fault: .LBB7_2
180; CHECK-NEXT:  // %bb.1: // %not_null
181; CHECK-NEXT:    ret
182; CHECK-NEXT:  .LBB7_2:
183; CHECK-NEXT:    mov w0, #42
184; CHECK-NEXT:    ret
185 entry:
186  %c = icmp eq ptr %x, null
187  br i1 %c, label %is_null, label %not_null, !make.implicit !0
188
189 is_null:
190  ret i32 42
191
192 not_null:
193  %x.gep = getelementptr i32, ptr %x, i32 32
194  %t = load i32, ptr %x.gep
195  ret i32 %t
196}
197
198define i32 @imp_null_check_add_result(ptr %x, i32 %p) {
199; CHECK-LABEL: imp_null_check_add_result:
200; CHECK:       // %bb.0: // %entry
201; CHECK-NEXT:  .Ltmp5:
202; CHECK-NEXT:    ldr w8, [x0] // on-fault: .LBB8_2
203; CHECK-NEXT:  // %bb.1: // %not_null
204; CHECK-NEXT:    add w0, w8, w1
205; CHECK-NEXT:    ret
206; CHECK-NEXT:  .LBB8_2:
207; CHECK-NEXT:    mov w0, #42
208; CHECK-NEXT:    ret
209 entry:
210  %c = icmp eq ptr %x, null
211  br i1 %c, label %is_null, label %not_null, !make.implicit !0
212
213 is_null:
214  ret i32 42
215
216 not_null:
217  %t = load i32, ptr %x
218  %p1 = add i32 %t, %p
219  ret i32 %p1
220}
221
222; Can hoist over a potential faulting instruction as long as we don't
223; change the conditions under which the instruction faults.
224define i32 @imp_null_check_hoist_over_udiv(ptr %x, i32 %a, i32 %b) {
225; CHECK-LABEL: imp_null_check_hoist_over_udiv:
226; CHECK:       // %bb.0: // %entry
227; CHECK-NEXT:    cbz x0, .LBB9_2
228; CHECK-NEXT:  // %bb.1: // %not_null
229; CHECK-NEXT:    udiv w8, w1, w2
230; CHECK-NEXT:    ldr w9, [x0]
231; CHECK-NEXT:    add w0, w9, w8
232; CHECK-NEXT:    ret
233; CHECK-NEXT:  .LBB9_2:
234; CHECK-NEXT:    mov w0, #42
235; CHECK-NEXT:    ret
236 entry:
237  %c = icmp eq ptr %x, null
238  br i1 %c, label %is_null, label %not_null, !make.implicit !0
239
240 is_null:
241  ret i32 42
242
243 not_null:
244  %p1 = udiv i32 %a, %b
245  %t = load i32, ptr %x
246  %res = add i32 %t, %p1
247  ret i32 %res
248}
249
250
251; TODO: We should be able to hoist this - we can on x86, why isn't this
252; working for aarch64?  Aliasing?
253define i32 @imp_null_check_hoist_over_unrelated_load(ptr %x, ptr %y, ptr %z) {
254; CHECK-LABEL: imp_null_check_hoist_over_unrelated_load:
255; CHECK:       // %bb.0: // %entry
256; CHECK-NEXT:    cbz x0, .LBB10_2
257; CHECK-NEXT:  // %bb.1: // %not_null
258; CHECK-NEXT:    ldr w8, [x1]
259; CHECK-NEXT:    ldr w0, [x0]
260; CHECK-NEXT:    str w8, [x2]
261; CHECK-NEXT:    ret
262; CHECK-NEXT:  .LBB10_2:
263; CHECK-NEXT:    mov w0, #42
264; CHECK-NEXT:    ret
265 entry:
266  %c = icmp eq ptr %x, null
267  br i1 %c, label %is_null, label %not_null, !make.implicit !0
268
269 is_null:
270  ret i32 42
271
272 not_null:
273  %t0 = load i32, ptr %y
274  %t1 = load i32, ptr %x
275  store i32 %t0, ptr %z
276  ret i32 %t1
277}
278
279define i32 @imp_null_check_gep_load_with_use_dep(ptr %x, i32 %a) {
280; CHECK-LABEL: imp_null_check_gep_load_with_use_dep:
281; CHECK:       // %bb.0: // %entry
282; CHECK-NEXT:  .Ltmp6:
283; CHECK-NEXT:    ldr w8, [x0] // on-fault: .LBB11_2
284; CHECK-NEXT:  // %bb.1: // %not_null
285; CHECK-NEXT:    add w9, w0, w1
286; CHECK-NEXT:    add w8, w9, w8
287; CHECK-NEXT:    add w0, w8, #4
288; CHECK-NEXT:    ret
289; CHECK-NEXT:  .LBB11_2:
290; CHECK-NEXT:    mov w0, #42
291; CHECK-NEXT:    ret
292 entry:
293  %c = icmp eq ptr %x, null
294  br i1 %c, label %is_null, label %not_null, !make.implicit !0
295
296 is_null:
297  ret i32 42
298
299 not_null:
300  %x.loc = getelementptr i32, ptr %x, i32 1
301  %y = ptrtoint ptr %x.loc to i32
302  %b = add i32 %a, %y
303  %t = load i32, ptr %x
304  %z = add i32 %t, %b
305  ret i32 %z
306}
307
308;; TODO: We could handle this case as we can lift the fence into the
309;; previous block before the conditional without changing behavior.
310define i32 @imp_null_check_load_fence1(ptr %x) {
311; CHECK-LABEL: imp_null_check_load_fence1:
312; CHECK:       // %bb.0: // %entry
313; CHECK-NEXT:    cbz x0, .LBB12_2
314; CHECK-NEXT:  // %bb.1: // %not_null
315; CHECK-NEXT:    dmb ishld
316; CHECK-NEXT:    ldr w0, [x0]
317; CHECK-NEXT:    ret
318; CHECK-NEXT:  .LBB12_2:
319; CHECK-NEXT:    mov w0, #42
320; CHECK-NEXT:    ret
321entry:
322  %c = icmp eq ptr %x, null
323  br i1 %c, label %is_null, label %not_null, !make.implicit !0
324
325is_null:
326  ret i32 42
327
328not_null:
329  fence acquire
330  %t = load i32, ptr %x
331  ret i32 %t
332}
333
334;; TODO: We could handle this case as we can lift the fence into the
335;; previous block before the conditional without changing behavior.
336define i32 @imp_null_check_load_fence2(ptr %x) {
337; CHECK-LABEL: imp_null_check_load_fence2:
338; CHECK:       // %bb.0: // %entry
339; CHECK-NEXT:    cbz x0, .LBB13_2
340; CHECK-NEXT:  // %bb.1: // %not_null
341; CHECK-NEXT:    dmb ish
342; CHECK-NEXT:    ldr w0, [x0]
343; CHECK-NEXT:    ret
344; CHECK-NEXT:  .LBB13_2:
345; CHECK-NEXT:    mov w0, #42
346; CHECK-NEXT:    ret
347entry:
348  %c = icmp eq ptr %x, null
349  br i1 %c, label %is_null, label %not_null, !make.implicit !0
350
351is_null:
352  ret i32 42
353
354not_null:
355  fence seq_cst
356  %t = load i32, ptr %x
357  ret i32 %t
358}
359
360; TODO: We can fold to implicit null here, not sure why this isn't working
361define void @imp_null_check_store(ptr %x) {
362; CHECK-LABEL: imp_null_check_store:
363; CHECK:       // %bb.0: // %entry
364; CHECK-NEXT:    cbz x0, .LBB14_2
365; CHECK-NEXT:  // %bb.1: // %not_null
366; CHECK-NEXT:    mov w8, #1
367; CHECK-NEXT:    str w8, [x0]
368; CHECK-NEXT:  .LBB14_2: // %common.ret
369; CHECK-NEXT:    ret
370 entry:
371  %c = icmp eq ptr %x, null
372  br i1 %c, label %is_null, label %not_null, !make.implicit !0
373
374 is_null:
375  ret void
376
377 not_null:
378  store i32 1, ptr %x
379  ret void
380}
381
382;; TODO: can be implicit
383define void @imp_null_check_unordered_store(ptr %x) {
384; CHECK-LABEL: imp_null_check_unordered_store:
385; CHECK:       // %bb.0: // %entry
386; CHECK-NEXT:    cbz x0, .LBB15_2
387; CHECK-NEXT:  // %bb.1: // %not_null
388; CHECK-NEXT:    mov w8, #1
389; CHECK-NEXT:    str w8, [x0]
390; CHECK-NEXT:  .LBB15_2: // %common.ret
391; CHECK-NEXT:    ret
392 entry:
393  %c = icmp eq ptr %x, null
394  br i1 %c, label %is_null, label %not_null, !make.implicit !0
395
396 is_null:
397  ret void
398
399 not_null:
400  store atomic i32 1, ptr %x unordered, align 4
401  ret void
402}
403
404define i32 @imp_null_check_neg_gep_load(ptr %x) {
405; CHECK-LABEL: imp_null_check_neg_gep_load:
406; CHECK:       // %bb.0: // %entry
407; CHECK-NEXT:  .Ltmp7:
408; CHECK-NEXT:    ldur w0, [x0, #-128] // on-fault: .LBB16_2
409; CHECK-NEXT:  // %bb.1: // %not_null
410; CHECK-NEXT:    ret
411; CHECK-NEXT:  .LBB16_2:
412; CHECK-NEXT:    mov w0, #42
413; CHECK-NEXT:    ret
414 entry:
415  %c = icmp eq ptr %x, null
416  br i1 %c, label %is_null, label %not_null, !make.implicit !0
417
418 is_null:
419  ret i32 42
420
421 not_null:
422  %x.gep = getelementptr i32, ptr %x, i32 -32
423  %t = load i32, ptr %x.gep
424  ret i32 %t
425}
426
427!0 = !{}
428