xref: /llvm-project/llvm/test/CodeGen/AArch64/argument-blocks-array-of-struct.ll (revision e4ecd83fe969c77c23f7a5bd4d5ed521550b5737)
1; RUN: llc -mtriple=aarch64-none-linux-gnu -o - %s | FileCheck %s
2
3;; Check that the llvm aarch64 backend can handle arrays of
4;; structs and vice versa when passed from IR.
5;; (this layering is something clang would normally simplify)
6;;
7;; Some of these examples are not ABI compliant and they're not
8;; meant to be. For instance according to the ABI an aggregate
9;; with more than 4 members must go in memory. This restriction
10;; is applied earlier in the compilation process so here we do
11;; see 8 member types in registers.
12;;
13;; When we have more than 8 members we simply run out of registers
14;; and that's what produces the 8 limit here.
15
16;; Plain arrays
17
18define [ 0 x double ] @array_0() {
19; CHECK-LABEL: array_0:
20; CHECK:  ret
21  ret [ 0 x double ] zeroinitializer
22}
23
24define [ 1 x double ] @array_1() {
25; CHECK-LABEL: array_1:
26; CHECK:       movi d0, #0000000000000000
27; CHECK-NEXT:  ret
28  ret [ 1 x double ] zeroinitializer
29}
30
31define [ 8 x double ] @array_8() {
32; CHECK-LABEL: array_8:
33; CHECK:       movi d0, #0000000000000000
34; CHECK-NEXT:  movi d1, #0000000000000000
35; CHECK-NEXT:  movi d2, #0000000000000000
36; CHECK-NEXT:  movi d3, #0000000000000000
37; CHECK-NEXT:  movi d4, #0000000000000000
38; CHECK-NEXT:  movi d5, #0000000000000000
39; CHECK-NEXT:  movi d6, #0000000000000000
40; CHECK-NEXT:  movi d7, #0000000000000000
41; CHECK-NEXT:  ret
42  ret [ 8 x double ] zeroinitializer
43}
44
45;; > 8 items goes on the stack
46
47define [ 9 x double ] @array_9() {
48; CHECK-LABEL: array_9:
49; CHECK:      movi v0.2d, #0000000000000000
50; CHECK-NEXT: str xzr, [x8, #64]
51; CHECK-NEXT: stp q0, q0, [x8, #32]
52; CHECK-NEXT: stp q0, q0, [x8]
53; CHECK-NEXT:  ret
54  ret [ 9 x double ] zeroinitializer
55}
56
57;; Won't use any registers, just checking for assumptions.
58%T_STRUCT_0M = type { }
59
60define %T_STRUCT_0M @struct_zero_fields() {
61; CHECK-LABEL: struct_zero_fields:
62; CHECK:  ret
63  ret %T_STRUCT_0M zeroinitializer
64}
65
66define [ 1 x %T_STRUCT_0M ] @array_of_struct_zero_fields() {
67; CHECK-LABEL: array_of_struct_zero_fields:
68; CHECK:  ret
69  ret [ 1 x %T_STRUCT_0M ] zeroinitializer
70}
71
72define [ 2 x %T_STRUCT_0M ] @array_of_struct_zero_fields_in_struct() {
73; CHECK-LABEL: array_of_struct_zero_fields_in_struct:
74; CHECK:  ret
75  ret [ 2 x %T_STRUCT_0M ] zeroinitializer
76}
77
78%T_STRUCT_1M = type { i32 }
79
80define %T_STRUCT_1M @struct_one_field() {
81; CHECK-LABEL: struct_one_field:
82; CHECK:       w0, wzr
83; CHECK-NEXT:  ret
84  ret %T_STRUCT_1M zeroinitializer
85}
86
87define [ 1 x %T_STRUCT_1M ] @array_of_struct_one_field() {
88; CHECK-LABEL: array_of_struct_one_field:
89; CHECK:       w0, wzr
90; CHECK-NEXT:  ret
91  ret [ 1 x %T_STRUCT_1M ] zeroinitializer
92}
93
94;; This one will be a reg block
95define [ 2 x %T_STRUCT_1M ] @array_of_struct_one_field_2() {
96; CHECK-LABEL: array_of_struct_one_field_2:
97; CHECK:       w0, wzr
98; CHECK:       w1, wzr
99; CHECK-NEXT:  ret
100  ret [ 2 x %T_STRUCT_1M ] zeroinitializer
101}
102
103;; Different types for each field, will not be put in a reg block
104%T_STRUCT_DIFFM = type { double, i32 }
105
106define %T_STRUCT_DIFFM @struct_different_field_types() {
107; CHECK-LABEL: struct_different_field_types:
108; CHECK:       movi d0, #0000000000000000
109; CHECK-NEXT:  w0, wzr
110; CHECK-NEXT:  ret
111  ret %T_STRUCT_DIFFM zeroinitializer
112}
113
114define [ 1 x %T_STRUCT_DIFFM ] @array_of_struct_different_field_types() {
115; CHECK-LABEL: array_of_struct_different_field_types:
116; CHECK:       movi d0, #0000000000000000
117; CHECK-NEXT:  w0, wzr
118; CHECK-NEXT:  ret
119  ret [ 1 x %T_STRUCT_DIFFM ] zeroinitializer
120}
121
122define [ 2 x %T_STRUCT_DIFFM ] @array_of_struct_different_field_types_2() {
123; CHECK-LABEL: array_of_struct_different_field_types_2:
124; CHECK:       movi d0, #0000000000000000
125; CHECK-NEXT:  movi d1, #0000000000000000
126; CHECK-NEXT:  w0, wzr
127; CHECK-NEXT:  w1, wzr
128; CHECK-NEXT:  ret
129  ret [ 2 x %T_STRUCT_DIFFM ] zeroinitializer
130}
131
132;; Each field is the same type, can be put in a reg block
133%T_STRUCT_SAMEM = type { double, double }
134
135;; Here isn't a block as such, we just allocate two consecutive registers
136define %T_STRUCT_SAMEM @struct_same_field_types() {
137; CHECK-LABEL: struct_same_field_types:
138; CHECK:       movi d0, #0000000000000000
139; CHECK-NEXT:  movi d1, #0000000000000000
140; CHECK-NEXT:  ret
141  ret %T_STRUCT_SAMEM zeroinitializer
142}
143
144define [ 1 x %T_STRUCT_SAMEM ] @array_of_struct_same_field_types() {
145; CHECK-LABEL: array_of_struct_same_field_types:
146; CHECK:       movi d0, #0000000000000000
147; CHECK-NEXT:  movi d1, #0000000000000000
148; CHECK-NEXT:  ret
149  ret [ 1 x %T_STRUCT_SAMEM ] zeroinitializer
150}
151
152define [ 2 x %T_STRUCT_SAMEM ] @array_of_struct_same_field_types_2() {
153; CHECK-LABEL: array_of_struct_same_field_types_2:
154; CHECK:       movi d0, #0000000000000000
155; CHECK-NEXT:  movi d1, #0000000000000000
156; CHECK-NEXT:  movi d2, #0000000000000000
157; CHECK-NEXT:  movi d3, #0000000000000000
158; CHECK-NEXT:  ret
159  ret [ 2 x %T_STRUCT_SAMEM ] zeroinitializer
160}
161
162;; Same field type but integer this time. Put into x registers instead.
163%T_STRUCT_SAMEM_INT = type { i64, i64 }
164
165define %T_STRUCT_SAMEM_INT @struct_same_field_types_int() {
166; CHECK-LABEL: struct_same_field_types_int:
167; CHECK:       x0, xzr
168; CHECK-NEXT:  x1, xzr
169; CHECK-NEXT:  ret
170  ret %T_STRUCT_SAMEM_INT zeroinitializer
171}
172
173define [ 1 x %T_STRUCT_SAMEM_INT ] @array_of_struct_same_field_types_int() {
174; CHECK-LABEL: array_of_struct_same_field_types_int:
175; CHECK:       x0, xzr
176; CHECK-NEXT:  x1, xzr
177; CHECK-NEXT:  ret
178  ret [ 1 x %T_STRUCT_SAMEM_INT ] zeroinitializer
179}
180
181define [ 2 x %T_STRUCT_SAMEM_INT ] @array_of_struct_same_field_types_int_2() {
182; CHECK-LABEL: array_of_struct_same_field_types_int_2:
183; CHECK:       x0, xzr
184; CHECK-NEXT:  x1, xzr
185; CHECK-NEXT:  x2, xzr
186; CHECK-NEXT:  x3, xzr
187; CHECK-NEXT:  ret
188  ret [ 2 x %T_STRUCT_SAMEM_INT ] zeroinitializer
189}
190
191;; An aggregate of more than 8 items must go in memory.
192;; 4x2 struct fields = 8 items so it goes in a block.
193
194define [ 4 x %T_STRUCT_SAMEM ] @array_of_struct_8_fields() {
195; CHECK-LABEL: array_of_struct_8_fields:
196; CHECK:       movi d0, #0000000000000000
197; CHECK-NEXT:  movi d1, #0000000000000000
198; CHECK-NEXT:  movi d2, #0000000000000000
199; CHECK-NEXT:  movi d3, #0000000000000000
200; CHECK-NEXT:  movi d4, #0000000000000000
201; CHECK-NEXT:  movi d5, #0000000000000000
202; CHECK-NEXT:  movi d6, #0000000000000000
203; CHECK-NEXT:  movi d7, #0000000000000000
204; CHECK-NEXT:  ret
205  ret [ 4 x %T_STRUCT_SAMEM ] zeroinitializer
206}
207
208;; 5x2 fields = 10 so it is returned in memory.
209
210define [ 5 x %T_STRUCT_SAMEM ] @array_of_struct_in_memory() {
211; CHECK-LABEL: array_of_struct_in_memory:
212; CHECK:       movi    v0.2d, #0000000000000000
213; CHECK-NEXT:  stp     q0, q0, [x8, #48]
214; CHECK-NEXT:  stp     q0, q0, [x8, #16]
215; CHECK-NEXT:  str     q0, [x8]
216; CHECK-NEXT:  ret
217  ret [ 5 x %T_STRUCT_SAMEM ] zeroinitializer
218}
219
220;; A struct whose field is an array.
221%T_STRUCT_ARRAYM = type { [ 2 x double ]};
222
223define %T_STRUCT_ARRAYM @struct_array_field() {
224; CHECK-LABEL: struct_array_field:
225; CHECK:       movi    d0, #0000000000000000
226; CHECK-NEXT:  movi    d1, #0000000000000000
227; CHECK-NEXT:  ret
228  ret %T_STRUCT_ARRAYM zeroinitializer
229}
230
231define [ 1 x %T_STRUCT_ARRAYM ] @array_of_struct_array_field() {
232; CHECK-LABEL: array_of_struct_array_field:
233; CHECK:       movi    d0, #0000000000000000
234; CHECK-NEXT:  movi    d1, #0000000000000000
235; CHECK-NEXT:  ret
236  ret [ 1 x %T_STRUCT_ARRAYM ] zeroinitializer
237}
238
239define [ 2 x %T_STRUCT_ARRAYM ] @array_of_struct_array_field_2() {
240; CHECK-LABEL: array_of_struct_array_field_2:
241; CHECK:       movi    d0, #0000000000000000
242; CHECK-NEXT:  movi    d1, #0000000000000000
243; CHECK-NEXT:  movi    d2, #0000000000000000
244; CHECK-NEXT:  movi    d3, #0000000000000000
245; CHECK-NEXT:  ret
246  ret [ 2 x %T_STRUCT_ARRAYM ] zeroinitializer
247}
248
249;; All non-aggregate fields must have the same type, all through the
250;; overall aggreagate. This is false here because of the i32.
251%T_NESTED_STRUCT_DIFFM = type {
252  [ 1 x { { double, double } } ],
253  [ 1 x { { double, i32 } } ]
254};
255
256define %T_NESTED_STRUCT_DIFFM @struct_nested_different_field_types() {
257; CHECK-LABEL: struct_nested_different_field_types:
258; CHECK:       movi d0, #0000000000000000
259; CHECK:       movi d1, #0000000000000000
260; CHECK:       movi d2, #0000000000000000
261; CHECK-NEXT:  w0, wzr
262; CHECK-NEXT:  ret
263  ret %T_NESTED_STRUCT_DIFFM zeroinitializer
264}
265
266define [ 1 x %T_NESTED_STRUCT_DIFFM ] @array_of_struct_nested_different_field_types() {
267; CHECK-LABEL: array_of_struct_nested_different_field_types:
268; CHECK:       movi d0, #0000000000000000
269; CHECK:       movi d1, #0000000000000000
270; CHECK:       movi d2, #0000000000000000
271; CHECK-NEXT:  w0, wzr
272; CHECK-NEXT:  ret
273  ret [ 1 x %T_NESTED_STRUCT_DIFFM ] zeroinitializer
274}
275
276define [ 2 x %T_NESTED_STRUCT_DIFFM ] @array_of_struct_nested_different_field_types_2() {
277; CHECK-LABEL: array_of_struct_nested_different_field_types_2:
278; CHECK:       movi d0, #0000000000000000
279; CHECK:       movi d1, #0000000000000000
280; CHECK:       movi d2, #0000000000000000
281; CHECK-NEXT:  movi d3, #0000000000000000
282; CHECK-NEXT:  movi d4, #0000000000000000
283; CHECK-NEXT:  movi d5, #0000000000000000
284; CHECK-NEXT:  w0, wzr
285; CHECK-NEXT:  w1, wzr
286; CHECK-NEXT:  ret
287  ret [ 2 x %T_NESTED_STRUCT_DIFFM ] zeroinitializer
288}
289
290;; All fields here are the same type, more nesting to stress the recursive walk.
291%T_NESTED_STRUCT_SAMEM = type {
292  { { double} },
293  { [ 2 x { double, double } ] }
294};
295
296define %T_NESTED_STRUCT_SAMEM @struct_nested_same_field_types() {
297; CHECK-LABEL: struct_nested_same_field_types:
298; CHECK:       movi d0, #0000000000000000
299; CHECK-NEXT:  movi d1, #0000000000000000
300; CHECK-NEXT:  movi d2, #0000000000000000
301; CHECK-NEXT:  movi d3, #0000000000000000
302; CHECK-NEXT:  movi d4, #0000000000000000
303; CHECK-NEXT:  ret
304  ret %T_NESTED_STRUCT_SAMEM zeroinitializer
305}
306
307define [ 1 x %T_NESTED_STRUCT_SAMEM ] @array_of_struct_nested_same_field_types() {
308; CHECK-LABEL: array_of_struct_nested_same_field_types:
309; CHECK:       movi d0, #0000000000000000
310; CHECK-NEXT:  movi d1, #0000000000000000
311; CHECK-NEXT:  movi d2, #0000000000000000
312; CHECK-NEXT:  movi d3, #0000000000000000
313; CHECK-NEXT:  movi d4, #0000000000000000
314; CHECK-NEXT:  ret
315  ret [ 1 x %T_NESTED_STRUCT_SAMEM ] zeroinitializer
316}
317
318;; 2 x (1 + (2 x 2)) = 10 so this is returned in memory
319define [ 2 x %T_NESTED_STRUCT_SAMEM ] @array_of_struct_nested_same_field_types_2() {
320; CHECK-LABEL: array_of_struct_nested_same_field_types_2:
321; CHECK:      movi    v0.2d, #0000000000000000
322; CHECK-NEXT: stp     q0, q0, [x8, #48]
323; CHECK-NEXT: stp     q0, q0, [x8, #16]
324; CHECK-NEXT: str     q0, [x8]
325; CHECK-NEXT: ret
326  ret [ 2 x %T_NESTED_STRUCT_SAMEM ] zeroinitializer
327}
328
329;; Check combinations of call, return and argument passing
330
331%T_IN_BLOCK = type [ 2 x { double, { double, double } } ]
332
333define %T_IN_BLOCK @return_in_block() {
334; CHECK-LABEL: return_in_block:
335; CHECK:      movi d0, #0000000000000000
336; CHECK-NEXT: movi d1, #0000000000000000
337; CHECK-NEXT: movi d2, #0000000000000000
338; CHECK-NEXT: movi d3, #0000000000000000
339; CHECK-NEXT: movi d4, #0000000000000000
340; CHECK-NEXT: movi d5, #0000000000000000
341; CHECK-NEXT: ret
342  ret %T_IN_BLOCK zeroinitializer
343}
344
345@in_block_store = dso_local global %T_IN_BLOCK zeroinitializer, align 8
346
347define void @caller_in_block() {
348; CHECK-LABEL: caller_in_block:
349; CHECK: bl   return_in_block
350; CHECK-NEXT: adrp x8, in_block_store
351; CHECK-NEXT: add x8, x8, :lo12:in_block_store
352; CHECK-NEXT: stp d0, d1, [x8]
353; CHECK-NEXT: stp d2, d3, [x8, #16]
354; CHECK-NEXT: stp d4, d5, [x8, #32]
355; CHECK-NEXT: ldr x30, [sp], #16
356; CHECK-NEXT: ret
357  %1 = call %T_IN_BLOCK @return_in_block()
358  store %T_IN_BLOCK %1, %T_IN_BLOCK* @in_block_store
359  ret void
360}
361
362define void @callee_in_block(%T_IN_BLOCK %a) {
363; CHECK-LABEL: callee_in_block:
364; CHECK:      adrp x8, in_block_store
365; CHECK-NEXT: add x8, x8, :lo12:in_block_store
366; CHECK-NEXT: stp d4, d5, [x8, #32]
367; CHECK-NEXT: stp d2, d3, [x8, #16]
368; CHECK-NEXT: stp d0, d1, [x8]
369; CHECK-NEXT: ret
370  store %T_IN_BLOCK %a, %T_IN_BLOCK* @in_block_store
371  ret void
372}
373
374define void @argument_in_block() {
375; CHECK-LABEL: argument_in_block:
376; CHECK:      adrp x8, in_block_store
377; CHECK-NEXT: add x8, x8, :lo12:in_block_store
378; CHECK-NEXT: ldp d4, d5, [x8, #32]
379; CHECK-NEXT: ldp d2, d3, [x8, #16]
380; CHECK-NEXT: ldp d0, d1, [x8]
381; CHECK-NEXT: bl callee_in_block
382  %1 = load %T_IN_BLOCK, %T_IN_BLOCK* @in_block_store
383  call void @callee_in_block(%T_IN_BLOCK %1)
384  ret void
385}
386
387%T_IN_MEMORY = type [ 3 x { double, { double, double } } ]
388
389define %T_IN_MEMORY @return_in_memory() {
390; CHECK-LABEL: return_in_memory:
391; CHECK:       movi v0.2d, #0000000000000000
392; CHECK-NEXT:  str xzr, [x8, #64]
393; CHECK-NEXT:  stp q0, q0, [x8, #32]
394; CHECK-NEXT:  stp q0, q0, [x8]
395; CHECK-NEXT:  ret
396  ret %T_IN_MEMORY zeroinitializer
397}
398
399@in_memory_store = dso_local global %T_IN_MEMORY zeroinitializer, align 8
400
401define void @caller_in_memory() {
402; CHECK-LABEL: caller_in_memory:
403; CHECK:      add     x8, sp, #8
404; CHECK-NEXT: bl      return_in_memory
405; CHECK-NEXT: ldr     d0, [sp, #72]
406; CHECK-NEXT: ldur    q1, [sp, #24]
407; CHECK-NEXT: ldur    q2, [sp, #8]
408; CHECK-NEXT: ldur    q3, [sp, #56]
409; CHECK-NEXT: ldur    q4, [sp, #40]
410; CHECK-NEXT: ldr     x30, [sp, #80]
411; CHECK-NEXT: adrp    x8, in_memory_store
412; CHECK-NEXT: add     x8, x8, :lo12:in_memory_store
413; CHECK-NEXT: stp     q2, q1, [x8]
414; CHECK-NEXT: stp     q4, q3, [x8, #32]
415; CHECK-NEXT: str     d0, [x8, #64]
416; CHECK-NEXT: add     sp, sp, #96
417; CHECK-NEXT: ret
418  %1 = call %T_IN_MEMORY @return_in_memory()
419  store %T_IN_MEMORY %1, %T_IN_MEMORY* @in_memory_store
420  ret void
421}
422
423define void @callee_in_memory(%T_IN_MEMORY %a) {
424; CHECK-LABEL: callee_in_memory:
425; CHECK:      ldp     q0, q1, [sp, #32]
426; CHECK-NEXT: ldr     d2, [sp, #64]
427; CHECK-NEXT: ldp     q3, q4, [sp]
428; CHECK-NEXT: adrp    x8, in_memory_store
429; CHECK-NEXT: add     x8, x8, :lo12:in_memory_store
430; CHECK-NEXT: str     d2, [x8, #64]
431; CHECK-NEXT: stp     q0, q1, [x8, #32]
432; CHECK-NEXT: stp     q3, q4, [x8]
433; CHECK-NEXT: ret
434  store %T_IN_MEMORY %a, %T_IN_MEMORY* @in_memory_store
435  ret void
436}
437
438define void @argument_in_memory() {
439; CHECK-LABEL: argument_in_memory:
440; CHECK:      adrp    x8, in_memory_store
441; CHECK-NEXT: add     x8, x8, :lo12:in_memory_store
442; CHECK-NEXT: ldp     q0, q1, [x8]
443; CHECK-NEXT: ldp     q2, q3, [x8, #32]
444; CHECK-NEXT: ldr     d4, [x8, #64]
445; CHECK-NEXT: stp     q0, q1, [sp]
446; CHECK-NEXT: stp     q2, q3, [sp, #32]
447; CHECK-NEXT: str     d4, [sp, #64]
448; CHECK-NEXT: bl      callee_in_memory
449  %1 = load %T_IN_MEMORY, %T_IN_MEMORY* @in_memory_store
450  call void @callee_in_memory(%T_IN_MEMORY %1)
451  ret void
452}
453
454%T_NO_BLOCK = type [ 2 x { double, { i32 } } ]
455
456define %T_NO_BLOCK @return_no_block() {
457; CHECK-LABEL: return_no_block:
458; CHECK:       movi d0, #0000000000000000
459; CHECK-NEXT:  movi d1, #0000000000000000
460; CHECK-NEXT:  mov w0, wzr
461; CHECK-NEXT:  mov w1, wzr
462; CHECK-NEXT:  ret
463  ret %T_NO_BLOCK zeroinitializer
464}
465
466@no_block_store = dso_local global %T_NO_BLOCK zeroinitializer, align 8
467
468define void @caller_no_block() {
469; CHECK-LABEL: caller_no_block:
470; CHECK:       bl return_no_block
471; CHECK-NEXT:  adrp x8, no_block_store
472; CHECK-NEXT:  add x8, x8, :lo12:no_block_store
473; CHECK-NEXT:  str d0, [x8]
474; CHECK-NEXT:  str w0, [x8, #8]
475; CHECK-NEXT:  str d1, [x8, #16]
476; CHECK-NEXT:  str w1, [x8, #24]
477; CHECK-NEXT:  ldr x30, [sp], #16
478; CHECK-NEXT:  ret
479  %1 = call %T_NO_BLOCK @return_no_block()
480  store %T_NO_BLOCK %1, %T_NO_BLOCK* @no_block_store
481  ret void
482}
483
484define void @callee_no_block(%T_NO_BLOCK %a) {
485; CHECK-LABEL: callee_no_block:
486; CHECK:       adrp x8, no_block_store
487; CHECK-NEXT:  add x8, x8, :lo12:no_block_store
488; CHECK-NEXT:  str w1, [x8, #24]
489; CHECK-NEXT:  str d1, [x8, #16]
490; CHECK-NEXT:  str w0, [x8, #8]
491; CHECK-NEXT:  str d0, [x8]
492; CHECK-NEXT:  ret
493  store %T_NO_BLOCK %a, %T_NO_BLOCK* @no_block_store
494  ret void
495}
496
497define void @argument_no_block() {
498; CHECK-LABEL: argument_no_block:
499; CHECK:       adrp x8, no_block_store
500; CHECK-NEXT:  add x8, x8, :lo12:no_block_store
501; CHECK-NEXT:  ldr w1, [x8, #24]
502; CHECK-NEXT:  ldr d1, [x8, #16]
503; CHECK-NEXT:  ldr w0, [x8, #8]
504; CHECK-NEXT:  ldr d0, [x8]
505; CHECK-NEXT:  bl callee_no_block
506; CHECK-NEXT:  ldr x30, [sp], #16
507; CHECK-NEXT:  ret
508  %1 = load %T_NO_BLOCK, %T_NO_BLOCK* @no_block_store
509  call void @callee_no_block(%T_NO_BLOCK %1)
510  ret void
511}
512