blob: 3573a7910adc2579d27fa71f16f9cf94f47a63de [file]
;; r18 is the default shadow call stack pointer register; reserving it is all
;; that is required.
; RUN: llc -mtriple=hexagon -mattr=+reserved-r18 < %s | FileCheck %s
; RUN: llc -mtriple=hexagon -mattr=+reserved-r18 < %s | FileCheck %s --check-prefix=CFI
; RUN: llc -mtriple=hexagon-unknown-linux-musl -mattr=+reserved-r18 < %s | FileCheck %s --check-prefix=MUSL
;; The backend fatally errors unless the SCS register is reserved (backstop for
;; the driver diagnostic in SanitizerArgs.cpp). Reserving some other register
;; does not help.
; RUN: not --crash llc -mtriple=hexagon < %s 2>&1 | FileCheck %s --check-prefix=ERR
; RUN: not --crash llc -mtriple=hexagon -mattr=+reserved-r19 < %s 2>&1 | FileCheck %s --check-prefix=ERR
;; scs-reg-rN selects a different register; the diagnostic follows it.
; RUN: llc -mtriple=hexagon -mattr=+scs-reg-r16,+reserved-r16 < %s \
; RUN: | FileCheck %s --check-prefix=R16
; RUN: not --crash llc -mtriple=hexagon -mattr=+scs-reg-r16,+reserved-r18 < %s 2>&1 \
; RUN: | FileCheck %s --check-prefix=ERR16
; RUN: not --crash llc -mtriple=hexagon -mattr=+scs-reg-r16,+scs-reg-r17 < %s 2>&1 \
; RUN: | FileCheck %s --check-prefix=ERRMULTI
;; Only one scs-reg-rN may be given.
; ERRMULTI: Only one shadow call stack pointer register may be selected
;; With r16 selected, the prologue/epilogue use r16 and leave r18 alone.
; R16-LABEL: nonleaf:
; R16: r16 = add(r16,#4)
; R16: call bar
; R16: memw(r16+#-4) = r31
; R16: {
; R16-DAG: r16 = add(r16,#-4)
; R16-DAG: r31 = memw(r16+#-4)
; R16: }
; R16: jumpr r31
;; Every spill stub saves the range starting at r16, so with r16 as the SCS
;; register no stub can ever be used - reserving r16 removes d8 from the
;; callee-saved set entirely.
; R16-LABEL: minsize_multicall:
; R16-NOT: __save_
; R16-NOT: __restore_
;; Leaf function - no LR spill, SCS should not emit any r18 instructions.
; CHECK-LABEL: leaf:
; CHECK-NOT: r18
; CHECK: jumpr r31
;; Non-leaf function - SCS emits prologue (addi + store) and epilogue (load + addi).
;; The SCS store is fused into the same packet as the first call; because
;; Hexagon packets use old-value reads the original R31 is saved regardless.
;; The epilogue load and addi are also in the same packet; the load uses the
;; old (pre-decrement) r18 value per Hexagon packet semantics, and the -4
;; offset correctly addresses the saved slot.
; CHECK-LABEL: nonleaf:
; CHECK: r18 = add(r18,#4)
; CHECK: call bar
; CHECK: memw(r18+#-4) = r31
; CHECK: {
; CHECK-DAG: r18 = add(r18,#-4)
; CHECK-DAG: r31 = memw(r18+#-4)
; CHECK: }
; CHECK: jumpr r31
;; Multi-call function - only one SCS prologue/epilogue pair, not one per call.
; CHECK-LABEL: twocalls:
; CHECK: r18 = add(r18,#4)
; CHECK: call bar
; CHECK: memw(r18+#-4) = r31
; CHECK: call bar
; CHECK: {
; CHECK-DAG: r18 = add(r18,#-4)
; CHECK-DAG: r31 = memw(r18+#-4)
; CHECK: }
; CHECK: jumpr r31
;; Conditional call (shrink-wrapping): the early-return path is a leaf and
;; has no SCS prologue/epilogue. The call path gets the SCS pair.
; CHECK-LABEL: condcall:
; CHECK: if (!p0.new) jumpr:nt r31
; CHECK: r18 = add(r18,#4)
; CHECK: call bar
; CHECK: memw(r18+#-4) = r31
; CHECK: {
; CHECK-DAG: r18 = add(r18,#-4)
; CHECK-DAG: r31 = memw(r18+#-4)
; CHECK: }
; CHECK: jumpr r31
;; Tail call - SCS prologue and epilogue are both emitted; the epilogue
;; instructions and the tail jump are fused into the same packet.
; CHECK-LABEL: tailcall:
; CHECK: r18 = add(r18,#4)
; CHECK: memw(r18+#-4) = r31
; CHECK: {
; CHECK-DAG: r18 = add(r18,#-4)
; CHECK-DAG: r31 = memw(r18+#-4)
; CHECK-DAG: jump bar
; CHECK: }
;; Noreturn call - SCS prologue is emitted but no SCS epilogue since the
;; function never returns.
; CHECK-LABEL: noret:
; CHECK: r18 = add(r18,#4)
; CHECK: memw(r18+#-4) = r31
; CHECK-NOT: r31 = memw
; CHECK-NOT: r18 = add(r18,#-4)
; CHECK-LABEL: nonleaf_cfi:
;; Minsize + multiple callee-saved registers: the restore stub
;; (__restore_r16_through_r17_and_deallocframe) must NOT be used when SCS is
;; active because it performs deallocframe+jumpr without the SCS epilogue.
; CHECK-LABEL: minsize_multicall:
; CHECK: r18 = add(r18,#4)
; CHECK: memw(r18+#-4) = r31
; CHECK: {
; CHECK-DAG: r18 = add(r18,#-4)
; CHECK-DAG: r31 = memw(r18+#-4)
; CHECK: }
; CHECK-NOT: __restore_
; CHECK: jumpr r31
;; Minsize + tail call + multiple callee-saved registers: the tailcall restore
;; stub (__restore_r16_through_r17_and_deallocframe_before_tailcall) must NOT be
;; used when SCS is active. The SCS epilogue and tail jump are fused together.
; CHECK-LABEL: minsize_tailcall:
; CHECK: r18 = add(r18,#4)
; CHECK: memw(r18+#-4) = r31
; CHECK: {
; CHECK-DAG: r18 = add(r18,#-4)
; CHECK-DAG: r31 = memw(r18+#-4)
; CHECK-DAG: jump bar
; CHECK: }
; CHECK-NOT: __restore_
;; Multiple return paths - each exit block gets its own SCS epilogue.
; CHECK-LABEL: multi_return:
; CHECK: r18 = add(r18,#4)
; CHECK: memw(r18+#-4) = r31
; CHECK: r31 = memw(r18+#-4)
; CHECK: r18 = add(r18,#-4)
; CHECK: jumpr r31
; CHECK: r31 = memw(r18+#-4)
; CHECK: r18 = add(r18,#-4)
; CHECK: jumpr r31
;; A minsize function using many callee-saved registers - without SCS this is
;; the shape that gets a spill/restore stub. Reserving the SCS register breaks
;; the r19:18 double, which leaves r19 in the callee-saved set as a lone single
;; register and forces inline spills, so no stub covering r18 can be selected.
;; This locks in the invariant that a stub never reaches the SCS register.
; CHECK-LABEL: minsize_manycsr:
; CHECK-NOT: __save_
; CHECK-NOT: __restore_
; CHECK: jumpr r31
;; Without the SCS register reserved, SCS should report an error naming it.
; ERR: Must reserve r18 to use shadow call stack on Hexagon
; ERR16: Must reserve r16 to use shadow call stack on Hexagon
;; Non-leaf with uwtable - exercises CFI escape (DW_CFA_val_expression for r18)
;; and cfi_restore on epilogue.
; CFI-LABEL: nonleaf_cfi:
; CFI: r18 = add(r18,#4)
; CFI: memw(r18+#-4) = r31
; CFI: .cfi_escape 0x16, 0x12, 0x02, 0x82, 0x7c
; CFI: {
; CFI-DAG: r31 = memw(r18+#-4)
; CFI-DAG: r18 = add(r18,#-4)
; CFI: }
; CFI: .cfi_restore r18
; CFI: jumpr r31
;; Musl vararg - exercises the vararg epilogue path with SCS.
; MUSL-LABEL: vararg_musl:
; MUSL: r18 = add(r18,#4)
; MUSL: memw(r18+#-4) = r31
; MUSL: {
; MUSL-DAG: r18 = add(r18,#-4)
; MUSL-DAG: r31 = memw(r18+#-4)
; MUSL: }
; MUSL: jumpr r31
declare i32 @foo(i32)
declare void @bar()
declare void @baz(i32)
define void @leaf() shadowcallstack nounwind {
ret void
}
define void @nonleaf() shadowcallstack nounwind {
call void @bar()
ret void
}
define void @twocalls() shadowcallstack nounwind {
call void @bar()
call void @bar()
ret void
}
define void @condcall(i1 %cond) shadowcallstack nounwind {
br i1 %cond, label %call, label %ret
call:
call void @bar()
br label %ret
ret:
ret void
}
define void @tailcall() shadowcallstack nounwind {
call void @bar()
tail call void @bar()
ret void
}
define void @noret() shadowcallstack nounwind {
call void @bar() noreturn
unreachable
}
define void @nonleaf_cfi() shadowcallstack uwtable {
call void @bar()
ret void
}
define void @vararg_musl(i32 %a, ...) shadowcallstack nounwind {
call void @bar()
ret void
}
define i32 @minsize_multicall(i32 %x) shadowcallstack nounwind minsize
"disable-tail-calls"="true" {
%call = call i32 @foo(i32 %x)
%call1 = call i32 @foo(i32 %x)
%sum = add i32 %call, %call1
ret i32 %sum
}
define void @minsize_tailcall(i32 %x) shadowcallstack nounwind minsize {
call void @baz(i32 %x)
call void @baz(i32 %x)
tail call void @bar()
ret void
}
define i32 @multi_return(i32 %x) shadowcallstack nounwind optnone noinline {
entry:
%call = call i32 @foo(i32 %x)
%cmp = icmp sgt i32 %call, 0
br i1 %cmp, label %pos, label %neg
pos:
%r1 = call i32 @foo(i32 %call)
ret i32 %r1
neg:
%r2 = call i32 @foo(i32 0)
ret i32 %r2
}
define i32 @minsize_manycsr(i32 %x) shadowcallstack nounwind minsize
"disable-tail-calls"="true" {
%a = call i32 @foo(i32 %x)
%b = call i32 @foo(i32 %a)
%c = call i32 @foo(i32 %b)
%d = call i32 @foo(i32 %c)
%e = call i32 @foo(i32 %d)
%f = call i32 @foo(i32 %e)
%g = call i32 @foo(i32 %f)
%s1 = add i32 %a, %b
%s2 = add i32 %s1, %c
%s3 = add i32 %s2, %d
%s4 = add i32 %s3, %e
%s5 = add i32 %s4, %f
%s6 = add i32 %s5, %g
ret i32 %s6
}