From 6cd8511e5932e4a53b2bb7780f69489355fc7783 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Wed, 30 Sep 2020 17:21:57 -0700 Subject: [PATCH] [WebAssembly] New-style command support This adds support for new-style command support. In this mode, all exports are considered command entrypoints, and the linker inserts calls to `__wasm_call_ctors` and `__wasm_call_dtors` for all such entrypoints. This enables support for: - Command entrypoints taking arguments other than strings and return values other than `int`. - Multicall executables without requiring on the use of string-based command-line arguments. This new behavior is disabled when the input has an explicit call to `__wasm_call_ctors`, indicating code not expecting new-style command support. This change does mean that wasm-ld no longer supports DCE-ing the `__wasm_call_ctors` function when there are no calls to it. If there are no calls to it, and there are ctors present, we assume it's wasm-ld's job to insert the calls. This seems ok though, because if there are ctors present, the program is expecting them to be called. This change affects the init-fini-gc.ll test. --- lld/test/wasm/command-exports-no-tors.s | 54 +++++++++++++++ lld/test/wasm/command-exports.s | 113 ++++++++++++++++++++++++++++++++ lld/test/wasm/init-fini-gc.ll | 48 -------------- lld/test/wasm/init-fini-no-gc.ll | 85 ++++++++++++++++++++++++ lld/wasm/Driver.cpp | 24 ++++++- lld/wasm/InputChunks.h | 10 ++- lld/wasm/MarkLive.cpp | 64 +++++++++++------- lld/wasm/Symbols.cpp | 1 + lld/wasm/Symbols.h | 4 ++ lld/wasm/Writer.cpp | 109 +++++++++++++++++++++++++++++- 10 files changed, 438 insertions(+), 74 deletions(-) create mode 100644 lld/test/wasm/command-exports-no-tors.s create mode 100644 lld/test/wasm/command-exports.s delete mode 100644 lld/test/wasm/init-fini-gc.ll create mode 100644 lld/test/wasm/init-fini-no-gc.ll diff --git a/lld/test/wasm/command-exports-no-tors.s b/lld/test/wasm/command-exports-no-tors.s new file mode 100644 index 0000000..e00712b --- /dev/null +++ b/lld/test/wasm/command-exports-no-tors.s @@ -0,0 +1,54 @@ +# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s +# RUN: wasm-ld --no-entry %t.o -o %t.wasm +# RUN: obj2yaml %t.wasm | FileCheck %s + +# Like command-exports.s, but with no ctors or dtors, so there should be no +# __wasm_call_ctors, __cxa_atexit, or wrappers. + + .globl foo_i32 +foo_i32: + .functype foo_i32 (i32, i32) -> (i32) + local.get 0 + local.get 1 + i32.add + end_function + + .globl foo_f64 +foo_f64: + .functype foo_f64 (f64, f64) -> (f64) + local.get 0 + local.get 1 + f64.add + end_function + + .export_name foo_i32, foo_i32 + .export_name foo_f64, 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.s b/lld/test/wasm/command-exports.s new file mode 100644 index 0000000..e1b47ce --- /dev/null +++ b/lld/test/wasm/command-exports.s @@ -0,0 +1,113 @@ +# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s +# RUN: wasm-ld --no-entry %t.o -o %t.wasm +# RUN: obj2yaml %t.wasm | FileCheck %s + +# This test defines a command with two exported functions, as well as a static +# constructor and a static destructor. Check that the exports, constructor, and +# destructor are all set up properly. + + .globl foo_i32 +foo_i32: + .functype foo_i32 (i32, i32) -> (i32) + local.get 0 + local.get 1 + i32.add + end_function + + .globl foo_f64 +foo_f64: + .functype foo_f64 (f64, f64) -> (f64) + local.get 0 + local.get 1 + f64.add + end_function + + .globl some_ctor +some_ctor: + .functype some_ctor () -> () + end_function + + .globl some_dtor +some_dtor: + .functype some_dtor () -> () + end_function + + .hidden __cxa_atexit + .globl __cxa_atexit +__cxa_atexit: + .functype __cxa_atexit (i32, i32, i32) -> (i32) + i32.const 0 + end_function + + .section .text..Lcall_dtors.1,"",@ +.Lcall_dtors.1: + .functype .Lcall_dtors.1 (i32) -> () + call some_dtor + end_function + + .section .text..Lregister_call_dtors.1,"",@ +.Lregister_call_dtors.1: + .functype .Lregister_call_dtors.1 () -> () + block + i32.const .Lcall_dtors.1 + i32.const 0 + i32.const 0 + call __cxa_atexit + i32.eqz + br_if 0 + unreachable +.LBB6_2: + end_block + end_function + + .section .init_array.1,"",@ + .p2align 2 + .int32 some_ctor + .int32 .Lregister_call_dtors.1 + .export_name foo_i32, foo_i32 + .export_name foo_f64, 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 deleted file mode 100644 index 4b2c14b..0000000 --- a/lld/test/wasm/init-fini-gc.ll +++ /dev/null @@ -1,48 +0,0 @@ -; RUN: llc -filetype=obj -o %t.o %s -; 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 - -target triple = "wasm32-unknown-unknown" - -define hidden void @_start() { -entry: - ret void -} - -define hidden void @func1() { -entry: - ret void -} - -define hidden void @func2() { -entry: - ret void -} - -define 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 ()* @func1, i8* null } -] - -@llvm.global_dtors = appending global [1 x { i32, void ()*, i8* }] [ - { i32, void ()*, i8* } { i32 1, void ()* @func2, i8* null } -] - -; CHECK-NOT: __cxa_atexit -; CHECK-NOT: __wasm_call_ctors - -; EXPORT: __wasm_call_ctors -; EXPORT: func1 -; EXPORT: func2 -; EXPORT: __cxa_atexit diff --git a/lld/test/wasm/init-fini-no-gc.ll b/lld/test/wasm/init-fini-no-gc.ll new file mode 100644 index 0000000..6241568 --- /dev/null +++ b/lld/test/wasm/init-fini-no-gc.ll @@ -0,0 +1,85 @@ +; RUN: llc -filetype=obj -o %t.o %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 we emit wrappers and call __wasm_call_ctor when not referenced. + +target triple = "wasm32-unknown-unknown" + +define hidden void @_start() { +entry: + ret void +} + +define hidden void @func1() { +entry: + ret void +} + +define hidden void @func2() { +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 ()* @func1, i8* null } +] + +@llvm.global_dtors = appending global [1 x { i32, void ()*, i8* }] [ + { i32, void ()*, i8* } { i32 1, void ()* @func2, i8* null } +] + +; Check that we have exactly the needed exports: `memory` because that's +; currently on by default, and `_start`, because that's the default entrypoint. + +; 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 the body of `_start`'s command-export wrapper. + +; CHECK: - Type: CODE + +; CHECK: - Index: 7 +; CHECK-NEXT: Locals: [] +; CHECK-NEXT: Body: 100010010B + +; Check the symbol table to ensure all the functions are here, and that +; index 7 above refers to the function we think it does. + +; 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 +; EXPORT: func2 +; EXPORT: __cxa_atexit diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp index 9b5f669..a6d26dcf 100644 --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -572,7 +572,6 @@ static void createSyntheticSymbols() { make(nullSignature, "__wasm_apply_relocs")); } - if (config->isPic) { WasmSym::stackPointer = createUndefinedGlobal("__stack_pointer", config->is64.getValueOr(false) @@ -841,6 +840,29 @@ void LinkerDriver::link(ArrayRef argsArr) { 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 = handleUndefined("__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"); + } + 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 index be91b19..e5671fb 100644 --- a/lld/wasm/InputChunks.h +++ b/lld/wasm/InputChunks.h @@ -122,7 +122,10 @@ protected: 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 || @@ -133,8 +136,10 @@ public: 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; } @@ -172,6 +177,7 @@ protected: } 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 index 2764c88..2766eec 100644 --- a/lld/wasm/MarkLive.cpp +++ b/lld/wasm/MarkLive.cpp @@ -44,6 +44,7 @@ private: void enqueue(Symbol *sym); void markSymbol(Symbol *sym); void mark(); + bool isCallCtorsLive(); // A list of chunks to visit. SmallVector queue; @@ -58,22 +59,6 @@ void MarkLive::enqueue(Symbol *sym) { 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,16 +71,29 @@ void MarkLive::run() { 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::applyRelocs); + + // If we have any non-discarded init functions, mark `__wasm_call_ctors` as + // live so that we assign it an index and call it. + if (isCallCtorsLive()) enqueue(WasmSym::callCtors); if (config->sharedMemory && !config->shared) @@ -169,5 +167,27 @@ void markLive() { } } +bool MarkLive::isCallCtorsLive() { + // In a reloctable link, we don't call `__wasm_call_ctors`. + if (config->relocatable) + return false; + + // In Emscripten-style PIC, we call `__wasm_call_ctors` which calls + // `__wasm_apply_relocs`. + if (config->isPic) + return true; + + // If there are any init functions, mark `__wasm_call_ctors` live so that + // it can call them. + for (const ObjFile *file : symtab->objectFiles) { + const WasmLinkingData &l = file->getWasmObj()->linkingData(); + for (const WasmInitFunc &f : l.InitFunctions) + if (!file->getFunctionSymbol(f.Symbol)->isDiscarded()) + return true; + } + + return false; +} + } // namespace wasm } // namespace lld diff --git a/lld/wasm/Symbols.cpp b/lld/wasm/Symbols.cpp index 4b40f95..d69ef003 100644 --- a/lld/wasm/Symbols.cpp +++ b/lld/wasm/Symbols.cpp @@ -66,6 +66,7 @@ std::string toString(wasm::Symbol::Kind kind) { namespace wasm { DefinedFunction *WasmSym::callCtors; +DefinedFunction *WasmSym::callDtors; DefinedFunction *WasmSym::initMemory; DefinedFunction *WasmSym::applyRelocs; DefinedFunction *WasmSym::initTLS; diff --git a/lld/wasm/Symbols.h b/lld/wasm/Symbols.h index eed481a..69195e4 100644 --- a/lld/wasm/Symbols.h +++ b/lld/wasm/Symbols.h @@ -471,6 +471,10 @@ struct WasmSym { // 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/Writer.cpp b/lld/wasm/Writer.cpp index 1d669ca..fee87f2 100644 --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -62,6 +62,8 @@ private: void createApplyRelocationsFunction(); void createCallCtorsFunction(); void createInitTLSFunction(); + void createCommandExportWrappers(); + void createCommandExportWrapper(uint32_t functionIndex, DefinedFunction *f); void assignIndexes(); void populateSymtab(); @@ -95,6 +97,9 @@ private: 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; @@ -640,6 +645,53 @@ void Writer::calculateTypes() { 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() { + // This logic doesn't currently support Emscripten-style PIC mode. + assert(!config->isPic); + + // If there are no ctors and there's no libc `__wasm_call_dtors` to + // call, don't wrap the exports. + if (initFunctions.empty() && WasmSym::callDtors == NULL) + return; + + std::vector toWrap; + + for (Symbol *sym : symtab->getSymbols()) + if (sym->isExported()) + if (auto *f = dyn_cast(sym)) + toWrap.push_back(f); + + for (auto *f : toWrap) { + 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"); @@ -925,7 +977,10 @@ void Writer::createApplyRelocationsFunction() { // 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, 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. @@ -954,6 +1009,46 @@ void Writer::createCallCtorsFunction() { 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; @@ -1090,6 +1185,18 @@ void Writer::run() { 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->isExported()) { + log("-- createCommandExportWrappers"); + createCommandExportWrappers(); + } } if (!config->relocatable && config->sharedMemory && !config->shared) -- 2.7.4