[lld] [lld][MachO] Tail merge strings (PR #161262)
Ellis Hoag via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 30 11:45:37 PDT 2025
https://github.com/ellishg updated https://github.com/llvm/llvm-project/pull/161262
>From 2e997dba5502eec845f0371e0aef083281f77fc4 Mon Sep 17 00:00:00 2001
From: Ellis Hoag <ellishoag at meta.com>
Date: Mon, 29 Sep 2025 10:20:28 -0700
Subject: [PATCH 1/7] [lld][macho][NFC] Factor count zeros into helper function
---
lld/MachO/SyntheticSections.cpp | 57 +++++++++++++++++----------------
1 file changed, 29 insertions(+), 28 deletions(-)
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index 228b84db21c2a..d38b6c9e00157 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -1685,31 +1685,7 @@ void CStringSection::writeTo(uint8_t *buf) const {
}
}
-void CStringSection::finalizeContents() {
- uint64_t offset = 0;
- // TODO: Call buildCStringPriorities() to support cstring ordering when
- // deduplication is off, although this may negatively impact build
- // performance.
- for (CStringInputSection *isec : inputs) {
- for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
- if (!piece.live)
- continue;
- // See comment above DeduplicatedCStringSection for how alignment is
- // handled.
- uint32_t pieceAlign = 1
- << llvm::countr_zero(isec->align | piece.inSecOff);
- offset = alignToPowerOf2(offset, pieceAlign);
- piece.outSecOff = offset;
- isec->isFinal = true;
- StringRef string = isec->getStringRef(i);
- offset += string.size() + 1; // account for null terminator
- }
- }
- size = offset;
-}
-
-// Mergeable cstring literals are found under the __TEXT,__cstring section. In
-// contrast to ELF, which puts strings that need different alignments into
+// In contrast to ELF, which puts strings that need different alignments into
// different sections, clang's Mach-O backend puts them all in one section.
// Strings that need to be aligned have the .p2align directive emitted before
// them, which simply translates into zero padding in the object file. In other
@@ -1744,8 +1720,33 @@ void CStringSection::finalizeContents() {
// requires its operand addresses to be 16-byte aligned). However, there will
// typically also be other cstrings in the same file that aren't used via SIMD
// and don't need this alignment. They will be emitted at some arbitrary address
-// `A`, but ld64 will treat them as being 16-byte aligned with an offset of `16
-// % A`.
+// `A`, but ld64 will treat them as being 16-byte aligned with an offset of
+// `16 % A`.
+static uint8_t getStringPieceAlignment(const CStringInputSection *isec,
+ const StringPiece &piece) {
+ return llvm::countr_zero(isec->align | piece.inSecOff);
+}
+
+void CStringSection::finalizeContents() {
+ uint64_t offset = 0;
+ // TODO: Call buildCStringPriorities() to support cstring ordering when
+ // deduplication is off, although this may negatively impact build
+ // performance.
+ for (CStringInputSection *isec : inputs) {
+ for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
+ if (!piece.live)
+ continue;
+ uint32_t pieceAlign = 1 << getStringPieceAlignment(isec, piece);
+ offset = alignToPowerOf2(offset, pieceAlign);
+ piece.outSecOff = offset;
+ isec->isFinal = true;
+ StringRef string = isec->getStringRef(i);
+ offset += string.size() + 1; // account for null terminator
+ }
+ }
+ size = offset;
+}
+
void DeduplicatedCStringSection::finalizeContents() {
// Find the largest alignment required for each string.
for (const CStringInputSection *isec : inputs) {
@@ -1754,7 +1755,7 @@ void DeduplicatedCStringSection::finalizeContents() {
continue;
auto s = isec->getCachedHashStringRef(i);
assert(isec->align != 0);
- uint8_t trailingZeros = llvm::countr_zero(isec->align | piece.inSecOff);
+ uint8_t trailingZeros = getStringPieceAlignment(isec, piece);
auto it = stringOffsetMap.insert(
std::make_pair(s, StringOffset(trailingZeros)));
if (!it.second && it.first->second.trailingZeros < trailingZeros)
>From ee87e104056cc89a9d5e151edefca335aa823909 Mon Sep 17 00:00:00 2001
From: Ellis Hoag <ellishoag at meta.com>
Date: Mon, 29 Sep 2025 10:47:52 -0700
Subject: [PATCH 2/7] fix format
---
lld/MachO/SyntheticSections.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index d38b6c9e00157..5645d8a05a28f 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -1723,7 +1723,7 @@ void CStringSection::writeTo(uint8_t *buf) const {
// `A`, but ld64 will treat them as being 16-byte aligned with an offset of
// `16 % A`.
static uint8_t getStringPieceAlignment(const CStringInputSection *isec,
- const StringPiece &piece) {
+ const StringPiece &piece) {
return llvm::countr_zero(isec->align | piece.inSecOff);
}
>From d47f1b9a1075e9588e0bc54afb2da33838ba71b1 Mon Sep 17 00:00:00 2001
From: Ellis Hoag <ellishoag at meta.com>
Date: Mon, 29 Sep 2025 11:03:21 -0700
Subject: [PATCH 3/7] [lld][MachO] Use llvm::Align and remove StringOffset type
---
lld/MachO/SyntheticSections.cpp | 64 ++++++++++++++-------------------
lld/MachO/SyntheticSections.h | 12 ++-----
2 files changed, 28 insertions(+), 48 deletions(-)
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index 5645d8a05a28f..38386c107fea0 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -848,8 +848,7 @@ void ObjCSelRefsHelper::initialize() {
void ObjCSelRefsHelper::cleanup() { methnameToSelref.clear(); }
ConcatInputSection *ObjCSelRefsHelper::makeSelRef(StringRef methname) {
- auto methnameOffset =
- in.objcMethnameSection->getStringOffset(methname).outSecOff;
+ auto methnameOffset = in.objcMethnameSection->getStringOffset(methname);
size_t wordSize = target->wordSize;
uint8_t *selrefData = bAlloc().Allocate<uint8_t>(wordSize);
@@ -1722,13 +1721,12 @@ void CStringSection::writeTo(uint8_t *buf) const {
// and don't need this alignment. They will be emitted at some arbitrary address
// `A`, but ld64 will treat them as being 16-byte aligned with an offset of
// `16 % A`.
-static uint8_t getStringPieceAlignment(const CStringInputSection *isec,
- const StringPiece &piece) {
- return llvm::countr_zero(isec->align | piece.inSecOff);
+static Align getStringPieceAlignment(const CStringInputSection *isec,
+ const StringPiece &piece) {
+ return llvm::Align(1ULL << llvm::countr_zero(isec->align | piece.inSecOff));
}
void CStringSection::finalizeContents() {
- uint64_t offset = 0;
// TODO: Call buildCStringPriorities() to support cstring ordering when
// deduplication is off, although this may negatively impact build
// performance.
@@ -1736,30 +1734,27 @@ void CStringSection::finalizeContents() {
for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
if (!piece.live)
continue;
- uint32_t pieceAlign = 1 << getStringPieceAlignment(isec, piece);
- offset = alignToPowerOf2(offset, pieceAlign);
- piece.outSecOff = offset;
- isec->isFinal = true;
+ piece.outSecOff = alignTo(size, getStringPieceAlignment(isec, piece));
StringRef string = isec->getStringRef(i);
- offset += string.size() + 1; // account for null terminator
+ size = piece.outSecOff + string.size() + 1; // account for null terminator
}
+ isec->isFinal = true;
}
- size = offset;
}
void DeduplicatedCStringSection::finalizeContents() {
// Find the largest alignment required for each string.
+ DenseMap<CachedHashStringRef, Align> strToAlignment;
for (const CStringInputSection *isec : inputs) {
for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
if (!piece.live)
continue;
auto s = isec->getCachedHashStringRef(i);
assert(isec->align != 0);
- uint8_t trailingZeros = getStringPieceAlignment(isec, piece);
- auto it = stringOffsetMap.insert(
- std::make_pair(s, StringOffset(trailingZeros)));
- if (!it.second && it.first->second.trailingZeros < trailingZeros)
- it.first->second.trailingZeros = trailingZeros;
+ auto align = getStringPieceAlignment(isec, piece);
+ auto [it, wasInserted] = strToAlignment.try_emplace(s, align);
+ if (!wasInserted && it->second < align)
+ it->second = align;
}
}
@@ -1769,38 +1764,31 @@ void DeduplicatedCStringSection::finalizeContents() {
for (auto &[isec, i] : priorityBuilder.buildCStringPriorities(inputs)) {
auto &piece = isec->pieces[i];
auto s = isec->getCachedHashStringRef(i);
- auto it = stringOffsetMap.find(s);
- assert(it != stringOffsetMap.end());
- lld::macho::DeduplicatedCStringSection::StringOffset &offsetInfo =
- it->second;
- if (offsetInfo.outSecOff == UINT64_MAX) {
- offsetInfo.outSecOff =
- alignToPowerOf2(size, 1ULL << offsetInfo.trailingZeros);
- size = offsetInfo.outSecOff + s.size() + 1; // account for null terminator
+ auto [it, wasInserted] = stringOffsetMap.try_emplace(s, /*placeholder*/ 0);
+ if (wasInserted) {
+ // Avoid computing the offset until we are sure we will need to
+ uint64_t offset = alignTo(size, strToAlignment.at(s));
+ it->second = offset;
+ size = offset + s.size() + 1; // account for null terminator
}
- piece.outSecOff = offsetInfo.outSecOff;
+ // If the string was already in stringOffsetMap, it is a duplicate and we
+ // only need to assign the offset.
+ piece.outSecOff = it->second;
}
for (CStringInputSection *isec : inputs)
isec->isFinal = true;
}
void DeduplicatedCStringSection::writeTo(uint8_t *buf) const {
- for (const auto &p : stringOffsetMap) {
- StringRef data = p.first.val();
- uint64_t off = p.second.outSecOff;
- if (!data.empty())
- memcpy(buf + off, data.data(), data.size());
- }
+ for (const auto &[s, outSecOff] : stringOffsetMap)
+ if (s.size())
+ memcpy(buf + outSecOff, s.data(), s.size());
}
-DeduplicatedCStringSection::StringOffset
-DeduplicatedCStringSection::getStringOffset(StringRef str) const {
+uint64_t DeduplicatedCStringSection::getStringOffset(StringRef str) const {
// StringPiece uses 31 bits to store the hashes, so we replicate that
uint32_t hash = xxh3_64bits(str) & 0x7fffffff;
- auto offset = stringOffsetMap.find(CachedHashStringRef(str, hash));
- assert(offset != stringOffsetMap.end() &&
- "Looked-up strings should always exist in section");
- return offset->second;
+ return stringOffsetMap.at(CachedHashStringRef(str, hash));
}
// This section is actually emitted as __TEXT,__const by ld64, but clang may
diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h
index 1abf3c210a64e..a37dd66107ee7 100644
--- a/lld/MachO/SyntheticSections.h
+++ b/lld/MachO/SyntheticSections.h
@@ -571,18 +571,10 @@ class DeduplicatedCStringSection final : public CStringSection {
uint64_t getSize() const override { return size; }
void finalizeContents() override;
void writeTo(uint8_t *buf) const override;
-
- struct StringOffset {
- uint8_t trailingZeros;
- uint64_t outSecOff = UINT64_MAX;
-
- explicit StringOffset(uint8_t zeros) : trailingZeros(zeros) {}
- };
-
- StringOffset getStringOffset(StringRef str) const;
+ uint64_t getStringOffset(StringRef str) const;
private:
- llvm::DenseMap<llvm::CachedHashStringRef, StringOffset> stringOffsetMap;
+ llvm::DenseMap<llvm::CachedHashStringRef, uint64_t> stringOffsetMap;
size_t size = 0;
};
>From 4e0bde29f824a1dc2cfce4a64ad052783e80db11 Mon Sep 17 00:00:00 2001
From: Ellis Hoag <ellishoag at meta.com>
Date: Mon, 29 Sep 2025 12:01:51 -0700
Subject: [PATCH 4/7] init size to zero
---
lld/MachO/SyntheticSections.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index 38386c107fea0..903ba78a27c75 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -1727,6 +1727,7 @@ static Align getStringPieceAlignment(const CStringInputSection *isec,
}
void CStringSection::finalizeContents() {
+ size = 0;
// TODO: Call buildCStringPriorities() to support cstring ordering when
// deduplication is off, although this may negatively impact build
// performance.
>From 4742cca2ec71b6651f76c12b9d4aea2706cec02c Mon Sep 17 00:00:00 2001
From: Ellis Hoag <ellishoag at meta.com>
Date: Mon, 29 Sep 2025 12:21:49 -0700
Subject: [PATCH 5/7] [lld][MachO] Tail merge strings
---
lld/MachO/SyntheticSections.cpp | 58 +++++++++++++++++++-
lld/test/MachO/cstring-dedup.s | 3 +-
lld/test/MachO/cstring-tailmerge.s | 85 ++++++++++++++++++++++++++++++
3 files changed, 143 insertions(+), 3 deletions(-)
create mode 100644 lld/test/MachO/cstring-tailmerge.s
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index 903ba78a27c75..460a0b5a16ab0 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -1746,6 +1746,7 @@ void CStringSection::finalizeContents() {
void DeduplicatedCStringSection::finalizeContents() {
// Find the largest alignment required for each string.
DenseMap<CachedHashStringRef, Align> strToAlignment;
+ std::vector<CachedHashStringRef> deduplicatedStrs;
for (const CStringInputSection *isec : inputs) {
for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
if (!piece.live)
@@ -1754,17 +1755,57 @@ void DeduplicatedCStringSection::finalizeContents() {
assert(isec->align != 0);
auto align = getStringPieceAlignment(isec, piece);
auto [it, wasInserted] = strToAlignment.try_emplace(s, align);
+ if (wasInserted)
+ deduplicatedStrs.push_back(s);
if (!wasInserted && it->second < align)
it->second = align;
}
}
+ // Like lexigraphical sort, except we read strings in reverse and take the
+ // longest string first
+ // TODO: We could improve performance by implementing our own sort that avoids
+ // comparing characters we know to be the same. See
+ // StringTableBuilder::multikeySort() for details
+ llvm::sort(deduplicatedStrs, [](const auto &left, const auto &right) {
+ for (const auto &[leftChar, rightChar] :
+ llvm::zip(llvm::reverse(left.val()), llvm::reverse(right.val()))) {
+ if (leftChar == rightChar)
+ continue;
+ return leftChar < rightChar;
+ }
+ return left.size() > right.size();
+ });
+ std::optional<CachedHashStringRef> mergeCandidate;
+ DenseMap<CachedHashStringRef, std::pair<CachedHashStringRef, uint64_t>>
+ tailMergeMap;
+ for (auto &s : deduplicatedStrs) {
+ if (!mergeCandidate || !mergeCandidate->val().ends_with(s.val())) {
+ mergeCandidate = s;
+ continue;
+ }
+ uint64_t tailOffset = mergeCandidate->size() - s.size();
+ // TODO: If the tail offset is incompatible with this string's alignment, we
+ // might be able to find another superstring with a compatible tail offset.
+ // The difficulty is how to do this efficiently
+ const auto &align = strToAlignment.at(s);
+ if (!isAligned(align, tailOffset))
+ continue;
+ auto &mergeCandidateAlign = strToAlignment[*mergeCandidate];
+ if (align > mergeCandidateAlign)
+ mergeCandidateAlign = align;
+ tailMergeMap.try_emplace(s, *mergeCandidate, tailOffset);
+ }
+
// Sort the strings for performance and compression size win, and then
// assign an offset for each string and save it to the corresponding
// StringPieces for easy access.
for (auto &[isec, i] : priorityBuilder.buildCStringPriorities(inputs)) {
auto &piece = isec->pieces[i];
auto s = isec->getCachedHashStringRef(i);
+ // Skip tail merged strings until their superstring offsets are resolved
+ if (tailMergeMap.count(s))
+ continue;
auto [it, wasInserted] = stringOffsetMap.try_emplace(s, /*placeholder*/ 0);
if (wasInserted) {
// Avoid computing the offset until we are sure we will need to
@@ -1776,8 +1817,23 @@ void DeduplicatedCStringSection::finalizeContents() {
// only need to assign the offset.
piece.outSecOff = it->second;
}
- for (CStringInputSection *isec : inputs)
+ for (CStringInputSection *isec : inputs) {
+ for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
+ if (!piece.live)
+ continue;
+ auto s = isec->getCachedHashStringRef(i);
+ auto it = tailMergeMap.find(s);
+ if (it == tailMergeMap.end())
+ continue;
+ const auto &[superString, tailOffset] = it->second;
+ assert(!tailMergeMap.count(superString));
+ auto &outSecOff = stringOffsetMap[s];
+ outSecOff = stringOffsetMap.at(superString) + tailOffset;
+ piece.outSecOff = outSecOff;
+ assert(isAligned(strToAlignment.at(s), piece.outSecOff));
+ }
isec->isFinal = true;
+ }
}
void DeduplicatedCStringSection::writeTo(uint8_t *buf) const {
diff --git a/lld/test/MachO/cstring-dedup.s b/lld/test/MachO/cstring-dedup.s
index a4b15f26afff0..0a42b3d6fcff3 100644
--- a/lld/test/MachO/cstring-dedup.s
+++ b/lld/test/MachO/cstring-dedup.s
@@ -8,11 +8,10 @@
# RUN: llvm-objdump --macho --section="__DATA,ptrs" --syms %t/test | FileCheck %s
# RUN: llvm-readobj --section-headers %t/test | FileCheck %s --check-prefix=HEADER
-## Make sure we only have 3 deduplicated strings in __cstring.
+## Make sure we only have 2 deduplicated strings in __cstring.
# STR: Contents of (__TEXT,__cstring) section
# STR: {{[[:xdigit:]]+}} foo
# STR: {{[[:xdigit:]]+}} barbaz
-# STR: {{[[:xdigit:]]+}} {{$}}
## Make sure both symbol and section relocations point to the right thing.
# CHECK: Contents of (__DATA,ptrs) section
diff --git a/lld/test/MachO/cstring-tailmerge.s b/lld/test/MachO/cstring-tailmerge.s
new file mode 100644
index 0000000000000..83d2810a78139
--- /dev/null
+++ b/lld/test/MachO/cstring-tailmerge.s
@@ -0,0 +1,85 @@
+# REQUIRES: x86
+# RUN: rm -rf %t; split-file %s %t
+
+# RUN: sed "s/<ALIGN>/0/g" %t/align.s.template > %t/align-1.s
+# RUN: sed "s/<ALIGN>/1/g" %t/align.s.template > %t/align-2.s
+# RUN: sed "s/<ALIGN>/2/g" %t/align.s.template > %t/align-4.s
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/first.s -o %t/first.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/align-1.s -o %t/align-1.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/align-2.s -o %t/align-2.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/align-4.s -o %t/align-4.o
+
+# RUN: %lld -dylib --deduplicate-strings %t/first.o %t/align-1.o -o %t/align-1
+# RUN: llvm-objdump --macho --section="__TEXT,__cstring" --syms %t/align-1 | FileCheck %s --check-prefixes=CHECK,ALIGN1
+
+# RUN: %lld -dylib --deduplicate-strings %t/first.o %t/align-2.o -o %t/align-2
+# RUN: llvm-objdump --macho --section="__TEXT,__cstring" --syms %t/align-2 | FileCheck %s --check-prefixes=CHECK,ALIGN2
+
+# RUN: %lld -dylib --deduplicate-strings %t/first.o %t/align-4.o -o %t/align-4
+# RUN: llvm-objdump --macho --section="__TEXT,__cstring" --syms %t/align-4 | FileCheck %s --check-prefixes=CHECK,ALIGN4
+
+# CHECK: Contents of (__TEXT,__cstring) section
+# CHECK: [[#%.16x,START:]] get awkward offset{{$}}
+
+# ALIGN1: [[#%.16x,START+19]] myotherlongstr{{$}}
+# ALIGN1: [[#%.16x,START+19+15]] otherstr{{$}}
+
+# ALIGN2: [[#%.16x,START+20]] myotherlongstr{{$}}
+# ALIGN2: [[#%.16x,START+20+16]] longstr{{$}}
+# ALIGN2: [[#%.16x,START+20+16+8]] otherstr{{$}}
+# ALIGN2: [[#%.16x,START+20+16+8+10]] str{{$}}
+
+# ALIGN4: [[#%.16x,START+20]] myotherlongstr{{$}}
+# ALIGN4: [[#%.16x,START+20+16]] otherlongstr{{$}}
+# ALIGN4: [[#%.16x,START+20+16+16]] longstr{{$}}
+# ALIGN4: [[#%.16x,START+20+16+16+8]] otherstr{{$}}
+# ALIGN4: [[#%.16x,START+20+16+16+8+12]] str{{$}}
+
+# CHECK: SYMBOL TABLE:
+
+# ALIGN1: [[#%.16x,START+19]] l O __TEXT,__cstring _myotherlongstr
+# ALIGN1: [[#%.16x,START+21]] l O __TEXT,__cstring _otherlongstr
+# ALIGN1: [[#%.16x,START+26]] l O __TEXT,__cstring _longstr
+# ALIGN1: [[#%.16x,START+34]] l O __TEXT,__cstring _otherstr
+# ALIGN1: [[#%.16x,START+39]] l O __TEXT,__cstring _str
+
+# ALIGN2: [[#%.16x,START+20]] l O __TEXT,__cstring _myotherlongstr
+# ALIGN2: [[#%.16x,START+20+2]] l O __TEXT,__cstring _otherlongstr
+# ALIGN2: [[#%.16x,START+20+16]] l O __TEXT,__cstring _longstr
+# ALIGN2: [[#%.16x,START+20+16+8]] l O __TEXT,__cstring _otherstr
+# ALIGN2: [[#%.16x,START+20+16+8+10]] l O __TEXT,__cstring _str
+
+# ALIGN4: [[#%.16x,START+20]] l O __TEXT,__cstring _myotherlongstr
+# ALIGN4: [[#%.16x,START+20+16]] l O __TEXT,__cstring _otherlongstr
+# ALIGN4: [[#%.16x,START+20+16+16]] l O __TEXT,__cstring _longstr
+# ALIGN4: [[#%.16x,START+20+16+16+8]] l O __TEXT,__cstring _otherstr
+# ALIGN4: [[#%.16x,START+20+16+16+8+12]] l O __TEXT,__cstring _str
+
+#--- first.s
+.cstring
+.p2align 2
+.asciz "get awkward offset" # length = 19
+
+#--- align.s.template
+.cstring
+
+.p2align <ALIGN>
+ _myotherlongstr:
+.asciz "myotherlongstr" # length = 15
+
+.p2align <ALIGN>
+ _otherlongstr:
+.asciz "otherlongstr" # length = 13, tail offset = 2
+
+.p2align <ALIGN>
+ _longstr:
+.asciz "longstr" # length = 8, tail offset = 7
+
+.p2align <ALIGN>
+ _otherstr:
+.asciz "otherstr" # length = 9
+
+.p2align <ALIGN>
+ _str:
+.asciz "str" # length = 4, tail offset = 5
>From 73693d673d5b4b2ce4588248b4acaf870ffb6c01 Mon Sep 17 00:00:00 2001
From: Ellis Hoag <ellishoag at meta.com>
Date: Mon, 29 Sep 2025 16:04:17 -0700
Subject: [PATCH 6/7] add to release notes
---
lld/docs/ReleaseNotes.rst | 3 +++
1 file changed, 3 insertions(+)
diff --git a/lld/docs/ReleaseNotes.rst b/lld/docs/ReleaseNotes.rst
index 6ea1ea0fd6c2f..fa2247d64b690 100644
--- a/lld/docs/ReleaseNotes.rst
+++ b/lld/docs/ReleaseNotes.rst
@@ -44,6 +44,9 @@ MinGW Improvements
MachO Improvements
------------------
+* cstrings sections are now tail merged
+ (`#161262 <https://github.com/llvm/llvm-project/pull/161262>`_)
+
WebAssembly Improvements
------------------------
>From 6a1da5f20f915b07275ed14f8259aa5e144f72e0 Mon Sep 17 00:00:00 2001
From: Ellis Hoag <ellishoag at meta.com>
Date: Mon, 29 Sep 2025 16:10:00 -0700
Subject: [PATCH 7/7] add assert
---
lld/MachO/SyntheticSections.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index 460a0b5a16ab0..edb90fe7fcbcc 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -1826,6 +1826,7 @@ void DeduplicatedCStringSection::finalizeContents() {
if (it == tailMergeMap.end())
continue;
const auto &[superString, tailOffset] = it->second;
+ assert(superString.val().ends_with(s.val()));
assert(!tailMergeMap.count(superString));
auto &outSecOff = stringOffsetMap[s];
outSecOff = stringOffsetMap.at(superString) + tailOffset;
More information about the llvm-commits
mailing list