[lld][WebAssembly] Allow references to __tls_base without shared memory
authorSam Clegg <sbc@chromium.org>
Tue, 10 Nov 2020 01:52:39 +0000 (17:52 -0800)
committerSam Clegg <sbc@chromium.org>
Wed, 11 Nov 2020 01:58:06 +0000 (17:58 -0800)
Previously we limited the use of atomics and TLS to programs
linked with `--shared-memory`.

However, as of https://reviews.llvm.org/D79530 we now allow
programs that use atomic to be linked without `--shared-memory`.
For this to be useful we also want to all TLS usage in such
programs.  In this case, since we know we are single threaded
we simply include the TLS data as a regular active segment
and create an immutable `__tls_base` global that point to the
start of this segment.

Fixes: https://github.com/emscripten-core/emscripten/issues/12489

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

lld/test/wasm/no-tls.s
lld/test/wasm/tls-no-shared.s [new file with mode: 0644]
lld/wasm/Driver.cpp
lld/wasm/SymbolTable.cpp
lld/wasm/SymbolTable.h
lld/wasm/Writer.cpp

index d1ba766..74b9331 100644 (file)
@@ -52,4 +52,4 @@ _start:
 # CHECK-NEXT:        Mutable:         false
 # CHECK-NEXT:        InitExpr:
 # CHECK-NEXT:          Opcode:          I32_CONST
-# CHECK-NEXT:          Value:           1
+# CHECK-NEXT:          Value:           0
diff --git a/lld/test/wasm/tls-no-shared.s b/lld/test/wasm/tls-no-shared.s
new file mode 100644 (file)
index 0000000..3fdc705
--- /dev/null
@@ -0,0 +1,75 @@
+# Test that linking without shared memory causes __tls_base to be
+# interlized
+
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
+
+.globaltype __tls_base, i32
+
+.globl get_tls1
+get_tls1:
+  .functype get_tls1 () -> (i32)
+  global.get __tls_base
+  i32.const tls1
+  i32.add
+  end_function
+
+.section  .data.no_tls,"",@
+.globl  no_tls
+.p2align  2
+no_tls:
+  .int32  42
+  .size no_tls, 4
+
+.section  .tdata.tls1,"",@
+.globl  tls1
+.p2align  2
+tls1:
+  .int32  43
+  .size tls1, 2
+
+.section  .custom_section.target_features,"",@
+  .int8 2
+  .int8 43
+  .int8 7
+  .ascii  "atomics"
+  .int8 43
+  .int8 11
+  .ascii  "bulk-memory"
+
+# RUN: wasm-ld --no-gc-sections --no-entry -o %t.wasm %t.o
+# RUN: obj2yaml %t.wasm | FileCheck %s
+
+#      CHECK:   - Type:            GLOBAL
+# __stack_pointer
+# CHECK-NEXT:     Globals:
+# CHECK-NEXT:       - Index:           0
+# CHECK-NEXT:         Type:            I32
+# CHECK-NEXT:         Mutable:         true
+# CHECK-NEXT:         InitExpr:
+# CHECK-NEXT:           Opcode:          I32_CONST
+# CHECK-NEXT:           Value:           66576
+# __tls_base
+# CHECK-NEXT:       - Index:           1
+# CHECK-NEXT:         Type:            I32
+# CHECK-NEXT:         Mutable:         false
+# CHECK-NEXT:         InitExpr:
+# CHECK-NEXT:           Opcode:          I32_CONST
+# CHECK-NEXT:           Value:           1028
+# CHECK-NEXT:   - Type:            EXPORT
+
+#      CHECK:  - Type:            DATA
+# .data
+# CHECK-NEXT:    Segments:
+# CHECK-NEXT:      - SectionOffset:   7
+# CHECK-NEXT:        InitFlags:       0
+# CHECK-NEXT:        Offset:
+# CHECK-NEXT:          Opcode:          I32_CONST
+# CHECK-NEXT:          Value:           1024
+# CHECK-NEXT:        Content:         2A000000
+# .tdata
+# CHECK-NEXT:      - SectionOffset:   17
+# CHECK-NEXT:        InitFlags:       0
+# CHECK-NEXT:        Offset:
+# CHECK-NEXT:          Opcode:          I32_CONST
+# CHECK-NEXT:          Value:           1028
+# CHECK-NEXT:        Content:         2B000000
index fb00355..5b12b48 100644 (file)
@@ -541,21 +541,30 @@ createUndefinedGlobal(StringRef name, llvm::wasm::WasmGlobalType *type) {
   return sym;
 }
 
-static GlobalSymbol *createGlobalVariable(StringRef name, bool isMutable,
-                                          int value) {
+static InputGlobal *createGlobal(StringRef name, bool isMutable) {
   llvm::wasm::WasmGlobal wasmGlobal;
   if (config->is64.getValueOr(false)) {
     wasmGlobal.Type = {WASM_TYPE_I64, isMutable};
-    wasmGlobal.InitExpr.Value.Int64 = value;
     wasmGlobal.InitExpr.Opcode = WASM_OPCODE_I64_CONST;
+    wasmGlobal.InitExpr.Value.Int64 = 0;
   } else {
     wasmGlobal.Type = {WASM_TYPE_I32, isMutable};
-    wasmGlobal.InitExpr.Value.Int32 = value;
     wasmGlobal.InitExpr.Opcode = WASM_OPCODE_I32_CONST;
+    wasmGlobal.InitExpr.Value.Int32 = 0;
   }
   wasmGlobal.SymbolName = name;
-  return symtab->addSyntheticGlobal(name, WASM_SYMBOL_VISIBILITY_HIDDEN,
-                                    make<InputGlobal>(wasmGlobal, nullptr));
+  return make<InputGlobal>(wasmGlobal, nullptr);
+}
+
+static GlobalSymbol *createGlobalVariable(StringRef name, bool isMutable) {
+  InputGlobal *g = createGlobal(name, isMutable);
+  return symtab->addSyntheticGlobal(name, WASM_SYMBOL_VISIBILITY_HIDDEN, g);
+}
+
+static GlobalSymbol *createOptionalGlobal(StringRef name, bool isMutable) {
+  InputGlobal *g = createGlobal(name, isMutable);
+  return symtab->addOptionalGlobalSymbols(name, WASM_SYMBOL_VISIBILITY_HIDDEN,
+                                          g);
 }
 
 // Create ABI-defined synthetic symbols
@@ -602,7 +611,7 @@ static void createSyntheticSymbols() {
     WasmSym::tableBase->markLive();
   } else {
     // For non-PIC code
-    WasmSym::stackPointer = createGlobalVariable("__stack_pointer", true, 0);
+    WasmSym::stackPointer = createGlobalVariable("__stack_pointer", true);
     WasmSym::stackPointer->markLive();
   }
 
@@ -616,9 +625,9 @@ static void createSyntheticSymbols() {
     WasmSym::initMemoryFlag = symtab->addSyntheticDataSymbol(
         "__wasm_init_memory_flag", WASM_SYMBOL_VISIBILITY_HIDDEN);
     assert(WasmSym::initMemoryFlag);
-    WasmSym::tlsBase = createGlobalVariable("__tls_base", true, 0);
-    WasmSym::tlsSize = createGlobalVariable("__tls_size", false, 0);
-    WasmSym::tlsAlign = createGlobalVariable("__tls_align", false, 1);
+    WasmSym::tlsBase = createGlobalVariable("__tls_base", true);
+    WasmSym::tlsSize = createGlobalVariable("__tls_size", false);
+    WasmSym::tlsAlign = createGlobalVariable("__tls_align", false);
     WasmSym::initTLS = symtab->addSyntheticFunction(
         "__wasm_init_tls", WASM_SYMBOL_VISIBILITY_HIDDEN,
         make<SyntheticFunction>(
@@ -642,6 +651,18 @@ static void createOptionalSymbols() {
     WasmSym::definedMemoryBase = symtab->addOptionalDataSymbol("__memory_base");
     WasmSym::definedTableBase = symtab->addOptionalDataSymbol("__table_base");
   }
+
+  // For non-shared memory programs we still need to define __tls_base since we
+  // allow object files built with TLS to be linked into single threaded
+  // programs, and such object files can contains refernced to this symbol.
+  //
+  // However, in this case __tls_base is immutable and points directly to the
+  // start of the `.tdata` static segment.
+  //
+  // __tls_size and __tls_align are not needed in this case since they are only
+  // needed for __wasm_init_tls (which we do not create in this case).
+  if (!config->sharedMemory)
+    WasmSym::tlsBase = createOptionalGlobal("__tls_base", false);
 }
 
 // Reconstructs command line arguments so that so that you can re-run
index cb152d5..ce11985 100644 (file)
@@ -206,7 +206,7 @@ DefinedFunction *SymbolTable::addSyntheticFunction(StringRef name,
                                         flags, nullptr, function);
 }
 
-// Adds an optional, linker generated, data symbols.  The symbol will only be
+// Adds an optional, linker generated, data symbol.  The symbol will only be
 // added if there is an undefine reference to it, or if it is explicitly
 // exported via the --export flag.  Otherwise we don't add the symbol and return
 // nullptr.
@@ -241,6 +241,18 @@ DefinedGlobal *SymbolTable::addSyntheticGlobal(StringRef name, uint32_t flags,
                                       nullptr, global);
 }
 
+DefinedGlobal *SymbolTable::addOptionalGlobalSymbols(StringRef name,
+                                                     uint32_t flags,
+                                                     InputGlobal *global) {
+  LLVM_DEBUG(dbgs() << "addOptionalGlobalSymbols: " << name << " -> " << global
+                    << "\n");
+  Symbol *s = find(name);
+  if (!s || s->isDefined())
+    return nullptr;
+  syntheticGlobals.emplace_back(global);
+  return replaceSymbol<DefinedGlobal>(s, name, flags, nullptr, global);
+}
+
 static bool shouldReplace(const Symbol *existing, InputFile *newFile,
                           uint32_t newFlags) {
   // If existing symbol is undefined, replace it.
index fe26f70..08978d3 100644 (file)
@@ -83,6 +83,8 @@ public:
   DefinedFunction *addSyntheticFunction(StringRef name, uint32_t flags,
                                         InputFunction *function);
   DefinedData *addOptionalDataSymbol(StringRef name, uint64_t value = 0);
+  DefinedGlobal *addOptionalGlobalSymbols(StringRef name, uint32_t flags,
+                                          InputGlobal *global);
 
   void handleSymbolVariants();
   void handleWeakUndefines();
index 975f173..744c217 100644 (file)
@@ -204,6 +204,16 @@ void Writer::writeSections() {
   });
 }
 
+static void setGlobalPtr(DefinedGlobal *g, uint64_t memoryPtr) {
+  if (config->is64.getValueOr(false)) {
+    assert(g->global->global.InitExpr.Opcode == WASM_OPCODE_I64_CONST);
+    g->global->global.InitExpr.Value.Int64 = memoryPtr;
+  } else {
+    assert(g->global->global.InitExpr.Opcode == WASM_OPCODE_I32_CONST);
+    g->global->global.InitExpr.Value.Int32 = memoryPtr;
+  }
+}
+
 // 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.
@@ -267,18 +277,21 @@ void Writer::layoutMemory() {
     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;
 
-    if (WasmSym::tlsSize && seg->name == ".tdata") {
-      auto *tlsSize = cast<DefinedGlobal>(WasmSym::tlsSize);
-      assert(tlsSize->global->global.InitExpr.Opcode == WASM_OPCODE_I32_CONST);
-      tlsSize->global->global.InitExpr.Value.Int32 = seg->size;
+    if (seg->name == ".tdata") {
+      if (config->sharedMemory) {
+        auto *tlsSize = cast<DefinedGlobal>(WasmSym::tlsSize);
+        setGlobalPtr(tlsSize, seg->size);
 
-      auto *tlsAlign = cast<DefinedGlobal>(WasmSym::tlsAlign);
-      assert(tlsAlign->global->global.InitExpr.Opcode == WASM_OPCODE_I32_CONST);
-      tlsAlign->global->global.InitExpr.Value.Int32 = int64_t{1}
-                                                      << seg->alignment;
+        auto *tlsAlign = cast<DefinedGlobal>(WasmSym::tlsAlign);
+        setGlobalPtr(tlsAlign, int64_t{1} << seg->alignment);
+      } else {
+        auto *tlsBase = cast<DefinedGlobal>(WasmSym::tlsBase);
+        setGlobalPtr(tlsBase, memoryPtr);
+      }
     }
+
+    memoryPtr += seg->size;
   }
 
   // Make space for the memory initialization flag
@@ -768,7 +781,7 @@ void Writer::createOutputSegments() {
       if (s == nullptr) {
         LLVM_DEBUG(dbgs() << "new segment: " << name << "\n");
         s = make<OutputSegment>(name);
-        if (config->sharedMemory || name == ".tdata")
+        if (config->sharedMemory)
           s->initFlags = WASM_SEGMENT_IS_PASSIVE;
         // Exported memories are guaranteed to be zero-initialized, so no need
         // to emit data segments for bss sections.