mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-25 12:16:08 +00:00
[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
This commit is contained in:
parent
21f2858dcf
commit
42bba4b852
@ -29,6 +29,9 @@ BUILTIN(__builtin_wasm_memory_grow, "zIiz", "n")
|
|||||||
TARGET_BUILTIN(__builtin_wasm_memory_init, "vIUiIUiv*UiUi", "", "bulk-memory")
|
TARGET_BUILTIN(__builtin_wasm_memory_init, "vIUiIUiv*UiUi", "", "bulk-memory")
|
||||||
TARGET_BUILTIN(__builtin_wasm_data_drop, "vIUi", "", "bulk-memory")
|
TARGET_BUILTIN(__builtin_wasm_data_drop, "vIUi", "", "bulk-memory")
|
||||||
|
|
||||||
|
// Thread-local storage
|
||||||
|
TARGET_BUILTIN(__builtin_wasm_tls_size, "z", "nc", "bulk-memory")
|
||||||
|
|
||||||
// Floating point min/max
|
// Floating point min/max
|
||||||
BUILTIN(__builtin_wasm_min_f32, "fff", "nc")
|
BUILTIN(__builtin_wasm_min_f32, "fff", "nc")
|
||||||
BUILTIN(__builtin_wasm_max_f32, "fff", "nc")
|
BUILTIN(__builtin_wasm_max_f32, "fff", "nc")
|
||||||
|
@ -13913,6 +13913,11 @@ Value *CodeGenFunction::EmitWebAssemblyBuiltinExpr(unsigned BuiltinID,
|
|||||||
Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_data_drop);
|
Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_data_drop);
|
||||||
return Builder.CreateCall(Callee, {Arg});
|
return Builder.CreateCall(Callee, {Arg});
|
||||||
}
|
}
|
||||||
|
case WebAssembly::BI__builtin_wasm_tls_size: {
|
||||||
|
llvm::Type *ResultType = ConvertType(E->getType());
|
||||||
|
Function *Callee = CGM.getIntrinsic(Intrinsic::wasm_tls_size, ResultType);
|
||||||
|
return Builder.CreateCall(Callee);
|
||||||
|
}
|
||||||
case WebAssembly::BI__builtin_wasm_throw: {
|
case WebAssembly::BI__builtin_wasm_throw: {
|
||||||
Value *Tag = EmitScalarExpr(E->getArg(0));
|
Value *Tag = EmitScalarExpr(E->getArg(0));
|
||||||
Value *Obj = EmitScalarExpr(E->getArg(1));
|
Value *Obj = EmitScalarExpr(E->getArg(1));
|
||||||
|
@ -38,6 +38,12 @@ void data_drop() {
|
|||||||
// WEBASSEMBLY64: call void @llvm.wasm.data.drop(i32 3)
|
// WEBASSEMBLY64: call void @llvm.wasm.data.drop(i32 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
__SIZE_TYPE__ tls_size() {
|
||||||
|
return __builtin_wasm_tls_size();
|
||||||
|
// WEBASSEMBLY32: call i32 @llvm.wasm.tls.size.i32()
|
||||||
|
// WEBASSEMBLY64: call i64 @llvm.wasm.tls.size.i64()
|
||||||
|
}
|
||||||
|
|
||||||
void throw(void *obj) {
|
void throw(void *obj) {
|
||||||
return __builtin_wasm_throw(0, obj);
|
return __builtin_wasm_throw(0, obj);
|
||||||
// WEBASSEMBLY32: call void @llvm.wasm.throw(i32 0, i8* %{{.*}})
|
// WEBASSEMBLY32: call void @llvm.wasm.throw(i32 0, i8* %{{.*}})
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
; atomics => active segments (TODO: error)
|
; atomics => active segments (TODO: error)
|
||||||
; RUN: wasm-ld -no-gc-sections --no-entry --shared-memory --max-memory=131072 %t.atomics.o -o %t.atomics.wasm
|
; RUN: wasm-ld -no-gc-sections --no-entry --shared-memory --max-memory=131072 %t.atomics.o -o %t.atomics.wasm
|
||||||
; RUN: obj2yaml %t.atomics.wasm | FileCheck %s --check-prefix ACTIVE
|
; RUN: obj2yaml %t.atomics.wasm | FileCheck %s --check-prefixes ACTIVE,ACTIVE-TLS
|
||||||
|
|
||||||
; atomics, active segments => active segments
|
; atomics, active segments => active segments
|
||||||
; RUN: wasm-ld -no-gc-sections --no-entry --shared-memory --max-memory=131072 --active-segments %t.atomics.o -o %t.atomics.active.wasm
|
; RUN: wasm-ld -no-gc-sections --no-entry --shared-memory --max-memory=131072 --active-segments %t.atomics.o -o %t.atomics.active.wasm
|
||||||
; RUN: obj2yaml %t.atomics.active.wasm | FileCheck %s --check-prefix ACTIVE
|
; RUN: obj2yaml %t.atomics.active.wasm | FileCheck %s --check-prefixes ACTIVE,ACTIVE-TLS
|
||||||
|
|
||||||
; atomics, passive segments => error
|
; atomics, passive segments => error
|
||||||
; 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
|
; 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 @@
|
|||||||
|
|
||||||
; atomics, bulk memory => active segments (TODO: passive)
|
; atomics, bulk memory => active segments (TODO: passive)
|
||||||
; RUN: wasm-ld -no-gc-sections --no-entry --shared-memory --max-memory=131072 %t.atomics.bulk-mem.o -o %t.atomics.bulk-mem.wasm
|
; RUN: wasm-ld -no-gc-sections --no-entry --shared-memory --max-memory=131072 %t.atomics.bulk-mem.o -o %t.atomics.bulk-mem.wasm
|
||||||
; RUN: obj2yaml %t.atomics.bulk-mem.wasm | FileCheck %s --check-prefix ACTIVE
|
; RUN: obj2yaml %t.atomics.bulk-mem.wasm | FileCheck %s --check-prefixes ACTIVE,ACTIVE-TLS
|
||||||
|
|
||||||
; atomics, bulk memory, active segments => active segments
|
; atomics, bulk memory, active segments => active segments
|
||||||
; 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
|
; 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
|
||||||
; RUN: obj2yaml %t.atomics.bulk-mem.active.wasm | FileCheck %s --check-prefix ACTIVE
|
; RUN: obj2yaml %t.atomics.bulk-mem.active.wasm | FileCheck %s --check-prefixes ACTIVE,ACTIVE-TLS
|
||||||
|
|
||||||
; atomics, bulk memory, passive segments => passive segments
|
; atomics, bulk memory, passive segments => passive segments
|
||||||
; 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
|
; 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
|
||||||
; RUN: obj2yaml %t.atomics.bulk-mem.passive.wasm | FileCheck %s --check-prefix PASSIVE
|
; RUN: obj2yaml %t.atomics.bulk-mem.passive.wasm | FileCheck %s --check-prefixes PASSIVE,PASSIVE-TLS
|
||||||
|
|
||||||
target triple = "wasm32-unknown-unknown"
|
target triple = "wasm32-unknown-unknown"
|
||||||
|
|
||||||
@ -54,6 +54,9 @@ target triple = "wasm32-unknown-unknown"
|
|||||||
; ACTIVE-NEXT: - Index: 0
|
; ACTIVE-NEXT: - Index: 0
|
||||||
; ACTIVE-NEXT: Locals: []
|
; ACTIVE-NEXT: Locals: []
|
||||||
; ACTIVE-NEXT: Body: 0B
|
; ACTIVE-NEXT: Body: 0B
|
||||||
|
; ACTIVE-TLS-NEXT: - Index: 1
|
||||||
|
; ACTIVE-TLS-NEXT: Locals: []
|
||||||
|
; ACTIVE-TLS-NEXT: Body: 0B
|
||||||
; ACTIVE-NEXT: - Type: DATA
|
; ACTIVE-NEXT: - Type: DATA
|
||||||
; ACTIVE-NEXT: Segments:
|
; ACTIVE-NEXT: Segments:
|
||||||
; ACTIVE-NEXT: - SectionOffset: 7
|
; ACTIVE-NEXT: - SectionOffset: 7
|
||||||
@ -80,6 +83,8 @@ target triple = "wasm32-unknown-unknown"
|
|||||||
; ACTIVE-NEXT: FunctionNames:
|
; ACTIVE-NEXT: FunctionNames:
|
||||||
; ACTIVE-NEXT: - Index: 0
|
; ACTIVE-NEXT: - Index: 0
|
||||||
; ACTIVE-NEXT: Name: __wasm_call_ctors
|
; ACTIVE-NEXT: Name: __wasm_call_ctors
|
||||||
|
; ACTIVE-TLS-NEXT: - Index: 1
|
||||||
|
; ACTIVE-TLS-NEXT: Name: __wasm_init_tls
|
||||||
|
|
||||||
; PASSIVE-LABEL: - Type: CODE
|
; PASSIVE-LABEL: - Type: CODE
|
||||||
; PASSIVE-NEXT: Functions:
|
; PASSIVE-NEXT: Functions:
|
||||||
@ -89,6 +94,9 @@ target triple = "wasm32-unknown-unknown"
|
|||||||
; PASSIVE-NEXT: - Index: 1
|
; PASSIVE-NEXT: - Index: 1
|
||||||
; PASSIVE-NEXT: Locals: []
|
; PASSIVE-NEXT: Locals: []
|
||||||
; PASSIVE-NEXT: Body: 41800841004114FC080000FC090041940841004190CE00FC080100FC090141A4D6004100410DFC080200FC09020B
|
; PASSIVE-NEXT: Body: 41800841004114FC080000FC090041940841004190CE00FC080100FC090141A4D6004100410DFC080200FC09020B
|
||||||
|
; PASSIVE-TLS-NEXT: - Index: 2
|
||||||
|
; PASSIVE-TLS-NEXT: Locals: []
|
||||||
|
; PASSIVE-TLS-NEXT: Body: 0B
|
||||||
; PASSIVE-NEXT: - Type: DATA
|
; PASSIVE-NEXT: - Type: DATA
|
||||||
; PASSIVE-NEXT: Segments:
|
; PASSIVE-NEXT: Segments:
|
||||||
; PASSIVE-NEXT: - SectionOffset: 3
|
; PASSIVE-NEXT: - SectionOffset: 3
|
||||||
@ -108,3 +116,5 @@ target triple = "wasm32-unknown-unknown"
|
|||||||
; PASSIVE-NEXT: Name: __wasm_call_ctors
|
; PASSIVE-NEXT: Name: __wasm_call_ctors
|
||||||
; PASSIVE-NEXT: - Index: 1
|
; PASSIVE-NEXT: - Index: 1
|
||||||
; PASSIVE-NEXT: Name: __wasm_init_memory
|
; PASSIVE-NEXT: Name: __wasm_init_memory
|
||||||
|
; PASSIVE-TLS-NEXT: - Index: 2
|
||||||
|
; PASSIVE-TLS-NEXT: Name: __wasm_init_tls
|
||||||
|
81
lld/test/wasm/tls.ll
Normal file
81
lld/test/wasm/tls.ll
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
; RUN: llc -mattr=+bulk-memory -filetype=obj %s -o %t.o
|
||||||
|
|
||||||
|
target triple = "wasm32-unknown-unknown"
|
||||||
|
|
||||||
|
@tls1 = thread_local(localexec) global i32 1, align 4
|
||||||
|
@no_tls = global i32 0, align 4
|
||||||
|
@tls2 = thread_local(localexec) global i32 1, align 4
|
||||||
|
|
||||||
|
define i32* @tls1_addr() {
|
||||||
|
ret i32* @tls1
|
||||||
|
}
|
||||||
|
|
||||||
|
define i32* @tls2_addr() {
|
||||||
|
ret i32* @tls2
|
||||||
|
}
|
||||||
|
|
||||||
|
; RUN: wasm-ld -no-gc-sections --shared-memory --max-memory=131072 --no-entry -o %t.wasm %t.o
|
||||||
|
; RUN: obj2yaml %t.wasm | FileCheck %s
|
||||||
|
|
||||||
|
; RUN: wasm-ld -no-gc-sections --shared-memory --max-memory=131072 --no-merge-data-segments --no-entry -o %t.wasm %t.o
|
||||||
|
; RUN: obj2yaml %t.wasm | FileCheck %s
|
||||||
|
|
||||||
|
; CHECK: - Type: GLOBAL
|
||||||
|
; 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
|
||||||
|
; CHECK-NEXT: - Index: 1
|
||||||
|
; CHECK-NEXT: Type: I32
|
||||||
|
; CHECK-NEXT: Mutable: true
|
||||||
|
; CHECK-NEXT: InitExpr:
|
||||||
|
; CHECK-NEXT: Opcode: I32_CONST
|
||||||
|
; CHECK-NEXT: Value: 0
|
||||||
|
; CHECK-NEXT: - Index: 2
|
||||||
|
; CHECK-NEXT: Type: I32
|
||||||
|
; CHECK-NEXT: Mutable: false
|
||||||
|
; CHECK-NEXT: InitExpr:
|
||||||
|
; CHECK-NEXT: Opcode: I32_CONST
|
||||||
|
; CHECK-NEXT: Value: 8
|
||||||
|
|
||||||
|
|
||||||
|
; CHECK: - Type: CODE
|
||||||
|
; CHECK-NEXT: Functions:
|
||||||
|
; CHECK-NEXT: - Index: 0
|
||||||
|
; CHECK-NEXT: Locals: []
|
||||||
|
; CHECK-NEXT: Body: 0B
|
||||||
|
; CHECK-NEXT: - Index: 1
|
||||||
|
; CHECK-NEXT: Locals: []
|
||||||
|
; CHECK-NEXT: Body: 20002401200041004108FC0800000B
|
||||||
|
|
||||||
|
; Expected body of __wasm_init_tls:
|
||||||
|
; local.get 0
|
||||||
|
; global.set 1
|
||||||
|
; local.get 0
|
||||||
|
; i32.const 0
|
||||||
|
; i32.const 8
|
||||||
|
; memory.init 0, 0
|
||||||
|
; end
|
||||||
|
|
||||||
|
; CHECK-NEXT: - Index: 2
|
||||||
|
; CHECK-NEXT: Locals: []
|
||||||
|
; CHECK-NEXT: Body: 2381808080004180808080006A0B
|
||||||
|
|
||||||
|
; Expected body of tls1_addr:
|
||||||
|
; global.get 1
|
||||||
|
; i32.const 0
|
||||||
|
; i32.add
|
||||||
|
; end
|
||||||
|
|
||||||
|
; CHECK-NEXT: - Index: 3
|
||||||
|
; CHECK-NEXT: Locals: []
|
||||||
|
; CHECK-NEXT: Body: 2381808080004184808080006A0B
|
||||||
|
|
||||||
|
; Expected body of tls1_addr:
|
||||||
|
; global.get 1
|
||||||
|
; i32.const 4
|
||||||
|
; i32.add
|
||||||
|
; end
|
@ -454,6 +454,7 @@ createUndefinedGlobal(StringRef name, llvm::wasm::WasmGlobalType *type) {
|
|||||||
// Create ABI-defined synthetic symbols
|
// Create ABI-defined synthetic symbols
|
||||||
static void createSyntheticSymbols() {
|
static void createSyntheticSymbols() {
|
||||||
static WasmSignature nullSignature = {{}, {}};
|
static WasmSignature nullSignature = {{}, {}};
|
||||||
|
static WasmSignature i32ArgSignature = {{}, {ValType::I32}};
|
||||||
static llvm::wasm::WasmGlobalType globalTypeI32 = {WASM_TYPE_I32, false};
|
static llvm::wasm::WasmGlobalType globalTypeI32 = {WASM_TYPE_I32, false};
|
||||||
static llvm::wasm::WasmGlobalType mutableGlobalTypeI32 = {WASM_TYPE_I32,
|
static llvm::wasm::WasmGlobalType mutableGlobalTypeI32 = {WASM_TYPE_I32,
|
||||||
true};
|
true};
|
||||||
@ -516,6 +517,30 @@ static void createSyntheticSymbols() {
|
|||||||
WasmSym::heapBase = symtab->addOptionalDataSymbol("__heap_base");
|
WasmSym::heapBase = symtab->addOptionalDataSymbol("__heap_base");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config->sharedMemory && !config->shared) {
|
||||||
|
llvm::wasm::WasmGlobal tlsBaseGlobal;
|
||||||
|
tlsBaseGlobal.Type = {WASM_TYPE_I32, true};
|
||||||
|
tlsBaseGlobal.InitExpr.Value.Int32 = 0;
|
||||||
|
tlsBaseGlobal.InitExpr.Opcode = WASM_OPCODE_I32_CONST;
|
||||||
|
tlsBaseGlobal.SymbolName = "__tls_base";
|
||||||
|
WasmSym::tlsBase =
|
||||||
|
symtab->addSyntheticGlobal("__tls_base", WASM_SYMBOL_VISIBILITY_HIDDEN,
|
||||||
|
make<InputGlobal>(tlsBaseGlobal, nullptr));
|
||||||
|
|
||||||
|
llvm::wasm::WasmGlobal tlsSizeGlobal;
|
||||||
|
tlsSizeGlobal.Type = {WASM_TYPE_I32, false};
|
||||||
|
tlsSizeGlobal.InitExpr.Value.Int32 = 0;
|
||||||
|
tlsSizeGlobal.InitExpr.Opcode = WASM_OPCODE_I32_CONST;
|
||||||
|
tlsSizeGlobal.SymbolName = "__tls_size";
|
||||||
|
WasmSym::tlsSize =
|
||||||
|
symtab->addSyntheticGlobal("__tls_size", WASM_SYMBOL_VISIBILITY_HIDDEN,
|
||||||
|
make<InputGlobal>(tlsSizeGlobal, nullptr));
|
||||||
|
|
||||||
|
WasmSym::initTLS = symtab->addSyntheticFunction(
|
||||||
|
"__wasm_init_tls", WASM_SYMBOL_VISIBILITY_HIDDEN,
|
||||||
|
make<SyntheticFunction>(i32ArgSignature, "__wasm_init_tls"));
|
||||||
|
}
|
||||||
|
|
||||||
WasmSym::dsoHandle = symtab->addSyntheticDataSymbol(
|
WasmSym::dsoHandle = symtab->addSyntheticDataSymbol(
|
||||||
"__dso_handle", WASM_SYMBOL_VISIBILITY_HIDDEN);
|
"__dso_handle", WASM_SYMBOL_VISIBILITY_HIDDEN);
|
||||||
}
|
}
|
||||||
|
@ -27,11 +27,14 @@ using namespace lld::wasm;
|
|||||||
DefinedFunction *WasmSym::callCtors;
|
DefinedFunction *WasmSym::callCtors;
|
||||||
DefinedFunction *WasmSym::initMemory;
|
DefinedFunction *WasmSym::initMemory;
|
||||||
DefinedFunction *WasmSym::applyRelocs;
|
DefinedFunction *WasmSym::applyRelocs;
|
||||||
|
DefinedFunction *WasmSym::initTLS;
|
||||||
DefinedData *WasmSym::dsoHandle;
|
DefinedData *WasmSym::dsoHandle;
|
||||||
DefinedData *WasmSym::dataEnd;
|
DefinedData *WasmSym::dataEnd;
|
||||||
DefinedData *WasmSym::globalBase;
|
DefinedData *WasmSym::globalBase;
|
||||||
DefinedData *WasmSym::heapBase;
|
DefinedData *WasmSym::heapBase;
|
||||||
GlobalSymbol *WasmSym::stackPointer;
|
GlobalSymbol *WasmSym::stackPointer;
|
||||||
|
GlobalSymbol *WasmSym::tlsBase;
|
||||||
|
GlobalSymbol *WasmSym::tlsSize;
|
||||||
UndefinedGlobal *WasmSym::tableBase;
|
UndefinedGlobal *WasmSym::tableBase;
|
||||||
UndefinedGlobal *WasmSym::memoryBase;
|
UndefinedGlobal *WasmSym::memoryBase;
|
||||||
|
|
||||||
@ -200,8 +203,14 @@ DefinedFunction::DefinedFunction(StringRef name, uint32_t flags, InputFile *f,
|
|||||||
|
|
||||||
uint32_t DefinedData::getVirtualAddress() const {
|
uint32_t DefinedData::getVirtualAddress() const {
|
||||||
LLVM_DEBUG(dbgs() << "getVirtualAddress: " << getName() << "\n");
|
LLVM_DEBUG(dbgs() << "getVirtualAddress: " << getName() << "\n");
|
||||||
if (segment)
|
if (segment) {
|
||||||
|
// For thread local data, the symbol location is relative to the start of
|
||||||
|
// the .tdata section, since they are used as offsets from __tls_base.
|
||||||
|
// Hence, we do not add in segment->outputSeg->startVA.
|
||||||
|
if (segment->outputSeg->name == ".tdata")
|
||||||
|
return segment->outputSegmentOffset + offset;
|
||||||
return segment->outputSeg->startVA + segment->outputSegmentOffset + offset;
|
return segment->outputSeg->startVA + segment->outputSegmentOffset + offset;
|
||||||
|
}
|
||||||
return offset;
|
return offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,6 +426,15 @@ struct WasmSym {
|
|||||||
// linear memory.
|
// linear memory.
|
||||||
static GlobalSymbol *stackPointer;
|
static GlobalSymbol *stackPointer;
|
||||||
|
|
||||||
|
// __tls_base
|
||||||
|
// Global that holds the address of the base of the current thread's
|
||||||
|
// TLS block.
|
||||||
|
static GlobalSymbol *tlsBase;
|
||||||
|
|
||||||
|
// __tls_size
|
||||||
|
// Symbol whose value is the size of the TLS block.
|
||||||
|
static GlobalSymbol *tlsSize;
|
||||||
|
|
||||||
// __data_end
|
// __data_end
|
||||||
// Symbol marking the end of the data and bss.
|
// Symbol marking the end of the data and bss.
|
||||||
static DefinedData *dataEnd;
|
static DefinedData *dataEnd;
|
||||||
@ -448,6 +457,10 @@ struct WasmSym {
|
|||||||
// Function that applies relocations to data segment post-instantiation.
|
// Function that applies relocations to data segment post-instantiation.
|
||||||
static DefinedFunction *applyRelocs;
|
static DefinedFunction *applyRelocs;
|
||||||
|
|
||||||
|
// __wasm_init_tls
|
||||||
|
// Function that allocates thread-local storage and initializes it.
|
||||||
|
static DefinedFunction *initTLS;
|
||||||
|
|
||||||
// __dso_handle
|
// __dso_handle
|
||||||
// Symbol used in calls to __cxa_atexit to determine current DLL
|
// Symbol used in calls to __cxa_atexit to determine current DLL
|
||||||
static DefinedData *dsoHandle;
|
static DefinedData *dsoHandle;
|
||||||
|
@ -57,6 +57,7 @@ private:
|
|||||||
void createInitMemoryFunction();
|
void createInitMemoryFunction();
|
||||||
void createApplyRelocationsFunction();
|
void createApplyRelocationsFunction();
|
||||||
void createCallCtorsFunction();
|
void createCallCtorsFunction();
|
||||||
|
void createInitTLSFunction();
|
||||||
|
|
||||||
void assignIndexes();
|
void assignIndexes();
|
||||||
void populateSymtab();
|
void populateSymtab();
|
||||||
@ -242,6 +243,11 @@ void Writer::layoutMemory() {
|
|||||||
log(formatv("mem: {0,-15} offset={1,-8} size={2,-8} align={3}", seg->name,
|
log(formatv("mem: {0,-15} offset={1,-8} size={2,-8} align={3}", seg->name,
|
||||||
memoryPtr, seg->size, seg->alignment));
|
memoryPtr, seg->size, seg->alignment));
|
||||||
memoryPtr += seg->size;
|
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.
|
// TODO: Add .bss space here.
|
||||||
@ -353,6 +359,7 @@ void Writer::populateTargetFeatures() {
|
|||||||
StringMap<std::string> used;
|
StringMap<std::string> used;
|
||||||
StringMap<std::string> required;
|
StringMap<std::string> required;
|
||||||
StringMap<std::string> disallowed;
|
StringMap<std::string> disallowed;
|
||||||
|
bool tlsUsed = false;
|
||||||
|
|
||||||
// Only infer used features if user did not specify features
|
// Only infer used features if user did not specify features
|
||||||
bool inferFeatures = !config->features.hasValue();
|
bool inferFeatures = !config->features.hasValue();
|
||||||
@ -385,6 +392,14 @@ void Writer::populateTargetFeatures() {
|
|||||||
std::to_string(feature.Prefix));
|
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)
|
if (inferFeatures)
|
||||||
@ -411,6 +426,10 @@ void Writer::populateTargetFeatures() {
|
|||||||
error("'bulk-memory' feature must be used in order to emit passive "
|
error("'bulk-memory' feature must be used in order to emit passive "
|
||||||
"segments");
|
"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
|
// Validate that used features are allowed in output
|
||||||
if (!inferFeatures) {
|
if (!inferFeatures) {
|
||||||
for (auto &feature : used.keys()) {
|
for (auto &feature : used.keys()) {
|
||||||
@ -492,8 +511,8 @@ void Writer::calculateExports() {
|
|||||||
// implement in all major browsers.
|
// implement in all major browsers.
|
||||||
// See: https://github.com/WebAssembly/mutable-global
|
// See: https://github.com/WebAssembly/mutable-global
|
||||||
if (g->getGlobalType()->Mutable) {
|
if (g->getGlobalType()->Mutable) {
|
||||||
// Only the __stack_pointer should ever be create as mutable.
|
// Only __stack_pointer and __tls_base should ever be create as mutable.
|
||||||
assert(g == WasmSym::stackPointer);
|
assert(g == WasmSym::stackPointer || g == WasmSym::tlsBase);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
export_ = {name, WASM_EXTERNAL_GLOBAL, g->getGlobalIndex()};
|
export_ = {name, WASM_EXTERNAL_GLOBAL, g->getGlobalIndex()};
|
||||||
@ -602,6 +621,11 @@ static StringRef getOutputDataSegmentName(StringRef name) {
|
|||||||
// we only have a single __memory_base to use as our base address.
|
// we only have a single __memory_base to use as our base address.
|
||||||
if (config->isPic)
|
if (config->isPic)
|
||||||
return ".data";
|
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)
|
if (!config->mergeDataSegments)
|
||||||
return name;
|
return name;
|
||||||
if (name.startswith(".text."))
|
if (name.startswith(".text."))
|
||||||
@ -625,7 +649,7 @@ void Writer::createOutputSegments() {
|
|||||||
if (s == nullptr) {
|
if (s == nullptr) {
|
||||||
LLVM_DEBUG(dbgs() << "new segment: " << name << "\n");
|
LLVM_DEBUG(dbgs() << "new segment: " << name << "\n");
|
||||||
s = make<OutputSegment>(name, segments.size());
|
s = make<OutputSegment>(name, segments.size());
|
||||||
if (config->passiveSegments)
|
if (config->passiveSegments || name == ".tdata")
|
||||||
s->initFlags = WASM_SEGMENT_IS_PASSIVE;
|
s->initFlags = WASM_SEGMENT_IS_PASSIVE;
|
||||||
segments.push_back(s);
|
segments.push_back(s);
|
||||||
}
|
}
|
||||||
@ -655,7 +679,7 @@ void Writer::createInitMemoryFunction() {
|
|||||||
|
|
||||||
// initialize passive data segments
|
// initialize passive data segments
|
||||||
for (const OutputSegment *s : 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
|
// destination address
|
||||||
writeU8(os, WASM_OPCODE_I32_CONST, "i32.const");
|
writeU8(os, WASM_OPCODE_I32_CONST, "i32.const");
|
||||||
writeSleb128(os, s->startVA, "destination address");
|
writeSleb128(os, s->startVA, "destination address");
|
||||||
@ -737,6 +761,49 @@ void Writer::createCallCtorsFunction() {
|
|||||||
createFunction(WasmSym::callCtors, bodyContent);
|
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.
|
// Populate InitFunctions vector with init functions from all input objects.
|
||||||
// This is then used either when creating the output linking section or to
|
// This is then used either when creating the output linking section or to
|
||||||
// synthesize the "__wasm_call_ctors" function.
|
// synthesize the "__wasm_call_ctors" function.
|
||||||
@ -829,6 +896,12 @@ void Writer::run() {
|
|||||||
createCallCtorsFunction();
|
createCallCtorsFunction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config->sharedMemory && !config->shared)
|
||||||
|
createInitTLSFunction();
|
||||||
|
|
||||||
|
if (errorCount())
|
||||||
|
return;
|
||||||
|
|
||||||
log("-- calculateTypes");
|
log("-- calculateTypes");
|
||||||
calculateTypes();
|
calculateTypes();
|
||||||
log("-- calculateExports");
|
log("-- calculateExports");
|
||||||
|
@ -242,7 +242,9 @@ enum : unsigned {
|
|||||||
enum : unsigned {
|
enum : unsigned {
|
||||||
WASM_OPCODE_END = 0x0b,
|
WASM_OPCODE_END = 0x0b,
|
||||||
WASM_OPCODE_CALL = 0x10,
|
WASM_OPCODE_CALL = 0x10,
|
||||||
|
WASM_OPCODE_LOCAL_GET = 0x20,
|
||||||
WASM_OPCODE_GLOBAL_GET = 0x23,
|
WASM_OPCODE_GLOBAL_GET = 0x23,
|
||||||
|
WASM_OPCODE_GLOBAL_SET = 0x24,
|
||||||
WASM_OPCODE_I32_STORE = 0x36,
|
WASM_OPCODE_I32_STORE = 0x36,
|
||||||
WASM_OPCODE_I32_CONST = 0x41,
|
WASM_OPCODE_I32_CONST = 0x41,
|
||||||
WASM_OPCODE_I64_CONST = 0x42,
|
WASM_OPCODE_I64_CONST = 0x42,
|
||||||
|
@ -124,4 +124,13 @@ def int_wasm_data_drop :
|
|||||||
[llvm_i32_ty],
|
[llvm_i32_ty],
|
||||||
[IntrNoDuplicate, IntrHasSideEffects, ImmArg<0>]>;
|
[IntrNoDuplicate, IntrHasSideEffects, ImmArg<0>]>;
|
||||||
|
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
// Thread-local storage intrinsics
|
||||||
|
//===----------------------------------------------------------------------===//
|
||||||
|
|
||||||
|
def int_wasm_tls_size :
|
||||||
|
Intrinsic<[llvm_anyint_ty],
|
||||||
|
[],
|
||||||
|
[IntrNoMem, IntrSpeculatable]>;
|
||||||
|
|
||||||
} // TargetPrefix = "wasm"
|
} // TargetPrefix = "wasm"
|
||||||
|
@ -66,7 +66,8 @@ public:
|
|||||||
bool isVirtualSection() const override;
|
bool isVirtualSection() const override;
|
||||||
|
|
||||||
bool isWasmData() const {
|
bool isWasmData() const {
|
||||||
return Kind.isGlobalWriteableData() || Kind.isReadOnly();
|
return Kind.isGlobalWriteableData() || Kind.isReadOnly() ||
|
||||||
|
Kind.isThreadLocal();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isUnique() const { return UniqueID != ~0U; }
|
bool isUnique() const { return UniqueID != ~0U; }
|
||||||
|
@ -233,6 +233,8 @@ bool WebAssemblyFastISel::computeAddress(const Value *Obj, Address &Addr) {
|
|||||||
return false;
|
return false;
|
||||||
if (Addr.getGlobalValue())
|
if (Addr.getGlobalValue())
|
||||||
return false;
|
return false;
|
||||||
|
if (GV->isThreadLocal())
|
||||||
|
return false;
|
||||||
Addr.setGlobalValue(GV);
|
Addr.setGlobalValue(GV);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -614,6 +616,8 @@ unsigned WebAssemblyFastISel::fastMaterializeConstant(const Constant *C) {
|
|||||||
if (const GlobalValue *GV = dyn_cast<GlobalValue>(C)) {
|
if (const GlobalValue *GV = dyn_cast<GlobalValue>(C)) {
|
||||||
if (TLI.isPositionIndependent())
|
if (TLI.isPositionIndependent())
|
||||||
return 0;
|
return 0;
|
||||||
|
if (GV->isThreadLocal())
|
||||||
|
return 0;
|
||||||
unsigned ResultReg =
|
unsigned ResultReg =
|
||||||
createResultReg(Subtarget->hasAddr64() ? &WebAssembly::I64RegClass
|
createResultReg(Subtarget->hasAddr64() ? &WebAssembly::I64RegClass
|
||||||
: &WebAssembly::I32RegClass);
|
: &WebAssembly::I32RegClass);
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
#include "WebAssembly.h"
|
#include "WebAssembly.h"
|
||||||
#include "WebAssemblyTargetMachine.h"
|
#include "WebAssemblyTargetMachine.h"
|
||||||
#include "llvm/CodeGen/SelectionDAGISel.h"
|
#include "llvm/CodeGen/SelectionDAGISel.h"
|
||||||
|
#include "llvm/IR/DiagnosticInfo.h"
|
||||||
#include "llvm/IR/Function.h" // To access function attributes.
|
#include "llvm/IR/Function.h" // To access function attributes.
|
||||||
#include "llvm/Support/Debug.h"
|
#include "llvm/Support/Debug.h"
|
||||||
#include "llvm/Support/KnownBits.h"
|
#include "llvm/Support/KnownBits.h"
|
||||||
@ -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:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -77,9 +77,11 @@ MCSymbol *WebAssemblyMCInstLower::GetExternalSymbolSymbol(
|
|||||||
// functions. It's OK to hardcode knowledge of specific symbols here; this
|
// functions. It's OK to hardcode knowledge of specific symbols here; this
|
||||||
// method is precisely there for fetching the signatures of known
|
// method is precisely there for fetching the signatures of known
|
||||||
// Clang-provided symbols.
|
// Clang-provided symbols.
|
||||||
if (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, "__memory_base") == 0 || strcmp(Name, "__table_base") == 0 ||
|
||||||
bool Mutable = strcmp(Name, "__stack_pointer") == 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->setType(wasm::WASM_SYMBOL_TYPE_GLOBAL);
|
||||||
WasmSym->setGlobalType(wasm::WasmGlobalType{
|
WasmSym->setGlobalType(wasm::WasmGlobalType{
|
||||||
uint8_t(Subtarget.hasAddr64() ? wasm::WASM_TYPE_I64
|
uint8_t(Subtarget.hasAddr64() ? wasm::WASM_TYPE_I64
|
||||||
|
@ -186,13 +186,21 @@ public:
|
|||||||
for (auto &F : M)
|
for (auto &F : M)
|
||||||
replaceFeatures(F, FeatureStr);
|
replaceFeatures(F, FeatureStr);
|
||||||
|
|
||||||
bool Stripped = false;
|
bool StrippedAtomics = false;
|
||||||
if (!Features[WebAssembly::FeatureAtomics]) {
|
bool StrippedTLS = false;
|
||||||
Stripped |= stripAtomics(M);
|
|
||||||
Stripped |= stripThreadLocals(M);
|
|
||||||
}
|
|
||||||
|
|
||||||
recordFeatures(M, Features, Stripped);
|
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, StrippedAtomics || StrippedTLS);
|
||||||
|
|
||||||
// Conservatively assume we have made some change
|
// Conservatively assume we have made some change
|
||||||
return true;
|
return true;
|
||||||
@ -271,7 +279,8 @@ private:
|
|||||||
// "atomics" is special: code compiled without atomics may have had its
|
// "atomics" is special: code compiled without atomics may have had its
|
||||||
// atomics lowered to nonatomic operations. In that case, atomics is
|
// atomics lowered to nonatomic operations. In that case, atomics is
|
||||||
// disallowed to prevent unsafe linking with atomics-enabled objects.
|
// 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,
|
M.addModuleFlag(Module::ModFlagBehavior::Error, MDKey,
|
||||||
wasm::WASM_FEATURE_PREFIX_DISALLOWED);
|
wasm::WASM_FEATURE_PREFIX_DISALLOWED);
|
||||||
} else if (Features[KV.Value]) {
|
} else if (Features[KV.Value]) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
; RUN: llc < %s -mattr=-atomics | FileCheck %s --check-prefixes CHECK,NO-ATOMICS
|
; RUN: llc < %s -mattr=-bulk-memory | FileCheck %s --check-prefixes NO-BULK-MEM
|
||||||
; RUN: llc < %s -mattr=+atomics | FileCheck %s --check-prefixes CHECK,ATOMICS
|
; RUN: llc < %s -mattr=+bulk-memory | FileCheck %s --check-prefixes BULK-MEM
|
||||||
|
|
||||||
; Test that the target features section contains -atomics or +atomics
|
; Test that the target features section contains -atomics or +atomics
|
||||||
; for modules that have thread local storage in their source.
|
; for modules that have thread local storage in their source.
|
||||||
@ -9,18 +9,18 @@ target triple = "wasm32-unknown-unknown"
|
|||||||
|
|
||||||
@foo = internal thread_local global i32 0
|
@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
|
; +bulk-memory
|
||||||
; NO-ATOMICS-NEXT: .int8 1
|
; BULK-MEM-LABEL: .custom_section.target_features,"",@
|
||||||
; NO-ATOMICS-NEXT: .int8 45
|
; BULK-MEM-NEXT: .int8 1
|
||||||
; NO-ATOMICS-NEXT: .int8 7
|
; BULK-MEM-NEXT: .int8 43
|
||||||
; NO-ATOMICS-NEXT: .ascii "atomics"
|
; BULK-MEM-NEXT: .int8 11
|
||||||
; NO-ATOMICS-NEXT: .bss.foo,"",@
|
; BULK-MEM-NEXT: .ascii "bulk-memory"
|
||||||
|
; BULK-MEM-NEXT: .tbss.foo,"",@
|
||||||
; +atomics
|
|
||||||
; ATOMICS-NEXT: .int8 1
|
|
||||||
; ATOMICS-NEXT: .int8 43
|
|
||||||
; ATOMICS-NEXT: .int8 7
|
|
||||||
; ATOMICS-NEXT: .ascii "atomics"
|
|
||||||
; ATOMICS-NEXT: .tbss.foo,"",@
|
|
||||||
|
@ -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 datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
|
||||||
target triple = "wasm32-unknown-unknown"
|
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() {
|
define i32 @address_of_tls() {
|
||||||
; SINGLE: i32.const $push0=, tls
|
; TLS-DAG: global.get __tls_base
|
||||||
; SINGLE-NEXT: return $pop0
|
; 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)
|
ret i32 ptrtoint(i32* @tls to i32)
|
||||||
}
|
}
|
||||||
|
|
||||||
; SINGLE: .type tls,@object
|
; CHECK-LABEL: ptr_to_tls:
|
||||||
; SINGLE-NEXT: .section .bss.tls,"",@
|
; CHECK-NEXT: .functype ptr_to_tls () -> (i32)
|
||||||
; SINGLE-NEXT: .p2align 2
|
define i32* @ptr_to_tls() {
|
||||||
; SINGLE-NEXT: tls:
|
; TLS-DAG: global.get __tls_base
|
||||||
; SINGLE-NEXT: .int32 0
|
; TLS-DAG: i32.const tls
|
||||||
@tls = internal thread_local global i32 0
|
; 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()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user