diff --git a/lld/test/wasm/pie.ll b/lld/test/wasm/pie.ll --- a/lld/test/wasm/pie.ll +++ b/lld/test/wasm/pie.ll @@ -91,7 +91,7 @@ ; RUN: obj2yaml %t.shmem.wasm | FileCheck %s --check-prefix=SHMEM ; SHMEM: - Type: CODE -; SHMEM: - Index: 6 +; SHMEM: - Index: 7 ; SHMEM-NEXT: Locals: [] ; SHMEM-NEXT: Body: 100310050B @@ -109,11 +109,13 @@ ; SHMEM-NEXT: - Index: 5 ; SHMEM-NEXT: Name: __wasm_apply_global_relocs ; SHMEM-NEXT: - Index: 6 -; SHMEM-NEXT: Name: __wasm_start +; SHMEM-NEXT: Name: __wasm_apply_global_tls_relocs ; SHMEM-NEXT: - Index: 7 -; SHMEM-NEXT: Name: foo +; SHMEM-NEXT: Name: __wasm_start ; SHMEM-NEXT: - Index: 8 -; SHMEM-NEXT: Name: get_data_address +; SHMEM-NEXT: Name: foo ; SHMEM-NEXT: - Index: 9 +; SHMEM-NEXT: Name: get_data_address +; SHMEM-NEXT: - Index: 10 ; SHMEM-NEXT: Name: _start diff --git a/lld/test/wasm/tls-export.s b/lld/test/wasm/tls-export.s deleted file mode 100644 --- a/lld/test/wasm/tls-export.s +++ /dev/null @@ -1,26 +0,0 @@ -# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s -# RUN: wasm-ld -no-gc-sections --shared-memory --no-entry -o %t.wasm %t.o -# RUN: not wasm-ld --shared-memory --no-entry --export=tls1 -o %t.wasm %t.o 2>&1 | FileCheck %s -# With --export-all we ignore TLS symbols so we don't expect an error here -# RUN: wasm-ld --shared-memory --no-entry --export-all -o %t.wasm %t.o - -# CHECK: error: TLS symbols cannot yet be exported: `tls1` - -.section .tdata.tls1,"",@ -.globl tls1 -.p2align 2 -tls1: - .int32 1 - .size tls1, 4 - -.section .custom_section.target_features,"",@ - .int8 3 - .int8 43 - .int8 7 - .ascii "atomics" - .int8 43 - .int8 11 - .ascii "bulk-memory" - .int8 43 - .int8 15 - .ascii "mutable-globals" diff --git a/lld/test/wasm/tls-import.s b/lld/test/wasm/tls-import.s deleted file mode 100644 --- a/lld/test/wasm/tls-import.s +++ /dev/null @@ -1,23 +0,0 @@ -# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s -# RUN: not wasm-ld -shared --experimental-pic --shared-memory -o %t.wasm %t.o 2>&1 | FileCheck %s - -# CHECK: error: {{.*}}.o: TLS symbol is undefined, but TLS symbols cannot yet be imported: `foo` - -.globl _start -_start: - .functype _start () -> () - i32.const foo@TLSREL - drop - end_function - -.section .custom_section.target_features,"",@ - .int8 3 - .int8 43 - .int8 7 - .ascii "atomics" - .int8 43 - .int8 11 - .ascii "bulk-memory" - .int8 43 - .int8 15 - .ascii "mutable-globals" diff --git a/lld/test/wasm/tls-non-shared-memory.s b/lld/test/wasm/tls-non-shared-memory.s --- a/lld/test/wasm/tls-non-shared-memory.s +++ b/lld/test/wasm/tls-non-shared-memory.s @@ -1,5 +1,5 @@ # Test that linking without shared memory causes __tls_base to be -# internalized +# internalized. # RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s @@ -13,6 +13,12 @@ i32.add end_function +.globl get_tls1 +get_tls1_got: + .functype get_tls1_got () -> (i32) + global.get tls1@GOT + end_function + .section .data.no_tls,"",@ .globl no_tls .p2align 2 @@ -20,7 +26,7 @@ .int32 42 .size no_tls, 4 -.section .tdata.tls1,"",@ +.section .tdata.tls1,"T",@ .globl tls1 .p2align 2 tls1: @@ -58,6 +64,13 @@ # CHECK-NEXT: InitExpr: # CHECK-NEXT: Opcode: I32_CONST # CHECK-NEXT: Value: 1024 +# GOT.data.internal.tls1 +# CHECK-NEXT: - Index: 2 +# CHECK-NEXT: Type: I32 +# CHECK-NEXT: Mutable: false +# CHECK-NEXT: InitExpr: +# CHECK-NEXT: Opcode: I32_CONST +# CHECK-NEXT: Value: 1024 # CHECK-NEXT: - Type: EXPORT # CHECK: - Type: DATA diff --git a/lld/wasm/Relocations.cpp b/lld/wasm/Relocations.cpp --- a/lld/wasm/Relocations.cpp +++ b/lld/wasm/Relocations.cpp @@ -139,12 +139,6 @@ } if (config->isPic) { - if (sym->isTLS() && sym->isUndefined()) { - error(toString(file) + - ": TLS symbol is undefined, but TLS symbols cannot yet be " - "imported: `" + - toString(*sym) + "`"); - } switch (reloc.Type) { case R_WASM_TABLE_INDEX_SLEB: case R_WASM_TABLE_INDEX_SLEB64: diff --git a/lld/wasm/Symbols.h b/lld/wasm/Symbols.h --- a/lld/wasm/Symbols.h +++ b/lld/wasm/Symbols.h @@ -124,6 +124,8 @@ return gotIndex; } + void setTLS() { flags |= llvm::wasm::WASM_SYMBOL_TLS; } + void setGOTIndex(uint32_t index); bool hasGOTIndex() const { return gotIndex != INVALID_INDEX; } @@ -289,9 +291,7 @@ public: // Constructor for regular data symbols originating from input files. DefinedData(StringRef name, uint32_t flags, InputFile *f, InputChunk *segment, - uint64_t value, uint64_t size) - : DataSymbol(name, DefinedDataKind, flags, f), segment(segment), - value(value), size(size) {} + uint64_t value, uint64_t size); // Constructor for linker synthetic data symbols. DefinedData(StringRef name, uint32_t flags) @@ -552,10 +552,14 @@ static DefinedFunction *applyDataRelocs; // __wasm_apply_global_relocs - // Function that applies relocations to data segment post-instantiation. + // Function that applies relocations to wasm globals post-instantiation. // Unlike __wasm_apply_data_relocs this needs to run on every thread. static DefinedFunction *applyGlobalRelocs; + // __wasm_apply_global_tls_relocs + // Like applyGlobalRelocs but for global that hold TLS addresess. + static DefinedFunction *applyGlobalTLSRelocs; + // __wasm_init_tls // Function that allocates thread-local storage and initializes it. static DefinedFunction *initTLS; diff --git a/lld/wasm/Symbols.cpp b/lld/wasm/Symbols.cpp --- a/lld/wasm/Symbols.cpp +++ b/lld/wasm/Symbols.cpp @@ -74,6 +74,7 @@ DefinedFunction *WasmSym::initMemory; DefinedFunction *WasmSym::applyDataRelocs; DefinedFunction *WasmSym::applyGlobalRelocs; +DefinedFunction *WasmSym::applyGlobalTLSRelocs; DefinedFunction *WasmSym::initTLS; DefinedFunction *WasmSym::startFunction; DefinedData *WasmSym::dsoHandle; @@ -298,6 +299,14 @@ return function->getFunctionIndex(); } +DefinedData::DefinedData(StringRef name, uint32_t flags, InputFile *f, + InputChunk *segment, uint64_t value, uint64_t size) + : DataSymbol(name, DefinedDataKind, flags, f), segment(segment), + value(value), size(size) { + if (segment && segment->isTLS()) + setTLS(); +} + uint64_t DefinedData::getVA() const { LLVM_DEBUG(dbgs() << "getVA: " << getName() << "\n"); if (segment) diff --git a/lld/wasm/SyntheticSections.h b/lld/wasm/SyntheticSections.h --- a/lld/wasm/SyntheticSections.h +++ b/lld/wasm/SyntheticSections.h @@ -287,9 +287,9 @@ // transform a `global.get` to an `i32.const`. void addInternalGOTEntry(Symbol *sym); bool needsRelocations() { return internalGotSymbols.size(); } - void generateRelocationCode(raw_ostream &os) const; + void generateRelocationCode(raw_ostream &os, bool TLS) const; - std::vector dataAddressGlobals; + std::vector dataAddressGlobals; std::vector inputGlobals; std::vector internalGotSymbols; diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp --- a/lld/wasm/SyntheticSections.cpp +++ b/lld/wasm/SyntheticSections.cpp @@ -64,6 +64,57 @@ writeUleb128(os, symtab->sharedFiles.size(), "Needed"); for (auto *so : symtab->sharedFiles) writeStr(os, llvm::sys::path::filename(so->getName()), "so name"); + + std::vector exportInfo; + std::vector importInfo; + + for (const Symbol *sym : symtab->getSymbols()) { + if (sym->isExported() && sym->isLive() && sym->isTLS() && + isa(sym)) { + exportInfo.push_back(sym); + } + } + + for (const Symbol *sym : out.importSec->importedSymbols) { + if (sym->isWeak()) { + importInfo.push_back(sym); + } + } + + if (!importInfo.empty()) { + SubSection sub(WASM_DYLINK_IMPORT_INFO); + writeUleb128(sub.os, importInfo.size(), "num imports"); + + for (const Symbol *sym : importInfo) { + LLVM_DEBUG(llvm::dbgs() << "import info: " << toString(*sym) << "\n"); + StringRef module = sym->importModule.getValueOr(defaultModule); + StringRef name = sym->importName.getValueOr(sym->getName()); + writeStr(sub.os, module, "sym name"); + writeStr(sub.os, name, "sym name"); + writeUleb128(sub.os, sym->flags, "sym flags"); + } + + sub.writeTo(os); + } + + if (!exportInfo.empty()) { + SubSection sub(WASM_DYLINK_EXPORT_INFO); + writeUleb128(sub.os, exportInfo.size(), "num exports"); + + for (const Symbol *sym : exportInfo) { + LLVM_DEBUG(llvm::dbgs() << "export info: " << toString(*sym) << "\n"); + StringRef name = sym->getName(); + if (auto *f = dyn_cast(sym)) { + if (Optional exportName = f->function->getExportName()) { + name = *exportName; + } + } + writeStr(sub.os, name, "sym name"); + writeUleb128(sub.os, sym->flags, "sym flags"); + } + + sub.writeTo(os); + } } uint32_t TypeSection::registerType(const WasmSignature &sig) { @@ -336,7 +387,7 @@ internalGotSymbols.push_back(sym); } -void GlobalSection::generateRelocationCode(raw_ostream &os) const { +void GlobalSection::generateRelocationCode(raw_ostream &os, bool TLS) const { bool is64 = config->is64.getValueOr(false); unsigned opcode_ptr_const = is64 ? WASM_OPCODE_I64_CONST : WASM_OPCODE_I32_CONST; @@ -344,10 +395,17 @@ : WASM_OPCODE_I32_ADD; for (const Symbol *sym : internalGotSymbols) { + if (TLS != sym->isTLS()) + continue; + if (auto *d = dyn_cast(sym)) { // Get __memory_base writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET"); - writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), "__memory_base"); + if (sym->isTLS()) + writeUleb128(os, WasmSym::tlsBase->getGlobalIndex(), "__tls_base"); + else + writeUleb128(os, WasmSym::memoryBase->getGlobalIndex(), + "__memory_base"); // Add the virtual address of the data symbol writeU8(os, opcode_ptr_const, "CONST"); diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp --- a/lld/wasm/Writer.cpp +++ b/lld/wasm/Writer.cpp @@ -63,6 +63,7 @@ void createStartFunction(); void createApplyDataRelocationsFunction(); void createApplyGlobalRelocationsFunction(); + void createApplyGlobalTLSRelocationsFunction(); void createCallCtorsFunction(); void createInitTLSFunction(); void createCommandExportWrappers(); @@ -639,12 +640,6 @@ } else if (auto *t = dyn_cast(sym)) { export_ = {name, WASM_EXTERNAL_TAG, t->getTagIndex()}; } else if (auto *d = dyn_cast(sym)) { - if (sym->isTLS()) { - // We can't currenly export TLS data symbols. - if (sym->isExportedExplicit()) - error("TLS symbols cannot yet be exported: `" + toString(*sym) + "`"); - continue; - } out.globalSec->dataAddressGlobals.push_back(d); export_ = {name, WASM_EXTERNAL_GLOBAL, globalIndex++}; } else { @@ -991,6 +986,13 @@ "__wasm_apply_global_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN, make(nullSignature, "__wasm_apply_global_relocs")); WasmSym::applyGlobalRelocs->markLive(); + if (config->sharedMemory) { + WasmSym::applyGlobalTLSRelocs = symtab->addSyntheticFunction( + "__wasm_apply_global_tls_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN, + make(nullSignature, + "__wasm_apply_global_tls_relocs")); + WasmSym::applyGlobalTLSRelocs->markLive(); + } } } @@ -1214,13 +1216,29 @@ { raw_string_ostream os(bodyContent); writeUleb128(os, 0, "num locals"); - out.globalSec->generateRelocationCode(os); + out.globalSec->generateRelocationCode(os, false); writeU8(os, WASM_OPCODE_END, "END"); } createFunction(WasmSym::applyGlobalRelocs, bodyContent); } +// Similar to createApplyGlobalRelocationsFunction but for +// TLS symbols. This cannot be run during the start function +// but must be delayed until __wasm_init_tls is called. +void Writer::createApplyGlobalTLSRelocationsFunction() { + // First write the body's contents to a string. + std::string bodyContent; + { + raw_string_ostream os(bodyContent); + writeUleb128(os, 0, "num locals"); + out.globalSec->generateRelocationCode(os, true); + writeU8(os, WASM_OPCODE_END, "END"); + } + + createFunction(WasmSym::applyGlobalTLSRelocs, bodyContent); +} + // Create synthetic "__wasm_call_ctors" function based on ctor functions // in input object. void Writer::createCallCtorsFunction() { @@ -1331,6 +1349,12 @@ writeUleb128(os, tlsSeg->index, "segment index immediate"); writeU8(os, 0, "memory index immediate"); } + + if (WasmSym::applyGlobalTLSRelocs) { + writeU8(os, WASM_OPCODE_CALL, "CALL"); + writeUleb128(os, WasmSym::applyGlobalTLSRelocs->getFunctionIndex(), + "function index"); + } writeU8(os, WASM_OPCODE_END, "end function"); } @@ -1465,6 +1489,8 @@ createApplyDataRelocationsFunction(); if (WasmSym::applyGlobalRelocs) createApplyGlobalRelocationsFunction(); + if (WasmSym::applyGlobalTLSRelocs) + createApplyGlobalTLSRelocationsFunction(); if (WasmSym::initMemory) createInitMemoryFunction(); createStartFunction(); diff --git a/llvm/include/llvm/BinaryFormat/Wasm.h b/llvm/include/llvm/BinaryFormat/Wasm.h --- a/llvm/include/llvm/BinaryFormat/Wasm.h +++ b/llvm/include/llvm/BinaryFormat/Wasm.h @@ -339,6 +339,12 @@ WASM_SYMBOL_TABLE = 0x8, }; +// Kind codes used in the custom "dylink" section +enum : unsigned { + WASM_DYLINK_EXPORT_INFO = 0x1, + WASM_DYLINK_IMPORT_INFO = 0x2, +}; + // Kind codes used in the custom "linking" section in the WASM_COMDAT_INFO enum : unsigned { WASM_COMDAT_DATA = 0x0, diff --git a/llvm/lib/Object/WasmObjectFile.cpp b/llvm/lib/Object/WasmObjectFile.cpp --- a/llvm/lib/Object/WasmObjectFile.cpp +++ b/llvm/lib/Object/WasmObjectFile.cpp @@ -349,6 +349,45 @@ while (Count--) { DylinkInfo.Needed.push_back(readString(Ctx)); } + + const uint8_t *OrigEnd = Ctx.End; + while (Ctx.Ptr < OrigEnd) { + Ctx.End = OrigEnd; + uint8_t Type = readUint8(Ctx); + uint32_t Size = readVaruint32(Ctx); + LLVM_DEBUG(dbgs() << "readSubsection type=" << int(Type) << " size=" << Size + << "\n"); + Ctx.End = Ctx.Ptr + Size; + switch (Type) { + case wasm::WASM_DYLINK_IMPORT_INFO: { + uint32_t Count = readVaruint32(Ctx); + while (Count--) { + /*module*/ readString(Ctx); + /*name*/ readString(Ctx); + /*flags*/ readVaruint32(Ctx); + } + break; + } + case wasm::WASM_DYLINK_EXPORT_INFO: { + uint32_t Count = readVaruint32(Ctx); + while (Count--) { + /*name*/ readString(Ctx); + /*flags*/ readVaruint32(Ctx); + } + break; + } + default: + return make_error("unknown dylink sub-section", + object_error::parse_failed); + Ctx.Ptr += Size; + break; + } + if (Ctx.Ptr != Ctx.End) { + return make_error( + "dylink sub-section ended prematurely", object_error::parse_failed); + } + } + if (Ctx.Ptr != Ctx.End) return make_error("dylink section ended prematurely", object_error::parse_failed); diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp @@ -1539,7 +1539,6 @@ SelectionDAG &DAG) const { SDLoc DL(Op); const auto *GA = cast(Op); - MVT PtrVT = getPointerTy(DAG.getDataLayout()); MachineFunction &MF = DAG.getMachineFunction(); if (!MF.getSubtarget().hasBulkMemory()) @@ -1561,21 +1560,43 @@ false); } - auto GlobalGet = PtrVT == MVT::i64 ? WebAssembly::GLOBAL_GET_I64 - : WebAssembly::GLOBAL_GET_I32; - const char *BaseName = MF.createExternalSymbolName("__tls_base"); + auto model = GV->getThreadLocalMode(); - SDValue BaseAddr( - DAG.getMachineNode(GlobalGet, DL, PtrVT, - DAG.getTargetExternalSymbol(BaseName, PtrVT)), - 0); + // Unsupported TLS modes + assert(model != GlobalValue::NotThreadLocal); + assert(model != GlobalValue::InitialExecTLSModel); - SDValue TLSOffset = DAG.getTargetGlobalAddress( - GV, DL, PtrVT, GA->getOffset(), WebAssemblyII::MO_TLS_BASE_REL); - SDValue SymOffset = - DAG.getNode(WebAssemblyISD::WrapperREL, DL, PtrVT, TLSOffset); + if (model == GlobalValue::LocalExecTLSModel || + model == GlobalValue::LocalDynamicTLSModel || + (model == GlobalValue::GeneralDynamicTLSModel && + getTargetMachine().shouldAssumeDSOLocal(*GV->getParent(), GV))) { + // For DSO-local TLS variables we use offset from __tls_base - return DAG.getNode(ISD::ADD, DL, PtrVT, BaseAddr, SymOffset); + MVT PtrVT = getPointerTy(DAG.getDataLayout()); + auto GlobalGet = PtrVT == MVT::i64 ? WebAssembly::GLOBAL_GET_I64 + : WebAssembly::GLOBAL_GET_I32; + const char *BaseName = MF.createExternalSymbolName("__tls_base"); + + SDValue BaseAddr( + DAG.getMachineNode(GlobalGet, DL, PtrVT, + DAG.getTargetExternalSymbol(BaseName, PtrVT)), + 0); + + SDValue TLSOffset = DAG.getTargetGlobalAddress( + GV, DL, PtrVT, GA->getOffset(), WebAssemblyII::MO_TLS_BASE_REL); + SDValue SymOffset = + DAG.getNode(WebAssemblyISD::WrapperREL, DL, PtrVT, TLSOffset); + + return DAG.getNode(ISD::ADD, DL, PtrVT, BaseAddr, SymOffset); + } + + assert(model == GlobalValue::GeneralDynamicTLSModel); + + EVT VT = Op.getValueType(); + return DAG.getNode(WebAssemblyISD::Wrapper, DL, VT, + DAG.getTargetGlobalAddress(GA->getGlobal(), DL, VT, + GA->getOffset(), + WebAssemblyII::MO_GOT)); } SDValue WebAssemblyTargetLowering::LowerGlobalAddress(SDValue Op, diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td b/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td --- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td +++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td @@ -398,6 +398,11 @@ def : Pat<(i64 (WebAssemblyWrapperREL tglobaltlsaddr:$addr)), (CONST_I64 tglobaltlsaddr:$addr)>, Requires<[HasAddr64]>; +def : Pat<(i32 (WebAssemblyWrapper tglobaltlsaddr:$addr)), + (GLOBAL_GET_I32 tglobaltlsaddr:$addr)>, Requires<[HasAddr32]>; +def : Pat<(i64 (WebAssemblyWrapper tglobaltlsaddr:$addr)), + (GLOBAL_GET_I64 tglobaltlsaddr:$addr)>, Requires<[HasAddr64]>; + def : Pat<(i32 (WebAssemblyWrapper texternalsym:$addr)), (GLOBAL_GET_I32 texternalsym:$addr)>, Requires<[IsPIC, HasAddr32]>; def : Pat<(i64 (WebAssemblyWrapper texternalsym:$addr)),