[lld] 6cd8511 - [WebAssembly] New-style command support

Dan Gohman via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 30 19:02:59 PDT 2020


Author: Dan Gohman
Date: 2020-09-30T19:02:40-07:00
New Revision: 6cd8511e5932e4a53b2bb7780f69489355fc7783

URL: https://github.com/llvm/llvm-project/commit/6cd8511e5932e4a53b2bb7780f69489355fc7783
DIFF: https://github.com/llvm/llvm-project/commit/6cd8511e5932e4a53b2bb7780f69489355fc7783.diff

LOG: [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.

Added: 
    lld/test/wasm/command-exports-no-tors.s
    lld/test/wasm/command-exports.s
    lld/test/wasm/init-fini-no-gc.ll

Modified: 
    lld/wasm/Driver.cpp
    lld/wasm/InputChunks.h
    lld/wasm/MarkLive.cpp
    lld/wasm/Symbols.cpp
    lld/wasm/Symbols.h
    lld/wasm/Writer.cpp

Removed: 
    lld/test/wasm/init-fini-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 000000000000..e00712bed538
--- /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 000000000000..e1b47ce9658f
--- /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 4b2c14bd6858..000000000000
--- 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
-}
-
- at llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [
-  { i32, void ()*, i8* } { i32 1, void ()* @func1, i8* null }
-]
-
- at 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 000000000000..62415686847e
--- /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
+}
+
+ at llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] [
+  { i32, void ()*, i8* } { i32 1, void ()* @func1, i8* null }
+]
+
+ at 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 9b5f6690ebf0..a6d26dcfcc43 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -572,7 +572,6 @@ static void createSyntheticSymbols() {
         make<SyntheticFunction>(nullSignature, "__wasm_apply_relocs"));
   }
 
-
   if (config->isPic) {
     WasmSym::stackPointer =
         createUndefinedGlobal("__stack_pointer", config->is64.getValueOr(false)
@@ -841,6 +840,29 @@ void LinkerDriver::link(ArrayRef<const char *> 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<DefinedFunction>(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 be91b19ed452..e5671fb89237 100644
--- a/lld/wasm/InputChunks.h
+++ b/lld/wasm/InputChunks.h
@@ -122,7 +122,10 @@ class InputSegment : public InputChunk {
 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<std::string>()) {}
 
   static bool classof(const InputChunk *c) {
     return c->kind() == InputChunk::Function ||
@@ -133,8 +136,10 @@ class InputFunction : public InputChunk {
   StringRef getName() const override { return function->SymbolName; }
   StringRef getDebugName() const override { return function->DebugName; }
   llvm::Optional<StringRef> getExportName() const {
-    return function ? function->ExportName : llvm::Optional<StringRef>();
+    return exportName.hasValue() ? llvm::Optional<StringRef>(*exportName)
+                                 : llvm::Optional<StringRef>();
   }
+  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 @@ class InputFunction : public InputChunk {
   }
 
   const WasmFunction *function;
+  llvm::Optional<std::string> exportName;
   llvm::Optional<uint32_t> functionIndex;
   llvm::Optional<uint32_t> tableIndex;
   uint32_t compressedFuncSize = 0;

diff  --git a/lld/wasm/MarkLive.cpp b/lld/wasm/MarkLive.cpp
index 2764c88f492c..2766eec07ecb 100644
--- a/lld/wasm/MarkLive.cpp
+++ b/lld/wasm/MarkLive.cpp
@@ -44,6 +44,7 @@ class MarkLive {
   void enqueue(Symbol *sym);
   void markSymbol(Symbol *sym);
   void mark();
+  bool isCallCtorsLive();
 
   // A list of chunks to visit.
   SmallVector<InputChunk *, 256> 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 4b40f95ed52d..d69ef00329c9 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 eed481a0b44d..69195e414b47 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 1d669ca7a723..fee87f292c90 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -62,6 +62,8 @@ class Writer {
   void createApplyRelocationsFunction();
   void createCallCtorsFunction();
   void createInitTLSFunction();
+  void createCommandExportWrappers();
+  void createCommandExportWrapper(uint32_t functionIndex, DefinedFunction *f);
 
   void assignIndexes();
   void populateSymtab();
@@ -95,6 +97,9 @@ class Writer {
   std::vector<WasmInitEntry> initFunctions;
   llvm::StringMap<std::vector<InputSection *>> customSectionMapping;
 
+  // Stable storage for command export wrapper function name strings.
+  std::list<std::string> commandExportWrapperNames;
+
   // Elements that are used to construct the final output
   std::string header;
   std::vector<OutputSection *> 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<DefinedFunction *> toWrap;
+
+  for (Symbol *sym : symtab->getSymbols())
+    if (sym->isExported())
+      if (auto *f = dyn_cast<DefinedFunction>(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<SyntheticFunction>(*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)


        


More information about the llvm-commits mailing list