[clang] [llvm] [ARM] Fix musttail calls (PR #109943)
Oliver Stannard via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 26 03:17:36 PDT 2024
================
@@ -0,0 +1,321 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc -mtriple=armv7a-none-eabi %s -o - | FileCheck %s
+
+declare i32 @many_args_callee(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4, i32 %5)
+
+define i32 @many_args_tail(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4, i32 %5) {
+; CHECK-LABEL: many_args_tail:
+; CHECK: @ %bb.0:
+; CHECK-NEXT: mov r0, #5
+; CHECK-NEXT: mov r1, #2
+; CHECK-NEXT: str r0, [sp]
+; CHECK-NEXT: mov r0, #6
+; CHECK-NEXT: str r0, [sp, #4]
+; CHECK-NEXT: mov r0, #1
+; CHECK-NEXT: mov r2, #3
+; CHECK-NEXT: mov r3, #4
+; CHECK-NEXT: b many_args_callee
+ %ret = tail call i32 @many_args_callee(i32 1, i32 2, i32 3, i32 4, i32 5, i32 6)
+ ret i32 %ret
+}
+
+define i32 @many_args_musttail(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4, i32 %5) {
+; CHECK-LABEL: many_args_musttail:
+; CHECK: @ %bb.0:
+; CHECK-NEXT: mov r0, #5
+; CHECK-NEXT: mov r1, #2
+; CHECK-NEXT: str r0, [sp]
+; CHECK-NEXT: mov r0, #6
+; CHECK-NEXT: str r0, [sp, #4]
+; CHECK-NEXT: mov r0, #1
+; CHECK-NEXT: mov r2, #3
+; CHECK-NEXT: mov r3, #4
+; CHECK-NEXT: b many_args_callee
+ %ret = musttail call i32 @many_args_callee(i32 1, i32 2, i32 3, i32 4, i32 5, i32 6)
+ ret i32 %ret
+}
+
+; This function has more arguments than it's tail-callee. This isn't valid for
+; the musttail attribute, but can still be tail-called as a non-guaranteed
+; optimisation, because the outgoing arguments to @many_args_callee fit in the
+; stack space allocated by the caller of @more_args_tail.
+define i32 @more_args_tail(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4, i32 %5, i32 %6) {
+; CHECK-LABEL: more_args_tail:
+; CHECK: @ %bb.0:
+; CHECK-NEXT: mov r0, #5
+; CHECK-NEXT: mov r1, #2
+; CHECK-NEXT: str r0, [sp]
+; CHECK-NEXT: mov r0, #6
+; CHECK-NEXT: str r0, [sp, #4]
+; CHECK-NEXT: mov r0, #1
+; CHECK-NEXT: mov r2, #3
+; CHECK-NEXT: mov r3, #4
+; CHECK-NEXT: b many_args_callee
+ %ret = tail call i32 @many_args_callee(i32 1, i32 2, i32 3, i32 4, i32 5, i32 6)
+ ret i32 %ret
+}
+
+; Again, this isn't valid for musttail, but can be tail-called in practice
+; because the stack size if the same.
+define i32 @different_args_tail(i64 %0, i64 %1, i64 %2) {
+; CHECK-LABEL: different_args_tail:
+; CHECK: @ %bb.0:
+; CHECK-NEXT: mov r0, #5
+; CHECK-NEXT: mov r1, #2
+; CHECK-NEXT: str r0, [sp]
+; CHECK-NEXT: mov r0, #6
+; CHECK-NEXT: str r0, [sp, #4]
+; CHECK-NEXT: mov r0, #1
+; CHECK-NEXT: mov r2, #3
+; CHECK-NEXT: mov r3, #4
+; CHECK-NEXT: b many_args_callee
+ %ret = tail call i32 @many_args_callee(i32 1, i32 2, i32 3, i32 4, i32 5, i32 6)
+ ret i32 %ret
+}
+
+; Here, the caller requires less stack space for it's arguments than the
+; callee, so it would not ba valid to do a tail-call.
+define i32 @fewer_args_tail(i32 %0, i32 %1, i32 %2, i32 %3, i32 %4) {
+; CHECK-LABEL: fewer_args_tail:
+; CHECK: @ %bb.0:
+; CHECK-NEXT: .save {r11, lr}
+; CHECK-NEXT: push {r11, lr}
+; CHECK-NEXT: .pad #8
+; CHECK-NEXT: sub sp, sp, #8
+; CHECK-NEXT: mov r1, #6
+; CHECK-NEXT: mov r0, #5
+; CHECK-NEXT: strd r0, r1, [sp]
+; CHECK-NEXT: mov r0, #1
+; CHECK-NEXT: mov r1, #2
+; CHECK-NEXT: mov r2, #3
+; CHECK-NEXT: mov r3, #4
+; CHECK-NEXT: bl many_args_callee
+; CHECK-NEXT: add sp, sp, #8
+; CHECK-NEXT: pop {r11, pc}
+ %ret = tail call i32 @many_args_callee(i32 1, i32 2, i32 3, i32 4, i32 5, i32 6)
+ ret i32 %ret
+}
+
+declare void @sret_callee(ptr sret({ double, double }) align 8)
+
+; Functions which return by sret can be tail-called because the incoming sret
+; pointer gets passed through to the callee.
+define void @sret_caller_tail(ptr sret({ double, double }) align 8 %result) {
+; CHECK-LABEL: sret_caller_tail:
+; CHECK: @ %bb.0: @ %entry
+; CHECK-NEXT: b sret_callee
+entry:
+ tail call void @sret_callee(ptr sret({ double, double }) align 8 %result)
+ ret void
+}
+
+define void @sret_caller_musttail(ptr sret({ double, double }) align 8 %result) {
+; CHECK-LABEL: sret_caller_musttail:
+; CHECK: @ %bb.0: @ %entry
+; CHECK-NEXT: b sret_callee
+entry:
+ musttail call void @sret_callee(ptr sret({ double, double }) align 8 %result)
+ ret void
+}
+
+%large_struct = type { [20 x i32] }
+declare void @large_callee(%large_struct* byval(%large_struct) align 4)
+
+; Functions with sret parameters can be tail-called, because the value is
+; actually passed in registers and the stack in the same way for the caller and
+; callee. Within @large_caller the first 16 bytes of the argument are spilled
+; to the local stack frame, but for the tail-call they are passed in r0-r3, so
+; it's safe to de-allocate that memory before the call. Most of the code
+; generated for this isn't needed, but that's a missed optimisation, not a
+; correctness issue.
+define void @large_caller(%large_struct* byval(%large_struct) align 4 %a) {
+; CHECK-LABEL: large_caller:
+; CHECK: @ %bb.0: @ %entry
+; CHECK-NEXT: .pad #16
+; CHECK-NEXT: sub sp, sp, #16
+; CHECK-NEXT: .save {r4, lr}
+; CHECK-NEXT: push {r4, lr}
+; CHECK-NEXT: add r12, sp, #8
+; CHECK-NEXT: stm r12, {r0, r1, r2, r3}
+; CHECK-NEXT: add r12, sp, #8
+; CHECK-NEXT: add lr, r12, #16
+; CHECK-NEXT: add r12, sp, #24
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: pop {r4, lr}
+; CHECK-NEXT: add sp, sp, #16
+; CHECK-NEXT: b large_callee
+entry:
+ musttail call void @large_callee(%large_struct* byval(%large_struct) align 4 %a)
+ ret void
+}
+
+; As above, but with some inline asm to test that the arguments in r0-r3 are
+; re-loaded before the call.
+define void @large_caller_check_regs(%large_struct* byval(%large_struct) align 4 %a) {
+; CHECK-LABEL: large_caller_check_regs:
+; CHECK: @ %bb.0: @ %entry
+; CHECK-NEXT: .pad #16
+; CHECK-NEXT: sub sp, sp, #16
+; CHECK-NEXT: .save {r4, lr}
+; CHECK-NEXT: push {r4, lr}
+; CHECK-NEXT: add r12, sp, #8
+; CHECK-NEXT: stm r12, {r0, r1, r2, r3}
+; CHECK-NEXT: @APP
+; CHECK-NEXT: @NO_APP
+; CHECK-NEXT: add r12, sp, #24
+; CHECK-NEXT: add r0, sp, #8
+; CHECK-NEXT: add lr, r0, #16
+; CHECK-NEXT: add r3, sp, #8
+; CHECK-NEXT: ldm r3, {r0, r1, r2, r3}
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: ldr r4, [lr], #4
+; CHECK-NEXT: str r4, [r12], #4
+; CHECK-NEXT: pop {r4, lr}
+; CHECK-NEXT: add sp, sp, #16
+; CHECK-NEXT: b large_callee
+entry:
+ tail call void asm sideeffect "", "~{r0},~{r1},~{r2},~{r3}"()
+ musttail call void @large_callee(%large_struct* byval(%large_struct) align 4 %a)
+ ret void
+}
+
+; The IR for this one looks dodgy, because it has an alloca passed to a
+; musttail function, but it is passed as a byval argument, so will be copied
+; into the stack space allocated by @large_caller_new_value's caller, so is
+; valid.
+define void @large_caller_new_value(%large_struct* byval(%large_struct) align 4 %a) {
+; CHECK-LABEL: large_caller_new_value:
+; CHECK: @ %bb.0: @ %entry
+; CHECK-NEXT: .pad #96
+; CHECK-NEXT: sub sp, sp, #96
+; CHECK-NEXT: add r12, sp, #80
+; CHECK-NEXT: stm r12, {r0, r1, r2, r3}
+; CHECK-NEXT: mov r0, #5
+; CHECK-NEXT: mov r3, #3
+; CHECK-NEXT: str r0, [sp, #20]
+; CHECK-NEXT: mov r0, #4
+; CHECK-NEXT: str r0, [sp, #16]
+; CHECK-NEXT: mov r0, #3
+; CHECK-NEXT: str r0, [sp, #12]
+; CHECK-NEXT: mov r0, #2
+; CHECK-NEXT: str r0, [sp, #8]
+; CHECK-NEXT: mov r0, #1
+; CHECK-NEXT: str r0, [sp, #4]
+; CHECK-NEXT: mov r0, #0
+; CHECK-NEXT: str r0, [sp]
+; CHECK-NEXT: mov r0, sp
+; CHECK-NEXT: add r1, r0, #16
+; CHECK-NEXT: add r0, sp, #96
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: ldr r2, [r1], #4
+; CHECK-NEXT: str r2, [r0], #4
+; CHECK-NEXT: mov r1, #1
+; CHECK-NEXT: mov r0, #0
+; CHECK-NEXT: mov r2, #2
+; CHECK-NEXT: add sp, sp, #96
+; CHECK-NEXT: b large_callee
+entry:
+ %y = alloca %large_struct, align 4
+ store i32 0, ptr %y, align 4
+ %0 = getelementptr inbounds i8, ptr %y, i32 4
+ store i32 1, ptr %0, align 4
+ %1 = getelementptr inbounds i8, ptr %y, i32 8
+ store i32 2, ptr %1, align 4
+ %2 = getelementptr inbounds i8, ptr %y, i32 12
+ store i32 3, ptr %2, align 4
+ %3 = getelementptr inbounds i8, ptr %y, i32 16
+ store i32 4, ptr %3, align 4
+ %4 = getelementptr inbounds i8, ptr %y, i32 20
+ store i32 5, ptr %4, align 4
+ musttail call void @large_callee(%large_struct* byval(%large_struct) align 4 %y)
+ ret void
+}
----------------
ostannard wrote:
Good catch, we generate wrong code for that case, because we try do to the copies in-place without temporary storage. I'll see if there's a reasonable way to fix that, and if not I might have to back out the byval change for now.
https://github.com/llvm/llvm-project/pull/109943
More information about the llvm-commits
mailing list