From 29a3056bb5346d9b38e4dcb13700576efbff9425 Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Mon, 9 Nov 2020 17:52:39 -0800 Subject: [PATCH] [lld][WebAssembly] Allow references to __tls_base without shared memory 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 | 2 +- lld/test/wasm/tls-no-shared.s | 75 +++++++++++++++++++++++++++++++++++++++++++ lld/wasm/Driver.cpp | 41 +++++++++++++++++------ lld/wasm/SymbolTable.cpp | 14 +++++++- lld/wasm/SymbolTable.h | 2 ++ lld/wasm/Writer.cpp | 33 +++++++++++++------ 6 files changed, 145 insertions(+), 22 deletions(-) create mode 100644 lld/test/wasm/tls-no-shared.s diff --git a/lld/test/wasm/no-tls.s b/lld/test/wasm/no-tls.s index d1ba766..74b9331 100644 --- a/lld/test/wasm/no-tls.s +++ b/lld/test/wasm/no-tls.s @@ -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 index 0000000..3fdc705 --- /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 index fb00355..5b12b48 100644 --- a/lld/wasm/Driver.cpp +++ b/lld/wasm/Driver.cpp @@ -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(wasmGlobal, nullptr)); + return make(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( @@ -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 diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp index cb152d5..ce11985 100644 --- a/lld/wasm/SymbolTable.cpp +++ b/lld/wasm/SymbolTable.cpp @@ -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(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/SymbolTable.h b/lld/wasm/SymbolTable.h index fe26f70..08978d3 100644 --- a/lld/wasm/SymbolTable.h +++ b/lld/wasm/SymbolTable.h @@ -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(); diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp index 975f173..744c217 100644 --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -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(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,7 +781,7 @@ void Writer::createOutputSegments() { if (s == nullptr) { LLVM_DEBUG(dbgs() << "new segment: " << name << "\n"); s = make(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. -- 2.7.4