[lld][WebAssembly] Allow references to __tls_base without shared memory

Previously we limited the use of atomics and TLS to programs
linked with `--shared-memory`.

However, as of https://reviews.llvm.org/D79530 we now allow
programs that use atomic to be linked without `--shared-memory`.
For this to be useful we also want to all TLS usage in such
programs.  In this case, since we know we are single threaded
we simply include the TLS data as a regular active segment
and create an immutable `__tls_base` global that point to the
start of this segment.

Fixes: https://github.com/emscripten-core/emscripten/issues/12489

Differential Revision: https://reviews.llvm.org/D91115
This commit is contained in:
Sam Clegg 2020-11-09 17:52:39 -08:00
parent e1eeb026e6
commit 29a3056bb5
6 changed files with 145 additions and 22 deletions

View File

@ -52,4 +52,4 @@ _start:
# CHECK-NEXT: Mutable: false
# CHECK-NEXT: InitExpr:
# CHECK-NEXT: Opcode: I32_CONST
# CHECK-NEXT: Value: 1
# CHECK-NEXT: Value: 0

View File

@ -0,0 +1,75 @@
# Test that linking without shared memory causes __tls_base to be
# interlized
# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
.globaltype __tls_base, i32
.globl get_tls1
get_tls1:
.functype get_tls1 () -> (i32)
global.get __tls_base
i32.const tls1
i32.add
end_function
.section .data.no_tls,"",@
.globl no_tls
.p2align 2
no_tls:
.int32 42
.size no_tls, 4
.section .tdata.tls1,"",@
.globl tls1
.p2align 2
tls1:
.int32 43
.size tls1, 2
.section .custom_section.target_features,"",@
.int8 2
.int8 43
.int8 7
.ascii "atomics"
.int8 43
.int8 11
.ascii "bulk-memory"
# RUN: wasm-ld --no-gc-sections --no-entry -o %t.wasm %t.o
# RUN: obj2yaml %t.wasm | FileCheck %s
# CHECK: - Type: GLOBAL
# __stack_pointer
# 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
# __tls_base
# CHECK-NEXT: - Index: 1
# CHECK-NEXT: Type: I32
# CHECK-NEXT: Mutable: false
# CHECK-NEXT: InitExpr:
# CHECK-NEXT: Opcode: I32_CONST
# CHECK-NEXT: Value: 1028
# CHECK-NEXT: - Type: EXPORT
# CHECK: - Type: DATA
# .data
# CHECK-NEXT: Segments:
# CHECK-NEXT: - SectionOffset: 7
# CHECK-NEXT: InitFlags: 0
# CHECK-NEXT: Offset:
# CHECK-NEXT: Opcode: I32_CONST
# CHECK-NEXT: Value: 1024
# CHECK-NEXT: Content: 2A000000
# .tdata
# CHECK-NEXT: - SectionOffset: 17
# CHECK-NEXT: InitFlags: 0
# CHECK-NEXT: Offset:
# CHECK-NEXT: Opcode: I32_CONST
# CHECK-NEXT: Value: 1028
# CHECK-NEXT: Content: 2B000000

View File

@ -541,21 +541,30 @@ createUndefinedGlobal(StringRef name, llvm::wasm::WasmGlobalType *type) {
return sym;
}
static GlobalSymbol *createGlobalVariable(StringRef name, bool isMutable,
int value) {
static InputGlobal *createGlobal(StringRef name, bool isMutable) {
llvm::wasm::WasmGlobal wasmGlobal;
if (config->is64.getValueOr(false)) {
wasmGlobal.Type = {WASM_TYPE_I64, isMutable};
wasmGlobal.InitExpr.Value.Int64 = value;
wasmGlobal.InitExpr.Opcode = WASM_OPCODE_I64_CONST;
wasmGlobal.InitExpr.Value.Int64 = 0;
} else {
wasmGlobal.Type = {WASM_TYPE_I32, isMutable};
wasmGlobal.InitExpr.Value.Int32 = value;
wasmGlobal.InitExpr.Opcode = WASM_OPCODE_I32_CONST;
wasmGlobal.InitExpr.Value.Int32 = 0;
}
wasmGlobal.SymbolName = name;
return symtab->addSyntheticGlobal(name, WASM_SYMBOL_VISIBILITY_HIDDEN,
make<InputGlobal>(wasmGlobal, nullptr));
return make<InputGlobal>(wasmGlobal, nullptr);
}
static GlobalSymbol *createGlobalVariable(StringRef name, bool isMutable) {
InputGlobal *g = createGlobal(name, isMutable);
return symtab->addSyntheticGlobal(name, WASM_SYMBOL_VISIBILITY_HIDDEN, g);
}
static GlobalSymbol *createOptionalGlobal(StringRef name, bool isMutable) {
InputGlobal *g = createGlobal(name, isMutable);
return symtab->addOptionalGlobalSymbols(name, WASM_SYMBOL_VISIBILITY_HIDDEN,
g);
}
// Create ABI-defined synthetic symbols
@ -602,7 +611,7 @@ static void createSyntheticSymbols() {
WasmSym::tableBase->markLive();
} else {
// For non-PIC code
WasmSym::stackPointer = createGlobalVariable("__stack_pointer", true, 0);
WasmSym::stackPointer = createGlobalVariable("__stack_pointer", true);
WasmSym::stackPointer->markLive();
}
@ -616,9 +625,9 @@ static void createSyntheticSymbols() {
WasmSym::initMemoryFlag = symtab->addSyntheticDataSymbol(
"__wasm_init_memory_flag", WASM_SYMBOL_VISIBILITY_HIDDEN);
assert(WasmSym::initMemoryFlag);
WasmSym::tlsBase = createGlobalVariable("__tls_base", true, 0);
WasmSym::tlsSize = createGlobalVariable("__tls_size", false, 0);
WasmSym::tlsAlign = createGlobalVariable("__tls_align", false, 1);
WasmSym::tlsBase = createGlobalVariable("__tls_base", true);
WasmSym::tlsSize = createGlobalVariable("__tls_size", false);
WasmSym::tlsAlign = createGlobalVariable("__tls_align", false);
WasmSym::initTLS = symtab->addSyntheticFunction(
"__wasm_init_tls", WASM_SYMBOL_VISIBILITY_HIDDEN,
make<SyntheticFunction>(
@ -642,6 +651,18 @@ static void createOptionalSymbols() {
WasmSym::definedMemoryBase = symtab->addOptionalDataSymbol("__memory_base");
WasmSym::definedTableBase = symtab->addOptionalDataSymbol("__table_base");
}
// For non-shared memory programs we still need to define __tls_base since we
// allow object files built with TLS to be linked into single threaded
// programs, and such object files can contains refernced to this symbol.
//
// However, in this case __tls_base is immutable and points directly to the
// start of the `.tdata` static segment.
//
// __tls_size and __tls_align are not needed in this case since they are only
// needed for __wasm_init_tls (which we do not create in this case).
if (!config->sharedMemory)
WasmSym::tlsBase = createOptionalGlobal("__tls_base", false);
}
// Reconstructs command line arguments so that so that you can re-run

View File

@ -206,7 +206,7 @@ DefinedFunction *SymbolTable::addSyntheticFunction(StringRef name,
flags, nullptr, function);
}
// Adds an optional, linker generated, data symbols. The symbol will only be
// Adds an optional, linker generated, data symbol. The symbol will only be
// added if there is an undefine reference to it, or if it is explicitly
// exported via the --export flag. Otherwise we don't add the symbol and return
// nullptr.
@ -241,6 +241,18 @@ DefinedGlobal *SymbolTable::addSyntheticGlobal(StringRef name, uint32_t flags,
nullptr, global);
}
DefinedGlobal *SymbolTable::addOptionalGlobalSymbols(StringRef name,
uint32_t flags,
InputGlobal *global) {
LLVM_DEBUG(dbgs() << "addOptionalGlobalSymbols: " << name << " -> " << global
<< "\n");
Symbol *s = find(name);
if (!s || s->isDefined())
return nullptr;
syntheticGlobals.emplace_back(global);
return replaceSymbol<DefinedGlobal>(s, name, flags, nullptr, global);
}
static bool shouldReplace(const Symbol *existing, InputFile *newFile,
uint32_t newFlags) {
// If existing symbol is undefined, replace it.

View File

@ -83,6 +83,8 @@ public:
DefinedFunction *addSyntheticFunction(StringRef name, uint32_t flags,
InputFunction *function);
DefinedData *addOptionalDataSymbol(StringRef name, uint64_t value = 0);
DefinedGlobal *addOptionalGlobalSymbols(StringRef name, uint32_t flags,
InputGlobal *global);
void handleSymbolVariants();
void handleWeakUndefines();

View File

@ -204,6 +204,16 @@ void Writer::writeSections() {
});
}
static void setGlobalPtr(DefinedGlobal *g, uint64_t memoryPtr) {
if (config->is64.getValueOr(false)) {
assert(g->global->global.InitExpr.Opcode == WASM_OPCODE_I64_CONST);
g->global->global.InitExpr.Value.Int64 = memoryPtr;
} else {
assert(g->global->global.InitExpr.Opcode == WASM_OPCODE_I32_CONST);
g->global->global.InitExpr.Value.Int32 = memoryPtr;
}
}
// Fix the memory layout of the output binary. This assigns memory offsets
// to each of the input data sections as well as the explicit stack region.
// The default memory layout is as follows, from low to high.
@ -267,18 +277,21 @@ void Writer::layoutMemory() {
seg->startVA = memoryPtr;
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);
assert(tlsSize->global->global.InitExpr.Opcode == WASM_OPCODE_I32_CONST);
tlsSize->global->global.InitExpr.Value.Int32 = seg->size;
if (seg->name == ".tdata") {
if (config->sharedMemory) {
auto *tlsSize = cast<DefinedGlobal>(WasmSym::tlsSize);
setGlobalPtr(tlsSize, seg->size);
auto *tlsAlign = cast<DefinedGlobal>(WasmSym::tlsAlign);
assert(tlsAlign->global->global.InitExpr.Opcode == WASM_OPCODE_I32_CONST);
tlsAlign->global->global.InitExpr.Value.Int32 = int64_t{1}
<< seg->alignment;
auto *tlsAlign = cast<DefinedGlobal>(WasmSym::tlsAlign);
setGlobalPtr(tlsAlign, int64_t{1} << seg->alignment);
} else {
auto *tlsBase = cast<DefinedGlobal>(WasmSym::tlsBase);
setGlobalPtr(tlsBase, memoryPtr);
}
}
memoryPtr += seg->size;
}
// Make space for the memory initialization flag
@ -768,7 +781,7 @@ void Writer::createOutputSegments() {
if (s == nullptr) {
LLVM_DEBUG(dbgs() << "new segment: " << name << "\n");
s = make<OutputSegment>(name);
if (config->sharedMemory || name == ".tdata")
if (config->sharedMemory)
s->initFlags = WASM_SEGMENT_IS_PASSIVE;
// Exported memories are guaranteed to be zero-initialized, so no need
// to emit data segments for bss sections.