https://github.com/TartanLlama updated 
https://github.com/llvm/llvm-project/pull/175800

>From 48a90dd7e288ad6c3ce8038c5b2d143dcc1dac34 Mon Sep 17 00:00:00 2001
From: Sy Brand <[email protected]>
Date: Mon, 5 Jan 2026 14:43:56 +0000
Subject: [PATCH 01/12] Initial work

---
 clang/lib/Driver/ToolChains/WebAssembly.cpp   |  33 ++-
 lld/wasm/Config.h                             |  10 +
 lld/wasm/Driver.cpp                           |  66 +++--
 lld/wasm/Symbols.cpp                          |   8 +-
 lld/wasm/SyntheticSections.cpp                |  11 +-
 lld/wasm/Writer.cpp                           |  27 +-
 lld/wasm/WriterUtils.cpp                      |  24 +-
 lld/wasm/WriterUtils.h                        |   4 +
 llvm/include/llvm/MC/MCSymbolWasm.h           |   8 +-
 .../AsmParser/WebAssemblyAsmParser.cpp        |  22 +-
 .../MCTargetDesc/WebAssemblyMCTargetDesc.h    | 242 +++++++++---------
 .../WebAssembly/WebAssemblyAsmPrinter.cpp     |  31 ++-
 .../WebAssembly/WebAssemblyFrameLowering.cpp  |  53 ++--
 .../WebAssembly/WebAssemblyFrameLowering.h    |   6 +-
 .../WebAssembly/WebAssemblyISelDAGToDAG.cpp   |   6 +-
 .../WebAssembly/WebAssemblyISelLowering.cpp   |  21 +-
 .../WebAssembly/WebAssemblyLateEHPrepare.cpp  |   2 +-
 .../WebAssembly/WebAssemblyUtilities.cpp      |  18 ++
 .../Target/WebAssembly/WebAssemblyUtilities.h |   9 +
 19 files changed, 372 insertions(+), 229 deletions(-)

diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp 
b/clang/lib/Driver/ToolChains/WebAssembly.cpp
index 15c6f19e87fee..d6ff6584258d2 100644
--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp
+++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp
@@ -30,13 +30,14 @@ using namespace llvm::opt;
 std::string WebAssembly::getMultiarchTriple(const Driver &D,
                                             const llvm::Triple &TargetTriple,
                                             StringRef SysRoot) const {
-    return (TargetTriple.getArchName() + "-" +
-            TargetTriple.getOSAndEnvironmentName()).str();
+  return (TargetTriple.getArchName() + "-" +
+          TargetTriple.getOSAndEnvironmentName())
+      .str();
 }
 
 std::string wasm::Linker::getLinkerPath(const ArgList &Args) const {
   const ToolChain &ToolChain = getToolChain();
-  if (const Arg* A = Args.getLastArg(options::OPT_fuse_ld_EQ)) {
+  if (const Arg *A = Args.getLastArg(options::OPT_fuse_ld_EQ)) {
     StringRef UseLinker = A->getValue();
     if (!UseLinker.empty()) {
       if (llvm::sys::path::is_absolute(UseLinker) &&
@@ -79,6 +80,10 @@ static bool WantsPthread(const llvm::Triple &Triple, const 
ArgList &Args) {
   return WantsPthread;
 }
 
+static bool WantsSharedMemory(const llvm::Triple &Triple, const ArgList &Args) 
{
+  return WantsPthread(Triple, Args) && !TargetBuildsComponents(Triple);
+}
+
 void wasm::Linker::ConstructJob(Compilation &C, const JobAction &JA,
                                 const InputInfo &Output,
                                 const InputInfoList &Inputs,
@@ -90,10 +95,14 @@ void wasm::Linker::ConstructJob(Compilation &C, const 
JobAction &JA,
   ArgStringList CmdArgs;
 
   CmdArgs.push_back("-m");
+  std::string arch;
   if (ToolChain.getTriple().isArch64Bit())
-    CmdArgs.push_back("wasm64");
+    arch = "wasm64";
   else
-    CmdArgs.push_back("wasm32");
+    arch = "wasm32";
+  if (ToolChain.getTriple().getOSName() == "wasip3")
+    arch += "-wasip3";
+  CmdArgs.push_back(Args.MakeArgString(arch));
 
   if (Args.hasArg(options::OPT_s))
     CmdArgs.push_back("--strip-all");
@@ -160,7 +169,7 @@ void wasm::Linker::ConstructJob(Compilation &C, const 
JobAction &JA,
 
   AddLinkerInputs(ToolChain, Inputs, Args, CmdArgs, JA);
 
-  if (WantsPthread(ToolChain.getTriple(), Args))
+  if (WantsSharedMemory(ToolChain.getTriple(), Args))
     CmdArgs.push_back("--shared-memory");
 
   if (!Args.hasArg(options::OPT_nostdlib, options::OPT_nodefaultlibs)) {
@@ -233,9 +242,9 @@ void wasm::Linker::ConstructJob(Compilation &C, const 
JobAction &JA,
 /// Given a base library directory, append path components to form the
 /// LTO directory.
 static std::string AppendLTOLibDir(const std::string &Dir) {
-    // The version allows the path to be keyed to the specific version of
-    // LLVM in used, as the bitcode format is not stable.
-    return Dir + "/llvm-lto/" LLVM_VERSION_STRING;
+  // The version allows the path to be keyed to the specific version of
+  // LLVM in used, as the bitcode format is not stable.
+  return Dir + "/llvm-lto/" LLVM_VERSION_STRING;
 }
 
 WebAssembly::WebAssembly(const Driver &D, const llvm::Triple &Triple,
@@ -502,7 +511,8 @@ void WebAssembly::AddClangSystemIncludeArgs(const ArgList 
&DriverArgs,
   if (getTriple().getOS() != llvm::Triple::UnknownOS) {
     const std::string MultiarchTriple =
         getMultiarchTriple(D, getTriple(), D.SysRoot);
-    addSystemInclude(DriverArgs, CC1Args, D.SysRoot + "/include/" + 
MultiarchTriple);
+    addSystemInclude(DriverArgs, CC1Args,
+                     D.SysRoot + "/include/" + MultiarchTriple);
   }
   addSystemInclude(DriverArgs, CC1Args, D.SysRoot + "/include");
 }
@@ -631,5 +641,6 @@ void WebAssembly::addLibStdCXXIncludePaths(
   // Second add the generic one.
   addSystemInclude(DriverArgs, CC1Args, LibPath + "/c++/" + Version);
   // Third the backward one.
-  addSystemInclude(DriverArgs, CC1Args, LibPath + "/c++/" + Version + 
"/backward");
+  addSystemInclude(DriverArgs, CC1Args,
+                   LibPath + "/c++/" + Version + "/backward");
 }
diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index 9d903e0c799db..55dea0a78eadb 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -35,6 +35,7 @@ class Symbol;
 class DefinedData;
 class GlobalSymbol;
 class DefinedFunction;
+class UndefinedFunction;
 class UndefinedGlobal;
 class TableSymbol;
 
@@ -70,6 +71,7 @@ struct Config {
   bool importTable;
   bool importUndefined;
   std::optional<bool> is64;
+  bool isWasip3;
   bool mergeDataSegments;
   bool noinhibitExec;
   bool pie;
@@ -248,6 +250,14 @@ struct Ctx {
     // Used as an address space for function pointers, with each function that
     // is used as a function pointer being allocated a slot.
     TableSymbol *indirectFunctionTable;
+
+    // __wasm_component_model_builtin_context_set_1
+    // Function used to set TLS base in component model modules.
+    UndefinedFunction *contextSet1;
+
+    // __wasm_component_model_builtin_context_get_1
+    // Function used to get TLS base in component model modules.
+    UndefinedFunction *contextGet1;
   };
   WasmSym sym;
 
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index fac166587cb9b..79ecc6c9f5858 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -656,15 +656,16 @@ static void readConfigs(opt::InputArgList &args) {
   ctx.arg.exportDynamic =
       args.hasFlag(OPT_export_dynamic, OPT_no_export_dynamic, ctx.arg.shared);
 
-  // Parse wasm32/64.
+  // Parse wasm32/64 and maybe -wasip3.
   if (auto *arg = args.getLastArg(OPT_m)) {
     StringRef s = arg->getValue();
-    if (s == "wasm32")
+    if (s.starts_with("wasm32"))
       ctx.arg.is64 = false;
-    else if (s == "wasm64")
+    else if (s.starts_with("wasm64"))
       ctx.arg.is64 = true;
     else
       error("invalid target architecture: " + s);
+    ctx.arg.isWasip3 = s.ends_with("-wasip3");
   }
 
   // --threads= takes a positive integer and provides the default value for
@@ -827,6 +828,10 @@ static void checkOptions(opt::InputArgList &args) {
     if (ctx.arg.tableBase)
       error("--table-base may not be used with -shared/-pie");
   }
+
+  if (ctx.arg.sharedMemory && ctx.arg.isWasip3) {
+    error("--shared-memory is incompatible with the wasip3 target");
+  }
 }
 
 static const char *getReproduceOption(opt::InputArgList &args) {
@@ -885,7 +890,7 @@ static void writeWhyExtract() {
 // Equivalent of demote demoteSharedAndLazySymbols() in the ELF linker
 static void demoteLazySymbols() {
   for (Symbol *sym : symtab->symbols()) {
-    if (auto* s = dyn_cast<LazySymbol>(sym)) {
+    if (auto *s = dyn_cast<LazySymbol>(sym)) {
       if (s->signature) {
         LLVM_DEBUG(llvm::dbgs()
                    << "demoting lazy func: " << s->getName() << "\n");
@@ -906,6 +911,18 @@ createUndefinedGlobal(StringRef name, 
llvm::wasm::WasmGlobalType *type) {
   return sym;
 }
 
+static UndefinedFunction *
+createUndefinedFunction(StringRef name, std::optional<StringRef> importName,
+                        std::optional<StringRef> importModule,
+                        WasmSignature *signature) {
+  auto *sym = cast<UndefinedFunction>(symtab->addUndefinedFunction(
+      name, importName, importModule, WASM_SYMBOL_UNDEFINED, nullptr, 
signature,
+      true));
+  ctx.arg.allowUndefinedSymbols.insert(sym->getName());
+  sym->isUsedInRegularObj = true;
+  return sym;
+}
+
 static InputGlobal *createGlobal(StringRef name, bool isMutable) {
   llvm::wasm::WasmGlobal wasmGlobal;
   bool is64 = ctx.arg.is64.value_or(false);
@@ -946,11 +963,13 @@ static void createSyntheticSymbols() {
 
   bool is64 = ctx.arg.is64.value_or(false);
 
+  auto stack_pointer_name =
+      ctx.arg.isWasip3 ? "__init_stack_pointer" : "__stack_pointer";
   if (ctx.isPic) {
     ctx.sym.stackPointer =
-        createUndefinedGlobal("__stack_pointer", ctx.arg.is64.value_or(false)
-                                                     ? &mutableGlobalTypeI64
-                                                     : &mutableGlobalTypeI32);
+        createUndefinedGlobal(stack_pointer_name, ctx.arg.is64.value_or(false)
+                                                      ? &mutableGlobalTypeI64
+                                                      : &mutableGlobalTypeI32);
     // For PIC code, we import two global variables (__memory_base and
     // __table_base) from the environment and use these as the offset at
     // which to load our static data and function table.
@@ -963,14 +982,15 @@ static void createSyntheticSymbols() {
     ctx.sym.tableBase->markLive();
   } else {
     // For non-PIC code
-    ctx.sym.stackPointer = createGlobalVariable("__stack_pointer", true);
+    ctx.sym.stackPointer = createGlobalVariable(stack_pointer_name, true);
     ctx.sym.stackPointer->markLive();
   }
 
-  if (ctx.arg.sharedMemory) {
+  if (ctx.arg.sharedMemory || ctx.arg.isWasip3) {
     // TLS symbols are all hidden/dso-local
-    ctx.sym.tlsBase =
-        createGlobalVariable("__tls_base", true, 
WASM_SYMBOL_VISIBILITY_HIDDEN);
+    auto tls_base_name = ctx.arg.isWasip3 ? "__init_tls_base" : "__tls_base";
+    ctx.sym.tlsBase = createGlobalVariable(tls_base_name, true,
+                                           WASM_SYMBOL_VISIBILITY_HIDDEN);
     ctx.sym.tlsSize = createGlobalVariable("__tls_size", false,
                                            WASM_SYMBOL_VISIBILITY_HIDDEN);
     ctx.sym.tlsAlign = createGlobalVariable("__tls_align", false,
@@ -979,6 +999,16 @@ static void createSyntheticSymbols() {
         "__wasm_init_tls", WASM_SYMBOL_VISIBILITY_HIDDEN,
         make<SyntheticFunction>(is64 ? i64ArgSignature : i32ArgSignature,
                                 "__wasm_init_tls"));
+    static WasmSignature contextSet1Signature{{}, {ValType::I32}};
+    ctx.sym.contextSet1 = createUndefinedFunction(
+        "__wasm_component_model_builtin_context_set_1", "[context-set-1]",
+        "$root", &contextSet1Signature);
+    ctx.sym.contextSet1->markLive();
+    static WasmSignature contextGet1Signature{{ValType::I32}, {}};
+    ctx.sym.contextGet1 = createUndefinedFunction(
+        "__wasm_component_model_builtin_context_get_1", "[context-get-1]",
+        "$root", &contextGet1Signature);
+    ctx.sym.contextGet1->markLive();
   }
 }
 
@@ -1014,7 +1044,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.arg.sharedMemory && !ctx.arg.isWasip3)
     ctx.sym.tlsBase = createOptionalGlobal("__tls_base", false);
 }
 
@@ -1023,15 +1053,15 @@ static void processStubLibrariesPreLTO() {
   for (auto &stub_file : ctx.stubFiles) {
     LLVM_DEBUG(llvm::dbgs()
                << "processing stub file: " << stub_file->getName() << "\n");
-    for (auto [name, deps]: stub_file->symbolDependencies) {
-      auto* sym = symtab->find(name);
+    for (auto [name, deps] : stub_file->symbolDependencies) {
+      auto *sym = symtab->find(name);
       // If the symbol is not present at all (yet), or if it is present but
       // undefined, then mark the dependent symbols as used by a regular
       // object so they will be preserved and exported by the LTO process.
       if (!sym || sym->isUndefined()) {
         for (const auto dep : deps) {
-          auto* needed = symtab->find(dep);
-          if (needed ) {
+          auto *needed = symtab->find(dep);
+          if (needed) {
             needed->isUsedInRegularObj = true;
             // Like with handleLibcall we have to extract any LTO archive
             // members that might need to be exported due to stub library
@@ -1103,8 +1133,8 @@ static void processStubLibraries() {
 
       // First look for any imported symbols that directly match
       // the names of the stub imports
-      for (auto [name, deps]: stub_file->symbolDependencies) {
-        auto* sym = symtab->find(name);
+      for (auto [name, deps] : stub_file->symbolDependencies) {
+        auto *sym = symtab->find(name);
         if (sym && sym->isUndefined()) {
           depsAdded |= addStubSymbolDeps(stub_file, sym, deps);
         } else {
diff --git a/lld/wasm/Symbols.cpp b/lld/wasm/Symbols.cpp
index f2040441e6257..97a9871a06308 100644
--- a/lld/wasm/Symbols.cpp
+++ b/lld/wasm/Symbols.cpp
@@ -95,7 +95,7 @@ WasmSymbolType Symbol::getWasmType() const {
 }
 
 const WasmSignature *Symbol::getSignature() const {
-  if (auto* f = dyn_cast<FunctionSymbol>(this))
+  if (auto *f = dyn_cast<FunctionSymbol>(this))
     return f->signature;
   if (auto *t = dyn_cast<TagSymbol>(this))
     return t->signature;
@@ -223,9 +223,7 @@ bool Symbol::isExportedExplicit() const {
   return forceExport || flags & WASM_SYMBOL_EXPORTED;
 }
 
-bool Symbol::isNoStrip() const {
-  return flags & WASM_SYMBOL_NO_STRIP;
-}
+bool Symbol::isNoStrip() const { return flags & WASM_SYMBOL_NO_STRIP; }
 
 uint32_t FunctionSymbol::getFunctionIndex() const {
   if (const auto *u = dyn_cast<UndefinedFunction>(this))
@@ -413,7 +411,7 @@ void LazySymbol::setWeak() {
   flags |= (flags & ~WASM_SYMBOL_BINDING_MASK) | WASM_SYMBOL_BINDING_WEAK;
 }
 
-void printTraceSymbolUndefined(StringRef name, const InputFile* file) {
+void printTraceSymbolUndefined(StringRef name, const InputFile *file) {
   message(toString(file) + ": reference to " + name);
 }
 
diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp
index 399a5084e6595..0d3e060ac381e 100644
--- a/lld/wasm/SyntheticSections.cpp
+++ b/lld/wasm/SyntheticSections.cpp
@@ -434,8 +434,7 @@ void GlobalSection::addInternalGOTEntry(Symbol *sym) {
 void GlobalSection::generateRelocationCode(raw_ostream &os, bool TLS) const {
   assert(!ctx.arg.extendedConst);
   bool is64 = ctx.arg.is64.value_or(false);
-  unsigned opcode_ptr_add = is64 ? WASM_OPCODE_I64_ADD
-                                 : WASM_OPCODE_I32_ADD;
+  unsigned opcode_ptr_add = is64 ? WASM_OPCODE_I64_ADD : WASM_OPCODE_I32_ADD;
 
   for (const Symbol *sym : internalGotSymbols) {
     if (TLS != sym->isTLS())
@@ -445,7 +444,7 @@ void GlobalSection::generateRelocationCode(raw_ostream &os, 
bool TLS) const {
       // Get __memory_base
       writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
       if (sym->isTLS())
-        writeUleb128(os, ctx.sym.tlsBase->getGlobalIndex(), "__tls_base");
+        writeGetTLSBase(ctx, os);
       else
         writeUleb128(os, ctx.sym.memoryBase->getGlobalIndex(), 
"__memory_base");
 
@@ -614,7 +613,7 @@ void ElemSection::writeBody() {
   uint32_t tableIndex = ctx.arg.tableBase;
   for (const FunctionSymbol *sym : indirectFunctions) {
     assert(sym->getTableIndex() == tableIndex);
-    (void) tableIndex;
+    (void)tableIndex;
     writeUleb128(os, sym->getFunctionIndex(), "function index");
     ++tableIndex;
   }
@@ -631,7 +630,7 @@ void DataCountSection::writeBody() {
 }
 
 bool DataCountSection::isNeeded() const {
-  return numSegments && ctx.arg.sharedMemory;
+  return numSegments && (ctx.arg.sharedMemory || ctx.arg.isWasip3);
 }
 
 void LinkingSection::writeBody() {
@@ -960,4 +959,4 @@ void BuildIdSection::writeBuildId(llvm::ArrayRef<uint8_t> 
buf) {
   memcpy(hashPlaceholderPtr, buf.data(), hashSize);
 }
 
-} // namespace wasm::lld
+} // namespace lld::wasm
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 9a5b56fc52e2f..12f72dc239a09 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -311,7 +311,8 @@ void Writer::writeBuildId() {
 }
 
 static void setGlobalPtr(DefinedGlobal *g, uint64_t memoryPtr) {
-  LLVM_DEBUG(dbgs() << "setGlobalPtr " << g->getName() << " -> " << memoryPtr 
<< "\n");
+  LLVM_DEBUG(dbgs() << "setGlobalPtr " << g->getName() << " -> " << memoryPtr
+                    << "\n");
   g->global->setPointerValue(memoryPtr);
 }
 
@@ -358,7 +359,8 @@ void Writer::layoutMemory() {
     placeStack();
     if (ctx.arg.globalBase) {
       if (ctx.arg.globalBase < memoryPtr) {
-        error("--global-base cannot be less than stack size when --stack-first 
is used");
+        error("--global-base cannot be less than stack size when --stack-first 
"
+              "is used");
         return;
       }
       memoryPtr = ctx.arg.globalBase;
@@ -382,6 +384,7 @@ void Writer::layoutMemory() {
   for (OutputSegment *seg : segments) {
     out.dylinkSec->memAlign = std::max(out.dylinkSec->memAlign, 
seg->alignment);
     memoryPtr = alignTo(memoryPtr, 1ULL << seg->alignment);
+
     seg->startVA = memoryPtr;
     log(formatv("mem: {0,-15} offset={1,-8} size={2,-8} align={3}", seg->name,
                 memoryPtr, seg->size, seg->alignment));
@@ -1175,7 +1178,7 @@ void Writer::createSyntheticInitFunctions() {
 
     auto hasTLSRelocs = [](const OutputSegment *segment) {
       if (segment->isTLS())
-        for (const auto* is: segment->inputSegments)
+        for (const auto *is : segment->inputSegments)
           if (is->getRelocations().size())
             return true;
       return false;
@@ -1350,8 +1353,7 @@ void Writer::createInitMemoryFunction() {
           } else {
             writePtrConst(os, s->startVA, is64, "destination address");
           }
-          writeU8(os, WASM_OPCODE_GLOBAL_SET, "GLOBAL_SET");
-          writeUleb128(os, ctx.sym.tlsBase->getGlobalIndex(), "__tls_base");
+          writeSetTLSBase(ctx, os);
           if (ctx.isPic) {
             writeU8(os, WASM_OPCODE_LOCAL_GET, "local.tee");
             writeUleb128(os, 1, "local 1");
@@ -1624,10 +1626,17 @@ void Writer::createInitTLSFunction() {
       writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get");
       writeUleb128(os, 0, "local index");
 
-      writeU8(os, WASM_OPCODE_GLOBAL_SET, "global.set");
-      writeUleb128(os, ctx.sym.tlsBase->getGlobalIndex(), "global index");
+      if (ctx.arg.isWasip3) {
+        writeU8(os, WASM_OPCODE_CALL, "call");
+        writeUleb128(os, ctx.sym.contextSet1->getFunctionIndex(),
+                     "function index");
+      } else {
+        writeU8(os, WASM_OPCODE_GLOBAL_SET, "global.set");
+        writeUleb128(os, ctx.sym.tlsBase->getGlobalIndex(), "global index");
+      }
 
-      // FIXME(wvo): this local needs to be I64 in wasm64, or we need an 
extend op.
+      // FIXME(wvo): this local needs to be I64 in wasm64, or we need an
+      // extend op.
       writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get");
       writeUleb128(os, 0, "local index");
 
@@ -1893,4 +1902,4 @@ void Writer::createHeader() {
 
 void writeResult() { Writer().run(); }
 
-} // namespace wasm::lld
+} // namespace lld::wasm
diff --git a/lld/wasm/WriterUtils.cpp b/lld/wasm/WriterUtils.cpp
index b78c354ffb97b..8e945f57ef39c 100644
--- a/lld/wasm/WriterUtils.cpp
+++ b/lld/wasm/WriterUtils.cpp
@@ -7,6 +7,8 @@
 
//===----------------------------------------------------------------------===//
 
 #include "WriterUtils.h"
+#include "Config.h"
+#include "Symbols.h"
 #include "lld/Common/ErrorHandler.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/Debug.h"
@@ -97,7 +99,7 @@ void writeSleb128(raw_ostream &os, int64_t number, const 
Twine &msg) {
 }
 
 void writeBytes(raw_ostream &os, const char *bytes, size_t count,
-                      const Twine &msg) {
+                const Twine &msg) {
   debugWrite(os.tell(), msg + " [data[" + Twine(count) + "]]");
   os.write(bytes, count);
 }
@@ -266,5 +268,25 @@ void writeExport(raw_ostream &os, const WasmExport 
&export_) {
   }
 }
 
+void writeGetTLSBase(const Ctx &ctx, raw_ostream &os) {
+  if (ctx.arg.isWasip3) {
+    writeU8(os, WASM_OPCODE_CALL, "call");
+    writeUleb128(os, ctx.sym.contextGet1->getFunctionIndex(), "function 
index");
+  } else {
+    writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_SET");
+    writeUleb128(os, ctx.sym.tlsBase->getGlobalIndex(), "__tls_base");
+  }
+}
+
+void writeSetTLSBase(const Ctx &ctx, raw_ostream &os) {
+  if (ctx.arg.isWasip3) {
+    writeU8(os, WASM_OPCODE_CALL, "call");
+    writeUleb128(os, ctx.sym.contextSet1->getFunctionIndex(), "function 
index");
+  } else {
+    writeU8(os, WASM_OPCODE_GLOBAL_SET, "GLOBAL_SET");
+    writeUleb128(os, ctx.sym.tlsBase->getGlobalIndex(), "__tls_base");
+  }
+}
+
 } // namespace wasm
 } // namespace lld
diff --git a/lld/wasm/WriterUtils.h b/lld/wasm/WriterUtils.h
index 2be79d1d86e97..404c5c33ed7b6 100644
--- a/lld/wasm/WriterUtils.h
+++ b/lld/wasm/WriterUtils.h
@@ -64,6 +64,10 @@ void writeImport(raw_ostream &os, const 
llvm::wasm::WasmImport &import);
 
 void writeExport(raw_ostream &os, const llvm::wasm::WasmExport &export_);
 
+struct Ctx;
+void writeGetTLSBase(const Ctx &ctx, raw_ostream &os);
+void writeSetTLSBase(const Ctx &ctx, raw_ostream &os);
+
 } // namespace wasm
 
 std::string toString(llvm::wasm::ValType type);
diff --git a/llvm/include/llvm/MC/MCSymbolWasm.h 
b/llvm/include/llvm/MC/MCSymbolWasm.h
index 5c9f14e5e6d64..11cbce5b0ccd0 100644
--- a/llvm/include/llvm/MC/MCSymbolWasm.h
+++ b/llvm/include/llvm/MC/MCSymbolWasm.h
@@ -54,16 +54,12 @@ class MCSymbolWasm : public MCSymbol {
 
   void setType(wasm::WasmSymbolType type) { Type = type; }
 
-  bool isExported() const {
-    return getFlags() & wasm::WASM_SYMBOL_EXPORTED;
-  }
+  bool isExported() const { return getFlags() & wasm::WASM_SYMBOL_EXPORTED; }
   void setExported() const {
     modifyFlags(wasm::WASM_SYMBOL_EXPORTED, wasm::WASM_SYMBOL_EXPORTED);
   }
 
-  bool isNoStrip() const {
-    return getFlags() & wasm::WASM_SYMBOL_NO_STRIP;
-  }
+  bool isNoStrip() const { return getFlags() & wasm::WASM_SYMBOL_NO_STRIP; }
   void setNoStrip() const {
     modifyFlags(wasm::WASM_SYMBOL_NO_STRIP, wasm::WASM_SYMBOL_NO_STRIP);
   }
diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp 
b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
index 9175b2731dac0..25704c9ef0ac4 100644
--- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
+++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
@@ -415,6 +415,23 @@ class WebAssemblyAsmParser final : public 
MCTargetAsmParser {
     return Name;
   }
 
+  StringRef expectStringOrIdent() {
+    if (Lexer.is(AsmToken::String)) {
+      auto Str = Lexer.getTok().getString();
+      Parser.Lex();
+      Str.consume_front("\"");
+      Str.consume_back("\"");
+      return Str;
+    }
+    if (Lexer.is(AsmToken::Identifier)) {
+      auto Name = Lexer.getTok().getString();
+      Parser.Lex();
+      llvm::outs() << "Parsed ident: " << Name << "\n";
+      return Name;
+    }
+    error("Expected string or identifier, got: ", Lexer.getTok());
+  }
+
   bool parseRegTypeList(SmallVectorImpl<wasm::ValType> &Types) {
     while (Lexer.is(AsmToken::Identifier)) {
       auto Type = WebAssembly::parseType(Lexer.getTok().getString());
@@ -1057,7 +1074,8 @@ class WebAssemblyAsmParser final : public 
MCTargetAsmParser {
         return ParseStatus::Failure;
       if (expect(AsmToken::Comma, ","))
         return ParseStatus::Failure;
-      auto ImportModule = expectIdent();
+
+      StringRef ImportModule = expectStringOrIdent();
       if (ImportModule.empty())
         return ParseStatus::Failure;
       auto *WasmSym =
@@ -1073,7 +1091,7 @@ class WebAssemblyAsmParser final : public 
MCTargetAsmParser {
         return ParseStatus::Failure;
       if (expect(AsmToken::Comma, ","))
         return ParseStatus::Failure;
-      auto ImportName = expectIdent();
+      StringRef ImportName = expectStringOrIdent();
       if (ImportName.empty())
         return ParseStatus::Failure;
       auto *WasmSym =
diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h 
b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h
index 5dc0e3aa91622..5c6d07ba88c61 100644
--- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h
+++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h
@@ -146,128 +146,128 @@ static const unsigned End = 0x0b;
 /// Return the default p2align value for a load or store with the given opcode.
 inline unsigned GetDefaultP2AlignAny(unsigned Opc) {
   switch (Opc) {
-#define WASM_LOAD_STORE(NAME) \
-  case WebAssembly::NAME##_A32: \
-  case WebAssembly::NAME##_A64: \
-  case WebAssembly::NAME##_A32_S: \
+#define WASM_LOAD_STORE(NAME)                                                  
\
+  case WebAssembly::NAME##_A32:                                                
\
+  case WebAssembly::NAME##_A64:                                                
\
+  case WebAssembly::NAME##_A32_S:                                              
\
   case WebAssembly::NAME##_A64_S:
-  WASM_LOAD_STORE(LOAD8_S_I32)
-  WASM_LOAD_STORE(LOAD8_U_I32)
-  WASM_LOAD_STORE(LOAD8_S_I64)
-  WASM_LOAD_STORE(LOAD8_U_I64)
-  WASM_LOAD_STORE(ATOMIC_LOAD8_U_I32)
-  WASM_LOAD_STORE(ATOMIC_LOAD8_U_I64)
-  WASM_LOAD_STORE(STORE8_I32)
-  WASM_LOAD_STORE(STORE8_I64)
-  WASM_LOAD_STORE(ATOMIC_STORE8_I32)
-  WASM_LOAD_STORE(ATOMIC_STORE8_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW8_U_ADD_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW8_U_ADD_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW8_U_SUB_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW8_U_SUB_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW8_U_AND_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW8_U_AND_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW8_U_OR_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW8_U_OR_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW8_U_XOR_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW8_U_XOR_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW8_U_XCHG_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW8_U_XCHG_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW8_U_CMPXCHG_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW8_U_CMPXCHG_I64)
-  WASM_LOAD_STORE(LOAD8_SPLAT)
-  WASM_LOAD_STORE(LOAD_LANE_8)
-  WASM_LOAD_STORE(STORE_LANE_I8x16)
-  return 0;
-  WASM_LOAD_STORE(LOAD16_S_I32)
-  WASM_LOAD_STORE(LOAD16_U_I32)
-  WASM_LOAD_STORE(LOAD16_S_I64)
-  WASM_LOAD_STORE(LOAD16_U_I64)
-  WASM_LOAD_STORE(ATOMIC_LOAD16_U_I32)
-  WASM_LOAD_STORE(ATOMIC_LOAD16_U_I64)
-  WASM_LOAD_STORE(STORE16_I32)
-  WASM_LOAD_STORE(STORE16_I64)
-  WASM_LOAD_STORE(ATOMIC_STORE16_I32)
-  WASM_LOAD_STORE(ATOMIC_STORE16_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW16_U_ADD_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW16_U_ADD_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW16_U_SUB_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW16_U_SUB_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW16_U_AND_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW16_U_AND_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW16_U_OR_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW16_U_OR_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW16_U_XOR_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW16_U_XOR_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW16_U_XCHG_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW16_U_XCHG_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW16_U_CMPXCHG_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW16_U_CMPXCHG_I64)
-  WASM_LOAD_STORE(LOAD16_SPLAT)
-  WASM_LOAD_STORE(LOAD_LANE_16)
-  WASM_LOAD_STORE(STORE_LANE_I16x8)
-  WASM_LOAD_STORE(LOAD_F16_F32)
-  WASM_LOAD_STORE(STORE_F16_F32)
-  return 1;
-  WASM_LOAD_STORE(LOAD_I32)
-  WASM_LOAD_STORE(LOAD_F32)
-  WASM_LOAD_STORE(STORE_I32)
-  WASM_LOAD_STORE(STORE_F32)
-  WASM_LOAD_STORE(LOAD32_S_I64)
-  WASM_LOAD_STORE(LOAD32_U_I64)
-  WASM_LOAD_STORE(STORE32_I64)
-  WASM_LOAD_STORE(ATOMIC_LOAD_I32)
-  WASM_LOAD_STORE(ATOMIC_LOAD32_U_I64)
-  WASM_LOAD_STORE(ATOMIC_STORE_I32)
-  WASM_LOAD_STORE(ATOMIC_STORE32_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW_ADD_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW32_U_ADD_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW_SUB_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW32_U_SUB_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW_AND_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW32_U_AND_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW_OR_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW32_U_OR_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW_XOR_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW32_U_XOR_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW_XCHG_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW32_U_XCHG_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW_CMPXCHG_I32)
-  WASM_LOAD_STORE(ATOMIC_RMW32_U_CMPXCHG_I64)
-  WASM_LOAD_STORE(MEMORY_ATOMIC_NOTIFY)
-  WASM_LOAD_STORE(MEMORY_ATOMIC_WAIT32)
-  WASM_LOAD_STORE(LOAD32_SPLAT)
-  WASM_LOAD_STORE(LOAD_ZERO_32)
-  WASM_LOAD_STORE(LOAD_LANE_32)
-  WASM_LOAD_STORE(STORE_LANE_I32x4)
-  return 2;
-  WASM_LOAD_STORE(LOAD_I64)
-  WASM_LOAD_STORE(LOAD_F64)
-  WASM_LOAD_STORE(STORE_I64)
-  WASM_LOAD_STORE(STORE_F64)
-  WASM_LOAD_STORE(ATOMIC_LOAD_I64)
-  WASM_LOAD_STORE(ATOMIC_STORE_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW_ADD_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW_SUB_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW_AND_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW_OR_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW_XOR_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW_XCHG_I64)
-  WASM_LOAD_STORE(ATOMIC_RMW_CMPXCHG_I64)
-  WASM_LOAD_STORE(MEMORY_ATOMIC_WAIT64)
-  WASM_LOAD_STORE(LOAD64_SPLAT)
-  WASM_LOAD_STORE(LOAD_EXTEND_S_I16x8)
-  WASM_LOAD_STORE(LOAD_EXTEND_U_I16x8)
-  WASM_LOAD_STORE(LOAD_EXTEND_S_I32x4)
-  WASM_LOAD_STORE(LOAD_EXTEND_U_I32x4)
-  WASM_LOAD_STORE(LOAD_EXTEND_S_I64x2)
-  WASM_LOAD_STORE(LOAD_EXTEND_U_I64x2)
-  WASM_LOAD_STORE(LOAD_ZERO_64)
-  WASM_LOAD_STORE(LOAD_LANE_64)
-  WASM_LOAD_STORE(STORE_LANE_I64x2)
-  return 3;
-  WASM_LOAD_STORE(LOAD_V128)
-  WASM_LOAD_STORE(STORE_V128)
+    WASM_LOAD_STORE(LOAD8_S_I32)
+    WASM_LOAD_STORE(LOAD8_U_I32)
+    WASM_LOAD_STORE(LOAD8_S_I64)
+    WASM_LOAD_STORE(LOAD8_U_I64)
+    WASM_LOAD_STORE(ATOMIC_LOAD8_U_I32)
+    WASM_LOAD_STORE(ATOMIC_LOAD8_U_I64)
+    WASM_LOAD_STORE(STORE8_I32)
+    WASM_LOAD_STORE(STORE8_I64)
+    WASM_LOAD_STORE(ATOMIC_STORE8_I32)
+    WASM_LOAD_STORE(ATOMIC_STORE8_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW8_U_ADD_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW8_U_ADD_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW8_U_SUB_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW8_U_SUB_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW8_U_AND_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW8_U_AND_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW8_U_OR_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW8_U_OR_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW8_U_XOR_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW8_U_XOR_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW8_U_XCHG_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW8_U_XCHG_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW8_U_CMPXCHG_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW8_U_CMPXCHG_I64)
+    WASM_LOAD_STORE(LOAD8_SPLAT)
+    WASM_LOAD_STORE(LOAD_LANE_8)
+    WASM_LOAD_STORE(STORE_LANE_I8x16)
+    return 0;
+    WASM_LOAD_STORE(LOAD16_S_I32)
+    WASM_LOAD_STORE(LOAD16_U_I32)
+    WASM_LOAD_STORE(LOAD16_S_I64)
+    WASM_LOAD_STORE(LOAD16_U_I64)
+    WASM_LOAD_STORE(ATOMIC_LOAD16_U_I32)
+    WASM_LOAD_STORE(ATOMIC_LOAD16_U_I64)
+    WASM_LOAD_STORE(STORE16_I32)
+    WASM_LOAD_STORE(STORE16_I64)
+    WASM_LOAD_STORE(ATOMIC_STORE16_I32)
+    WASM_LOAD_STORE(ATOMIC_STORE16_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW16_U_ADD_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW16_U_ADD_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW16_U_SUB_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW16_U_SUB_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW16_U_AND_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW16_U_AND_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW16_U_OR_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW16_U_OR_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW16_U_XOR_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW16_U_XOR_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW16_U_XCHG_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW16_U_XCHG_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW16_U_CMPXCHG_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW16_U_CMPXCHG_I64)
+    WASM_LOAD_STORE(LOAD16_SPLAT)
+    WASM_LOAD_STORE(LOAD_LANE_16)
+    WASM_LOAD_STORE(STORE_LANE_I16x8)
+    WASM_LOAD_STORE(LOAD_F16_F32)
+    WASM_LOAD_STORE(STORE_F16_F32)
+    return 1;
+    WASM_LOAD_STORE(LOAD_I32)
+    WASM_LOAD_STORE(LOAD_F32)
+    WASM_LOAD_STORE(STORE_I32)
+    WASM_LOAD_STORE(STORE_F32)
+    WASM_LOAD_STORE(LOAD32_S_I64)
+    WASM_LOAD_STORE(LOAD32_U_I64)
+    WASM_LOAD_STORE(STORE32_I64)
+    WASM_LOAD_STORE(ATOMIC_LOAD_I32)
+    WASM_LOAD_STORE(ATOMIC_LOAD32_U_I64)
+    WASM_LOAD_STORE(ATOMIC_STORE_I32)
+    WASM_LOAD_STORE(ATOMIC_STORE32_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW_ADD_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW32_U_ADD_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW_SUB_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW32_U_SUB_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW_AND_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW32_U_AND_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW_OR_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW32_U_OR_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW_XOR_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW32_U_XOR_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW_XCHG_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW32_U_XCHG_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW_CMPXCHG_I32)
+    WASM_LOAD_STORE(ATOMIC_RMW32_U_CMPXCHG_I64)
+    WASM_LOAD_STORE(MEMORY_ATOMIC_NOTIFY)
+    WASM_LOAD_STORE(MEMORY_ATOMIC_WAIT32)
+    WASM_LOAD_STORE(LOAD32_SPLAT)
+    WASM_LOAD_STORE(LOAD_ZERO_32)
+    WASM_LOAD_STORE(LOAD_LANE_32)
+    WASM_LOAD_STORE(STORE_LANE_I32x4)
+    return 2;
+    WASM_LOAD_STORE(LOAD_I64)
+    WASM_LOAD_STORE(LOAD_F64)
+    WASM_LOAD_STORE(STORE_I64)
+    WASM_LOAD_STORE(STORE_F64)
+    WASM_LOAD_STORE(ATOMIC_LOAD_I64)
+    WASM_LOAD_STORE(ATOMIC_STORE_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW_ADD_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW_SUB_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW_AND_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW_OR_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW_XOR_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW_XCHG_I64)
+    WASM_LOAD_STORE(ATOMIC_RMW_CMPXCHG_I64)
+    WASM_LOAD_STORE(MEMORY_ATOMIC_WAIT64)
+    WASM_LOAD_STORE(LOAD64_SPLAT)
+    WASM_LOAD_STORE(LOAD_EXTEND_S_I16x8)
+    WASM_LOAD_STORE(LOAD_EXTEND_U_I16x8)
+    WASM_LOAD_STORE(LOAD_EXTEND_S_I32x4)
+    WASM_LOAD_STORE(LOAD_EXTEND_U_I32x4)
+    WASM_LOAD_STORE(LOAD_EXTEND_S_I64x2)
+    WASM_LOAD_STORE(LOAD_EXTEND_U_I64x2)
+    WASM_LOAD_STORE(LOAD_ZERO_64)
+    WASM_LOAD_STORE(LOAD_LANE_64)
+    WASM_LOAD_STORE(STORE_LANE_I64x2)
+    return 3;
+    WASM_LOAD_STORE(LOAD_V128)
+    WASM_LOAD_STORE(STORE_V128)
     return 4;
   default:
     return -1;
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
index 526420bb2b294..ce17ee65c45eb 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
@@ -227,11 +227,11 @@ MCSymbol 
*WebAssemblyAsmPrinter::getOrCreateWasmSymbol(StringRef Name) {
   // functions. It's OK to hardcode knowledge of specific symbols here; this
   // method is precisely there for fetching the signatures of known
   // Clang-provided symbols.
-  if (Name == "__stack_pointer" || Name == "__tls_base" ||
+  if (Name == "__stack_pointer" || Name == "__init_stack_pointer" ||
+      Name == "__tls_base" || Name == "__init_tls_base" ||
       Name == "__memory_base" || Name == "__table_base" ||
       Name == "__tls_size" || Name == "__tls_align") {
-    bool Mutable =
-        Name == "__stack_pointer" || Name == "__tls_base";
+    bool Mutable = Name == "__stack_pointer" || Name == "__tls_base";
     WasmSym->setType(wasm::WASM_SYMBOL_TYPE_GLOBAL);
     WasmSym->setGlobalType(wasm::WasmGlobalType{
         uint8_t(Subtarget.hasAddr64() ? wasm::WASM_TYPE_I64
@@ -259,6 +259,26 @@ MCSymbol 
*WebAssemblyAsmPrinter::getOrCreateWasmSymbol(StringRef Name) {
     wasm::ValType AddrType =
         Subtarget.hasAddr64() ? wasm::ValType::I64 : wasm::ValType::I32;
     Params.push_back(AddrType);
+  } else if (Name == "__wasm_component_model_builtin_context_get_0") {
+    WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION);
+    WasmSym->setImportModule("$root");
+    WasmSym->setImportName("[context-get-0]");
+    Returns.push_back(wasm::ValType::I32);
+  } else if (Name == "__wasm_component_model_builtin_context_set_0") {
+    WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION);
+    WasmSym->setImportModule("$root");
+    WasmSym->setImportName("[context-set-0]");
+    Params.push_back(wasm::ValType::I32);
+  } else if (Name == "__wasm_component_model_builtin_context_get_1") {
+    WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION);
+    WasmSym->setImportModule("$root");
+    WasmSym->setImportName("[context-get-1]");
+    Returns.push_back(wasm::ValType::I32);
+  } else if (Name == "__wasm_component_model_builtin_context_set_1") {
+    WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION);
+    WasmSym->setImportModule("$root");
+    WasmSym->setImportName("[context-set-1]");
+    Params.push_back(wasm::ValType::I32);
   } else { // Function symbols
     WasmSym->setType(wasm::WASM_SYMBOL_TYPE_FUNCTION);
     WebAssembly::getLibcallSignature(Subtarget, Name, Returns, Params);
@@ -471,7 +491,7 @@ void WebAssemblyAsmPrinter::EmitProducerInfo(Module &M) {
     OutStreamer->switchSection(Producers);
     OutStreamer->emitULEB128IntValue(FieldCount);
     for (auto &Producers : {std::make_pair("language", &Languages),
-            std::make_pair("processed-by", &Tools)}) {
+                            std::make_pair("processed-by", &Tools)}) {
       if (Producers.second->empty())
         continue;
       OutStreamer->emitULEB128IntValue(strlen(Producers.first));
@@ -580,7 +600,8 @@ void WebAssemblyAsmPrinter::EmitFunctionAttributes(Module 
&M) {
   // Emit a custom section for each unique attribute.
   for (const auto &[Name, Symbols] : CustomSections) {
     MCSectionWasm *CustomSection = OutContext.getWasmSection(
-        ".custom_section.llvm.func_attr.annotate." + Name, 
SectionKind::getMetadata());
+        ".custom_section.llvm.func_attr.annotate." + Name,
+        SectionKind::getMetadata());
     OutStreamer->pushSection();
     OutStreamer->switchSection(CustomSection);
 
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
index 5a1779c2c80fb..67b9e75be482f 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
@@ -188,8 +188,7 @@ unsigned WebAssemblyFrameLowering::getFPReg(const 
MachineFunction &MF) {
              : WebAssembly::FP32;
 }
 
-unsigned
-WebAssemblyFrameLowering::getOpcConst(const MachineFunction &MF) {
+unsigned WebAssemblyFrameLowering::getOpcConst(const MachineFunction &MF) {
   return MF.getSubtarget<WebAssemblySubtarget>().hasAddr64()
              ? WebAssembly::CONST_I64
              : WebAssembly::CONST_I32;
@@ -213,31 +212,38 @@ unsigned WebAssemblyFrameLowering::getOpcAnd(const 
MachineFunction &MF) {
              : WebAssembly::AND_I32;
 }
 
-unsigned
-WebAssemblyFrameLowering::getOpcGlobGet(const MachineFunction &MF) {
+unsigned WebAssemblyFrameLowering::getOpcGlobGet(const MachineFunction &MF) {
   return MF.getSubtarget<WebAssemblySubtarget>().hasAddr64()
              ? WebAssembly::GLOBAL_GET_I64
              : WebAssembly::GLOBAL_GET_I32;
 }
 
-unsigned
-WebAssemblyFrameLowering::getOpcGlobSet(const MachineFunction &MF) {
+unsigned WebAssemblyFrameLowering::getOpcGlobSet(const MachineFunction &MF) {
   return MF.getSubtarget<WebAssemblySubtarget>().hasAddr64()
              ? WebAssembly::GLOBAL_SET_I64
              : WebAssembly::GLOBAL_SET_I32;
 }
 
-void WebAssemblyFrameLowering::writeSPToGlobal(
+void WebAssemblyFrameLowering::writeBackSP(
     unsigned SrcReg, MachineFunction &MF, MachineBasicBlock &MBB,
     MachineBasicBlock::iterator &InsertStore, const DebugLoc &DL) const {
   const auto *TII = MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
 
-  const char *ES = "__stack_pointer";
-  auto *SPSymbol = MF.createExternalSymbolName(ES);
+  if (MF.getSubtarget<WebAssemblySubtarget>().getTargetTriple().getOSName() ==
+      "wasip3") {
+    const char *ES = "__wasm_component_model_builtin_context_set_0";
+    auto *SPSymbol = MF.createExternalSymbolName(ES);
+    BuildMI(MBB, InsertStore, DL, TII->get(WebAssembly::CALL))
+        .addExternalSymbol(SPSymbol)
+        .addReg(SrcReg);
+  } else {
+    const char *ES = "__stack_pointer";
+    auto *SPSymbol = MF.createExternalSymbolName(ES);
 
-  BuildMI(MBB, InsertStore, DL, TII->get(getOpcGlobSet(MF)))
-      .addExternalSymbol(SPSymbol)
-      .addReg(SrcReg);
+    BuildMI(MBB, InsertStore, DL, TII->get(getOpcGlobSet(MF)))
+        .addExternalSymbol(SPSymbol)
+        .addReg(SrcReg);
+  }
 }
 
 MachineBasicBlock::iterator
@@ -251,7 +257,7 @@ WebAssemblyFrameLowering::eliminateCallFramePseudoInstr(
   if (I->getOpcode() == TII->getCallFrameDestroyOpcode() &&
       needsSPWriteback(MF)) {
     DebugLoc DL = I->getDebugLoc();
-    writeSPToGlobal(getSPReg(MF), MF, MBB, I, DL);
+    writeBackSP(getSPReg(MF), MF, MBB, I, DL);
   }
   return MBB.erase(I);
 }
@@ -283,10 +289,17 @@ void 
WebAssemblyFrameLowering::emitPrologue(MachineFunction &MF,
   if (StackSize)
     SPReg = MRI.createVirtualRegister(PtrRC);
 
-  const char *ES = "__stack_pointer";
-  auto *SPSymbol = MF.createExternalSymbolName(ES);
-  BuildMI(MBB, InsertPt, DL, TII->get(getOpcGlobGet(MF)), SPReg)
-      .addExternalSymbol(SPSymbol);
+  if (ST.getTargetTriple().getOSName() == "wasip3") {
+    const char *ES = "__wasm_component_model_builtin_context_get_0";
+    auto *SPSymbol = MF.createExternalSymbolName(ES);
+    BuildMI(MBB, InsertPt, DL, TII->get(WebAssembly::CALL), SPReg)
+        .addExternalSymbol(SPSymbol);
+  } else {
+    const char *ES = "__stack_pointer";
+    auto *SPSymbol = MF.createExternalSymbolName(ES);
+    BuildMI(MBB, InsertPt, DL, TII->get(getOpcGlobGet(MF)), SPReg)
+        .addExternalSymbol(SPSymbol);
+  }
 
   bool HasBP = hasBP(MF);
   if (HasBP) {
@@ -309,7 +322,7 @@ void WebAssemblyFrameLowering::emitPrologue(MachineFunction 
&MF,
     Register BitmaskReg = MRI.createVirtualRegister(PtrRC);
     Align Alignment = MFI.getMaxAlign();
     BuildMI(MBB, InsertPt, DL, TII->get(getOpcConst(MF)), BitmaskReg)
-        .addImm((int64_t) ~(Alignment.value() - 1));
+        .addImm((int64_t)~(Alignment.value() - 1));
     BuildMI(MBB, InsertPt, DL, TII->get(getOpcAnd(MF)), getSPReg(MF))
         .addReg(getSPReg(MF))
         .addReg(BitmaskReg);
@@ -322,7 +335,7 @@ void WebAssemblyFrameLowering::emitPrologue(MachineFunction 
&MF,
         .addReg(getSPReg(MF));
   }
   if (StackSize && needsSPWriteback(MF)) {
-    writeSPToGlobal(getSPReg(MF), MF, MBB, InsertPt, DL);
+    writeBackSP(getSPReg(MF), MF, MBB, InsertPt, DL);
   }
 }
 
@@ -364,7 +377,7 @@ void WebAssemblyFrameLowering::emitEpilogue(MachineFunction 
&MF,
     SPReg = SPFPReg;
   }
 
-  writeSPToGlobal(SPReg, MF, MBB, InsertPt, DL);
+  writeBackSP(SPReg, MF, MBB, InsertPt, DL);
 }
 
 bool WebAssemblyFrameLowering::isSupportedStackID(
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.h 
b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.h
index 710d5173d64db..e567345e93773 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.h
@@ -23,7 +23,7 @@ class WebAssemblyFrameLowering final : public 
TargetFrameLowering {
 public:
   /// Size of the red zone for the user stack (leaf functions can use this much
   /// space below the stack pointer without writing it back to __stack_pointer
-  /// global).
+  /// global/context.set).
   // TODO: (ABI) Revisit and decide how large it should be.
   static const size_t RedZoneSize = 128;
 
@@ -47,8 +47,8 @@ class WebAssemblyFrameLowering final : public 
TargetFrameLowering {
 
   bool needsPrologForEH(const MachineFunction &MF) const;
 
-  /// Write SP back to __stack_pointer global.
-  void writeSPToGlobal(unsigned SrcReg, MachineFunction &MF,
+  /// Write SP back to __stack_pointer global or context.set.
+  void writeBackSP(unsigned SrcReg, MachineFunction &MF,
                        MachineBasicBlock &MBB,
                        MachineBasicBlock::iterator &InsertStore,
                        const DebugLoc &DL) const;
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
index 2541b0433ab59..d083eefe9b29d 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelDAGToDAG.cpp
@@ -307,10 +307,8 @@ void WebAssemblyDAGToDAGISel::Select(SDNode *Node) {
     MVT PtrVT = TLI.getPointerTy(CurDAG->getDataLayout());
     switch (IntNo) {
     case Intrinsic::wasm_tls_base: {
-      MachineSDNode *TLSBase = CurDAG->getMachineNode(
-          GlobalGetIns, DL, PtrVT, MVT::Other,
-          CurDAG->getTargetExternalSymbol("__tls_base", PtrVT),
-          Node->getOperand(0));
+      MachineSDNode *TLSBase =
+          llvm::WebAssembly::getTLSBase(*CurDAG, DL, Subtarget);
       ReplaceNode(Node, TLSBase);
       return;
     }
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
index ad70d1b7e0a1e..295ee45abe8b5 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
@@ -2001,17 +2001,11 @@ 
WebAssemblyTargetLowering::LowerGlobalTLSAddress(SDValue Op,
       model == GlobalValue::LocalDynamicTLSModel ||
       (model == GlobalValue::GeneralDynamicTLSModel &&
        getTargetMachine().shouldAssumeDSOLocal(GV))) {
-    // For DSO-local TLS variables we use offset from __tls_base
+    // For DSO-local TLS variables we use offset from __tls_base, or
+    // context.get(1) on wasip3.
 
     MVT PtrVT = getPointerTy(DAG.getDataLayout());
-    auto GlobalGet = PtrVT == MVT::i64 ? WebAssembly::GLOBAL_GET_I64
-                                       : WebAssembly::GLOBAL_GET_I32;
-    const char *BaseName = MF.createExternalSymbolName("__tls_base");
-
-    SDValue BaseAddr(
-        DAG.getMachineNode(GlobalGet, DL, PtrVT,
-                           DAG.getTargetExternalSymbol(BaseName, PtrVT)),
-        0);
+    SDValue BaseAddr(WebAssembly::getTLSBase(DAG, DL, Subtarget), 0);
 
     SDValue TLSOffset = DAG.getTargetGlobalAddress(
         GV, DL, PtrVT, GA->getOffset(), WebAssemblyII::MO_TLS_BASE_REL);
@@ -2197,14 +2191,7 @@ SDValue 
WebAssemblyTargetLowering::LowerIntrinsic(SDValue Op,
   }
 
   case Intrinsic::thread_pointer: {
-    MVT PtrVT = getPointerTy(DAG.getDataLayout());
-    auto GlobalGet = PtrVT == MVT::i64 ? WebAssembly::GLOBAL_GET_I64
-                                       : WebAssembly::GLOBAL_GET_I32;
-    const char *TlsBase = MF.createExternalSymbolName("__tls_base");
-    return SDValue(
-        DAG.getMachineNode(GlobalGet, DL, PtrVT,
-                           DAG.getTargetExternalSymbol(TlsBase, PtrVT)),
-        0);
+    return SDValue(WebAssembly::getTLSBase(DAG, DL, Subtarget), 0);
   }
   }
 }
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
index 8ac32f939c5f2..24d5c19399bdb 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
@@ -405,7 +405,7 @@ bool 
WebAssemblyLateEHPrepare::restoreStackPointer(MachineFunction &MF) {
            WebAssembly::isCatch(InsertPos->getOpcode()) &&
            "catch/catch_all should be present in every EH pad at this point");
     ++InsertPos; // Skip the catch instruction
-    FrameLowering->writeSPToGlobal(FrameLowering->getSPReg(MF), MF, MBB,
+    FrameLowering->writeBackSP(FrameLowering->getSPReg(MF), MF, MBB,
                                    InsertPos, MBB.begin()->getDebugLoc());
   }
   return Changed;
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
index 890486778e700..2996e8ca58829 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
@@ -194,3 +194,21 @@ bool WebAssembly::canLowerReturn(size_t ResultSize,
                                  const WebAssemblySubtarget *Subtarget) {
   return ResultSize <= 1 || canLowerMultivalueReturn(Subtarget);
 }
+
+MachineSDNode *WebAssembly::getTLSBase(SelectionDAG &DAG, const SDLoc &DL,
+                                       const WebAssemblySubtarget *Subtarget) {
+  MVT PtrVT = Subtarget->hasAddr64() ? MVT::i64 : MVT::i32;
+  auto GlobalGetIns = PtrVT == MVT::i64 ? WebAssembly::GLOBAL_GET_I64
+                                        : WebAssembly::GLOBAL_GET_I32;
+
+  if (Subtarget->getTargetTriple().getOSName() == "wasip3") {
+    return DAG.getMachineNode(
+        WebAssembly::CALL, DL, PtrVT, MVT::Other,
+        DAG.getTargetExternalSymbol(
+            "__wasm_component_model_builtin_context_get_1", PtrVT),
+        DAG.getEntryNode());
+  } else {
+    return DAG.getMachineNode(GlobalGetIns, DL, PtrVT,
+                              DAG.getTargetExternalSymbol("__tls_base", 
PtrVT));
+  }
+}
\ No newline at end of file
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h 
b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
index 046b1b5db2a79..9dc94d53b46e8 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
@@ -27,6 +27,9 @@ class MCSymbolWasm;
 class TargetRegisterClass;
 class WebAssemblyFunctionInfo;
 class WebAssemblySubtarget;
+class MachineSDNode;
+class SDLoc;
+class SelectionDAG;
 
 namespace WebAssembly {
 
@@ -73,6 +76,12 @@ bool canLowerMultivalueReturn(const WebAssemblySubtarget 
*Subtarget);
 /// memory.
 bool canLowerReturn(size_t ResultSize, const WebAssemblySubtarget *Subtarget);
 
+// Get the TLS base value for the current target
+// On wasip3: calls __wasm_component_model_builtin_context_get_1
+// Otherwise: global.get __tls_base
+MachineSDNode *getTLSBase(SelectionDAG &DAG, const SDLoc &DL,
+                          const WebAssemblySubtarget *Subtarget);
+
 } // end namespace WebAssembly
 
 } // end namespace llvm

>From 540d785b5489e8b48bde3de39e736b2ab2865ca3 Mon Sep 17 00:00:00 2001
From: Sy Brand <[email protected]>
Date: Fri, 9 Jan 2026 12:24:41 +0000
Subject: [PATCH 02/12] Get wasip3 into working state

---
 lld/wasm/Config.h                             |  2 ++
 lld/wasm/Driver.cpp                           | 27 ++++++++-------
 lld/wasm/Relocations.cpp                      |  4 +--
 lld/wasm/SyntheticSections.cpp                | 13 ++++----
 lld/wasm/Writer.cpp                           | 10 +++---
 .../WebAssembly/WebAssemblyTargetMachine.cpp  | 33 ++++++++++++-------
 6 files changed, 54 insertions(+), 35 deletions(-)

diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index 55dea0a78eadb..e26ec0e399893 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -50,6 +50,8 @@ enum class BuildIdKind { None, Fast, Sha1, Hexstring, Uuid };
 // and such fields have the same name as the corresponding options.
 // Most fields are initialized by the driver.
 struct Config {
+  bool isMultithreaded() const { return sharedMemory || isWasip3; }
+
   bool allowMultipleDefinition;
   bool bsymbolic;
   bool checkFeatures;
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 79ecc6c9f5858..4c12e8ac9f8e7 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -986,7 +986,7 @@ static void createSyntheticSymbols() {
     ctx.sym.stackPointer->markLive();
   }
 
-  if (ctx.arg.sharedMemory || ctx.arg.isWasip3) {
+  if (ctx.arg.isMultithreaded()) {
     // TLS symbols are all hidden/dso-local
     auto tls_base_name = ctx.arg.isWasip3 ? "__init_tls_base" : "__tls_base";
     ctx.sym.tlsBase = createGlobalVariable(tls_base_name, true,
@@ -999,16 +999,21 @@ static void createSyntheticSymbols() {
         "__wasm_init_tls", WASM_SYMBOL_VISIBILITY_HIDDEN,
         make<SyntheticFunction>(is64 ? i64ArgSignature : i32ArgSignature,
                                 "__wasm_init_tls"));
-    static WasmSignature contextSet1Signature{{}, {ValType::I32}};
-    ctx.sym.contextSet1 = createUndefinedFunction(
-        "__wasm_component_model_builtin_context_set_1", "[context-set-1]",
-        "$root", &contextSet1Signature);
-    ctx.sym.contextSet1->markLive();
-    static WasmSignature contextGet1Signature{{ValType::I32}, {}};
-    ctx.sym.contextGet1 = createUndefinedFunction(
-        "__wasm_component_model_builtin_context_get_1", "[context-get-1]",
-        "$root", &contextGet1Signature);
-    ctx.sym.contextGet1->markLive();
+    if (ctx.arg.isWasip3) {
+      ctx.sym.tlsBase->markLive();
+      ctx.sym.tlsSize->markLive();
+      ctx.sym.tlsAlign->markLive();
+      static WasmSignature contextSet1Signature{{}, {ValType::I32}};
+      ctx.sym.contextSet1 = createUndefinedFunction(
+          "__wasm_component_model_builtin_context_set_1", "[context-set-1]",
+          "$root", &contextSet1Signature);
+      ctx.sym.contextSet1->markLive();
+      static WasmSignature contextGet1Signature{{ValType::I32}, {}};
+      ctx.sym.contextGet1 = createUndefinedFunction(
+          "__wasm_component_model_builtin_context_get_1", "[context-get-1]",
+          "$root", &contextGet1Signature);
+      ctx.sym.contextGet1->markLive();
+    }
   }
 }
 
diff --git a/lld/wasm/Relocations.cpp b/lld/wasm/Relocations.cpp
index 52888ad25034e..46bbc4806961c 100644
--- a/lld/wasm/Relocations.cpp
+++ b/lld/wasm/Relocations.cpp
@@ -33,7 +33,7 @@ static bool requiresGOTAccess(const Symbol *sym) {
   return true;
 }
 
-static bool allowUndefined(const Symbol* sym) {
+static bool allowUndefined(const Symbol *sym) {
   // Symbols that are explicitly imported are always allowed to be undefined at
   // link time.
   if (sym->isImported())
@@ -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 0d3e060ac381e..1a4fe26145e81 100644
--- a/lld/wasm/SyntheticSections.cpp
+++ b/lld/wasm/SyntheticSections.cpp
@@ -487,9 +487,9 @@ void GlobalSection::writeBody() {
       // the correct runtime value during `__wasm_apply_global_relocs`.
       if (!ctx.arg.extendedConst && ctx.isPic && !sym->isTLS())
         mutable_ = true;
-      // With multi-theadeding any TLS globals must be mutable since they get
+      // 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_};
@@ -526,10 +526,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 multi-threaded case, TLS globals are set during
+        // `__wasm_apply_global_tls_relocs`, but in the non-multi-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 {
@@ -630,7 +631,7 @@ void DataCountSection::writeBody() {
 }
 
 bool DataCountSection::isNeeded() const {
-  return numSegments && (ctx.arg.sharedMemory || ctx.arg.isWasip3);
+  return numSegments && ctx.arg.isMultithreaded();
 }
 
 void LinkingSection::writeBody() {
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 12f72dc239a09..b75cda6384b70 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -1024,7 +1024,7 @@ 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)
+  if (ctx.arg.isMultithreaded())
     s->initFlags = WASM_DATA_SEGMENT_IS_PASSIVE;
   if (!ctx.arg.relocatable && name.starts_with(".bss"))
     s->isBss = true;
@@ -1158,14 +1158,14 @@ void Writer::createSyntheticInitFunctions() {
         "__wasm_init_memory", WASM_SYMBOL_VISIBILITY_HIDDEN,
         make<SyntheticFunction>(nullSignature, "__wasm_init_memory"));
     ctx.sym.initMemory->markLive();
-    if (ctx.arg.sharedMemory) {
+    if (ctx.arg.isMultithreaded()) {
       // This global is assigned during  __wasm_init_memory in the shared 
memory
       // case.
       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,
@@ -1416,7 +1416,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");
@@ -1626,6 +1626,8 @@ void Writer::createInitTLSFunction() {
       writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get");
       writeUleb128(os, 0, "local index");
 
+      // On WASIP3, we call `context.set 1` to set the TLS base,
+      // otherwise, we set the `__tls_base` global.
       if (ctx.arg.isWasip3) {
         writeU8(os, WASM_OPCODE_CALL, "call");
         writeUleb128(os, ctx.sym.contextSet1->getFunctionIndex(),
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
index 621640c12f695..6c11958e8cf3b 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
@@ -260,7 +260,8 @@ class CoalesceFeaturesAndStripAtomics final : public 
ModulePass {
   // Take the union of all features used in the module and use it for each
   // function individually, since having multiple feature sets in one module
   // currently does not make sense for WebAssembly. If atomics are not enabled,
-  // also strip atomic operations and thread local storage.
+  // also strip atomic operations and thread local storage, unless the target
+  // is WASIP3, which can use TLS without atomics due to cooperative threading.
   static char ID;
   WebAssemblyTargetMachine *WasmTM;
 
@@ -279,17 +280,25 @@ class CoalesceFeaturesAndStripAtomics final : public 
ModulePass {
     bool StrippedAtomics = false;
     bool StrippedTLS = false;
 
-    if (!Features[WebAssembly::FeatureAtomics]) {
-      StrippedAtomics = stripAtomics(M);
-      StrippedTLS = stripThreadLocals(M);
-    } else if (!Features[WebAssembly::FeatureBulkMemory]) {
-      StrippedTLS |= stripThreadLocals(M);
-    }
+    if (WasmTM->getTargetTriple().getOSName() == "wasip3") {
+      // WASIP3 allows TLS without atomics, so don't strip TLS even if
+      // atomics are disabled.
+      if (!Features[WebAssembly::FeatureAtomics]) {
+        StrippedAtomics = stripAtomics(M);
+      }
+    } else {
+      if (!Features[WebAssembly::FeatureAtomics]) {
+        StrippedAtomics = stripAtomics(M);
+        StrippedTLS = stripThreadLocals(M);
+      } else if (!Features[WebAssembly::FeatureBulkMemory]) {
+        StrippedTLS |= stripThreadLocals(M);
+      }
 
-    if (StrippedAtomics && !StrippedTLS)
-      stripThreadLocals(M);
-    else if (StrippedTLS && !StrippedAtomics)
-      stripAtomics(M);
+      if (StrippedAtomics && !StrippedTLS)
+        stripThreadLocals(M);
+      else if (StrippedTLS && !StrippedAtomics)
+        stripAtomics(M);
+    }
 
     recordFeatures(M, Features, StrippedAtomics || StrippedTLS);
 
@@ -404,7 +413,7 @@ class CoalesceFeaturesAndStripAtomics final : public 
ModulePass {
     // Code compiled without atomics or bulk-memory may have had its atomics or
     // thread-local data lowered to nonatomic operations or non-thread-local
     // data. In that case, we mark the pseudo-feature "shared-mem" as 
disallowed
-    // to tell the linker that it would be unsafe to allow this code ot be used
+    // to tell the linker that it would be unsafe to allow this code to be used
     // in a module with shared memory.
     if (Stripped) {
       M.addModuleFlag(Module::ModFlagBehavior::Error, 
"wasm-feature-shared-mem",

>From 8f222c968466b30c21fa7de858c2f4da8048b584 Mon Sep 17 00:00:00 2001
From: Sy Brand <[email protected]>
Date: Tue, 17 Feb 2026 08:03:38 +0000
Subject: [PATCH 03/12] Don't disable threads on WASIP3 if no atomics, and
 don't set the TLS from __tls_init

---
 clang/lib/Basic/Targets/WebAssembly.cpp |  3 ++-
 lld/wasm/Writer.cpp                     | 14 +++++---------
 2 files changed, 7 insertions(+), 10 deletions(-)

diff --git a/clang/lib/Basic/Targets/WebAssembly.cpp 
b/clang/lib/Basic/Targets/WebAssembly.cpp
index 5bbb7af4c2ca1..160d39996d902 100644
--- a/clang/lib/Basic/Targets/WebAssembly.cpp
+++ b/clang/lib/Basic/Targets/WebAssembly.cpp
@@ -398,7 +398,8 @@ 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 (getTriple().getOSName() != "wasip3" &&
+      (!HasAtomics || !HasBulkMemory)) {
     Opts.POSIXThreads = false;
     Opts.setThreadModel(LangOptions::ThreadModelKind::Single);
     Opts.ThreadsafeStatics = false;
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index b75cda6384b70..67dc7c8acd343 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -1623,16 +1623,12 @@ void Writer::createInitTLSFunction() {
 
     writeUleb128(os, 0, "num locals");
     if (tlsSeg) {
-      writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get");
-      writeUleb128(os, 0, "local index");
+      // On WASIP3, we don't set the TLS base inside the thread context;
+      // this should be done as part of the thread startup stub.
+      if (!ctx.arg.isWasip3) {
+        writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get");
+        writeUleb128(os, 0, "local index");
 
-      // On WASIP3, we call `context.set 1` to set the TLS base,
-      // otherwise, we set the `__tls_base` global.
-      if (ctx.arg.isWasip3) {
-        writeU8(os, WASM_OPCODE_CALL, "call");
-        writeUleb128(os, ctx.sym.contextSet1->getFunctionIndex(),
-                     "function index");
-      } else {
         writeU8(os, WASM_OPCODE_GLOBAL_SET, "global.set");
         writeUleb128(os, ctx.sym.tlsBase->getGlobalIndex(), "global index");
       }

>From 6d2b4645c91b5dcf9cddd984b48d721860d86b98 Mon Sep 17 00:00:00 2001
From: Sy Brand <[email protected]>
Date: Tue, 17 Feb 2026 12:26:43 +0000
Subject: [PATCH 04/12] Factor out WASIP3 into options and features

---
 clang/include/clang/Options/Options.td        |  2 ++
 clang/lib/Basic/Targets/WebAssembly.cpp       | 16 ++++++++++----
 clang/lib/Basic/Targets/WebAssembly.h         |  4 ++++
 clang/lib/Driver/ToolChains/WebAssembly.cpp   | 11 ++++++++--
 lld/wasm/Config.h                             |  4 ++--
 lld/wasm/Driver.cpp                           | 22 ++++++++++---------
 lld/wasm/Options.td                           |  3 +++
 lld/wasm/Writer.cpp                           |  6 ++---
 lld/wasm/WriterUtils.cpp                      |  4 ++--
 llvm/lib/Target/WebAssembly/WebAssembly.td    |  4 ++++
 .../WebAssembly/WebAssemblyFrameLowering.cpp  |  5 ++---
 .../WebAssembly/WebAssemblyISelLowering.cpp   |  2 +-
 .../WebAssembly/WebAssemblyInstrInfo.td       |  5 +++++
 .../WebAssembly/WebAssemblySubtarget.cpp      |  5 +++++
 .../Target/WebAssembly/WebAssemblySubtarget.h |  2 ++
 .../WebAssembly/WebAssemblyTargetMachine.cpp  |  9 ++++----
 .../WebAssembly/WebAssemblyUtilities.cpp      |  2 +-
 .../Target/WebAssembly/WebAssemblyUtilities.h |  2 +-
 18 files changed, 75 insertions(+), 33 deletions(-)

diff --git a/clang/include/clang/Options/Options.td 
b/clang/include/clang/Options/Options.td
index cda11fdc94230..04972a2cd4b9c 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -5438,6 +5438,8 @@ def mbulk_memory_opt : Flag<["-"], "mbulk-memory-opt">, 
Group<m_wasm_Features_Gr
 def mno_bulk_memory_opt : Flag<["-"], "mno-bulk-memory-opt">, 
Group<m_wasm_Features_Group>;
 def mcall_indirect_overlong : Flag<["-"], "mcall-indirect-overlong">, 
Group<m_wasm_Features_Group>;
 def mno_call_indirect_overlong : Flag<["-"], "mno-call-indirect-overlong">, 
Group<m_wasm_Features_Group>;
+def mcomponent_model_thread_context : Flag<["-"], 
"mcomponent-model-thread-context">, Group<m_wasm_Features_Group>;
+def mno_component_model_thread_context : Flag<["-"], 
"mno-component-model-thread-context">, Group<m_wasm_Features_Group>;
 def mexception_handing : Flag<["-"], "mexception-handling">, 
Group<m_wasm_Features_Group>;
 def mno_exception_handing : Flag<["-"], "mno-exception-handling">, 
Group<m_wasm_Features_Group>;
 def mextended_const : Flag<["-"], "mextended-const">, 
Group<m_wasm_Features_Group>;
diff --git a/clang/lib/Basic/Targets/WebAssembly.cpp 
b/clang/lib/Basic/Targets/WebAssembly.cpp
index 160d39996d902..1d557bec6157e 100644
--- a/clang/lib/Basic/Targets/WebAssembly.cpp
+++ b/clang/lib/Basic/Targets/WebAssembly.cpp
@@ -362,6 +362,14 @@ bool WebAssemblyTargetInfo::handleTargetFeatures(
       HasWideArithmetic = false;
       continue;
     }
+    if (Feature == "+component-model-thread-context") {
+      HasComponentModelThreadContext = true;
+      continue;
+    }
+    if (Feature == "-component-model-thread-context") {
+      HasComponentModelThreadContext = false;
+      continue;
+    }
 
     Diags.Report(diag::err_opt_not_valid_with_opt)
         << Feature << "-target-feature";
@@ -395,10 +403,10 @@ WebAssemblyTargetInfo::getTargetBuiltins() const {
 void WebAssemblyTargetInfo::adjust(DiagnosticsEngine &Diags, LangOptions &Opts,
                                    const TargetInfo *Aux) {
   TargetInfo::adjust(Diags, Opts, Aux);
-  // 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 (getTriple().getOSName() != "wasip3" &&
+  // If not using component model threading intrinsics, 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 (!HasComponentModelThreadContext &&
       (!HasAtomics || !HasBulkMemory)) {
     Opts.POSIXThreads = false;
     Opts.setThreadModel(LangOptions::ThreadModelKind::Single);
diff --git a/clang/lib/Basic/Targets/WebAssembly.h 
b/clang/lib/Basic/Targets/WebAssembly.h
index 4de6ce6bb5a21..5bd8fae7c8311 100644
--- a/clang/lib/Basic/Targets/WebAssembly.h
+++ b/clang/lib/Basic/Targets/WebAssembly.h
@@ -61,6 +61,7 @@ class LLVM_LIBRARY_VISIBILITY WebAssemblyTargetInfo : public 
TargetInfo {
   bool HasBulkMemory = false;
   bool HasBulkMemoryOpt = false;
   bool HasCallIndirectOverlong = false;
+  bool HasComponentModelThreadContext = false;
   bool HasExceptionHandling = false;
   bool HasExtendedConst = false;
   bool HasFP16 = false;
@@ -105,6 +106,9 @@ class LLVM_LIBRARY_VISIBILITY WebAssemblyTargetInfo : 
public TargetInfo {
       PtrDiffType = SignedLong;
       IntPtrType = SignedLong;
     }
+    if (T.getOSName() == "wasip3") {
+      HasComponentModelThreadContext = true;
+    }
   }
 
   StringRef getABI() const override;
diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp 
b/clang/lib/Driver/ToolChains/WebAssembly.cpp
index d6ff6584258d2..436794445379d 100644
--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp
+++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp
@@ -100,8 +100,7 @@ void wasm::Linker::ConstructJob(Compilation &C, const 
JobAction &JA,
     arch = "wasm64";
   else
     arch = "wasm32";
-  if (ToolChain.getTriple().getOSName() == "wasip3")
-    arch += "-wasip3";
+
   CmdArgs.push_back(Args.MakeArgString(arch));
 
   if (Args.hasArg(options::OPT_s))
@@ -172,6 +171,14 @@ void wasm::Linker::ConstructJob(Compilation &C, const 
JobAction &JA,
   if (WantsSharedMemory(ToolChain.getTriple(), Args))
     CmdArgs.push_back("--shared-memory");
 
+  // Enable component model thread context by default for wasip3
+  bool DefaultComponentModelThreadContext =
+      ToolChain.getTriple().getOSName() == "wasip3";
+  if (Args.hasFlag(options::OPT_mcomponent_model_thread_context,
+                   options::OPT_mno_component_model_thread_context,
+                   DefaultComponentModelThreadContext))
+    CmdArgs.push_back("--component-model-thread-context");
+
   if (!Args.hasArg(options::OPT_nostdlib, options::OPT_nodefaultlibs)) {
     if (ToolChain.ShouldLinkCXXStdlib(Args))
       ToolChain.AddCXXStdlibLibArgs(Args, CmdArgs);
diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index e26ec0e399893..8f7566803231d 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -50,11 +50,12 @@ enum class BuildIdKind { None, Fast, Sha1, Hexstring, Uuid 
};
 // and such fields have the same name as the corresponding options.
 // Most fields are initialized by the driver.
 struct Config {
-  bool isMultithreaded() const { return sharedMemory || isWasip3; }
+  bool isMultithreaded() const { return sharedMemory || 
componentModelThreadContext; }
 
   bool allowMultipleDefinition;
   bool bsymbolic;
   bool checkFeatures;
+  bool componentModelThreadContext;
   bool compressRelocations;
   bool demangle;
   bool disableVerify;
@@ -73,7 +74,6 @@ struct Config {
   bool importTable;
   bool importUndefined;
   std::optional<bool> is64;
-  bool isWasip3;
   bool mergeDataSegments;
   bool noinhibitExec;
   bool pie;
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 4c12e8ac9f8e7..ed2287d649844 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -656,16 +656,15 @@ static void readConfigs(opt::InputArgList &args) {
   ctx.arg.exportDynamic =
       args.hasFlag(OPT_export_dynamic, OPT_no_export_dynamic, ctx.arg.shared);
 
-  // Parse wasm32/64 and maybe -wasip3.
+   // Parse wasm32/64.
   if (auto *arg = args.getLastArg(OPT_m)) {
     StringRef s = arg->getValue();
-    if (s.starts_with("wasm32"))
+    if (s == "wasm32")
       ctx.arg.is64 = false;
-    else if (s.starts_with("wasm64"))
+    else if (s == "wasm64")
       ctx.arg.is64 = true;
     else
       error("invalid target architecture: " + s);
-    ctx.arg.isWasip3 = s.ends_with("-wasip3");
   }
 
   // --threads= takes a positive integer and provides the default value for
@@ -706,6 +705,9 @@ static void readConfigs(opt::InputArgList &args) {
   if (args.hasArg(OPT_print_map))
     ctx.arg.mapFile = "-";
 
+  if (args.hasArg(OPT_component_model_thread_context))
+    ctx.arg.componentModelThreadContext = true;
+
   std::tie(ctx.arg.buildId, ctx.arg.buildIdVector) = getBuildId(args);
 }
 
@@ -829,8 +831,8 @@ static void checkOptions(opt::InputArgList &args) {
       error("--table-base may not be used with -shared/-pie");
   }
 
-  if (ctx.arg.sharedMemory && ctx.arg.isWasip3) {
-    error("--shared-memory is incompatible with the wasip3 target");
+  if (ctx.arg.sharedMemory && ctx.arg.componentModelThreadContext) {
+    error("--shared-memory is incompatible with component model thread context 
intrinsics");
   }
 }
 
@@ -964,7 +966,7 @@ static void createSyntheticSymbols() {
   bool is64 = ctx.arg.is64.value_or(false);
 
   auto stack_pointer_name =
-      ctx.arg.isWasip3 ? "__init_stack_pointer" : "__stack_pointer";
+      ctx.arg.componentModelThreadContext ? "__init_stack_pointer" : 
"__stack_pointer";
   if (ctx.isPic) {
     ctx.sym.stackPointer =
         createUndefinedGlobal(stack_pointer_name, ctx.arg.is64.value_or(false)
@@ -988,7 +990,7 @@ static void createSyntheticSymbols() {
 
   if (ctx.arg.isMultithreaded()) {
     // TLS symbols are all hidden/dso-local
-    auto tls_base_name = ctx.arg.isWasip3 ? "__init_tls_base" : "__tls_base";
+    auto tls_base_name = ctx.arg.componentModelThreadContext ? 
"__init_tls_base" : "__tls_base";
     ctx.sym.tlsBase = createGlobalVariable(tls_base_name, true,
                                            WASM_SYMBOL_VISIBILITY_HIDDEN);
     ctx.sym.tlsSize = createGlobalVariable("__tls_size", false,
@@ -999,7 +1001,7 @@ static void createSyntheticSymbols() {
         "__wasm_init_tls", WASM_SYMBOL_VISIBILITY_HIDDEN,
         make<SyntheticFunction>(is64 ? i64ArgSignature : i32ArgSignature,
                                 "__wasm_init_tls"));
-    if (ctx.arg.isWasip3) {
+    if (ctx.arg.componentModelThreadContext) {
       ctx.sym.tlsBase->markLive();
       ctx.sym.tlsSize->markLive();
       ctx.sym.tlsAlign->markLive();
@@ -1049,7 +1051,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 && !ctx.arg.isWasip3)
+  if (!ctx.arg.sharedMemory && !ctx.arg.componentModelThreadContext)
     ctx.sym.tlsBase = createOptionalGlobal("__tls_base", false);
 }
 
diff --git a/lld/wasm/Options.td b/lld/wasm/Options.td
index 33ecf03176d36..a9def1bc47c9a 100644
--- a/lld/wasm/Options.td
+++ b/lld/wasm/Options.td
@@ -190,6 +190,9 @@ def allow_undefined_file: J<"allow-undefined-file=">,
 def allow_undefined_file_s: Separate<["-"], "allow-undefined-file">,
   Alias<allow_undefined_file>;
 
+def component_model_thread_context: FF<"component-model-thread-context">,
+  HelpText<"Use component model thread context intrinsics instead of global 
variables for the stack pointer and thread local storage">;
+
 defm export: Eq<"export", "Force a symbol to be exported">;
 
 defm export_if_defined: Eq<"export-if-defined",
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 67dc7c8acd343..ab6998a6e3bc2 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -1623,9 +1623,9 @@ void Writer::createInitTLSFunction() {
 
     writeUleb128(os, 0, "num locals");
     if (tlsSeg) {
-      // On WASIP3, we don't set the TLS base inside the thread context;
-      // this should be done as part of the thread startup stub.
-      if (!ctx.arg.isWasip3) {
+      // When using component model thread context intrinsics, we don't set 
the TLS base
+      //inside __init_tls; this should be done as part of the thread startup 
stub.
+      if (!ctx.arg.componentModelThreadContext) {
         writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get");
         writeUleb128(os, 0, "local index");
 
diff --git a/lld/wasm/WriterUtils.cpp b/lld/wasm/WriterUtils.cpp
index 8e945f57ef39c..4cb2a9018cca8 100644
--- a/lld/wasm/WriterUtils.cpp
+++ b/lld/wasm/WriterUtils.cpp
@@ -269,7 +269,7 @@ void writeExport(raw_ostream &os, const WasmExport 
&export_) {
 }
 
 void writeGetTLSBase(const Ctx &ctx, raw_ostream &os) {
-  if (ctx.arg.isWasip3) {
+  if (ctx.arg.componentModelThreadContext) {
     writeU8(os, WASM_OPCODE_CALL, "call");
     writeUleb128(os, ctx.sym.contextGet1->getFunctionIndex(), "function 
index");
   } else {
@@ -279,7 +279,7 @@ void writeGetTLSBase(const Ctx &ctx, raw_ostream &os) {
 }
 
 void writeSetTLSBase(const Ctx &ctx, raw_ostream &os) {
-  if (ctx.arg.isWasip3) {
+  if (ctx.arg.componentModelThreadContext) {
     writeU8(os, WASM_OPCODE_CALL, "call");
     writeUleb128(os, ctx.sym.contextSet1->getFunctionIndex(), "function 
index");
   } else {
diff --git a/llvm/lib/Target/WebAssembly/WebAssembly.td 
b/llvm/lib/Target/WebAssembly/WebAssembly.td
index 089be5f1dc70e..ff6a78027037d 100644
--- a/llvm/lib/Target/WebAssembly/WebAssembly.td
+++ b/llvm/lib/Target/WebAssembly/WebAssembly.td
@@ -37,6 +37,10 @@ def FeatureCallIndirectOverlong :
       SubtargetFeature<"call-indirect-overlong", "HasCallIndirectOverlong", 
"true",
                        "Enable overlong encoding for call_indirect 
immediates">;
 
+def FeatureComponentModelThreadContext :
+      SubtargetFeature<"component-model-thread-context", 
"HasComponentModelThreadContext", "false",
+                       "Enable component model thread context intrinsics">;
+
 def FeatureExceptionHandling :
       SubtargetFeature<"exception-handling", "HasExceptionHandling", "true",
                        "Enable Wasm exception handling">;
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
index 67b9e75be482f..5389318ad0e28 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
@@ -229,8 +229,7 @@ void WebAssemblyFrameLowering::writeBackSP(
     MachineBasicBlock::iterator &InsertStore, const DebugLoc &DL) const {
   const auto *TII = MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
 
-  if (MF.getSubtarget<WebAssemblySubtarget>().getTargetTriple().getOSName() ==
-      "wasip3") {
+  if 
(MF.getSubtarget<WebAssemblySubtarget>().hasComponentModelThreadContext()) {
     const char *ES = "__wasm_component_model_builtin_context_set_0";
     auto *SPSymbol = MF.createExternalSymbolName(ES);
     BuildMI(MBB, InsertStore, DL, TII->get(WebAssembly::CALL))
@@ -289,7 +288,7 @@ void WebAssemblyFrameLowering::emitPrologue(MachineFunction 
&MF,
   if (StackSize)
     SPReg = MRI.createVirtualRegister(PtrRC);
 
-  if (ST.getTargetTriple().getOSName() == "wasip3") {
+  if (ST.hasComponentModelThreadContext()) {
     const char *ES = "__wasm_component_model_builtin_context_get_0";
     auto *SPSymbol = MF.createExternalSymbolName(ES);
     BuildMI(MBB, InsertPt, DL, TII->get(WebAssembly::CALL), SPReg)
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
index 295ee45abe8b5..dcbd39dc71e66 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyISelLowering.cpp
@@ -2002,7 +2002,7 @@ WebAssemblyTargetLowering::LowerGlobalTLSAddress(SDValue 
Op,
       (model == GlobalValue::GeneralDynamicTLSModel &&
        getTargetMachine().shouldAssumeDSOLocal(GV))) {
     // For DSO-local TLS variables we use offset from __tls_base, or
-    // context.get(1) on wasip3.
+    // context.get(1) if using component model threading intrinsics.
 
     MVT PtrVT = getPointerTy(DAG.getDataLayout());
     SDValue BaseAddr(WebAssembly::getTLSBase(DAG, DL, Subtarget), 0);
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td 
b/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td
index 13d048a98d6ea..d4fd93cb71233 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyInstrInfo.td
@@ -38,6 +38,11 @@ def HasCallIndirectOverlong :
     Predicate<"Subtarget->hasCallIndirectOverlong()">,
     AssemblerPredicate<(all_of FeatureCallIndirectOverlong), 
"call-indirect-overlong">;
 
+def HasComponentModelThreadContext :
+    Predicate<"Subtarget->hasComponentModelThreadContext()">,
+    AssemblerPredicate<(all_of FeatureComponentModelThreadContext),
+                       "component-model-thread-context">;
+
 def HasExceptionHandling :
     Predicate<"Subtarget->hasExceptionHandling()">,
     AssemblerPredicate<(all_of FeatureExceptionHandling), 
"exception-handling">;
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
index a3ce40f0297ec..68d474631f23f 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
@@ -33,6 +33,11 @@ 
WebAssemblySubtarget::initializeSubtargetDependencies(StringRef CPU,
   if (CPU.empty())
     CPU = "generic";
 
+  // WASIP3 implies using the component model thread context intrinsics by 
default.
+  if (TargetTriple.getOSName() == "wasip3") {
+    HasComponentModelThreadContext = true;
+  }
+
   ParseSubtargetFeatures(CPU, /*TuneCPU*/ CPU, FS);
 
   FeatureBitset Bits = getFeatureBits();
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h 
b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
index 2f88bbba05d00..5c086b8a4fe31 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
@@ -43,6 +43,7 @@ class WebAssemblySubtarget final : public 
WebAssemblyGenSubtargetInfo {
   bool HasBulkMemory = false;
   bool HasBulkMemoryOpt = false;
   bool HasCallIndirectOverlong = false;
+  bool HasComponentModelThreadContext = false;
   bool HasExceptionHandling = false;
   bool HasExtendedConst = false;
   bool HasFP16 = false;
@@ -100,6 +101,7 @@ class WebAssemblySubtarget final : public 
WebAssemblyGenSubtargetInfo {
   bool hasBulkMemory() const { return HasBulkMemory; }
   bool hasBulkMemoryOpt() const { return HasBulkMemoryOpt; }
   bool hasCallIndirectOverlong() const { return HasCallIndirectOverlong; }
+  bool hasComponentModelThreadContext() const { return 
HasComponentModelThreadContext; }
   bool hasExceptionHandling() const { return HasExceptionHandling; }
   bool hasExtendedConst() const { return HasExtendedConst; }
   bool hasFP16() const { return HasFP16; }
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
index 6c11958e8cf3b..bd34f4149fc5e 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
@@ -261,7 +261,8 @@ class CoalesceFeaturesAndStripAtomics final : public 
ModulePass {
   // function individually, since having multiple feature sets in one module
   // currently does not make sense for WebAssembly. If atomics are not enabled,
   // also strip atomic operations and thread local storage, unless the target
-  // is WASIP3, which can use TLS without atomics due to cooperative threading.
+  // is using component model threading intrinsics which allow thread local 
storage 
+  // without atomics, in which case only strip atomics.
   static char ID;
   WebAssemblyTargetMachine *WasmTM;
 
@@ -280,9 +281,9 @@ class CoalesceFeaturesAndStripAtomics final : public 
ModulePass {
     bool StrippedAtomics = false;
     bool StrippedTLS = false;
 
-    if (WasmTM->getTargetTriple().getOSName() == "wasip3") {
-      // WASIP3 allows TLS without atomics, so don't strip TLS even if
-      // atomics are disabled.
+    if (Features[WebAssembly::FeatureComponentModelThreadContext]) {
+      // Using component model threading intrinsics allows TLS without 
+      // atomics, so don't strip TLS even if atomics are disabled.
       if (!Features[WebAssembly::FeatureAtomics]) {
         StrippedAtomics = stripAtomics(M);
       }
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
index 2996e8ca58829..1c8aeae8bc1cb 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.cpp
@@ -201,7 +201,7 @@ MachineSDNode *WebAssembly::getTLSBase(SelectionDAG &DAG, 
const SDLoc &DL,
   auto GlobalGetIns = PtrVT == MVT::i64 ? WebAssembly::GLOBAL_GET_I64
                                         : WebAssembly::GLOBAL_GET_I32;
 
-  if (Subtarget->getTargetTriple().getOSName() == "wasip3") {
+  if (Subtarget->hasComponentModelThreadContext()) {
     return DAG.getMachineNode(
         WebAssembly::CALL, DL, PtrVT, MVT::Other,
         DAG.getTargetExternalSymbol(
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h 
b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
index 9dc94d53b46e8..5b4e9cff19a55 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
@@ -77,7 +77,7 @@ bool canLowerMultivalueReturn(const WebAssemblySubtarget 
*Subtarget);
 bool canLowerReturn(size_t ResultSize, const WebAssemblySubtarget *Subtarget);
 
 // Get the TLS base value for the current target
-// On wasip3: calls __wasm_component_model_builtin_context_get_1
+// If using component model threading intrinsics: calls 
__wasm_component_model_builtin_context_get_1
 // Otherwise: global.get __tls_base
 MachineSDNode *getTLSBase(SelectionDAG &DAG, const SDLoc &DL,
                           const WebAssemblySubtarget *Subtarget);

>From 4a18333fe63c7a16d6b74633e334981cbbda5ade Mon Sep 17 00:00:00 2001
From: Sy Brand <[email protected]>
Date: Tue, 17 Feb 2026 14:16:17 +0000
Subject: [PATCH 05/12] Tighten up features

---
 clang/lib/Basic/Targets/WebAssembly.cpp                |  3 +++
 lld/wasm/Writer.cpp                                    | 10 ++++++++++
 llvm/lib/Target/WebAssembly/WebAssembly.td             |  2 +-
 llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp   |  9 ++++++---
 .../Target/WebAssembly/WebAssemblyTargetMachine.cpp    |  8 ++++++++
 5 files changed, 28 insertions(+), 4 deletions(-)

diff --git a/clang/lib/Basic/Targets/WebAssembly.cpp 
b/clang/lib/Basic/Targets/WebAssembly.cpp
index 0d7e99b11576e..785bec699f925 100644
--- a/clang/lib/Basic/Targets/WebAssembly.cpp
+++ b/clang/lib/Basic/Targets/WebAssembly.cpp
@@ -56,6 +56,7 @@ bool WebAssemblyTargetInfo::hasFeature(StringRef Feature) 
const {
       .Case("bulk-memory", HasBulkMemory)
       .Case("bulk-memory-opt", HasBulkMemoryOpt)
       .Case("call-indirect-overlong", HasCallIndirectOverlong)
+      .Case("component-model-thread-context", HasComponentModelThreadContext)
       .Case("compact-imports", HasCompactImports)
       .Case("exception-handling", HasExceptionHandling)
       .Case("extended-const", HasExtendedConst)
@@ -120,6 +121,8 @@ void WebAssemblyTargetInfo::getTargetDefines(const 
LangOptions &Opts,
     Builder.defineMacro("__wasm_tail_call__");
   if (HasWideArithmetic)
     Builder.defineMacro("__wasm_wide_arithmetic__");
+  if (HasComponentModelThreadContext)
+    Builder.defineMacro("__wasm_component_model_thread_context__");
   // Note that not all wasm features appear here.   For example,
   // HasCompatctImports
 
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index eaf5481a5dcd6..d18999f907561 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -644,6 +644,16 @@ void Writer::populateTargetFeatures() {
             " because it was not compiled with 'atomics' or 'bulk-memory' "
             "features.");
 
+  if (ctx.arg.componentModelThreadContext && 
disallowed.contains("component-model-thread-context"))
+    error("--component-model-thread-context is disallowed by " +
+            disallowed["component-model-thread-context"] +
+            " because it was not compiled with the 
'component-model-thread-context' feature.");
+
+  if (!ctx.arg.componentModelThreadContext && 
used.contains("component-model-thread-context"))
+    error("component-model-thread-context feature used by " +
+            used["component-model-thread-context"] +
+            " but --component-model-thread-context not specified.");
+
     for (auto feature : {"atomics", "bulk-memory"})
       if (!allowed.contains(feature))
         error(StringRef("'") + feature +
diff --git a/llvm/lib/Target/WebAssembly/WebAssembly.td 
b/llvm/lib/Target/WebAssembly/WebAssembly.td
index 0575f471b5412..5c1076aff985b 100644
--- a/llvm/lib/Target/WebAssembly/WebAssembly.td
+++ b/llvm/lib/Target/WebAssembly/WebAssembly.td
@@ -38,7 +38,7 @@ def FeatureCallIndirectOverlong :
                        "Enable overlong encoding for call_indirect 
immediates">;
 
 def FeatureComponentModelThreadContext :
-      SubtargetFeature<"component-model-thread-context", 
"HasComponentModelThreadContext", "false",
+      SubtargetFeature<"component-model-thread-context", 
"HasComponentModelThreadContext", "true",
                        "Enable component model thread context intrinsics">;
 
 def FeatureExceptionHandling :
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
index e027221f454ce..fa99249840dd9 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
@@ -38,13 +38,16 @@ 
WebAssemblySubtarget::initializeSubtargetDependencies(StringRef CPU,
   if (CPU.empty())
     CPU = "generic";
 
+  ParseSubtargetFeatures(CPU, /*TuneCPU*/ CPU, FS);  
+  
   // WASIP3 implies using the component model thread context intrinsics by 
default.
-  if (TargetTriple.getOSName() == "wasip3") {
+  if (!FS.contains("component-model-thread-context") && 
+      !HasComponentModelThreadContext && 
+      TargetTriple.getOSName() == "wasip3") {
+    ToggleFeature(WebAssembly::FeatureComponentModelThreadContext);
     HasComponentModelThreadContext = true;
   }
 
-  ParseSubtargetFeatures(CPU, /*TuneCPU*/ CPU, FS);
-
   FeatureBitset Bits = getFeatureBits();
 
   // bulk-memory implies bulk-memory-opt
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp 
b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
index 8ed00ec2cf064..b484468d95a1e 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
@@ -429,6 +429,14 @@ class CoalesceFeaturesAndStripAtomics final : public 
ModulePass {
       M.addModuleFlag(Module::ModFlagBehavior::Error, 
"wasm-feature-shared-mem",
                       wasm::WASM_FEATURE_PREFIX_DISALLOWED);
     }
+    
+    // Mark component-model-thread-context as disallowed when not in use to
+    // prevent linking object files with incompatible threading ABIs.
+    if (!Features[WebAssembly::FeatureComponentModelThreadContext]) {
+      M.addModuleFlag(Module::ModFlagBehavior::Error,
+                      "wasm-feature-component-model-thread-context",
+                      wasm::WASM_FEATURE_PREFIX_DISALLOWED);
+    }
   }
 };
 char CoalesceFeaturesAndStripAtomics::ID = 0;

>From 927daeb7e990692208adea16de038b271e659f60 Mon Sep 17 00:00:00 2001
From: Sy Brand <[email protected]>
Date: Tue, 17 Feb 2026 14:26:15 +0000
Subject: [PATCH 06/12] Make feature detection work properly

---
 lld/wasm/Writer.cpp | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index d18999f907561..431169d3fdd39 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -644,6 +644,12 @@ void Writer::populateTargetFeatures() {
             " because it was not compiled with 'atomics' or 'bulk-memory' "
             "features.");
 
+    for (auto feature : {"atomics", "bulk-memory"})
+      if (!allowed.contains(feature))
+        error(StringRef("'") + feature +
+              "' feature must be used in order to use shared memory");
+  }
+
   if (ctx.arg.componentModelThreadContext && 
disallowed.contains("component-model-thread-context"))
     error("--component-model-thread-context is disallowed by " +
             disallowed["component-model-thread-context"] +
@@ -654,13 +660,7 @@ void Writer::populateTargetFeatures() {
             used["component-model-thread-context"] +
             " but --component-model-thread-context not specified.");
 
-    for (auto feature : {"atomics", "bulk-memory"})
-      if (!allowed.contains(feature))
-        error(StringRef("'") + feature +
-              "' feature must be used in order to use shared memory");
-  }
-
-  if (tlsUsed) {
+  if (tlsUsed && !ctx.arg.componentModelThreadContext) {
     for (auto feature : {"atomics", "bulk-memory"})
       if (!allowed.contains(feature))
         error(StringRef("'") + feature +

>From e632709c827ad5e5bb6a6e87a7f2d852b14ecc3f Mon Sep 17 00:00:00 2001
From: Sy Brand <[email protected]>
Date: Tue, 17 Feb 2026 15:16:34 +0000
Subject: [PATCH 07/12] Fix TLS relocations for non WASIP3

---
 lld/wasm/SyntheticSections.cpp | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp
index 023c690c14354..27fe215b6143b 100644
--- a/lld/wasm/SyntheticSections.cpp
+++ b/lld/wasm/SyntheticSections.cpp
@@ -474,11 +474,12 @@ void GlobalSection::generateRelocationCode(raw_ostream 
&os, bool TLS) const {
 
     if (auto *d = dyn_cast<DefinedData>(sym)) {
       // Get __memory_base
-      writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
       if (sym->isTLS())
         writeGetTLSBase(ctx, os);
-      else
+      else {
+        writeU8(os, WASM_OPCODE_GLOBAL_GET, "GLOBAL_GET");
         writeUleb128(os, ctx.sym.memoryBase->getGlobalIndex(), 
"__memory_base");
+      }
 
       // Add the virtual address of the data symbol
       writePtrConst(os, d->getVA(), is64, "offset");

>From f44bf2984770351e7ff7100fb524aa1b1b9bf956 Mon Sep 17 00:00:00 2001
From: Sy Brand <[email protected]>
Date: Tue, 17 Feb 2026 15:18:27 +0000
Subject: [PATCH 08/12] Add stack pointer ABI test

---
 lld/test/wasm/stack-pointer-abi.s | 13 +++++++++++++
 1 file changed, 13 insertions(+)
 create mode 100644 lld/test/wasm/stack-pointer-abi.s

diff --git a/lld/test/wasm/stack-pointer-abi.s 
b/lld/test/wasm/stack-pointer-abi.s
new file mode 100644
index 0000000000000..11355a9c4586e
--- /dev/null
+++ b/lld/test/wasm/stack-pointer-abi.s
@@ -0,0 +1,13 @@
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
+# RUN: wasm-ld --component-model-thread-context -o %t-component-model.wasm %t.o
+# RUN: obj2yaml %t-component-model.wasm | FileCheck %s --check-prefix=WITH
+# RUN: wasm-ld -o %t-original.wasm %t.o
+# RUN: obj2yaml %t-original.wasm | FileCheck %s --check-prefix=WITHOUT
+
+.globl _start
+_start:
+  .functype _start () -> ()
+  end_function
+
+# WITH: Name: __init_stack_pointer
+# WITHOUT: Name: __stack_pointer
\ No newline at end of file

>From 18a1ee3ae2db83e411bd8f8f842def0351be7a6c Mon Sep 17 00:00:00 2001
From: Sy Brand <[email protected]>
Date: Tue, 17 Feb 2026 15:43:16 +0000
Subject: [PATCH 09/12] Add tests for component model threading feature

---
 ...nent-model-threading-features-disallowed.s | 20 +++++++++++++++++++
 .../wasm/component-model-threading-features.s | 20 +++++++++++++++++++
 2 files changed, 40 insertions(+)
 create mode 100644 
lld/test/wasm/component-model-threading-features-disallowed.s
 create mode 100644 lld/test/wasm/component-model-threading-features.s

diff --git a/lld/test/wasm/component-model-threading-features-disallowed.s 
b/lld/test/wasm/component-model-threading-features-disallowed.s
new file mode 100644
index 0000000000000..9644b7e7caedd
--- /dev/null
+++ b/lld/test/wasm/component-model-threading-features-disallowed.s
@@ -0,0 +1,20 @@
+# Test that objects with component-model-thread-context feature marked as 
DISALLOWED
+# cannot link with --component-model-thread-context flag
+
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t-without.o %s
+# RUN: wasm-ld %t-without.o -o %t.wasm
+# RUN: not wasm-ld --component-model-thread-context %t-without.o -o %t2.wasm 
2>&1 | FileCheck %s
+
+# CHECK: error: --component-model-thread-context is disallowed by {{.*}} 
because it was not compiled with the 'component-model-thread-context' feature.
+
+.globl _start
+_start:
+  .functype _start () -> ()
+  end_function
+
+# Mark the feature as DISALLOWED (0x2d = '-' = WASM_FEATURE_PREFIX_DISALLOWED)
+.section  .custom_section.target_features,"",@
+  .int8 1
+  .int8 45
+  .int8 30
+  .ascii  "component-model-thread-context"
diff --git a/lld/test/wasm/component-model-threading-features.s 
b/lld/test/wasm/component-model-threading-features.s
new file mode 100644
index 0000000000000..dd617c6f8fec5
--- /dev/null
+++ b/lld/test/wasm/component-model-threading-features.s
@@ -0,0 +1,20 @@
+# Test that objects with component-model-thread-context feature marked as USED
+# can only link with --component-model-thread-context flag
+
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t-with.o %s
+# RUN: wasm-ld --component-model-thread-context %t-with.o -o %t.wasm
+# RUN: not wasm-ld %t-with.o -o %t2.wasm 2>&1 | FileCheck %s
+
+# CHECK: error: component-model-thread-context feature used by {{.*}} but 
--component-model-thread-context not specified. 
+
+.globl _start
+_start:
+  .functype _start () -> ()
+  end_function
+
+# Mark the feature as USED (0x2b = '+' = WASM_FEATURE_PREFIX_USED)
+.section  .custom_section.target_features,"",@
+  .int8 1
+  .int8 43
+  .int8 30
+  .ascii  "component-model-thread-context"

>From 8980e38482f3d2f90e538a0316990151527c58f1 Mon Sep 17 00:00:00 2001
From: Sy Brand <[email protected]>
Date: Tue, 17 Feb 2026 16:47:01 +0000
Subject: [PATCH 10/12] Add wasm-features test

---
 clang/test/Driver/wasm-features.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/clang/test/Driver/wasm-features.c 
b/clang/test/Driver/wasm-features.c
index 89ced36eeffab..3a52150d2de27 100644
--- a/clang/test/Driver/wasm-features.c
+++ b/clang/test/Driver/wasm-features.c
@@ -112,3 +112,10 @@
 
 // COMPACT-IMPORTS: "-target-feature" "+compact-imports"
 // NO-COMPACT-IMPORTS: "-target-feature" "-compact-imports"
+
+// RUN: %clang --target=wasm32-unknown-unknown -### %s 
-mcomponent-model-thread-context 2>&1 | FileCheck %s 
-check-prefix=COMPONENT-MODEL-THREAD-CONTEXT
+// RUN: %clang --target=wasm32-unknown-unknown -### %s 
-mno-component-model-thread-context 2>&1 | FileCheck %s 
-check-prefix=NO-COMPONENT-MODEL-THREAD-CONTEXT
+
+// COMPONENT-MODEL-THREAD-CONTEXT: "-target-feature" 
"+component-model-thread-context"
+// NO-COMPONENT-MODEL-THREAD-CONTEXT: "-target-feature" 
"-component-model-thread-context"
+

>From 08a3a164e68889291d218e67959c889fe7ca4084 Mon Sep 17 00:00:00 2001
From: Sy Brand <[email protected]>
Date: Tue, 17 Feb 2026 16:58:11 +0000
Subject: [PATCH 11/12] wasm-toolchain tests

---
 clang/test/Driver/wasm-toolchain.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/clang/test/Driver/wasm-toolchain.c 
b/clang/test/Driver/wasm-toolchain.c
index 29a94aeec77a9..8654021e9a959 100644
--- a/clang/test/Driver/wasm-toolchain.c
+++ b/clang/test/Driver/wasm-toolchain.c
@@ -303,3 +303,17 @@
 // 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 --component-model-thread-context to the 
linker by default.
+
+// RUN: %clang --target=wasm32-wasip3 %s -### 2>&1 | FileCheck 
-check-prefix=WASIP3_DEFAULT %s
+// WASIP3_DEFAULT: wasm-component-ld{{.*}}" {{.*}} 
"--component-model-thread-context"
+
+// Test that `wasm32-wasip3` does not pass --component-model-thread-context to 
the linker when 
+// -mno-component-model-thread-context is used, and that it also passes 
-target-feature -component-model-thread-context 
+// to disable the feature in clang-cc1.
+
+// RUN: %clang --target=wasm32-wasip3 %s -### 
-mno-component-model-thread-context 2>&1 | FileCheck 
-check-prefix=WASIP3_DISABLED %s
+
+// WASIP3_DISABLED-NOT: "--component-model-thread-context"
+// WASIP3_DISABLED: "-target-feature" "-component-model-thread-context"
\ No newline at end of file

>From 9092b93263908b886ff513a76febb59306907034 Mon Sep 17 00:00:00 2001
From: Sy Brand <[email protected]>
Date: Tue, 17 Feb 2026 17:12:56 +0000
Subject: [PATCH 12/12] Formatting

---
 clang/lib/Driver/ToolChains/WebAssembly.cpp | 17 +++++++----------
 1 file changed, 7 insertions(+), 10 deletions(-)

diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp 
b/clang/lib/Driver/ToolChains/WebAssembly.cpp
index eaa049237ce19..0d4cac5eeb6ae 100644
--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp
+++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp
@@ -31,13 +31,12 @@ std::string WebAssembly::getMultiarchTriple(const Driver &D,
                                             const llvm::Triple &TargetTriple,
                                             StringRef SysRoot) const {
   return (TargetTriple.getArchName() + "-" +
-          TargetTriple.getOSAndEnvironmentName())
-      .str();
+          TargetTriple.getOSAndEnvironmentName()).str();
 }
 
 std::string wasm::Linker::getLinkerPath(const ArgList &Args) const {
   const ToolChain &ToolChain = getToolChain();
-  if (const Arg *A = Args.getLastArg(options::OPT_fuse_ld_EQ)) {
+  if (const Arg* A = Args.getLastArg(options::OPT_fuse_ld_EQ)) {
     StringRef UseLinker = A->getValue();
     if (!UseLinker.empty()) {
       if (llvm::sys::path::is_absolute(UseLinker) &&
@@ -249,9 +248,9 @@ void wasm::Linker::ConstructJob(Compilation &C, const 
JobAction &JA,
 /// Given a base library directory, append path components to form the
 /// LTO directory.
 static std::string AppendLTOLibDir(const std::string &Dir) {
-  // The version allows the path to be keyed to the specific version of
-  // LLVM in used, as the bitcode format is not stable.
-  return Dir + "/llvm-lto/" LLVM_VERSION_STRING;
+    // The version allows the path to be keyed to the specific version of
+    // LLVM in used, as the bitcode format is not stable.
+    return Dir + "/llvm-lto/" LLVM_VERSION_STRING;
 }
 
 WebAssembly::WebAssembly(const Driver &D, const llvm::Triple &Triple,
@@ -524,8 +523,7 @@ void WebAssembly::AddClangSystemIncludeArgs(const ArgList 
&DriverArgs,
   if (getTriple().getOS() != llvm::Triple::UnknownOS) {
     const std::string MultiarchTriple =
         getMultiarchTriple(D, getTriple(), D.SysRoot);
-    addSystemInclude(DriverArgs, CC1Args,
-                     D.SysRoot + "/include/" + MultiarchTriple);
+    addSystemInclude(DriverArgs, CC1Args, D.SysRoot + "/include/" + 
MultiarchTriple);
   }
   addSystemInclude(DriverArgs, CC1Args, D.SysRoot + "/include");
 }
@@ -654,6 +652,5 @@ void WebAssembly::addLibStdCXXIncludePaths(
   // Second add the generic one.
   addSystemInclude(DriverArgs, CC1Args, LibPath + "/c++/" + Version);
   // Third the backward one.
-  addSystemInclude(DriverArgs, CC1Args,
-                   LibPath + "/c++/" + Version + "/backward");
+  addSystemInclude(DriverArgs, CC1Args, LibPath + "/c++/" + Version + 
"/backward");
 }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to