[lld] 86c90f9 - [lld][WebAssembly] Add --unresolved-symbols=import-dynamic

Sam Clegg via llvm-commits llvm-commits at lists.llvm.org
Tue Mar 15 15:13:49 PDT 2022


Author: Sam Clegg
Date: 2022-03-15T15:10:21-07:00
New Revision: 86c90f9bfdc4e9f02533196396a6ae363449b6a3

URL: https://github.com/llvm/llvm-project/commit/86c90f9bfdc4e9f02533196396a6ae363449b6a3
DIFF: https://github.com/llvm/llvm-project/commit/86c90f9bfdc4e9f02533196396a6ae363449b6a3.diff

LOG: [lld][WebAssembly] Add --unresolved-symbols=import-dynamic

This is a new mode for handling unresolved symbols that allows all
symbols to be imported in the same that they would be in the case of
`-fpie` or `-shared`, but generting an otherwise fixed/non-relocatable
binary.

Code linked in this way should still be compiled with `-fPIC` so that
data symbols can be resolved via imports.

This essentially allows the building of static binaries that have
dynamic imports.  See:
https://github.com/emscripten-core/emscripten/issues/12682

As with other uses of the experimental dynamic linking ABI, this
behaviour will produce a warning unless run with `--experimental-pic`.

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

Added: 
    lld/test/wasm/unresolved-symbols-dynamic.s

Modified: 
    lld/docs/WebAssembly.rst
    lld/test/wasm/shared.s
    lld/test/wasm/shared64.s
    lld/test/wasm/undefined-data.s
    lld/test/wasm/unresolved-symbols.s
    lld/wasm/Config.h
    lld/wasm/Driver.cpp
    lld/wasm/InputChunks.cpp
    lld/wasm/Relocations.cpp
    lld/wasm/SyntheticSections.cpp
    lld/wasm/SyntheticSections.h
    lld/wasm/Writer.cpp

Removed: 
    


################################################################################
diff  --git a/lld/docs/WebAssembly.rst b/lld/docs/WebAssembly.rst
index ea8917ffc97d1..c40d4b322080a 100644
--- a/lld/docs/WebAssembly.rst
+++ b/lld/docs/WebAssembly.rst
@@ -92,6 +92,21 @@ WebAssembly-specific options:
      this is trivial.  For direct function calls, the linker will generate a
      trapping stub function in place of the undefined function.
 
+  import-dynamic:
+
+     Undefined symbols generate WebAssembly imports, including undefined data
+     symbols.  This is somewhat similar to the --import-undefined option but
+     works all symbol types.  This options puts limitations on the type of
+     relocations that are allowed for imported data symbols.  Relocations that
+     require absolute data addresses (i.e. All R_WASM_MEMORY_ADDR_I32) will
+     generate an error if they cannot be resolved statically.  For clang/llvm
+     this means inputs should be compiled with `-fPIC` (i.e. `pic` or
+     `dynamic-no-pic` relocation models).  This options is useful for linking
+     binaries that are themselves static (non-relocatable) but whose undefined
+     symbols are resolved by a dynamic linker.  Since the dynamic linking API is
+     experimental, this option currently requires `--experimental-pic` to also
+     be specified.
+
 .. option:: --import-memory
 
   Import memory from the environment.

diff  --git a/lld/test/wasm/shared.s b/lld/test/wasm/shared.s
index 01dc8d51474d9..086f228308de9 100644
--- a/lld/test/wasm/shared.s
+++ b/lld/test/wasm/shared.s
@@ -214,32 +214,32 @@ get_local_func_address:
 
 # DIS:      <__wasm_apply_data_relocs>:
 # DIS-EMPTY:
-# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i32.const       4
+# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i32.add
 # DIS-NEXT:                 global.get      4
 # DIS-NEXT:                 i32.store       0
-# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i32.const       8
+# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i32.add
 # DIS-NEXT:                 global.get      2
 # DIS-NEXT:                 i32.const       1
 # DIS-NEXT:                 i32.add
 # DIS-NEXT:                 i32.store       0
-# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i32.const       12
+# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i32.add
 # DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i32.const       0
 # DIS-NEXT:                 i32.add
 # DIS-NEXT:                 i32.store       0
-# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i32.const       16
+# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i32.add
 # DIS-NEXT:                 global.get      5
 # DIS-NEXT:                 i32.store       0
-# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i32.const       20
+# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i32.add
 # DIS-NEXT:                 global.get      6
 # DIS-NEXT:                 i32.const       4

diff  --git a/lld/test/wasm/shared64.s b/lld/test/wasm/shared64.s
index 080b05213a04a..c5a813ea98f34 100644
--- a/lld/test/wasm/shared64.s
+++ b/lld/test/wasm/shared64.s
@@ -221,32 +221,32 @@ get_local_func_address:
 
 # DIS:      <__wasm_apply_data_relocs>:
 # DIS-EMPTY:
-# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i64.const       4
+# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i64.add
 # DIS-NEXT:                 global.get      5
 # DIS-NEXT:                 i64.store       0:p2align=2
-# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i64.const       12
+# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i64.add
 # DIS-NEXT:                 global.get      2
 # DIS-NEXT:                 i64.const       1
 # DIS-NEXT:                 i64.add
 # DIS-NEXT:                 i64.store       0:p2align=2
-# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i64.const       20
+# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i64.add
 # DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i32.const       0
 # DIS-NEXT:                 i32.add
 # DIS-NEXT:                 i32.store       0
-# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i64.const       24
+# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i64.add
 # DIS-NEXT:                 global.get      6
 # DIS-NEXT:                 i64.store       0:p2align=2
-# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i64.const       32
+# DIS-NEXT:                 global.get      1
 # DIS-NEXT:                 i64.add
 # DIS-NEXT:                 global.get      7
 # DIS-NEXT:                 i32.const       4

diff  --git a/lld/test/wasm/undefined-data.s b/lld/test/wasm/undefined-data.s
index edb8917bc125e..d63b667c4ea38 100644
--- a/lld/test/wasm/undefined-data.s
+++ b/lld/test/wasm/undefined-data.s
@@ -13,4 +13,4 @@ _start:
 .size data_external, 4
 
 # UNDEF: error: {{.*}}undefined-data.s.tmp.o: undefined symbol: data_external
-# SHARED: error: {{.*}}undefined-data.s.tmp.o: relocation R_WASM_MEMORY_ADDR_LEB cannot be used against symbol data_external; recompile with -fPIC
+# SHARED: error: {{.*}}undefined-data.s.tmp.o: relocation R_WASM_MEMORY_ADDR_LEB cannot be used against symbol `data_external`; recompile with -fPIC

diff  --git a/lld/test/wasm/unresolved-symbols-dynamic.s b/lld/test/wasm/unresolved-symbols-dynamic.s
new file mode 100644
index 0000000000000..c3d4a753acf60
--- /dev/null
+++ b/lld/test/wasm/unresolved-symbols-dynamic.s
@@ -0,0 +1,84 @@
+# Unresolve data symbols are allowing under import-dynamic when GOT
+# relocations are used
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %s -o %t-dynamic.o
+# RUN: wasm-ld %t-dynamic.o -o %t.wasm --unresolved-symbols=import-dynamic 2>&1 | FileCheck -check-prefix=WARN %s
+# WARN: wasm-ld: warning: dynamic imports are not yet stable (--unresolved-symbols=import-dynamic)
+# RUN: obj2yaml %t.wasm | FileCheck %s
+
+.functype undef () -> ()
+
+.globl get_data_addr
+get_data_addr:
+    .functype get_data_addr () -> (i32)
+    global.get undef_data at GOT
+    return
+    end_function
+
+.globl get_func_addr
+get_func_addr:
+    .functype get_func_addr () -> (i32)
+    global.get undef at GOT
+    return
+    end_function
+
+.globl _start
+_start:
+    .functype _start () -> ()
+    call undef
+    call get_func_addr
+    drop
+    call get_data_addr
+    i32.load data_ptr
+    drop
+    end_function
+
+.section  .data.data_ptr,"",@
+data_ptr:
+  .int32  data_external+42
+  .size data_ptr, 4
+
+.size data_external, 4
+
+#      CHECK:  - Type:            IMPORT
+# CHECK-NEXT:    Imports:
+# CHECK-NEXT:      - Module:          env
+# CHECK-NEXT:        Field:           undef
+# CHECK-NEXT:        Kind:            FUNCTION
+# CHECK-NEXT:        SigIndex:        0
+# CHECK-NEXT:      - Module:          GOT.mem
+# CHECK-NEXT:        Field:           undef_data
+# CHECK-NEXT:        Kind:            GLOBAL
+# CHECK-NEXT:        GlobalType:      I32
+# CHECK-NEXT:        GlobalMutable:   true
+# CHECK-NEXT:      - Module:          GOT.func
+# CHECK-NEXT:        Field:           undef
+# CHECK-NEXT:        Kind:            GLOBAL
+# CHECK-NEXT:        GlobalType:      I32
+# CHECK-NEXT:        GlobalMutable:   true
+
+#      CHECK:  - Type:            CUSTOM
+# CHECK-NEXT:    Name:            name
+# CHECK-NEXT:    FunctionNames:
+# CHECK-NEXT:      - Index:           0
+# CHECK-NEXT:        Name:            undef
+# CHECK-NEXT:      - Index:           1
+# CHECK-NEXT:        Name:            __wasm_apply_data_relocs
+# CHECK-NEXT:      - Index:           2
+# CHECK-NEXT:        Name:            get_data_addr
+# CHECK-NEXT:      - Index:           3
+# CHECK-NEXT:        Name:            get_func_addr
+# CHECK-NEXT:      - Index:           4
+# CHECK-NEXT:        Name:            _start
+# CHECK-NEXT:    GlobalNames:
+# CHECK-NEXT:      - Index:           0
+# CHECK-NEXT:        Name:            undef_data
+# CHECK-NEXT:      - Index:           1
+# CHECK-NEXT:        Name:            undef
+# CHECK-NEXT:      - Index:           2
+# CHECK-NEXT:        Name:            data_external
+# CHECK-NEXT:      - Index:           3
+# CHECK-NEXT:        Name:            __stack_pointer
+# CHECK-NEXT:    DataSegmentNames:
+# CHECK-NEXT:      - Index:           0
+# CHECK-NEXT:        Name:            .data
+# CHECK-NEXT:...

diff  --git a/lld/test/wasm/unresolved-symbols.s b/lld/test/wasm/unresolved-symbols.s
index 81c94a32f3b2c..5de54d76c6de8 100644
--- a/lld/test/wasm/unresolved-symbols.s
+++ b/lld/test/wasm/unresolved-symbols.s
@@ -79,6 +79,11 @@
 .functype get_data_addr () -> (i32)
 .functype get_func_addr () -> (i32)
 
+## import-dynamic should fail due to incompatible relocations.
+# RUN: not wasm-ld %t1.o -o %t5.wasm --unresolved-symbols=import-dynamic 2>&1 | FileCheck -check-prefix=ERRNOPIC %s
+# ERRNOPIC: relocation R_WASM_MEMORY_ADDR_SLEB cannot be used against symbol `undef_data`; recompile with -fPIC
+# ERRNOPIC: relocation R_WASM_TABLE_INDEX_SLEB cannot be used against symbol `undef_func`; recompile with -fPIC
+
 .globl _start
 _start:
     .functype _start () -> ()

diff  --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index b4172ff1737cb..2cde65144ce66 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -18,7 +18,7 @@ namespace lld {
 namespace wasm {
 
 // For --unresolved-symbols.
-enum class UnresolvedPolicy { ReportError, Warn, Ignore };
+enum class UnresolvedPolicy { ReportError, Warn, Ignore, ImportDynamic };
 
 // This struct contains the global configuration for the linker.
 // Most fields are direct mapping from the command line options

diff  --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 8ef86206a9b15..6d84b604796f0 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -335,6 +335,8 @@ static UnresolvedPolicy getUnresolvedSymbolPolicy(opt::InputArgList &args) {
     StringRef s = arg->getValue();
     if (s == "ignore-all")
       return UnresolvedPolicy::Ignore;
+    if (s == "import-dynamic")
+      return UnresolvedPolicy::ImportDynamic;
     if (s == "report-all")
       return errorOrWarn;
     error("unknown --unresolved-symbols value: " + s);
@@ -528,6 +530,11 @@ static void checkOptions(opt::InputArgList &args) {
     if (config->pie) {
       warn("creating PIEs, with -pie, is not yet stable");
     }
+
+    if (config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic) {
+      warn("dynamic imports are not yet stable "
+           "(--unresolved-symbols=import-dynamic)");
+    }
   }
 
   if (config->bsymbolic && !config->shared) {

diff  --git a/lld/wasm/InputChunks.cpp b/lld/wasm/InputChunks.cpp
index 242378504c3b8..332382c907b7c 100644
--- a/lld/wasm/InputChunks.cpp
+++ b/lld/wasm/InputChunks.cpp
@@ -376,19 +376,26 @@ void InputChunk::generateRelocationCode(raw_ostream &os) const {
   for (const WasmRelocation &rel : relocations) {
     uint64_t offset = getVA(rel.Offset) - getInputSectionOffset();
 
+    Symbol *sym = file->getSymbol(rel);
+    if (!config->isPic && sym->isDefined())
+      continue;
+
     LLVM_DEBUG(dbgs() << "gen reloc: type=" << relocTypeToString(rel.Type)
                       << " addend=" << rel.Addend << " index=" << rel.Index
                       << " output offset=" << offset << "\n");
 
-    // Get __memory_base
-    writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
-    writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), "memory_base");
-
-    // Add the offset of the relocation
+    // Calculate the address at which to apply the relocations
     writeU8(os, opcode_ptr_const, "CONST");
     writeSleb128(os, offset, "offset");
-    writeU8(os, opcode_ptr_add, "ADD");
 
+    // In PIC mode we need to add the __memory_base
+    if (config->isPic) {
+      writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
+      writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), "memory_base");
+      writeU8(os, opcode_ptr_add, "ADD");
+    }
+
+    // Now figure out what we want to store at this location
     bool is64 = relocIs64(rel.Type);
     unsigned opcode_reloc_const =
         is64 ? WASM_OPCODE_I64_CONST : WASM_OPCODE_I32_CONST;
@@ -397,8 +404,6 @@ void InputChunk::generateRelocationCode(raw_ostream &os) const {
     unsigned opcode_reloc_store =
         is64 ? WASM_OPCODE_I64_STORE : WASM_OPCODE_I32_STORE;
 
-    Symbol *sym = file->getSymbol(rel);
-    // Now figure out what we want to store
     if (sym->hasGOTIndex()) {
       writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
       writeUleb128(os, sym->getGOTIndex(), "global index");
@@ -408,6 +413,7 @@ void InputChunk::generateRelocationCode(raw_ostream &os) const {
         writeU8(os, opcode_reloc_add, "ADD");
       }
     } else {
+      assert(config->isPic);
       const GlobalSymbol* baseSymbol = WasmSym::memoryBase;
       if (rel.Type == R_WASM_TABLE_INDEX_I32 ||
           rel.Type == R_WASM_TABLE_INDEX_I64)

diff  --git a/lld/wasm/Relocations.cpp b/lld/wasm/Relocations.cpp
index 0109b6743f5c6..c7710a9baf320 100644
--- a/lld/wasm/Relocations.cpp
+++ b/lld/wasm/Relocations.cpp
@@ -20,7 +20,8 @@ namespace lld {
 namespace wasm {
 
 static bool requiresGOTAccess(const Symbol *sym) {
-  if (!config->isPic)
+  if (!config->isPic &&
+      config->unresolvedSymbols != UnresolvedPolicy::ImportDynamic)
     return false;
   if (sym->isHidden() || sym->isLocal())
     return false;
@@ -66,6 +67,8 @@ static void reportUndefined(Symbol *sym) {
         }
       }
       break;
+    case UnresolvedPolicy::ImportDynamic:
+      break;
     }
   }
 }
@@ -139,7 +142,9 @@ void scanRelocations(InputChunk *chunk) {
       break;
     }
 
-    if (config->isPic) {
+    if (config->isPic ||
+        (sym->isUndefined() &&
+         config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic)) {
       switch (reloc.Type) {
       case R_WASM_TABLE_INDEX_SLEB:
       case R_WASM_TABLE_INDEX_SLEB64:
@@ -150,8 +155,8 @@ void scanRelocations(InputChunk *chunk) {
         // Certain relocation types can't be used when building PIC output,
         // since they would require absolute symbol addresses at link time.
         error(toString(file) + ": relocation " + relocTypeToString(reloc.Type) +
-              " cannot be used against symbol " + toString(*sym) +
-              "; recompile with -fPIC");
+              " cannot be used against symbol `" + toString(*sym) +
+              "`; recompile with -fPIC");
         break;
       case R_WASM_TABLE_INDEX_I32:
       case R_WASM_TABLE_INDEX_I64:

diff  --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp
index a0a8d597118d3..c47c58ce1961b 100644
--- a/lld/wasm/SyntheticSections.cpp
+++ b/lld/wasm/SyntheticSections.cpp
@@ -54,6 +54,12 @@ class SubSection {
 
 } // namespace
 
+bool DylinkSection::isNeeded() const {
+  return config->isPic ||
+         config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic ||
+         !symtab->sharedFiles.empty();
+}
+
 void DylinkSection::writeBody() {
   raw_ostream &os = bodyOutputStream;
 

diff  --git a/lld/wasm/SyntheticSections.h b/lld/wasm/SyntheticSections.h
index 9a425ea4f6ec3..cb3b1888f1fa8 100644
--- a/lld/wasm/SyntheticSections.h
+++ b/lld/wasm/SyntheticSections.h
@@ -75,7 +75,7 @@ class SyntheticSection : public OutputSection {
 class DylinkSection : public SyntheticSection {
 public:
   DylinkSection() : SyntheticSection(llvm::wasm::WASM_SEC_CUSTOM, "dylink.0") {}
-  bool isNeeded() const override { return config->isPic; }
+  bool isNeeded() const override;
   void writeBody() override;
 
   uint32_t memAlign = 0;

diff  --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 621e48e7f273a..4465bb2e37b3d 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -597,7 +597,8 @@ static bool shouldImport(Symbol *sym) {
         return false;
   }
 
-  if (config->isPic || config->relocatable || config->importUndefined)
+  if (config->isPic || config->relocatable || config->importUndefined ||
+      config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic)
     return true;
   if (config->allowUndefinedSymbols.count(sym->getName()) != 0)
     return true;
@@ -1004,21 +1005,22 @@ void Writer::createSyntheticInitFunctions() {
     WasmSym::applyGlobalTLSRelocs->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.
+  if (config->isPic ||
+      config->unresolvedSymbols == UnresolvedPolicy::ImportDynamic) {
+    // For PIC code, or when dynamically importing addresses, 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 (config->isPic && 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();
   }
 
   int startCount = 0;


        


More information about the llvm-commits mailing list