diff --git a/lld/test/wasm/tls-no-shared.s b/lld/test/wasm/tls-no-shared.s new file mode 100644 --- /dev/null +++ b/lld/test/wasm/tls-no-shared.s @@ -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 diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -541,8 +541,7 @@ return sym; } -static GlobalSymbol *createGlobalVariable(StringRef name, bool isMutable, - int value) { +static InputGlobal *createGlobal(StringRef name, bool isMutable, int value) { llvm::wasm::WasmGlobal wasmGlobal; if (config->is64.getValueOr(false)) { wasmGlobal.Type = {WASM_TYPE_I64, isMutable}; @@ -554,8 +553,20 @@ wasmGlobal.InitExpr.Opcode = WASM_OPCODE_I32_CONST; } wasmGlobal.SymbolName = name; - return symtab->addSyntheticGlobal(name, WASM_SYMBOL_VISIBILITY_HIDDEN, - make(wasmGlobal, nullptr)); + return make(wasmGlobal, nullptr); +} + +static GlobalSymbol *createGlobalVariable(StringRef name, bool isMutable, + int value) { + InputGlobal *g = createGlobal(name, isMutable, value); + return symtab->addSyntheticGlobal(name, WASM_SYMBOL_VISIBILITY_HIDDEN, g); +} + +static GlobalSymbol *createOptionalGlobal(StringRef name, bool isMutable, + int value) { + InputGlobal *g = createGlobal(name, isMutable, value); + return symtab->addOptionalGlobalSymbols(name, WASM_SYMBOL_VISIBILITY_HIDDEN, + g); } // Create ABI-defined synthetic symbols @@ -642,6 +653,19 @@ WasmSym::definedMemoryBase = symtab->addOptionalDataSymbol("__memory_base"); WasmSym::definedTableBase = symtab->addOptionalDataSymbol("__table_base"); } + + if (!config->sharedMemory) { + Symbol *s = symtab->find("__tls_base"); + if (s && s->isUndefined()) { + // 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. + // + // However in this case __tls_base is immutable points to part of the + // static data region. + WasmSym::tlsBase = createOptionalGlobal("__tls_base", false, 0); + } + } } // Reconstructs command line arguments so that so that you can re-run diff --git a/lld/wasm/SymbolTable.h b/lld/wasm/SymbolTable.h --- a/lld/wasm/SymbolTable.h +++ b/lld/wasm/SymbolTable.h @@ -83,6 +83,8 @@ 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(); diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp --- a/lld/wasm/SymbolTable.cpp +++ b/lld/wasm/SymbolTable.cpp @@ -206,7 +206,7 @@ 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 @@ 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(s, name, flags, nullptr, global); +} + static bool shouldReplace(const Symbol *existing, InputFile *newFile, uint32_t newFlags) { // If existing symbol is undefined, replace it. diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -204,6 +204,16 @@ }); } +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 @@ 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(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(WasmSym::tlsSize); + setGlobalPtr(tlsSize, seg->size); - auto *tlsAlign = cast(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(WasmSym::tlsAlign); + setGlobalPtr(tlsAlign, int64_t{1} << seg->alignment); + } else { + auto *tlsBase = cast(WasmSym::tlsBase); + setGlobalPtr(tlsBase, memoryPtr); + } } + + memoryPtr += seg->size; } // Make space for the memory initialization flag @@ -768,8 +781,10 @@ if (s == nullptr) { LLVM_DEBUG(dbgs() << "new segment: " << name << "\n"); s = make(name); - if (config->sharedMemory || name == ".tdata") + if (config->sharedMemory) { + LLVM_DEBUG(dbgs() << " -> passive\n"); s->initFlags = WASM_SEGMENT_IS_PASSIVE; + } // Exported memories are guaranteed to be zero-initialized, so no need // to emit data segments for bss sections. // TODO: consider initializing bss sections with memory.fill