-
-
Save alexcrichton/5d13ea0601d53c4a35f54cf44c245688 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/test/wasm/cxx-mangling.ll b/test/wasm/cxx-mangling.ll | |
index 0ab48748a..4ec49419b 100644 | |
--- a/test/wasm/cxx-mangling.ll | |
+++ b/test/wasm/cxx-mangling.ll | |
@@ -1,66 +1,68 @@ | |
; RUN: llc -filetype=obj %s -o %t.o | |
; RUN: wasm-ld --export=_Z3fooi --demangle -o %t_demangle.wasm %t.o | |
-; RUN: obj2yaml %t_demangle.wasm | FileCheck %s | |
+; RUN: obj2yaml %t_demangle.wasm | FileCheck --check-prefixes=COMMON,DEMANGLE %s | |
; RUN: wasm-ld --export=_Z3fooi --no-demangle -o %t_nodemangle.wasm %t.o | |
-; RUN: obj2yaml %t_nodemangle.wasm | FileCheck %s | |
+; RUN: obj2yaml %t_nodemangle.wasm | FileCheck --check-prefixes=COMMON,MANGLE %s | |
target triple = "wasm32-unknown-unknown" | |
; Check that the EXPORT name is still mangled, but that the "name" custom | |
; section contains the unmangled name. | |
define void @_Z3fooi(i32 %arg) { | |
ret void | |
} | |
declare extern_weak void @_Z3bari(i32 %arg) | |
define void @_start() { | |
call void @_Z3fooi(i32 1) | |
call void @_Z3bari(i32 1) | |
ret void | |
} | |
-; CHECK: - Type: EXPORT | |
-; CHECK-NEXT: Exports: | |
-; CHECK-NEXT: - Name: memory | |
-; CHECK-NEXT: Kind: MEMORY | |
-; CHECK-NEXT: Index: 0 | |
-; CHECK-NEXT: - Name: __heap_base | |
-; CHECK-NEXT: Kind: GLOBAL | |
-; CHECK-NEXT: Index: 1 | |
-; CHECK-NEXT: - Name: __data_end | |
-; CHECK-NEXT: Kind: GLOBAL | |
-; CHECK-NEXT: Index: 2 | |
-; CHECK-NEXT: - Name: _Z3fooi | |
-; CHECK-NEXT: Kind: FUNCTION | |
-; CHECK-NEXT: Index: 2 | |
-; CHECK-NEXT: - Name: _start | |
-; CHECK-NEXT: Kind: FUNCTION | |
-; CHECK-NEXT: Index: 3 | |
-; CHECK-NEXT: - Type: CODE | |
-; CHECK-NEXT: Functions: | |
-; CHECK-NEXT: - Index: 0 | |
-; CHECK-NEXT: Locals: | |
-; CHECK-NEXT: Body: 0B | |
-; CHECK-NEXT: - Index: 1 | |
-; CHECK-NEXT: Locals: | |
-; CHECK-NEXT: Body: 000B | |
-; CHECK-NEXT: - Index: 2 | |
-; CHECK-NEXT: Locals: | |
-; CHECK-NEXT: Body: 0B | |
-; CHECK-NEXT: - Index: 3 | |
-; CHECK-NEXT: Locals: | |
-; CHECK-NEXT: Body: 410110828080800041011081808080000B | |
-; CHECK-NEXT: - Type: CUSTOM | |
-; CHECK-NEXT: Name: name | |
-; CHECK-NEXT: FunctionNames: | |
-; CHECK-NEXT: - Index: 0 | |
-; CHECK-NEXT: Name: __wasm_call_ctors | |
-; CHECK-NEXT: - Index: 1 | |
-; CHECK-NEXT: Name: 'undefined function bar(int)' | |
-; CHECK-NEXT: - Index: 2 | |
-; CHECK-NEXT: Name: 'foo(int)' | |
-; CHECK-NEXT: - Index: 3 | |
-; CHECK-NEXT: Name: _start | |
-; CHECK-NEXT: ... | |
+; COMMON: - Type: EXPORT | |
+; COMMON-NEXT: Exports: | |
+; COMMON-NEXT: - Name: memory | |
+; COMMON-NEXT: Kind: MEMORY | |
+; COMMON-NEXT: Index: 0 | |
+; COMMON-NEXT: - Name: __heap_base | |
+; COMMON-NEXT: Kind: GLOBAL | |
+; COMMON-NEXT: Index: 1 | |
+; COMMON-NEXT: - Name: __data_end | |
+; COMMON-NEXT: Kind: GLOBAL | |
+; COMMON-NEXT: Index: 2 | |
+; COMMON-NEXT: - Name: _Z3fooi | |
+; COMMON-NEXT: Kind: FUNCTION | |
+; COMMON-NEXT: Index: 2 | |
+; COMMON-NEXT: - Name: _start | |
+; COMMON-NEXT: Kind: FUNCTION | |
+; COMMON-NEXT: Index: 3 | |
+; COMMON-NEXT: - Type: CODE | |
+; COMMON-NEXT: Functions: | |
+; COMMON-NEXT: - Index: 0 | |
+; COMMON-NEXT: Locals: | |
+; COMMON-NEXT: Body: 0B | |
+; COMMON-NEXT: - Index: 1 | |
+; COMMON-NEXT: Locals: | |
+; COMMON-NEXT: Body: 000B | |
+; COMMON-NEXT: - Index: 2 | |
+; COMMON-NEXT: Locals: | |
+; COMMON-NEXT: Body: 0B | |
+; COMMON-NEXT: - Index: 3 | |
+; COMMON-NEXT: Locals: | |
+; COMMON-NEXT: Body: 410110828080800041011081808080000B | |
+; COMMON-NEXT: - Type: CUSTOM | |
+; COMMON-NEXT: Name: name | |
+; COMMON-NEXT: FunctionNames: | |
+; COMMON-NEXT: - Index: 0 | |
+; COMMON-NEXT: Name: __wasm_call_ctors | |
+; COMMON-NEXT: - Index: 1 | |
+; DEMANGLE-NEXT: Name: 'undefined function bar(int)' | |
+; MANGLE-NEXT: Name: undefined function _Z3bari | |
+; COMMON-NEXT: - Index: 2 | |
+; DEMANGLE-NEXT: Name: 'foo(int)' | |
+; MANGLE-NEXT: Name: _Z3fooi | |
+; COMMON-NEXT: - Index: 3 | |
+; COMMON-NEXT: Name: _start | |
+; COMMON-NEXT: ... | |
diff --git a/wasm/Driver.cpp b/wasm/Driver.cpp | |
index acdb5f93f..d90609396 100644 | |
--- a/wasm/Driver.cpp | |
+++ b/wasm/Driver.cpp | |
@@ -1,550 +1,548 @@ | |
//===- Driver.cpp ---------------------------------------------------------===// | |
// | |
// The LLVM Linker | |
// | |
// This file is distributed under the University of Illinois Open Source | |
// License. See LICENSE.TXT for details. | |
// | |
//===----------------------------------------------------------------------===// | |
#include "lld/Common/Driver.h" | |
#include "Config.h" | |
#include "InputChunks.h" | |
#include "InputGlobal.h" | |
#include "MarkLive.h" | |
#include "SymbolTable.h" | |
#include "Writer.h" | |
#include "lld/Common/Args.h" | |
#include "lld/Common/ErrorHandler.h" | |
#include "lld/Common/Memory.h" | |
#include "lld/Common/Strings.h" | |
#include "lld/Common/Threads.h" | |
#include "lld/Common/Version.h" | |
#include "llvm/ADT/Twine.h" | |
#include "llvm/Object/Wasm.h" | |
#include "llvm/Option/ArgList.h" | |
#include "llvm/Support/CommandLine.h" | |
#include "llvm/Support/Path.h" | |
#include "llvm/Support/Process.h" | |
#include "llvm/Support/TargetSelect.h" | |
#define DEBUG_TYPE "lld" | |
using namespace llvm; | |
using namespace llvm::sys; | |
using namespace llvm::wasm; | |
using namespace lld; | |
using namespace lld::wasm; | |
Configuration *lld::wasm::Config; | |
namespace { | |
// Create enum with OPT_xxx values for each option in Options.td | |
enum { | |
OPT_INVALID = 0, | |
#define OPTION(_1, _2, ID, _4, _5, _6, _7, _8, _9, _10, _11, _12) OPT_##ID, | |
#include "Options.inc" | |
#undef OPTION | |
}; | |
// This function is called on startup. We need this for LTO since | |
// LTO calls LLVM functions to compile bitcode files to native code. | |
// Technically this can be delayed until we read bitcode files, but | |
// we don't bother to do lazily because the initialization is fast. | |
static void initLLVM() { | |
InitializeAllTargets(); | |
InitializeAllTargetMCs(); | |
InitializeAllAsmPrinters(); | |
InitializeAllAsmParsers(); | |
} | |
class LinkerDriver { | |
public: | |
void link(ArrayRef<const char *> ArgsArr); | |
private: | |
void createFiles(opt::InputArgList &Args); | |
void addFile(StringRef Path); | |
void addLibrary(StringRef Name); | |
// True if we are in --whole-archive and --no-whole-archive. | |
bool InWholeArchive = false; | |
std::vector<InputFile *> Files; | |
}; | |
} // anonymous namespace | |
bool lld::wasm::link(ArrayRef<const char *> Args, bool CanExitEarly, | |
raw_ostream &Error) { | |
errorHandler().LogName = args::getFilenameWithoutExe(Args[0]); | |
errorHandler().ErrorOS = &Error; | |
errorHandler().ColorDiagnostics = Error.has_colors(); | |
errorHandler().ErrorLimitExceededMsg = | |
"too many errors emitted, stopping now (use " | |
"-error-limit=0 to see all errors)"; | |
Config = make<Configuration>(); | |
Symtab = make<SymbolTable>(); | |
initLLVM(); | |
LinkerDriver().link(Args); | |
// Exit immediately if we don't need to return to the caller. | |
// This saves time because the overhead of calling destructors | |
// for all globally-allocated objects is not negligible. | |
if (CanExitEarly) | |
exitLld(errorCount() ? 1 : 0); | |
freeArena(); | |
return !errorCount(); | |
} | |
// Create prefix string literals used in Options.td | |
#define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE; | |
#include "Options.inc" | |
#undef PREFIX | |
// Create table mapping all options defined in Options.td | |
static const opt::OptTable::Info OptInfo[] = { | |
#define OPTION(X1, X2, ID, KIND, GROUP, ALIAS, X7, X8, X9, X10, X11, X12) \ | |
{X1, X2, X10, X11, OPT_##ID, opt::Option::KIND##Class, \ | |
X9, X8, OPT_##GROUP, OPT_##ALIAS, X7, X12}, | |
#include "Options.inc" | |
#undef OPTION | |
}; | |
namespace { | |
class WasmOptTable : public llvm::opt::OptTable { | |
public: | |
WasmOptTable() : OptTable(OptInfo) {} | |
opt::InputArgList parse(ArrayRef<const char *> Argv); | |
}; | |
} // namespace | |
// Set color diagnostics according to -color-diagnostics={auto,always,never} | |
// or -no-color-diagnostics flags. | |
static void handleColorDiagnostics(opt::InputArgList &Args) { | |
auto *Arg = Args.getLastArg(OPT_color_diagnostics, OPT_color_diagnostics_eq, | |
OPT_no_color_diagnostics); | |
if (!Arg) | |
return; | |
if (Arg->getOption().getID() == OPT_color_diagnostics) { | |
errorHandler().ColorDiagnostics = true; | |
} else if (Arg->getOption().getID() == OPT_no_color_diagnostics) { | |
errorHandler().ColorDiagnostics = false; | |
} else { | |
StringRef S = Arg->getValue(); | |
if (S == "always") | |
errorHandler().ColorDiagnostics = true; | |
else if (S == "never") | |
errorHandler().ColorDiagnostics = false; | |
else if (S != "auto") | |
error("unknown option: --color-diagnostics=" + S); | |
} | |
} | |
// Find a file by concatenating given paths. | |
static Optional<std::string> findFile(StringRef Path1, const Twine &Path2) { | |
SmallString<128> S; | |
path::append(S, Path1, Path2); | |
if (fs::exists(S)) | |
return S.str().str(); | |
return None; | |
} | |
opt::InputArgList WasmOptTable::parse(ArrayRef<const char *> Argv) { | |
SmallVector<const char *, 256> Vec(Argv.data(), Argv.data() + Argv.size()); | |
unsigned MissingIndex; | |
unsigned MissingCount; | |
// Expand response files (arguments in the form of @<filename>) | |
cl::ExpandResponseFiles(Saver, cl::TokenizeGNUCommandLine, Vec); | |
opt::InputArgList Args = this->ParseArgs(Vec, MissingIndex, MissingCount); | |
handleColorDiagnostics(Args); | |
for (auto *Arg : Args.filtered(OPT_UNKNOWN)) | |
error("unknown argument: " + Arg->getSpelling()); | |
return Args; | |
} | |
// Currently we allow a ".imports" to live alongside a library. This can | |
// be used to specify a list of symbols which can be undefined at link | |
// time (imported from the environment. For example libc.a include an | |
// import file that lists the syscall functions it relies on at runtime. | |
// In the long run this information would be better stored as a symbol | |
// attribute/flag in the object file itself. | |
// See: https://github.com/WebAssembly/tool-conventions/issues/35 | |
static void readImportFile(StringRef Filename) { | |
if (Optional<MemoryBufferRef> Buf = readFile(Filename)) | |
for (StringRef Sym : args::getLines(*Buf)) | |
Config->AllowUndefinedSymbols.insert(Sym); | |
} | |
// Returns slices of MB by parsing MB as an archive file. | |
// Each slice consists of a member file in the archive. | |
std::vector<MemoryBufferRef> static getArchiveMembers(MemoryBufferRef MB) { | |
std::unique_ptr<Archive> File = | |
CHECK(Archive::create(MB), | |
MB.getBufferIdentifier() + ": failed to parse archive"); | |
std::vector<MemoryBufferRef> V; | |
Error Err = Error::success(); | |
for (const ErrorOr<Archive::Child> &COrErr : File->children(Err)) { | |
Archive::Child C = | |
CHECK(COrErr, MB.getBufferIdentifier() + | |
": could not get the child of the archive"); | |
MemoryBufferRef MBRef = | |
CHECK(C.getMemoryBufferRef(), | |
MB.getBufferIdentifier() + | |
": could not get the buffer for a child of the archive"); | |
V.push_back(MBRef); | |
} | |
if (Err) | |
fatal(MB.getBufferIdentifier() + | |
": Archive::children failed: " + toString(std::move(Err))); | |
// Take ownership of memory buffers created for members of thin archives. | |
for (std::unique_ptr<MemoryBuffer> &MB : File->takeThinBuffers()) | |
make<std::unique_ptr<MemoryBuffer>>(std::move(MB)); | |
return V; | |
} | |
void LinkerDriver::addFile(StringRef Path) { | |
Optional<MemoryBufferRef> Buffer = readFile(Path); | |
if (!Buffer.hasValue()) | |
return; | |
MemoryBufferRef MBRef = *Buffer; | |
switch (identify_magic(MBRef.getBuffer())) { | |
case file_magic::archive: { | |
// Handle -whole-archive. | |
if (InWholeArchive) { | |
for (MemoryBufferRef &M : getArchiveMembers(MBRef)) | |
Files.push_back(createObjectFile(M)); | |
return; | |
} | |
SmallString<128> ImportFile = Path; | |
path::replace_extension(ImportFile, ".imports"); | |
if (fs::exists(ImportFile)) | |
readImportFile(ImportFile.str()); | |
Files.push_back(make<ArchiveFile>(MBRef)); | |
return; | |
} | |
case file_magic::bitcode: | |
case file_magic::wasm_object: | |
Files.push_back(createObjectFile(MBRef)); | |
break; | |
default: | |
error("unknown file type: " + MBRef.getBufferIdentifier()); | |
} | |
} | |
// Add a given library by searching it from input search paths. | |
void LinkerDriver::addLibrary(StringRef Name) { | |
for (StringRef Dir : Config->SearchPaths) { | |
if (Optional<std::string> S = findFile(Dir, "lib" + Name + ".a")) { | |
addFile(*S); | |
return; | |
} | |
} | |
error("unable to find library -l" + Name); | |
} | |
void LinkerDriver::createFiles(opt::InputArgList &Args) { | |
for (auto *Arg : Args) { | |
switch (Arg->getOption().getUnaliasedOption().getID()) { | |
case OPT_l: | |
addLibrary(Arg->getValue()); | |
break; | |
case OPT_INPUT: | |
addFile(Arg->getValue()); | |
break; | |
case OPT_whole_archive: | |
InWholeArchive = true; | |
break; | |
case OPT_no_whole_archive: | |
InWholeArchive = false; | |
break; | |
} | |
} | |
} | |
static StringRef getEntry(opt::InputArgList &Args, StringRef Default) { | |
auto *Arg = Args.getLastArg(OPT_entry, OPT_no_entry); | |
if (!Arg) | |
return Default; | |
if (Arg->getOption().getID() == OPT_no_entry) | |
return ""; | |
return Arg->getValue(); | |
} | |
static const uint8_t UnreachableFn[] = { | |
0x03 /* ULEB length */, 0x00 /* ULEB num locals */, | |
0x00 /* opcode unreachable */, 0x0b /* opcode end */ | |
}; | |
// For weak undefined functions, there may be "call" instructions that reference | |
// the symbol. In this case, we need to synthesise a dummy/stub function that | |
// will abort at runtime, so that relocations can still provided an operand to | |
// the call instruction that passes Wasm validation. | |
static void handleWeakUndefines() { | |
for (Symbol *Sym : Symtab->getSymbols()) { | |
if (!Sym->isUndefined() || !Sym->isWeak()) | |
continue; | |
auto *FuncSym = dyn_cast<FunctionSymbol>(Sym); | |
if (!FuncSym) | |
continue; | |
// It is possible for undefined functions not to have a signature (eg. if | |
// added via "--undefined"), but weak undefined ones do have a signature. | |
assert(FuncSym->FunctionType); | |
const WasmSignature &Sig = *FuncSym->FunctionType; | |
// Add a synthetic dummy for weak undefined functions. These dummies will | |
// be GC'd if not used as the target of any "call" instructions. | |
- Optional<std::string> SymName = demangleItanium(Sym->getName()); | |
- StringRef DebugName = | |
- Saver.save("undefined function " + | |
- (SymName ? StringRef(*SymName) : Sym->getName())); | |
+ std::string SymName = toString(*Sym); | |
+ StringRef DebugName = Saver.save("undefined function " + SymName); | |
SyntheticFunction *Func = | |
make<SyntheticFunction>(Sig, Sym->getName(), DebugName); | |
Func->setBody(UnreachableFn); | |
// Ensure it compares equal to the null pointer, and so that table relocs | |
// don't pull in the stub body (only call-operand relocs should do that). | |
Func->setTableIndex(0); | |
Symtab->SyntheticFunctions.emplace_back(Func); | |
// Hide our dummy to prevent export. | |
uint32_t Flags = WASM_SYMBOL_VISIBILITY_HIDDEN; | |
replaceSymbol<DefinedFunction>(Sym, Sym->getName(), Flags, nullptr, Func); | |
} | |
} | |
// Force Sym to be entered in the output. Used for -u or equivalent. | |
static Symbol *handleUndefined(StringRef Name) { | |
Symbol *Sym = Symtab->find(Name); | |
if (!Sym) | |
return nullptr; | |
// Since symbol S may not be used inside the program, LTO may | |
// eliminate it. Mark the symbol as "used" to prevent it. | |
Sym->IsUsedInRegularObj = true; | |
if (auto *LazySym = dyn_cast<LazySymbol>(Sym)) | |
LazySym->fetch(); | |
return Sym; | |
} | |
void LinkerDriver::link(ArrayRef<const char *> ArgsArr) { | |
WasmOptTable Parser; | |
opt::InputArgList Args = Parser.parse(ArgsArr.slice(1)); | |
// Handle --help | |
if (Args.hasArg(OPT_help)) { | |
Parser.PrintHelp(outs(), | |
(std::string(ArgsArr[0]) + " [options] file...").c_str(), | |
"LLVM Linker", false); | |
return; | |
} | |
// Handle --version | |
if (Args.hasArg(OPT_version) || Args.hasArg(OPT_v)) { | |
outs() << getLLDVersion() << "\n"; | |
return; | |
} | |
// Parse and evaluate -mllvm options. | |
std::vector<const char *> V; | |
V.push_back("wasm-ld (LLVM option parsing)"); | |
for (auto *Arg : Args.filtered(OPT_mllvm)) | |
V.push_back(Arg->getValue()); | |
cl::ParseCommandLineOptions(V.size(), V.data()); | |
errorHandler().ErrorLimit = args::getInteger(Args, OPT_error_limit, 20); | |
Config->AllowUndefined = Args.hasArg(OPT_allow_undefined); | |
Config->Demangle = Args.hasFlag(OPT_demangle, OPT_no_demangle, true); | |
Config->DisableVerify = Args.hasArg(OPT_disable_verify); | |
Config->Entry = getEntry(Args, Args.hasArg(OPT_relocatable) ? "" : "_start"); | |
Config->ExportAll = Args.hasArg(OPT_export_all); | |
Config->ExportDynamic = Args.hasFlag(OPT_export_dynamic, | |
OPT_no_export_dynamic, false); | |
Config->ExportTable = Args.hasArg(OPT_export_table); | |
errorHandler().FatalWarnings = | |
Args.hasFlag(OPT_fatal_warnings, OPT_no_fatal_warnings, false); | |
Config->ImportMemory = Args.hasArg(OPT_import_memory); | |
Config->SharedMemory = Args.hasArg(OPT_shared_memory); | |
Config->ImportTable = Args.hasArg(OPT_import_table); | |
Config->LTOO = args::getInteger(Args, OPT_lto_O, 2); | |
Config->LTOPartitions = args::getInteger(Args, OPT_lto_partitions, 1); | |
Config->Optimize = args::getInteger(Args, OPT_O, 0); | |
Config->OutputFile = Args.getLastArgValue(OPT_o); | |
Config->Relocatable = Args.hasArg(OPT_relocatable); | |
Config->GcSections = | |
Args.hasFlag(OPT_gc_sections, OPT_no_gc_sections, !Config->Relocatable); | |
Config->MergeDataSegments = | |
Args.hasFlag(OPT_merge_data_segments, OPT_no_merge_data_segments, | |
!Config->Relocatable); | |
Config->PrintGcSections = | |
Args.hasFlag(OPT_print_gc_sections, OPT_no_print_gc_sections, false); | |
Config->SaveTemps = Args.hasArg(OPT_save_temps); | |
Config->SearchPaths = args::getStrings(Args, OPT_L); | |
Config->StripAll = Args.hasArg(OPT_strip_all); | |
Config->StripDebug = Args.hasArg(OPT_strip_debug); | |
Config->CompressRelocations = Args.hasArg(OPT_compress_relocations); | |
Config->StackFirst = Args.hasArg(OPT_stack_first); | |
Config->ThinLTOCacheDir = Args.getLastArgValue(OPT_thinlto_cache_dir); | |
Config->ThinLTOCachePolicy = CHECK( | |
parseCachePruningPolicy(Args.getLastArgValue(OPT_thinlto_cache_policy)), | |
"--thinlto-cache-policy: invalid cache policy"); | |
Config->ThinLTOJobs = args::getInteger(Args, OPT_thinlto_jobs, -1u); | |
errorHandler().Verbose = Args.hasArg(OPT_verbose); | |
ThreadsEnabled = Args.hasFlag(OPT_threads, OPT_no_threads, true); | |
Config->InitialMemory = args::getInteger(Args, OPT_initial_memory, 0); | |
Config->GlobalBase = args::getInteger(Args, OPT_global_base, 1024); | |
Config->MaxMemory = args::getInteger(Args, OPT_max_memory, 0); | |
Config->ZStackSize = | |
args::getZOptionValue(Args, OPT_z, "stack-size", WasmPageSize); | |
if (!Config->StripDebug && !Config->StripAll && Config->CompressRelocations) | |
error("--compress-relocations is incompatible with output debug" | |
" information. Please pass --strip-debug or --strip-all"); | |
if (Config->LTOO > 3) | |
error("invalid optimization level for LTO: " + Twine(Config->LTOO)); | |
if (Config->LTOPartitions == 0) | |
error("--lto-partitions: number of threads must be > 0"); | |
if (Config->ThinLTOJobs == 0) | |
error("--thinlto-jobs: number of threads must be > 0"); | |
if (auto *Arg = Args.getLastArg(OPT_allow_undefined_file)) | |
readImportFile(Arg->getValue()); | |
if (!Args.hasArg(OPT_INPUT)) { | |
error("no input files"); | |
return; | |
} | |
if (Config->OutputFile.empty()) | |
error("no output file specified"); | |
if (Config->ImportTable && Config->ExportTable) | |
error("--import-table and --export-table may not be used together"); | |
if (Config->Relocatable) { | |
if (!Config->Entry.empty()) | |
error("entry point specified for relocatable output file"); | |
if (Config->GcSections) | |
error("-r and --gc-sections may not be used together"); | |
if (Config->CompressRelocations) | |
error("-r -and --compress-relocations may not be used together"); | |
if (Args.hasArg(OPT_undefined)) | |
error("-r -and --undefined may not be used together"); | |
} | |
Symbol *EntrySym = nullptr; | |
if (!Config->Relocatable) { | |
llvm::wasm::WasmGlobal Global; | |
Global.Type = {WASM_TYPE_I32, true}; | |
Global.InitExpr.Value.Int32 = 0; | |
Global.InitExpr.Opcode = WASM_OPCODE_I32_CONST; | |
Global.SymbolName = "__stack_pointer"; | |
InputGlobal *StackPointer = make<InputGlobal>(Global, nullptr); | |
StackPointer->Live = true; | |
static WasmSignature NullSignature = {{}, {}}; | |
// Add synthetic symbols before any others | |
WasmSym::CallCtors = Symtab->addSyntheticFunction( | |
"__wasm_call_ctors", WASM_SYMBOL_VISIBILITY_HIDDEN, | |
make<SyntheticFunction>(NullSignature, "__wasm_call_ctors")); | |
// TODO(sbc): Remove WASM_SYMBOL_VISIBILITY_HIDDEN when the mutable global | |
// spec proposal is implemented in all major browsers. | |
// See: https://github.com/WebAssembly/mutable-global | |
WasmSym::StackPointer = Symtab->addSyntheticGlobal( | |
"__stack_pointer", WASM_SYMBOL_VISIBILITY_HIDDEN, StackPointer); | |
WasmSym::HeapBase = Symtab->addSyntheticDataSymbol("__heap_base", 0); | |
WasmSym::DsoHandle = Symtab->addSyntheticDataSymbol( | |
"__dso_handle", WASM_SYMBOL_VISIBILITY_HIDDEN); | |
WasmSym::DataEnd = Symtab->addSyntheticDataSymbol("__data_end", 0); | |
// These two synthetic symbols exist purely for the embedder so we always | |
// want to export them. | |
WasmSym::HeapBase->ForceExport = true; | |
WasmSym::DataEnd->ForceExport = true; | |
} | |
createFiles(Args); | |
if (errorCount()) | |
return; | |
// Add all files to the symbol table. This will add almost all | |
// symbols that we need to the symbol table. | |
for (InputFile *F : Files) | |
Symtab->addFile(F); | |
if (errorCount()) | |
return; | |
// Handle the `--undefined <sym>` options. | |
for (auto *Arg : Args.filtered(OPT_undefined)) | |
handleUndefined(Arg->getValue()); | |
// Handle the `--export <sym>` options | |
// This works like --undefined but also exports the symbol if its found | |
for (auto *Arg : Args.filtered(OPT_export)) { | |
Symbol *Sym = handleUndefined(Arg->getValue()); | |
if (Sym && Sym->isDefined()) | |
Sym->ForceExport = true; | |
else if (!Config->AllowUndefined) | |
error(Twine("symbol exported via --export not found: ") + | |
Arg->getValue()); | |
} | |
if (!Config->Relocatable) { | |
// Add synthetic dummies for weak undefined functions. | |
handleWeakUndefines(); | |
if (!Config->Entry.empty()) { | |
EntrySym = handleUndefined(Config->Entry); | |
if (EntrySym && EntrySym->isDefined()) | |
EntrySym->ForceExport = true; | |
else | |
error("entry symbol not defined (pass --no-entry to supress): " + | |
Config->Entry); | |
} | |
// Make sure we have resolved all symbols. | |
if (!Config->AllowUndefined) | |
Symtab->reportRemainingUndefines(); | |
} | |
if (errorCount()) | |
return; | |
// Do link-time optimization if given files are LLVM bitcode files. | |
// This compiles bitcode files into real object files. | |
Symtab->addCombinedLTOObject(); | |
if (errorCount()) | |
return; | |
if (EntrySym) | |
EntrySym->setHidden(false); | |
if (errorCount()) | |
return; | |
// Do size optimizations: garbage collection | |
markLive(); | |
// Write the result to the file. | |
writeResult(); | |
} | |
diff --git a/wasm/Symbols.cpp b/wasm/Symbols.cpp | |
index 035c7f2d7..0e60262dc 100644 | |
--- a/wasm/Symbols.cpp | |
+++ b/wasm/Symbols.cpp | |
@@ -1,255 +1,259 @@ | |
//===- Symbols.cpp --------------------------------------------------------===// | |
// | |
// The LLVM Linker | |
// | |
// This file is distributed under the University of Illinois Open Source | |
// License. See LICENSE.TXT for details. | |
// | |
//===----------------------------------------------------------------------===// | |
#include "Symbols.h" | |
#include "Config.h" | |
#include "InputChunks.h" | |
#include "InputFiles.h" | |
#include "InputGlobal.h" | |
#include "OutputSegment.h" | |
#include "lld/Common/ErrorHandler.h" | |
#include "lld/Common/Strings.h" | |
#define DEBUG_TYPE "lld" | |
using namespace llvm; | |
using namespace llvm::wasm; | |
using namespace lld; | |
using namespace lld::wasm; | |
DefinedFunction *WasmSym::CallCtors; | |
DefinedData *WasmSym::DsoHandle; | |
DefinedData *WasmSym::DataEnd; | |
DefinedData *WasmSym::HeapBase; | |
DefinedGlobal *WasmSym::StackPointer; | |
WasmSymbolType Symbol::getWasmType() const { | |
if (isa<FunctionSymbol>(this)) | |
return WASM_SYMBOL_TYPE_FUNCTION; | |
if (isa<DataSymbol>(this)) | |
return WASM_SYMBOL_TYPE_DATA; | |
if (isa<GlobalSymbol>(this)) | |
return WASM_SYMBOL_TYPE_GLOBAL; | |
if (isa<SectionSymbol>(this)) | |
return WASM_SYMBOL_TYPE_SECTION; | |
llvm_unreachable("invalid symbol kind"); | |
} | |
InputChunk *Symbol::getChunk() const { | |
if (auto *F = dyn_cast<DefinedFunction>(this)) | |
return F->Function; | |
if (auto *D = dyn_cast<DefinedData>(this)) | |
return D->Segment; | |
return nullptr; | |
} | |
bool Symbol::isLive() const { | |
if (auto *G = dyn_cast<DefinedGlobal>(this)) | |
return G->Global->Live; | |
if (InputChunk *C = getChunk()) | |
return C->Live; | |
return Referenced; | |
} | |
void Symbol::markLive() { | |
if (auto *G = dyn_cast<DefinedGlobal>(this)) | |
G->Global->Live = true; | |
if (InputChunk *C = getChunk()) | |
C->Live = true; | |
Referenced = true; | |
} | |
uint32_t Symbol::getOutputSymbolIndex() const { | |
assert(OutputSymbolIndex != INVALID_INDEX); | |
return OutputSymbolIndex; | |
} | |
void Symbol::setOutputSymbolIndex(uint32_t Index) { | |
LLVM_DEBUG(dbgs() << "setOutputSymbolIndex " << Name << " -> " << Index | |
<< "\n"); | |
assert(OutputSymbolIndex == INVALID_INDEX); | |
OutputSymbolIndex = Index; | |
} | |
bool Symbol::isWeak() const { | |
return (Flags & WASM_SYMBOL_BINDING_MASK) == WASM_SYMBOL_BINDING_WEAK; | |
} | |
bool Symbol::isLocal() const { | |
return (Flags & WASM_SYMBOL_BINDING_MASK) == WASM_SYMBOL_BINDING_LOCAL; | |
} | |
bool Symbol::isHidden() const { | |
return (Flags & WASM_SYMBOL_VISIBILITY_MASK) == WASM_SYMBOL_VISIBILITY_HIDDEN; | |
} | |
void Symbol::setHidden(bool IsHidden) { | |
LLVM_DEBUG(dbgs() << "setHidden: " << Name << " -> " << IsHidden << "\n"); | |
Flags &= ~WASM_SYMBOL_VISIBILITY_MASK; | |
if (IsHidden) | |
Flags |= WASM_SYMBOL_VISIBILITY_HIDDEN; | |
else | |
Flags |= WASM_SYMBOL_VISIBILITY_DEFAULT; | |
} | |
bool Symbol::isExported() const { | |
if (!isDefined() || isLocal()) | |
return false; | |
if (ForceExport || Config->ExportAll) | |
return true; | |
if (Config->ExportDynamic && !isHidden()) | |
return true; | |
return false; | |
} | |
uint32_t FunctionSymbol::getFunctionIndex() const { | |
if (auto *F = dyn_cast<DefinedFunction>(this)) | |
return F->Function->getFunctionIndex(); | |
assert(FunctionIndex != INVALID_INDEX); | |
return FunctionIndex; | |
} | |
void FunctionSymbol::setFunctionIndex(uint32_t Index) { | |
LLVM_DEBUG(dbgs() << "setFunctionIndex " << Name << " -> " << Index << "\n"); | |
assert(FunctionIndex == INVALID_INDEX); | |
FunctionIndex = Index; | |
} | |
bool FunctionSymbol::hasFunctionIndex() const { | |
if (auto *F = dyn_cast<DefinedFunction>(this)) | |
return F->Function->hasFunctionIndex(); | |
return FunctionIndex != INVALID_INDEX; | |
} | |
uint32_t FunctionSymbol::getTableIndex() const { | |
if (auto *F = dyn_cast<DefinedFunction>(this)) | |
return F->Function->getTableIndex(); | |
assert(TableIndex != INVALID_INDEX); | |
return TableIndex; | |
} | |
bool FunctionSymbol::hasTableIndex() const { | |
if (auto *F = dyn_cast<DefinedFunction>(this)) | |
return F->Function->hasTableIndex(); | |
return TableIndex != INVALID_INDEX; | |
} | |
void FunctionSymbol::setTableIndex(uint32_t Index) { | |
// For imports, we set the table index here on the Symbol; for defined | |
// functions we set the index on the InputFunction so that we don't export | |
// the same thing twice (keeps the table size down). | |
if (auto *F = dyn_cast<DefinedFunction>(this)) { | |
F->Function->setTableIndex(Index); | |
return; | |
} | |
LLVM_DEBUG(dbgs() << "setTableIndex " << Name << " -> " << Index << "\n"); | |
assert(TableIndex == INVALID_INDEX); | |
TableIndex = Index; | |
} | |
DefinedFunction::DefinedFunction(StringRef Name, uint32_t Flags, InputFile *F, | |
InputFunction *Function) | |
: FunctionSymbol(Name, DefinedFunctionKind, Flags, F, | |
Function ? &Function->Signature : nullptr), | |
Function(Function) {} | |
uint32_t DefinedData::getVirtualAddress() const { | |
LLVM_DEBUG(dbgs() << "getVirtualAddress: " << getName() << "\n"); | |
if (Segment) | |
return Segment->OutputSeg->StartVA + Segment->OutputSegmentOffset + Offset; | |
return Offset; | |
} | |
void DefinedData::setVirtualAddress(uint32_t Value) { | |
LLVM_DEBUG(dbgs() << "setVirtualAddress " << Name << " -> " << Value << "\n"); | |
assert(!Segment); | |
Offset = Value; | |
} | |
uint32_t DefinedData::getOutputSegmentOffset() const { | |
LLVM_DEBUG(dbgs() << "getOutputSegmentOffset: " << getName() << "\n"); | |
return Segment->OutputSegmentOffset + Offset; | |
} | |
uint32_t DefinedData::getOutputSegmentIndex() const { | |
LLVM_DEBUG(dbgs() << "getOutputSegmentIndex: " << getName() << "\n"); | |
return Segment->OutputSeg->Index; | |
} | |
uint32_t GlobalSymbol::getGlobalIndex() const { | |
if (auto *F = dyn_cast<DefinedGlobal>(this)) | |
return F->Global->getGlobalIndex(); | |
assert(GlobalIndex != INVALID_INDEX); | |
return GlobalIndex; | |
} | |
void GlobalSymbol::setGlobalIndex(uint32_t Index) { | |
LLVM_DEBUG(dbgs() << "setGlobalIndex " << Name << " -> " << Index << "\n"); | |
assert(GlobalIndex == INVALID_INDEX); | |
GlobalIndex = Index; | |
} | |
bool GlobalSymbol::hasGlobalIndex() const { | |
if (auto *F = dyn_cast<DefinedGlobal>(this)) | |
return F->Global->hasGlobalIndex(); | |
return GlobalIndex != INVALID_INDEX; | |
} | |
DefinedGlobal::DefinedGlobal(StringRef Name, uint32_t Flags, InputFile *File, | |
InputGlobal *Global) | |
: GlobalSymbol(Name, DefinedGlobalKind, Flags, File, | |
Global ? &Global->getType() : nullptr), | |
Global(Global) {} | |
uint32_t SectionSymbol::getOutputSectionIndex() const { | |
LLVM_DEBUG(dbgs() << "getOutputSectionIndex: " << getName() << "\n"); | |
assert(OutputSectionIndex != INVALID_INDEX); | |
return OutputSectionIndex; | |
} | |
void SectionSymbol::setOutputSectionIndex(uint32_t Index) { | |
LLVM_DEBUG(dbgs() << "setOutputSectionIndex: " << getName() << " -> " << Index | |
<< "\n"); | |
assert(Index != INVALID_INDEX); | |
OutputSectionIndex = Index; | |
} | |
void LazySymbol::fetch() { cast<ArchiveFile>(File)->addMember(&ArchiveSymbol); } | |
std::string lld::toString(const wasm::Symbol &Sym) { | |
+ return lld::maybeDemangleSymbol(Sym.getName()); | |
+} | |
+ | |
+std::string lld::maybeDemangleSymbol(StringRef Name) { | |
if (Config->Demangle) | |
- if (Optional<std::string> S = demangleItanium(Sym.getName())) | |
+ if (Optional<std::string> S = demangleItanium(Name)) | |
return *S; | |
- return Sym.getName(); | |
+ return Name; | |
} | |
std::string lld::toString(wasm::Symbol::Kind Kind) { | |
switch (Kind) { | |
case wasm::Symbol::DefinedFunctionKind: | |
return "DefinedFunction"; | |
case wasm::Symbol::DefinedDataKind: | |
return "DefinedData"; | |
case wasm::Symbol::DefinedGlobalKind: | |
return "DefinedGlobal"; | |
case wasm::Symbol::UndefinedFunctionKind: | |
return "UndefinedFunction"; | |
case wasm::Symbol::UndefinedDataKind: | |
return "UndefinedData"; | |
case wasm::Symbol::UndefinedGlobalKind: | |
return "UndefinedGlobal"; | |
case wasm::Symbol::LazyKind: | |
return "LazyKind"; | |
case wasm::Symbol::SectionKind: | |
return "SectionKind"; | |
} | |
llvm_unreachable("invalid symbol kind"); | |
} | |
diff --git a/wasm/Symbols.h b/wasm/Symbols.h | |
index 815cc97d2..c8c47257c 100644 | |
--- a/wasm/Symbols.h | |
+++ b/wasm/Symbols.h | |
@@ -1,355 +1,356 @@ | |
//===- Symbols.h ------------------------------------------------*- C++ -*-===// | |
// | |
// The LLVM Linker | |
// | |
// This file is distributed under the University of Illinois Open Source | |
// License. See LICENSE.TXT for details. | |
// | |
//===----------------------------------------------------------------------===// | |
#ifndef LLD_WASM_SYMBOLS_H | |
#define LLD_WASM_SYMBOLS_H | |
#include "Config.h" | |
#include "lld/Common/LLVM.h" | |
#include "llvm/Object/Archive.h" | |
#include "llvm/Object/Wasm.h" | |
using llvm::object::Archive; | |
using llvm::object::WasmSymbol; | |
using llvm::wasm::WasmGlobal; | |
using llvm::wasm::WasmGlobalType; | |
using llvm::wasm::WasmSignature; | |
using llvm::wasm::WasmSymbolType; | |
namespace lld { | |
namespace wasm { | |
class InputFile; | |
class InputChunk; | |
class InputSegment; | |
class InputFunction; | |
class InputGlobal; | |
class InputSection; | |
#define INVALID_INDEX UINT32_MAX | |
// The base class for real symbol classes. | |
class Symbol { | |
public: | |
enum Kind { | |
DefinedFunctionKind, | |
DefinedDataKind, | |
DefinedGlobalKind, | |
SectionKind, | |
UndefinedFunctionKind, | |
UndefinedDataKind, | |
UndefinedGlobalKind, | |
LazyKind, | |
}; | |
Kind kind() const { return SymbolKind; } | |
bool isDefined() const { | |
return SymbolKind == DefinedFunctionKind || SymbolKind == DefinedDataKind || | |
SymbolKind == DefinedGlobalKind || SymbolKind == SectionKind; | |
} | |
bool isUndefined() const { | |
return SymbolKind == UndefinedFunctionKind || | |
SymbolKind == UndefinedDataKind || SymbolKind == UndefinedGlobalKind; | |
} | |
bool isLazy() const { return SymbolKind == LazyKind; } | |
bool isLocal() const; | |
bool isWeak() const; | |
bool isHidden() const; | |
// Returns the symbol name. | |
StringRef getName() const { return Name; } | |
// Returns the file from which this symbol was created. | |
InputFile *getFile() const { return File; } | |
InputChunk *getChunk() const; | |
// Indicates that the section or import for this symbol will be included in | |
// the final image. | |
bool isLive() const; | |
// Marks the symbol's InputChunk as Live, so that it will be included in the | |
// final image. | |
void markLive(); | |
void setHidden(bool IsHidden); | |
// Get/set the index in the output symbol table. This is only used for | |
// relocatable output. | |
uint32_t getOutputSymbolIndex() const; | |
void setOutputSymbolIndex(uint32_t Index); | |
WasmSymbolType getWasmType() const; | |
bool isExported() const; | |
// True if this symbol was referenced by a regular (non-bitcode) object. | |
unsigned IsUsedInRegularObj : 1; | |
unsigned ForceExport : 1; | |
protected: | |
Symbol(StringRef Name, Kind K, uint32_t Flags, InputFile *F) | |
: IsUsedInRegularObj(false), ForceExport(false), Name(Name), | |
SymbolKind(K), Flags(Flags), File(F), Referenced(!Config->GcSections) {} | |
StringRef Name; | |
Kind SymbolKind; | |
uint32_t Flags; | |
InputFile *File; | |
uint32_t OutputSymbolIndex = INVALID_INDEX; | |
bool Referenced; | |
}; | |
class FunctionSymbol : public Symbol { | |
public: | |
static bool classof(const Symbol *S) { | |
return S->kind() == DefinedFunctionKind || | |
S->kind() == UndefinedFunctionKind; | |
} | |
// Get/set the table index | |
void setTableIndex(uint32_t Index); | |
uint32_t getTableIndex() const; | |
bool hasTableIndex() const; | |
// Get/set the function index | |
uint32_t getFunctionIndex() const; | |
void setFunctionIndex(uint32_t Index); | |
bool hasFunctionIndex() const; | |
const WasmSignature *FunctionType; | |
protected: | |
FunctionSymbol(StringRef Name, Kind K, uint32_t Flags, InputFile *F, | |
const WasmSignature *Type) | |
: Symbol(Name, K, Flags, F), FunctionType(Type) {} | |
uint32_t TableIndex = INVALID_INDEX; | |
uint32_t FunctionIndex = INVALID_INDEX; | |
}; | |
class DefinedFunction : public FunctionSymbol { | |
public: | |
DefinedFunction(StringRef Name, uint32_t Flags, InputFile *F, | |
InputFunction *Function); | |
static bool classof(const Symbol *S) { | |
return S->kind() == DefinedFunctionKind; | |
} | |
InputFunction *Function; | |
}; | |
class UndefinedFunction : public FunctionSymbol { | |
public: | |
UndefinedFunction(StringRef Name, uint32_t Flags, InputFile *File = nullptr, | |
const WasmSignature *Type = nullptr) | |
: FunctionSymbol(Name, UndefinedFunctionKind, Flags, File, Type) {} | |
static bool classof(const Symbol *S) { | |
return S->kind() == UndefinedFunctionKind; | |
} | |
}; | |
class SectionSymbol : public Symbol { | |
public: | |
static bool classof(const Symbol *S) { return S->kind() == SectionKind; } | |
SectionSymbol(StringRef Name, uint32_t Flags, const InputSection *S, | |
InputFile *F = nullptr) | |
: Symbol(Name, SectionKind, Flags, F), Section(S) {} | |
const InputSection *Section; | |
uint32_t getOutputSectionIndex() const; | |
void setOutputSectionIndex(uint32_t Index); | |
protected: | |
uint32_t OutputSectionIndex = INVALID_INDEX; | |
}; | |
class DataSymbol : public Symbol { | |
public: | |
static bool classof(const Symbol *S) { | |
return S->kind() == DefinedDataKind || S->kind() == UndefinedDataKind; | |
} | |
protected: | |
DataSymbol(StringRef Name, Kind K, uint32_t Flags, InputFile *F) | |
: Symbol(Name, K, Flags, F) {} | |
}; | |
class DefinedData : public DataSymbol { | |
public: | |
// Constructor for regular data symbols originating from input files. | |
DefinedData(StringRef Name, uint32_t Flags, InputFile *F, | |
InputSegment *Segment, uint32_t Offset, uint32_t Size) | |
: DataSymbol(Name, DefinedDataKind, Flags, F), Segment(Segment), | |
Offset(Offset), Size(Size) {} | |
// Constructor for linker synthetic data symbols. | |
DefinedData(StringRef Name, uint32_t Flags) | |
: DataSymbol(Name, DefinedDataKind, Flags, nullptr) {} | |
static bool classof(const Symbol *S) { return S->kind() == DefinedDataKind; } | |
// Returns the output virtual address of a defined data symbol. | |
uint32_t getVirtualAddress() const; | |
void setVirtualAddress(uint32_t VA); | |
// Returns the offset of a defined data symbol within its OutputSegment. | |
uint32_t getOutputSegmentOffset() const; | |
uint32_t getOutputSegmentIndex() const; | |
uint32_t getSize() const { return Size; } | |
InputSegment *Segment = nullptr; | |
protected: | |
uint32_t Offset = 0; | |
uint32_t Size = 0; | |
}; | |
class UndefinedData : public DataSymbol { | |
public: | |
UndefinedData(StringRef Name, uint32_t Flags, InputFile *File = nullptr) | |
: DataSymbol(Name, UndefinedDataKind, Flags, File) {} | |
static bool classof(const Symbol *S) { | |
return S->kind() == UndefinedDataKind; | |
} | |
}; | |
class GlobalSymbol : public Symbol { | |
public: | |
static bool classof(const Symbol *S) { | |
return S->kind() == DefinedGlobalKind || S->kind() == UndefinedGlobalKind; | |
} | |
const WasmGlobalType *getGlobalType() const { return GlobalType; } | |
// Get/set the global index | |
uint32_t getGlobalIndex() const; | |
void setGlobalIndex(uint32_t Index); | |
bool hasGlobalIndex() const; | |
protected: | |
GlobalSymbol(StringRef Name, Kind K, uint32_t Flags, InputFile *F, | |
const WasmGlobalType *GlobalType) | |
: Symbol(Name, K, Flags, F), GlobalType(GlobalType) {} | |
// Explicit function type, needed for undefined or synthetic functions only. | |
// For regular defined globals this information comes from the InputChunk. | |
const WasmGlobalType *GlobalType; | |
uint32_t GlobalIndex = INVALID_INDEX; | |
}; | |
class DefinedGlobal : public GlobalSymbol { | |
public: | |
DefinedGlobal(StringRef Name, uint32_t Flags, InputFile *File, | |
InputGlobal *Global); | |
static bool classof(const Symbol *S) { | |
return S->kind() == DefinedGlobalKind; | |
} | |
InputGlobal *Global; | |
}; | |
class UndefinedGlobal : public GlobalSymbol { | |
public: | |
UndefinedGlobal(StringRef Name, uint32_t Flags, InputFile *File = nullptr, | |
const WasmGlobalType *Type = nullptr) | |
: GlobalSymbol(Name, UndefinedGlobalKind, Flags, File, Type) {} | |
static bool classof(const Symbol *S) { | |
return S->kind() == UndefinedGlobalKind; | |
} | |
}; | |
class LazySymbol : public Symbol { | |
public: | |
LazySymbol(StringRef Name, InputFile *File, const Archive::Symbol &Sym) | |
: Symbol(Name, LazyKind, 0, File), ArchiveSymbol(Sym) {} | |
static bool classof(const Symbol *S) { return S->kind() == LazyKind; } | |
void fetch(); | |
private: | |
Archive::Symbol ArchiveSymbol; | |
}; | |
// linker-generated symbols | |
struct WasmSym { | |
// __stack_pointer | |
// Global that holds the address of the top of the explicit value stack in | |
// linear memory. | |
static DefinedGlobal *StackPointer; | |
// __data_end | |
// Symbol marking the end of the data and bss. | |
static DefinedData *DataEnd; | |
// __heap_base | |
// Symbol marking the end of the data, bss and explicit stack. Any linear | |
// memory following this address is not used by the linked code and can | |
// therefore be used as a backing store for brk()/malloc() implementations. | |
static DefinedData *HeapBase; | |
// __wasm_call_ctors | |
// Function that directly calls all ctors in priority order. | |
static DefinedFunction *CallCtors; | |
// __dso_handle | |
// Symbol used in calls to __cxa_atexit to determine current DLL | |
static DefinedData *DsoHandle; | |
}; | |
// A buffer class that is large enough to hold any Symbol-derived | |
// object. We allocate memory using this class and instantiate a symbol | |
// using the placement new. | |
union SymbolUnion { | |
alignas(DefinedFunction) char A[sizeof(DefinedFunction)]; | |
alignas(DefinedData) char B[sizeof(DefinedData)]; | |
alignas(DefinedGlobal) char C[sizeof(DefinedGlobal)]; | |
alignas(LazySymbol) char D[sizeof(LazySymbol)]; | |
alignas(UndefinedFunction) char E[sizeof(UndefinedFunction)]; | |
alignas(UndefinedData) char F[sizeof(UndefinedData)]; | |
alignas(UndefinedGlobal) char G[sizeof(UndefinedGlobal)]; | |
alignas(SectionSymbol) char I[sizeof(SectionSymbol)]; | |
}; | |
template <typename T, typename... ArgT> | |
T *replaceSymbol(Symbol *S, ArgT &&... Arg) { | |
static_assert(std::is_trivially_destructible<T>(), | |
"Symbol types must be trivially destructible"); | |
static_assert(sizeof(T) <= sizeof(SymbolUnion), "Symbol too small"); | |
static_assert(alignof(T) <= alignof(SymbolUnion), | |
"SymbolUnion not aligned enough"); | |
assert(static_cast<Symbol *>(static_cast<T *>(nullptr)) == nullptr && | |
"Not a Symbol"); | |
Symbol SymCopy = *S; | |
T *S2 = new (S) T(std::forward<ArgT>(Arg)...); | |
S2->IsUsedInRegularObj = SymCopy.IsUsedInRegularObj; | |
S2->ForceExport = SymCopy.ForceExport; | |
return S2; | |
} | |
} // namespace wasm | |
// Returns a symbol name for an error message. | |
std::string toString(const wasm::Symbol &Sym); | |
std::string toString(wasm::Symbol::Kind Kind); | |
+std::string maybeDemangleSymbol(StringRef Name); | |
} // namespace lld | |
#endif | |
diff --git a/wasm/Writer.cpp b/wasm/Writer.cpp | |
index c26ae5331..cb413de67 100644 | |
--- a/wasm/Writer.cpp | |
+++ b/wasm/Writer.cpp | |
@@ -1,1089 +1,1087 @@ | |
//===- Writer.cpp ---------------------------------------------------------===// | |
// | |
// The LLVM Linker | |
// | |
// This file is distributed under the University of Illinois Open Source | |
// License. See LICENSE.TXT for details. | |
// | |
//===----------------------------------------------------------------------===// | |
#include "Writer.h" | |
#include "Config.h" | |
#include "InputChunks.h" | |
#include "InputGlobal.h" | |
#include "OutputSections.h" | |
#include "OutputSegment.h" | |
#include "SymbolTable.h" | |
#include "WriterUtils.h" | |
#include "lld/Common/ErrorHandler.h" | |
#include "lld/Common/Memory.h" | |
#include "lld/Common/Strings.h" | |
#include "lld/Common/Threads.h" | |
#include "llvm/ADT/DenseSet.h" | |
#include "llvm/ADT/StringMap.h" | |
#include "llvm/BinaryFormat/Wasm.h" | |
#include "llvm/Object/WasmTraits.h" | |
#include "llvm/Support/FileOutputBuffer.h" | |
#include "llvm/Support/Format.h" | |
#include "llvm/Support/FormatVariadic.h" | |
#include "llvm/Support/LEB128.h" | |
#include <cstdarg> | |
#include <map> | |
#define DEBUG_TYPE "lld" | |
using namespace llvm; | |
using namespace llvm::wasm; | |
using namespace lld; | |
using namespace lld::wasm; | |
static constexpr int kStackAlignment = 16; | |
static constexpr int kInitialTableOffset = 1; | |
static constexpr const char *kFunctionTableName = "__indirect_function_table"; | |
namespace { | |
// An init entry to be written to either the synthetic init func or the | |
// linking metadata. | |
struct WasmInitEntry { | |
const FunctionSymbol *Sym; | |
uint32_t Priority; | |
}; | |
// The writer writes a SymbolTable result to a file. | |
class Writer { | |
public: | |
void run(); | |
private: | |
void openFile(); | |
uint32_t lookupType(const WasmSignature &Sig); | |
uint32_t registerType(const WasmSignature &Sig); | |
void createCtorFunction(); | |
void calculateInitFunctions(); | |
void assignIndexes(); | |
void calculateImports(); | |
void calculateExports(); | |
void calculateCustomSections(); | |
void assignSymtab(); | |
void calculateTypes(); | |
void createOutputSegments(); | |
void layoutMemory(); | |
void createHeader(); | |
void createSections(); | |
SyntheticSection *createSyntheticSection(uint32_t Type, StringRef Name = ""); | |
// Builtin sections | |
void createTypeSection(); | |
void createFunctionSection(); | |
void createTableSection(); | |
void createGlobalSection(); | |
void createExportSection(); | |
void createImportSection(); | |
void createMemorySection(); | |
void createElemSection(); | |
void createCodeSection(); | |
void createDataSection(); | |
void createCustomSections(); | |
// Custom sections | |
void createRelocSections(); | |
void createLinkingSection(); | |
void createNameSection(); | |
void writeHeader(); | |
void writeSections(); | |
uint64_t FileSize = 0; | |
uint32_t NumMemoryPages = 0; | |
uint32_t MaxMemoryPages = 0; | |
std::vector<const WasmSignature *> Types; | |
DenseMap<WasmSignature, int32_t> TypeIndices; | |
std::vector<const Symbol *> ImportedSymbols; | |
unsigned NumImportedFunctions = 0; | |
unsigned NumImportedGlobals = 0; | |
std::vector<WasmExport> Exports; | |
std::vector<const DefinedData *> DefinedFakeGlobals; | |
std::vector<InputGlobal *> InputGlobals; | |
std::vector<InputFunction *> InputFunctions; | |
std::vector<const FunctionSymbol *> IndirectFunctions; | |
std::vector<const Symbol *> SymtabEntries; | |
std::vector<WasmInitEntry> InitFunctions; | |
llvm::StringMap<std::vector<InputSection *>> CustomSectionMapping; | |
llvm::StringMap<SectionSymbol *> CustomSectionSymbols; | |
// Elements that are used to construct the final output | |
std::string Header; | |
std::vector<OutputSection *> OutputSections; | |
std::unique_ptr<FileOutputBuffer> Buffer; | |
std::vector<OutputSegment *> Segments; | |
llvm::SmallDenseMap<StringRef, OutputSegment *> SegmentMap; | |
}; | |
} // anonymous namespace | |
void Writer::createImportSection() { | |
uint32_t NumImports = ImportedSymbols.size(); | |
if (Config->ImportMemory) | |
++NumImports; | |
if (Config->ImportTable) | |
++NumImports; | |
if (NumImports == 0) | |
return; | |
SyntheticSection *Section = createSyntheticSection(WASM_SEC_IMPORT); | |
raw_ostream &OS = Section->getStream(); | |
writeUleb128(OS, NumImports, "import count"); | |
if (Config->ImportMemory) { | |
WasmImport Import; | |
Import.Module = "env"; | |
Import.Field = "memory"; | |
Import.Kind = WASM_EXTERNAL_MEMORY; | |
Import.Memory.Flags = 0; | |
Import.Memory.Initial = NumMemoryPages; | |
if (MaxMemoryPages != 0) { | |
Import.Memory.Flags |= WASM_LIMITS_FLAG_HAS_MAX; | |
Import.Memory.Maximum = MaxMemoryPages; | |
} | |
if (Config->SharedMemory) | |
Import.Memory.Flags |= WASM_LIMITS_FLAG_IS_SHARED; | |
writeImport(OS, Import); | |
} | |
if (Config->ImportTable) { | |
uint32_t TableSize = kInitialTableOffset + IndirectFunctions.size(); | |
WasmImport Import; | |
Import.Module = "env"; | |
Import.Field = kFunctionTableName; | |
Import.Kind = WASM_EXTERNAL_TABLE; | |
Import.Table.ElemType = WASM_TYPE_ANYFUNC; | |
Import.Table.Limits = {WASM_LIMITS_FLAG_HAS_MAX, TableSize, TableSize}; | |
writeImport(OS, Import); | |
} | |
for (const Symbol *Sym : ImportedSymbols) { | |
WasmImport Import; | |
Import.Module = "env"; | |
Import.Field = Sym->getName(); | |
if (auto *FunctionSym = dyn_cast<FunctionSymbol>(Sym)) { | |
Import.Kind = WASM_EXTERNAL_FUNCTION; | |
Import.SigIndex = lookupType(*FunctionSym->FunctionType); | |
} else { | |
auto *GlobalSym = cast<GlobalSymbol>(Sym); | |
Import.Kind = WASM_EXTERNAL_GLOBAL; | |
Import.Global = *GlobalSym->getGlobalType(); | |
} | |
writeImport(OS, Import); | |
} | |
} | |
void Writer::createTypeSection() { | |
SyntheticSection *Section = createSyntheticSection(WASM_SEC_TYPE); | |
raw_ostream &OS = Section->getStream(); | |
writeUleb128(OS, Types.size(), "type count"); | |
for (const WasmSignature *Sig : Types) | |
writeSig(OS, *Sig); | |
} | |
void Writer::createFunctionSection() { | |
if (InputFunctions.empty()) | |
return; | |
SyntheticSection *Section = createSyntheticSection(WASM_SEC_FUNCTION); | |
raw_ostream &OS = Section->getStream(); | |
writeUleb128(OS, InputFunctions.size(), "function count"); | |
for (const InputFunction *Func : InputFunctions) | |
writeUleb128(OS, lookupType(Func->Signature), "sig index"); | |
} | |
void Writer::createMemorySection() { | |
if (Config->ImportMemory) | |
return; | |
SyntheticSection *Section = createSyntheticSection(WASM_SEC_MEMORY); | |
raw_ostream &OS = Section->getStream(); | |
bool HasMax = MaxMemoryPages != 0; | |
writeUleb128(OS, 1, "memory count"); | |
unsigned Flags = 0; | |
if (HasMax) | |
Flags |= WASM_LIMITS_FLAG_HAS_MAX; | |
if (Config->SharedMemory) | |
Flags |= WASM_LIMITS_FLAG_IS_SHARED; | |
writeUleb128(OS, Flags, "memory limits flags"); | |
writeUleb128(OS, NumMemoryPages, "initial pages"); | |
if (HasMax) | |
writeUleb128(OS, MaxMemoryPages, "max pages"); | |
} | |
void Writer::createGlobalSection() { | |
unsigned NumGlobals = InputGlobals.size() + DefinedFakeGlobals.size(); | |
if (NumGlobals == 0) | |
return; | |
SyntheticSection *Section = createSyntheticSection(WASM_SEC_GLOBAL); | |
raw_ostream &OS = Section->getStream(); | |
writeUleb128(OS, NumGlobals, "global count"); | |
for (const InputGlobal *G : InputGlobals) | |
writeGlobal(OS, G->Global); | |
for (const DefinedData *Sym : DefinedFakeGlobals) { | |
WasmGlobal Global; | |
Global.Type = {WASM_TYPE_I32, false}; | |
Global.InitExpr.Opcode = WASM_OPCODE_I32_CONST; | |
Global.InitExpr.Value.Int32 = Sym->getVirtualAddress(); | |
writeGlobal(OS, Global); | |
} | |
} | |
void Writer::createTableSection() { | |
if (Config->ImportTable) | |
return; | |
// Always output a table section (or table import), even if there are no | |
// indirect calls. There are two reasons for this: | |
// 1. For executables it is useful to have an empty table slot at 0 | |
// which can be filled with a null function call handler. | |
// 2. If we don't do this, any program that contains a call_indirect but | |
// no address-taken function will fail at validation time since it is | |
// a validation error to include a call_indirect instruction if there | |
// is not table. | |
uint32_t TableSize = kInitialTableOffset + IndirectFunctions.size(); | |
SyntheticSection *Section = createSyntheticSection(WASM_SEC_TABLE); | |
raw_ostream &OS = Section->getStream(); | |
writeUleb128(OS, 1, "table count"); | |
WasmLimits Limits = {WASM_LIMITS_FLAG_HAS_MAX, TableSize, TableSize}; | |
writeTableType(OS, WasmTable{WASM_TYPE_ANYFUNC, Limits}); | |
} | |
void Writer::createExportSection() { | |
if (!Exports.size()) | |
return; | |
SyntheticSection *Section = createSyntheticSection(WASM_SEC_EXPORT); | |
raw_ostream &OS = Section->getStream(); | |
writeUleb128(OS, Exports.size(), "export count"); | |
for (const WasmExport &Export : Exports) | |
writeExport(OS, Export); | |
} | |
void Writer::calculateCustomSections() { | |
log("calculateCustomSections"); | |
bool StripDebug = Config->StripDebug || Config->StripAll; | |
for (ObjFile *File : Symtab->ObjectFiles) { | |
for (InputSection *Section : File->CustomSections) { | |
StringRef Name = Section->getName(); | |
// These custom sections are known the linker and synthesized rather than | |
// blindly copied | |
if (Name == "linking" || Name == "name" || Name.startswith("reloc.")) | |
continue; | |
// .. or it is a debug section | |
if (StripDebug && Name.startswith(".debug_")) | |
continue; | |
CustomSectionMapping[Name].push_back(Section); | |
} | |
} | |
} | |
void Writer::createCustomSections() { | |
log("createCustomSections"); | |
for (auto &Pair : CustomSectionMapping) { | |
StringRef Name = Pair.first(); | |
auto P = CustomSectionSymbols.find(Name); | |
if (P != CustomSectionSymbols.end()) { | |
uint32_t SectionIndex = OutputSections.size(); | |
P->second->setOutputSectionIndex(SectionIndex); | |
} | |
LLVM_DEBUG(dbgs() << "createCustomSection: " << Name << "\n"); | |
OutputSections.push_back(make<CustomSection>(Name, Pair.second)); | |
} | |
} | |
void Writer::createElemSection() { | |
if (IndirectFunctions.empty()) | |
return; | |
SyntheticSection *Section = createSyntheticSection(WASM_SEC_ELEM); | |
raw_ostream &OS = Section->getStream(); | |
writeUleb128(OS, 1, "segment count"); | |
writeUleb128(OS, 0, "table index"); | |
WasmInitExpr InitExpr; | |
InitExpr.Opcode = WASM_OPCODE_I32_CONST; | |
InitExpr.Value.Int32 = kInitialTableOffset; | |
writeInitExpr(OS, InitExpr); | |
writeUleb128(OS, IndirectFunctions.size(), "elem count"); | |
uint32_t TableIndex = kInitialTableOffset; | |
for (const FunctionSymbol *Sym : IndirectFunctions) { | |
assert(Sym->getTableIndex() == TableIndex); | |
writeUleb128(OS, Sym->getFunctionIndex(), "function index"); | |
++TableIndex; | |
} | |
} | |
void Writer::createCodeSection() { | |
if (InputFunctions.empty()) | |
return; | |
log("createCodeSection"); | |
auto Section = make<CodeSection>(InputFunctions); | |
OutputSections.push_back(Section); | |
} | |
void Writer::createDataSection() { | |
if (!Segments.size()) | |
return; | |
log("createDataSection"); | |
auto Section = make<DataSection>(Segments); | |
OutputSections.push_back(Section); | |
} | |
// Create relocations sections in the final output. | |
// These are only created when relocatable output is requested. | |
void Writer::createRelocSections() { | |
log("createRelocSections"); | |
// Don't use iterator here since we are adding to OutputSection | |
size_t OrigSize = OutputSections.size(); | |
for (size_t I = 0; I < OrigSize; I++) { | |
OutputSection *OSec = OutputSections[I]; | |
uint32_t Count = OSec->numRelocations(); | |
if (!Count) | |
continue; | |
StringRef Name; | |
if (OSec->Type == WASM_SEC_DATA) | |
Name = "reloc.DATA"; | |
else if (OSec->Type == WASM_SEC_CODE) | |
Name = "reloc.CODE"; | |
else if (OSec->Type == WASM_SEC_CUSTOM) | |
Name = Saver.save("reloc." + OSec->Name); | |
else | |
llvm_unreachable( | |
"relocations only supported for code, data, or custom sections"); | |
SyntheticSection *Section = createSyntheticSection(WASM_SEC_CUSTOM, Name); | |
raw_ostream &OS = Section->getStream(); | |
writeUleb128(OS, I, "reloc section"); | |
writeUleb128(OS, Count, "reloc count"); | |
OSec->writeRelocations(OS); | |
} | |
} | |
static uint32_t getWasmFlags(const Symbol *Sym) { | |
uint32_t Flags = 0; | |
if (Sym->isLocal()) | |
Flags |= WASM_SYMBOL_BINDING_LOCAL; | |
if (Sym->isWeak()) | |
Flags |= WASM_SYMBOL_BINDING_WEAK; | |
if (Sym->isHidden()) | |
Flags |= WASM_SYMBOL_VISIBILITY_HIDDEN; | |
if (Sym->isUndefined()) | |
Flags |= WASM_SYMBOL_UNDEFINED; | |
return Flags; | |
} | |
// Some synthetic sections (e.g. "name" and "linking") have subsections. | |
// Just like the synthetic sections themselves these need to be created before | |
// they can be written out (since they are preceded by their length). This | |
// class is used to create subsections and then write them into the stream | |
// of the parent section. | |
class SubSection { | |
public: | |
explicit SubSection(uint32_t Type) : Type(Type) {} | |
void writeTo(raw_ostream &To) { | |
OS.flush(); | |
writeUleb128(To, Type, "subsection type"); | |
writeUleb128(To, Body.size(), "subsection size"); | |
To.write(Body.data(), Body.size()); | |
} | |
private: | |
uint32_t Type; | |
std::string Body; | |
public: | |
raw_string_ostream OS{Body}; | |
}; | |
// Create the custom "linking" section containing linker metadata. | |
// This is only created when relocatable output is requested. | |
void Writer::createLinkingSection() { | |
SyntheticSection *Section = | |
createSyntheticSection(WASM_SEC_CUSTOM, "linking"); | |
raw_ostream &OS = Section->getStream(); | |
writeUleb128(OS, WasmMetadataVersion, "Version"); | |
if (!SymtabEntries.empty()) { | |
SubSection Sub(WASM_SYMBOL_TABLE); | |
writeUleb128(Sub.OS, SymtabEntries.size(), "num symbols"); | |
for (const Symbol *Sym : SymtabEntries) { | |
assert(Sym->isDefined() || Sym->isUndefined()); | |
WasmSymbolType Kind = Sym->getWasmType(); | |
uint32_t Flags = getWasmFlags(Sym); | |
writeU8(Sub.OS, Kind, "sym kind"); | |
writeUleb128(Sub.OS, Flags, "sym flags"); | |
if (auto *F = dyn_cast<FunctionSymbol>(Sym)) { | |
writeUleb128(Sub.OS, F->getFunctionIndex(), "index"); | |
if (Sym->isDefined()) | |
writeStr(Sub.OS, Sym->getName(), "sym name"); | |
} else if (auto *G = dyn_cast<GlobalSymbol>(Sym)) { | |
writeUleb128(Sub.OS, G->getGlobalIndex(), "index"); | |
if (Sym->isDefined()) | |
writeStr(Sub.OS, Sym->getName(), "sym name"); | |
} else if (isa<DataSymbol>(Sym)) { | |
writeStr(Sub.OS, Sym->getName(), "sym name"); | |
if (auto *DataSym = dyn_cast<DefinedData>(Sym)) { | |
writeUleb128(Sub.OS, DataSym->getOutputSegmentIndex(), "index"); | |
writeUleb128(Sub.OS, DataSym->getOutputSegmentOffset(), | |
"data offset"); | |
writeUleb128(Sub.OS, DataSym->getSize(), "data size"); | |
} | |
} else { | |
auto *S = cast<SectionSymbol>(Sym); | |
writeUleb128(Sub.OS, S->getOutputSectionIndex(), "sym section index"); | |
} | |
} | |
Sub.writeTo(OS); | |
} | |
if (Segments.size()) { | |
SubSection Sub(WASM_SEGMENT_INFO); | |
writeUleb128(Sub.OS, Segments.size(), "num data segments"); | |
for (const OutputSegment *S : Segments) { | |
writeStr(Sub.OS, S->Name, "segment name"); | |
writeUleb128(Sub.OS, S->Alignment, "alignment"); | |
writeUleb128(Sub.OS, 0, "flags"); | |
} | |
Sub.writeTo(OS); | |
} | |
if (!InitFunctions.empty()) { | |
SubSection Sub(WASM_INIT_FUNCS); | |
writeUleb128(Sub.OS, InitFunctions.size(), "num init functions"); | |
for (const WasmInitEntry &F : InitFunctions) { | |
writeUleb128(Sub.OS, F.Priority, "priority"); | |
writeUleb128(Sub.OS, F.Sym->getOutputSymbolIndex(), "function index"); | |
} | |
Sub.writeTo(OS); | |
} | |
struct ComdatEntry { | |
unsigned Kind; | |
uint32_t Index; | |
}; | |
std::map<StringRef, std::vector<ComdatEntry>> Comdats; | |
for (const InputFunction *F : InputFunctions) { | |
StringRef Comdat = F->getComdatName(); | |
if (!Comdat.empty()) | |
Comdats[Comdat].emplace_back( | |
ComdatEntry{WASM_COMDAT_FUNCTION, F->getFunctionIndex()}); | |
} | |
for (uint32_t I = 0; I < Segments.size(); ++I) { | |
const auto &InputSegments = Segments[I]->InputSegments; | |
if (InputSegments.empty()) | |
continue; | |
StringRef Comdat = InputSegments[0]->getComdatName(); | |
#ifndef NDEBUG | |
for (const InputSegment *IS : InputSegments) | |
assert(IS->getComdatName() == Comdat); | |
#endif | |
if (!Comdat.empty()) | |
Comdats[Comdat].emplace_back(ComdatEntry{WASM_COMDAT_DATA, I}); | |
} | |
if (!Comdats.empty()) { | |
SubSection Sub(WASM_COMDAT_INFO); | |
writeUleb128(Sub.OS, Comdats.size(), "num comdats"); | |
for (const auto &C : Comdats) { | |
writeStr(Sub.OS, C.first, "comdat name"); | |
writeUleb128(Sub.OS, 0, "comdat flags"); // flags for future use | |
writeUleb128(Sub.OS, C.second.size(), "num entries"); | |
for (const ComdatEntry &Entry : C.second) { | |
writeU8(Sub.OS, Entry.Kind, "entry kind"); | |
writeUleb128(Sub.OS, Entry.Index, "entry index"); | |
} | |
} | |
Sub.writeTo(OS); | |
} | |
} | |
// Create the custom "name" section containing debug symbol names. | |
void Writer::createNameSection() { | |
unsigned NumNames = NumImportedFunctions; | |
for (const InputFunction *F : InputFunctions) | |
if (!F->getName().empty() || !F->getDebugName().empty()) | |
++NumNames; | |
if (NumNames == 0) | |
return; | |
SyntheticSection *Section = createSyntheticSection(WASM_SEC_CUSTOM, "name"); | |
SubSection Sub(WASM_NAMES_FUNCTION); | |
writeUleb128(Sub.OS, NumNames, "name count"); | |
// Names must appear in function index order. As it happens ImportedSymbols | |
// and InputFunctions are numbered in order with imported functions coming | |
// first. | |
for (const Symbol *S : ImportedSymbols) { | |
if (auto *F = dyn_cast<FunctionSymbol>(S)) { | |
writeUleb128(Sub.OS, F->getFunctionIndex(), "func index"); | |
- Optional<std::string> Name = demangleItanium(F->getName()); | |
- writeStr(Sub.OS, Name ? StringRef(*Name) : F->getName(), "symbol name"); | |
+ writeStr(Sub.OS, toString(*S), "symbol name"); | |
} | |
} | |
for (const InputFunction *F : InputFunctions) { | |
if (!F->getName().empty()) { | |
writeUleb128(Sub.OS, F->getFunctionIndex(), "func index"); | |
if (!F->getDebugName().empty()) { | |
writeStr(Sub.OS, F->getDebugName(), "symbol name"); | |
} else { | |
- Optional<std::string> Name = demangleItanium(F->getName()); | |
- writeStr(Sub.OS, Name ? StringRef(*Name) : F->getName(), "symbol name"); | |
+ writeStr(Sub.OS, maybeDemangleSymbol(F->getName()), "symbol name"); | |
} | |
} | |
} | |
Sub.writeTo(Section->getStream()); | |
} | |
void Writer::writeHeader() { | |
memcpy(Buffer->getBufferStart(), Header.data(), Header.size()); | |
} | |
void Writer::writeSections() { | |
uint8_t *Buf = Buffer->getBufferStart(); | |
parallelForEach(OutputSections, [Buf](OutputSection *S) { S->writeTo(Buf); }); | |
} | |
// Fix the memory layout of the output binary. This assigns memory offsets | |
// to each of the input data sections as well as the explicit stack region. | |
// The default memory layout is as follows, from low to high. | |
// | |
// - initialized data (starting at Config->GlobalBase) | |
// - BSS data (not currently implemented in llvm) | |
// - explicit stack (Config->ZStackSize) | |
// - heap start / unallocated | |
// | |
// The --stack-first option means that stack is placed before any static data. | |
// This can be useful since it means that stack overflow traps immediately | |
// rather than overwriting global data, but also increases code size since all | |
// static data loads and stores requires larger offsets. | |
void Writer::layoutMemory() { | |
createOutputSegments(); | |
uint32_t MemoryPtr = 0; | |
auto PlaceStack = [&]() { | |
if (Config->Relocatable) | |
return; | |
MemoryPtr = alignTo(MemoryPtr, kStackAlignment); | |
if (Config->ZStackSize != alignTo(Config->ZStackSize, kStackAlignment)) | |
error("stack size must be " + Twine(kStackAlignment) + "-byte aligned"); | |
log("mem: stack size = " + Twine(Config->ZStackSize)); | |
log("mem: stack base = " + Twine(MemoryPtr)); | |
MemoryPtr += Config->ZStackSize; | |
WasmSym::StackPointer->Global->Global.InitExpr.Value.Int32 = MemoryPtr; | |
log("mem: stack top = " + Twine(MemoryPtr)); | |
}; | |
if (Config->StackFirst) { | |
PlaceStack(); | |
} else { | |
MemoryPtr = Config->GlobalBase; | |
log("mem: global base = " + Twine(Config->GlobalBase)); | |
} | |
uint32_t DataStart = MemoryPtr; | |
// Arbitrarily set __dso_handle handle to point to the start of the data | |
// segments. | |
if (WasmSym::DsoHandle) | |
WasmSym::DsoHandle->setVirtualAddress(DataStart); | |
for (OutputSegment *Seg : Segments) { | |
MemoryPtr = alignTo(MemoryPtr, Seg->Alignment); | |
Seg->StartVA = MemoryPtr; | |
log(formatv("mem: {0,-15} offset={1,-8} size={2,-8} align={3}", Seg->Name, | |
MemoryPtr, Seg->Size, Seg->Alignment)); | |
MemoryPtr += Seg->Size; | |
} | |
// TODO: Add .bss space here. | |
if (WasmSym::DataEnd) | |
WasmSym::DataEnd->setVirtualAddress(MemoryPtr); | |
log("mem: static data = " + Twine(MemoryPtr - DataStart)); | |
if (!Config->StackFirst) | |
PlaceStack(); | |
// Set `__heap_base` to directly follow the end of the stack or global data. | |
// The fact that this comes last means that a malloc/brk implementation | |
// can grow the heap at runtime. | |
if (!Config->Relocatable) { | |
WasmSym::HeapBase->setVirtualAddress(MemoryPtr); | |
log("mem: heap base = " + Twine(MemoryPtr)); | |
} | |
if (Config->InitialMemory != 0) { | |
if (Config->InitialMemory != alignTo(Config->InitialMemory, WasmPageSize)) | |
error("initial memory must be " + Twine(WasmPageSize) + "-byte aligned"); | |
if (MemoryPtr > Config->InitialMemory) | |
error("initial memory too small, " + Twine(MemoryPtr) + " bytes needed"); | |
else | |
MemoryPtr = Config->InitialMemory; | |
} | |
uint32_t MemSize = alignTo(MemoryPtr, WasmPageSize); | |
NumMemoryPages = MemSize / WasmPageSize; | |
log("mem: total pages = " + Twine(NumMemoryPages)); | |
if (Config->MaxMemory != 0) { | |
if (Config->MaxMemory != alignTo(Config->MaxMemory, WasmPageSize)) | |
error("maximum memory must be " + Twine(WasmPageSize) + "-byte aligned"); | |
if (MemoryPtr > Config->MaxMemory) | |
error("maximum memory too small, " + Twine(MemoryPtr) + " bytes needed"); | |
MaxMemoryPages = Config->MaxMemory / WasmPageSize; | |
log("mem: max pages = " + Twine(MaxMemoryPages)); | |
} | |
} | |
SyntheticSection *Writer::createSyntheticSection(uint32_t Type, | |
StringRef Name) { | |
auto Sec = make<SyntheticSection>(Type, Name); | |
log("createSection: " + toString(*Sec)); | |
OutputSections.push_back(Sec); | |
return Sec; | |
} | |
void Writer::createSections() { | |
// Known sections | |
createTypeSection(); | |
createImportSection(); | |
createFunctionSection(); | |
createTableSection(); | |
createMemorySection(); | |
createGlobalSection(); | |
createExportSection(); | |
createElemSection(); | |
createCodeSection(); | |
createDataSection(); | |
createCustomSections(); | |
// Custom sections | |
if (Config->Relocatable) { | |
createLinkingSection(); | |
createRelocSections(); | |
} | |
if (!Config->StripDebug && !Config->StripAll) | |
createNameSection(); | |
for (OutputSection *S : OutputSections) { | |
S->setOffset(FileSize); | |
S->finalizeContents(); | |
FileSize += S->getSize(); | |
} | |
} | |
void Writer::calculateImports() { | |
for (Symbol *Sym : Symtab->getSymbols()) { | |
if (!Sym->isUndefined()) | |
continue; | |
if (isa<DataSymbol>(Sym)) | |
continue; | |
if (Sym->isWeak() && !Config->Relocatable) | |
continue; | |
if (!Sym->isLive()) | |
continue; | |
if (!Sym->IsUsedInRegularObj) | |
continue; | |
LLVM_DEBUG(dbgs() << "import: " << Sym->getName() << "\n"); | |
ImportedSymbols.emplace_back(Sym); | |
if (auto *F = dyn_cast<FunctionSymbol>(Sym)) | |
F->setFunctionIndex(NumImportedFunctions++); | |
else | |
cast<GlobalSymbol>(Sym)->setGlobalIndex(NumImportedGlobals++); | |
} | |
} | |
void Writer::calculateExports() { | |
if (Config->Relocatable) | |
return; | |
if (!Config->Relocatable && !Config->ImportMemory) | |
Exports.push_back(WasmExport{"memory", WASM_EXTERNAL_MEMORY, 0}); | |
if (!Config->Relocatable && Config->ExportTable) | |
Exports.push_back(WasmExport{kFunctionTableName, WASM_EXTERNAL_TABLE, 0}); | |
unsigned FakeGlobalIndex = NumImportedGlobals + InputGlobals.size(); | |
for (Symbol *Sym : Symtab->getSymbols()) { | |
if (!Sym->isExported()) | |
continue; | |
if (!Sym->isLive()) | |
continue; | |
StringRef Name = Sym->getName(); | |
WasmExport Export; | |
if (auto *F = dyn_cast<DefinedFunction>(Sym)) { | |
Export = {Name, WASM_EXTERNAL_FUNCTION, F->getFunctionIndex()}; | |
} else if (auto *G = dyn_cast<DefinedGlobal>(Sym)) { | |
// TODO(sbc): Remove this check once to mutable global proposal is | |
// implement in all major browsers. | |
// See: https://github.com/WebAssembly/mutable-global | |
if (G->getGlobalType()->Mutable) { | |
// Only the __stack_pointer should ever be create as mutable. | |
assert(G == WasmSym::StackPointer); | |
continue; | |
} | |
Export = {Name, WASM_EXTERNAL_GLOBAL, G->getGlobalIndex()}; | |
} else { | |
auto *D = cast<DefinedData>(Sym); | |
DefinedFakeGlobals.emplace_back(D); | |
Export = {Name, WASM_EXTERNAL_GLOBAL, FakeGlobalIndex++}; | |
} | |
LLVM_DEBUG(dbgs() << "Export: " << Name << "\n"); | |
Exports.push_back(Export); | |
} | |
} | |
void Writer::assignSymtab() { | |
if (!Config->Relocatable) | |
return; | |
StringMap<uint32_t> SectionSymbolIndices; | |
unsigned SymbolIndex = SymtabEntries.size(); | |
for (ObjFile *File : Symtab->ObjectFiles) { | |
LLVM_DEBUG(dbgs() << "Symtab entries: " << File->getName() << "\n"); | |
for (Symbol *Sym : File->getSymbols()) { | |
if (Sym->getFile() != File) | |
continue; | |
if (auto *S = dyn_cast<SectionSymbol>(Sym)) { | |
StringRef Name = S->getName(); | |
if (CustomSectionMapping.count(Name) == 0) | |
continue; | |
auto SSI = SectionSymbolIndices.find(Name); | |
if (SSI != SectionSymbolIndices.end()) { | |
Sym->setOutputSymbolIndex(SSI->second); | |
continue; | |
} | |
SectionSymbolIndices[Name] = SymbolIndex; | |
CustomSectionSymbols[Name] = cast<SectionSymbol>(Sym); | |
Sym->markLive(); | |
} | |
// (Since this is relocatable output, GC is not performed so symbols must | |
// be live.) | |
assert(Sym->isLive()); | |
Sym->setOutputSymbolIndex(SymbolIndex++); | |
SymtabEntries.emplace_back(Sym); | |
} | |
} | |
// For the moment, relocatable output doesn't contain any synthetic functions, | |
// so no need to look through the Symtab for symbols not referenced by | |
// Symtab->ObjectFiles. | |
} | |
uint32_t Writer::lookupType(const WasmSignature &Sig) { | |
auto It = TypeIndices.find(Sig); | |
if (It == TypeIndices.end()) { | |
error("type not found: " + toString(Sig)); | |
return 0; | |
} | |
return It->second; | |
} | |
uint32_t Writer::registerType(const WasmSignature &Sig) { | |
auto Pair = TypeIndices.insert(std::make_pair(Sig, Types.size())); | |
if (Pair.second) { | |
LLVM_DEBUG(dbgs() << "type " << toString(Sig) << "\n"); | |
Types.push_back(&Sig); | |
} | |
return Pair.first->second; | |
} | |
void Writer::calculateTypes() { | |
// The output type section is the union of the following sets: | |
// 1. Any signature used in the TYPE relocation | |
// 2. The signatures of all imported functions | |
// 3. The signatures of all defined functions | |
for (ObjFile *File : Symtab->ObjectFiles) { | |
ArrayRef<WasmSignature> Types = File->getWasmObj()->types(); | |
for (uint32_t I = 0; I < Types.size(); I++) | |
if (File->TypeIsUsed[I]) | |
File->TypeMap[I] = registerType(Types[I]); | |
} | |
for (const Symbol *Sym : ImportedSymbols) | |
if (auto *F = dyn_cast<FunctionSymbol>(Sym)) | |
registerType(*F->FunctionType); | |
for (const InputFunction *F : InputFunctions) | |
registerType(F->Signature); | |
} | |
void Writer::assignIndexes() { | |
uint32_t FunctionIndex = NumImportedFunctions + InputFunctions.size(); | |
auto AddDefinedFunction = [&](InputFunction *Func) { | |
if (!Func->Live) | |
return; | |
InputFunctions.emplace_back(Func); | |
Func->setFunctionIndex(FunctionIndex++); | |
}; | |
for (InputFunction *Func : Symtab->SyntheticFunctions) | |
AddDefinedFunction(Func); | |
for (ObjFile *File : Symtab->ObjectFiles) { | |
LLVM_DEBUG(dbgs() << "Functions: " << File->getName() << "\n"); | |
for (InputFunction *Func : File->Functions) | |
AddDefinedFunction(Func); | |
} | |
uint32_t TableIndex = kInitialTableOffset; | |
auto HandleRelocs = [&](InputChunk *Chunk) { | |
if (!Chunk->Live) | |
return; | |
ObjFile *File = Chunk->File; | |
ArrayRef<WasmSignature> Types = File->getWasmObj()->types(); | |
for (const WasmRelocation &Reloc : Chunk->getRelocations()) { | |
if (Reloc.Type == R_WEBASSEMBLY_TABLE_INDEX_I32 || | |
Reloc.Type == R_WEBASSEMBLY_TABLE_INDEX_SLEB) { | |
FunctionSymbol *Sym = File->getFunctionSymbol(Reloc.Index); | |
if (Sym->hasTableIndex() || !Sym->hasFunctionIndex()) | |
continue; | |
Sym->setTableIndex(TableIndex++); | |
IndirectFunctions.emplace_back(Sym); | |
} else if (Reloc.Type == R_WEBASSEMBLY_TYPE_INDEX_LEB) { | |
// Mark target type as live | |
File->TypeMap[Reloc.Index] = registerType(Types[Reloc.Index]); | |
File->TypeIsUsed[Reloc.Index] = true; | |
} | |
} | |
}; | |
for (ObjFile *File : Symtab->ObjectFiles) { | |
LLVM_DEBUG(dbgs() << "Handle relocs: " << File->getName() << "\n"); | |
for (InputChunk *Chunk : File->Functions) | |
HandleRelocs(Chunk); | |
for (InputChunk *Chunk : File->Segments) | |
HandleRelocs(Chunk); | |
for (auto &P : File->CustomSections) | |
HandleRelocs(P); | |
} | |
uint32_t GlobalIndex = NumImportedGlobals + InputGlobals.size(); | |
auto AddDefinedGlobal = [&](InputGlobal *Global) { | |
if (Global->Live) { | |
LLVM_DEBUG(dbgs() << "AddDefinedGlobal: " << GlobalIndex << "\n"); | |
Global->setGlobalIndex(GlobalIndex++); | |
InputGlobals.push_back(Global); | |
} | |
}; | |
for (InputGlobal *Global : Symtab->SyntheticGlobals) | |
AddDefinedGlobal(Global); | |
for (ObjFile *File : Symtab->ObjectFiles) { | |
LLVM_DEBUG(dbgs() << "Globals: " << File->getName() << "\n"); | |
for (InputGlobal *Global : File->Globals) | |
AddDefinedGlobal(Global); | |
} | |
} | |
static StringRef getOutputDataSegmentName(StringRef Name) { | |
if (!Config->MergeDataSegments) | |
return Name; | |
if (Name.startswith(".text.")) | |
return ".text"; | |
if (Name.startswith(".data.")) | |
return ".data"; | |
if (Name.startswith(".bss.")) | |
return ".bss"; | |
if (Name.startswith(".rodata.")) | |
return ".rodata"; | |
return Name; | |
} | |
void Writer::createOutputSegments() { | |
for (ObjFile *File : Symtab->ObjectFiles) { | |
for (InputSegment *Segment : File->Segments) { | |
if (!Segment->Live) | |
continue; | |
StringRef Name = getOutputDataSegmentName(Segment->getName()); | |
OutputSegment *&S = SegmentMap[Name]; | |
if (S == nullptr) { | |
LLVM_DEBUG(dbgs() << "new segment: " << Name << "\n"); | |
S = make<OutputSegment>(Name, Segments.size()); | |
Segments.push_back(S); | |
} | |
S->addInputSegment(Segment); | |
LLVM_DEBUG(dbgs() << "added data: " << Name << ": " << S->Size << "\n"); | |
} | |
} | |
} | |
static const int OPCODE_CALL = 0x10; | |
static const int OPCODE_END = 0xb; | |
// Create synthetic "__wasm_call_ctors" function based on ctor functions | |
// in input object. | |
void Writer::createCtorFunction() { | |
// First write the body's contents to a string. | |
std::string BodyContent; | |
{ | |
raw_string_ostream OS(BodyContent); | |
writeUleb128(OS, 0, "num locals"); | |
for (const WasmInitEntry &F : InitFunctions) { | |
writeU8(OS, OPCODE_CALL, "CALL"); | |
writeUleb128(OS, F.Sym->getFunctionIndex(), "function index"); | |
} | |
writeU8(OS, OPCODE_END, "END"); | |
} | |
// Once we know the size of the body we can create the final function body | |
std::string FunctionBody; | |
{ | |
raw_string_ostream OS(FunctionBody); | |
writeUleb128(OS, BodyContent.size(), "function size"); | |
OS << BodyContent; | |
} | |
ArrayRef<uint8_t> Body = arrayRefFromStringRef(Saver.save(FunctionBody)); | |
cast<SyntheticFunction>(WasmSym::CallCtors->Function)->setBody(Body); | |
} | |
// Populate InitFunctions vector with init functions from all input objects. | |
// This is then used either when creating the output linking section or to | |
// synthesize the "__wasm_call_ctors" function. | |
void Writer::calculateInitFunctions() { | |
for (ObjFile *File : Symtab->ObjectFiles) { | |
const WasmLinkingData &L = File->getWasmObj()->linkingData(); | |
for (const WasmInitFunc &F : L.InitFunctions) { | |
FunctionSymbol *Sym = File->getFunctionSymbol(F.Symbol); | |
if (*Sym->FunctionType != WasmSignature{{}, {}}) | |
error("invalid signature for init func: " + toString(*Sym)); | |
InitFunctions.emplace_back(WasmInitEntry{Sym, F.Priority}); | |
} | |
} | |
// Sort in order of priority (lowest first) so that they are called | |
// in the correct order. | |
std::stable_sort(InitFunctions.begin(), InitFunctions.end(), | |
[](const WasmInitEntry &L, const WasmInitEntry &R) { | |
return L.Priority < R.Priority; | |
}); | |
} | |
void Writer::run() { | |
if (Config->Relocatable) | |
Config->GlobalBase = 0; | |
log("-- calculateImports"); | |
calculateImports(); | |
log("-- assignIndexes"); | |
assignIndexes(); | |
log("-- calculateInitFunctions"); | |
calculateInitFunctions(); | |
if (!Config->Relocatable) | |
createCtorFunction(); | |
log("-- calculateTypes"); | |
calculateTypes(); | |
log("-- layoutMemory"); | |
layoutMemory(); | |
log("-- calculateExports"); | |
calculateExports(); | |
log("-- calculateCustomSections"); | |
calculateCustomSections(); | |
log("-- assignSymtab"); | |
assignSymtab(); | |
if (errorHandler().Verbose) { | |
log("Defined Functions: " + Twine(InputFunctions.size())); | |
log("Defined Globals : " + Twine(InputGlobals.size())); | |
log("Function Imports : " + Twine(NumImportedFunctions)); | |
log("Global Imports : " + Twine(NumImportedGlobals)); | |
for (ObjFile *File : Symtab->ObjectFiles) | |
File->dumpInfo(); | |
} | |
createHeader(); | |
log("-- createSections"); | |
createSections(); | |
log("-- openFile"); | |
openFile(); | |
if (errorCount()) | |
return; | |
writeHeader(); | |
log("-- writeSections"); | |
writeSections(); | |
if (errorCount()) | |
return; | |
if (Error E = Buffer->commit()) | |
fatal("failed to write the output file: " + toString(std::move(E))); | |
} | |
// Open a result file. | |
void Writer::openFile() { | |
log("writing: " + Config->OutputFile); | |
Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr = | |
FileOutputBuffer::create(Config->OutputFile, FileSize, | |
FileOutputBuffer::F_executable); | |
if (!BufferOrErr) | |
error("failed to open " + Config->OutputFile + ": " + | |
toString(BufferOrErr.takeError())); | |
else | |
Buffer = std::move(*BufferOrErr); | |
} | |
void Writer::createHeader() { | |
raw_string_ostream OS(Header); | |
writeBytes(OS, WasmMagic, sizeof(WasmMagic), "wasm magic"); | |
writeU32(OS, WasmVersion, "wasm version"); | |
OS.flush(); | |
FileSize += Header.size(); | |
} | |
void lld::wasm::writeResult() { Writer().run(); } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment