[llvm] [AArch64] Fix missing :lo12: on the catchret address pair (PR #219200)

Jonas Heinle via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 27 07:06:45 PDT 2026


https://github.com/Kataglyphis updated https://github.com/llvm/llvm-project/pull/219200

>From 5ffe34a01054ada90d609f35d671ab8548ab2d21 Mon Sep 17 00:00:00 2001
From: Jonas Heinle <jonasheinle at googlemail.com>
Date: Thu, 27 Aug 2026 14:32:13 +0200
Subject: [PATCH] [AArch64] Fix missing :lo12: on the catchret address pair

The CATCHRET expansion builds the ADRP/ADD pair for the continuation block
without relocation specifiers, so the ADD prints as

    add x0, x0, .LBB0_1

and LLVM's own assembler then rejects LLVM's own output:

    error: expected compatible register, symbol or integer in range [0, 4095]

Any function with a catchret hits this. Set MO_PAGE and MO_PAGEOFF|MO_NC on the
two block operands and let lowerOperand() honour them, the way every other
symbol operand is already handled.

Two things a reviewer will want to know, both learned the hard way:

  * The flags have to go on the MBB operands. Rewriting the pair with .addSym()
    fixes the printing too, but it drops the block reference and shifts
    basic-block numbering in wineh-catchret-label-generation.ll.
  * LowerSymbolOperand() reads MO.getOffset(), which an MBB operand does not
    have. It needs the same guard the neighbouring line already carries for
    jump-table indices, in all three of the ELF/MachO/COFF paths.

Objects are unaffected -- the relocations were always correct (PAGEBASE_REL21 +
PAGEOFFSET_12A, verified by assembling the fixed output and diffing against
-filetype=obj). This is -S and clang-cl /FA only. That is still the path you
take when a codegen error arrives without a source location, and on
Windows-on-ARM with C++ exceptions it was closed.

Verified on a local AArch64 build with assertions, by running the patched and
the unpatched llc over all 2869 tests in test/CodeGen/AArch64: of the 2081 both
could compile, 14 differ, every one of them a Windows-EH test, and the whole
diff is 14 lines -- each the added :lo12:. Seven of those tests check that line
and are updated here; the other seven pass unchanged. The new test fails
against unpatched llc.

Found with clang-cl 23.1.0 building OpenCV for aarch64-pc-windows-msvc.

Assisted-by: Claude (Anthropic)
---
 llvm/lib/Target/AArch64/AArch64InstrInfo.cpp  |  4 +--
 .../lib/Target/AArch64/AArch64MCInstLower.cpp | 17 +++++++++----
 llvm/test/CodeGen/AArch64/pr58516.ll          |  2 +-
 .../test/CodeGen/AArch64/wineh-bti-funclet.ll |  2 +-
 .../wineh-catchret-label-generation.ll        |  2 +-
 .../CodeGen/AArch64/wineh-catchret-lo12.ll    | 25 +++++++++++++++++++
 .../CodeGen/AArch64/wineh-try-catch-nobase.ll |  2 +-
 .../AArch64/wineh-try-catch-realign.ll        |  2 +-
 .../CodeGen/AArch64/wineh-try-catch-vla.ll    |  2 +-
 llvm/test/CodeGen/AArch64/wineh-try-catch.ll  |  2 +-
 10 files changed, 46 insertions(+), 14 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/wineh-catchret-lo12.ll

diff --git a/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp b/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
index 43aab1400e81f..2a3b699b2ce87 100644
--- a/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
+++ b/llvm/lib/Target/AArch64/AArch64InstrInfo.cpp
@@ -2592,11 +2592,11 @@ bool AArch64InstrInfo::expandPostRAPseudo(MachineInstr &MI) const {
       FirstEpilogSEH = std::next(FirstEpilogSEH);
     BuildMI(MBB, FirstEpilogSEH, DL, TII->get(AArch64::ADRP))
         .addReg(AArch64::X0, RegState::Define)
-        .addMBB(TargetMBB);
+        .addMBB(TargetMBB, AArch64II::MO_PAGE);
     BuildMI(MBB, FirstEpilogSEH, DL, TII->get(AArch64::ADDXri))
         .addReg(AArch64::X0, RegState::Define)
         .addReg(AArch64::X0)
-        .addMBB(TargetMBB)
+        .addMBB(TargetMBB, AArch64II::MO_PAGEOFF | AArch64II::MO_NC)
         .addImm(0);
     TargetMBB->setMachineBlockAddressTaken();
     return true;
diff --git a/llvm/lib/Target/AArch64/AArch64MCInstLower.cpp b/llvm/lib/Target/AArch64/AArch64MCInstLower.cpp
index d3a38624488e8..2ac934540e3f5 100644
--- a/llvm/lib/Target/AArch64/AArch64MCInstLower.cpp
+++ b/llvm/lib/Target/AArch64/AArch64MCInstLower.cpp
@@ -173,7 +173,7 @@ MCOperand AArch64MCInstLower::lowerSymbolOperandMachO(const MachineOperand &MO,
   }
   // TODO: Migrate to MCSpecifierExpr::create like ELF.
   const MCExpr *Expr = MCSymbolRefExpr::create(Sym, Spec, Ctx);
-  if (!MO.isJTI() && MO.getOffset())
+  if (!MO.isJTI() && !MO.isMBB() && MO.getOffset())
     Expr = MCBinaryExpr::createAdd(
         Expr, MCConstantExpr::create(MO.getOffset(), Ctx), Ctx);
   return MCOperand::createExpr(Expr);
@@ -259,7 +259,7 @@ MCOperand AArch64MCInstLower::lowerSymbolOperandELF(const MachineOperand &MO,
     RefFlags |= AArch64::S_NC;
 
   const MCExpr *Expr = MCSymbolRefExpr::create(Sym, Ctx);
-  if (!MO.isJTI() && MO.getOffset())
+  if (!MO.isJTI() && !MO.isMBB() && MO.getOffset())
     Expr = MCBinaryExpr::createAdd(
         Expr, MCConstantExpr::create(MO.getOffset(), Ctx), Ctx);
 
@@ -310,7 +310,7 @@ MCOperand AArch64MCInstLower::lowerSymbolOperandCOFF(const MachineOperand &MO,
   }
 
   const MCExpr *Expr = MCSymbolRefExpr::create(Sym, Ctx);
-  if (!MO.isJTI() && MO.getOffset())
+  if (!MO.isJTI() && !MO.isMBB() && MO.getOffset())
     Expr = MCBinaryExpr::createAdd(
         Expr, MCConstantExpr::create(MO.getOffset(), Ctx), Ctx);
 
@@ -347,8 +347,15 @@ bool AArch64MCInstLower::lowerOperand(const MachineOperand &MO,
     MCOp = MCOperand::createImm(MO.getImm());
     break;
   case MachineOperand::MO_MachineBasicBlock:
-    MCOp = MCOperand::createExpr(
-        MCSymbolRefExpr::create(MO.getMBB()->getSymbol(), Ctx));
+    // A block operand can carry a fragment flag -- the ADRP/ADD pair a Windows
+    // catch funclet uses to materialise its catchret continuation block does.
+    // Those need their relocation specifier; a plain branch target has no
+    // flags and keeps the bare symbol reference.
+    if (MO.getTargetFlags() & AArch64II::MO_FRAGMENT)
+      MCOp = LowerSymbolOperand(MO, MO.getMBB()->getSymbol());
+    else
+      MCOp = MCOperand::createExpr(
+          MCSymbolRefExpr::create(MO.getMBB()->getSymbol(), Ctx));
     break;
   case MachineOperand::MO_GlobalAddress:
     MCOp = LowerSymbolOperand(MO, GetGlobalAddressSymbol(MO));
diff --git a/llvm/test/CodeGen/AArch64/pr58516.ll b/llvm/test/CodeGen/AArch64/pr58516.ll
index 5554826a6ac76..66f56cf5485d6 100644
--- a/llvm/test/CodeGen/AArch64/pr58516.ll
+++ b/llvm/test/CodeGen/AArch64/pr58516.ll
@@ -76,7 +76,7 @@ define void @osfx(ptr %this) comdat personality ptr @__CxxFrameHandler3 {
 ; CHECK-NEXT:    .seh_save_fplr 24
 ; CHECK-NEXT:    .seh_endprologue
 ; CHECK-NEXT:    adrp x0, .LBB0_2
-; CHECK-NEXT:    add x0, x0, .LBB0_2
+; CHECK-NEXT:    add x0, x0, :lo12:.LBB0_2
 ; CHECK-NEXT:    .seh_startepilogue
 ; CHECK-NEXT:    ldp x29, x30, [sp, #24] // 16-byte Folded Reload
 ; CHECK-NEXT:    .seh_save_fplr 24
diff --git a/llvm/test/CodeGen/AArch64/wineh-bti-funclet.ll b/llvm/test/CodeGen/AArch64/wineh-bti-funclet.ll
index 4f4f984b8974b..9bb6504c6985f 100644
--- a/llvm/test/CodeGen/AArch64/wineh-bti-funclet.ll
+++ b/llvm/test/CodeGen/AArch64/wineh-bti-funclet.ll
@@ -54,7 +54,7 @@ define dso_local void @wineh_funclet() #0 personality ptr @__CxxFrameHandler3 {
 ; CHECK-NEXT:    .seh_endprologue
 ; CHECK-NEXT:    bl may_throw
 ; CHECK-NEXT:    adrp x0, .LBB0_1
-; CHECK-NEXT:    add x0, x0, .LBB0_1
+; CHECK-NEXT:    add x0, x0, :lo12:.LBB0_1
 ; CHECK-NEXT:    .seh_startepilogue
 ; CHECK-NEXT:    ldp x29, x30, [sp], #16 // 16-byte Folded Reload
 ; CHECK-NEXT:    .seh_save_fplr_x 16
diff --git a/llvm/test/CodeGen/AArch64/wineh-catchret-label-generation.ll b/llvm/test/CodeGen/AArch64/wineh-catchret-label-generation.ll
index 3f7df585c52b4..9bc7f0704a275 100644
--- a/llvm/test/CodeGen/AArch64/wineh-catchret-label-generation.ll
+++ b/llvm/test/CodeGen/AArch64/wineh-catchret-label-generation.ll
@@ -51,7 +51,7 @@ define fastcc ptr @test_function(i1 %0, ptr %_Fmtfl.i.i, i1 %1) personality ptr
 ; CHECK-NEXT:    .seh_save_fplr_x 16
 ; CHECK-NEXT:    .seh_endprologue
 ; CHECK-NEXT:    adrp x0, .LBB0_2
-; CHECK-NEXT:    add x0, x0, .LBB0_2
+; CHECK-NEXT:    add x0, x0, :lo12:.LBB0_2
 ; CHECK-NEXT:    .seh_startepilogue
 ; CHECK-NEXT:    ldp x29, x30, [sp], #16 // 16-byte Folded Reload
 ; CHECK-NEXT:    .seh_save_fplr_x 16
diff --git a/llvm/test/CodeGen/AArch64/wineh-catchret-lo12.ll b/llvm/test/CodeGen/AArch64/wineh-catchret-lo12.ll
new file mode 100644
index 0000000000000..7039b1b666004
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/wineh-catchret-lo12.ll
@@ -0,0 +1,25 @@
+; RUN: llc -mtriple=aarch64-pc-windows-msvc < %s | FileCheck %s
+; The output has to assemble as well as compile:
+; RUN: llc -mtriple=aarch64-pc-windows-msvc < %s | llvm-mc -triple=aarch64-pc-windows-msvc -filetype=obj -o /dev/null
+
+declare void @g()
+declare i32 @__CxxFrameHandler3(...)
+
+define void @f() personality ptr @__CxxFrameHandler3 {
+entry:
+  invoke void @g()
+          to label %cont unwind label %catch.dispatch
+
+catch.dispatch:
+  %cs = catchswitch within none [label %catch] unwind to caller
+
+catch:
+  %cp = catchpad within %cs [ptr null, i32 64, ptr null]
+  catchret from %cp to label %cont
+
+cont:
+  ret void
+}
+
+; CHECK:      adrp x0, .LBB0_1
+; CHECK-NEXT: add x0, x0, :lo12:.LBB0_1
diff --git a/llvm/test/CodeGen/AArch64/wineh-try-catch-nobase.ll b/llvm/test/CodeGen/AArch64/wineh-try-catch-nobase.ll
index 361dc8c61e161..9a3c4f18e220d 100644
--- a/llvm/test/CodeGen/AArch64/wineh-try-catch-nobase.ll
+++ b/llvm/test/CodeGen/AArch64/wineh-try-catch-nobase.ll
@@ -13,7 +13,7 @@
 ; CHECK-NEXT:        mov     x1, xzr
 ; CHECK-NEXT:        bl      "?bb@@YAXPEAHH at Z"
 ; CHECK-NEXT:        adrp    x0, .LBB0_1
-; CHECK-NEXT:        add     x0, x0, .LBB0_1
+; CHECK-NEXT:        add     x0, x0, :lo12:.LBB0_1
 ; CHECK-NEXT:        .seh_startepilogue
 ; CHECK-NEXT:        ldp     x29, x30, [sp], #16     // 16-byte Folded Reload
 ; CHECK-NEXT:        .seh_save_fplr_x 16
diff --git a/llvm/test/CodeGen/AArch64/wineh-try-catch-realign.ll b/llvm/test/CodeGen/AArch64/wineh-try-catch-realign.ll
index 484e1879549c4..c70049fae7599 100644
--- a/llvm/test/CodeGen/AArch64/wineh-try-catch-realign.ll
+++ b/llvm/test/CodeGen/AArch64/wineh-try-catch-realign.ll
@@ -20,7 +20,7 @@
 ; CHECK-NEXT: mov     w1, wzr
 ; CHECK-NEXT: bl      "?bb@@YAXPEAHH at Z"
 ; CHECK-NEXT: adrp    x0, .LBB0_1
-; CHECK-NEXT: add     x0, x0, .LBB0_1
+; CHECK-NEXT: add     x0, x0, :lo12:.LBB0_1
 ; CHECK-NEXT: .seh_startepilogue
 ; CHECK-NEXT: ldp     x29, x30, [sp, #16]
 ; CHECK-NEXT: .seh_save_fplr 16
diff --git a/llvm/test/CodeGen/AArch64/wineh-try-catch-vla.ll b/llvm/test/CodeGen/AArch64/wineh-try-catch-vla.ll
index 46e5f2403f2ae..44f1c44fc19f8 100644
--- a/llvm/test/CodeGen/AArch64/wineh-try-catch-vla.ll
+++ b/llvm/test/CodeGen/AArch64/wineh-try-catch-vla.ll
@@ -15,7 +15,7 @@
 ; CHECK-NEXT: mov     x1, x0
 ; CHECK-NEXT: bl      "?bb@@YAXPEAHH at Z"
 ; CHECK-NEXT: adrp    x0, .LBB0_1
-; CHECK-NEXT: add     x0, x0, .LBB0_1
+; CHECK-NEXT: add     x0, x0, :lo12:.LBB0_1
 ; CHECK-NEXT: .seh_startepilogue
 ; CHECK-NEXT: ldp     x29, x30, [sp], #16
 ; CHECK-NEXT: .seh_save_fplr_x 16
diff --git a/llvm/test/CodeGen/AArch64/wineh-try-catch.ll b/llvm/test/CodeGen/AArch64/wineh-try-catch.ll
index 6bb22fca24591..874c0dc28c3b7 100644
--- a/llvm/test/CodeGen/AArch64/wineh-try-catch.ll
+++ b/llvm/test/CodeGen/AArch64/wineh-try-catch.ll
@@ -67,7 +67,7 @@
 
 ; Check that the funclet branches back to the catchret destination
 ; CHECK:       adrp    x0, .LBB0_2
-; CHECK-NEXT:  add     x0, x0, [[CATCHRETDEST]]
+; CHECK-NEXT:  add     x0, x0, :lo12:[[CATCHRETDEST]]
 
 
 ; Now check that the offset of the unwind help object from the stack pointer on



More information about the llvm-commits mailing list