Skip to content

Commit 5000a1b

Browse files
committed
[lld][WebAssembly] Initial support merging string data
This change adds support for a new WASM_SEG_FLAG_STRINGS flag in the object format which works in a similar fashion to SHF_STRINGS in the ELF world. Unlike the ELF linker this support is currently limited: - No support for SHF_MERGE (non-string merging) - Always do full tail merging ("lo" can be merged with "hello") - Only support single byte strings (p2align 0) Like the ELF linker merging is only performed at `-O1` and above. This fixes part of https://bugs.llvm.org/show_bug.cgi?id=48828, although crucially it doesn't not currently support debug sections because they are not represented by data segments (they are custom sections) Differential Revision: https://reviews.llvm.org/D97657
1 parent 85af8a8 commit 5000a1b

25 files changed

+559
-75
lines changed

lld/test/wasm/merge-string.s

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %s -o %t.o
2+
// RUN: wasm-ld -O2 %t.o -o %t.wasm --no-gc-sections --no-entry
3+
// RUN: obj2yaml %t.wasm | FileCheck %s --check-prefixes=COMMON,MERGE
4+
// RUN: wasm-ld -O0 %t.o -o %t2.wasm --no-gc-sections --no-entry
5+
// RUN: obj2yaml %t2.wasm | FileCheck --check-prefixes=COMMON,NOMERGE %s
6+
7+
.section .rodata1,"S",@
8+
.asciz "abc"
9+
foo:
10+
.ascii "a"
11+
.size foo, 1
12+
bar:
13+
.asciz "bc"
14+
.asciz "bc"
15+
.size bar, 4
16+
17+
.globl foo
18+
.globl bar
19+
.export_name foo, foo
20+
.export_name bar, bar
21+
22+
// COMMON: - Type: GLOBAL
23+
// COMMON-NEXT: Globals:
24+
// COMMON-NEXT: - Index: 0
25+
// COMMON-NEXT: Type: I32
26+
// COMMON-NEXT: Mutable: true
27+
// COMMON-NEXT: InitExpr:
28+
// COMMON-NEXT: Opcode: I32_CONST
29+
// COMMON-NEXT: Value: 66576
30+
// COMMON-NEXT: - Index: 1
31+
// COMMON-NEXT: Type: I32
32+
// COMMON-NEXT: Mutable: false
33+
// COMMON-NEXT: InitExpr:
34+
// COMMON-NEXT: Opcode: I32_CONST
35+
// MERGE-NEXT: Value: 1024
36+
// NOMERGE-NEXT: Value: 1028
37+
// COMMON-NEXT: - Index: 2
38+
// COMMON-NEXT: Type: I32
39+
// COMMON-NEXT: Mutable: false
40+
// COMMON-NEXT: InitExpr:
41+
// COMMON-NEXT: Opcode: I32_CONST
42+
// MERGE-NEXT: Value: 1025
43+
// NOMERGE-NEXT: Value: 1029
44+
// COMMON-NEXT: - Type: EXPORT
45+
// COMMON-NEXT: Exports:
46+
// COMMON-NEXT: - Name: memory
47+
// COMMON-NEXT: Kind: MEMORY
48+
// COMMON-NEXT: Index: 0
49+
// COMMON-NEXT: - Name: foo
50+
// COMMON-NEXT: Kind: GLOBAL
51+
// COMMON-NEXT: Index: 1
52+
// COMMON-NEXT: - Name: bar
53+
// COMMON-NEXT: Kind: GLOBAL
54+
// COMMON-NEXT: Index: 2
55+
56+
//
57+
// COMMON: - Type: DATA
58+
// COMMON-NEXT: Segments:
59+
// COMMON-NEXT: - SectionOffset: 7
60+
// COMMON-NEXT: InitFlags: 0
61+
// COMMON-NEXT: Offset:
62+
// COMMON-NEXT: Opcode: I32_CONST
63+
// COMMON-NEXT: Value: 1024
64+
// MERGE-NEXT: Content: '61626300'
65+
// NOMERGE-NEXT: Content: '6162630061626300626300'

lld/wasm/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_lld_library(lldWasm
1010
MapFile.cpp
1111
MarkLive.cpp
1212
OutputSections.cpp
13+
OutputSegment.cpp
1314
Relocations.cpp
1415
SymbolTable.cpp
1516
Symbols.cpp

lld/wasm/Driver.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ static void readConfigs(opt::InputArgList &args) {
385385
LLVM_ENABLE_NEW_PASS_MANAGER);
386386
config->ltoDebugPassManager = args.hasArg(OPT_lto_debug_pass_manager);
387387
config->mapFile = args.getLastArgValue(OPT_Map);
388-
config->optimize = args::getInteger(args, OPT_O, 0);
388+
config->optimize = args::getInteger(args, OPT_O, 1);
389389
config->outputFile = args.getLastArgValue(OPT_o);
390390
config->relocatable = args.hasArg(OPT_relocatable);
391391
config->gcSections =
@@ -795,6 +795,18 @@ static void wrapSymbols(ArrayRef<WrappedSymbol> wrapped) {
795795
symtab->wrap(w.sym, w.real, w.wrap);
796796
}
797797

798+
static void splitSections() {
799+
// splitIntoPieces needs to be called on each MergeInputSection
800+
// before calling finalizeContents().
801+
LLVM_DEBUG(llvm::dbgs() << "splitSections\n");
802+
parallelForEach(symtab->objectFiles, [](ObjFile *file) {
803+
for (InputSegment *seg : file->segments) {
804+
if (auto *s = dyn_cast<MergeInputSegment>(seg))
805+
s->splitIntoPieces();
806+
}
807+
});
808+
}
809+
798810
void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
799811
WasmOptTable parser;
800812
opt::InputArgList args = parser.parse(argsArr.slice(1));
@@ -981,6 +993,10 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
981993
if (errorCount())
982994
return;
983995

996+
// Split WASM_SEG_FLAG_STRINGS sections into pieces in preparation for garbage
997+
// collection.
998+
splitSections();
999+
9841000
// Do size optimizations: garbage collection
9851001
markLive();
9861002

lld/wasm/InputChunks.cpp

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "lld/Common/ErrorHandler.h"
1414
#include "lld/Common/LLVM.h"
1515
#include "llvm/Support/LEB128.h"
16+
#include "llvm/Support/xxhash.h"
1617

1718
#define DEBUG_TYPE "lld"
1819

@@ -126,6 +127,10 @@ void InputChunk::writeTo(uint8_t *buf) const {
126127
memcpy(buf + outSecOff, data().data(), data().size());
127128

128129
// Apply relocations
130+
relocate(buf + outSecOff);
131+
}
132+
133+
void InputChunk::relocate(uint8_t *buf) const {
129134
if (relocations.empty())
130135
return;
131136

@@ -135,11 +140,11 @@ void InputChunk::writeTo(uint8_t *buf) const {
135140

136141
LLVM_DEBUG(dbgs() << "applying relocations: " << toString(this)
137142
<< " count=" << relocations.size() << "\n");
138-
int32_t off = outSecOff - getInputSectionOffset();
143+
int32_t inputSectionOffset = getInputSectionOffset();
139144
auto tombstone = getTombstone();
140145

141146
for (const WasmRelocation &rel : relocations) {
142-
uint8_t *loc = buf + rel.Offset + off;
147+
uint8_t *loc = buf + rel.Offset - inputSectionOffset;
143148
auto value = file->calcNewValue(rel, tombstone, this);
144149
LLVM_DEBUG(dbgs() << "apply reloc: type=" << relocTypeToString(rel.Type));
145150
if (rel.Type != R_WASM_TYPE_INDEX_LEB)
@@ -357,8 +362,20 @@ void InputFunction::writeTo(uint8_t *buf) const {
357362
LLVM_DEBUG(dbgs() << " total: " << (buf + chunkSize - orig) << "\n");
358363
}
359364

365+
uint64_t InputSegment::getOffset(uint64_t offset) const {
366+
if (const MergeInputSegment *ms = dyn_cast<MergeInputSegment>(this)) {
367+
LLVM_DEBUG(dbgs() << "getOffset(merged): " << getName() << "\n");
368+
LLVM_DEBUG(dbgs() << "offset: " << offset << "\n");
369+
LLVM_DEBUG(dbgs() << "parentOffset: " << ms->getParentOffset(offset)
370+
<< "\n");
371+
assert(ms->parent);
372+
return ms->parent->getOffset(ms->getParentOffset(offset));
373+
}
374+
return outputSegmentOffset + offset;
375+
}
376+
360377
uint64_t InputSegment::getVA(uint64_t offset) const {
361-
return outputSeg->startVA + outputSegmentOffset + offset;
378+
return (outputSeg ? outputSeg->startVA : 0) + getOffset(offset);
362379
}
363380

364381
// Generate code to apply relocations to the data section at runtime.
@@ -431,6 +448,93 @@ void InputSegment::generateRelocationCode(raw_ostream &os) const {
431448
}
432449
}
433450

451+
// Split WASM_SEG_FLAG_STRINGS section. Such a section is a sequence of
452+
// null-terminated strings.
453+
void MergeInputSegment::splitStrings(ArrayRef<uint8_t> data) {
454+
LLVM_DEBUG(llvm::dbgs() << "splitStrings\n");
455+
size_t off = 0;
456+
StringRef s = toStringRef(data);
457+
458+
while (!s.empty()) {
459+
size_t end = s.find(0);
460+
if (end == StringRef::npos)
461+
fatal(toString(this) + ": string is not null terminated");
462+
size_t size = end + 1;
463+
464+
pieces.emplace_back(off, xxHash64(s.substr(0, size)), true);
465+
s = s.substr(size);
466+
off += size;
467+
}
468+
}
469+
470+
// This function is called after we obtain a complete list of input sections
471+
// that need to be linked. This is responsible to split section contents
472+
// into small chunks for further processing.
473+
//
474+
// Note that this function is called from parallelForEach. This must be
475+
// thread-safe (i.e. no memory allocation from the pools).
476+
void MergeInputSegment::splitIntoPieces() {
477+
assert(pieces.empty());
478+
// As of now we only support WASM_SEG_FLAG_STRINGS but in the future we
479+
// could add other types of splitting (see ELF's splitIntoPieces).
480+
assert(segment->Data.LinkingFlags & WASM_SEG_FLAG_STRINGS);
481+
splitStrings(data());
482+
}
483+
484+
SegmentPiece *MergeInputSegment::getSegmentPiece(uint64_t offset) {
485+
if (this->data().size() <= offset)
486+
fatal(toString(this) + ": offset is outside the section");
487+
488+
// If Offset is not at beginning of a section piece, it is not in the map.
489+
// In that case we need to do a binary search of the original section piece
490+
// vector.
491+
auto it = partition_point(
492+
pieces, [=](SegmentPiece p) { return p.inputOff <= offset; });
493+
return &it[-1];
494+
}
495+
496+
// Returns the offset in an output section for a given input offset.
497+
// Because contents of a mergeable section is not contiguous in output,
498+
// it is not just an addition to a base output offset.
499+
uint64_t MergeInputSegment::getParentOffset(uint64_t offset) const {
500+
// If Offset is not at beginning of a section piece, it is not in the map.
501+
// In that case we need to search from the original section piece vector.
502+
const SegmentPiece *piece = getSegmentPiece(offset);
503+
uint64_t addend = offset - piece->inputOff;
504+
return piece->outputOff + addend;
505+
}
506+
507+
uint32_t SyntheticMergedDataSegment::getSize() const {
508+
return builder.getSize();
509+
}
510+
511+
void SyntheticMergedDataSegment::writeTo(uint8_t *buf) const {
512+
builder.write(buf + outSecOff);
513+
514+
// Apply relocations
515+
relocate(buf + outSecOff);
516+
}
517+
518+
void SyntheticMergedDataSegment::finalizeContents() {
519+
// Add all string pieces to the string table builder to create section
520+
// contents.
521+
for (MergeInputSegment *sec : segments)
522+
for (size_t i = 0, e = sec->pieces.size(); i != e; ++i)
523+
if (sec->pieces[i].live)
524+
builder.add(sec->getData(i));
525+
526+
// Fix the string table content. After this, the contents will never change.
527+
builder.finalize();
528+
529+
// finalize() fixed tail-optimized strings, so we can now get
530+
// offsets of strings. Get an offset for each string and save it
531+
// to a corresponding SectionPiece for easy access.
532+
for (MergeInputSegment *sec : segments)
533+
for (size_t i = 0, e = sec->pieces.size(); i != e; ++i)
534+
if (sec->pieces[i].live)
535+
sec->pieces[i].outputOff = builder.getOffset(sec->getData(i));
536+
}
537+
434538
uint64_t InputSection::getTombstoneForSection(StringRef name) {
435539
// When a function is not live we need to update relocations referring to it.
436540
// If they occur in DWARF debug symbols, we want to change the pc of the

0 commit comments

Comments
 (0)