[llvm] [BOLT] Add x86-64 large code model support (.ltext sections) (PR #190637)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Apr 6 10:43:54 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-bolt
Author: Farid Zakaria (fzakaria)
<details>
<summary>Changes</summary>
## Motivation
I am investigating large code-model and we run BOLT on our binaries. I've noticed that support may be incorrect for large-code model.
## Summary
BOLT previously had no awareness of .ltext sections (SHF_X86_64_LARGE), which are produced by -mcmodel=large compilation. Functions from .ltext were silently emitted into .text, dropping the SHF_X86_64_LARGE flag. This is incorrect for binaries that rely on large code model semantics. Without this patch, compute() would be emitted to .text (no large flag), which is incorrect for binaries needing large code model addressing.
## Changes
- Detect .ltext sections and set HasLargeCodeModel flag
- Emit .ltext-origin functions back to .ltext with SHF_X86_64_LARGE
- Rename .ltext to .bolt.org.ltext (mirroring .text handling)
- Update LSDA encoding to use 8-byte pointers for large code model
- Propagate SHF_X86_64_LARGE through section creation pipeline
- Add .ltext to code section sort order (placed before .text)
## Manual validation:
$ cat test.c __attribute__((section(".ltext"))) int compute(int x) { return x * x + 1; } int main(void) { printf("result = %d\n", compute(6)); return 0; }
$ clang -O2 -mcmodel=large -c test.c && clang -O2 -o test test.o -Wl,-q $ llvm-bolt test -o test.bolt --reorder-blocks=none BOLT-INFO: large code model detected (.ltext section found)
$ llvm-readelf -S test.bolt | grep -E 'ltext|\.text '
.bolt.org.ltext PROGBITS ... AXl # original preserved
.ltext PROGBITS ... AXl # new output with SHF_X86_64_LARGE
.text PROGBITS ... AX # regular text, no large flag
$ ./test.bolt
result = 37 # runs correctly post-BOLT
$ llvm-objdump --syms test.bolt | grep compute
0x400380 g F .ltext compute # function stays in .ltext
---
Full diff: https://github.com/llvm/llvm-project/pull/190637.diff
8 Files Affected:
- (modified) bolt/include/bolt/Core/BinaryContext.h (+21-4)
- (modified) bolt/lib/Core/BinaryContext.cpp (+3-1)
- (modified) bolt/lib/Passes/BinaryPasses.cpp (+13-3)
- (modified) bolt/lib/Rewrite/ExecutableFileMemoryManager.cpp (+5-2)
- (modified) bolt/lib/Rewrite/RewriteInstance.cpp (+50-4)
- (added) bolt/test/X86/ltext-debug-sections.s (+46)
- (added) bolt/test/X86/ltext-multiple-functions.s (+58)
- (modified) bolt/unittests/Core/BinaryContext.cpp (+12)
``````````diff
diff --git a/bolt/include/bolt/Core/BinaryContext.h b/bolt/include/bolt/Core/BinaryContext.h
index f8a0be0418433..fbc26bfc1a438 100644
--- a/bolt/include/bolt/Core/BinaryContext.h
+++ b/bolt/include/bolt/Core/BinaryContext.h
@@ -726,6 +726,9 @@ class BinaryContext {
/// FunctionFragment::getFragmentNum() == FragmentNum::warm()
bool HasWarmSection{false};
+ /// Indicates if the binary has large code model sections (.ltext).
+ bool HasLargeCodeModel{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.
@@ -1069,13 +1072,16 @@ class BinaryContext {
/// Return code section with a given name.
MCSection *getCodeSection(StringRef SectionName) const {
- if (isELF())
- return Ctx->getELFSection(SectionName, ELF::SHT_PROGBITS,
- ELF::SHF_EXECINSTR | ELF::SHF_ALLOC);
- else
+ if (isELF()) {
+ unsigned Flags = ELF::SHF_EXECINSTR | ELF::SHF_ALLOC;
+ if (isLargeCodeSection(SectionName))
+ Flags |= ELF::SHF_X86_64_LARGE;
+ return Ctx->getELFSection(SectionName, ELF::SHT_PROGBITS, Flags);
+ } else {
return Ctx->getMachOSection("__TEXT", SectionName,
MachO::S_ATTR_PURE_INSTRUCTIONS,
SectionKind::getText());
+ }
}
/// Return data section with a given name.
@@ -1100,6 +1106,17 @@ class BinaryContext {
return ".text.injected.cold";
}
+ /// \name Large code model section names
+ /// @{
+ const char *getLargeMainCodeSectionName() const { return ".ltext"; }
+ const char *getLargeColdCodeSectionName() const { return ".ltext.cold"; }
+ /// @}
+
+ /// Return true if \p SectionName is a large code model section.
+ static bool isLargeCodeSection(StringRef SectionName) {
+ return SectionName == ".ltext" || SectionName.starts_with(".ltext.");
+ }
+
ErrorOr<BinarySection &> getGdbIndexSection() const {
return getUniqueSectionByName(".gdb_index");
}
diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
index 5b3baf1669624..68322c08f3cba 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -256,7 +256,9 @@ 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.
+ // Use large code model encoding for AArch64 (always) and X86 when the
+ // binary has large code model sections (.ltext). The LSDAEncoding will be
+ // updated after sections are read if HasLargeCodeModel is set.
bool Large = false;
if (TheTriple.getArch() == llvm::Triple::aarch64)
Large = true;
diff --git a/bolt/lib/Passes/BinaryPasses.cpp b/bolt/lib/Passes/BinaryPasses.cpp
index 480d0cef58f43..d178e9fe004c1 100644
--- a/bolt/lib/Passes/BinaryPasses.cpp
+++ b/bolt/lib/Passes/BinaryPasses.cpp
@@ -1326,13 +1326,23 @@ Error AssignSections::runOnFunctions(BinaryContext &BC) {
continue;
}
+ // Assign functions from .ltext to .ltext output section to preserve
+ // SHF_X86_64_LARGE flag for large code model support.
+ const bool IsLargeCode =
+ Function.getOriginSectionName() &&
+ BinaryContext::isLargeCodeSection(*Function.getOriginSectionName());
+ const char *MainSection = IsLargeCode ? BC.getLargeMainCodeSectionName()
+ : BC.getMainCodeSectionName();
+ const char *ColdSection = IsLargeCode ? BC.getLargeColdCodeSectionName()
+ : BC.getColdCodeSectionName();
+
if (!UseColdSection || Function.hasValidIndex())
- Function.setCodeSectionName(BC.getMainCodeSectionName());
+ Function.setCodeSectionName(MainSection);
else
- Function.setCodeSectionName(BC.getColdCodeSectionName());
+ Function.setCodeSectionName(ColdSection);
if (Function.isSplit())
- Function.setColdCodeSectionName(BC.getColdCodeSectionName());
+ Function.setColdCodeSectionName(ColdSection);
}
return Error::success();
}
diff --git a/bolt/lib/Rewrite/ExecutableFileMemoryManager.cpp b/bolt/lib/Rewrite/ExecutableFileMemoryManager.cpp
index 041d0d8c2b274..a8e44c3696361 100644
--- a/bolt/lib/Rewrite/ExecutableFileMemoryManager.cpp
+++ b/bolt/lib/Rewrite/ExecutableFileMemoryManager.cpp
@@ -162,10 +162,13 @@ void ExecutableFileMemoryManager::updateSection(
// Register the new section under a unique name to avoid name collision with
// sections in the input file.
+ unsigned Flags = BinarySection::getFlags(IsReadOnly, IsCode, true);
+ // Preserve SHF_X86_64_LARGE for large code model sections.
+ if (BinaryContext::isLargeCodeSection(SectionName))
+ Flags |= ELF::SHF_X86_64_LARGE;
BinarySection &NewSection = BC.registerOrUpdateSection(
UsePrefix ? NewSecPrefix + SectionName : SectionName, ELF::SHT_PROGBITS,
- BinarySection::getFlags(IsReadOnly, IsCode, true), Contents, Size,
- Alignment);
+ Flags, Contents, Size, Alignment);
if (UsePrefix)
NewSection.setOutputName(SectionName);
Section = &NewSection;
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index b3208ac024ae6..ebba654ba068b 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -2237,6 +2237,20 @@ Error RewriteInstance::readSpecialSections() {
"Use -update-debug-sections to keep it.\n";
}
+ // Detect large code model sections (.ltext).
+ if (BC->getUniqueSectionByName(BC->getLargeMainCodeSectionName())) {
+ BC->HasLargeCodeModel = true;
+ BC->outs() << "BOLT-INFO: large code model detected (.ltext section found)\n";
+
+ // Update LSDA encoding for large code model — use 8-byte pointers so that
+ // exception handling works for functions at addresses above 2GB.
+ if (BC->isX86()) {
+ BC->LSDAEncoding = BC->HasFixedLoadAddress
+ ? dwarf::DW_EH_PE_absptr
+ : (dwarf::DW_EH_PE_pcrel | dwarf::DW_EH_PE_sdata8);
+ }
+ }
+
HasTextRelocations = (bool)BC->getUniqueSectionByName(
".rela" + std::string(BC->getMainCodeSectionName()));
HasSymbolTable = (bool)BC->getUniqueSectionByName(".symtab");
@@ -3967,6 +3981,16 @@ void RewriteInstance::emitAndLink() {
BC->renameSection(*TextSection,
getOrgSecPrefix() + BC->getMainCodeSectionName());
+ // Rename .ltext to .bolt.org.ltext for large code model support.
+ if (BC->HasRelocations && BC->HasLargeCodeModel) {
+ ErrorOr<BinarySection &> LTextSection =
+ BC->getUniqueSectionByName(BC->getLargeMainCodeSectionName());
+ if (LTextSection)
+ BC->renameSection(*LTextSection,
+ getOrgSecPrefix() +
+ BC->getLargeMainCodeSectionName());
+ }
+
//////////////////////////////////////////////////////////////////////////////
// Assign addresses to new sections.
//////////////////////////////////////////////////////////////////////////////
@@ -4109,6 +4133,17 @@ std::vector<BinarySection *> RewriteInstance::getCodeSections() {
: (A->getName() < B->getName());
}
+ // Same ordering for .ltext.cold sections.
+ if (A->getName().starts_with(BC->getLargeColdCodeSectionName()) &&
+ B->getName().starts_with(BC->getLargeColdCodeSectionName())) {
+ if (A->getName().size() != B->getName().size())
+ return (opts::HotFunctionsAtEnd)
+ ? (A->getName().size() > B->getName().size())
+ : (A->getName().size() < B->getName().size());
+ return (opts::HotFunctionsAtEnd) ? (A->getName() > B->getName())
+ : (A->getName() < B->getName());
+ }
+
// Place hot text movers before anything else.
if (opts::HotText) {
if (A->getName() == BC->getHotTextMoverSectionName())
@@ -4117,18 +4152,29 @@ std::vector<BinarySection *> RewriteInstance::getCodeSections() {
return false;
}
+ // Place .ltext before .text (matching typical linker layout where .ltext
+ // precedes .text in the output binary).
+ const bool AIsLarge = BinaryContext::isLargeCodeSection(A->getName());
+ const bool BIsLarge = BinaryContext::isLargeCodeSection(B->getName());
+ if (AIsLarge != BIsLarge)
+ return AIsLarge;
+
// Depending on opts::HotFunctionsAtEnd, place main and warm sections in
// order.
if (opts::HotFunctionsAtEnd) {
- if (B->getName() == BC->getMainCodeSectionName())
+ if (B->getName() == BC->getMainCodeSectionName() ||
+ B->getName() == BC->getLargeMainCodeSectionName())
return true;
- if (A->getName() == BC->getMainCodeSectionName())
+ if (A->getName() == BC->getMainCodeSectionName() ||
+ A->getName() == BC->getLargeMainCodeSectionName())
return false;
return (B->getName() == BC->getWarmCodeSectionName());
} else {
- if (A->getName() == BC->getMainCodeSectionName())
+ if (A->getName() == BC->getMainCodeSectionName() ||
+ A->getName() == BC->getLargeMainCodeSectionName())
return true;
- if (B->getName() == BC->getMainCodeSectionName())
+ if (B->getName() == BC->getMainCodeSectionName() ||
+ B->getName() == BC->getLargeMainCodeSectionName())
return false;
return (A->getName() == BC->getWarmCodeSectionName());
}
diff --git a/bolt/test/X86/ltext-debug-sections.s b/bolt/test/X86/ltext-debug-sections.s
new file mode 100644
index 0000000000000..f81508e4f4467
--- /dev/null
+++ b/bolt/test/X86/ltext-debug-sections.s
@@ -0,0 +1,46 @@
+## Test that BOLT correctly handles binaries with .ltext (SHF_X86_64_LARGE)
+## sections. Functions from .ltext should be emitted back to .ltext with
+## the SHF_X86_64_LARGE flag preserved.
+
+# REQUIRES: system-linux
+
+# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe -o %t.bolt --update-debug-sections --reorder-blocks=none \
+# RUN: 2>&1 | FileCheck %s --check-prefix=CHECK-BOLT
+# RUN: llvm-readelf -S %t.bolt | FileCheck %s --check-prefix=CHECK-SECTIONS
+# RUN: llvm-objdump --syms %t.bolt | FileCheck %s --check-prefix=CHECK-SYMS
+
+## Verify BOLT detects large code model.
+# CHECK-BOLT: large code model detected (.ltext section found)
+
+## Verify original sections are renamed and new sections preserve flags.
+# CHECK-SECTIONS: .bolt.org.ltext {{.*}} AXl
+# CHECK-SECTIONS: .bolt.org.text {{.*}} AX
+# CHECK-SECTIONS: .ltext {{.*}} AXl
+# CHECK-SECTIONS: .text {{.*}} AX
+
+## Verify large_func lands in .ltext and _start lands in .text.
+# CHECK-SYMS-DAG: .ltext{{.*}} large_func
+# CHECK-SYMS-DAG: .text{{.*}} _start
+
+ .text
+ .globl _start
+ .type _start, @function
+_start:
+ call large_func
+ xorl %eax, %eax
+ retq
+ .size _start, .-_start
+
+## Large code model function in .ltext with SHF_X86_64_LARGE flag.
+ .section .ltext,"axl", at progbits
+ .globl large_func
+ .type large_func, @function
+large_func:
+ pushq %rbp
+ movq %rsp, %rbp
+ movl $42, %eax
+ popq %rbp
+ retq
+ .size large_func, .-large_func
diff --git a/bolt/test/X86/ltext-multiple-functions.s b/bolt/test/X86/ltext-multiple-functions.s
new file mode 100644
index 0000000000000..ac4f44c03de7b
--- /dev/null
+++ b/bolt/test/X86/ltext-multiple-functions.s
@@ -0,0 +1,58 @@
+## Test that BOLT handles multiple functions split across .text and .ltext
+## sections, assigning each to the correct output section.
+
+# REQUIRES: system-linux
+
+# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux %s -o %t.o
+# RUN: ld.lld %t.o -o %t.exe --emit-relocs -e _start
+# RUN: llvm-bolt %t.exe -o %t.bolt --reorder-blocks=none 2>&1 \
+# RUN: | FileCheck %s --check-prefix=CHECK-BOLT
+# RUN: llvm-readelf -S %t.bolt | FileCheck %s --check-prefix=CHECK-SEC
+# RUN: llvm-objdump --syms %t.bolt | FileCheck %s --check-prefix=CHECK-SYMS
+
+# CHECK-BOLT: large code model detected
+
+## Both .ltext (with large flag) and .text (without) must exist in output.
+# CHECK-SEC: .ltext {{.*}} AXl
+# CHECK-SEC: .text {{.*}} AX
+
+## Functions from .ltext stay in .ltext; functions from .text stay in .text.
+# CHECK-SYMS-DAG: .ltext{{.*}} large_foo
+# CHECK-SYMS-DAG: .ltext{{.*}} large_bar
+# CHECK-SYMS-DAG: .text{{.*}} _start
+# CHECK-SYMS-DAG: .text{{.*}} small_helper
+
+ .text
+ .globl _start
+ .type _start, @function
+_start:
+ call large_foo
+ call small_helper
+ xorl %eax, %eax
+ retq
+ .size _start, .-_start
+
+ .globl small_helper
+ .type small_helper, @function
+small_helper:
+ movl $1, %eax
+ retq
+ .size small_helper, .-small_helper
+
+ .section .ltext,"axl", at progbits
+ .globl large_foo
+ .type large_foo, @function
+large_foo:
+ pushq %rbp
+ movq %rsp, %rbp
+ call large_bar
+ popq %rbp
+ retq
+ .size large_foo, .-large_foo
+
+ .globl large_bar
+ .type large_bar, @function
+large_bar:
+ movl $42, %eax
+ retq
+ .size large_bar, .-large_bar
diff --git a/bolt/unittests/Core/BinaryContext.cpp b/bolt/unittests/Core/BinaryContext.cpp
index d7374b323c916..8b6b587ae82db 100644
--- a/bolt/unittests/Core/BinaryContext.cpp
+++ b/bolt/unittests/Core/BinaryContext.cpp
@@ -238,6 +238,18 @@ TEST_P(BinaryContextTester, BaseAddress2) {
ASSERT_FALSE(BaseAddress.has_value());
}
+TEST(BinaryContextTest, IsLargeCodeSection) {
+ EXPECT_TRUE(BinaryContext::isLargeCodeSection(".ltext"));
+ EXPECT_TRUE(BinaryContext::isLargeCodeSection(".ltext.hot"));
+ EXPECT_TRUE(BinaryContext::isLargeCodeSection(".ltext.cold"));
+ EXPECT_TRUE(BinaryContext::isLargeCodeSection(".ltext.unlikely"));
+ EXPECT_FALSE(BinaryContext::isLargeCodeSection(".text"));
+ EXPECT_FALSE(BinaryContext::isLargeCodeSection(".text.cold"));
+ EXPECT_FALSE(BinaryContext::isLargeCodeSection(".ltextfoo"));
+ EXPECT_FALSE(BinaryContext::isLargeCodeSection("ltext"));
+ EXPECT_FALSE(BinaryContext::isLargeCodeSection(""));
+}
+
TEST_P(BinaryContextTester, BaseAddressSegmentsSmallerThanAlignment) {
// Check that the correct segment is used to compute the base address
// when multiple segments are close together in the ELF file (closer
``````````
</details>
https://github.com/llvm/llvm-project/pull/190637
More information about the llvm-commits
mailing list