[WebAssembly] Cooperative threading for WASIP3 (#200855)

This PR builds on the changes to allow libcall thread context from
https://github.com/llvm/llvm-project/pull/175800/changes and adds the
necessary changes to support cooperative multithreading in the WASIP3
target:

- Not marking memory as shared 
- Allowing thread local accesses without atomics
- Only using passive segments for TLS segments

The linker changes are supported by a new flag called
`--cooperative-multithreading`. We talked about having two flags, one
for the `--libcall-thread-context` part and one for the cooperative
multithreading part. For now, I've simply replaced the
`--libcall-thread-context` flag with the `--cooperative-multithreading`
one and kept the internal configuration intact for simplicity.
This commit is contained in:
Sy Brand authored and GitHub committed 2026-06-23 13:19:55 -07:00
1 parent e584691adc
commit b74154ccda
18 files changed
+231 -64

No files matched your search

+1 -1
View File
@@ -424,7 +424,7 @@ void WebAssemblyTargetInfo::adjust(DiagnosticsEngine &Diags, LangOptions &Opts,
// Turn off POSIXThreads and ThreadModel so that we don't predefine _REENTRANT
// or __STDCPP_THREADS__ if we will eventually end up stripping atomics
// because they are unsupported.
if (!HasAtomics || !HasBulkMemory) {
if ((!HasCooperativeThreading && !HasAtomics) || !HasBulkMemory) {
Opts.POSIXThreads = false;
Opts.setThreadModel(LangOptions::ThreadModelKind::Single);
Opts.ThreadsafeStatics = false;
+4 -1
View File
@@ -63,6 +63,7 @@ class LLVM_LIBRARY_VISIBILITY WebAssemblyTargetInfo : public TargetInfo {
bool HasBulkMemory = false;
bool HasBulkMemoryOpt = false;
bool HasCallIndirectOverlong = false;
bool HasCooperativeThreading = false;
bool HasCompactImports = false;
bool HasExceptionHandling = false;
bool HasExtendedConst = false;
@@ -111,8 +112,10 @@ public:
PtrDiffType = SignedLong;
IntPtrType = SignedLong;
}
if (T.getOS() == llvm::Triple::WASIp3)
if (T.getOS() == llvm::Triple::WASIp3) {
HasLibcallThreadContext = true;
HasCooperativeThreading = true;
}
}
StringRef getABI() const override;
+23 -9
View File
@@ -85,14 +85,23 @@ static bool WantsPthread(const llvm::Triple &Triple, const ArgList &Args) {
if (Triple.isOSWASI() && Triple.getEnvironmentName() == "threads")
WantsPthread = true;
// WASIp3 also implies pthreads support
if (Triple.getOS() == llvm::Triple::WASIp3)
WantsPthread = true;
return WantsPthread;
}
static bool WantsLibcallThreadContext(const llvm::Triple &Triple,
const ArgList &Args) {
static bool WantsCooperativeMultithreading(const llvm::Triple &Triple,
const ArgList &Args) {
return Triple.getOS() == llvm::Triple::WASIp3;
}
static bool WantsSharedMemory(const llvm::Triple &Triple, const ArgList &Args) {
return WantsPthread(Triple, Args) &&
!WantsCooperativeMultithreading(Triple, Args);
}
void wasm::Linker::ConstructJob(Compilation &C, const JobAction &JA,
const InputInfo &Output,
const InputInfoList &Inputs,
@@ -174,10 +183,10 @@ void wasm::Linker::ConstructJob(Compilation &C, const JobAction &JA,
AddLinkerInputs(ToolChain, Inputs, Args, CmdArgs, JA);
if (WantsLibcallThreadContext(ToolChain.getTriple(), Args))
CmdArgs.push_back("--libcall-thread-context");
if (WantsCooperativeMultithreading(ToolChain.getTriple(), Args))
CmdArgs.push_back("--cooperative-threading");
if (WantsPthread(ToolChain.getTriple(), Args))
if (WantsSharedMemory(ToolChain.getTriple(), Args))
CmdArgs.push_back("--shared-memory");
if (!Args.hasArg(options::OPT_nostdlib, options::OPT_nodefaultlibs)) {
@@ -331,9 +340,12 @@ void WebAssembly::addClangTargetOptions(const ArgList &DriverArgs,
options::OPT_fno_use_init_array, true))
CC1Args.push_back("-fno-use-init-array");
// '-pthread' implies atomics, bulk-memory, mutable-globals, and sign-ext
// '-pthread' implies bulk-memory, mutable-globals, and sign-ext.
// It also implies atomics, so long as we're not targeting a cooperative
// threading environment.
if (WantsPthread(getTriple(), DriverArgs)) {
if (DriverArgs.hasFlag(options::OPT_mno_atomics, options::OPT_matomics,
if (!WantsCooperativeMultithreading(getTriple(), DriverArgs) &&
DriverArgs.hasFlag(options::OPT_mno_atomics, options::OPT_matomics,
false))
getDriver().Diag(diag::err_drv_argument_not_allowed_with)
<< "-pthread"
@@ -353,8 +365,10 @@ void WebAssembly::addClangTargetOptions(const ArgList &DriverArgs,
getDriver().Diag(diag::err_drv_argument_not_allowed_with)
<< "-pthread"
<< "-mno-sign-ext";
CC1Args.push_back("-target-feature");
CC1Args.push_back("+atomics");
if (!WantsCooperativeMultithreading(getTriple(), DriverArgs)) {
CC1Args.push_back("-target-feature");
CC1Args.push_back("+atomics");
}
CC1Args.push_back("-target-feature");
CC1Args.push_back("+bulk-memory");
CC1Args.push_back("-target-feature");
+6
View File
@@ -303,3 +303,9 @@
// RUN: | FileCheck -check-prefix=LINK_WALI_BASIC %s
// LINK_WALI_BASIC: "-cc1" {{.*}} "-o" "[[temp:[^"]*]]"
// LINK_WALI_BASIC: wasm-ld{{.*}}" "-L/foo/lib/wasm32-linux-muslwali" "crt1.o" "[[temp]]" "-lc" "{{.*[/\\]}}libclang_rt.builtins.a" "-o" "a.out"
// Test that `wasm32-wasip3` passes `--cooperative-threading` to the linker.
// RUN: %clang -### --target=wasm32-wasip3 -fuse-ld=lld %s --sysroot /foo 2>&1 \
// RUN: | FileCheck -check-prefix=LINK_WASIP3_COOP %s
// LINK_WASIP3_COOP: wasm-ld{{.*}}" {{.*}} "--cooperative-threading"
+85
View File
@@ -0,0 +1,85 @@
# Test that --cooperative-threading uses the libcall ABI naming for
# thread-context globals (__init_stack_pointer, __init_tls_base, etc.) and
# works without --shared-memory and atomics.
# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
# RUN: wasm-ld --cooperative-threading -no-gc-sections -o %t.wasm %t.o
# RUN: obj2yaml %t.wasm | FileCheck %s
# RUN: llvm-objdump -d --no-print-imm-hex --no-show-raw-insn %t.wasm | FileCheck %s --check-prefix=DIS
# Test that --cooperative-threading and --shared-memory are mutually exclusive.
# RUN: not wasm-ld --cooperative-threading --shared-memory %t.o -o %t2.wasm 2>&1 | FileCheck %s --check-prefix=INCOMPAT
# INCOMPAT: --cooperative-threading is incompatible with --shared-memory
.globl __wasm_get_tls_base
__wasm_get_tls_base:
.functype __wasm_get_tls_base () -> (i32)
i32.const 0
end_function
.globl _start
_start:
.functype _start () -> (i32)
call __wasm_get_tls_base
i32.const tls1@TLSREL
i32.add
i32.load 0
call __wasm_get_tls_base
i32.const tls2@TLSREL
i32.add
i32.load 0
i32.add
end_function
.section .tdata.tls1,"",@
.globl tls1
tls1:
.int32 1
.size tls1, 4
.section .tdata.tls2,"",@
.globl tls2
tls2:
.int32 2
.size tls2, 4
.section .custom_section.target_features,"",@
.int8 2
.int8 43
.int8 11
.ascii "bulk-memory"
.int8 43
.int8 7
.ascii "atomics"
# Memory must NOT be marked as shared.
# CHECK: - Type: MEMORY
# CHECK-NEXT: Memories:
# CHECK-NEXT: - Minimum: 0x2
# CHECK-NOT: Shared
# Globals should use the libcall ABI naming, not the global ABI.
# CHECK: GlobalNames:
# CHECK-NEXT: - Index: 0
# CHECK-NEXT: Name: __init_stack_pointer
# CHECK-NEXT: - Index: 1
# CHECK-NEXT: Name: __init_tls_base
# CHECK-NEXT: - Index: 2
# CHECK-NEXT: Name: __tls_size
# CHECK-NEXT: - Index: 3
# CHECK-NEXT: Name: __tls_align
# DIS-LABEL: <__wasm_init_memory>:
# DIS-LABEL: <_start>:
# DIS-EMPTY:
# DIS-NEXT: call {{[0-9]+}}
# DIS-NEXT: i32.const 0
# DIS-NEXT: i32.add
# DIS-NEXT: i32.load 0
# DIS-NEXT: call {{[0-9]+}}
# DIS-NEXT: i32.const 4
# DIS-NEXT: i32.add
# DIS-NEXT: i32.load 0
# DIS-NEXT: i32.add
# DIS-NEXT: end
+1 -1
View File
@@ -1,5 +1,5 @@
# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
# RUN: wasm-ld --libcall-thread-context --no-gc-sections -o %t.libcall.wasm %t.o
# RUN: wasm-ld --cooperative-threading --no-gc-sections -o %t.libcall.wasm %t.o
# RUN: obj2yaml %t.libcall.wasm | FileCheck %s --check-prefix=LIBCALL
# RUN: wasm-ld --no-gc-sections -o %t.global.wasm %t.o
# RUN: obj2yaml %t.global.wasm | FileCheck %s --check-prefix=GLOBAL
+2 -3
View File
@@ -3,10 +3,9 @@
# as an indication that the global thread context ABI is being used.
# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
# RUN: not wasm-ld --libcall-thread-context %t.o -o %t.wasm 2>&1 | FileCheck %s
# CHECK: object file uses globals for thread context, but --libcall-thread-context was specified
# RUN: not wasm-ld --cooperative-threading %t.o -o %t.wasm 2>&1 | FileCheck %s
# CHECK: object file uses globals for thread context, but --cooperative-threading was specified
.globl _start
_start:
.functype _start () -> ()
+1 -1
View File
@@ -1,5 +1,5 @@
# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
# RUN: wasm-ld --libcall-thread-context --shared-memory -no-gc-sections -o %t.wasm %t.o
# RUN: wasm-ld --cooperative-threading -no-gc-sections -o %t.wasm %t.o
# RUN: obj2yaml %t.wasm | FileCheck %s
# RUN: llvm-objdump -d --no-print-imm-hex --no-show-raw-insn %t.wasm | FileCheck %s --check-prefix=DIS
+3
View File
@@ -65,6 +65,7 @@ struct Config {
bool growableTable;
bool gcSections;
llvm::StringSet<> keepSections;
bool cooperativeThreading;
bool libcallThreadContext;
std::optional<std::pair<llvm::StringRef, llvm::StringRef>> memoryImport;
std::optional<llvm::StringRef> memoryExport;
@@ -134,6 +135,8 @@ struct Config {
std::optional<std::vector<std::string>> features;
std::optional<std::vector<std::string>> extraFeatures;
llvm::SmallVector<uint8_t, 0> buildIdVector;
bool isMultithreaded() const { return sharedMemory || cooperativeThreading; }
};
// The Ctx object hold all other (non-configuration) global state.
+15 -12
View File
@@ -561,7 +561,7 @@ static void readConfigs(opt::InputArgList &args) {
ctx.arg.soName = args.getLastArgValue(OPT_soname);
ctx.arg.importTable = args.hasArg(OPT_import_table);
ctx.arg.importUndefined = args.hasArg(OPT_import_undefined);
ctx.arg.libcallThreadContext = args.hasArg(OPT_libcall_thread_context);
ctx.arg.cooperativeThreading = args.hasArg(OPT_cooperative_threading);
ctx.arg.ltoo = args::getInteger(args, OPT_lto_O, 2);
if (ctx.arg.ltoo > 3)
error("invalid optimization level for LTO: " + Twine(ctx.arg.ltoo));
@@ -755,6 +755,11 @@ static void setConfigs() {
if (!ctx.arg.memoryExport.has_value() && !ctx.arg.memoryImport.has_value()) {
ctx.arg.memoryExport = memoryName;
}
if (ctx.arg.cooperativeThreading) {
if (ctx.arg.sharedMemory)
error("--cooperative-threading is incompatible with --shared-memory");
ctx.arg.libcallThreadContext = true;
}
}
// Some command line options or some combinations of them are not allowed.
@@ -964,7 +969,7 @@ static void createSyntheticSymbols() {
createGlobalVariable(stack_pointer_name, !ctx.arg.libcallThreadContext);
}
if (ctx.arg.sharedMemory) {
if (ctx.arg.isMultithreaded()) {
// TLS symbols are all hidden/dso-local
auto tls_base_name =
ctx.arg.libcallThreadContext ? "__init_tls_base" : "__tls_base";
@@ -986,9 +991,11 @@ static void createSyntheticSymbols() {
static WasmSignature setTLSBaseSignature{{}, {ValType::I32}};
ctx.sym.setTLSBase =
createUndefinedFunction("__wasm_set_tls_base", &setTLSBaseSignature);
ctx.sym.setTLSBase->markLive();
static WasmSignature getTLSBaseSignature{{ValType::I32}, {}};
ctx.sym.getTLSBase =
createUndefinedFunction("__wasm_get_tls_base", &getTLSBaseSignature);
ctx.sym.getTLSBase->markLive();
}
}
}
@@ -1019,16 +1026,12 @@ static void createOptionalSymbols() {
if (ctx.sym.firstPageEnd)
ctx.sym.firstPageEnd->setVA(ctx.arg.pageSize);
// 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 contain references 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 (!ctx.arg.sharedMemory)
// TLS object files may be linked into single-threaded programs, so
// __tls_base must always be defined. In this case it is immutable and points
// directly to the start of the `.tdata` segment. __tls_size and __tls_align
// are omitted since they are only used by __wasm_init_tls, which is not
// created in this case.
if (!ctx.sym.tlsBase)
ctx.sym.tlsBase = createOptionalGlobal("__tls_base", false);
}
+2 -2
View File
@@ -238,8 +238,8 @@ def page_size: JJ<"page-size=">,
def initial_memory: JJ<"initial-memory=">,
HelpText<"Initial size of the linear memory">;
def libcall_thread_context: FF<"libcall-thread-context">,
HelpText<"Use library calls for thread context access instead of globals.">;
def cooperative_threading: FF<"cooperative-threading">,
HelpText<"Enable cooperative multithreading.">;
def max_memory: JJ<"max-memory=">,
HelpText<"Maximum size of the linear memory">;
+1 -1
View File
@@ -125,7 +125,7 @@ void scanRelocations(InputChunk *chunk) {
// In single-threaded builds TLS is lowered away and TLS data can be
// merged with normal data and allowing TLS relocation in non-TLS
// segments.
if (ctx.arg.sharedMemory) {
if (ctx.arg.isMultithreaded()) {
if (!sym->isTLS()) {
error(toString(file) + ": relocation " +
relocTypeToString(reloc.Type) +
+7 -6
View File
@@ -57,7 +57,7 @@ void writeGetTLSBase(const Ctx &ctx, raw_ostream &os) {
writeU8(os, WASM_OPCODE_CALL, "call");
writeUleb128(os, ctx.sym.getTLSBase->getFunctionIndex(), "function index");
} else {
writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_SET");
writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
writeUleb128(os, ctx.sym.tlsBase->getGlobalIndex(), "__tls_base");
}
}
@@ -532,7 +532,7 @@ void GlobalSection::writeBody() {
mutable_ = true;
// With multi-threading any TLS globals must be mutable since they get
// set during `__wasm_apply_global_tls_relocs`
if (ctx.arg.sharedMemory && sym->isTLS())
if (ctx.arg.isMultithreaded() && sym->isTLS())
mutable_ = true;
}
WasmGlobalType type{itype, mutable_};
@@ -569,10 +569,11 @@ void GlobalSection::writeBody() {
} else {
WasmInitExpr initExpr;
if (auto *d = dyn_cast<DefinedData>(sym))
// In the sharedMemory case TLS globals are set during
// `__wasm_apply_global_tls_relocs`, but in the non-shared case
// In the multithreaded case, TLS globals are set during
// `__wasm_apply_global_tls_relocs`, but in the single-threaded case
// we know the absolute value at link time.
initExpr = intConst(d->getVA(/*absolute=*/!ctx.arg.sharedMemory), is64);
initExpr =
intConst(d->getVA(/*absolute=*/!ctx.arg.isMultithreaded()), is64);
else if (auto *f = dyn_cast<FunctionSymbol>(sym))
initExpr = intConst(f->isStub ? 0 : f->getTableIndex(), is64);
else {
@@ -680,7 +681,7 @@ bool DataCountSection::isNeeded() const {
// instructions are not yet supported in input files. However, in the case
// of shared memory, lld itself will generate these instructions as part of
// `__wasm_init_memory`. See Writer::createInitMemoryFunction.
return numSegments && ctx.arg.sharedMemory;
return numSegments && ctx.arg.isMultithreaded();
}
void LinkingSection::writeBody() {
+36 -22
View File
@@ -423,7 +423,7 @@ void Writer::layoutMemory() {
// Even in the absense of any actual TLS data, this symbol can still be
// referenced (for example by __builtin_thread_pointer, which should not
// return NULL).
if (!ctx.arg.sharedMemory && ctx.sym.tlsBase) {
if (!ctx.arg.isMultithreaded() && ctx.sym.tlsBase) {
setGlobalPtr(ctx.sym.tlsBase, fixedTLSBase);
}
@@ -650,7 +650,7 @@ void Writer::populateTargetFeatures() {
sym->importModule && sym->importModule == "env";
}))
error(fileName + ": object file uses globals for thread context, "
"but --libcall-thread-context was specified");
"but --cooperative-threading was specified");
}
if (inferFeatures)
@@ -673,10 +673,12 @@ void Writer::populateTargetFeatures() {
}
if (tlsUsed) {
for (auto feature : {"atomics", "bulk-memory"})
if (!allowed.contains(feature))
error(StringRef("'") + feature +
"' feature must be used in order to use thread-local storage");
if (!allowed.contains("bulk-memory"))
error("'bulk-memory' feature must be used in order to use thread-local "
"storage");
if (!allowed.contains("atomics") && !ctx.arg.cooperativeThreading)
error("'atomics' feature must be used in order to use thread-local "
"storage");
}
// Validate that used features are allowed in output
@@ -1054,7 +1056,17 @@ static StringRef getOutputDataSegmentName(const InputChunk &seg) {
OutputSegment *Writer::createOutputSegment(StringRef name) {
LLVM_DEBUG(dbgs() << "new segment: " << name << "\n");
OutputSegment *s = make<OutputSegment>(name);
if (ctx.arg.sharedMemory)
// In the shared memory case, all data segments must be passive since they
// will be initialized once by the main thread and then shared with other
// threads. In the cooperative threading case, TLS segments must be passive
// so they can be re-initialized per-thread via memory.init, and .bss
// segments are passive to avoid serializing their zero bytes into the binary;
// they are still present as passive segment entries and zero-filled via
// memory.fill in __wasm_init_memory.
bool needsPassiveInit =
ctx.arg.sharedMemory || (ctx.arg.cooperativeThreading &&
(s->isTLS() || s->name.starts_with(".bss")));
if (needsPassiveInit)
s->initFlags = WASM_DATA_SEGMENT_IS_PASSIVE;
if (!ctx.arg.relocatable && name.starts_with(".bss"))
s->isBss = true;
@@ -1113,7 +1125,7 @@ void Writer::combineOutputSegments() {
// This restriction does not apply when the extended const extension is
// available: https://github.com/WebAssembly/extended-const
assert(!ctx.arg.extendedConst);
assert(ctx.isPic && !ctx.arg.sharedMemory);
assert(ctx.isPic && !ctx.arg.isMultithreaded());
if (segments.size() <= 1)
return;
OutputSegment *combined = make<OutputSegment>(".data");
@@ -1188,22 +1200,25 @@ void Writer::createSyntheticInitFunctions() {
"__wasm_init_memory", WASM_SYMBOL_VISIBILITY_HIDDEN,
make<SyntheticFunction>(nullSignature, "__wasm_init_memory"));
ctx.sym.initMemory->markLive();
if (ctx.arg.sharedMemory) {
// This global is assigned during __wasm_init_memory in the shared memory
// case.
// __wasm_init_memory uses __tls_base/__wasm_set_tls_base
if (ctx.sym.setTLSBase)
ctx.sym.setTLSBase->markLive();
else if (ctx.arg.sharedMemory)
ctx.sym.tlsBase->markLive();
}
}
if (ctx.arg.sharedMemory) {
if (ctx.arg.isMultithreaded()) {
if (out.globalSec->needsTLSRelocations()) {
ctx.sym.applyGlobalTLSRelocs = symtab->addSyntheticFunction(
"__wasm_apply_global_tls_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN,
make<SyntheticFunction>(nullSignature,
"__wasm_apply_global_tls_relocs"));
ctx.sym.applyGlobalTLSRelocs->markLive();
// TLS relocations depend on the __tls_base symbols
ctx.sym.tlsBase->markLive();
// TLS relocations depend on the __tls_base/__wasm_get_tls_base symbols
if (ctx.sym.getTLSBase)
ctx.sym.getTLSBase->markLive();
else if (ctx.arg.sharedMemory)
ctx.sym.tlsBase->markLive();
}
auto hasTLSRelocs = [](const OutputSegment *segment) {
@@ -1375,7 +1390,7 @@ void Writer::createInitMemoryFunction() {
// When we initialize the TLS segment we also set the TLS base.
// This allows the runtime to use this static copy of the TLS data
// for the first/main thread.
if (ctx.arg.sharedMemory && s->isTLS()) {
if (ctx.arg.isMultithreaded() && s->isTLS()) {
if (ctx.isPic) {
// Cache the result of the addionion in local 0
writeU8(os, WASM_OPCODE_LOCAL_TEE, "local.tee");
@@ -1446,7 +1461,7 @@ void Writer::createInitMemoryFunction() {
if (needsPassiveInitialization(s) && !s->isBss) {
// The TLS region should not be dropped since its is needed
// during the initialization of each thread (__wasm_init_tls).
if (ctx.arg.sharedMemory && s->isTLS())
if (ctx.arg.isMultithreaded() && s->isTLS())
continue;
// data.drop instruction
writeU8(os, WASM_OPCODE_MISC_PREFIX, "bulk-memory prefix");
@@ -1499,7 +1514,7 @@ void Writer::createApplyDataRelocationsFunction() {
writeUleb128(os, 0, "num locals");
bool generated = false;
for (const OutputSegment *seg : segments)
if (!ctx.arg.sharedMemory || !seg->isTLS())
if (!ctx.arg.isMultithreaded() || !seg->isTLS())
for (const InputChunk *inSeg : seg->inputSegments)
generated |= inSeg->generateRelocationCode(os);
@@ -1655,7 +1670,6 @@ void Writer::createInitTLSFunction() {
if (tlsSeg) {
writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get");
writeUleb128(os, 0, "local index");
writeSetTLSBase(ctx, os);
// FIXME(wvo): this local needs to be I64 in wasm64, or we need an extend
@@ -1788,9 +1802,9 @@ void Writer::run() {
// `__memory_base` import. Unless we support the extended const expression we
// can't do addition inside the constant expression, so we much combine the
// segments into a single one that can live at `__memory_base`.
if (ctx.isPic && !ctx.arg.extendedConst && !ctx.arg.sharedMemory) {
// In shared memory mode all data segments are passive and initialized
// via __wasm_init_memory.
if (ctx.isPic && !ctx.arg.extendedConst && !ctx.arg.isMultithreaded()) {
// In multithreaded modes (shared or cooperative), data segments may be
// passive and must not be combined into a single active segment.
log("-- combineOutputSegments");
combineOutputSegments();
}
@@ -40,9 +40,12 @@ WebAssemblySubtarget::initializeSubtargetDependencies(StringRef CPU,
ParseSubtargetFeatures(CPU, /*TuneCPU*/ CPU, FS);
// WASIP3 implies using the libcall thread context.
if (TargetTriple.getOS() == Triple::WASIp3)
// WASIP3 uses cooperative multithreading, which implies using libcall
// thread context.
if (TargetTriple.getOS() == Triple::WASIp3) {
HasCooperativeMultithreading = true;
HasLibcallThreadContext = true;
}
FeatureBitset Bits = getFeatureBits();
@@ -52,6 +52,7 @@ class WebAssemblySubtarget final : public WebAssemblyGenSubtargetInfo {
bool HasExtendedConst = false;
bool HasFP16 = false;
bool HasGC = false;
bool HasCooperativeMultithreading = false;
bool HasLibcallThreadContext = false;
bool HasMultiMemory = false;
bool HasMultivalue = false;
@@ -117,6 +118,9 @@ public:
bool hasExtendedConst() const { return HasExtendedConst; }
bool hasFP16() const { return HasFP16; }
bool hasGC() const { return HasGC; }
bool hasCooperativeMultithreading() const {
return HasCooperativeMultithreading;
}
bool hasLibcallThreadContext() const { return HasLibcallThreadContext; }
bool hasMultiMemory() const { return HasMultiMemory; }
bool hasMultivalue() const { return HasMultivalue; }
@@ -278,14 +278,21 @@ public:
bool StrippedAtomics = false;
bool StrippedTLS = false;
// In cooperative threading mode, thread locals are meaningful even without
// atomics.
bool CooperativeThreading =
WasmTM->getSubtargetImpl()->hasCooperativeMultithreading();
if (!Features[WebAssembly::FeatureAtomics]) {
StrippedAtomics = stripAtomics(M);
if (!CooperativeThreading)
StrippedTLS = stripThreadLocals(M);
}
if (!Features[WebAssembly::FeatureBulkMemory] && !StrippedTLS) {
StrippedTLS = stripThreadLocals(M);
} else if (!Features[WebAssembly::FeatureBulkMemory]) {
StrippedTLS |= stripThreadLocals(M);
}
if (StrippedAtomics && !StrippedTLS)
if (StrippedAtomics && !StrippedTLS && !CooperativeThreading)
stripThreadLocals(M);
else if (StrippedTLS && !StrippedAtomics)
stripAtomics(M);
@@ -0,0 +1,25 @@
; Test that in cooperative threading mode (wasm32-wasip3), thread-local variables
; are NOT stripped even when atomics are absent. In non-cooperative mode
; (wasm32-unknown-unknown) TLS is treated as normal data when atomics are absent.
; RUN: llc < %s -mtriple=wasm32-wasip3 -mcpu=mvp -mattr=-atomics,+bulk-memory \
; RUN: | FileCheck %s --check-prefixes=COOP
; RUN: llc < %s -mtriple=wasm32-unknown-unknown -mcpu=mvp -mattr=-atomics,+bulk-memory \
; RUN: | FileCheck %s --check-prefixes=PLAIN
target triple = "wasm32-unknown-unknown"
@foo = internal thread_local global i32 0
@bar = internal thread_local global i32 1
; Cooperative threading: TLS is preserved — the section stays .tbss.
; COOP: .tbss.foo
; COOP: .tdata.bar
; COOP-NOT: .bss.foo
; COOP-NOT: .data.bar
; Non-cooperative: TLS stripped
; PLAIN: .bss.foo
; PLAIN: .data.bar
; PLAIN-NOT: .tbss.foo
; PLAIN-NOT: .tdata.bar