[llvm] [AArch64][BTI] Add BTI at EH entries. (PR #155308)

Shashi Shankar via llvm-commits llvm-commits at lists.llvm.org
Tue Aug 26 12:34:12 PDT 2025


https://github.com/shashforge updated https://github.com/llvm/llvm-project/pull/155308

>From 3e520689d7618f6318881f508e0ed672e860b829 Mon Sep 17 00:00:00 2001
From: Shashi Shankar <shashishankar1687 at gmail.com>
Date: Mon, 25 Aug 2025 23:13:19 +0200
Subject: [PATCH] [AArch64][BTI] Add BTI at EH entries: BTI j for Itanium EH,
 BTI c for WinEH

Signed-off-by: Shashi Shankar <shashishankar1687 at gmail.com>
---
 .../Target/AArch64/AArch64BranchTargets.cpp   | 25 +++++-
 llvm/test/CodeGen/AArch64/bti-ehpad.ll        | 50 +++++++++++
 .../test/CodeGen/AArch64/wineh-bti-funclet.ll | 83 +++++++++++++++++++
 3 files changed, 154 insertions(+), 4 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/bti-ehpad.ll
 create mode 100644 llvm/test/CodeGen/AArch64/wineh-bti-funclet.ll

diff --git a/llvm/lib/Target/AArch64/AArch64BranchTargets.cpp b/llvm/lib/Target/AArch64/AArch64BranchTargets.cpp
index 3436dc9ef4521..ccd08859b54d2 100644
--- a/llvm/lib/Target/AArch64/AArch64BranchTargets.cpp
+++ b/llvm/lib/Target/AArch64/AArch64BranchTargets.cpp
@@ -62,9 +62,8 @@ bool AArch64BranchTargets::runOnMachineFunction(MachineFunction &MF) {
   if (!MF.getInfo<AArch64FunctionInfo>()->branchTargetEnforcement())
     return false;
 
-  LLVM_DEBUG(
-      dbgs() << "********** AArch64 Branch Targets  **********\n"
-             << "********** Function: " << MF.getName() << '\n');
+  LLVM_DEBUG(dbgs() << "********** AArch64 Branch Targets  **********\n"
+                    << "********** Function: " << MF.getName() << '\n');
   const Function &F = MF.getFunction();
 
   // LLVM does not consider basic blocks which are the targets of jump tables
@@ -103,6 +102,12 @@ bool AArch64BranchTargets::runOnMachineFunction(MachineFunction &MF) {
         JumpTableTargets.count(&MBB))
       CouldJump = true;
 
+    if (MBB.isEHPad()) {
+      if (HasWinCFI && (MBB.isEHFuncletEntry() || MBB.isCleanupFuncletEntry()))
+        CouldCall = true;
+      else
+        CouldJump = true;
+    }
     if (CouldCall || CouldJump) {
       addBTI(MBB, CouldCall, CouldJump, HasWinCFI);
       MadeChange = true;
@@ -129,7 +134,14 @@ void AArch64BranchTargets::addBTI(MachineBasicBlock &MBB, bool CouldCall,
   assert(HintNum != 32 && "No target kinds!");
 
   auto MBBI = MBB.begin();
+  bool SawEHLabel = false;
 
+  // If the block starts with EH_LABEL(s), skip them first and remember we saw
+  // one.
+  while (MBBI != MBB.end() && MBBI->isEHLabel()) {
+    ++MBBI;
+    SawEHLabel = true;
+  }
   // Skip the meta instructions, those will be removed anyway.
   for (; MBBI != MBB.end() &&
          (MBBI->isMetaInstruction() || MBBI->getOpcode() == AArch64::EMITBKEY);
@@ -147,7 +159,12 @@ void AArch64BranchTargets::addBTI(MachineBasicBlock &MBB, bool CouldCall,
     BuildMI(MBB, MBB.begin(), MBB.findDebugLoc(MBB.begin()),
             TII->get(AArch64::SEH_Nop));
   }
-  BuildMI(MBB, MBB.begin(), MBB.findDebugLoc(MBB.begin()),
+  // Insertion policy:
+  //  If the block started with an EH_LABEL, insert BTI after the label and
+  //  meta (at MBBI).Otherwise, keep legacy behavior: insert at block begin
+  //  (before CFI/SEH NOP), to preserve existing codegen/test expectations.
+  MachineBasicBlock::iterator InsertHere = SawEHLabel ? MBBI : MBB.begin();
+  BuildMI(MBB, InsertHere, MBB.findDebugLoc(InsertHere),
           TII->get(AArch64::HINT))
       .addImm(HintNum);
 }
diff --git a/llvm/test/CodeGen/AArch64/bti-ehpad.ll b/llvm/test/CodeGen/AArch64/bti-ehpad.ll
new file mode 100644
index 0000000000000..874a14145b19b
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/bti-ehpad.ll
@@ -0,0 +1,50 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc -mtriple=aarch64-unknown-linux-gnu %s -o - | FileCheck %s
+
+; Purpose: Validate that for Itanium EH on AArch64 with BTI, the landing pad
+; (%lpad) begins with an EH_LABEL and the very next executed instruction is
+; `bti j`. This ensures the unwinder’s indirect branch lands on BTI-protected
+; code (BTI inserted *after* the EH label).
+
+declare i32 @__gxx_personality_v0(...)
+declare void @may_throw()
+
+define void @test() #0 personality ptr @__gxx_personality_v0 {
+; CHECK-LABEL: test:
+; CHECK:       .Lfunc_begin0:
+; CHECK-NEXT:    .cfi_startproc
+; CHECK-NEXT:    .cfi_personality 156, DW.ref.__gxx_personality_v0
+; CHECK-NEXT:    .cfi_lsda 28, .Lexception0
+; CHECK-NEXT:  // %bb.0: // %entry
+; CHECK-NEXT:    bti c
+; CHECK-NEXT:    str x30, [sp, #-16]! // 8-byte Folded Spill
+; CHECK-NEXT:    .cfi_def_cfa_offset 16
+; CHECK-NEXT:    .cfi_offset w30, -16
+; CHECK-NEXT:  .Ltmp0: // EH_LABEL
+; CHECK-NEXT:    bl may_throw
+; CHECK-NEXT:  .Ltmp1: // EH_LABEL
+; CHECK-NEXT:  // %bb.1: // %common.ret
+; CHECK-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-NEXT:    ret
+; CHECK-NEXT:  .LBB0_2: // %lpad
+; CHECK-NEXT:  .Ltmp2: // EH_LABEL
+; CHECK-NEXT:    bti j
+; CHECK-NEXT:    ldr x30, [sp], #16 // 8-byte Folded Reload
+; CHECK-NEXT:    ret
+entry:
+  invoke void @may_throw()
+          to label %ret unwind label %lpad
+
+lpad:
+  landingpad { ptr, i32 } cleanup
+  ret void
+
+ret:
+  ret void
+}
+
+; Enforce BTI at codegen.
+attributes #0 = { noinline "branch-target-enforcement"="true" "target-features"="+bti" }
+
+; (No extra BTI checks needed—the autogenerated CHECK block above already
+;  asserts that the landing pad label is followed by EH_LABEL and `bti j`.)
diff --git a/llvm/test/CodeGen/AArch64/wineh-bti-funclet.ll b/llvm/test/CodeGen/AArch64/wineh-bti-funclet.ll
new file mode 100644
index 0000000000000..b13b55ee99921
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/wineh-bti-funclet.ll
@@ -0,0 +1,83 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5
+; RUN: llc -mtriple=aarch64-windows -mattr=+bti -o - %s | FileCheck %s
+
+declare i32 @__CxxFrameHandler3(...)
+declare void @may_throw()
+
+; Purpose: For WinEH funclets, entry is call-like: accept `bti c` / `hint #34`
+; or a PAC prologue (paciasp/pacibsp).
+
+define dso_local void @wineh_funclet() #0 personality ptr @__CxxFrameHandler3 {
+; CHECK-LABEL: wineh_funclet:
+; CHECK:       .Lfunc_begin0:
+; CHECK-NEXT:  .seh_proc wineh_funclet
+; CHECK-NEXT:    .seh_handler __CxxFrameHandler3, @unwind, @except
+; CHECK-NEXT:  // %bb.0: // %entry
+; CHECK-NEXT:    bti c
+; CHECK-NEXT:    .seh_nop
+; CHECK-NEXT:    stp x29, x30, [sp, #-32]! // 16-byte Folded Spill
+; CHECK-NEXT:    .seh_save_fplr_x 32
+; CHECK-NEXT:    mov x29, sp
+; CHECK-NEXT:    .seh_set_fp
+; CHECK-NEXT:    .seh_endprologue
+; CHECK-NEXT:    mov x0, #-2 // =0xfffffffffffffffe
+; CHECK-NEXT:    stur x0, [x29, #16]
+; CHECK-NEXT:  .Ltmp0: // EH_LABEL
+; CHECK-NEXT:    bl may_throw
+; CHECK-NEXT:  .Ltmp1: // EH_LABEL
+; CHECK-NEXT:  .LBB0_1: // Block address taken
+; CHECK-NEXT:    // %try.cont
+; CHECK-NEXT:  $ehgcr_0_1:
+; CHECK-NEXT:    bti j
+; CHECK-NEXT:    .seh_startepilogue
+; CHECK-NEXT:    ldp x29, x30, [sp], #32 // 16-byte Folded Reload
+; CHECK-NEXT:    .seh_save_fplr_x 32
+; CHECK-NEXT:    .seh_endepilogue
+; CHECK-NEXT:    ret
+; CHECK-NEXT:    .seh_endfunclet
+; CHECK-NEXT:    .seh_handlerdata
+; CHECK-NEXT:    .word $cppxdata$wineh_funclet at IMGREL
+; CHECK-NEXT:    .text
+; CHECK-NEXT:    .seh_endproc
+; CHECK-NEXT:    .def "?catch$2@?0?wineh_funclet at 4HA";
+; CHECK-NEXT:    .scl 3;
+; CHECK-NEXT:    .type 32;
+; CHECK-NEXT:    .endef
+; CHECK-NEXT:    .p2align 2
+; CHECK-NEXT:  "?catch$2@?0?wineh_funclet at 4HA":
+; CHECK-NEXT:  .seh_proc "?catch$2@?0?wineh_funclet at 4HA"
+; CHECK-NEXT:    .seh_handler __CxxFrameHandler3, @unwind, @except
+; CHECK-NEXT:  .LBB0_2: // %catch
+; CHECK-NEXT:    bti c
+; CHECK-NEXT:    .seh_nop
+; CHECK-NEXT:    stp x29, x30, [sp, #-16]! // 16-byte Folded Spill
+; CHECK-NEXT:    .seh_save_fplr_x 16
+; CHECK-NEXT:    .seh_endprologue
+; CHECK-NEXT:    bl may_throw
+; CHECK-NEXT:    adrp x0, .LBB0_1
+; CHECK-NEXT:    add x0, x0, .LBB0_1
+; CHECK-NEXT:    .seh_startepilogue
+; CHECK-NEXT:    ldp x29, x30, [sp], #16 // 16-byte Folded Reload
+; CHECK-NEXT:    .seh_save_fplr_x 16
+; CHECK-NEXT:    .seh_endepilogue
+; CHECK-NEXT:    ret
+entry:
+  invoke void @may_throw()
+          to label %try.cont unwind label %catch.dispatch
+
+catch.dispatch:
+  %cs = catchswitch within none [label %catch] unwind to caller
+
+catch:
+  %cp = catchpad within %cs [ptr null, i32 0, ptr null]
+  call void @may_throw() ["funclet"(token %cp)]
+  catchret from %cp to label %try.cont
+
+try.cont:
+  ret void
+}
+
+attributes #0 = { noinline "branch-target-enforcement"="true" }
+
+; (Full-body autogenerated checks above already assert `bti c` at entry and
+; `bti c` at the funclet/catch entries for WinEH with BTI.)



More information about the llvm-commits mailing list