[llvm] [BOLT] Update LSDA encoding for x86-64 large code model (PR #190685)

Farid Zakaria via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 9 21:20:45 PDT 2026


https://github.com/fzakaria updated https://github.com/llvm/llvm-project/pull/190685

>From e1fe2e49038deb7a2fa62e9afa4a20a9eb0402aa Mon Sep 17 00:00:00 2001
From: Farid Zakaria <fmzakari at fb.com>
Date: Thu, 9 Apr 2026 21:19:44 -0700
Subject: [PATCH] [BOLT] Update LSDA encoding for x86-64 large code model

BOLT hardcoded 4-byte LSDA (exception table) encoding for x86-64.
This is insufficient for large code model binaries where functions
in .ltext sections may be placed at addresses above 2GB, exceeding
the range of DW_EH_PE_udata4/DW_EH_PE_sdata4 encodings.

Detect large code model by the presence of .ltext sections, and add
a --large-code-model flag for manual override (e.g. for compilers
that don't emit .ltext). Update LSDAEncoding to use 8-byte pointers:
- Non-PIC: DW_EH_PE_absptr (8-byte absolute)
- PIC: DW_EH_PE_pcrel | DW_EH_PE_sdata8 (8-byte PC-relative)

This is the first step toward full large code model support in BOLT.
Without this change, BOLT-processed binaries with C++ exceptions in
.ltext would produce incorrect exception handling tables.
---
 bolt/include/bolt/Core/BinaryContext.h |   9 ++
 bolt/lib/Core/BinaryContext.cpp        |  30 ++++---
 bolt/lib/Rewrite/RewriteInstance.cpp   |  13 +++
 bolt/lib/Utils/CommandLineOpts.cpp     |   6 ++
 bolt/test/X86/lsda-encoding.s          | 110 +++++++++++++++++++++++++
 5 files changed, 157 insertions(+), 11 deletions(-)
 create mode 100644 bolt/test/X86/lsda-encoding.s

diff --git a/bolt/include/bolt/Core/BinaryContext.h b/bolt/include/bolt/Core/BinaryContext.h
index f8a0be0418433..b5932c09efdaf 100644
--- a/bolt/include/bolt/Core/BinaryContext.h
+++ b/bolt/include/bolt/Core/BinaryContext.h
@@ -726,6 +726,11 @@ class BinaryContext {
   /// FunctionFragment::getFragmentNum() == FragmentNum::warm()
   bool HasWarmSection{false};
 
+  /// Indicates if the binary should assume large code model
+  /// Can be triggered by the presence of .ltext sections if
+  // unspecified.
+  bool UseLargeCodeModel{false};
+
   /// Is the binary always loaded at a fixed address. Shared objects and
   /// position-independent executables (PIEs) are examples of binaries that
   /// will have HasFixedLoadAddress set to false.
@@ -851,6 +856,10 @@ class BinaryContext {
   /// enum Constants, e.g. DW_EH_PE_omit.
   unsigned LSDAEncoding = dwarf::DW_EH_PE_omit;
 
+  /// Update LSDAEncoding for the binary taking into account
+  /// large code model and position-independent executables.
+  void updateLSDAEncoding();
+
   BinaryContext(std::unique_ptr<MCContext> Ctx,
                 std::unique_ptr<DWARFContext> DwCtx,
                 std::unique_ptr<Triple> TheTriple,
diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
index 5b3baf1669624..278a0f2e555df 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -47,6 +47,8 @@ using namespace llvm;
 
 namespace opts {
 
+extern cl::opt<bool> LargeCodeModel;
+
 static cl::opt<bool>
     NoHugePages("no-huge-pages",
                 cl::desc("use regular size pages for code alignment"),
@@ -256,16 +258,6 @@ Expected<std::unique_ptr<BinaryContext>> BinaryContext::createBinaryContext(
   std::unique_ptr<MCObjectFileInfo> MOFI(
       TheTarget->createMCObjectFileInfo(*Ctx, IsPIC));
   Ctx->setObjectFileInfo(MOFI.get());
-  // We do not support X86 Large code model. Change this in the future.
-  bool Large = false;
-  if (TheTriple.getArch() == llvm::Triple::aarch64)
-    Large = true;
-  unsigned LSDAEncoding =
-      Large ? dwarf::DW_EH_PE_absptr : dwarf::DW_EH_PE_udata4;
-  if (IsPIC) {
-    LSDAEncoding = dwarf::DW_EH_PE_pcrel |
-                   (Large ? dwarf::DW_EH_PE_sdata8 : dwarf::DW_EH_PE_sdata4);
-  }
 
   std::unique_ptr<MCDisassembler> DisAsm(
       TheTarget->createMCDisassembler(*STI, *Ctx));
@@ -303,7 +295,13 @@ Expected<std::unique_ptr<BinaryContext>> BinaryContext::createBinaryContext(
       std::move(InstructionPrinter), std::move(MIA), nullptr, std::move(MRI),
       std::move(DisAsm), Logger);
 
-  BC->LSDAEncoding = LSDAEncoding;
+  // Use large code model encoding for AArch64 (always). For X86, this is
+  // updated after detecting .ltext if unset.
+  // Otherwise allow the user to force it via `--large-code-model` flag.
+  if (TheTriple.getArch() == llvm::Triple::aarch64)
+    BC->UseLargeCodeModel = true;
+  else if (opts::LargeCodeModel.getNumOccurrences())
+    BC->UseLargeCodeModel = opts::LargeCodeModel;
 
   BC->MAB = std::unique_ptr<MCAsmBackend>(
       BC->TheTarget->createMCAsmBackend(*BC->STI, *BC->MRI, MCTargetOptions()));
@@ -315,6 +313,8 @@ Expected<std::unique_ptr<BinaryContext>> BinaryContext::createBinaryContext(
   BC->SymbolicDisAsm = std::unique_ptr<MCDisassembler>(
       BC->TheTarget->createMCDisassembler(*BC->STI, *BC->Ctx));
 
+  BC->updateLSDAEncoding();
+
   if (!BC->SymbolicDisAsm)
     return createStringError(
         make_error_code(std::errc::not_supported),
@@ -390,6 +390,14 @@ bool BinaryContext::validateHoles() const {
   return Valid;
 }
 
+void BinaryContext::updateLSDAEncoding() {
+  LSDAEncoding = HasFixedLoadAddress
+                     ? dwarf::DW_EH_PE_absptr
+                     : (dwarf::DW_EH_PE_pcrel |
+                        (this->UseLargeCodeModel ? dwarf::DW_EH_PE_sdata8
+                                                 : dwarf::DW_EH_PE_sdata4));
+}
+
 void BinaryContext::updateObjectNesting(BinaryDataMapType::iterator GAI) {
   const uint64_t Address = GAI->second->getAddress();
   const uint64_t Size = GAI->second->getSize();
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index b3208ac024ae6..42363fccc849f 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -82,6 +82,7 @@ extern cl::opt<bool> Hugify;
 extern cl::opt<bool> Instrument;
 extern cl::opt<uint32_t> InstrumentationSleepTime;
 extern cl::opt<bool> KeepNops;
+extern cl::opt<bool> LargeCodeModel;
 extern cl::opt<bool> Lite;
 extern cl::list<std::string> PrintOnly;
 extern cl::opt<std::string> PrintOnlyFile;
@@ -2237,6 +2238,18 @@ Error RewriteInstance::readSpecialSections() {
                   "Use -update-debug-sections to keep it.\n";
   }
 
+  // Detect large code model by the presence of .ltext sections.
+  if (opts::LargeCodeModel.getNumOccurrences() == 0 &&
+      BC->getUniqueSectionByName(".ltext")) {
+    BC->UseLargeCodeModel = true;
+  }
+
+  // Placed in a separate block so that we can check for it in lit tests
+  if (BC->TheTriple->getArch() == llvm::Triple::x86_64 && BC->UseLargeCodeModel) {
+    BC->outs() << "BOLT-INFO: large code model detected\n";
+    BC->updateLSDAEncoding();
+  }
+
   HasTextRelocations = (bool)BC->getUniqueSectionByName(
       ".rela" + std::string(BC->getMainCodeSectionName()));
   HasSymbolTable = (bool)BC->getUniqueSectionByName(".symtab");
diff --git a/bolt/lib/Utils/CommandLineOpts.cpp b/bolt/lib/Utils/CommandLineOpts.cpp
index b7eb209af8aca..cc00d5522a3f0 100644
--- a/bolt/lib/Utils/CommandLineOpts.cpp
+++ b/bolt/lib/Utils/CommandLineOpts.cpp
@@ -221,6 +221,12 @@ cl::opt<bool>
                cl::desc("instrument code to generate accurate profile data"),
                cl::cat(BoltOptCategory));
 
+cl::opt<bool> LargeCodeModel(
+    "large-code-model",
+    cl::desc("use large code model for exception handling encodings. "
+             "Auto-detected by the presence of .ltext sections otherwise."),
+    cl::cat(BoltCategory));
+
 cl::opt<bool> Lite("lite", cl::desc("skip processing of cold functions"),
                    cl::cat(BoltCategory));
 
diff --git a/bolt/test/X86/lsda-encoding.s b/bolt/test/X86/lsda-encoding.s
new file mode 100644
index 0000000000000..9cdb2cfa8d401
--- /dev/null
+++ b/bolt/test/X86/lsda-encoding.s
@@ -0,0 +1,110 @@
+## Test that BOLT handles large code model LSDA encoding correctly:
+## 1. Auto-detection via .ltext sections
+## 2. Disabling auto-detection with --large-code-model=0
+## 3. Forcing large code model with --large-code-model flag
+
+# REQUIRES: system-linux
+
+## Build two variants: one with .ltext section, one without.
+# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux \
+# RUN:   --defsym LTEXT=1 %s -o %t.ltext.o
+# RUN: ld.lld --no-pie %t.ltext.o -o %t.ltext.exe -q -e _start
+# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux \
+# RUN:   --defsym LTEXT=0 %s -o %t.text.o
+# RUN: ld.lld --no-pie %t.text.o -o %t.text.exe -q -e _start
+
+## Test 1: Auto-detection via .ltext section.
+# RUN: llvm-bolt %t.ltext.exe -o %t.ltext.bolt --reorder-blocks=none 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=CHECK-BOLT
+# CHECK-BOLT: large code model detected
+
+# RUN: llvm-dwarfdump --eh-frame %t.ltext.bolt \
+# RUN:   | FileCheck %s --check-prefix=CHECK-EH
+
+## Test 2: Disable large code model with --large-code-model=0, overriding
+## auto-detection even though .ltext is present.
+# RUN: llvm-bolt %t.ltext.exe -o %t.ltext.bolt2 --reorder-blocks=none \
+# RUN:   --large-code-model=0 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=CHECK-DISABLED
+# CHECK-DISABLED-NOT: large code model detected
+
+# RUN: llvm-dwarfdump --eh-frame %t.ltext.bolt2 \
+# RUN:   | FileCheck %s --check-prefix=CHECK-EH
+
+## Test 3: --large-code-model flag forces large code model on a binary
+## without .ltext sections.
+# RUN: llvm-bolt %t.text.exe -o %t.text.bolt --reorder-blocks=none \
+# RUN:   --large-code-model 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=CHECK-BOLT
+
+# RUN: llvm-dwarfdump --eh-frame %t.text.bolt \
+# RUN:   | FileCheck %s --check-prefix=CHECK-EH
+
+## Verify the BOLT-emitted CIE uses 8-byte LSDA encoding (DW_EH_PE_absptr
+## = 0x00) instead of the default 4-byte encoding (DW_EH_PE_sdata4 = 0x1B).
+## In the "zLR" augmentation data: [L-enc] [R-enc].
+# CHECK-EH:      Augmentation:          "zLR"
+# CHECK-EH:      Augmentation data:     00 1B
+
+  .text
+  .globl foo
+  .type foo, @function
+foo:
+  .cfi_startproc
+  ret
+  .cfi_endproc
+  .size foo, .-foo
+
+  .globl _start
+  .type _start, @function
+_start:
+.Lfunc_begin0:
+  .cfi_startproc
+  .cfi_lsda 27, .Lexception0
+  call foo
+.Ltmp0:
+  call foo
+.Ltmp1:
+  ret
+
+## Landing pads.
+.LLP0:
+  ret
+.LLP1:
+  ret
+
+  .cfi_endproc
+.Lfunc_end0:
+  .size _start, .Lfunc_end0-_start
+
+## Exception table.
+  .section .gcc_except_table,"a", at progbits
+  .p2align 2
+.Lexception0:
+  .byte 255                             # @LPStart Encoding = omit
+  .byte 255                             # @TType Encoding = omit
+  .byte 1                               # Call site Encoding = uleb128
+  .uleb128 .Lcst_end0-.Lcst_begin0
+.Lcst_begin0:
+  .uleb128 .Lfunc_begin0-.Lfunc_begin0  # Call Site 1
+  .uleb128 .Ltmp0-.Lfunc_begin0
+  .uleb128 .LLP0-.Lfunc_begin0          # landing pad
+  .byte 0                               # action: cleanup
+  .uleb128 .Ltmp0-.Lfunc_begin0         # Call Site 2
+  .uleb128 .Ltmp1-.Ltmp0
+  .uleb128 .LLP1-.Lfunc_begin0          # landing pad
+  .byte 0                               # action: cleanup
+.Lcst_end0:
+
+## When LTEXT=1, emit large_func in .ltext to trigger auto-detection.
+.if LTEXT
+  .section .ltext,"axl", at progbits
+  .globl large_func
+  .type large_func, @function
+large_func:
+  .cfi_startproc
+  movl $42, %eax
+  retq
+  .cfi_endproc
+  .size large_func, .-large_func
+.endif



More information about the llvm-commits mailing list