Commit ab58e4cb authored by Sam Clegg's avatar Sam Clegg
Browse files

[lld][WebAssembly] Add suppport for PIC + passive data initialization

This change improves our support for shared memory to include
PIC executables (and shared libraries).

To handle this case the linker-generated `__wasm_init_memory`
function (that only exists in shared memory builds) must be
capable of loading memory segements at non-const offsets based
on the runtime value of `__memory_base`.

Differential Revision: https://reviews.llvm.org/D92620
parent 2518433f
Loading
Loading
Loading
Loading
+1 −2
Original line number Diff line number Diff line
@@ -18,8 +18,7 @@ config.name = 'lld'

# testFormat: The test format to use to interpret tests.
#
# For now we require '&&' between commands, until they get globally killed and
# the test runner updated.
# For now we require '&&' between commands, until they get globally killed and the test runner updated.
config.test_format = lit.formats.ShTest(not llvm_config.use_lit_shell)

# suffixes: A list of file extensions to treat as test files.
+46 −1
Original line number Diff line number Diff line
@@ -2,6 +2,8 @@
; RUN: llc --mtriple=wasm32-unknown-unknown -filetype=obj %s -o %t.bulk-mem.o -mattr=+bulk-memory
; RUN: llc --mtriple=wasm32-unknown-unknown -filetype=obj %s -o %t.atomics.bulk-mem.o -mattr=+atomics,+bulk-memory
; RUN: llc --mtriple=wasm64-unknown-unknown -filetype=obj %s -o %t.atomics.bulk-mem64.o -mattr=+atomics,+bulk-memory
; RUN: llc --mtriple=wasm32-unknown-unknown -filetype=obj %s -o %t.atomics.bulk-mem.pic.o -relocation-model=pic -mattr=+atomics,+bulk-memory,+mutable-globals
; RUN: llc --mtriple=wasm64-unknown-unknown -filetype=obj %s -o %t.atomics.bulk-mem.pic-mem64.o -relocation-model=pic -mattr=+atomics,+bulk-memory,+mutable-globals

; atomics, shared memory => error
; RUN: not wasm-ld -no-gc-sections --no-entry --shared-memory --max-memory=131072 %t.atomics.o -o %t.atomics.wasm 2>&1 | FileCheck %s --check-prefix ERROR
@@ -19,8 +21,12 @@
; RUN: obj2yaml %t.atomics.bulk-mem64.wasm | FileCheck %s --check-prefixes PASSIVE,PASSIVE64

; Also test in combination with PIC/pie
; RUN: llc --mtriple=wasm32-unknown-unknown -filetype=obj -relocation-model=pic %s -o %t.atomics.bulk-mem.pic.o -mattr=+atomics,+bulk-memory,+mutable-globals
; RUN: wasm-ld --experimental-pic -pie -no-gc-sections --no-entry --shared-memory --max-memory=131072 %t.atomics.bulk-mem.pic.o -o %t.pic.wasm
; RUN: obj2yaml %t.pic.wasm | FileCheck %s --check-prefixes PASSIVE-PIC,PASSIVE32-PIC

; Also test in combination with PIC/pie + wasm64
; RUN: wasm-ld -mwasm64 --experimental-pic -pie -no-gc-sections --no-entry --shared-memory --max-memory=131072 %t.atomics.bulk-mem.pic-mem64.o -o %t.pic-mem64.wasm
; RUN: obj2yaml %t.pic-mem64.wasm | FileCheck %s --check-prefixes PASSIVE-PIC,PASSIVE64-PIC

@a = hidden global [6 x i8] c"hello\00", align 1
@b = hidden global [8 x i8] c"goodbye\00", align 1
@@ -91,3 +97,42 @@
; PASSIVE-NEXT:        Name:            __wasm_init_memory
; PASSIVE-NEXT:      - Index:           2
; PASSIVE-NEXT:        Name:            __wasm_init_tls

;      PASSIVE-PIC:  - Type:            START
; 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:      - Index:           1
; PASSIVE-PIC-NEXT:        Locals:          []
; PASSIVE-PIC-NEXT:        Body:            0B
; PASSIVE-PIC-NEXT:      - Index:           2
; 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:        InitFlags:       1

;      PASSIVE-PIC:  - Type:            CUSTOM
; PASSIVE-PIC-NEXT:    Name:            name
; PASSIVE-PIC-NEXT:    FunctionNames:
; 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_memory
; PASSIVE-PIC-NEXT:      - Index:           3
; PASSIVE-PIC-NEXT:        Name:            __wasm_init_tls
+1 −1
Original line number Diff line number Diff line
@@ -645,7 +645,7 @@ static void createSyntheticSymbols() {
    WasmSym::stackPointer->markLive();
  }

  if (config->sharedMemory && !config->shared) {
  if (config->sharedMemory) {
    // 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
+1 −1
Original line number Diff line number Diff line
@@ -372,7 +372,7 @@ void ExportSection::writeBody() {
}

bool StartSection::isNeeded() const {
  return !config->relocatable && hasInitializedSegments && config->sharedMemory;
  return WasmSym::initMemory != nullptr;
}

void StartSection::writeBody() {
+40 −10
Original line number Diff line number Diff line
@@ -869,10 +869,24 @@ void Writer::createInitMemoryFunction() {
  LLVM_DEBUG(dbgs() << "createInitMemoryFunction\n");
  assert(WasmSym::initMemoryFlag);
  uint64_t flagAddress = WasmSym::initMemoryFlag->getVirtualAddress();
  bool is64 = config->is64.getValueOr(false);
  std::string bodyContent;
  {
    raw_string_ostream os(bodyContent);
    // With PIC code we cache the flag address in local 0
    if (config->isPic) {
      writeUleb128(os, 1, "num local decls");
      writeUleb128(os, 1, "local count");
      writeU8(os, is64 ? WASM_TYPE_I64 : WASM_TYPE_I32, "address type");
      writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
      writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), "memory_base");
      writePtrConst(os, flagAddress, is64, "flag address");
      writeU8(os, WASM_OPCODE_I32_ADD, "add");
      writeU8(os, WASM_OPCODE_LOCAL_SET, "local.set");
      writeUleb128(os, 0, "local 0");
    } else {
      writeUleb128(os, 0, "num locals");
    }

    if (hasPassiveInitializedSegments()) {
      // Initialize memory in a thread-safe manner. The thread that successfully
@@ -916,11 +930,24 @@ void Writer::createInitMemoryFunction() {
      //  )
      //  ( ... drop data segments ... )
      // )
      //
      // When we are building with PIC, calculate the flag location using:
      //
      //    (global.get $__memory_base)
      //    (i32.const $__init_memory_flag)
      //    (i32.const 1)

      bool is64 = config->is64.getValueOr(false);
      auto writeGetFlagAddress = [&]() {
        if (config->isPic) {
          writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get");
          writeUleb128(os, 0, "local 0");
        } else {
          writePtrConst(os, flagAddress, is64, "flag address");
        }
      };

      // Atomically check whether this is the main thread.
      writePtrConst(os, flagAddress, is64, "flag address");
      writeGetFlagAddress();
      writeI32Const(os, 0, "expected flag value");
      writeI32Const(os, 1, "flag value");
      writeU8(os, WASM_OPCODE_ATOMICS_PREFIX, "atomics prefix");
@@ -930,7 +957,7 @@ void Writer::createInitMemoryFunction() {
      writeU8(os, WASM_TYPE_NORESULT, "blocktype");

      // Did not increment 0, so wait for main thread to initialize memory
      writePtrConst(os, flagAddress, is64, "flag address");
      writeGetFlagAddress();
      writeI32Const(os, 1, "expected flag value");
      writeI64Const(os, -1, "timeout");

@@ -946,6 +973,12 @@ void Writer::createInitMemoryFunction() {
        if (needsPassiveInitialization(s)) {
          // destination address
          writePtrConst(os, s->startVA, is64, "destination address");
          if (config->isPic) {
            writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
            writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(),
                         "memory_base");
            writeU8(os, WASM_OPCODE_I32_ADD, "i32.add");
          }
          // source segment offset
          writeI32Const(os, 0, "segment offset");
          // memory region size
@@ -959,14 +992,14 @@ void Writer::createInitMemoryFunction() {
      }

      // Set flag to 2 to mark end of initialization
      writePtrConst(os, flagAddress, is64, "flag address");
      writeGetFlagAddress();
      writeI32Const(os, 2, "flag value");
      writeU8(os, WASM_OPCODE_ATOMICS_PREFIX, "atomics prefix");
      writeUleb128(os, WASM_OPCODE_I32_ATOMIC_STORE, "i32.atomic.store");
      writeMemArg(os, 2, 0);

      // Notify any waiters that memory initialization is complete
      writePtrConst(os, flagAddress, is64, "flag address");
      writeGetFlagAddress();
      writeI32Const(os, -1, "number of waiters");
      writeU8(os, WASM_OPCODE_ATOMICS_PREFIX, "atomics prefix");
      writeUleb128(os, WASM_OPCODE_ATOMIC_NOTIFY, "atomic.notify");
@@ -1095,9 +1128,6 @@ void Writer::createCommandExportWrapper(uint32_t functionIndex,
}

void Writer::createInitTLSFunction() {
  if (!WasmSym::initTLS->isLive())
    return;

  std::string bodyContent;
  {
    raw_string_ostream os(bodyContent);
@@ -1244,7 +1274,7 @@ void Writer::run() {
    }
  }

  if (!config->relocatable && config->sharedMemory && !config->shared)
  if (WasmSym::initTLS && WasmSym::initTLS->isLive())
    createInitTLSFunction();

  if (errorCount())
Loading