diff --git a/lld/test/wasm/command-exports-no-tors.ll b/lld/test/wasm/command-exports-no-tors.ll new file mode 100644 --- /dev/null +++ b/lld/test/wasm/command-exports-no-tors.ll @@ -0,0 +1,53 @@ +; RUN: llc -filetype=obj -o %t.o %s +; RUN: wasm-ld --no-entry %t.o -o %t.wasm +; RUN: obj2yaml %t.wasm | FileCheck %s + +; Like command-exports.ll, but with no ctors or dtors, so there should be no +; __wasm_call_ctors, __cxa_atexit, or wrappers. + +target triple = "wasm32-unknown-unknown" + +define i32 @foo_i32(i32 %a, i32 %b) #0 { +entry: + %c = add i32 %a, %b + ret i32 %c +} + +define double @foo_f64(double %a, double %b) #1 { +entry: + %c = fadd double %a, %b + ret double %c +} + +attributes #0 = { "wasm-export-name"="foo_i32" } + +attributes #1 = { "wasm-export-name"="foo_f64" } + +; CHECK: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: memory +; CHECK-NEXT: Kind: MEMORY +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: foo_i32 +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: foo_f64 +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 1 + +; CHECK: - Type: CODE + +; CHECK: - Index: 0 +; CHECK-NEXT: Locals: [] +; CHECK-NEXT: Body: 200020016A0B +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Locals: [] +; CHECK-NEXT: Body: 20002001A00B + +; CHECK: - Type: CUSTOM +; CHECK-NEXT: Name: name +; CHECK-NEXT: FunctionNames: +; CHECK-NEXT: - Index: 0 +; CHECK-NEXT: Name: foo_i32 +; CHECK-NEXT: - Index: 1 +; CHECK-NEXT: Name: foo_f64 diff --git a/lld/test/wasm/command-exports.ll b/lld/test/wasm/command-exports.ll new file mode 100644 --- /dev/null +++ b/lld/test/wasm/command-exports.ll @@ -0,0 +1,88 @@ +; RUN: llc -filetype=obj -o %t.o %s +; RUN: wasm-ld --no-entry %t.o -o %t.wasm +; RUN: obj2yaml %t.wasm | FileCheck %s + +target triple = "wasm32-unknown-unknown" + +define i32 @foo_i32(i32 %a, i32 %b) #0 { +entry: + %c = add i32 %a, %b + ret i32 %c +} + +define double @foo_f64(double %a, double %b) #1 { +entry: + %c = fadd double %a, %b + ret double %c +} + +define void @some_ctor() { +entry: + ret void +} + +define void @some_dtor() { +entry: + ret void +} + +define hidden i32 @__cxa_atexit(i32 %func, i32 %arg, i32 %dso_handle) { + ret i32 0 +} + +@llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [ + { i32, void ()*, i8* } { i32 1, void ()* @some_ctor, i8* null } +] + +@llvm.global_dtors = appending global [1 x { i32, void ()*, i8* }] [ + { i32, void ()*, i8* } { i32 1, void ()* @some_dtor, i8* null } +] + +attributes #0 = { "wasm-export-name"="foo_i32" } + +attributes #1 = { "wasm-export-name"="foo_f64" } + +; CHECK: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: memory +; CHECK-NEXT: Kind: MEMORY +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: foo_i32 +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 8 +; CHECK-NEXT: - Name: foo_f64 +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 9 + +; CHECK: - Type: CODE + +; CHECK: - Index: 8 +; CHECK-NEXT: Locals: [] +; CHECK-NEXT: Body: 10002000200110010B +; CHECK-NEXT: - Index: 9 +; CHECK-NEXT: Locals: [] +; CHECK-NEXT: Body: 10002000200110020B + +; CHECK: - 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: foo_i32 +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: Name: foo_f64 +; CHECK-NEXT: - Index: 3 +; CHECK-NEXT: Name: some_ctor +; CHECK-NEXT: - Index: 4 +; CHECK-NEXT: Name: some_dtor +; CHECK-NEXT: - Index: 5 +; CHECK-NEXT: Name: __cxa_atexit +; CHECK-NEXT: - Index: 6 +; CHECK-NEXT: Name: .Lcall_dtors.1 +; CHECK-NEXT: - Index: 7 +; CHECK-NEXT: Name: .Lregister_call_dtors.1 +; CHECK-NEXT: - Index: 8 +; CHECK-NEXT: Name: foo_i32.command_export +; CHECK-NEXT: - Index: 9 +; CHECK-NEXT: Name: foo_f64.command_export diff --git a/lld/test/wasm/init-fini-gc.ll b/lld/test/wasm/init-fini-gc.ll --- a/lld/test/wasm/init-fini-gc.ll +++ b/lld/test/wasm/init-fini-gc.ll @@ -2,13 +2,10 @@ ; RUN: wasm-ld %t.o -o %t.wasm ; RUN: obj2yaml %t.wasm | FileCheck %s -; RUN: wasm-ld %t.o -o %t.wasm -; RUN: obj2yaml %t.wasm | FileCheck %s - ; RUN: wasm-ld --export=__wasm_call_ctors %t.o -o %t.export.wasm ; RUN: obj2yaml %t.export.wasm | FileCheck %s -check-prefix=EXPORT -; Test that the __wasm_call_ctor function if not referenced +; Test that we emit wrappers and call __wasm_call_ctor when not referenced. target triple = "wasm32-unknown-unknown" @@ -27,7 +24,7 @@ ret void } -define i32 @__cxa_atexit(i32 %func, i32 %arg, i32 %dso_handle) { +define hidden i32 @__cxa_atexit(i32 %func, i32 %arg, i32 %dso_handle) { ret i32 0 } @@ -39,8 +36,40 @@ { i32, void ()*, i8* } { i32 1, void ()* @func2, i8* null } ] -; CHECK-NOT: __cxa_atexit -; CHECK-NOT: __wasm_call_ctors +; CHECK: - Type: EXPORT +; CHECK-NEXT: Exports: +; CHECK-NEXT: - Name: memory +; CHECK-NEXT: Kind: MEMORY +; CHECK-NEXT: Index: 0 +; CHECK-NEXT: - Name: _start +; CHECK-NEXT: Kind: FUNCTION +; CHECK-NEXT: Index: 7 + +; CHECK: - Type: CODE + +; CHECK: - Index: 7 +; CHECK-NEXT: Locals: [] +; CHECK-NEXT: Body: 100010010B + +; CHECK: - 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: _start +; CHECK-NEXT: - Index: 2 +; CHECK-NEXT: Name: func1 +; CHECK-NEXT: - Index: 3 +; CHECK-NEXT: Name: func2 +; CHECK-NEXT: - Index: 4 +; CHECK-NEXT: Name: __cxa_atexit +; CHECK-NEXT: - Index: 5 +; CHECK-NEXT: Name: .Lcall_dtors.1 +; CHECK-NEXT: - Index: 6 +; CHECK-NEXT: Name: .Lregister_call_dtors.1 +; CHECK-NEXT: - Index: 7 +; CHECK-NEXT: Name: _start.command_export ; EXPORT: __wasm_call_ctors ; EXPORT: func1 diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h --- a/lld/wasm/Config.h +++ b/lld/wasm/Config.h @@ -27,6 +27,7 @@ bool compressRelocations; bool demangle; bool disableVerify; + bool emscripten; bool emitRelocs; bool exportAll; bool exportDynamic; diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -332,6 +332,7 @@ config->demangle = args.hasFlag(OPT_demangle, OPT_no_demangle, true); config->disableVerify = args.hasArg(OPT_disable_verify); config->emitRelocs = args.hasArg(OPT_emit_relocs); + config->emscripten = args.hasArg(OPT_emscripten); config->entry = getEntry(args); config->exportAll = args.hasArg(OPT_export_all); config->exportTable = args.hasArg(OPT_export_table); @@ -456,6 +457,23 @@ if (config->sharedMemory) error("-r and --shared-memory may not be used together"); } + + // To begin to prepare for Module Linking-style shared libraries, start + // warning about uses of `-shared` and related flags outside of Emscripten + // mode, to give anyone using them a heads-up that they will be changing. + // + // Also, warn about flags which request explicit exports. + if (!config->emscripten) { + // -shared will change meaning when Module Linking is implemented. + if (config->shared) { + warn("shared libraries are not yet implemented"); + } + + // -pie will change meaning when Module Linking is implemented. + if (config->pie) { + warn("PIE is not yet implemented"); + } + } } // Force Sym to be entered in the output. Used for -u or equivalent. @@ -790,6 +808,31 @@ config->entry); } + // If the user code defines a `__wasm_call_dtors` function, remember it so + // that we can call it from the command export wrappers. Unlike + // `__wasm_call_ctors` which we synthesize, `__wasm_call_dtors` is defined + // by libc/etc., because destructors are registered dynamically with + // `__cxa_atexit` and friends. + if (!config->relocatable && + !config->shared && + !WasmSym::callCtors->isUsedInRegularObj && + WasmSym::callCtors->getName() != config->entry && + !config->exportedSymbols.count(WasmSym::callCtors->getName())) { + if (Symbol *callDtors = symtab->find("__wasm_call_dtors")) { + if (auto *callDtorsFunc = dyn_cast(callDtors)) { + if (callDtorsFunc->signature && + (!callDtorsFunc->signature->Params.empty() || + !callDtorsFunc->signature->Returns.empty())) { + error("__wasm_call_dtors must have no argument or return values"); + } + handleUndefined("__wasm_call_dtors"); + WasmSym::callDtors = callDtorsFunc; + } else { + error("__wasm_call_dtors must be a function"); + } + } + } + createOptionalSymbols(); if (errorCount()) diff --git a/lld/wasm/InputChunks.h b/lld/wasm/InputChunks.h --- a/lld/wasm/InputChunks.h +++ b/lld/wasm/InputChunks.h @@ -120,7 +120,10 @@ class InputFunction : public InputChunk { public: InputFunction(const WasmSignature &s, const WasmFunction *func, ObjFile *f) - : InputChunk(f, InputChunk::Function), signature(s), function(func) {} + : InputChunk(f, InputChunk::Function), signature(s), function(func), + exportName(func && func->ExportName.hasValue() ? + (*func->ExportName).str() : + llvm::Optional()) {} static bool classof(const InputChunk *c) { return c->kind() == InputChunk::Function || @@ -131,8 +134,11 @@ StringRef getName() const override { return function->SymbolName; } StringRef getDebugName() const override { return function->DebugName; } llvm::Optional getExportName() const { - return function ? function->ExportName : llvm::Optional(); + return exportName.hasValue() ? + llvm::Optional(*exportName) : + llvm::Optional(); } + void setExportName(std::string exportName) { this->exportName = exportName; } uint32_t getComdat() const override { return function->Comdat; } uint32_t getFunctionInputOffset() const { return getInputSectionOffset(); } uint32_t getFunctionCodeOffset() const { return function->CodeOffset; } @@ -170,6 +176,7 @@ } const WasmFunction *function; + llvm::Optional exportName; llvm::Optional functionIndex; llvm::Optional tableIndex; uint32_t compressedFuncSize = 0; diff --git a/lld/wasm/MarkLive.cpp b/lld/wasm/MarkLive.cpp --- a/lld/wasm/MarkLive.cpp +++ b/lld/wasm/MarkLive.cpp @@ -58,22 +58,6 @@ sym->markLive(); if (InputChunk *chunk = sym->getChunk()) queue.push_back(chunk); - - // The ctor functions are all referenced by the synthetic callCtors - // function. However, this function does not contain relocations so we - // have to manually mark the ctors as live if callCtors itself is live. - if (sym == WasmSym::callCtors) { - if (config->isPic) - enqueue(WasmSym::applyRelocs); - for (const ObjFile *obj : symtab->objectFiles) { - const WasmLinkingData &l = obj->getWasmObj()->linkingData(); - for (const WasmInitFunc &f : l.InitFunctions) { - auto* initSym = obj->getFunctionSymbol(f.Symbol); - if (!initSym->isDiscarded()) - enqueue(initSym); - } - } - } } void MarkLive::run() { @@ -86,17 +70,25 @@ if (sym->isNoStrip() || sym->isExported()) enqueue(sym); - // For relocatable output, we need to preserve all the ctor functions - if (config->relocatable) { - for (const ObjFile *obj : symtab->objectFiles) { - const WasmLinkingData &l = obj->getWasmObj()->linkingData(); - for (const WasmInitFunc &f : l.InitFunctions) - enqueue(obj->getFunctionSymbol(f.Symbol)); + // If we'll be calling the user's `__wasm_call_dtors` function, mark it live. + if (Symbol *callDtors = WasmSym::callDtors) + enqueue(callDtors); + + // The ctor functions are all referenced by the synthetic callCtors + // function. However, this function does not contain relocations so we + // have to manually mark the ctors as live. + for (const ObjFile *obj : symtab->objectFiles) { + const WasmLinkingData &l = obj->getWasmObj()->linkingData(); + for (const WasmInitFunc &f : l.InitFunctions) { + auto* initSym = obj->getFunctionSymbol(f.Symbol); + if (!initSym->isDiscarded()) + enqueue(initSym); } } + // In Emscripten-style PIC, `__wasm_call_ctors` calls `__wasm_apply_relocs`. if (config->isPic) - enqueue(WasmSym::callCtors); + enqueue(WasmSym::applyRelocs); if (config->sharedMemory && !config->shared) enqueue(WasmSym::initMemory); diff --git a/lld/wasm/Options.td b/lld/wasm/Options.td --- a/lld/wasm/Options.td +++ b/lld/wasm/Options.td @@ -199,3 +199,7 @@ defm thinlto_cache_policy: Eq<"thinlto-cache-policy", "Pruning policy for the ThinLTO cache">; def thinlto_jobs: J<"thinlto-jobs=">, HelpText<"Number of ThinLTO jobs. Default to --threads=">; + +// Emscripten mode. +def emscripten: F<"emscripten">, + HelpText<"Indicate that the compilation is for Emscripten">; diff --git a/lld/wasm/Symbols.h b/lld/wasm/Symbols.h --- a/lld/wasm/Symbols.h +++ b/lld/wasm/Symbols.h @@ -470,6 +470,10 @@ // Function that directly calls all ctors in priority order. static DefinedFunction *callCtors; + // __wasm_call_dtors + // Function that calls the libc/etc. cleanup function. + static DefinedFunction *callDtors; + // __wasm_apply_relocs // Function that applies relocations to data segment post-instantiation. static DefinedFunction *applyRelocs; diff --git a/lld/wasm/Symbols.cpp b/lld/wasm/Symbols.cpp --- a/lld/wasm/Symbols.cpp +++ b/lld/wasm/Symbols.cpp @@ -66,6 +66,7 @@ namespace wasm { DefinedFunction *WasmSym::callCtors; +DefinedFunction *WasmSym::callDtors; DefinedFunction *WasmSym::initMemory; DefinedFunction *WasmSym::applyRelocs; DefinedFunction *WasmSym::initTLS; diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -61,11 +61,14 @@ void createApplyRelocationsFunction(); void createCallCtorsFunction(); void createInitTLSFunction(); + void createCommandExportWrappers(); + void createCommandExportWrapper(uint32_t FunctionIndex, DefinedFunction *f); void assignIndexes(); void populateSymtab(); void populateProducers(); void populateTargetFeatures(); + void markCallCtorsLive(); void calculateInitFunctions(); void calculateImports(); void calculateExports(); @@ -94,6 +97,9 @@ std::vector initFunctions; llvm::StringMap> customSectionMapping; + // Stable storage for command export wrapper function name strings. + std::list commandExportWrapperNames; + // Elements that are used to construct the final output std::string header; std::vector outputSections; @@ -610,6 +616,51 @@ out.typeSec->registerType(e->signature); } +// In a command-style link, create a wrapper for each exported symbol +// which calls the constructors and destructors. +void Writer::createCommandExportWrappers() { + // If there are no ctors, and we aren't calling `__wasm_apply_relocs` for + // Emscripten-style PIC mode, and there's no libc `__wasm_call_dtors` to + // call, don't wrap the exports. + if (initFunctions.empty() && !config->isPic && WasmSym::callDtors == NULL) + return; + + std::vector to_wrap; + + for (Symbol *sym : symtab->getSymbols()) + if (sym->isExported()) + if (auto *f = dyn_cast(sym)) + to_wrap.push_back(f); + + for (auto *f : to_wrap) { + auto funcNameStr = (f->getName() + ".command_export").str(); + commandExportWrapperNames.push_back(funcNameStr); + const std::string &funcName = commandExportWrapperNames.back(); + + auto func = make(*f->getSignature(), funcName); + if (f->function->getExportName().hasValue()) + func->setExportName(f->function->getExportName()->str()); + else + func->setExportName(f->getName().str()); + + DefinedFunction *def = + symtab->addSyntheticFunction(funcName, f->flags, func); + def->markLive(); + + def->flags |= WASM_SYMBOL_EXPORTED; + def->flags &= ~WASM_SYMBOL_VISIBILITY_HIDDEN; + def->forceExport = f->forceExport; + + f->flags |= WASM_SYMBOL_VISIBILITY_HIDDEN; + f->flags &= ~WASM_SYMBOL_EXPORTED; + f->forceExport = false; + + out.functionSec->addFunction(func); + + createCommandExportWrapper(f->getFunctionIndex(), def); + } +} + static void scanRelocations() { for (ObjFile *file : symtab->objectFiles) { LLVM_DEBUG(dbgs() << "scanRelocations: " << file->getName() << "\n"); @@ -890,7 +941,10 @@ // Create synthetic "__wasm_call_ctors" function based on ctor functions // in input object. void Writer::createCallCtorsFunction() { - if (!WasmSym::callCtors->isLive()) + // If __wasm_call_ctors isn't referenced, or there aren't any ctors and we + // aren't calling `__wasm_apply_relocs` for Emscripten-style PIC, don't + // define the `__wasm_call_ctors` function. + if (!WasmSym::callCtors->isLive() && initFunctions.empty() && !config->isPic) return; // First write the body's contents to a string. @@ -916,6 +970,46 @@ createFunction(WasmSym::callCtors, bodyContent); } +// Create a wrapper around a function export which calls the +// static constructors and destructors. +void Writer::createCommandExportWrapper( + uint32_t FunctionIndex, + DefinedFunction *f +) { + // First write the body's contents to a string. + std::string bodyContent; + { + raw_string_ostream os(bodyContent); + writeUleb128(os, 0, "num locals"); + + // If we have any ctors, or we're calling `__wasm_apply_relocs` for + // Emscripten-style PIC, call `__wasm_call_ctors` which performs those calls. + if (!initFunctions.empty() || config->isPic) { + writeU8(os, WASM_OPCODE_CALL, "CALL"); + writeUleb128(os, WasmSym::callCtors->getFunctionIndex(), "function index"); + } + + // Call the user's code, leaving any return values on the operand stack. + for (size_t i = 0; i < f->signature->Params.size(); ++i) { + writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get"); + writeUleb128(os, i, "local index"); + } + writeU8(os, WASM_OPCODE_CALL, "CALL"); + writeUleb128(os, FunctionIndex, "function index"); + + // Call the function that calls the destructors. + if (DefinedFunction *callDtors = WasmSym::callDtors) { + writeU8(os, WASM_OPCODE_CALL, "CALL"); + writeUleb128(os, callDtors->getFunctionIndex(), "function index"); + } + + // End the function, returning the return values from the user's code. + writeU8(os, WASM_OPCODE_END, "END"); + } + + createFunction(f, bodyContent); +} + void Writer::createInitTLSFunction() { if (!WasmSym::initTLS->isLive()) return; @@ -958,6 +1052,30 @@ createFunction(WasmSym::initTLS, bodyContent); } +// If we have any non-discarded init functions, mark `__wasm_call_ctors` as +// live so that we assign it an index and call it. +// +// Return true if any init functions are present. +void Writer::markCallCtorsLive() { + // In Emscripten-style PIC, `__wasm_call_ctors` calls `__wasm_apply_relocs`. + if (config->isPic) { + WasmSym::callCtors->markLive(); + return; + } + + // If there are any init functions, mark `__wasm_call_ctors` live so that + // it can call them. + for (ObjFile *file : symtab->objectFiles) { + const WasmLinkingData &l = file->getWasmObj()->linkingData(); + for (const WasmInitFunc &f : l.InitFunctions) { + if (!file->getFunctionSymbol(f.Symbol)->isDiscarded()) { + WasmSym::callCtors->markLive(); + return; + } + } + } +} + // 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. @@ -1041,6 +1159,12 @@ log("-- scanRelocations"); scanRelocations(); + + if (!config->relocatable) { + log("-- markCallCtorsLive"); + markCallCtorsLive(); + } + log("-- assignIndexes"); assignIndexes(); log("-- calculateInitFunctions"); @@ -1053,6 +1177,20 @@ if (config->isPic) createApplyRelocationsFunction(); createCallCtorsFunction(); + + // Create export wrappers for commands if needed. + // + // If the input contains a call to `__wasm_call_ctors`, either in one of + // the input objects or an explicit export from the command-line, we + // assume ctors and dtors are taken care of already. + if (!config->relocatable && + !config->isPic && + !WasmSym::callCtors->isUsedInRegularObj && + WasmSym::callCtors->getName() != config->entry && + !config->exportedSymbols.count(WasmSym::callCtors->getName())) { + log("-- createCommandExportWrappers"); + createCommandExportWrappers(); + } } if (!config->relocatable && config->sharedMemory && !config->shared)