Skip to content

Commit 74b5a0a

Browse files
sbc100tstellar
authored andcommitted
[lld][WebAssembly] Initial support for stub libraries
See the docs in lld/docs/WebAssembly.rst for more on this. This feature unlocks a lot of simplification in the emscripten toolchain since we can represent the JS libraries to wasm-ld as stub libraries. See emscripten-core/emscripten#18875 Differential Revision: https://reviews.llvm.org/D145308 (cherry picked from commit 3111784)
1 parent 9c865c2 commit 74b5a0a

14 files changed

+224
-5
lines changed

lld/docs/WebAssembly.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ WebAssembly-specific options:
7575
flag which corresponds to ``--unresolve-symbols=ignore`` +
7676
``--import-undefined``.
7777

78+
.. option:: --allow-undefined-file=<filename>
79+
80+
Like ``--allow-undefined``, but the filename specified a flat list of
81+
symbols, one per line, which are allowed to be undefined.
82+
7883
.. option:: --unresolved-symbols=<method>
7984

8085
This is a more full featured version of ``--allow-undefined``.
@@ -182,11 +187,39 @@ Imports
182187
By default no undefined symbols are allowed in the final binary. The flag
183188
``--allow-undefined`` results in a WebAssembly import being defined for each
184189
undefined symbol. It is then up to the runtime to provide such symbols.
190+
``--allow-undefined-file`` is the same but allows a list of symbols to be
191+
specified.
185192

186193
Alternatively symbols can be marked in the source code as with the
187194
``import_name`` and/or ``import_module`` clang attributes which signals that
188195
they are expected to be undefined at static link time.
189196

197+
Stub Libraries
198+
~~~~~~~~~~~~~~
199+
200+
Another way to specify imports and exports is via a "stub library". This
201+
feature is inspired by the ELF stub objects which are supported by the Solaris
202+
linker. Stub libraries are text files that can be passed as normal linker
203+
inputs, similar to how linker scripts can be passed to the ELF linker. The stub
204+
library is a stand-in for a set of symbols that will be available at runtime,
205+
but doesn't contain any actual code or data. Instead it contains just a list of
206+
symbols, one per line. Each symbol can specify zero or more dependencies.
207+
These dependencies are symbols that must be defined, and exported, by the output
208+
module if the symbol is question is imported/required by the output module.
209+
210+
For example, imagine the runtime provides an external symbol ``foo`` that
211+
depends on the ``malloc`` and ``free``. This can be expressed simply as::
212+
213+
#STUB
214+
foo: malloc,free
215+
216+
Here we are saying that ``foo`` is allowed to be imported (undefined) but that
217+
if it is imported, then the output module must also export ``malloc`` and
218+
``free`` to the runtime. If ``foo`` is imported (undefined), but the output
219+
module does not define ``malloc`` and ``free`` then the link will fail.
220+
221+
Stub libraries must begin with ``#STUB`` on a line by itself.
222+
190223
Garbage Collection
191224
~~~~~~~~~~~~~~~~~~
192225

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#STUB
2+
foo: missing_dep,missing_dep2
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#STUB
2+
# Symbol `foo` is missing from this file which causes stub_object.s to fail
3+
bar

lld/test/wasm/Inputs/libstub.so

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#STUB
2+
# This is a comment
3+
foo: foodep1,foodep2
4+
# This symbols as no dependencies
5+
bar

lld/test/wasm/stub_library.s

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
2+
# RUN: wasm-ld %t.o %p/Inputs/libstub.so -o %t.wasm
3+
# RUN: obj2yaml %t.wasm | FileCheck %s
4+
5+
# When the dependencies are missing the link fails
6+
# RUN: not wasm-ld %t.o %p/Inputs/libstub-missing-dep.so -o %t.wasm 2>&1 | FileCheck --check-prefix=MISSING-DEP %s
7+
8+
# When the dependencies are missing the link fails
9+
# RUN: not wasm-ld %t.o %p/Inputs/libstub-missing-sym.so -o %t.wasm 2>&1 | FileCheck --check-prefix=MISSING-SYM %s
10+
11+
# MISSING-DEP: libstub-missing-dep.so: undefined symbol: missing_dep. Required by foo
12+
# MISSING-DEP: libstub-missing-dep.so: undefined symbol: missing_dep2. Required by foo
13+
14+
# MISSING-SYM: undefined symbol: foo
15+
16+
# The function foo is defined in libstub.so but depend on foodep1 and foodep2
17+
.functype foo () -> ()
18+
19+
.globl foodep1
20+
foodep1:
21+
.functype foodep1 () -> ()
22+
end_function
23+
24+
.globl foodep2
25+
foodep2:
26+
.functype foodep2 () -> ()
27+
end_function
28+
29+
.globl _start
30+
_start:
31+
.functype _start () -> ()
32+
call foo
33+
end_function
34+
35+
# CHECK: - Type: EXPORT
36+
# CHECK-NEXT: Exports:
37+
# CHECK-NEXT: - Name: memory
38+
# CHECK-NEXT: Kind: MEMORY
39+
# CHECK-NEXT: Index: 0
40+
# CHECK-NEXT: - Name: foodep1
41+
# CHECK-NEXT: Kind: FUNCTION
42+
# CHECK-NEXT: Index: 1
43+
# CHECK-NEXT: - Name: foodep2
44+
# CHECK-NEXT: Kind: FUNCTION
45+
# CHECK-NEXT: Index: 2
46+
# CHECK-NEXT: - Name: _start
47+
# CHECK-NEXT: Kind: FUNCTION
48+
# CHECK-NEXT: Index: 3

lld/wasm/Driver.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,12 @@ void LinkerDriver::addFile(StringRef path) {
280280
case file_magic::wasm_object:
281281
files.push_back(createObjectFile(mbref));
282282
break;
283+
case file_magic::unknown:
284+
if (mbref.getBuffer().starts_with("#STUB\n")) {
285+
files.push_back(make<StubFile>(mbref));
286+
break;
287+
}
288+
[[fallthrough]];
283289
default:
284290
error("unknown file type: " + mbref.getBufferIdentifier());
285291
}
@@ -834,6 +840,53 @@ static void createOptionalSymbols() {
834840
WasmSym::tlsBase = createOptionalGlobal("__tls_base", false);
835841
}
836842

843+
static void processStubLibraries() {
844+
log("-- processStubLibraries");
845+
for (auto &stub_file : symtab->stubFiles) {
846+
LLVM_DEBUG(llvm::dbgs()
847+
<< "processing stub file: " << stub_file->getName() << "\n");
848+
for (auto [name, deps]: stub_file->symbolDependencies) {
849+
auto* sym = symtab->find(name);
850+
if (!sym || !sym->isUndefined() || !sym->isUsedInRegularObj ||
851+
sym->forceImport) {
852+
LLVM_DEBUG(llvm::dbgs() << "stub not in needed: " << name << "\n");
853+
continue;
854+
}
855+
// The first stub library to define a given symbol sets this and
856+
// definitions in later stub libraries are ignored.
857+
sym->forceImport = true;
858+
if (sym->traced)
859+
message(toString(stub_file) + ": importing " + name);
860+
else
861+
LLVM_DEBUG(llvm::dbgs()
862+
<< toString(stub_file) << ": importing " << name << "\n");
863+
for (const auto dep : deps) {
864+
auto* needed = symtab->find(dep);
865+
if (!needed) {
866+
error(toString(stub_file) + ": undefined symbol: " + dep +
867+
". Required by " + toString(*sym));
868+
} else if (needed->isUndefined()) {
869+
error(toString(stub_file) +
870+
": undefined symbol: " + toString(*needed) +
871+
". Required by " + toString(*sym));
872+
} else {
873+
LLVM_DEBUG(llvm::dbgs()
874+
<< "force export: " << toString(*needed) << "\n");
875+
needed->forceExport = true;
876+
needed->isUsedInRegularObj = true;
877+
if (auto *lazy = dyn_cast<LazySymbol>(needed)) {
878+
lazy->fetch();
879+
if (!config->whyExtract.empty())
880+
config->whyExtractRecords.emplace_back(stub_file->getName(),
881+
sym->getFile(), *sym);
882+
}
883+
}
884+
}
885+
}
886+
}
887+
log("-- done processStubLibraries");
888+
}
889+
837890
// Reconstructs command line arguments so that so that you can re-run
838891
// the same command with the same inputs. This is for --reproduce.
839892
static std::string createResponseFile(const opt::InputArgList &args) {
@@ -1132,6 +1185,8 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
11321185
if (errorCount())
11331186
return;
11341187

1188+
processStubLibraries();
1189+
11351190
createOptionalSymbols();
11361191

11371192
// Resolve any variant symbols that were created due to signature

lld/wasm/InputFiles.cpp

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "InputElement.h"
1313
#include "OutputSegment.h"
1414
#include "SymbolTable.h"
15+
#include "lld/Common/Args.h"
1516
#include "lld/Common/CommonLinkerContext.h"
1617
#include "lld/Common/Reproduce.h"
1718
#include "llvm/Object/Binary.h"
@@ -678,6 +679,48 @@ Symbol *ObjFile::createUndefined(const WasmSymbol &sym, bool isCalledDirectly) {
678679
llvm_unreachable("unknown symbol kind");
679680
}
680681

682+
683+
StringRef strip(StringRef s) {
684+
while (s.starts_with(" ")) {
685+
s = s.drop_front();
686+
}
687+
while (s.ends_with(" ")) {
688+
s = s.drop_back();
689+
}
690+
return s;
691+
}
692+
693+
void StubFile::parse() {
694+
bool first = false;
695+
696+
for (StringRef line : args::getLines(mb)) {
697+
// File must begin with #STUB
698+
if (first) {
699+
assert(line == "#STUB\n");
700+
first = false;
701+
}
702+
703+
// Lines starting with # are considered comments
704+
if (line.startswith("#"))
705+
continue;
706+
707+
StringRef sym;
708+
StringRef rest;
709+
std::tie(sym, rest) = line.split(':');
710+
sym = strip(sym);
711+
rest = strip(rest);
712+
713+
symbolDependencies[sym] = {};
714+
715+
while (rest.size()) {
716+
StringRef first;
717+
std::tie(first, rest) = rest.split(',');
718+
first = strip(first);
719+
symbolDependencies[sym].push_back(first);
720+
}
721+
}
722+
}
723+
681724
void ArchiveFile::parse() {
682725
// Parse a MemoryBufferRef as an archive file.
683726
LLVM_DEBUG(dbgs() << "Parsing library: " << toString(this) << "\n");

lld/wasm/InputFiles.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class InputFile {
4747
SharedKind,
4848
ArchiveKind,
4949
BitcodeKind,
50+
StubKind,
5051
};
5152

5253
virtual ~InputFile() {}
@@ -183,6 +184,18 @@ class BitcodeFile : public InputFile {
183184
static bool doneLTO;
184185
};
185186

187+
// Stub libray (See docs/WebAssembly.rst)
188+
class StubFile : public InputFile {
189+
public:
190+
explicit StubFile(MemoryBufferRef m) : InputFile(StubKind, m) {}
191+
192+
static bool classof(const InputFile *f) { return f->kind() == StubKind; }
193+
194+
void parse();
195+
196+
llvm::DenseMap<StringRef, std::vector<StringRef>> symbolDependencies;
197+
};
198+
186199
inline bool isBitcode(MemoryBufferRef mb) {
187200
return identify_magic(mb.getBuffer()) == llvm::file_magic::bitcode;
188201
}

lld/wasm/Relocations.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,9 @@ static bool requiresGOTAccess(const Symbol *sym) {
3333
}
3434

3535
static bool allowUndefined(const Symbol* sym) {
36-
// Symbols with explicit import names are always allowed to be undefined at
36+
// Symbols that are explicitly imported are always allowed to be undefined at
3737
// link time.
38-
if (sym->importName)
38+
if (sym->isImported())
3939
return true;
4040
if (isa<UndefinedFunction>(sym) && config->importUndefined)
4141
return true;

lld/wasm/SymbolTable.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ void SymbolTable::addFile(InputFile *file) {
3939
return;
4040
}
4141

42+
// stub file
43+
if (auto *f = dyn_cast<StubFile>(file)) {
44+
f->parse();
45+
stubFiles.push_back(f);
46+
return;
47+
}
48+
4249
if (config->trace)
4350
message(toString(file));
4451

lld/wasm/SymbolTable.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ class SymbolTable {
103103
DefinedFunction *createUndefinedStub(const WasmSignature &sig);
104104

105105
std::vector<ObjFile *> objectFiles;
106+
std::vector<StubFile *> stubFiles;
106107
std::vector<SharedFile *> sharedFiles;
107108
std::vector<BitcodeFile *> bitcodeFiles;
108109
std::vector<InputFunction *> syntheticFunctions;

lld/wasm/Symbols.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,10 @@ void Symbol::setHidden(bool isHidden) {
220220
flags |= WASM_SYMBOL_VISIBILITY_DEFAULT;
221221
}
222222

223+
bool Symbol::isImported() const {
224+
return isUndefined() && (importName.has_value() || forceImport);
225+
}
226+
223227
bool Symbol::isExported() const {
224228
if (!isDefined() || isLocal())
225229
return false;

lld/wasm/Symbols.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ class Symbol {
114114
void setOutputSymbolIndex(uint32_t index);
115115

116116
WasmSymbolType getWasmType() const;
117+
bool isImported() const;
117118
bool isExported() const;
118119
bool isExportedExplicit() const;
119120

@@ -135,7 +136,8 @@ class Symbol {
135136
Symbol(StringRef name, Kind k, uint32_t flags, InputFile *f)
136137
: name(name), file(f), symbolKind(k), referenced(!config->gcSections),
137138
requiresGOT(false), isUsedInRegularObj(false), forceExport(false),
138-
canInline(false), traced(false), isStub(false), flags(flags) {}
139+
forceImport(false), canInline(false), traced(false), isStub(false),
140+
flags(flags) {}
139141

140142
StringRef name;
141143
InputFile *file;
@@ -160,6 +162,8 @@ class Symbol {
160162
// -e/--export command line flag)
161163
bool forceExport : 1;
162164

165+
bool forceImport : 1;
166+
163167
// False if LTO shouldn't inline whatever this symbol points to. If a symbol
164168
// is overwritten after LTO, LTO shouldn't inline the symbol because it
165169
// doesn't know the final contents of the symbol.
@@ -656,6 +660,7 @@ T *replaceSymbol(Symbol *s, ArgT &&... arg) {
656660
T *s2 = new (s) T(std::forward<ArgT>(arg)...);
657661
s2->isUsedInRegularObj = symCopy.isUsedInRegularObj;
658662
s2->forceExport = symCopy.forceExport;
663+
s2->forceImport = symCopy.forceImport;
659664
s2->canInline = symCopy.canInline;
660665
s2->traced = symCopy.traced;
661666
s2->referenced = symCopy.referenced;

lld/wasm/Writer.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,7 @@ static bool shouldImport(Symbol *sym) {
647647
if (config->allowUndefinedSymbols.count(sym->getName()) != 0)
648648
return true;
649649

650-
return sym->importName.has_value();
650+
return sym->isImported();
651651
}
652652

653653
void Writer::calculateImports() {
@@ -1570,7 +1570,7 @@ void Writer::run() {
15701570
sym->forceExport = true;
15711571
}
15721572

1573-
// Delay reporting error about explicit exports until after
1573+
// Delay reporting errors about explicit exports until after
15741574
// addStartStopSymbols which can create optional symbols.
15751575
for (auto &name : config->requiredExports) {
15761576
Symbol *sym = symtab->find(name);

0 commit comments

Comments
 (0)