[WebAssembly] Cooperative threading for WASIP3 - #200855
Conversation
|
@llvm/pr-subscribers-lld-wasm @llvm/pr-subscribers-lld Author: Sy Brand (TartanLlama) ChangesThis 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:
The linker changes are supported by a new flag called Patch is 22.26 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/200855.diff 16 Files Affected:
diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp b/clang/lib/Driver/ToolChains/WebAssembly.cpp
index 4c1cd937e81aa..d8f23175eb58b 100644
--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp
+++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp
@@ -88,11 +88,16 @@ static bool WantsPthread(const llvm::Triple &Triple, const ArgList &Args) {
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 +179,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-multithreading");
- if (WantsPthread(ToolChain.getTriple(), Args))
+ if (WantsSharedMemory(ToolChain.getTriple(), Args))
CmdArgs.push_back("--shared-memory");
if (!Args.hasArg(options::OPT_nostdlib, options::OPT_nodefaultlibs)) {
diff --git a/clang/test/Driver/wasm-toolchain.c b/clang/test/Driver/wasm-toolchain.c
index 29a94aeec77a9..40d75da3166d9 100644
--- a/clang/test/Driver/wasm-toolchain.c
+++ b/clang/test/Driver/wasm-toolchain.c
@@ -303,3 +303,10 @@
// 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-multithreading` 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-multithreading"
+// LINK_WASIP3_COOP-NOT: "--libcall-thread-context"
diff --git a/lld/test/wasm/cooperative-multithreading.s b/lld/test/wasm/cooperative-multithreading.s
new file mode 100644
index 0000000000000..5adfaa99e40a0
--- /dev/null
+++ b/lld/test/wasm/cooperative-multithreading.s
@@ -0,0 +1,85 @@
+# Test that --cooperative-multithreading 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-multithreading -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-multithreading and --shared-memory are mutually exclusive.
+# RUN: not wasm-ld --cooperative-multithreading --shared-memory %t.o -o %t2.wasm 2>&1 | FileCheck %s --check-prefix=INCOMPAT
+# INCOMPAT: --cooperative-multithreading 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: false
+
+# 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
diff --git a/lld/test/wasm/stack-pointer-abi.s b/lld/test/wasm/stack-pointer-abi.s
index 869f972710991..fbae0475bcba2 100644
--- a/lld/test/wasm/stack-pointer-abi.s
+++ b/lld/test/wasm/stack-pointer-abi.s
@@ -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
diff --git a/lld/test/wasm/thread-context-abi-mismatch.s b/lld/test/wasm/thread-context-abi-mismatch.s
index 069534cbe5762..a817ca4407aab 100644
--- a/lld/test/wasm/thread-context-abi-mismatch.s
+++ b/lld/test/wasm/thread-context-abi-mismatch.s
@@ -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-multithreading %t.o -o %t.wasm 2>&1 | FileCheck %s
+# CHECK: object file uses globals for thread context, but --cooperative-multithreading was specified
.globl _start
_start:
.functype _start () -> ()
diff --git a/lld/test/wasm/tls-libcall.s b/lld/test/wasm/tls-libcall.s
index df8b8f8be0207..a0a7f37379bac 100644
--- a/lld/test/wasm/tls-libcall.s
+++ b/lld/test/wasm/tls-libcall.s
@@ -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 --shared-memory -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
diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index 71a378a412e9e..d4789b88203eb 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -65,6 +65,7 @@ struct Config {
bool growableTable;
bool gcSections;
llvm::StringSet<> keepSections;
+ bool cooperativeMultithreading;
bool libcallThreadContext;
std::optional<std::pair<llvm::StringRef, llvm::StringRef>> memoryImport;
std::optional<llvm::StringRef> memoryExport;
@@ -134,6 +135,10 @@ 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 || cooperativeMultithreading;
+ }
};
// The Ctx object hold all other (non-configuration) global state.
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index fe1e2eec95037..b2723220b5afb 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -561,7 +561,9 @@ 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.cooperativeMultithreading =
+ args.hasArg(OPT_cooperative_multithreading);
+ ;
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 +757,12 @@ static void setConfigs() {
if (!ctx.arg.memoryExport.has_value() && !ctx.arg.memoryImport.has_value()) {
ctx.arg.memoryExport = memoryName;
}
+ if (ctx.arg.cooperativeMultithreading) {
+ if (ctx.arg.sharedMemory)
+ error(
+ "--cooperative-multithreading is incompatible with --shared-memory");
+ ctx.arg.libcallThreadContext = true;
+ }
}
// Some command line options or some combinations of them are not allowed.
@@ -964,7 +972,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";
@@ -1028,7 +1036,7 @@ static void createOptionalSymbols() {
//
// __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)
+ if (!ctx.sym.tlsBase)
ctx.sym.tlsBase = createOptionalGlobal("__tls_base", false);
}
diff --git a/lld/wasm/Options.td b/lld/wasm/Options.td
index 144eee33061e1..6d18a0400ef97 100644
--- a/lld/wasm/Options.td
+++ b/lld/wasm/Options.td
@@ -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_multithreading: FF<"cooperative-multithreading">,
+ HelpText<"Enable cooperative multithreading.">;
def max_memory: JJ<"max-memory=">,
HelpText<"Maximum size of the linear memory">;
diff --git a/lld/wasm/Relocations.cpp b/lld/wasm/Relocations.cpp
index a1840abe88b3a..cb597fdeffcf3 100644
--- a/lld/wasm/Relocations.cpp
+++ b/lld/wasm/Relocations.cpp
@@ -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) +
diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp
index d1a01c7ec3f9d..050f61c7f5c56 100644
--- a/lld/wasm/SyntheticSections.cpp
+++ b/lld/wasm/SyntheticSections.cpp
@@ -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() {
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 688bb829e1c42..d90ca859f3479 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -425,7 +425,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) {
auto *tlsBase = cast<DefinedGlobal>(ctx.sym.tlsBase);
setGlobalPtr(tlsBase, fixedTLSBase);
}
@@ -653,7 +653,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)
@@ -1057,7 +1057,15 @@ 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 non-shared memory case, we use passive segments only for
+ // TLS segments, so that they can be reused, and for .bss segments, which
+ // don't need to be included in the binary at all.
+ bool needsPassiveInit =
+ ctx.arg.sharedMemory || (ctx.arg.cooperativeMultithreading &&
+ (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;
@@ -1116,7 +1124,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");
@@ -1198,7 +1206,7 @@ void Writer::createSyntheticInitFunctions() {
}
}
- 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,
@@ -1378,7 +1386,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");
@@ -1449,7 +1457,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");
@@ -1502,7 +1510,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);
@@ -1658,7 +1666,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
@@ -1791,9 +1798,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();
}
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
index 6326b7d76db82..9dea29fb0205d 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
@@ -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();
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
index 5c6f4cb5b36ff..f637ce59ebfce 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
@@ -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 @@ class WebAssemblySubtarget final : public WebAssemblyGenSubtargetInfo {
bool hasExtendedConst() const { return HasExtendedConst; }
bool hasFP16() const { return HasFP16; }
bool hasGC() const { return HasGC; }
+ bool hasCooperativeMultithreading() const {
+ return HasCooperativeMultithre...
[truncated]
|
|
@llvm/pr-subscribers-clang-driver Author: Sy Brand (TartanLlama) ChangesThis 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:
The linker changes are supported by a new flag called Patch is 22.26 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/200855.diff 16 Files Affected:
diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp b/clang/lib/Driver/ToolChains/WebAssembly.cpp
index 4c1cd937e81aa..d8f23175eb58b 100644
--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp
+++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp
@@ -88,11 +88,16 @@ static bool WantsPthread(const llvm::Triple &Triple, const ArgList &Args) {
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 +179,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-multithreading");
- if (WantsPthread(ToolChain.getTriple(), Args))
+ if (WantsSharedMemory(ToolChain.getTriple(), Args))
CmdArgs.push_back("--shared-memory");
if (!Args.hasArg(options::OPT_nostdlib, options::OPT_nodefaultlibs)) {
diff --git a/clang/test/Driver/wasm-toolchain.c b/clang/test/Driver/wasm-toolchain.c
index 29a94aeec77a9..40d75da3166d9 100644
--- a/clang/test/Driver/wasm-toolchain.c
+++ b/clang/test/Driver/wasm-toolchain.c
@@ -303,3 +303,10 @@
// 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-multithreading` 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-multithreading"
+// LINK_WASIP3_COOP-NOT: "--libcall-thread-context"
diff --git a/lld/test/wasm/cooperative-multithreading.s b/lld/test/wasm/cooperative-multithreading.s
new file mode 100644
index 0000000000000..5adfaa99e40a0
--- /dev/null
+++ b/lld/test/wasm/cooperative-multithreading.s
@@ -0,0 +1,85 @@
+# Test that --cooperative-multithreading 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-multithreading -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-multithreading and --shared-memory are mutually exclusive.
+# RUN: not wasm-ld --cooperative-multithreading --shared-memory %t.o -o %t2.wasm 2>&1 | FileCheck %s --check-prefix=INCOMPAT
+# INCOMPAT: --cooperative-multithreading 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: false
+
+# 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
diff --git a/lld/test/wasm/stack-pointer-abi.s b/lld/test/wasm/stack-pointer-abi.s
index 869f972710991..fbae0475bcba2 100644
--- a/lld/test/wasm/stack-pointer-abi.s
+++ b/lld/test/wasm/stack-pointer-abi.s
@@ -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
diff --git a/lld/test/wasm/thread-context-abi-mismatch.s b/lld/test/wasm/thread-context-abi-mismatch.s
index 069534cbe5762..a817ca4407aab 100644
--- a/lld/test/wasm/thread-context-abi-mismatch.s
+++ b/lld/test/wasm/thread-context-abi-mismatch.s
@@ -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-multithreading %t.o -o %t.wasm 2>&1 | FileCheck %s
+# CHECK: object file uses globals for thread context, but --cooperative-multithreading was specified
.globl _start
_start:
.functype _start () -> ()
diff --git a/lld/test/wasm/tls-libcall.s b/lld/test/wasm/tls-libcall.s
index df8b8f8be0207..a0a7f37379bac 100644
--- a/lld/test/wasm/tls-libcall.s
+++ b/lld/test/wasm/tls-libcall.s
@@ -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 --shared-memory -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
diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index 71a378a412e9e..d4789b88203eb 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -65,6 +65,7 @@ struct Config {
bool growableTable;
bool gcSections;
llvm::StringSet<> keepSections;
+ bool cooperativeMultithreading;
bool libcallThreadContext;
std::optional<std::pair<llvm::StringRef, llvm::StringRef>> memoryImport;
std::optional<llvm::StringRef> memoryExport;
@@ -134,6 +135,10 @@ 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 || cooperativeMultithreading;
+ }
};
// The Ctx object hold all other (non-configuration) global state.
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index fe1e2eec95037..b2723220b5afb 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -561,7 +561,9 @@ 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.cooperativeMultithreading =
+ args.hasArg(OPT_cooperative_multithreading);
+ ;
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 +757,12 @@ static void setConfigs() {
if (!ctx.arg.memoryExport.has_value() && !ctx.arg.memoryImport.has_value()) {
ctx.arg.memoryExport = memoryName;
}
+ if (ctx.arg.cooperativeMultithreading) {
+ if (ctx.arg.sharedMemory)
+ error(
+ "--cooperative-multithreading is incompatible with --shared-memory");
+ ctx.arg.libcallThreadContext = true;
+ }
}
// Some command line options or some combinations of them are not allowed.
@@ -964,7 +972,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";
@@ -1028,7 +1036,7 @@ static void createOptionalSymbols() {
//
// __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)
+ if (!ctx.sym.tlsBase)
ctx.sym.tlsBase = createOptionalGlobal("__tls_base", false);
}
diff --git a/lld/wasm/Options.td b/lld/wasm/Options.td
index 144eee33061e1..6d18a0400ef97 100644
--- a/lld/wasm/Options.td
+++ b/lld/wasm/Options.td
@@ -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_multithreading: FF<"cooperative-multithreading">,
+ HelpText<"Enable cooperative multithreading.">;
def max_memory: JJ<"max-memory=">,
HelpText<"Maximum size of the linear memory">;
diff --git a/lld/wasm/Relocations.cpp b/lld/wasm/Relocations.cpp
index a1840abe88b3a..cb597fdeffcf3 100644
--- a/lld/wasm/Relocations.cpp
+++ b/lld/wasm/Relocations.cpp
@@ -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) +
diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp
index d1a01c7ec3f9d..050f61c7f5c56 100644
--- a/lld/wasm/SyntheticSections.cpp
+++ b/lld/wasm/SyntheticSections.cpp
@@ -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() {
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 688bb829e1c42..d90ca859f3479 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -425,7 +425,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) {
auto *tlsBase = cast<DefinedGlobal>(ctx.sym.tlsBase);
setGlobalPtr(tlsBase, fixedTLSBase);
}
@@ -653,7 +653,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)
@@ -1057,7 +1057,15 @@ 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 non-shared memory case, we use passive segments only for
+ // TLS segments, so that they can be reused, and for .bss segments, which
+ // don't need to be included in the binary at all.
+ bool needsPassiveInit =
+ ctx.arg.sharedMemory || (ctx.arg.cooperativeMultithreading &&
+ (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;
@@ -1116,7 +1124,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");
@@ -1198,7 +1206,7 @@ void Writer::createSyntheticInitFunctions() {
}
}
- 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,
@@ -1378,7 +1386,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");
@@ -1449,7 +1457,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");
@@ -1502,7 +1510,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);
@@ -1658,7 +1666,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
@@ -1791,9 +1798,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();
}
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
index 6326b7d76db82..9dea29fb0205d 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
@@ -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();
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
index 5c6f4cb5b36ff..f637ce59ebfce 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
@@ -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 @@ class WebAssemblySubtarget final : public WebAssemblyGenSubtargetInfo {
bool hasExtendedConst() const { return HasExtendedConst; }
bool hasFP16() const { return HasFP16; }
bool hasGC() const { return HasGC; }
+ bool hasCooperativeMultithreading() const {
+ return HasCooperativeMultithre...
[truncated]
|
|
@llvm/pr-subscribers-backend-webassembly Author: Sy Brand (TartanLlama) ChangesThis 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:
The linker changes are supported by a new flag called Patch is 22.26 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/200855.diff 16 Files Affected:
diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp b/clang/lib/Driver/ToolChains/WebAssembly.cpp
index 4c1cd937e81aa..d8f23175eb58b 100644
--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp
+++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp
@@ -88,11 +88,16 @@ static bool WantsPthread(const llvm::Triple &Triple, const ArgList &Args) {
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 +179,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-multithreading");
- if (WantsPthread(ToolChain.getTriple(), Args))
+ if (WantsSharedMemory(ToolChain.getTriple(), Args))
CmdArgs.push_back("--shared-memory");
if (!Args.hasArg(options::OPT_nostdlib, options::OPT_nodefaultlibs)) {
diff --git a/clang/test/Driver/wasm-toolchain.c b/clang/test/Driver/wasm-toolchain.c
index 29a94aeec77a9..40d75da3166d9 100644
--- a/clang/test/Driver/wasm-toolchain.c
+++ b/clang/test/Driver/wasm-toolchain.c
@@ -303,3 +303,10 @@
// 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-multithreading` 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-multithreading"
+// LINK_WASIP3_COOP-NOT: "--libcall-thread-context"
diff --git a/lld/test/wasm/cooperative-multithreading.s b/lld/test/wasm/cooperative-multithreading.s
new file mode 100644
index 0000000000000..5adfaa99e40a0
--- /dev/null
+++ b/lld/test/wasm/cooperative-multithreading.s
@@ -0,0 +1,85 @@
+# Test that --cooperative-multithreading 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-multithreading -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-multithreading and --shared-memory are mutually exclusive.
+# RUN: not wasm-ld --cooperative-multithreading --shared-memory %t.o -o %t2.wasm 2>&1 | FileCheck %s --check-prefix=INCOMPAT
+# INCOMPAT: --cooperative-multithreading 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: false
+
+# 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
diff --git a/lld/test/wasm/stack-pointer-abi.s b/lld/test/wasm/stack-pointer-abi.s
index 869f972710991..fbae0475bcba2 100644
--- a/lld/test/wasm/stack-pointer-abi.s
+++ b/lld/test/wasm/stack-pointer-abi.s
@@ -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
diff --git a/lld/test/wasm/thread-context-abi-mismatch.s b/lld/test/wasm/thread-context-abi-mismatch.s
index 069534cbe5762..a817ca4407aab 100644
--- a/lld/test/wasm/thread-context-abi-mismatch.s
+++ b/lld/test/wasm/thread-context-abi-mismatch.s
@@ -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-multithreading %t.o -o %t.wasm 2>&1 | FileCheck %s
+# CHECK: object file uses globals for thread context, but --cooperative-multithreading was specified
.globl _start
_start:
.functype _start () -> ()
diff --git a/lld/test/wasm/tls-libcall.s b/lld/test/wasm/tls-libcall.s
index df8b8f8be0207..a0a7f37379bac 100644
--- a/lld/test/wasm/tls-libcall.s
+++ b/lld/test/wasm/tls-libcall.s
@@ -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 --shared-memory -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
diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index 71a378a412e9e..d4789b88203eb 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -65,6 +65,7 @@ struct Config {
bool growableTable;
bool gcSections;
llvm::StringSet<> keepSections;
+ bool cooperativeMultithreading;
bool libcallThreadContext;
std::optional<std::pair<llvm::StringRef, llvm::StringRef>> memoryImport;
std::optional<llvm::StringRef> memoryExport;
@@ -134,6 +135,10 @@ 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 || cooperativeMultithreading;
+ }
};
// The Ctx object hold all other (non-configuration) global state.
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index fe1e2eec95037..b2723220b5afb 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -561,7 +561,9 @@ 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.cooperativeMultithreading =
+ args.hasArg(OPT_cooperative_multithreading);
+ ;
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 +757,12 @@ static void setConfigs() {
if (!ctx.arg.memoryExport.has_value() && !ctx.arg.memoryImport.has_value()) {
ctx.arg.memoryExport = memoryName;
}
+ if (ctx.arg.cooperativeMultithreading) {
+ if (ctx.arg.sharedMemory)
+ error(
+ "--cooperative-multithreading is incompatible with --shared-memory");
+ ctx.arg.libcallThreadContext = true;
+ }
}
// Some command line options or some combinations of them are not allowed.
@@ -964,7 +972,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";
@@ -1028,7 +1036,7 @@ static void createOptionalSymbols() {
//
// __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)
+ if (!ctx.sym.tlsBase)
ctx.sym.tlsBase = createOptionalGlobal("__tls_base", false);
}
diff --git a/lld/wasm/Options.td b/lld/wasm/Options.td
index 144eee33061e1..6d18a0400ef97 100644
--- a/lld/wasm/Options.td
+++ b/lld/wasm/Options.td
@@ -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_multithreading: FF<"cooperative-multithreading">,
+ HelpText<"Enable cooperative multithreading.">;
def max_memory: JJ<"max-memory=">,
HelpText<"Maximum size of the linear memory">;
diff --git a/lld/wasm/Relocations.cpp b/lld/wasm/Relocations.cpp
index a1840abe88b3a..cb597fdeffcf3 100644
--- a/lld/wasm/Relocations.cpp
+++ b/lld/wasm/Relocations.cpp
@@ -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) +
diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp
index d1a01c7ec3f9d..050f61c7f5c56 100644
--- a/lld/wasm/SyntheticSections.cpp
+++ b/lld/wasm/SyntheticSections.cpp
@@ -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() {
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 688bb829e1c42..d90ca859f3479 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -425,7 +425,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) {
auto *tlsBase = cast<DefinedGlobal>(ctx.sym.tlsBase);
setGlobalPtr(tlsBase, fixedTLSBase);
}
@@ -653,7 +653,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)
@@ -1057,7 +1057,15 @@ 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 non-shared memory case, we use passive segments only for
+ // TLS segments, so that they can be reused, and for .bss segments, which
+ // don't need to be included in the binary at all.
+ bool needsPassiveInit =
+ ctx.arg.sharedMemory || (ctx.arg.cooperativeMultithreading &&
+ (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;
@@ -1116,7 +1124,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");
@@ -1198,7 +1206,7 @@ void Writer::createSyntheticInitFunctions() {
}
}
- 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,
@@ -1378,7 +1386,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");
@@ -1449,7 +1457,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");
@@ -1502,7 +1510,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);
@@ -1658,7 +1666,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
@@ -1791,9 +1798,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();
}
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
index 6326b7d76db82..9dea29fb0205d 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
@@ -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();
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
index 5c6f4cb5b36ff..f637ce59ebfce 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
@@ -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 @@ class WebAssemblySubtarget final : public WebAssemblyGenSubtargetInfo {
bool hasExtendedConst() const { return HasExtendedConst; }
bool hasFP16() const { return HasFP16; }
bool hasGC() const { return HasGC; }
+ bool hasCooperativeMultithreading() const {
+ return HasCooperativeMultithre...
[truncated]
|
🐧 Linux x64 Test Results
✅ The build succeeded and all tests passed. |
🪟 Windows x64 Test Results
✅ The build succeeded and all tests passed. |
| 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"); |
There was a problem hiding this comment.
Was this a typo from the previous commit?
| // will be initialized once by the main thread and then shared with other | ||
| // threads. In the non-shared memory case, we use passive segments only for | ||
| // TLS segments, so that they can be reused, and for .bss segments, which | ||
| // don't need to be included in the binary at all. |
There was a problem hiding this comment.
I'm not sure about the .bss part of the this comment. If they are not needed in the binary at all why would need passive init?
There was a problem hiding this comment.
I've updated the comment
I noticed this while reviewing llvm#200855.
| // For non-shared memory programs we still need to define __tls_base since we | ||
| // For non-multithreaded 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. |
There was a problem hiding this comment.
This sentence has both non-multithreaded and single threaded which reads a little odd to me.
Could we rephrase? Maybe shorten this sentence somehow?
There was a problem hiding this comment.
I've simplified the comment
| if (!CooperativeThreading) | ||
| StrippedTLS = stripThreadLocals(M); | ||
| } | ||
| if (!Features[WebAssembly::FeatureBulkMemory]) { |
There was a problem hiding this comment.
Does the the removal of the else there change the behaviour in the case we have atomics enabled but not explicitly bulk memory?
There was a problem hiding this comment.
The only difference in behaviour is in the CooperativeThreading case, which now does not strip thread locals in the !atomics case, but still does in the !bulk_memory case. The latter was the reason for removing the else.
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
|
@sbc100 looks all green now, anything else needed? |
| StrippedTLS = stripThreadLocals(M); | ||
| } | ||
| if (!Features[WebAssembly::FeatureBulkMemory]) { | ||
| StrippedTLS |= stripThreadLocals(M); |
There was a problem hiding this comment.
Do we risk calling stripThreadLocals twice now? Is there must cost to that?
There was a problem hiding this comment.
Ah, I forgot |= doesn't short-circuit, I've changed the condition to ensure it's called called twice
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 is a work-in-progress to get things enabled. The main bulk of the change here is updating LLVM itself to tip-of-tree as of yesterday morning. This removes two `*.patch` files because they've landed, and adds another `*.patch` file to work around a new issue that's cropped up. I've also included a `*.patch` for llvm/llvm-project#200855, too. This isn't ready yet to land as it's still failing to build/link/etc, and I'll comment some more below.
…#206831) With `--gc-sections` (the default), `wasm-ld` garbage-collects functions that are only reachable through `__wasm_get_tls_base` / `__wasm_set_tls_base` in the cooperative-threading (libcall thread-context) configuration. This produces a linked module that is invalid or behaves incorrectly: the relocation inside `__wasm_set_tls_base` is left dangling / mis-resolved, so callers trap at runtime (e.g. `validation error: ... values remaining on stack at end of block`, or a call to an unrelated function). In cooperative-threading mode (`--cooperative-threading`, added in #200855), per-task thread context is accessed through libcalls rather than wasm globals. `wasm-ld` synthesizes `__wasm_init_tls` / `__wasm_init_memory`, which invoke `__wasm_get_tls_base` and `__wasm_set_tls_base` via **raw `call` instructions that carry no relocations**. To keep those accessors in the output, the linker marks them live with `Symbol::markLive()`. ## Error and Repro ```bash Error: failed to compile: wasm[0]::function[16]::__wasm_set_tls_base Caused by: 0: WebAssembly translation error 1: Invalid input WebAssembly code at offset 778: type mismatch: values remaining on stack at end of block ``` import was dropped and __wasm_set_tls_base was rewritten from ```wat (func $__wasm_set_tls_base (param i32) local.get 0 call $__wasm_component_model_builtin_context_set_1) ;; correct ``` to ```wat (func $__wasm_set_tls_base (param i32) local.get 0 call $__wasm_component_model_builtin_context_get_0) ;; wrong: get_0 is () -> i32 ``` Minimal linker-only repro (no runtime needed) is: ```bash wasm-ld --cooperative-threading tls.o libc.a # context-set-1 dropped, set_tls_base calls get_0 wasm-ld --cooperative-threading --no-gc-sections tls.o libc.a # correct ``` ## Root cause `Symbol::markLive()` sets the live flag (and the chunk's live bit) but does not push the defining chunk onto the mark queue. The mark phase only follows relocations of chunks that were enqueued via `MarkLive::enqueue()`. As a result the accessors' own relocations are never traversed. This is the same situation already handled for constructors reached through the relocation-less `__wasm_call_ctors`, which `MarkLive::run()` enqueues explicitly. ## Fix In `MarkLive::run()`, enqueue the defining chunks of `__wasm_get_tls_base` and `__wasm_set_tls_base` before `mark()`, so their relocations are followed. ```cpp for (Symbol *sym : {static_cast<Symbol *>(ctx.sym.getTLSBase), static_cast<Symbol *>(ctx.sym.setTLSBase)}) if (sym) if (InputChunk *c = sym->getChunk()) enqueue(c); ``` The symbols are only set in the libcall-thread-context configuration and are null otherwise, so the loop is a no-op for all other builds. ## Testing Adds `lld/test/wasm/cooperative-threading-gc.s`, which links with `--cooperative-threading --gc-sections` and checks (via `obj2yaml`) that the import called by `__wasm_set_tls_base` survives GC. Without this change the test drops the import and rewrites `__wasm_set_tls_base` to call an unrelated function; with it the import is retained and the call is correct. ```sh ninja check-lld-wasm ``` ## Note Human-in-the-loop with claude opus 4.8. I iterated with this patch and got to a working component running with a wasi-sdk fork.
…llvm#206831) With `--gc-sections` (the default), `wasm-ld` garbage-collects functions that are only reachable through `__wasm_get_tls_base` / `__wasm_set_tls_base` in the cooperative-threading (libcall thread-context) configuration. This produces a linked module that is invalid or behaves incorrectly: the relocation inside `__wasm_set_tls_base` is left dangling / mis-resolved, so callers trap at runtime (e.g. `validation error: ... values remaining on stack at end of block`, or a call to an unrelated function). In cooperative-threading mode (`--cooperative-threading`, added in llvm#200855), per-task thread context is accessed through libcalls rather than wasm globals. `wasm-ld` synthesizes `__wasm_init_tls` / `__wasm_init_memory`, which invoke `__wasm_get_tls_base` and `__wasm_set_tls_base` via **raw `call` instructions that carry no relocations**. To keep those accessors in the output, the linker marks them live with `Symbol::markLive()`. ## Error and Repro ```bash Error: failed to compile: wasm[0]::function[16]::__wasm_set_tls_base Caused by: 0: WebAssembly translation error 1: Invalid input WebAssembly code at offset 778: type mismatch: values remaining on stack at end of block ``` import was dropped and __wasm_set_tls_base was rewritten from ```wat (func $__wasm_set_tls_base (param i32) local.get 0 call $__wasm_component_model_builtin_context_set_1) ;; correct ``` to ```wat (func $__wasm_set_tls_base (param i32) local.get 0 call $__wasm_component_model_builtin_context_get_0) ;; wrong: get_0 is () -> i32 ``` Minimal linker-only repro (no runtime needed) is: ```bash wasm-ld --cooperative-threading tls.o libc.a # context-set-1 dropped, set_tls_base calls get_0 wasm-ld --cooperative-threading --no-gc-sections tls.o libc.a # correct ``` ## Root cause `Symbol::markLive()` sets the live flag (and the chunk's live bit) but does not push the defining chunk onto the mark queue. The mark phase only follows relocations of chunks that were enqueued via `MarkLive::enqueue()`. As a result the accessors' own relocations are never traversed. This is the same situation already handled for constructors reached through the relocation-less `__wasm_call_ctors`, which `MarkLive::run()` enqueues explicitly. ## Fix In `MarkLive::run()`, enqueue the defining chunks of `__wasm_get_tls_base` and `__wasm_set_tls_base` before `mark()`, so their relocations are followed. ```cpp for (Symbol *sym : {static_cast<Symbol *>(ctx.sym.getTLSBase), static_cast<Symbol *>(ctx.sym.setTLSBase)}) if (sym) if (InputChunk *c = sym->getChunk()) enqueue(c); ``` The symbols are only set in the libcall-thread-context configuration and are null otherwise, so the loop is a no-op for all other builds. ## Testing Adds `lld/test/wasm/cooperative-threading-gc.s`, which links with `--cooperative-threading --gc-sections` and checks (via `obj2yaml`) that the import called by `__wasm_set_tls_base` survives GC. Without this change the test drops the import and rewrites `__wasm_set_tls_base` to call an unrelated function; with it the import is retained and the call is correct. ```sh ninja check-lld-wasm ``` ## Note Human-in-the-loop with claude opus 4.8. I iterated with this patch and got to a working component running with a wasi-sdk fork.
…llvm#206831) With `--gc-sections` (the default), `wasm-ld` garbage-collects functions that are only reachable through `__wasm_get_tls_base` / `__wasm_set_tls_base` in the cooperative-threading (libcall thread-context) configuration. This produces a linked module that is invalid or behaves incorrectly: the relocation inside `__wasm_set_tls_base` is left dangling / mis-resolved, so callers trap at runtime (e.g. `validation error: ... values remaining on stack at end of block`, or a call to an unrelated function). In cooperative-threading mode (`--cooperative-threading`, added in llvm#200855), per-task thread context is accessed through libcalls rather than wasm globals. `wasm-ld` synthesizes `__wasm_init_tls` / `__wasm_init_memory`, which invoke `__wasm_get_tls_base` and `__wasm_set_tls_base` via **raw `call` instructions that carry no relocations**. To keep those accessors in the output, the linker marks them live with `Symbol::markLive()`. ## Error and Repro ```bash Error: failed to compile: wasm[0]::function[16]::__wasm_set_tls_base Caused by: 0: WebAssembly translation error 1: Invalid input WebAssembly code at offset 778: type mismatch: values remaining on stack at end of block ``` import was dropped and __wasm_set_tls_base was rewritten from ```wat (func $__wasm_set_tls_base (param i32) local.get 0 call $__wasm_component_model_builtin_context_set_1) ;; correct ``` to ```wat (func $__wasm_set_tls_base (param i32) local.get 0 call $__wasm_component_model_builtin_context_get_0) ;; wrong: get_0 is () -> i32 ``` Minimal linker-only repro (no runtime needed) is: ```bash wasm-ld --cooperative-threading tls.o libc.a # context-set-1 dropped, set_tls_base calls get_0 wasm-ld --cooperative-threading --no-gc-sections tls.o libc.a # correct ``` ## Root cause `Symbol::markLive()` sets the live flag (and the chunk's live bit) but does not push the defining chunk onto the mark queue. The mark phase only follows relocations of chunks that were enqueued via `MarkLive::enqueue()`. As a result the accessors' own relocations are never traversed. This is the same situation already handled for constructors reached through the relocation-less `__wasm_call_ctors`, which `MarkLive::run()` enqueues explicitly. ## Fix In `MarkLive::run()`, enqueue the defining chunks of `__wasm_get_tls_base` and `__wasm_set_tls_base` before `mark()`, so their relocations are followed. ```cpp for (Symbol *sym : {static_cast<Symbol *>(ctx.sym.getTLSBase), static_cast<Symbol *>(ctx.sym.setTLSBase)}) if (sym) if (InputChunk *c = sym->getChunk()) enqueue(c); ``` The symbols are only set in the libcall-thread-context configuration and are null otherwise, so the loop is a no-op for all other builds. ## Testing Adds `lld/test/wasm/cooperative-threading-gc.s`, which links with `--cooperative-threading --gc-sections` and checks (via `obj2yaml`) that the import called by `__wasm_set_tls_base` survives GC. Without this change the test drops the import and rewrites `__wasm_set_tls_base` to call an unrelated function; with it the import is retained and the call is correct. ```sh ninja check-lld-wasm ``` ## Note Human-in-the-loop with claude opus 4.8. I iterated with this patch and got to a working component running with a wasi-sdk fork.
…#206831) With `--gc-sections` (the default), `wasm-ld` garbage-collects functions that are only reachable through `__wasm_get_tls_base` / `__wasm_set_tls_base` in the cooperative-threading (libcall thread-context) configuration. This produces a linked module that is invalid or behaves incorrectly: the relocation inside `__wasm_set_tls_base` is left dangling / mis-resolved, so callers trap at runtime (e.g. `validation error: ... values remaining on stack at end of block`, or a call to an unrelated function). In cooperative-threading mode (`--cooperative-threading`, added in #200855), per-task thread context is accessed through libcalls rather than wasm globals. `wasm-ld` synthesizes `__wasm_init_tls` / `__wasm_init_memory`, which invoke `__wasm_get_tls_base` and `__wasm_set_tls_base` via **raw `call` instructions that carry no relocations**. To keep those accessors in the output, the linker marks them live with `Symbol::markLive()`. ## Error and Repro ```bash Error: failed to compile: wasm[0]::function[16]::__wasm_set_tls_base Caused by: 0: WebAssembly translation error 1: Invalid input WebAssembly code at offset 778: type mismatch: values remaining on stack at end of block ``` import was dropped and __wasm_set_tls_base was rewritten from ```wat (func $__wasm_set_tls_base (param i32) local.get 0 call $__wasm_component_model_builtin_context_set_1) ;; correct ``` to ```wat (func $__wasm_set_tls_base (param i32) local.get 0 call $__wasm_component_model_builtin_context_get_0) ;; wrong: get_0 is () -> i32 ``` Minimal linker-only repro (no runtime needed) is: ```bash wasm-ld --cooperative-threading tls.o libc.a # context-set-1 dropped, set_tls_base calls get_0 wasm-ld --cooperative-threading --no-gc-sections tls.o libc.a # correct ``` ## Root cause `Symbol::markLive()` sets the live flag (and the chunk's live bit) but does not push the defining chunk onto the mark queue. The mark phase only follows relocations of chunks that were enqueued via `MarkLive::enqueue()`. As a result the accessors' own relocations are never traversed. This is the same situation already handled for constructors reached through the relocation-less `__wasm_call_ctors`, which `MarkLive::run()` enqueues explicitly. ## Fix In `MarkLive::run()`, enqueue the defining chunks of `__wasm_get_tls_base` and `__wasm_set_tls_base` before `mark()`, so their relocations are followed. ```cpp for (Symbol *sym : {static_cast<Symbol *>(ctx.sym.getTLSBase), static_cast<Symbol *>(ctx.sym.setTLSBase)}) if (sym) if (InputChunk *c = sym->getChunk()) enqueue(c); ``` The symbols are only set in the libcall-thread-context configuration and are null otherwise, so the loop is a no-op for all other builds. ## Testing Adds `lld/test/wasm/cooperative-threading-gc.s`, which links with `--cooperative-threading --gc-sections` and checks (via `obj2yaml`) that the import called by `__wasm_set_tls_base` survives GC. Without this change the test drops the import and rewrites `__wasm_set_tls_base` to call an unrelated function; with it the import is retained and the call is correct. ```sh ninja check-lld-wasm ``` ## Note Human-in-the-loop with claude opus 4.8. I iterated with this patch and got to a working component running with a wasi-sdk fork.
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:
The linker changes are supported by a new flag called
--cooperative-multithreading. We talked about having two flags, one for the--libcall-thread-contextpart and one for the cooperative multithreading part. For now, I've simply replaced the--libcall-thread-contextflag with the--cooperative-multithreadingone and kept the internal configuration intact for simplicity.