[lld][WebAssembly] Split __wasm_apply_relocs function in two
authorSam Clegg <sbc@chromium.org>
Thu, 10 Dec 2020 02:14:31 +0000 (18:14 -0800)
committerSam Clegg <sbc@chromium.org>
Fri, 11 Dec 2020 01:07:39 +0000 (17:07 -0800)
We have two types of relocations that we apply on startup:
1. Relocations that apply to wasm globals
2. Relocations that apply to wasm memory

The first set of relocations use only the `__memory_base` import to
update a set of internal globals.  Because wasm globals are thread local
these need to run on each thread.  Memory relocations, like static
constructors, must only be run once.

To ensure global relocations run on all threads and because the only
depend on the immutable `__memory_base` import we can run them during
the WebAssembly start functions, instead of waiting until the
post-instantiation __wasm_call_ctors.

Differential Revision: https://reviews.llvm.org/D93066

lld/test/wasm/bsymbolic.s
lld/test/wasm/data-segments.ll
lld/test/wasm/pie.ll
lld/test/wasm/weak-undefined-pic.s
lld/wasm/Driver.cpp
lld/wasm/MarkLive.cpp
lld/wasm/Symbols.cpp
lld/wasm/Symbols.h
lld/wasm/SyntheticSections.cpp
lld/wasm/SyntheticSections.h
lld/wasm/Writer.cpp

index dc0e0dd..07989fc 100644 (file)
@@ -1,5 +1,5 @@
 // RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %s -o %t.o
-// RUN: wasm-ld --no-entry -Bsymbolic %t.o -o %t2.so 2>&1 | FileCheck -check-prefix=WARNING %s
+// RUN: wasm-ld --no-entry -Bsymbolic %t.o -o %t.wasm 2>&1 | FileCheck -check-prefix=WARNING %s
 // WARNING: warning: -Bsymbolic is only meaningful when combined with -shared
 
 // RUN: wasm-ld --experimental-pic -shared %t.o -o %t0.so
index 6f6d96a..8c4c4ca 100644 (file)
 ; PASSIVE-NEXT:        Name:            __wasm_init_memory
 
 ;      PASSIVE-PIC:  - Type:            START
-; PASSIVE-PIC-NEXT:    StartFunction:   3
+; PASSIVE-PIC-NEXT:    StartFunction:   2
 ; PASSIVE-PIC-NEXT:  - Type:            DATACOUNT
 ; PASSIVE-PIC-NEXT:    Count:           1
 ; PASSIVE-PIC-NEXT:  - Type:            CODE
 ; PASSIVE-PIC-NEXT:    Functions:
 ; PASSIVE-PIC-NEXT:      - Index:           0
 ; PASSIVE-PIC-NEXT:        Locals:          []
-; PASSIVE-PIC-NEXT:        Body:            10010B
+; PASSIVE-PIC-NEXT:        Body:            10030B
 ; PASSIVE-PIC-NEXT:      - Index:           1
 ; PASSIVE-PIC-NEXT:        Locals:          []
 ; PASSIVE-PIC-NEXT:        Body:            0B
 ; PASSIVE-PIC-NEXT:      - Index:           2
-; PASSIVE-PIC-NEXT:        Locals:          []
-; PASSIVE-PIC-NEXT:        Body:            0B
-; PASSIVE-PIC-NEXT:      - Index:           3
 ; PASSIVE-PIC-NEXT:        Locals:
 ; PASSIVE32-PIC-NEXT:          - Type:            I32
 ; PASSIVE64-PIC-NEXT:          - Type:            I64
 ; PASSIVE-PIC-NEXT:            Count:           1
 ; PASSIVE32-PIC-NEXT:        Body:            230141B4CE006A2100200041004101FE480200044020004101427FFE0102001A05410023016A410041B1CE00FC08000020004102FE1702002000417FFE0002001A0BFC09000B
 ; PASSIVE64-PIC-NEXT:        Body:            230142B4CE006A2100200041004101FE480200044020004101427FFE0102001A05420023016A410041B1CE00FC08000020004102FE1702002000417FFE0002001A0BFC09000B
+; PASSIVE-PIC-NEXT:      - Index:           3
+; PASSIVE-PIC-NEXT:        Locals:          []
+; PASSIVE-PIC-NEXT:        Body:            0B
 ; PASSIVE-PIC-NEXT:  - Type:            DATA
 ; PASSIVE-PIC-NEXT:    Segments:
 ; PASSIVE-PIC-NEXT:      - SectionOffset:   4
 ; PASSIVE-PIC-NEXT:      - Index:           0
 ; PASSIVE-PIC-NEXT:        Name:            __wasm_call_ctors
 ; PASSIVE-PIC-NEXT:      - Index:           1
-; PASSIVE-PIC-NEXT:        Name:            __wasm_apply_relocs
-; PASSIVE-PIC-NEXT:      - Index:           2
 ; PASSIVE-PIC-NEXT:        Name:            __wasm_init_tls
-; PASSIVE-PIC-NEXT:      - Index:           3
+; PASSIVE-PIC-NEXT:      - Index:           2
 ; PASSIVE-PIC-NEXT:        Name:            __wasm_init_memory
+; PASSIVE-PIC-NEXT:      - Index:           3
+; PASSIVE-PIC-NEXT:        Name:            __wasm_apply_data_relocs
index 20a6c92..84555a0 100644 (file)
@@ -1,6 +1,5 @@
 ; RUN: llc -relocation-model=pic -mattr=+mutable-globals -filetype=obj %s -o %t.o
 ; RUN: wasm-ld --no-gc-sections --allow-undefined --experimental-pic -pie -o %t.wasm %t.o
-; RUN: obj2yaml %t.wasm | FileCheck %s
 
 target triple = "wasm32-unknown-emscripten"
 
@@ -65,4 +64,51 @@ define void @_start() {
 ; CHECK-NEXT:         GlobalType:      I32
 ; CHECK-NEXT:         GlobalMutable:   false
 
+; CHECK:        - Type:            START
+; CHECK-NEXT:     StartFunction:   2
+
+; 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:            __wasm_apply_data_relocs
+; CHECK-NEXT:       - Index:           2
+; CHECK-NEXT:         Name:            __wasm_apply_global_relocs
+
+
+; Run the same test with threading support.  In this mode
+; we expect __wasm_init_memory and __wasm_apply_data_relocs
+; to be generated along with __wasm_start as the start
+; function.
+
+; RUN: llc -relocation-model=pic -mattr=+mutable-globals,+atomics,+bulk-memory -filetype=obj %s -o %t.shmem.o
+; RUN: wasm-ld --no-gc-sections --shared-memory --allow-undefined --experimental-pic -pie -o %t.shmem.wasm %t.shmem.o
+; RUN: obj2yaml %t.shmem.wasm | FileCheck %s --check-prefix=SHMEM
+
+; SHMEM:         - Type:            CODE
+; SHMEM:           - Index:           5
+; SHMEM-NEXT:        Locals:          []
+; SHMEM-NEXT:        Body:            100210040B
+
+; SHMEM:         FunctionNames:
+; SHMEM-NEXT:      - Index:           0
+; SHMEM-NEXT:        Name:            __wasm_call_ctors
+; SHMEM-NEXT:      - Index:           1
+; SHMEM-NEXT:        Name:            __wasm_init_tls
+; SHMEM-NEXT:      - Index:           2
+; SHMEM-NEXT:        Name:            __wasm_init_memory
+; SHMEM-NEXT:      - Index:           3
+; SHMEM-NEXT:        Name:            __wasm_apply_data_relocs
+; SHMEM-NEXT:      - Index:           4
+; SHMEM-NEXT:        Name:            __wasm_apply_global_relocs
+; SHMEM-NEXT:      - Index:           5
+; SHMEM-NEXT:        Name:            __wasm_start
+; SHMEM-NEXT:      - Index:           6
+; SHMEM-NEXT:        Name:            foo
+; SHMEM-NEXT:      - Index:           7
+; SHMEM-NEXT:        Name:            get_data_address
+; SHMEM-NEXT:      - Index:           8
+; SHMEM-NEXT:        Name:            _start
 
index a169fd3..17dbc54 100644 (file)
@@ -66,7 +66,7 @@ _start:
 # CHECK-NEXT:      - Index:           0
 # CHECK-NEXT:        Name:            __stack_pointer
 # CHECK-NEXT:      - Index:           1
-# CHECK-NEXT:        Name:            'undefined_weak:foo'
+# CHECK-NEXT:        Name:            'GOT.func.internal.undefined_weak:foo'
 
 # With `-pie` or `-shared` the resolution should be deferred to the dynamic
 # linker and the function address should be imported as GOT.func.foo.
index 3be0a03..0d84914 100644 (file)
@@ -616,14 +616,6 @@ static void createSyntheticSymbols() {
       make<SyntheticFunction>(nullSignature, "__wasm_call_ctors"));
 
   if (config->isPic) {
-    // For PIC code we create a synthetic function __wasm_apply_relocs which
-    // is called from __wasm_call_ctors before the user-level constructors.
-    WasmSym::applyRelocs = symtab->addSyntheticFunction(
-        "__wasm_apply_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN,
-        make<SyntheticFunction>(nullSignature, "__wasm_apply_relocs"));
-  }
-
-  if (config->isPic) {
     WasmSym::stackPointer =
         createUndefinedGlobal("__stack_pointer", config->is64.getValueOr(false)
                                                      ? &mutableGlobalTypeI64
index acc3c4c..63c1254 100644 (file)
@@ -98,9 +98,6 @@ void MarkLive::run() {
   if (WasmSym::callDtors)
     enqueue(WasmSym::callDtors);
 
-  if (WasmSym::applyRelocs)
-    enqueue(WasmSym::applyRelocs);
-
   // Enqueue constructors in objects explicitly live from the command-line.
   for (const ObjFile *obj : symtab->objectFiles)
     if (obj->isLive())
index ffb52c1..9e41f7c 100644 (file)
@@ -68,8 +68,10 @@ namespace wasm {
 DefinedFunction *WasmSym::callCtors;
 DefinedFunction *WasmSym::callDtors;
 DefinedFunction *WasmSym::initMemory;
-DefinedFunction *WasmSym::applyRelocs;
+DefinedFunction *WasmSym::applyDataRelocs;
+DefinedFunction *WasmSym::applyGlobalRelocs;
 DefinedFunction *WasmSym::initTLS;
+DefinedFunction *WasmSym::startFunction;
 DefinedData *WasmSym::dsoHandle;
 DefinedData *WasmSym::dataEnd;
 DefinedData *WasmSym::globalBase;
index cfa6869..4fa1a83 100644 (file)
@@ -484,14 +484,23 @@ struct WasmSym {
   // Function that calls the libc/etc. cleanup function.
   static DefinedFunction *callDtors;
 
-  // __wasm_apply_relocs
+  // __wasm_apply_data_relocs
   // Function that applies relocations to data segment post-instantiation.
-  static DefinedFunction *applyRelocs;
+  static DefinedFunction *applyDataRelocs;
+
+  // __wasm_apply_global_relocs
+  // Function that applies relocations to data segment post-instantiation.
+  // Unlike __wasm_apply_data_relocs this needs to run on every thread.
+  static DefinedFunction *applyGlobalRelocs;
 
   // __wasm_init_tls
   // Function that allocates thread-local storage and initializes it.
   static DefinedFunction *initTLS;
 
+  // Pointer to the function that is to be used in the start section.
+  // (normally an alias of initMemory, or applyGlobalRelocs).
+  static DefinedFunction *startFunction;
+
   // __dso_handle
   // Symbol used in calls to __cxa_atexit to determine current DLL
   static DefinedData *dsoHandle;
index 8e2c7c6..768a265 100644 (file)
@@ -372,12 +372,13 @@ void ExportSection::writeBody() {
 }
 
 bool StartSection::isNeeded() const {
-  return WasmSym::initMemory != nullptr;
+  return WasmSym::startFunction != nullptr;
 }
 
 void StartSection::writeBody() {
   raw_ostream &os = bodyOutputStream;
-  writeUleb128(os, WasmSym::initMemory->getFunctionIndex(), "function index");
+  writeUleb128(os, WasmSym::startFunction->getFunctionIndex(),
+               "function index");
 }
 
 void ElemSection::addEntry(FunctionSymbol *sym) {
@@ -624,7 +625,10 @@ void NameSection::writeBody() {
     }
     for (Symbol *s : out.globalSec->internalGotSymbols) {
       writeUleb128(sub.os, s->getGOTIndex(), "global index");
-      writeStr(sub.os, toString(*s), "symbol name");
+      if (isa<FunctionSymbol>(s))
+        writeStr(sub.os, "GOT.func.internal." + toString(*s), "symbol name");
+      else
+        writeStr(sub.os, "GOT.data.internal." + toString(*s), "symbol name");
     }
 
     sub.writeTo(bodyOutputStream);
index 3f52b5d..b4d833d 100644 (file)
@@ -217,6 +217,7 @@ public:
   // specific relocation types combined with linker relaxation which could
   // transform a `global.get` to an `i32.const`.
   void addInternalGOTEntry(Symbol *sym);
+  bool needsRelocations() { return internalGotSymbols.size(); }
   void generateRelocationCode(raw_ostream &os) const;
 
   std::vector<const DefinedData *> dataAddressGlobals;
index ff9d0dc..3f25596 100644 (file)
@@ -60,7 +60,9 @@ private:
 
   void createSyntheticInitFunctions();
   void createInitMemoryFunction();
-  void createApplyRelocationsFunction();
+  void createStartFunction();
+  void createApplyDataRelocationsFunction();
+  void createApplyGlobalRelocationsFunction();
   void createCallCtorsFunction();
   void createInitTLSFunction();
   void createCommandExportWrappers();
@@ -296,8 +298,11 @@ void Writer::layoutMemory() {
   }
 
   // Make space for the memory initialization flag
-  if (WasmSym::initMemoryFlag) {
+  if (config->sharedMemory && hasPassiveInitializedSegments()) {
     memoryPtr = alignTo(memoryPtr, 4);
+    WasmSym::initMemoryFlag = symtab->addSyntheticDataSymbol(
+        "__wasm_init_memory_flag", WASM_SYMBOL_VISIBILITY_HIDDEN);
+    WasmSym::initMemoryFlag->markLive();
     WasmSym::initMemoryFlag->setVirtualAddress(memoryPtr);
     log(formatv("mem: {0,-15} offset={1,-8} size={2,-8} align={3}",
                 "__wasm_init_memory_flag", memoryPtr, 4, 4));
@@ -867,19 +872,43 @@ bool Writer::hasPassiveInitializedSegments() {
 }
 
 void Writer::createSyntheticInitFunctions() {
+  if (config->relocatable)
+    return;
+
+  static WasmSignature nullSignature = {{}, {}};
+
   // Passive segments are used to avoid memory being reinitialized on each
   // thread's instantiation. These passive segments are initialized and
   // dropped in __wasm_init_memory, which is registered as the start function
-
   if (config->sharedMemory && hasPassiveInitializedSegments()) {
-    static WasmSignature nullSignature = {{}, {}};
     WasmSym::initMemory = symtab->addSyntheticFunction(
         "__wasm_init_memory", WASM_SYMBOL_VISIBILITY_HIDDEN,
         make<SyntheticFunction>(nullSignature, "__wasm_init_memory"));
     WasmSym::initMemory->markLive();
-    WasmSym::initMemoryFlag = symtab->addSyntheticDataSymbol(
-        "__wasm_init_memory_flag", WASM_SYMBOL_VISIBILITY_HIDDEN);
-    WasmSym::initMemoryFlag->markLive();
+  }
+
+  if (config->isPic) {
+    // For PIC code we create synthetic functions that apply relocations.
+    // These get called from __wasm_call_ctors before the user-level
+    // constructors.
+    WasmSym::applyDataRelocs = symtab->addSyntheticFunction(
+        "__wasm_apply_data_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN,
+        make<SyntheticFunction>(nullSignature, "__wasm_apply_data_relocs"));
+    WasmSym::applyDataRelocs->markLive();
+
+    if (out.globalSec->needsRelocations()) {
+      WasmSym::applyGlobalRelocs = symtab->addSyntheticFunction(
+          "__wasm_apply_global_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN,
+          make<SyntheticFunction>(nullSignature, "__wasm_apply_global_relocs"));
+      WasmSym::applyGlobalRelocs->markLive();
+    }
+  }
+
+  if (WasmSym::applyGlobalRelocs && WasmSym::initMemory) {
+    WasmSym::startFunction = symtab->addSyntheticFunction(
+        "__wasm_start", WASM_SYMBOL_VISIBILITY_HIDDEN,
+        make<SyntheticFunction>(nullSignature, "__wasm_start"));
+    WasmSym::startFunction->markLive();
   }
 }
 
@@ -1042,24 +1071,39 @@ void Writer::createInitMemoryFunction() {
   createFunction(WasmSym::initMemory, bodyContent);
 }
 
+void Writer::createStartFunction() {
+  if (WasmSym::startFunction) {
+    std::string bodyContent;
+    {
+      raw_string_ostream os(bodyContent);
+      writeUleb128(os, 0, "num locals");
+      writeU8(os, WASM_OPCODE_CALL, "CALL");
+      writeUleb128(os, WasmSym::initMemory->getFunctionIndex(),
+                   "function index");
+      writeU8(os, WASM_OPCODE_CALL, "CALL");
+      writeUleb128(os, WasmSym::applyGlobalRelocs->getFunctionIndex(),
+                   "function index");
+      writeU8(os, WASM_OPCODE_END, "END");
+    }
+    createFunction(WasmSym::startFunction, bodyContent);
+  } else if (WasmSym::initMemory) {
+    WasmSym::startFunction = WasmSym::initMemory;
+  } else if (WasmSym::applyGlobalRelocs) {
+    WasmSym::startFunction = WasmSym::applyGlobalRelocs;
+  }
+}
+
 // For -shared (PIC) output, we create create a synthetic function which will
 // apply any relocations to the data segments on startup.  This function is
 // called __wasm_apply_relocs and is added at the beginning of __wasm_call_ctors
 // before any of the constructors run.
-void Writer::createApplyRelocationsFunction() {
-  LLVM_DEBUG(dbgs() << "createApplyRelocationsFunction\n");
+void Writer::createApplyDataRelocationsFunction() {
+  LLVM_DEBUG(dbgs() << "createApplyDataRelocationsFunction\n");
   // First write the body's contents to a string.
   std::string bodyContent;
   {
     raw_string_ostream os(bodyContent);
     writeUleb128(os, 0, "num locals");
-
-    // First apply relocations to any internalized GOT entries.  These
-    // are the result of relaxation when building with -Bsymbolic.
-    out.globalSec->generateRelocationCode(os);
-
-    // Next apply any realocation to the data section by reading GOT entry
-    // globals.
     for (const OutputSegment *seg : segments)
       for (const InputSegment *inSeg : seg->inputSegments)
         inSeg->generateRelocationCode(os);
@@ -1067,7 +1111,23 @@ void Writer::createApplyRelocationsFunction() {
     writeU8(os, WASM_OPCODE_END, "END");
   }
 
-  createFunction(WasmSym::applyRelocs, bodyContent);
+  createFunction(WasmSym::applyDataRelocs, bodyContent);
+}
+
+// Similar to createApplyDataRelocationsFunction but generates relocation code
+// fro WebAssembly globals. Because these globals are not shared between threads
+// these relocation need to run on every thread.
+void Writer::createApplyGlobalRelocationsFunction() {
+  // First write the body's contents to a string.
+  std::string bodyContent;
+  {
+    raw_string_ostream os(bodyContent);
+    writeUleb128(os, 0, "num locals");
+    out.globalSec->generateRelocationCode(os);
+    writeU8(os, WASM_OPCODE_END, "END");
+  }
+
+  createFunction(WasmSym::applyGlobalRelocs, bodyContent);
 }
 
 // Create synthetic "__wasm_call_ctors" function based on ctor functions
@@ -1076,7 +1136,8 @@ void Writer::createCallCtorsFunction() {
   // 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)
+  if (!WasmSym::callCtors->isLive() && !WasmSym::applyDataRelocs &&
+      initFunctions.empty())
     return;
 
   // First write the body's contents to a string.
@@ -1085,9 +1146,9 @@ void Writer::createCallCtorsFunction() {
     raw_string_ostream os(bodyContent);
     writeUleb128(os, 0, "num locals");
 
-    if (config->isPic) {
+    if (WasmSym::applyDataRelocs) {
       writeU8(os, WASM_OPCODE_CALL, "CALL");
-      writeUleb128(os, WasmSym::applyRelocs->getFunctionIndex(),
+      writeUleb128(os, WasmSym::applyDataRelocs->getFunctionIndex(),
                    "function index");
     }
 
@@ -1099,6 +1160,7 @@ void Writer::createCallCtorsFunction() {
         writeU8(os, WASM_OPCODE_DROP, "DROP");
       }
     }
+
     writeU8(os, WASM_OPCODE_END, "END");
   }
 
@@ -1118,7 +1180,7 @@ void Writer::createCommandExportWrapper(uint32_t functionIndex,
     // 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) {
+    if (WasmSym::callCtors->isLive()) {
       writeU8(os, WASM_OPCODE_CALL, "CALL");
       writeUleb128(os, WasmSym::callCtors->getFunctionIndex(),
                    "function index");
@@ -1253,8 +1315,6 @@ void Writer::run() {
   populateProducers();
   log("-- calculateImports");
   calculateImports();
-  log("-- createSyntheticInitFunctions");
-  createSyntheticInitFunctions();
   log("-- layoutMemory");
   layoutMemory();
 
@@ -1267,18 +1327,23 @@ void Writer::run() {
 
   log("-- scanRelocations");
   scanRelocations();
+  log("-- createSyntheticInitFunctions");
+  createSyntheticInitFunctions();
   log("-- assignIndexes");
   assignIndexes();
   log("-- calculateInitFunctions");
   calculateInitFunctions();
 
   if (!config->relocatable) {
-    if (WasmSym::applyRelocs)
-      createApplyRelocationsFunction();
+    // Create linker synthesized functions
+    if (WasmSym::applyDataRelocs)
+      createApplyDataRelocationsFunction();
+    if (WasmSym::applyGlobalRelocs)
+      createApplyGlobalRelocationsFunction();
     if (WasmSym::initMemory)
       createInitMemoryFunction();
+    createStartFunction();
 
-    // Create linker synthesized functions
     createCallCtorsFunction();
 
     // Create export wrappers for commands if needed.