Skip to content

Commit 42bba4b

Browse files
committed
[WebAssembly] Implement thread-local storage (local-exec model)
Summary: Thread local variables are placed inside a `.tdata` segment. Their symbols are offsets from the start of the segment. The address of a thread local variable is computed as `__tls_base` + the offset from the start of the segment. `.tdata` segment is a passive segment and `memory.init` is used once per thread to initialize the thread local storage. `__tls_base` is a wasm global. Since each thread has its own wasm instance, it is effectively thread local. Currently, `__tls_base` must be initialized at thread startup, and so cannot be used with dynamic libraries. `__tls_base` is to be initialized with a new linker-synthesized function, `__wasm_init_tls`, which takes as an argument a block of memory to use as the storage for thread locals. It then initializes the block of memory and sets `__tls_base`. As `__wasm_init_tls` will handle the memory initialization, the memory does not have to be zeroed. To help allocating memory for thread-local storage, a new compiler intrinsic is introduced: `__builtin_wasm_tls_size()`. This instrinsic function returns the size of the thread-local storage for the current function. The expected usage is to run something like the following upon thread startup: __wasm_init_tls(malloc(__builtin_wasm_tls_size())); Reviewers: tlively, aheejin, kripken, sbc100 Subscribers: dschuff, jgravelle-google, hiraditya, sunfish, jfb, cfe-commits, llvm-commits Tags: #clang, #llvm Differential Revision: https://reviews.llvm.org/D64537 llvm-svn: 366272
1 parent 21f2858 commit 42bba4b

File tree

18 files changed

+413
-47
lines changed

18 files changed

+413
-47
lines changed

clang/include/clang/Basic/BuiltinsWebAssembly.def

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ BUILTIN(__builtin_wasm_memory_grow, "zIiz", "n")
2929
TARGET_BUILTIN(__builtin_wasm_memory_init, "vIUiIUiv*UiUi", "", "bulk-memory")
3030
TARGET_BUILTIN(__builtin_wasm_data_drop, "vIUi", "", "bulk-memory")
3131

32+
// Thread-local storage
33+
TARGET_BUILTIN(__builtin_wasm_tls_size, "z", "nc", "bulk-memory")
34+
3235
// Floating point min/max
3336
BUILTIN(__builtin_wasm_min_f32, "fff", "nc")
3437
BUILTIN(__builtin_wasm_max_f32, "fff", "nc")

clang/lib/CodeGen/CGBuiltin.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -13913,6 +13913,11 @@ Value *CodeGenFunction::EmitWebAssemblyBuiltinExpr(unsigned BuiltinID,
1391313913
Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_data_drop);
1391413914
return Builder.CreateCall(Callee, {Arg});
1391513915
}
13916+
case WebAssembly::BI__builtin_wasm_tls_size: {
13917+
llvm::Type *ResultType = ConvertType(E->getType());
13918+
Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_tls_size, ResultType);
13919+
return Builder.CreateCall(Callee);
13920+
}
1391613921
case WebAssembly::BI__builtin_wasm_throw: {
1391713922
Value *Tag = EmitScalarExpr(E->getArg(0));
1391813923
Value *Obj = EmitScalarExpr(E->getArg(1));

clang/test/CodeGen/builtins-wasm.c

+6
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ void data_drop() {
3838
// WEBASSEMBLY64: call void @llvm.wasm.data.drop(i32 3)
3939
}
4040

41+
__SIZE_TYPE__ tls_size() {
42+
return __builtin_wasm_tls_size();
43+
// WEBASSEMBLY32: call i32 @llvm.wasm.tls.size.i32()
44+
// WEBASSEMBLY64: call i64 @llvm.wasm.tls.size.i64()
45+
}
46+
4147
void throw(void *obj) {
4248
return __builtin_wasm_throw(0, obj);
4349
// WEBASSEMBLY32: call void @llvm.wasm.throw(i32 0, i8* %{{.*}})

lld/test/wasm/data-segments.ll

+15-5
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44

55
; atomics => active segments (TODO: error)
66
; RUN: wasm-ld -no-gc-sections --no-entry --shared-memory --max-memory=131072 %t.atomics.o -o %t.atomics.wasm
7-
; RUN: obj2yaml %t.atomics.wasm | FileCheck %s --check-prefix ACTIVE
7+
; RUN: obj2yaml %t.atomics.wasm | FileCheck %s --check-prefixes ACTIVE,ACTIVE-TLS
88

99
; atomics, active segments => active segments
1010
; RUN: wasm-ld -no-gc-sections --no-entry --shared-memory --max-memory=131072 --active-segments %t.atomics.o -o %t.atomics.active.wasm
11-
; RUN: obj2yaml %t.atomics.active.wasm | FileCheck %s --check-prefix ACTIVE
11+
; RUN: obj2yaml %t.atomics.active.wasm | FileCheck %s --check-prefixes ACTIVE,ACTIVE-TLS
1212

1313
; atomics, passive segments => error
1414
; RUN: not wasm-ld -no-gc-sections --no-entry --shared-memory --max-memory=131072 --passive-segments %t.atomics.o -o %t.atomics.passive.wasm 2>&1 | FileCheck %s --check-prefix ERROR
@@ -27,15 +27,15 @@
2727

2828
; atomics, bulk memory => active segments (TODO: passive)
2929
; RUN: wasm-ld -no-gc-sections --no-entry --shared-memory --max-memory=131072 %t.atomics.bulk-mem.o -o %t.atomics.bulk-mem.wasm
30-
; RUN: obj2yaml %t.atomics.bulk-mem.wasm | FileCheck %s --check-prefix ACTIVE
30+
; RUN: obj2yaml %t.atomics.bulk-mem.wasm | FileCheck %s --check-prefixes ACTIVE,ACTIVE-TLS
3131

3232
; atomics, bulk memory, active segments => active segments
3333
; RUN: wasm-ld -no-gc-sections --no-entry --shared-memory --max-memory=131072 --active-segments %t.atomics.bulk-mem.o -o %t.atomics.bulk-mem.active.wasm
34-
; RUN: obj2yaml %t.atomics.bulk-mem.active.wasm | FileCheck %s --check-prefix ACTIVE
34+
; RUN: obj2yaml %t.atomics.bulk-mem.active.wasm | FileCheck %s --check-prefixes ACTIVE,ACTIVE-TLS
3535

3636
; atomics, bulk memory, passive segments => passive segments
3737
; RUN: wasm-ld -no-gc-sections --no-entry --shared-memory --max-memory=131072 --passive-segments %t.atomics.bulk-mem.o -o %t.atomics.bulk-mem.passive.wasm
38-
; RUN: obj2yaml %t.atomics.bulk-mem.passive.wasm | FileCheck %s --check-prefix PASSIVE
38+
; RUN: obj2yaml %t.atomics.bulk-mem.passive.wasm | FileCheck %s --check-prefixes PASSIVE,PASSIVE-TLS
3939

4040
target triple = "wasm32-unknown-unknown"
4141

@@ -54,6 +54,9 @@ target triple = "wasm32-unknown-unknown"
5454
; ACTIVE-NEXT: - Index: 0
5555
; ACTIVE-NEXT: Locals: []
5656
; ACTIVE-NEXT: Body: 0B
57+
; ACTIVE-TLS-NEXT: - Index: 1
58+
; ACTIVE-TLS-NEXT: Locals: []
59+
; ACTIVE-TLS-NEXT: Body: 0B
5760
; ACTIVE-NEXT: - Type: DATA
5861
; ACTIVE-NEXT: Segments:
5962
; ACTIVE-NEXT: - SectionOffset: 7
@@ -80,6 +83,8 @@ target triple = "wasm32-unknown-unknown"
8083
; ACTIVE-NEXT: FunctionNames:
8184
; ACTIVE-NEXT: - Index: 0
8285
; ACTIVE-NEXT: Name: __wasm_call_ctors
86+
; ACTIVE-TLS-NEXT: - Index: 1
87+
; ACTIVE-TLS-NEXT: Name: __wasm_init_tls
8388

8489
; PASSIVE-LABEL: - Type: CODE
8590
; PASSIVE-NEXT: Functions:
@@ -89,6 +94,9 @@ target triple = "wasm32-unknown-unknown"
8994
; PASSIVE-NEXT: - Index: 1
9095
; PASSIVE-NEXT: Locals: []
9196
; PASSIVE-NEXT: Body: 41800841004114FC080000FC090041940841004190CE00FC080100FC090141A4D6004100410DFC080200FC09020B
97+
; PASSIVE-TLS-NEXT: - Index: 2
98+
; PASSIVE-TLS-NEXT: Locals: []
99+
; PASSIVE-TLS-NEXT: Body: 0B
92100
; PASSIVE-NEXT: - Type: DATA
93101
; PASSIVE-NEXT: Segments:
94102
; PASSIVE-NEXT: - SectionOffset: 3
@@ -108,3 +116,5 @@ target triple = "wasm32-unknown-unknown"
108116
; PASSIVE-NEXT: Name: __wasm_call_ctors
109117
; PASSIVE-NEXT: - Index: 1
110118
; PASSIVE-NEXT: Name: __wasm_init_memory
119+
; PASSIVE-TLS-NEXT: - Index: 2
120+
; PASSIVE-TLS-NEXT: Name: __wasm_init_tls

lld/test/wasm/tls.ll

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
; RUN: llc -mattr=+bulk-memory -filetype=obj %s -o %t.o
2+
3+
target triple = "wasm32-unknown-unknown"
4+
5+
@tls1 = thread_local(localexec) global i32 1, align 4
6+
@no_tls = global i32 0, align 4
7+
@tls2 = thread_local(localexec) global i32 1, align 4
8+
9+
define i32* @tls1_addr() {
10+
ret i32* @tls1
11+
}
12+
13+
define i32* @tls2_addr() {
14+
ret i32* @tls2
15+
}
16+
17+
; RUN: wasm-ld -no-gc-sections --shared-memory --max-memory=131072 --no-entry -o %t.wasm %t.o
18+
; RUN: obj2yaml %t.wasm | FileCheck %s
19+
20+
; RUN: wasm-ld -no-gc-sections --shared-memory --max-memory=131072 --no-merge-data-segments --no-entry -o %t.wasm %t.o
21+
; RUN: obj2yaml %t.wasm | FileCheck %s
22+
23+
; CHECK: - Type: GLOBAL
24+
; CHECK-NEXT: Globals:
25+
; CHECK-NEXT: - Index: 0
26+
; CHECK-NEXT: Type: I32
27+
; CHECK-NEXT: Mutable: true
28+
; CHECK-NEXT: InitExpr:
29+
; CHECK-NEXT: Opcode: I32_CONST
30+
; CHECK-NEXT: Value: 66576
31+
; CHECK-NEXT: - Index: 1
32+
; CHECK-NEXT: Type: I32
33+
; CHECK-NEXT: Mutable: true
34+
; CHECK-NEXT: InitExpr:
35+
; CHECK-NEXT: Opcode: I32_CONST
36+
; CHECK-NEXT: Value: 0
37+
; CHECK-NEXT: - Index: 2
38+
; CHECK-NEXT: Type: I32
39+
; CHECK-NEXT: Mutable: false
40+
; CHECK-NEXT: InitExpr:
41+
; CHECK-NEXT: Opcode: I32_CONST
42+
; CHECK-NEXT: Value: 8
43+
44+
45+
; CHECK: - Type: CODE
46+
; CHECK-NEXT: Functions:
47+
; CHECK-NEXT: - Index: 0
48+
; CHECK-NEXT: Locals: []
49+
; CHECK-NEXT: Body: 0B
50+
; CHECK-NEXT: - Index: 1
51+
; CHECK-NEXT: Locals: []
52+
; CHECK-NEXT: Body: 20002401200041004108FC0800000B
53+
54+
; Expected body of __wasm_init_tls:
55+
; local.get 0
56+
; global.set 1
57+
; local.get 0
58+
; i32.const 0
59+
; i32.const 8
60+
; memory.init 0, 0
61+
; end
62+
63+
; CHECK-NEXT: - Index: 2
64+
; CHECK-NEXT: Locals: []
65+
; CHECK-NEXT: Body: 2381808080004180808080006A0B
66+
67+
; Expected body of tls1_addr:
68+
; global.get 1
69+
; i32.const 0
70+
; i32.add
71+
; end
72+
73+
; CHECK-NEXT: - Index: 3
74+
; CHECK-NEXT: Locals: []
75+
; CHECK-NEXT: Body: 2381808080004184808080006A0B
76+
77+
; Expected body of tls1_addr:
78+
; global.get 1
79+
; i32.const 4
80+
; i32.add
81+
; end

lld/wasm/Driver.cpp

+25
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,7 @@ createUndefinedGlobal(StringRef name, llvm::wasm::WasmGlobalType *type) {
454454
// Create ABI-defined synthetic symbols
455455
static void createSyntheticSymbols() {
456456
static WasmSignature nullSignature = {{}, {}};
457+
static WasmSignature i32ArgSignature = {{}, {ValType::I32}};
457458
static llvm::wasm::WasmGlobalType globalTypeI32 = {WASM_TYPE_I32, false};
458459
static llvm::wasm::WasmGlobalType mutableGlobalTypeI32 = {WASM_TYPE_I32,
459460
true};
@@ -516,6 +517,30 @@ static void createSyntheticSymbols() {
516517
WasmSym::heapBase = symtab->addOptionalDataSymbol("__heap_base");
517518
}
518519

520+
if (config->sharedMemory && !config->shared) {
521+
llvm::wasm::WasmGlobal tlsBaseGlobal;
522+
tlsBaseGlobal.Type = {WASM_TYPE_I32, true};
523+
tlsBaseGlobal.InitExpr.Value.Int32 = 0;
524+
tlsBaseGlobal.InitExpr.Opcode = WASM_OPCODE_I32_CONST;
525+
tlsBaseGlobal.SymbolName = "__tls_base";
526+
WasmSym::tlsBase =
527+
symtab->addSyntheticGlobal("__tls_base", WASM_SYMBOL_VISIBILITY_HIDDEN,
528+
make<InputGlobal>(tlsBaseGlobal, nullptr));
529+
530+
llvm::wasm::WasmGlobal tlsSizeGlobal;
531+
tlsSizeGlobal.Type = {WASM_TYPE_I32, false};
532+
tlsSizeGlobal.InitExpr.Value.Int32 = 0;
533+
tlsSizeGlobal.InitExpr.Opcode = WASM_OPCODE_I32_CONST;
534+
tlsSizeGlobal.SymbolName = "__tls_size";
535+
WasmSym::tlsSize =
536+
symtab->addSyntheticGlobal("__tls_size", WASM_SYMBOL_VISIBILITY_HIDDEN,
537+
make<InputGlobal>(tlsSizeGlobal, nullptr));
538+
539+
WasmSym::initTLS = symtab->addSyntheticFunction(
540+
"__wasm_init_tls", WASM_SYMBOL_VISIBILITY_HIDDEN,
541+
make<SyntheticFunction>(i32ArgSignature, "__wasm_init_tls"));
542+
}
543+
519544
WasmSym::dsoHandle = symtab->addSyntheticDataSymbol(
520545
"__dso_handle", WASM_SYMBOL_VISIBILITY_HIDDEN);
521546
}

lld/wasm/Symbols.cpp

+10-1
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@ using namespace lld::wasm;
2727
DefinedFunction *WasmSym::callCtors;
2828
DefinedFunction *WasmSym::initMemory;
2929
DefinedFunction *WasmSym::applyRelocs;
30+
DefinedFunction *WasmSym::initTLS;
3031
DefinedData *WasmSym::dsoHandle;
3132
DefinedData *WasmSym::dataEnd;
3233
DefinedData *WasmSym::globalBase;
3334
DefinedData *WasmSym::heapBase;
3435
GlobalSymbol *WasmSym::stackPointer;
36+
GlobalSymbol *WasmSym::tlsBase;
37+
GlobalSymbol *WasmSym::tlsSize;
3538
UndefinedGlobal *WasmSym::tableBase;
3639
UndefinedGlobal *WasmSym::memoryBase;
3740

@@ -200,8 +203,14 @@ DefinedFunction::DefinedFunction(StringRef name, uint32_t flags, InputFile *f,
200203

201204
uint32_t DefinedData::getVirtualAddress() const {
202205
LLVM_DEBUG(dbgs() << "getVirtualAddress: " << getName() << "\n");
203-
if (segment)
206+
if (segment) {
207+
// For thread local data, the symbol location is relative to the start of
208+
// the .tdata section, since they are used as offsets from __tls_base.
209+
// Hence, we do not add in segment->outputSeg->startVA.
210+
if (segment->outputSeg->name == ".tdata")
211+
return segment->outputSegmentOffset + offset;
204212
return segment->outputSeg->startVA + segment->outputSegmentOffset + offset;
213+
}
205214
return offset;
206215
}
207216

lld/wasm/Symbols.h

+13
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,15 @@ struct WasmSym {
426426
// linear memory.
427427
static GlobalSymbol *stackPointer;
428428

429+
// __tls_base
430+
// Global that holds the address of the base of the current thread's
431+
// TLS block.
432+
static GlobalSymbol *tlsBase;
433+
434+
// __tls_size
435+
// Symbol whose value is the size of the TLS block.
436+
static GlobalSymbol *tlsSize;
437+
429438
// __data_end
430439
// Symbol marking the end of the data and bss.
431440
static DefinedData *dataEnd;
@@ -448,6 +457,10 @@ struct WasmSym {
448457
// Function that applies relocations to data segment post-instantiation.
449458
static DefinedFunction *applyRelocs;
450459

460+
// __wasm_init_tls
461+
// Function that allocates thread-local storage and initializes it.
462+
static DefinedFunction *initTLS;
463+
451464
// __dso_handle
452465
// Symbol used in calls to __cxa_atexit to determine current DLL
453466
static DefinedData *dsoHandle;

0 commit comments

Comments
 (0)