| ;; 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 |
| } |