I think this pass was previously used under the assumption that most functions would not need a frame pointer and it would be more efficient to store the old stack pointer in a regular register pair. Unfortunately, right now we're forced to always reserve the Y register as a frame pointer: whether or not this is needed is only known after regsiter allocation at which point it doesn't make sense anymore to mark it as non-reserved. Therefore, it makes sense to use the Y register to store the old stack pointer in functions with dynamic allocas (with a variable size or not in the entry block). Knowing this can make the code around dynamic allocas a lot simpler: simply save/restore the frame pointer. This is especially relevant in functions that have a frame pointer anyway (for example, because they have stack spills). The stack restore in the epilogue will implicitly restore the old stack pointer, so there is no need to store the old stack pointer separately. It even reduces register pressure as a side effect. Differential Revision: https://reviews.llvm.org/D97815
127 lines
3.2 KiB
LLVM
127 lines
3.2 KiB
LLVM
; RUN: llc < %s -march=avr | FileCheck %s
|
|
|
|
declare void @foo(i16*, i16*, i8*)
|
|
|
|
define void @test1(i16 %x) {
|
|
; CHECK-LABEL: test1:
|
|
; Frame setup, with frame pointer
|
|
; CHECK: in r28, 61
|
|
; CHECK: in r29, 62
|
|
; CHECK: out 61, r28
|
|
; allocate first dynalloca
|
|
; CHECK: in {{.*}}, 61
|
|
; CHECK: in {{.*}}, 62
|
|
; CHECK: sub
|
|
; CHECK: sbc
|
|
; CHECK: in r0, 63
|
|
; CHECK-NEXT: cli
|
|
; CHECK-NEXT: out 62, {{.*}}
|
|
; CHECK-NEXT: out 63, r0
|
|
; CHECK-NEXT: out 61, {{.*}}
|
|
; Test writes
|
|
; CHECK: std Z+12, {{.*}}
|
|
; CHECK: std Z+13, {{.*}}
|
|
; CHECK: std Z+7, {{.*}}
|
|
; CHECK-NOT: std
|
|
; Test SP restore
|
|
; CHECK: in r0, 63
|
|
; CHECK-NEXT: cli
|
|
; CHECK-NEXT: out 62, r29
|
|
; CHECK-NEXT: out 63, r0
|
|
; CHECK-NEXT: out 61, r28
|
|
%a = alloca [8 x i16]
|
|
%vla = alloca i16, i16 %x
|
|
%add = shl nsw i16 %x, 1
|
|
%vla1 = alloca i8, i16 %add
|
|
%arrayidx = getelementptr inbounds [8 x i16], [8 x i16]* %a, i16 0, i16 2
|
|
store i16 3, i16* %arrayidx
|
|
%arrayidx2 = getelementptr inbounds i16, i16* %vla, i16 6
|
|
store i16 4, i16* %arrayidx2
|
|
%arrayidx3 = getelementptr inbounds i8, i8* %vla1, i16 7
|
|
store i8 44, i8* %arrayidx3
|
|
%arraydecay = getelementptr inbounds [8 x i16], [8 x i16]* %a, i16 0, i16 0
|
|
call void @foo(i16* %arraydecay, i16* %vla, i8* %vla1)
|
|
ret void
|
|
}
|
|
|
|
declare void @foo2(i16*, i64, i64, i64)
|
|
|
|
; Test that arguments are passed through pushes into the call instead of
|
|
; allocating the call frame space in the prologue. Also test that SP is restored
|
|
; after the call frame is restored and not before.
|
|
define void @dynalloca2(i16 %x) {
|
|
; CHECK-LABEL: dynalloca2:
|
|
; CHECK: in r28, 61
|
|
; CHECK: in r29, 62
|
|
; Allocate stack space for call
|
|
; CHECK: in {{.*}}, 61
|
|
; CHECK: in {{.*}}, 62
|
|
; CHECK: subi
|
|
; CHECK: sbci
|
|
; CHECK: in r0, 63
|
|
; CHECK-NEXT: cli
|
|
; CHECK-NEXT: out 62, {{.*}}
|
|
; CHECK-NEXT: out 63, r0
|
|
; CHECK-NEXT: out 61, {{.*}}
|
|
; Store values on the stack
|
|
; CHECK: ldi r16, 0
|
|
; CHECK: ldi r17, 0
|
|
; CHECK: std Z+7, r16
|
|
; CHECK: std Z+8, r17
|
|
; CHECK: std Z+5, r16
|
|
; CHECK: std Z+6, r17
|
|
; CHECK: std Z+3, r16
|
|
; CHECK: std Z+4, r17
|
|
; CHECK: std Z+1, r16
|
|
; CHECK: std Z+2, r17
|
|
; CHECK: call
|
|
; Call frame restore
|
|
; CHECK-NEXT: in r30, 61
|
|
; CHECK-NEXT: in r31, 62
|
|
; CHECK-NEXT: adiw r30, 8
|
|
; CHECK-NEXT: in r0, 63
|
|
; CHECK-NEXT: cli
|
|
; CHECK-NEXT: out 62, r31
|
|
; CHECK-NEXT: out 63, r0
|
|
; CHECK-NEXT: out 61, r30
|
|
; SP restore
|
|
; CHECK: in r0, 63
|
|
; CHECK-NEXT: cli
|
|
; CHECK-NEXT: out 62, r29
|
|
; CHECK-NEXT: out 63, r0
|
|
; CHECK-NEXT: out 61, r28
|
|
%vla = alloca i16, i16 %x
|
|
call void @foo2(i16* %vla, i64 0, i64 0, i64 0)
|
|
ret void
|
|
}
|
|
|
|
; Test a function with a variable sized object but without any other need for a
|
|
; frame pointer.
|
|
; Allocas that are not placed in the entry block are considered variable sized
|
|
; (they could be in a loop).
|
|
define void @dynalloca3() {
|
|
; CHECK-LABEL: dynalloca3:
|
|
; Read frame pointer
|
|
; CHECK: in r28, 61
|
|
; CHECK-NEXT: in r29, 62
|
|
; Allocate memory for the alloca
|
|
; CHECK-NEXT: in r24, 61
|
|
; CHECK-NEXT: in r25, 62
|
|
; CHECK-NEXT: sbiw r24, 8
|
|
; CHECK-NEXT: in r0, 63
|
|
; CHECK-NEXT: cli
|
|
; CHECK-NEXT: out 62, r25
|
|
; CHECK-NEXT: out 63, r0
|
|
; CHECK-NEXT: out 61, r24
|
|
; Restore frame pointer
|
|
; CHECK-NEXT: in r0, 63
|
|
; CHECK-NEXT: cli
|
|
; CHECK-NEXT: out 62, r29
|
|
; CHECK-NEXT: out 63, r0
|
|
; CHECK-NEXT: out 61, r28
|
|
br label %1
|
|
1:
|
|
%a = alloca i64
|
|
ret void
|
|
}
|