Skip to content

Commit 3111784

Browse files
committed
[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
1 parent 1c91733 commit 3111784

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
@@ -279,6 +279,12 @@ void LinkerDriver::addFile(StringRef path) {
279279
case file_magic::wasm_object:
280280
files.push_back(createObjectFile(mbref));
281281
break;
282+
case file_magic::unknown:
283+
if (mbref.getBuffer().starts_with("#STUB\n")) {
284+
files.push_back(make<StubFile>(mbref));
285+
break;
286+
}
287+
[[fallthrough]];
282288
default:
283289
error("unknown file type: " + mbref.getBufferIdentifier());
284290
}
@@ -868,6 +874,53 @@ static void createOptionalSymbols() {
868874
WasmSym::tlsBase = createOptionalGlobal("__tls_base", false);
869875
}
870876

877+
static void processStubLibraries() {
878+
log("-- processStubLibraries");
879+
for (auto &stub_file : symtab->stubFiles) {
880+
LLVM_DEBUG(llvm::dbgs()
881+
<< "processing stub file: " << stub_file->getName() << "\n");
882+
for (auto [name, deps]: stub_file->symbolDependencies) {
883+
auto* sym = symtab->find(name);
884+
if (!sym || !sym->isUndefined() || !sym->isUsedInRegularObj ||
885+
sym->forceImport) {
886+
LLVM_DEBUG(llvm::dbgs() << "stub not in needed: " << name << "\n");
887+
continue;
888+
}
889+
// The first stub library to define a given symbol sets this and
890+
// definitions in later stub libraries are ignored.
891+
sym->forceImport = true;
892+
if (sym->traced)
893+
message(toString(stub_file) + ": importing " + name);
894+
else
895+
LLVM_DEBUG(llvm::dbgs()
896+
<< toString(stub_file) << ": importing " << name << "\n");
897+
for (const auto dep : deps) {
898+
auto* needed = symtab->find(dep);
899+
if (!needed) {
900+
error(toString(stub_file) + ": undefined symbol: " + dep +
901+
". Required by " + toString(*sym));
902+
} else if (needed->isUndefined()) {
903+
error(toString(stub_file) +
904+
": undefined symbol: " + toString(*needed) +
905+
". Required by " + toString(*sym));
906+
} else {
907+
LLVM_DEBUG(llvm::dbgs()
908+
<< "force export: " << toString(*needed) << "\n");
909+
needed->forceExport = true;
910+
needed->isUsedInRegularObj = true;
911+
if (auto *lazy = dyn_cast<LazySymbol>(needed)) {
912+
lazy->fetch();
913+
if (!config->whyExtract.empty())
914+
config->whyExtractRecords.emplace_back(stub_file->getName(),
915+
sym->getFile(), *sym);
916+
}
917+
}
918+
}
919+
}
920+
}
921+
log("-- done processStubLibraries");
922+
}
923+
871924
// Reconstructs command line arguments so that so that you can re-run
872925
// the same command with the same inputs. This is for --reproduce.
873926
static std::string createResponseFile(const opt::InputArgList &args) {
@@ -1166,6 +1219,8 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
11661219
if (errorCount())
11671220
return;
11681221

1222+
processStubLibraries();
1223+
11691224
createOptionalSymbols();
11701225

11711226
// 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
@@ -32,9 +32,9 @@ static bool requiresGOTAccess(const Symbol *sym) {
3232
}
3333

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

lld/wasm/SymbolTable.cpp

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

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

lld/wasm/SymbolTable.h

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

104104
std::vector<ObjFile *> objectFiles;
105+
std::vector<StubFile *> stubFiles;
105106
std::vector<SharedFile *> sharedFiles;
106107
std::vector<BitcodeFile *> bitcodeFiles;
107108
std::vector<InputFunction *> syntheticFunctions;

lld/wasm/Symbols.cpp

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

224+
bool Symbol::isImported() const {
225+
return isUndefined() && (importName.has_value() || forceImport);
226+
}
227+
224228
bool Symbol::isExported() const {
225229
if (!isDefined() || isLocal())
226230
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.
@@ -661,6 +665,7 @@ T *replaceSymbol(Symbol *s, ArgT &&... arg) {
661665
T *s2 = new (s) T(std::forward<ArgT>(arg)...);
662666
s2->isUsedInRegularObj = symCopy.isUsedInRegularObj;
663667
s2->forceExport = symCopy.forceExport;
668+
s2->forceImport = symCopy.forceImport;
664669
s2->canInline = symCopy.canInline;
665670
s2->traced = symCopy.traced;
666671
s2->referenced = symCopy.referenced;

lld/wasm/Writer.cpp

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

747-
return sym->importName.has_value();
747+
return sym->isImported();
748748
}
749749

750750
void Writer::calculateImports() {
@@ -1709,7 +1709,7 @@ void Writer::run() {
17091709
sym->forceExport = true;
17101710
}
17111711

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

0 commit comments

Comments
 (0)