81 changes: 77 additions & 4 deletions lld/wasm/Writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class Writer {
void createInitMemoryFunction();
void createApplyRelocationsFunction();
void createCallCtorsFunction();
void createInitTLSFunction();

void assignIndexes();
void populateSymtab();
Expand Down Expand Up @@ -242,6 +243,11 @@ void Writer::layoutMemory() {
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<DefinedGlobal>(WasmSym::tlsSize);
tlsSize->global->global.InitExpr.Value.Int32 = seg->size;
}
}

// TODO: Add .bss space here.
Expand Down Expand Up @@ -353,6 +359,7 @@ void Writer::populateTargetFeatures() {
StringMap<std::string> used;
StringMap<std::string> required;
StringMap<std::string> disallowed;
bool tlsUsed = false;

// Only infer used features if user did not specify features
bool inferFeatures = !config->features.hasValue();
Expand Down Expand Up @@ -385,6 +392,14 @@ void Writer::populateTargetFeatures() {
std::to_string(feature.Prefix));
}
}

for (InputSegment *segment : file->segments) {
if (!segment->live)
continue;
StringRef name = segment->getName();
if (name.startswith(".tdata") || name.startswith(".tbss"))
tlsUsed = true;
}
}

if (inferFeatures)
Expand All @@ -411,6 +426,10 @@ void Writer::populateTargetFeatures() {
error("'bulk-memory' feature must be used in order to emit passive "
"segments");

if (!used.count("bulk-memory") && tlsUsed)
error("'bulk-memory' feature must be used in order to use thread-local "
"storage");

// Validate that used features are allowed in output
if (!inferFeatures) {
for (auto &feature : used.keys()) {
Expand Down Expand Up @@ -492,8 +511,8 @@ void Writer::calculateExports() {
// implement in all major browsers.
// See: https://github.com/WebAssembly/mutable-global
if (g->getGlobalType()->Mutable) {
// Only the __stack_pointer should ever be create as mutable.
assert(g == WasmSym::stackPointer);
// Only __stack_pointer and __tls_base should ever be create as mutable.
assert(g == WasmSym::stackPointer || g == WasmSym::tlsBase);
continue;
}
export_ = {name, WASM_EXTERNAL_GLOBAL, g->getGlobalIndex()};
Expand Down Expand Up @@ -602,6 +621,11 @@ static StringRef getOutputDataSegmentName(StringRef name) {
// we only have a single __memory_base to use as our base address.
if (config->isPic)
return ".data";
// We only support one thread-local segment, so we must merge the segments
// despite --no-merge-data-segments.
// We also need to merge .tbss into .tdata so they share the same offsets.
if (name.startswith(".tdata") || name.startswith(".tbss"))
return ".tdata";
if (!config->mergeDataSegments)
return name;
if (name.startswith(".text."))
Expand All @@ -625,7 +649,7 @@ void Writer::createOutputSegments() {
if (s == nullptr) {
LLVM_DEBUG(dbgs() << "new segment: " << name << "\n");
s = make<OutputSegment>(name, segments.size());
if (config->passiveSegments)
if (config->passiveSegments || name == ".tdata")
s->initFlags = WASM_SEGMENT_IS_PASSIVE;
segments.push_back(s);
}
Expand Down Expand Up @@ -655,7 +679,7 @@ void Writer::createInitMemoryFunction() {

// initialize passive data segments
for (const OutputSegment *s : segments) {
if (s->initFlags & WASM_SEGMENT_IS_PASSIVE) {
if (s->initFlags & WASM_SEGMENT_IS_PASSIVE && s->name != ".tdata") {
// destination address
writeU8(os, WASM_OPCODE_I32_CONST, "i32.const");
writeSleb128(os, s->startVA, "destination address");
Expand Down Expand Up @@ -737,6 +761,49 @@ void Writer::createCallCtorsFunction() {
createFunction(WasmSym::callCtors, bodyContent);
}

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

std::string bodyContent;
{
raw_string_ostream os(bodyContent);

OutputSegment *tlsSeg = nullptr;
for (auto *seg : segments) {
if (seg->name == ".tdata")
tlsSeg = seg;
break;
}

writeUleb128(os, 0, "num locals");
if (tlsSeg) {
writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get");
writeUleb128(os, 0, "local index");

writeU8(os, WASM_OPCODE_GLOBAL_SET, "global.set");
writeUleb128(os, WasmSym::tlsBase->getGlobalIndex(), "global index");

writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get");
writeUleb128(os, 0, "local index");

writeU8(os, WASM_OPCODE_I32_CONST, "i32.const");
writeSleb128(os, 0, "segment offset");

writeU8(os, WASM_OPCODE_I32_CONST, "i32.const");
writeSleb128(os, tlsSeg->size, "memory region size");

writeU8(os, WASM_OPCODE_MISC_PREFIX, "bulk-memory prefix");
writeUleb128(os, WASM_OPCODE_MEMORY_INIT, "MEMORY.INIT");
writeUleb128(os, tlsSeg->index, "segment index immediate");
writeU8(os, 0, "memory index immediate");
}
writeU8(os, WASM_OPCODE_END, "end function");
}

createFunction(WasmSym::initTLS, bodyContent);
}

// Populate InitFunctions vector with init functions from all input objects.
// This is then used either when creating the output linking section or to
// synthesize the "__wasm_call_ctors" function.
Expand Down Expand Up @@ -829,6 +896,12 @@ void Writer::run() {
createCallCtorsFunction();
}

if (config->sharedMemory && !config->shared)
createInitTLSFunction();

if (errorCount())
return;

log("-- calculateTypes");
calculateTypes();
log("-- calculateExports");
Expand Down
2 changes: 2 additions & 0 deletions llvm/include/llvm/BinaryFormat/Wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,9 @@ enum : unsigned {
enum : unsigned {
WASM_OPCODE_END = 0x0b,
WASM_OPCODE_CALL = 0x10,
WASM_OPCODE_LOCAL_GET = 0x20,
WASM_OPCODE_GLOBAL_GET = 0x23,
WASM_OPCODE_GLOBAL_SET = 0x24,
WASM_OPCODE_I32_STORE = 0x36,
WASM_OPCODE_I32_CONST = 0x41,
WASM_OPCODE_I64_CONST = 0x42,
Expand Down
9 changes: 9 additions & 0 deletions llvm/include/llvm/IR/IntrinsicsWebAssembly.td
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,13 @@ def int_wasm_data_drop :
[llvm_i32_ty],
[IntrNoDuplicate, IntrHasSideEffects, ImmArg<0>]>;

//===----------------------------------------------------------------------===//
// Thread-local storage intrinsics
//===----------------------------------------------------------------------===//

def int_wasm_tls_size :
Intrinsic<[llvm_anyint_ty],
[],
[IntrNoMem, IntrSpeculatable]>;

} // TargetPrefix = "wasm"
3 changes: 2 additions & 1 deletion llvm/include/llvm/MC/MCSectionWasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ class MCSectionWasm final : public MCSection {
bool isVirtualSection() const override;

bool isWasmData() const {
return Kind.isGlobalWriteableData() || Kind.isReadOnly();
return Kind.isGlobalWriteableData() || Kind.isReadOnly() ||
Kind.isThreadLocal();
}

bool isUnique() const { return UniqueID != ~0U; }
Expand Down
4 changes: 4 additions & 0 deletions llvm/lib/Target/WebAssembly/WebAssemblyFastISel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,8 @@ bool WebAssemblyFastISel::computeAddress(const Value *Obj, Address &Addr) {
return false;
if (Addr.getGlobalValue())
return false;
if (GV->isThreadLocal())
return false;
Addr.setGlobalValue(GV);
return true;
}
Expand Down Expand Up @@ -614,6 +616,8 @@ unsigned WebAssemblyFastISel::fastMaterializeConstant(const Constant *C) {
if (const GlobalValue *GV = dyn_cast<GlobalValue>(C)) {
if (TLI.isPositionIndependent())
return 0;
if (GV->isThreadLocal())
return 0;
unsigned ResultReg =
createResultReg(Subtarget->hasAddr64() ? &WebAssembly::I64RegClass
: &WebAssembly::I32RegClass);
Expand Down
49 changes: 49 additions & 0 deletions llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "WebAssembly.h"
#include "WebAssemblyTargetMachine.h"
#include "llvm/CodeGen/SelectionDAGISel.h"
#include "llvm/IR/DiagnosticInfo.h"
#include "llvm/IR/Function.h" // To access function attributes.
#include "llvm/Support/Debug.h"
#include "llvm/Support/KnownBits.h"
Expand Down Expand Up @@ -171,6 +172,54 @@ void WebAssemblyDAGToDAGISel::Select(SDNode *Node) {
}
}

case ISD::GlobalTLSAddress: {
const auto *GA = cast<GlobalAddressSDNode>(Node);

if (!MF.getSubtarget<WebAssemblySubtarget>().hasBulkMemory())
report_fatal_error("cannot use thread-local storage without bulk memory",
false);

if (GA->getGlobal()->getThreadLocalMode() !=
GlobalValue::LocalExecTLSModel) {
report_fatal_error("only -ftls-model=local-exec is supported for now",
false);
}

MVT PtrVT = TLI->getPointerTy(CurDAG->getDataLayout());
assert(PtrVT == MVT::i32 && "only wasm32 is supported for now");

SDValue TLSBaseSym = CurDAG->getTargetExternalSymbol("__tls_base", PtrVT);
SDValue TLSOffsetSym = CurDAG->getTargetGlobalAddress(
GA->getGlobal(), DL, PtrVT, GA->getOffset(), 0);

MachineSDNode *TLSBase = CurDAG->getMachineNode(WebAssembly::GLOBAL_GET_I32,
DL, MVT::i32, TLSBaseSym);
MachineSDNode *TLSOffset = CurDAG->getMachineNode(
WebAssembly::CONST_I32, DL, MVT::i32, TLSOffsetSym);
MachineSDNode *TLSAddress =
CurDAG->getMachineNode(WebAssembly::ADD_I32, DL, MVT::i32,
SDValue(TLSBase, 0), SDValue(TLSOffset, 0));
ReplaceNode(Node, TLSAddress);
return;
}

case ISD::INTRINSIC_WO_CHAIN: {
unsigned IntNo = cast<ConstantSDNode>(Node->getOperand(0))->getZExtValue();
switch (IntNo) {
case Intrinsic::wasm_tls_size: {
MVT PtrVT = TLI->getPointerTy(CurDAG->getDataLayout());
assert(PtrVT == MVT::i32 && "only wasm32 is supported for now");

MachineSDNode *TLSSize = CurDAG->getMachineNode(
WebAssembly::GLOBAL_GET_I32, DL, PtrVT,
CurDAG->getTargetExternalSymbol("__tls_size", MVT::i32));
ReplaceNode(Node, TLSSize);
return;
}
}
break;
}

default:
break;
}
Expand Down
8 changes: 5 additions & 3 deletions llvm/lib/Target/WebAssembly/WebAssemblyMCInstLower.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ MCSymbol *WebAssemblyMCInstLower::GetExternalSymbolSymbol(
// functions. It's OK to hardcode knowledge of specific symbols here; this
// method is precisely there for fetching the signatures of known
// Clang-provided symbols.
if (strcmp(Name, "__stack_pointer") == 0 ||
strcmp(Name, "__memory_base") == 0 || strcmp(Name, "__table_base") == 0) {
bool Mutable = strcmp(Name, "__stack_pointer") == 0;
if (strcmp(Name, "__stack_pointer") == 0 || strcmp(Name, "__tls_base") == 0 ||
strcmp(Name, "__memory_base") == 0 || strcmp(Name, "__table_base") == 0 ||
strcmp(Name, "__tls_size") == 0) {
bool Mutable =
strcmp(Name, "__stack_pointer") == 0 || strcmp(Name, "__tls_base") == 0;
WasmSym->setType(wasm::WASM_SYMBOL_TYPE_GLOBAL);
WasmSym->setGlobalType(wasm::WasmGlobalType{
uint8_t(Subtarget.hasAddr64() ? wasm::WASM_TYPE_I64
Expand Down
23 changes: 16 additions & 7 deletions llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,21 @@ class CoalesceFeaturesAndStripAtomics final : public ModulePass {
for (auto &F : M)
replaceFeatures(F, FeatureStr);

bool Stripped = false;
if (!Features[WebAssembly::FeatureAtomics]) {
Stripped |= stripAtomics(M);
Stripped |= stripThreadLocals(M);
}
bool StrippedAtomics = false;
bool StrippedTLS = false;

if (!Features[WebAssembly::FeatureAtomics])
StrippedAtomics = stripAtomics(M);

if (!Features[WebAssembly::FeatureBulkMemory])
StrippedTLS = stripThreadLocals(M);

if (StrippedAtomics && !StrippedTLS)
stripThreadLocals(M);
else if (StrippedTLS && !StrippedAtomics)
stripAtomics(M);

recordFeatures(M, Features, Stripped);
recordFeatures(M, Features, StrippedAtomics || StrippedTLS);

// Conservatively assume we have made some change
return true;
Expand Down Expand Up @@ -271,7 +279,8 @@ class CoalesceFeaturesAndStripAtomics final : public ModulePass {
// "atomics" is special: code compiled without atomics may have had its
// atomics lowered to nonatomic operations. In that case, atomics is
// disallowed to prevent unsafe linking with atomics-enabled objects.
assert(!Features[WebAssembly::FeatureAtomics]);
assert(!Features[WebAssembly::FeatureAtomics] ||
!Features[WebAssembly::FeatureBulkMemory]);
M.addModuleFlag(Module::ModFlagBehavior::Error, MDKey,
wasm::WASM_FEATURE_PREFIX_DISALLOWED);
} else if (Features[KV.Value]) {
Expand Down
32 changes: 16 additions & 16 deletions llvm/test/CodeGen/WebAssembly/target-features-tls.ll
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
; RUN: llc < %s -mattr=-atomics | FileCheck %s --check-prefixes CHECK,NO-ATOMICS
; RUN: llc < %s -mattr=+atomics | FileCheck %s --check-prefixes CHECK,ATOMICS
; RUN: llc < %s -mattr=-bulk-memory | FileCheck %s --check-prefixes NO-BULK-MEM
; RUN: llc < %s -mattr=+bulk-memory | FileCheck %s --check-prefixes BULK-MEM

; Test that the target features section contains -atomics or +atomics
; for modules that have thread local storage in their source.
Expand All @@ -9,18 +9,18 @@ target triple = "wasm32-unknown-unknown"

@foo = internal thread_local global i32 0

; CHECK-LABEL: .custom_section.target_features,"",@
; -bulk-memory
; NO-BULK-MEM-LABEL: .custom_section.target_features,"",@
; NO-BULK-MEM-NEXT: .int8 1
; NO-BULK-MEM-NEXT: .int8 45
; NO-BULK-MEM-NEXT: .int8 7
; NO-BULK-MEM-NEXT: .ascii "atomics"
; NO-BULK-MEM-NEXT: .bss.foo,"",@

; -atomics
; NO-ATOMICS-NEXT: .int8 1
; NO-ATOMICS-NEXT: .int8 45
; NO-ATOMICS-NEXT: .int8 7
; NO-ATOMICS-NEXT: .ascii "atomics"
; NO-ATOMICS-NEXT: .bss.foo,"",@

; +atomics
; ATOMICS-NEXT: .int8 1
; ATOMICS-NEXT: .int8 43
; ATOMICS-NEXT: .int8 7
; ATOMICS-NEXT: .ascii "atomics"
; ATOMICS-NEXT: .tbss.foo,"",@
; +bulk-memory
; BULK-MEM-LABEL: .custom_section.target_features,"",@
; BULK-MEM-NEXT: .int8 1
; BULK-MEM-NEXT: .int8 43
; BULK-MEM-NEXT: .int8 11
; BULK-MEM-NEXT: .ascii "bulk-memory"
; BULK-MEM-NEXT: .tbss.foo,"",@
85 changes: 75 additions & 10 deletions llvm/test/CodeGen/WebAssembly/tls.ll
Original file line number Diff line number Diff line change
@@ -1,17 +1,82 @@
; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers | FileCheck --check-prefix=SINGLE %s
; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -mattr=+bulk-memory | FileCheck %s --check-prefixes=CHECK,TLS
; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -mattr=+bulk-memory -fast-isel | FileCheck %s --check-prefixes=CHECK,TLS
; RUN: llc < %s -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -mattr=-bulk-memory | FileCheck %s --check-prefixes=CHECK,NO-TLS
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown"

; SINGLE-LABEL: address_of_tls:
; CHECK-LABEL: address_of_tls:
; CHECK-NEXT: .functype address_of_tls () -> (i32)
define i32 @address_of_tls() {
; SINGLE: i32.const $push0=, tls
; SINGLE-NEXT: return $pop0
; TLS-DAG: global.get __tls_base
; TLS-DAG: i32.const tls
; TLS-NEXT: i32.add
; TLS-NEXT: return

; NO-TLS-NEXT: i32.const tls
; NO-TLS-NEXT: return
ret i32 ptrtoint(i32* @tls to i32)
}

; SINGLE: .type tls,@object
; SINGLE-NEXT: .section .bss.tls,"",@
; SINGLE-NEXT: .p2align 2
; SINGLE-NEXT: tls:
; SINGLE-NEXT: .int32 0
@tls = internal thread_local global i32 0
; CHECK-LABEL: ptr_to_tls:
; CHECK-NEXT: .functype ptr_to_tls () -> (i32)
define i32* @ptr_to_tls() {
; TLS-DAG: global.get __tls_base
; TLS-DAG: i32.const tls
; TLS-NEXT: i32.add
; TLS-NEXT: return

; NO-TLS-NEXT: i32.const tls
; NO-TLS-NEXT: return
ret i32* @tls
}

; CHECK-LABEL: tls_load:
; CHECK-NEXT: .functype tls_load () -> (i32)
define i32 @tls_load() {
; TLS-DAG: global.get __tls_base
; TLS-DAG: i32.const tls
; TLS-NEXT: i32.add
; TLS-NEXT: i32.load 0
; TLS-NEXT: return

; NO-TLS-NEXT: i32.const 0
; NO-TLS-NEXT: i32.load tls
; NO-TLS-NEXT: return
%tmp = load i32, i32* @tls, align 4
ret i32 %tmp
}

; CHECK-LABEL: tls_store:
; CHECK-NEXT: .functype tls_store (i32) -> ()
define void @tls_store(i32 %x) {
; TLS-DAG: global.get __tls_base
; TLS-DAG: i32.const tls
; TLS-NEXT: i32.add
; TLS-NEXT: i32.store 0
; TLS-NEXT: return

; NO-TLS-NEXT: i32.const 0
; NO-TLS-NEXT: i32.store tls
; NO-TLS-NEXT: return
store i32 %x, i32* @tls, align 4
ret void
}

; CHECK-LABEL: tls_size:
; CHECK-NEXT: .functype tls_size () -> (i32)
define i32 @tls_size() {
; CHECK-NEXT: global.get __tls_size
; CHECK-NEXT: return
%1 = call i32 @llvm.wasm.tls.size.i32()
ret i32 %1
}

; CHECK: .type tls,@object
; TLS-NEXT: .section .tbss.tls,"",@
; NO-TLS-NEXT: .section .bss.tls,"",@
; CHECK-NEXT: .p2align 2
; CHECK-NEXT: tls:
; CHECK-NEXT: .int32 0
@tls = internal thread_local(localexec) global i32 0

declare i32 @llvm.wasm.tls.size.i32()