llvm-project/llvm/test/CodeGen/X86/callbr-asm-blockplacement.ll
Simon Tatham 56acb06bc6
[ARM,AArch64] Don't put BTI at asm goto branch targets (#141562)
In 'asm goto' statements ('callbr' in LLVM IR), you can specify one or
more labels / basic blocks in the containing function which the assembly
code might jump to. If you're also compiling with branch target
enforcement via BTI, then previously listing a basic block as a possible
jump destination of an asm goto would cause a BTI instruction to be
placed at the start of the block, in case the assembly code used an
_indirect_ branch instruction (i.e. to a destination address read from a
register) to jump to that location. Now it doesn't do that any more:
branches to destination labels from the assembly code are assumed to be
direct branches (to a relative offset encoded in the instruction), which
don't require a BTI at their destination.

This change was proposed in https://discourse.llvm.org/t/85845 and there
seemed to be no disagreement. The rationale is:

1. it brings clang's handling of asm goto in Arm and AArch64 in line
with gcc's, which didn't generate BTIs at the target labels in the first
place.

2. it improves performance in the Linux kernel, which uses a lot of 'asm
goto' in which the assembly language just contains a NOP, and the
label's address is saved elsewhere to let the kernel self-modify at run
time to swap between the original NOP and a direct branch to the label.
This allows hot code paths to be instrumented for debugging, at only the
cost of a NOP when the instrumentation is turned off, instead of the
larger cost of an indirect branch. In this situation a BTI is
unnecessary (if the branch happens it's direct), and since the code
paths are hot, also a noticeable performance hit.

Implementation:

`SelectionDAGBuilder::visitCallBr` is the place where 'asm goto' target
labels are handled. It calls `setIsInlineAsmBrIndirectTarget()` on each
target `MachineBasicBlock`. Previously it also called
`setMachineBlockAddressTaken()`, which made `hasAddressTaken()` return
true, which caused a BTI to be added in the Arm backends.

Now `visitCallBr` doesn't call `setMachineBlockAddressTaken()` any more
on asm goto targets, but `hasAddressTaken()` also checks the flag set by
`setIsInlineAsmBrIndirectTarget()`. So call sites that were using
`hasAddressTaken()` don't need to be modified. But the Arm backends
don't call `hasAddressTaken()` any more: instead they test two more
specific query functions that cover all the reasons `hasAddressTaken()`
might have returned true _except_ being an asm goto target.

Testing:

The new test `AArch64/callbr-asm-label-bti.ll` is testing the actual
change, where it expects not to see a `bti` instruction after
`[[LABEL]]`. The rest of the test changes are all churn, due to the
flags on basic blocks changing. Actual output code hasn't changed in any
of the existing tests, only comments and diagnostics.

Further work:

`RISCVIndirectBranchTracking.cpp` and `X86IndirectBranchTracking.cpp`
also call `hasAddressTaken()` in a way that might benefit from using the
same more specific check I've put in `ARMBranchTargets.cpp` and
`AArch64BranchTargets.cpp`. But I'm not sure of that, so in this commit
I've only changed the Arm backends, and left those alone.
2025-06-03 08:44:13 +01:00

108 lines
3.5 KiB
LLVM

; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
; RUN: llc < %s -mtriple=x86_64-unknown-linux-gnu | FileCheck %s
; This test asserted in MachineBlockPlacement during asm-goto bring up.
%struct.wibble = type { %struct.pluto, i32, ptr }
%struct.pluto = type { i32, i32, i32 }
@global = external dso_local global [0 x %struct.wibble]
define i32 @foo(i32 %arg, ptr %arg3) nounwind {
; CHECK-LABEL: foo:
; CHECK: # %bb.0: # %bb
; CHECK-NEXT: pushq %rbp
; CHECK-NEXT: pushq %r15
; CHECK-NEXT: pushq %r14
; CHECK-NEXT: pushq %r13
; CHECK-NEXT: pushq %r12
; CHECK-NEXT: pushq %rbx
; CHECK-NEXT: pushq %rax
; CHECK-NEXT: movabsq $-2305847407260205056, %r14 # imm = 0xDFFFFC0000000000
; CHECK-NEXT: xorl %eax, %eax
; CHECK-NEXT: testb %al, %al
; CHECK-NEXT: jne .LBB0_5
; CHECK-NEXT: # %bb.1: # %bb5
; CHECK-NEXT: movq %rsi, %rbx
; CHECK-NEXT: movslq %edi, %rbp
; CHECK-NEXT: leaq (,%rbp,8), %rax
; CHECK-NEXT: leaq global(%rax,%rax,2), %r14
; CHECK-NEXT: leaq global+4(%rax,%rax,2), %r15
; CHECK-NEXT: xorl %r13d, %r13d
; CHECK-NEXT: .p2align 4
; CHECK-NEXT: .LBB0_2: # %bb8
; CHECK-NEXT: # =>This Inner Loop Header: Depth=1
; CHECK-NEXT: callq bar@PLT
; CHECK-NEXT: movq %rax, %r12
; CHECK-NEXT: movq %rax, %rdi
; CHECK-NEXT: callq *%rbx
; CHECK-NEXT: movq %r14, %rdi
; CHECK-NEXT: callq hoge@PLT
; CHECK-NEXT: movq %r15, %rdi
; CHECK-NEXT: callq hoge@PLT
; CHECK-NEXT: testb %r13b, %r13b
; CHECK-NEXT: jne .LBB0_2
; CHECK-NEXT: # %bb.3: # %bb15
; CHECK-NEXT: leaq (%rbp,%rbp,2), %rax
; CHECK-NEXT: movq %r12, global+16(,%rax,8)
; CHECK-NEXT: movabsq $-2305847407260205056, %r14 # imm = 0xDFFFFC0000000000
; CHECK-NEXT: #APP
; CHECK-NEXT: #NO_APP
; CHECK-NEXT: # %bb.4: # %bb17
; CHECK-NEXT: callq widget@PLT
; CHECK-NEXT: .LBB0_5: # Inline asm indirect target
; CHECK-NEXT: # %bb18
; CHECK-NEXT: # Label of block must be emitted
; CHECK-NEXT: movw $0, 14(%r14)
; CHECK-NEXT: addq $8, %rsp
; CHECK-NEXT: popq %rbx
; CHECK-NEXT: popq %r12
; CHECK-NEXT: popq %r13
; CHECK-NEXT: popq %r14
; CHECK-NEXT: popq %r15
; CHECK-NEXT: popq %rbp
; CHECK-NEXT: retq
bb:
%tmp = add i64 0, -2305847407260205056
%tmp4 = sext i32 %arg to i64
br i1 undef, label %bb18, label %bb5
bb5: ; preds = %bb
%tmp6 = getelementptr [0 x %struct.wibble], ptr @global, i64 0, i64 %tmp4, i32 0, i32 0
%tmp7 = getelementptr [0 x %struct.wibble], ptr @global, i64 0, i64 %tmp4, i32 0, i32 1
br label %bb8
bb8: ; preds = %bb8, %bb5
%tmp9 = call ptr @bar(i64 undef)
%tmp10 = call i32 %arg3(ptr nonnull %tmp9)
%tmp11 = ptrtoint ptr %tmp6 to i64
call void @hoge(i64 %tmp11)
%tmp12 = ptrtoint ptr %tmp7 to i64
%tmp13 = add i64 undef, -2305847407260205056
call void @hoge(i64 %tmp12)
%tmp14 = icmp eq i32 0, 0
br i1 %tmp14, label %bb15, label %bb8
bb15: ; preds = %bb8
%tmp16 = getelementptr [0 x %struct.wibble], ptr @global, i64 0, i64 %tmp4, i32 2
store ptr %tmp9, ptr %tmp16
callbr void asm sideeffect "", "!i"()
to label %bb17 [label %bb18]
bb17: ; preds = %bb15
call void @widget()
br label %bb18
bb18: ; preds = %bb17, %bb15, %bb
%tmp19 = add i64 %tmp, 14
%tmp20 = inttoptr i64 %tmp19 to ptr
store i16 0, ptr %tmp20
ret i32 undef
}
declare ptr @bar(i64)
declare void @widget()
declare void @hoge(i64)