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/shared-needed.s b/lld/test/wasm/shared-needed.s --- a/lld/test/wasm/shared-needed.s +++ b/lld/test/wasm/shared-needed.s @@ -29,6 +29,7 @@ # SO1-NEXT: TableSize: 0 # SO1-NEXT: TableAlignment: 0 # SO1-NEXT: Needed: [] +# SO1-NEXT: ExportInfo: [] # SO1-NEXT: - Type: TYPE # SO2: Sections: @@ -40,4 +41,5 @@ # SO2-NEXT: TableAlignment: 0 # SO2-NEXT: Needed: # SO2-NEXT: - shared-needed.s.tmp1.so +# SO2-NEXT: ExportInfo: [] # SO2-NEXT: - Type: TYPE diff --git a/lld/test/wasm/shared.s b/lld/test/wasm/shared.s --- a/lld/test/wasm/shared.s +++ b/lld/test/wasm/shared.s @@ -133,6 +133,7 @@ # CHECK-NEXT: TableSize: 2 # CHECK-NEXT: TableAlignment: 0 # CHECK-NEXT: Needed: [] +# CHECK-NEXT: ExportInfo: [] # CHECK-NEXT: - Type: TYPE # check for import of __table_base and __memory_base globals diff --git a/lld/test/wasm/shared64.s b/lld/test/wasm/shared64.s --- a/lld/test/wasm/shared64.s +++ b/lld/test/wasm/shared64.s @@ -134,6 +134,7 @@ # CHECK-NEXT: TableSize: 2 # CHECK-NEXT: TableAlignment: 0 # CHECK-NEXT: Needed: [] +# CHECK-NEXT: ExportInfo: [] # CHECK-NEXT: - Type: TYPE # check for import of __table_base and __memory_base globals diff --git a/lld/test/wasm/tls-export.s b/lld/test/wasm/tls-export.s --- a/lld/test/wasm/tls-export.s +++ b/lld/test/wasm/tls-export.s @@ -1,10 +1,6 @@ # 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` +# RUN: wasm-ld -shared --experimental-pic -o %t.so %t.o +# RUN: obj2yaml %t.so | FileCheck %s .section .tdata.tls1,"",@ .globl tls1 @@ -24,3 +20,26 @@ .int8 43 .int8 15 .ascii "mutable-globals" + +# CHECK: ExportInfo: +# CHECK-NEXT: - Name: tls1 +# CHECK-NEXT: Flags: [ TLS ] +# CHECK-NEXT: - Type: TYPE + +# CHECK: - Type: GLOBAL +# CHECK-NEXT: Globals: +# CHECK-NEXT: - Index: 2 +# CHECK-NEXT: Type: I32 +# CHECK-NEXT: Mutable: false +# CHECK-NEXT: InitExpr: +# CHECK-NEXT: Opcode: I32_CONST +# CHECK-NEXT: Value: 0 + +# CHECK: - Type: EXPORT +# CHECK-NEXT: Exports: +# CHECK-NEXT: - Name: __wasm_call_ctors +# CHECK-NEXT: Kind: FUNCTION +# CHECK-NEXT: Index: 0 +# CHECK-NEXT: - Name: tls1 +# CHECK-NEXT: Kind: GLOBAL +# CHECK-NEXT: Index: 2 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,37 @@ writeUleb128(os, symtab->sharedFiles.size(), "Needed"); for (auto *so : symtab->sharedFiles) writeStr(os, llvm::sys::path::filename(so->getName()), "so name"); + + // Under certain circumstances we need to include extra information about the + // exports we are providing to the dynamic linker. Currently this is only the + // case for TLS symbols where the exported value is relative toe __tls_base + // rather than __memory_base. + std::vector exportInfo; + for (const Symbol *sym : symtab->getSymbols()) { + if (sym->isExported() && sym->isLive() && sym->isTLS() && + isa(sym)) { + exportInfo.push_back(sym); + } + } + + 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 +367,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 +375,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 @@ -36,12 +36,18 @@ uint32_t Version; }; +struct WasmDylinkExport { + StringRef Name; + uint32_t Flags; +}; + struct WasmDylinkInfo { uint32_t MemorySize; // Memory size in bytes uint32_t MemoryAlignment; // P2 alignment of memory uint32_t TableSize; // Table size in elements uint32_t TableAlignment; // P2 alignment of table std::vector Needed; // Shared library dependencies + std::vector ExportInfo; // Shared library dependencies }; struct WasmProducerInfo { @@ -339,6 +345,11 @@ WASM_SYMBOL_TABLE = 0x8, }; +// Kind codes used in the custom "dylink" section +enum : unsigned { + WASM_DYLINK_EXPORT_INFO = 0x1, +}; + // Kind codes used in the custom "linking" section in the WASM_COMDAT_INFO enum : unsigned { WASM_COMDAT_DATA = 0x0, diff --git a/llvm/include/llvm/MC/MCExpr.h b/llvm/include/llvm/MC/MCExpr.h --- a/llvm/include/llvm/MC/MCExpr.h +++ b/llvm/include/llvm/MC/MCExpr.h @@ -328,6 +328,7 @@ VK_WASM_TLSREL, // Memory address relative to __tls_base VK_WASM_MBREL, // Memory address relative to __memory_base VK_WASM_TBREL, // Table index relative to __table_base + VK_WASM_GOT_TLS, // Wasm global index of TLS symbol. VK_AMDGPU_GOTPCREL32_LO, // symbol@gotpcrel32@lo VK_AMDGPU_GOTPCREL32_HI, // symbol@gotpcrel32@hi diff --git a/llvm/include/llvm/ObjectYAML/WasmYAML.h b/llvm/include/llvm/ObjectYAML/WasmYAML.h --- a/llvm/include/llvm/ObjectYAML/WasmYAML.h +++ b/llvm/include/llvm/ObjectYAML/WasmYAML.h @@ -199,6 +199,11 @@ yaml::BinaryRef Payload; }; +struct DylinkExport { + StringRef Name; + SymbolFlags Flags; +}; + struct DylinkSection : CustomSection { DylinkSection() : CustomSection("dylink") {} @@ -212,6 +217,7 @@ uint32_t TableSize; uint32_t TableAlignment; std::vector Needed; + std::vector ExportInfo; }; struct NameSection : CustomSection { @@ -426,6 +432,7 @@ LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::WasmYAML::ComdatEntry) LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::WasmYAML::Comdat) LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::WasmYAML::Tag) +LLVM_YAML_IS_SEQUENCE_VECTOR(llvm::WasmYAML::DylinkExport) namespace llvm { namespace yaml { @@ -574,6 +581,10 @@ static void mapping(IO &IO, WasmYAML::Tag &Tag); }; +template <> struct MappingTraits { + static void mapping(IO &IO, WasmYAML::DylinkExport &Export); +}; + } // end namespace yaml } // end namespace llvm diff --git a/llvm/lib/MC/MCExpr.cpp b/llvm/lib/MC/MCExpr.cpp --- a/llvm/lib/MC/MCExpr.cpp +++ b/llvm/lib/MC/MCExpr.cpp @@ -358,6 +358,7 @@ case VK_WASM_MBREL: return "MBREL"; case VK_WASM_TLSREL: return "TLSREL"; case VK_WASM_TBREL: return "TBREL"; + case VK_WASM_GOT_TLS: return "GOT@TLS"; case VK_AMDGPU_GOTPCREL32_LO: return "gotpcrel32@lo"; case VK_AMDGPU_GOTPCREL32_HI: return "gotpcrel32@hi"; case VK_AMDGPU_REL32_LO: return "rel32@lo"; @@ -499,6 +500,7 @@ .Case("tbrel", VK_WASM_TBREL) .Case("mbrel", VK_WASM_MBREL) .Case("tlsrel", VK_WASM_TLSREL) + .Case("got@tls", VK_WASM_GOT_TLS) .Case("gotpcrel32@lo", VK_AMDGPU_GOTPCREL32_LO) .Case("gotpcrel32@hi", VK_AMDGPU_GOTPCREL32_HI) .Case("rel32@lo", VK_AMDGPU_REL32_LO) diff --git a/llvm/lib/MC/MCWasmStreamer.cpp b/llvm/lib/MC/MCWasmStreamer.cpp --- a/llvm/lib/MC/MCWasmStreamer.cpp +++ b/llvm/lib/MC/MCWasmStreamer.cpp @@ -232,9 +232,14 @@ case MCExpr::SymbolRef: { const MCSymbolRefExpr &symRef = *cast(expr); - if (symRef.getKind() == MCSymbolRefExpr::VK_WASM_TLSREL) { + switch (symRef.getKind()) { + case MCSymbolRefExpr::VK_WASM_TLSREL: + case MCSymbolRefExpr::VK_WASM_GOT_TLS: getAssembler().registerSymbol(symRef.getSymbol()); cast(symRef.getSymbol()).setTLS(); + break; + default: + break; } break; } 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,35 @@ 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_EXPORT_INFO: { + uint32_t Count = readVaruint32(Ctx); + while (Count--) { + DylinkInfo.ExportInfo.push_back({readString(Ctx), 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/ObjectYAML/WasmYAML.cpp b/llvm/lib/ObjectYAML/WasmYAML.cpp --- a/llvm/lib/ObjectYAML/WasmYAML.cpp +++ b/llvm/lib/ObjectYAML/WasmYAML.cpp @@ -55,6 +55,7 @@ IO.mapRequired("TableSize", Section.TableSize); IO.mapRequired("TableAlignment", Section.TableAlignment); IO.mapRequired("Needed", Section.Needed); + IO.mapRequired("ExportInfo", Section.ExportInfo); } static void sectionMapping(IO &IO, WasmYAML::NameSection &Section) { @@ -531,6 +532,12 @@ IO.mapRequired("SigIndex", Tag.SigIndex); } +void MappingTraits::mapping( + IO &IO, WasmYAML::DylinkExport &Export) { + IO.mapRequired("Name", Export.Name); + IO.mapRequired("Flags", Export.Flags); +} + void ScalarBitSetTraits::bitset( IO &IO, WasmYAML::LimitFlags &Value) { #define BCase(X) IO.bitSetCase(Value, #X, wasm::WASM_LIMITS_FLAG_##X) diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp --- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp +++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmTypeCheck.cpp @@ -155,8 +155,12 @@ break; case wasm::WASM_SYMBOL_TYPE_FUNCTION: case wasm::WASM_SYMBOL_TYPE_DATA: - if (SymRef->getKind() == MCSymbolRefExpr::VK_GOT) { + switch (SymRef->getKind()) { + case MCSymbolRefExpr::VK_GOT: + case MCSymbolRefExpr::VK_WASM_GOT_TLS: Type = is64 ? wasm::ValType::I64 : wasm::ValType::I32; + return false; + default: break; } LLVM_FALLTHROUGH; diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h --- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h +++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h @@ -95,6 +95,9 @@ // platforms. MO_GOT, + // Same as MO_GOT but the address stored in the global is a TLS address. + MO_GOT_TLS, + // On a symbol operand this indicates that the immediate is the symbol // address relative the __memory_base wasm global. // Only applicable to data symbols. 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_TLS)); } 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)), diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp --- a/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp +++ b/llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp @@ -102,6 +102,9 @@ switch (TargetFlags) { case WebAssemblyII::MO_NO_FLAG: break; + case WebAssemblyII::MO_GOT_TLS: + Kind = MCSymbolRefExpr::VK_WASM_GOT_TLS; + break; case WebAssemblyII::MO_GOT: Kind = MCSymbolRefExpr::VK_GOT; break; diff --git a/llvm/test/CodeGen/WebAssembly/tls-general-dynamic.ll b/llvm/test/CodeGen/WebAssembly/tls-general-dynamic.ll --- a/llvm/test/CodeGen/WebAssembly/tls-general-dynamic.ll +++ b/llvm/test/CodeGen/WebAssembly/tls-general-dynamic.ll @@ -20,6 +20,17 @@ ret i32 ptrtoint(i32* @tls to i32) } +; CHECK-LABEL: address_of_tls_external: +; CHECK-NEXT: .functype address_of_tls_external () -> (i32) +define i32 @address_of_tls_external() { + ; TLS-DAG: global.get tls_external@GOT@TLS + ; TLS-NEXT: return + + ; NO-TLS-NEXT: i32.const tls_external + ; NO-TLS-NEXT: return + ret i32 ptrtoint(i32* @tls_external to i32) +} + ; CHECK-LABEL: ptr_to_tls: ; CHECK-NEXT: .functype ptr_to_tls () -> (i32) define i32* @ptr_to_tls() { @@ -33,6 +44,17 @@ ret i32* @tls } +; CHECK-LABEL: ptr_to_tls_external: +; CHECK-NEXT: .functype ptr_to_tls_external () -> (i32) +define i32* @ptr_to_tls_external() { + ; TLS-DAG: global.get tls_external@GOT@TLS + ; TLS-NEXT: return + + ; NO-TLS-NEXT: i32.const tls_external + ; NO-TLS-NEXT: return + ret i32* @tls_external +} + ; CHECK-LABEL: tls_load: ; CHECK-NEXT: .functype tls_load () -> (i32) define i32 @tls_load() { @@ -49,6 +71,20 @@ ret i32 %tmp } +; CHECK-LABEL: tls_load_external: +; CHECK-NEXT: .functype tls_load_external () -> (i32) +define i32 @tls_load_external() { + ; TLS-DAG: global.get tls_external@GOT@TLS + ; TLS-NEXT: i32.load 0 + ; TLS-NEXT: return + + ; NO-TLS-NEXT: i32.const 0 + ; NO-TLS-NEXT: i32.load tls_external + ; NO-TLS-NEXT: return + %tmp = load i32, i32* @tls_external, align 4 + ret i32 %tmp +} + ; CHECK-LABEL: tls_store: ; CHECK-NEXT: .functype tls_store (i32) -> () define void @tls_store(i32 %x) { @@ -65,6 +101,20 @@ ret void } +; CHECK-LABEL: tls_store_external: +; CHECK-NEXT: .functype tls_store_external (i32) -> () +define void @tls_store_external(i32 %x) { + ; TLS-DAG: global.get tls_external@GOT@TLS + ; TLS-NEXT: i32.store 0 + ; TLS-NEXT: return + + ; NO-TLS-NEXT: i32.const 0 + ; NO-TLS-NEXT: i32.store tls_external + ; NO-TLS-NEXT: return + store i32 %x, i32* @tls_external, align 4 + ret void +} + ; CHECK-LABEL: tls_size: ; CHECK-NEXT: .functype tls_size () -> (i32) define i32 @tls_size() { @@ -111,6 +161,8 @@ ; CHECK-NEXT: .int32 0 @tls = internal thread_local global i32 0 +@tls_external = external thread_local global i32, align 4 + declare i32 @llvm.wasm.tls.size.i32() declare i32 @llvm.wasm.tls.align.i32() declare i8* @llvm.wasm.tls.base() diff --git a/llvm/test/CodeGen/WebAssembly/tls-local-exec.ll b/llvm/test/CodeGen/WebAssembly/tls-local-exec.ll --- a/llvm/test/CodeGen/WebAssembly/tls-local-exec.ll +++ b/llvm/test/CodeGen/WebAssembly/tls-local-exec.ll @@ -16,6 +16,19 @@ ret i32 ptrtoint(i32* @tls to i32) } +; CHECK-LABEL: address_of_tls_external: +; CHECK-NEXT: .functype address_of_tls_external () -> (i32) +define i32 @address_of_tls_external() { + ; TLS-DAG: global.get __tls_base + ; TLS-DAG: i32.const tls_external@TLSREL + ; TLS-NEXT: i32.add + ; TLS-NEXT: return + + ; NO-TLS-NEXT: i32.const tls_external + ; NO-TLS-NEXT: return + ret i32 ptrtoint(i32* @tls_external to i32) +} + ; CHECK-LABEL: ptr_to_tls: ; CHECK-NEXT: .functype ptr_to_tls () -> (i32) define i32* @ptr_to_tls() { @@ -78,4 +91,6 @@ ; CHECK-NEXT: .int32 0 @tls = internal thread_local(localexec) global i32 0 +@tls_external = external thread_local(localexec) global i32, align 4 + declare i32 @llvm.wasm.tls.size.i32() diff --git a/llvm/test/MC/WebAssembly/tls.s b/llvm/test/MC/WebAssembly/tls.s --- a/llvm/test/MC/WebAssembly/tls.s +++ b/llvm/test/MC/WebAssembly/tls.s @@ -20,7 +20,7 @@ tls_get_undefined: .functype tls_get_undefined (i32) -> (i32) - i32.const tls_undefined@TLSREL + global.get tls_undefined@GOT@TLS end_function .section .tls.foo,"T",@ diff --git a/llvm/tools/obj2yaml/wasm2yaml.cpp b/llvm/tools/obj2yaml/wasm2yaml.cpp --- a/llvm/tools/obj2yaml/wasm2yaml.cpp +++ b/llvm/tools/obj2yaml/wasm2yaml.cpp @@ -60,6 +60,8 @@ DylinkSec->TableSize = Info.TableSize; DylinkSec->TableAlignment = Info.TableAlignment; DylinkSec->Needed = Info.Needed; + for (const auto &Exp : Info.ExportInfo) + DylinkSec->ExportInfo.push_back({Exp.Name, Exp.Flags}); CustomSec = std::move(DylinkSec); } else if (WasmSec.Name == "name") { std::unique_ptr NameSec =