branch-target-enforcement.mir 13 KB
# RUN: llc -run-pass=aarch64-branch-targets %s -o - | FileCheck %s
--- |
  target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"
  target triple = "aarch64-arm-none-eabi"

  define hidden i32 @simple_external() "branch-target-enforcement"="true" {
  entry:
    ret i32 0
  }

  define internal i32 @simple_internal() "branch-target-enforcement"="true" {
  entry:
    ret i32 0
  }

  define hidden i32 @ptr_auth() "branch-target-enforcement"="true" {
  entry:
    tail call void asm sideeffect "", "~{lr}"()
    ret i32 0
  }

  define hidden i32 @ptr_auth_b() "branch-target-enforcement"="true" {
  entry:
    tail call void asm sideeffect "", "~{lr}"()
    ret i32 0
  }

  define hidden i32 @jump_table(i32 %a) "branch-target-enforcement"="true" {
  entry:
    switch i32 %a, label %sw.epilog [
      i32 1, label %sw.bb
      i32 2, label %sw.bb1
      i32 3, label %sw.bb2
      i32 4, label %sw.bb3
      i32 5, label %sw.bb4
    ]

  sw.bb:                                            ; preds = %entry
    tail call void asm sideeffect "", ""()
    br label %sw.epilog

  sw.bb1:                                           ; preds = %entry
    tail call void asm sideeffect "", ""()
    br label %sw.epilog

  sw.bb2:                                           ; preds = %entry
    tail call void asm sideeffect "", ""()
    br label %sw.epilog

  sw.bb3:                                           ; preds = %entry
    tail call void asm sideeffect "", ""()
    br label %sw.epilog

  sw.bb4:                                           ; preds = %entry
    tail call void asm sideeffect "", ""()
    br label %sw.epilog

  sw.epilog:                                        ; preds = %entry, %sw.bb4, %sw.bb3, %sw.bb2, %sw.bb1, %sw.bb
    ret i32 0
  }

  @label_address.addr = internal unnamed_addr global i8* blockaddress(@label_address, %return), align 8

  define hidden i32 @label_address() "branch-target-enforcement"="true" {
  entry:
    %0 = load i8*, i8** @label_address.addr, align 8
    indirectbr i8* %0, [label %return, label %lab2]

  lab2:                                             ; preds = %entry
    br label %.split

  return:                                           ; preds = %entry
    br label %.split

  .split:                                           ; preds = %lab2, %return
    %merge = phi i8* [ blockaddress(@label_address, %lab2), %return ], [ blockaddress(@label_address, %return), %lab2 ]
    %merge2 = phi i32 [ 1, %return ], [ 2, %lab2 ]
    store i8* %merge, i8** @label_address.addr, align 8
    ret i32 %merge2
  }

  define hidden i32 @label_address_entry() "branch-target-enforcement"="true" {
  entry:
    %0 = load i8*, i8** @label_address.addr, align 8
    indirectbr i8* %0, [label %return, label %lab2]

  lab2:                                             ; preds = %entry
    br label %.split

  return:                                           ; preds = %entry
    br label %.split

  .split:                                           ; preds = %lab2, %return
    %merge = phi i8* [ blockaddress(@label_address, %lab2), %return ], [ blockaddress(@label_address, %return), %lab2 ]
    %merge2 = phi i32 [ 1, %return ], [ 2, %lab2 ]
    store i8* %merge, i8** @label_address.addr, align 8
    ret i32 %merge2
  }

  define hidden i32 @debug_ptr_auth() "branch-target-enforcement"="true" {
  entry:
    tail call void asm sideeffect "", "~{lr}"()
    ret i32 0
  }

...
---
# External function, could be addres-taken elsewhere so needs BTI JC.
name:            simple_external
body:             |
  bb.0.entry:
    ; CHECK-LABEL: name: simple_external
    ; CHECK: HINT 34
    ; CHECK: RET
    $w0 = ORRWrs $wzr, $wzr, 0
    RET undef $lr, implicit killed $w0

---
# Internal function, not address-taken in this module, so no BTI needed.
name:            simple_internal
body:             |
  bb.0.entry:
    ; CHECK-LABEL: name: simple_internal
    ; CHECK-NOT: HINT
    ; CHECK: RET
    $w0 = ORRWrs $wzr, $wzr, 0
    RET undef $lr, implicit killed $w0

---
# Function starts with PACIASP, which implicitly acts as BTI JC, so no change
# needed.
name:            ptr_auth
stack:
  - { id: 0, name: '', type: spill-slot, offset: -16, size: 8, alignment: 16,
      stack-id: default, callee-saved-register: '$lr', callee-saved-restored: true,
      debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
body:             |
  bb.0.entry:
    liveins: $lr

    ; CHECK-LABEL: name: ptr_auth
    ; CHECK-NOT: HINT
    ; CHECK: frame-setup PACIASP
    ; CHECK-NOT: HINT
    ; CHECK: RETAA
    frame-setup PACIASP implicit-def $lr, implicit killed $lr, implicit $sp
    early-clobber $sp = frame-setup STRXpre killed $lr, $sp, -16 :: (store 8 into %stack.0)
    INLINEASM &"", 1, 12, implicit-def dead early-clobber $lr
    $w0 = ORRWrs $wzr, $wzr, 0
    early-clobber $sp, $lr = frame-destroy LDRXpost $sp, 16 :: (load 8 from %stack.0)
    RETAA implicit $sp, implicit $lr, implicit killed $w0

---
# Function starts with PACIBSP, which implicitly acts as BTI JC, so no change
# needed.
name:            ptr_auth_b
stack:
  - { id: 0, name: '', type: spill-slot, offset: -16, size: 8, alignment: 16,
      stack-id: default, callee-saved-register: '$lr', callee-saved-restored: true,
      debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
body:             |
  bb.0.entry:
    liveins: $lr

    ; CHECK-LABEL: name: ptr_auth_b
    ; CHECK-NOT: HINT
    ; CHECK: frame-setup PACIBSP
    ; CHECK-NOT: HINT
    ; CHECK: RETAB
    frame-setup PACIBSP implicit-def $lr, implicit killed $lr, implicit $sp
    early-clobber $sp = frame-setup STRXpre killed $lr, $sp, -16 :: (store 8 into %stack.0)
    INLINEASM &"", 1, 12, implicit-def dead early-clobber $lr
    $w0 = ORRWrs $wzr, $wzr, 0
    early-clobber $sp, $lr = frame-destroy LDRXpost $sp, 16 :: (load 8 from %stack.0)
    RETAB implicit $sp, implicit $lr, implicit killed $w0

---
# Function contains a jump table, so every target of the jump table must start
# with BTI J.
name:            jump_table
jumpTable:
  kind:            block-address
  entries:
    - id:              0
      blocks:          [ '%bb.2', '%bb.3', '%bb.4', '%bb.5', '%bb.6' ]
body:             |
  bb.0.entry:
    ; CHECK-LABEL: name: jump_table
    ; CHECK: HINT 34
    successors: %bb.7(0x15555555), %bb.1(0x6aaaaaab)
    liveins: $w0

    renamable $w8 = SUBWri killed renamable $w0, 1, 0, implicit-def $x8
    dead $wzr = SUBSWri renamable $w8, 4, 0, implicit-def $nzcv
    Bcc 8, %bb.7, implicit $nzcv

  bb.1.entry:
    ; CHECK: bb.1.entry:
    ; CHECK-NOT: HINT
    ; CHECK: BR killed renamable $x8
    successors: %bb.2(0x1999999a), %bb.3(0x1999999a), %bb.4(0x1999999a), %bb.5(0x1999999a), %bb.6(0x1999999a)
    liveins: $x8

    $x9 = ADRP target-flags(aarch64-page) %jump-table.0
    renamable $x9 = ADDXri killed $x9, target-flags(aarch64-pageoff, aarch64-nc) %jump-table.0, 0
    renamable $x8 = LDRXroX killed renamable $x9, killed renamable $x8, 0, 1 :: (load 8 from jump-table)
    BR killed renamable $x8

  bb.2.sw.bb:
    ; CHECK: bb.2.sw.bb
    ; CHECK-NEXT: HINT 36
    $w0 = ORRWrs $wzr, $wzr, 0
    INLINEASM &"", 1
    RET undef $lr, implicit killed $w0

  bb.3.sw.bb1:
    ; CHECK: bb.3.sw.bb1
    ; CHECK-NEXT: HINT 36
    $w0 = ORRWrs $wzr, $wzr, 0
    INLINEASM &"", 1
    RET undef $lr, implicit killed $w0

  bb.4.sw.bb2:
    ; CHECK: bb.4.sw.bb2
    ; CHECK-NEXT: HINT 36
    $w0 = ORRWrs $wzr, $wzr, 0
    INLINEASM &"", 1
    RET undef $lr, implicit killed $w0

  bb.5.sw.bb3:
    ; CHECK: bb.5.sw.bb3
    ; CHECK-NEXT: HINT 36
    $w0 = ORRWrs $wzr, $wzr, 0
    INLINEASM &"", 1
    RET undef $lr, implicit killed $w0

  bb.6.sw.bb4:
    ; CHECK: bb.6.sw.bb4
    ; CHECK-NEXT: successors: %bb.7(0x80000000)
    ; CHECK-NEXT: {{  }}
    ; CHECK-NEXT: HINT 36
    successors: %bb.7(0x80000000)

    INLINEASM &"", 1

  bb.7.sw.epilog:
    ; CHECK: bb.7.sw.epilog:
    ; CHECK-NOT: HINT
    ; CHECK: RET
    $w0 = ORRWrs $wzr, $wzr, 0
    RET undef $lr, implicit killed $w0

---
# Function takes address of basic blocks, so they must start with BTI J.
name:            label_address
body:             |
  bb.0.entry:
    ; CHECK-LABEL: label_address
    ; CHECK: bb.0.entry:
    ; CHECK-NEXT: successors: %bb.1(0x40000000), %bb.2(0x40000000)
    ; CHECK-NEXT: {{  }}
    ; CHECK-NEXT: HINT 34
    ; CHECK: BR killed renamable $x9
    successors: %bb.1(0x40000000), %bb.2(0x40000000)

    renamable $x8 = ADRP target-flags(aarch64-page) @label_address.addr
    renamable $x9 = LDRXui renamable $x8, target-flags(aarch64-pageoff, aarch64-nc) @label_address.addr :: (dereferenceable load 8 from @label_address.addr)
    BR killed renamable $x9

  bb.1.return (address-taken):
    ; CHECK: bb.1.return (address-taken):
    ; CHECK-NEXT: HINT 36
    liveins: $x8

    $x9 = ADRP target-flags(aarch64-page) blockaddress(@label_address, %ir-block.lab2)
    renamable $w0 = ORRWri $wzr, 0
    renamable $x9 = ADDXri killed $x9, target-flags(aarch64-pageoff, aarch64-nc) blockaddress(@label_address, %ir-block.lab2), 0
    STRXui killed renamable $x9, killed renamable $x8, target-flags(aarch64-pageoff, aarch64-nc) @label_address.addr :: (store 8 into @label_address.addr)
    RET undef $lr, implicit killed $w0

  bb.2.lab2 (address-taken):
    ; CHECK: bb.2.lab2 (address-taken):
    ; CHECK-NEXT: HINT 36
    liveins: $x8

    $x9 = ADRP target-flags(aarch64-page) blockaddress(@label_address, %ir-block.return)
    renamable $w0 = ORRWri $wzr, 1984
    renamable $x9 = ADDXri killed $x9, target-flags(aarch64-pageoff, aarch64-nc) blockaddress(@label_address, %ir-block.return), 0
    STRXui killed renamable $x9, killed renamable $x8, target-flags(aarch64-pageoff, aarch64-nc) @label_address.addr :: (store 8 into @label_address.addr)
    RET undef $lr, implicit killed $w0

---
# Function takes address of the entry block, so the entry block needs a BTI JC.
name:            label_address_entry
stack:
  - { id: 0, name: '', type: spill-slot, offset: -16, size: 8, alignment: 16,
      stack-id: default, callee-saved-register: '$lr', callee-saved-restored: true,
      debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
body:             |
  bb.0.entry (address-taken):
    ; CHECK-LABEL: label_address_entry
    ; CHECK: bb.0.entry (address-taken):
    ; CHECK-NEXT: successors: %bb.1(0x40000000), %bb.2(0x40000000)
    ; CHECK-NEXT: {{  }}
    ; CHECK-NEXT: HINT 38
    ; CHECK: BR killed renamable $x9
    successors: %bb.1(0x40000000), %bb.2(0x40000000)

    renamable $x8 = ADRP target-flags(aarch64-page) @label_address.addr
    renamable $x9 = LDRXui renamable $x8, target-flags(aarch64-pageoff, aarch64-nc) @label_address.addr :: (dereferenceable load 8 from @label_address.addr)
    BR killed renamable $x9

  bb.1.return (address-taken):
    ; CHECK: bb.1.return (address-taken):
    ; CHECK-NEXT: HINT 36
    liveins: $x8
    frame-setup PACIASP implicit-def $lr, implicit killed $lr, implicit $sp
    frame-setup CFI_INSTRUCTION negate_ra_sign_state
    early-clobber $sp = frame-setup STRXpre killed $lr, $sp, -16 :: (store 8 into %stack.0)
    INLINEASM &"", 1, 12, implicit-def dead early-clobber $lr
    $x9 = ADRP target-flags(aarch64-page) blockaddress(@label_address, %ir-block.entry)
    renamable $w0 = ORRWri $wzr, 0
    renamable $x9 = ADDXri killed $x9, target-flags(aarch64-pageoff, aarch64-nc) blockaddress(@label_address, %ir-block.entry), 0
    STRXui killed renamable $x9, killed renamable $x8, target-flags(aarch64-pageoff, aarch64-nc) @label_address.addr :: (store 8 into @label_address.addr)
    early-clobber $sp, $lr = frame-destroy LDRXpost $sp, 16 :: (load 8 from %stack.0)
    RETAA implicit $sp, implicit $lr, implicit killed $w0

  bb.2.lab2:
    ; CHECK: bb.2.lab2:
    ; CHECK-NOT: HINT
    liveins: $x8

    $x9 = ADRP target-flags(aarch64-page) blockaddress(@label_address, %ir-block.return)
    renamable $w0 = ORRWri $wzr, 1984
    renamable $x9 = ADDXri killed $x9, target-flags(aarch64-pageoff, aarch64-nc) blockaddress(@label_address, %ir-block.return), 0
    STRXui killed renamable $x9, killed renamable $x8, target-flags(aarch64-pageoff, aarch64-nc) @label_address.addr :: (store 8 into @label_address.addr)
    RET undef $lr, implicit killed $w0
---
# When PACIASP is the first real instruction in the functions then BTI should not be inserted.
name:            debug_ptr_auth
stack:
  - { id: 0, name: '', type: spill-slot, offset: -16, size: 8, alignment: 16,
      stack-id: default, callee-saved-register: '$lr', callee-saved-restored: true,
      debug-info-variable: '', debug-info-expression: '', debug-info-location: '' }
body:             |
  bb.0.entry:
    liveins: $lr

    ; CHECK-LABEL: name: debug_ptr_auth
    ; CHECK-NOT: HINT
    ; CHECK: frame-setup PACIASP
    ; CHECK-NOT: HINT
    ; CHECK: RETAA
    frame-setup PACIASP implicit-def $lr, implicit killed $lr, implicit $sp
    frame-setup CFI_INSTRUCTION negate_ra_sign_state
    early-clobber $sp = frame-setup STRXpre killed $lr, $sp, -16 :: (store 8 into %stack.0)
    INLINEASM &"", 1, 12, implicit-def dead early-clobber $lr
    $w0 = ORRWrs $wzr, $wzr, 0
    early-clobber $sp, $lr = frame-destroy LDRXpost $sp, 16 :: (load 8 from %stack.0)
    RETAA implicit $sp, implicit $lr, implicit killed $w0

...