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