; RUN: llc -mtriple=thumbv6m-eabi -disable-fp-elim=false %s -o - | FileCheck %s ; struct S { int x[128]; } s; ; int f(int *, int, int, int, struct S); ; int g(int *, int, int, int, int, int); ; int h(int *, int *, int *); ; int u(int *, int *, int *, struct S, struct S); %struct.S = type { [128 x i32] } %struct.__va_list = type { i8* } @s = common dso_local global %struct.S zeroinitializer, align 4 declare void @llvm.va_start(i8*) declare dso_local i32 @g(i32*, i32, i32, i32, i32, i32) local_unnamed_addr declare dso_local i32 @f(i32*, i32, i32, i32, %struct.S* byval align 4) local_unnamed_addr declare dso_local i32 @h(i32*, i32*, i32*) local_unnamed_addr declare dso_local i32 @u(i32*, i32*, i32*, %struct.S* byval align 4, %struct.S* byval align 4) local_unnamed_addr ; ; Test access to arguments, passed on stack (including varargs) ; ; Usual case, access via SP ; int test_args_sp(int a, int b, int c, int d, int e) { ; int v[4]; ; return g(v, a, b, c, d, e); ; } define dso_local i32 @test_args_sp(i32 %a, i32 %b, i32 %c, i32 %d, i32 %e) local_unnamed_addr { entry: %v = alloca [4 x i32], align 4 %0 = bitcast [4 x i32]* %v to i8* %arraydecay = getelementptr inbounds [4 x i32], [4 x i32]* %v, i32 0, i32 0 %call = call i32 @g(i32* nonnull %arraydecay, i32 %a, i32 %b, i32 %c, i32 %d, i32 %e) ret i32 %call } ; CHECK-LABEL: test_args_sp ; Load `e` ; CHECK: ldr r0, [sp, #40] ; CHECK-NEXT: mov r5, sp ; CHECK-NEXT: str r3, [r5] ; Pass `e` on stack ; CHECK-NEXT: str r0, [r5, #4] ; CHECK: bl g ; int test_varargs_sp(int a, ...) { ; int v[4]; ; __builtin_va_list ap; ; __builtin_va_start(ap, a); ; return g(v, a, 0, 0, 0, 0); ; } define dso_local i32 @test_varargs_sp(i32 %a, ...) local_unnamed_addr { entry: %v = alloca [4 x i32], align 4 %ap = alloca %struct.__va_list, align 4 %0 = bitcast [4 x i32]* %v to i8* %1 = bitcast %struct.__va_list* %ap to i8* call void @llvm.va_start(i8* nonnull %1) %arraydecay = getelementptr inbounds [4 x i32], [4 x i32]* %v, i32 0, i32 0 %call = call i32 @g(i32* nonnull %arraydecay, i32 %a, i32 0, i32 0, i32 0, i32 0) ret i32 %call } ; CHECK-LABEL: test_varargs_sp ; Three incoming varargs in registers ; CHECK: sub sp, #12 ; CHECK: sub sp, #28 ; Incoming arguments area is accessed via SP ; CHECK: add r0, sp, #36 ; CHECK: stm r0!, {r1, r2, r3} ; Re-aligned stack, access via FP ; int test_args_realign(int a, int b, int c, int d, int e) { ; __attribute__((aligned(16))) int v[4]; ; return g(v, a, b, c, d, e); ; } ; Function Attrs: nounwind define dso_local i32 @test_args_realign(i32 %a, i32 %b, i32 %c, i32 %d, i32 %e) local_unnamed_addr { entry: %v = alloca [4 x i32], align 16 %0 = bitcast [4 x i32]* %v to i8* %arraydecay = getelementptr inbounds [4 x i32], [4 x i32]* %v, i32 0, i32 0 %call = call i32 @g(i32* nonnull %arraydecay, i32 %a, i32 %b, i32 %c, i32 %d, i32 %e) ret i32 %call } ; CHECK-LABEL: test_args_realign ; Setup frame pointer ; CHECK: add r7, sp, #8 ; Align stack ; CHECK: mov r4, sp ; CHECK-NEXT: lsrs r4, r4, #4 ; CHECK-NEXT: lsls r4, r4, #4 ; CHECK-NEXT: mov sp, r4 ; Load `e` via FP ; CHECK: ldr r0, [r7, #8] ; CHECK-NEXT: mov r5, sp ; CHECK-NEXT: str r3, [r5] ; Pass `e` as argument ; CHECK-NEXT: str r0, [r5, #4] ; CHECK: bl g ; int test_varargs_realign(int a, ...) { ; __attribute__((aligned(16))) int v[4]; ; __builtin_va_list ap; ; __builtin_va_start(ap, a); ; return g(v, a, 0, 0, 0, 0); ; } define dso_local i32 @test_varargs_realign(i32 %a, ...) local_unnamed_addr { entry: %v = alloca [4 x i32], align 16 %ap = alloca %struct.__va_list, align 4 %0 = bitcast [4 x i32]* %v to i8* %1 = bitcast %struct.__va_list* %ap to i8* call void @llvm.va_start(i8* nonnull %1) %arraydecay = getelementptr inbounds [4 x i32], [4 x i32]* %v, i32 0, i32 0 %call = call i32 @g(i32* nonnull %arraydecay, i32 %a, i32 0, i32 0, i32 0, i32 0) ret i32 %call } ; CHECK-LABEL: test_varargs_realign ; Three incoming register varargs ; CHECK: sub sp, #12 ; Setup frame pointer ; CHECK: add r7, sp, #8 ; Align stack ; CHECK: mov r4, sp ; CHECK-NEXT: lsrs r4, r4, #4 ; CHECK-NEXT: lsls r4, r4, #4 ; CHECK-NEXT: mov sp, r4 ; Incoming register varargs stored via FP ; CHECK: str r3, [r7, #16] ; CHECK-NEXT: str r2, [r7, #12] ; CHECK-NEXT: str r1, [r7, #8] ; VLAs present, access via FP ; int test_args_vla(int a, int b, int c, int d, int e) { ; int v[a]; ; return g(v, a, b, c, d, e); ; } define dso_local i32 @test_args_vla(i32 %a, i32 %b, i32 %c, i32 %d, i32 %e) local_unnamed_addr { entry: %vla = alloca i32, i32 %a, align 4 %call = call i32 @g(i32* nonnull %vla, i32 %a, i32 %b, i32 %c, i32 %d, i32 %e) ret i32 %call } ; CHECK-LABEL: test_args_vla ; Setup frame pointer ; CHECK: add r7, sp, #12 ; Allocate outgoing stack arguments space ; CHECK: sub sp, #4 ; Load `e` via FP ; CHECK: ldr r5, [r7, #8] ; CHECK-NEXT: mov r0, sp ; Pass `d` and `e` as arguments ; CHECK-NEXT: stm r0!, {r3, r5} ; CHECK: bl g ; int test_varargs_vla(int a, ...) { ; int v[a]; ; __builtin_va_list ap; ; __builtin_va_start(ap, a); ; return g(v, a, 0, 0, 0, 0); ; } define dso_local i32 @test_varargs_vla(i32 %a, ...) local_unnamed_addr { entry: %ap = alloca %struct.__va_list, align 4 %vla = alloca i32, i32 %a, align 4 %0 = bitcast %struct.__va_list* %ap to i8* call void @llvm.va_start(i8* nonnull %0) %call = call i32 @g(i32* nonnull %vla, i32 %a, i32 0, i32 0, i32 0, i32 0) ret i32 %call } ; CHECK-LABEL: test_varargs_vla ; Three incoming register varargs ; CHECK: sub sp, #12 ; Setup frame pointer ; CHECK: add r7, sp, #8 ; Register varargs stored via FP ; CHECK: str r3, [r7, #16] ; CHECK-NEXT: str r2, [r7, #12] ; CHECK-NEXT: str r1, [r7, #8] ; Moving SP, access via SP ; int test_args_moving_sp(int a, int b, int c, int d, int e) { ; int v[4]; ; return f(v, a, b + c + d, e, s) + h(v, v+1, v+2); ; } define dso_local i32 @test_args_moving_sp(i32 %a, i32 %b, i32 %c, i32 %d, i32 %e) local_unnamed_addr { entry: %v = alloca [4 x i32], align 4 %0 = bitcast [4 x i32]* %v to i8* %arraydecay = getelementptr inbounds [4 x i32], [4 x i32]* %v, i32 0, i32 0 %add = add nsw i32 %c, %b %add1 = add nsw i32 %add, %d %call = call i32 @f(i32* nonnull %arraydecay, i32 %a, i32 %add1, i32 %e, %struct.S* byval nonnull align 4 @s) %add.ptr = getelementptr inbounds [4 x i32], [4 x i32]* %v, i32 0, i32 1 %add.ptr5 = getelementptr inbounds [4 x i32], [4 x i32]* %v, i32 0, i32 2 %call6 = call i32 @h(i32* nonnull %arraydecay, i32* nonnull %add.ptr, i32* nonnull %add.ptr5) %add7 = add nsw i32 %call6, %call ret i32 %add7 } ; CHECK-LABEL: test_args_moving_sp ; 20 bytes callee-saved area ; CHECK: push {r4, r5, r6, r7, lr} ; 20 bytes locals ; CHECK: sub sp, #20 ; Allocate outgoing arguments space ; CHECK: sub sp, #508 ; CHECK: sub sp, #4 ; Load `e` via SP, 552 = 512 + 20 + 20 ; CHECK: ldr r3, [sp, #552] ; CHECK: bl f ; Stack restored before next call ; CHECK-NEXT: add sp, #508 ; CHECK-NEXT: add sp, #4 ; CHECK: bl h ; int test_varargs_moving_sp(int a, ...) { ; int v[4]; ; __builtin_va_list ap; ; __builtin_va_start(ap, a); ; return f(v, a, 0, 0, s) + h(v, v+1, v+2); ; } define dso_local i32 @test_varargs_moving_sp(i32 %a, ...) local_unnamed_addr { entry: %v = alloca [4 x i32], align 4 %ap = alloca %struct.__va_list, align 4 %0 = bitcast [4 x i32]* %v to i8* %1 = bitcast %struct.__va_list* %ap to i8* call void @llvm.va_start(i8* nonnull %1) %arraydecay = getelementptr inbounds [4 x i32], [4 x i32]* %v, i32 0, i32 0 %call = call i32 @f(i32* nonnull %arraydecay, i32 %a, i32 0, i32 0, %struct.S* byval nonnull align 4 @s) %add.ptr = getelementptr inbounds [4 x i32], [4 x i32]* %v, i32 0, i32 1 %add.ptr5 = getelementptr inbounds [4 x i32], [4 x i32]* %v, i32 0, i32 2 %call6 = call i32 @h(i32* nonnull %arraydecay, i32* nonnull %add.ptr, i32* nonnull %add.ptr5) %add = add nsw i32 %call6, %call ret i32 %add } ; CHECK-LABEL: test_varargs_moving_sp ; Three incoming register varargs ; CHECK: sub sp, #12 ; 16 bytes callee-saves ; CHECK: push {r4, r5, r7, lr} ; 20 bytes locals ; CHECK: sub sp, #20 ; Incoming varargs stored via SP, 36 = 20 + 16 ; CHECK: add r0, sp, #36 ; CHECK-NEXT: stm r0!, {r1, r2, r3} ; ; Access to locals ; ; Usual case, access via SP. ; int test_local(int n) { ; int v[4]; ; int x, y, z; ; h(&x, &y, &z); ; return g(v, x, y, z, 0, 0); ; } define dso_local i32 @test_local(i32 %n) local_unnamed_addr { entry: %v = alloca [4 x i32], align 4 %x = alloca i32, align 4 %y = alloca i32, align 4 %z = alloca i32, align 4 %0 = bitcast [4 x i32]* %v to i8* %1 = bitcast i32* %x to i8* %2 = bitcast i32* %y to i8* %3 = bitcast i32* %z to i8* %call = call i32 @h(i32* nonnull %x, i32* nonnull %y, i32* nonnull %z) %arraydecay = getelementptr inbounds [4 x i32], [4 x i32]* %v, i32 0, i32 0 %4 = load i32, i32* %x, align 4 %5 = load i32, i32* %y, align 4 %6 = load i32, i32* %z, align 4 %call1 = call i32 @g(i32* nonnull %arraydecay, i32 %4, i32 %5, i32 %6, i32 0, i32 0) ret i32 %call1 } ; CHECK-LABEL: test_local ; Arguments to `h` relative to SP ; CHECK: add r0, sp, #20 ; CHECK-NEXT: add r1, sp, #16 ; CHECK-NEXT: add r2, sp, #12 ; CHECK-NEXT: bl h ; Load `x`, `y`, and `z` via SP ; CHECK: ldr r1, [sp, #20] ; CHECK-NEXT: ldr r2, [sp, #16] ; CHECK-NEXT: ldr r3, [sp, #12] ; CHECK: bl g ; Re-aligned stack, access via SP. ; int test_local_realign(int n) { ; __attribute__((aligned(16))) int v[4]; ; int x, y, z; ; h(&x, &y, &z); ; return g(v, x, y, z, 0, 0); ; } define dso_local i32 @test_local_realign(i32 %n) local_unnamed_addr { entry: %v = alloca [4 x i32], align 16 %x = alloca i32, align 4 %y = alloca i32, align 4 %z = alloca i32, align 4 %0 = bitcast [4 x i32]* %v to i8* %1 = bitcast i32* %x to i8* %2 = bitcast i32* %y to i8* %3 = bitcast i32* %z to i8* %call = call i32 @h(i32* nonnull %x, i32* nonnull %y, i32* nonnull %z) %arraydecay = getelementptr inbounds [4 x i32], [4 x i32]* %v, i32 0, i32 0 %4 = load i32, i32* %x, align 4 %5 = load i32, i32* %y, align 4 %6 = load i32, i32* %z, align 4 %call1 = call i32 @g(i32* nonnull %arraydecay, i32 %4, i32 %5, i32 %6, i32 0, i32 0) ret i32 %call1 } ; CHECK-LABEL: test_local_realign ; Setup frame pointer ; CHECK: add r7, sp, #8 ; Re-align stack ; CHECK: mov r4, sp ; CHECK-NEXT: lsrs r4, r4, #4 ; CHECK-NEXT: lsls r4, r4, #4 ; CHECK-NEXT: mov sp, r4 ; Arguments to `h` computed relative to SP ; CHECK: add r0, sp, #28 ; CHECK-NEXT: add r1, sp, #24 ; CHECK-NEXT: add r2, sp, #20 ; CHECK-NEXT: bl h ; Load `x`, `y`, and `z` via SP for passing to `g` ; CHECK: ldr r1, [sp, #28] ; CHECK-NEXT: ldr r2, [sp, #24] ; CHECK-NEXT: ldr r3, [sp, #20] ; CHECK: bl g ; VLAs, access via BP. ; int test_local_vla(int n) { ; int v[n]; ; int x, y, z; ; h(&x, &y, &z); ; return g(v, x, y, z, 0, 0); ; } define dso_local i32 @test_local_vla(i32 %n) local_unnamed_addr { entry: %x = alloca i32, align 4 %y = alloca i32, align 4 %z = alloca i32, align 4 %vla = alloca i32, i32 %n, align 4 %0 = bitcast i32* %x to i8* %1 = bitcast i32* %y to i8* %2 = bitcast i32* %z to i8* %call = call i32 @h(i32* nonnull %x, i32* nonnull %y, i32* nonnull %z) %3 = load i32, i32* %x, align 4 %4 = load i32, i32* %y, align 4 %5 = load i32, i32* %z, align 4 %call1 = call i32 @g(i32* nonnull %vla, i32 %3, i32 %4, i32 %5, i32 0, i32 0) ret i32 %call1 } ; CHECK-LABEL: test_local_vla ; Setup frame pointer ; CHECK: add r7, sp, #12 ; Setup base pointer ; CHECK: mov r6, sp ; CHECK: mov r5, r6 ; Arguments to `h` compute relative to BP ; CHECK: adds r0, r6, #7 ; CHECK-NEXT: adds r0, #1 ; CHECK-NEXT: adds r1, r6, #4 ; CHECK-NEXT: mov r2, r6 ; CHECK-NEXT: bl h ; Load `x`, `y`, `z` via BP (r5 should still have the value of r6 from the move ; above) ; CHECK: ldr r3, [r5] ; CHECK-NEXT: ldr r2, [r5, #4] ; CHECK-NEXT: ldr r1, [r5, #8] ; CHECK: bl g ; Moving SP, access via SP. ; int test_local_moving_sp(int n) { ; int v[4]; ; int x, y, z; ; return u(v, &x, &y, s, s) + u(v, &y, &z, s, s); ; } define dso_local i32 @test_local_moving_sp(i32 %n) local_unnamed_addr { entry: %v = alloca [4 x i32], align 4 %x = alloca i32, align 4 %y = alloca i32, align 4 %z = alloca i32, align 4 %0 = bitcast [4 x i32]* %v to i8* %1 = bitcast i32* %x to i8* %2 = bitcast i32* %y to i8* %3 = bitcast i32* %z to i8* %arraydecay = getelementptr inbounds [4 x i32], [4 x i32]* %v, i32 0, i32 0 %call = call i32 @u(i32* nonnull %arraydecay, i32* nonnull %x, i32* nonnull %y, %struct.S* byval nonnull align 4 @s, %struct.S* byval nonnull align 4 @s) %call2 = call i32 @u(i32* nonnull %arraydecay, i32* nonnull %y, i32* nonnull %z, %struct.S* byval nonnull align 4 @s, %struct.S* byval nonnull align 4 @s) %add = add nsw i32 %call2, %call ret i32 %add } ; CHECK-LABEL: test_local_moving_sp ; Locals area ; CHECK: sub sp, #36 ; Outoging arguments ; CHECK: sub sp, #508 ; CHECK-NEXT: sub sp, #508 ; CHECK-NEXT: sub sp, #8 ; Argument addresses computed relative to SP ; CHECK: add r4, sp, #1020 ; CHECK-NEXT: adds r4, #24 ; CHECK: add r1, sp, #1020 ; CHECK-NEXT: adds r1, #20 ; CHECK: add r5, sp, #1020 ; CHECK-NEXT: adds r5, #16 ; CHECK: bl u ; Stack restored before next call ; CHECK: add sp, #508 ; CHECK-NEXT: add sp, #508 ; CHECK-NEXT: add sp, #8 ; CHECK: bl u