[clang] [lld] [llvm] [WebAssembly] WASIP3 and component model threading support (PR #175800)

Sy Brand via cfe-commits cfe-commits at lists.llvm.org
Tue Mar 3 08:21:52 PST 2026


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 <sy.brand at fastly.com>
Date: Mon, 5 Jan 2026 14:43:56 +0000
Subject: [PATCH 01/64] 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 <sy.brand at fastly.com>
Date: Fri, 9 Jan 2026 12:24:41 +0000
Subject: [PATCH 02/64] 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 <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 08:03:38 +0000
Subject: [PATCH 03/64] 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 <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 12:26:43 +0000
Subject: [PATCH 04/64] 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 <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 14:16:17 +0000
Subject: [PATCH 05/64] 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 <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 14:26:15 +0000
Subject: [PATCH 06/64] 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 <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 15:16:34 +0000
Subject: [PATCH 07/64] 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 <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 15:18:27 +0000
Subject: [PATCH 08/64] 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 <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 15:43:16 +0000
Subject: [PATCH 09/64] 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 <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 16:47:01 +0000
Subject: [PATCH 10/64] 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 <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 16:58:11 +0000
Subject: [PATCH 11/64] 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 <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 17:12:56 +0000
Subject: [PATCH 12/64] 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");
 }

>From 81ffb2b323bb1c757e88523d46b0289e8935e2b3 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 17:13:44 +0000
Subject: [PATCH 13/64] Formatting

---
 clang/lib/Driver/ToolChains/WebAssembly.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp b/clang/lib/Driver/ToolChains/WebAssembly.cpp
index 0d4cac5eeb6ae..bb70887c00ade 100644
--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp
+++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp
@@ -30,8 +30,8 @@ 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 {

>From a9e73101f8858721bd9781317f86607f2c6f8a54 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 17:14:41 +0000
Subject: [PATCH 14/64] Revert arch change

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

diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp b/clang/lib/Driver/ToolChains/WebAssembly.cpp
index bb70887c00ade..e8214c80d5f6f 100644
--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp
+++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp
@@ -94,13 +94,10 @@ void wasm::Linker::ConstructJob(Compilation &C, const JobAction &JA,
   ArgStringList CmdArgs;
 
   CmdArgs.push_back("-m");
-  std::string arch;
   if (ToolChain.getTriple().isArch64Bit())
-    arch = "wasm64";
+    CmdArgs.push_back("wasm64");
   else
-    arch = "wasm32";
-
-  CmdArgs.push_back(Args.MakeArgString(arch));
+    CmdArgs.push_back("wasm32");
 
   if (Args.hasArg(options::OPT_s))
     CmdArgs.push_back("--strip-all");

>From c369bb2ec1340fca42c9d1b05677c8c60125f617 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 17:15:21 +0000
Subject: [PATCH 15/64] Formatting

---
 lld/wasm/Driver.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 24d1637f702a3..9c6e5ef555b5a 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -656,7 +656,7 @@ 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.
   if (auto *arg = args.getLastArg(OPT_m)) {
     StringRef s = arg->getValue();
     if (s == "wasm32")

>From d3688381869e7616a7eb6918add87e259a479568 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 17:16:05 +0000
Subject: [PATCH 16/64] Revert formatting change

---
 lld/wasm/Driver.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 9c6e5ef555b5a..c59f79b5a4b6e 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -892,7 +892,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");

>From 61f03e50ad420fc99ffe7cd453a9c5e35ea92e9c Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 17:17:33 +0000
Subject: [PATCH 17/64] Revert formatting change

---
 .../MCTargetDesc/WebAssemblyMCTargetDesc.h    | 242 +++++++++---------
 1 file changed, 121 insertions(+), 121 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h
index 5c6d07ba88c61..5dc0e3aa91622 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;

>From c9665c1f4ee21d51206dfa9fbaf24fe0bd94ba3c Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 17:20:06 +0000
Subject: [PATCH 18/64] Revert formatting changes

---
 llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp |  5 ++---
 .../Target/WebAssembly/WebAssemblyFrameLowering.cpp   | 11 +++++++----
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
index 8019f1d37b3b1..d87fbfb64cfae 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
@@ -498,7 +498,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));
@@ -607,8 +607,7 @@ 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 5389318ad0e28..f54bdd0dedb35 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
@@ -188,7 +188,8 @@ 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;
@@ -212,13 +213,15 @@ 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;
@@ -321,7 +324,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);

>From 07e122d7e70c71349100efd7dbe1d9ec92c484a0 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 17:34:53 +0000
Subject: [PATCH 19/64] Revert formatting changes

---
 lld/wasm/Driver.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index c59f79b5a4b6e..81cc99c4bb287 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -1063,15 +1063,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

>From 651c362ae6bb6bc5aa6b325efa2a4eafac0adc33 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 17:35:25 +0000
Subject: [PATCH 20/64] Revert formatting changes

---
 lld/wasm/Relocations.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lld/wasm/Relocations.cpp b/lld/wasm/Relocations.cpp
index cb597fdeffcf3..046a04fa726ce 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())

>From 0175bd56857849abd25bf61d07c6128f2b21a446 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 17:35:46 +0000
Subject: [PATCH 21/64] Revert formatting changes

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

diff --git a/lld/wasm/Symbols.cpp b/lld/wasm/Symbols.cpp
index 97a9871a06308..f2040441e6257 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,7 +223,9 @@ 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))
@@ -411,7 +413,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);
 }
 

>From 9a93078408a3277e61a7faa1926787b2884ac285 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 17:36:44 +0000
Subject: [PATCH 22/64] Revert formatting changes

---
 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 27fe215b6143b..f71aeef7975f3 100644
--- a/lld/wasm/SyntheticSections.cpp
+++ b/lld/wasm/SyntheticSections.cpp
@@ -466,7 +466,8 @@ 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())
@@ -647,7 +648,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;
   }

>From 85fab66cd4a511ab6b20792485b170876e51a767 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 17:38:10 +0000
Subject: [PATCH 23/64] Revert formatting changes

---
 lld/wasm/Writer.cpp | 9 +++------
 1 file changed, 3 insertions(+), 6 deletions(-)

diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 431169d3fdd39..b5496f8c4a208 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -311,8 +311,7 @@ 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);
 }
 
@@ -359,8 +358,7 @@ 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;
@@ -384,7 +382,6 @@ 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));
@@ -1196,7 +1193,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;

>From e577185ea0e9179b347f2b68f23abf85a4d79b7e Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 17:38:38 +0000
Subject: [PATCH 24/64] Revert formatting changes

---
 lld/wasm/WriterUtils.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/lld/wasm/WriterUtils.cpp b/lld/wasm/WriterUtils.cpp
index f68964ac6f990..541863f800488 100644
--- a/lld/wasm/WriterUtils.cpp
+++ b/lld/wasm/WriterUtils.cpp
@@ -98,8 +98,7 @@ void writeSleb128(raw_ostream &os, int64_t number, const Twine &msg) {
   encodeSLEB128(number, os);
 }
 
-void writeBytes(raw_ostream &os, const char *bytes, size_t count,
-                const Twine &msg) {
+void writeBytes(raw_ostream &os, const char *bytes, size_t count, const Twine &msg) {
   debugWrite(os.tell(), msg + " [data[" + Twine(count) + "]]");
   os.write(bytes, count);
 }

>From 446d56ded155ba9c3d46c1db4268f3183743fbac Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 17 Feb 2026 17:40:25 +0000
Subject: [PATCH 25/64] Revert formatting changes

---
 llvm/include/llvm/MC/MCSymbolWasm.h                      | 8 ++++++--
 llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp | 6 +++---
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/llvm/include/llvm/MC/MCSymbolWasm.h b/llvm/include/llvm/MC/MCSymbolWasm.h
index 11cbce5b0ccd0..5c9f14e5e6d64 100644
--- a/llvm/include/llvm/MC/MCSymbolWasm.h
+++ b/llvm/include/llvm/MC/MCSymbolWasm.h
@@ -54,12 +54,16 @@ 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/WebAssemblyFrameLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
index f54bdd0dedb35..eb176161d1fdf 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
@@ -188,7 +188,7 @@ unsigned WebAssemblyFrameLowering::getFPReg(const MachineFunction &MF) {
              : WebAssembly::FP32;
 }
 
-unsigned 
+unsigned
 WebAssemblyFrameLowering::getOpcConst(const MachineFunction &MF) {
   return MF.getSubtarget<WebAssemblySubtarget>().hasAddr64()
              ? WebAssembly::CONST_I64
@@ -213,14 +213,14 @@ unsigned WebAssemblyFrameLowering::getOpcAnd(const MachineFunction &MF) {
              : WebAssembly::AND_I32;
 }
 
-unsigned 
+unsigned
 WebAssemblyFrameLowering::getOpcGlobGet(const MachineFunction &MF) {
   return MF.getSubtarget<WebAssemblySubtarget>().hasAddr64()
              ? WebAssembly::GLOBAL_GET_I64
              : WebAssembly::GLOBAL_GET_I32;
 }
 
-unsigned 
+unsigned
 WebAssemblyFrameLowering::getOpcGlobSet(const MachineFunction &MF) {
   return MF.getSubtarget<WebAssemblySubtarget>().hasAddr64()
              ? WebAssembly::GLOBAL_SET_I64

>From 3f10fe8cf65143e22b930d53700135c027758d75 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 10:30:52 +0000
Subject: [PATCH 26/64] Remove linker flag

---
 clang/lib/Basic/Targets/WebAssembly.cpp     |  4 ++
 clang/lib/Basic/Targets/WebAssembly.h       |  3 --
 clang/lib/Driver/ToolChains/WebAssembly.cpp | 55 ++++++++++-----------
 lld/wasm/Config.h                           | 16 ++++--
 lld/wasm/Driver.cpp                         | 21 ++++----
 lld/wasm/Relocations.cpp                    |  2 +-
 lld/wasm/SymbolTable.cpp                    | 52 +++++++++++++++++++
 lld/wasm/SymbolTable.h                      |  2 +
 lld/wasm/SyntheticSections.cpp              |  6 +--
 lld/wasm/Writer.cpp                         | 22 +++------
 lld/wasm/WriterUtils.cpp                    |  4 +-
 11 files changed, 117 insertions(+), 70 deletions(-)

diff --git a/clang/lib/Basic/Targets/WebAssembly.cpp b/clang/lib/Basic/Targets/WebAssembly.cpp
index 785bec699f925..bced84f317c76 100644
--- a/clang/lib/Basic/Targets/WebAssembly.cpp
+++ b/clang/lib/Basic/Targets/WebAssembly.cpp
@@ -215,6 +215,10 @@ bool WebAssemblyTargetInfo::initFeatureMap(
     addBleedingEdgeFeatures();
   }
 
+  if (getTriple().getOSName() == "wasip3") {
+    Features["component-model-thread-context"] = true;
+  }
+
   return TargetInfo::initFeatureMap(Features, Diags, CPU, FeaturesVec);
 }
 
diff --git a/clang/lib/Basic/Targets/WebAssembly.h b/clang/lib/Basic/Targets/WebAssembly.h
index 95d2f9ace8d56..524849f2eb767 100644
--- a/clang/lib/Basic/Targets/WebAssembly.h
+++ b/clang/lib/Basic/Targets/WebAssembly.h
@@ -109,9 +109,6 @@ 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 e8214c80d5f6f..dc66113de9af2 100644
--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp
+++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp
@@ -167,14 +167,6 @@ 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);
@@ -319,36 +311,39 @@ void WebAssembly::addClangTargetOptions(const ArgList &DriverArgs,
                           options::OPT_fno_use_init_array, true))
     CC1Args.push_back("-fno-use-init-array");
 
-  // '-pthread' implies atomics, bulk-memory, mutable-globals, and sign-ext
+  // '-pthread' implies bulk-memory, and shared memory is also used,
+  // also implies atomics, mutable-globals, and sign-ext.
   if (WantsPthread(getTriple(), DriverArgs)) {
-    if (DriverArgs.hasFlag(options::OPT_mno_atomics, options::OPT_matomics,
-                           false))
-      getDriver().Diag(diag::err_drv_argument_not_allowed_with)
-          << "-pthread"
-          << "-mno-atomics";
     if (DriverArgs.hasFlag(options::OPT_mno_bulk_memory,
                            options::OPT_mbulk_memory, false))
       getDriver().Diag(diag::err_drv_argument_not_allowed_with)
           << "-pthread"
           << "-mno-bulk-memory";
-    if (DriverArgs.hasFlag(options::OPT_mno_mutable_globals,
-                           options::OPT_mmutable_globals, false))
-      getDriver().Diag(diag::err_drv_argument_not_allowed_with)
-          << "-pthread"
-          << "-mno-mutable-globals";
-    if (DriverArgs.hasFlag(options::OPT_mno_sign_ext, options::OPT_msign_ext,
-                           false))
-      getDriver().Diag(diag::err_drv_argument_not_allowed_with)
-          << "-pthread"
-          << "-mno-sign-ext";
-    CC1Args.push_back("-target-feature");
-    CC1Args.push_back("+atomics");
     CC1Args.push_back("-target-feature");
     CC1Args.push_back("+bulk-memory");
-    CC1Args.push_back("-target-feature");
-    CC1Args.push_back("+mutable-globals");
-    CC1Args.push_back("-target-feature");
-    CC1Args.push_back("+sign-ext");
+    if (WantsSharedMemory(getTriple(), DriverArgs)) {
+      if (DriverArgs.hasFlag(options::OPT_mno_atomics, options::OPT_matomics,
+                             false))
+        getDriver().Diag(diag::err_drv_argument_not_allowed_with)
+            << "-pthread"
+            << "-mno-atomics";
+      if (DriverArgs.hasFlag(options::OPT_mno_mutable_globals,
+                             options::OPT_mmutable_globals, false))
+        getDriver().Diag(diag::err_drv_argument_not_allowed_with)
+            << "-pthread"
+            << "-mno-mutable-globals";
+      if (DriverArgs.hasFlag(options::OPT_mno_sign_ext, options::OPT_msign_ext,
+                             false))
+        getDriver().Diag(diag::err_drv_argument_not_allowed_with)
+            << "-pthread"
+            << "-mno-sign-ext";
+      CC1Args.push_back("-target-feature");
+      CC1Args.push_back("+atomics");
+      CC1Args.push_back("-target-feature");
+      CC1Args.push_back("+mutable-globals");
+      CC1Args.push_back("-target-feature");
+      CC1Args.push_back("+sign-ext");
+    }
   }
 
   if (!DriverArgs.hasFlag(options::OPT_mmutable_globals,
diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index 0cda43f84e7ce..5514b1d6d7de2 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -51,12 +51,9 @@ 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 || componentModelThreadContext; }
-
   bool allowMultipleDefinition;
   bool bsymbolic;
   bool checkFeatures;
-  bool componentModelThreadContext;
   bool compressRelocations;
   bool demangle;
   bool disableVerify;
@@ -138,6 +135,8 @@ struct Config {
   llvm::SmallVector<uint8_t, 0> buildIdVector;
 };
 
+enum class ThreadContextAbi { Undetermined, Globals, ComponentModelBuiltins };
+
 // The Ctx object hold all other (non-configuration) global state.
 struct Ctx {
   Config arg;
@@ -283,8 +282,19 @@ struct Ctx {
                     0>
       whyExtractRecords;
 
+  ThreadContextAbi threadContextAbi = ThreadContextAbi::Undetermined;
+
   Ctx();
   void reset();
+  bool componentModelThreadContext() const {
+    return threadContextAbi == ThreadContextAbi::ComponentModelBuiltins;
+  }
+  bool globalsThreadContext() const {
+    // Use the global thread context ABI by default, even if we can't determine
+    // the ABI from the object files passed.
+    return !componentModelThreadContext();
+  }
+  bool isMultithreaded() const { return componentModelThreadContext() || arg.sharedMemory; }
 };
 
 extern Ctx ctx;
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 81cc99c4bb287..b1aacfe680451 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -705,9 +705,6 @@ 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);
 }
 
@@ -831,8 +828,8 @@ static void checkOptions(opt::InputArgList &args) {
       error("--table-base may not be used with -shared/-pie");
   }
 
-  if (ctx.arg.sharedMemory && ctx.arg.componentModelThreadContext) {
-    error("--shared-memory is incompatible with component model thread context intrinsics");
+  if (ctx.arg.sharedMemory && ctx.componentModelThreadContext()) {
+    error("--shared-memory is currently incompatible with component model thread context intrinsics");
   }
 }
 
@@ -966,7 +963,7 @@ static void createSyntheticSymbols() {
   bool is64 = ctx.arg.is64.value_or(false);
 
   auto stack_pointer_name =
-      ctx.arg.componentModelThreadContext ? "__init_stack_pointer" : "__stack_pointer";
+      ctx.componentModelThreadContext() ? "__init_stack_pointer" : "__stack_pointer";
   if (ctx.isPic) {
     ctx.sym.stackPointer =
         createUndefinedGlobal(stack_pointer_name, ctx.arg.is64.value_or(false)
@@ -988,9 +985,9 @@ static void createSyntheticSymbols() {
     ctx.sym.stackPointer->markLive();
   }
 
-  if (ctx.arg.isMultithreaded()) {
+  if (ctx.isMultithreaded()) {
     // TLS symbols are all hidden/dso-local
-    auto tls_base_name = ctx.arg.componentModelThreadContext ? "__init_tls_base" : "__tls_base";
+    auto tls_base_name = ctx.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,
@@ -1001,7 +998,7 @@ static void createSyntheticSymbols() {
         "__wasm_init_tls", WASM_SYMBOL_VISIBILITY_HIDDEN,
         make<SyntheticFunction>(is64 ? i64ArgSignature : i32ArgSignature,
                                 "__wasm_init_tls"));
-    if (ctx.arg.componentModelThreadContext) {
+    if (ctx.componentModelThreadContext()) {
       ctx.sym.tlsBase->markLive();
       ctx.sym.tlsSize->markLive();
       ctx.sym.tlsAlign->markLive();
@@ -1054,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.componentModelThreadContext)
+  if (!ctx.arg.sharedMemory && !ctx.componentModelThreadContext())
     ctx.sym.tlsBase = createOptionalGlobal("__tls_base", false);
 }
 
@@ -1398,14 +1395,14 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
     ctx.arg.requiredExports.push_back(arg->getValue());
   }
 
-  createSyntheticSymbols();
-
   // Add all files to the symbol table. This will add almost all
   // symbols that we need to the symbol table.
   for (InputFile *f : files)
     symtab->addFile(f);
   if (errorCount())
     return;
+  
+  createSyntheticSymbols();
 
   // Handle the `--undefined <sym>` options.
   for (auto *arg : args.filtered(OPT_undefined))
diff --git a/lld/wasm/Relocations.cpp b/lld/wasm/Relocations.cpp
index 046a04fa726ce..4a1523a371d15 100644
--- a/lld/wasm/Relocations.cpp
+++ b/lld/wasm/Relocations.cpp
@@ -125,7 +125,7 @@ void scanRelocations(InputChunk *chunk) {
       // In single-threaded builds TLS is lowered away and TLS data can be
       // merged with normal data and allowing TLS relocation in non-TLS
       // segments.
-      if (ctx.arg.isMultithreaded()) {
+      if (ctx.isMultithreaded()) {
         if (!sym->isTLS()) {
           error(toString(file) + ": relocation " +
                 relocTypeToString(reloc.Type) +
diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp
index 011e4341519cd..74e8f6440778a 100644
--- a/lld/wasm/SymbolTable.cpp
+++ b/lld/wasm/SymbolTable.cpp
@@ -69,6 +69,7 @@ void SymbolTable::addFile(InputFile *file, StringRef symName) {
   auto *f = cast<ObjFile>(file);
   f->parse(false);
   ctx.objectFiles.push_back(f);
+  validateThreadContextAbi(f);
 }
 
 // This function is where all the optimizations of link-time
@@ -91,6 +92,57 @@ void SymbolTable::compileBitcodeFiles() {
     auto *obj = cast<ObjFile>(file);
     obj->parse(true);
     ctx.objectFiles.push_back(obj);
+    validateThreadContextAbi(obj);
+  }
+}
+
+void SymbolTable::validateThreadContextAbi(const ObjFile *obj) {
+  // Check for the component-model-thread-context feature and validate that all
+  // files are consistent in whether they use it or disallow it. 
+
+  // A complication is that a user may attempt to link together object files
+  // compiled with different versions of LLVM, where one does not specifiy
+  // -component-model-thread-context when using the global thread context ABI.
+  // They may also attempt to link object files with the global ABI compiled with
+  // older LLVM versions, but link them with a newer wasm-ld. To ensure the correct behavior
+  // in both of these cases, we treat the presence of an __stack_pointer global as an indication 
+  // that the global thread context ABI is being used, even if the component-model-thread-context 
+  // feature is not disallowed. 
+
+  auto targetFeatures = obj->getWasmObj()->getTargetFeatures();
+  auto threadContextFeature = llvm::find_if(targetFeatures,
+                            [](const auto &f) {
+                              return f.Name == "component-model-thread-context";
+                            });
+
+  bool usesComponentModelThreadContext = threadContextFeature != targetFeatures.end() &&
+                                  threadContextFeature->Prefix == WASM_FEATURE_PREFIX_USED;
+
+  if (threadContextFeature == targetFeatures.end()) {
+    // If the feature is not explicitly used or disallowed, check for the presence of __stack_pointer
+    // to determine if the global thread context ABI is being used.
+    auto sym = find("__stack_pointer");
+    if (!sym || !sym->isDefined()) {
+      // No __stack_pointer global, so this is probably an object file compiled from assembly or
+      // some other source that doesn't care about the thread context ABI. As such, we let it pass.
+      return;
+    }
+  }
+
+  if (usesComponentModelThreadContext) {
+    if (ctx.threadContextAbi == ThreadContextAbi::Undetermined) {
+      ctx.threadContextAbi = ThreadContextAbi::ComponentModelBuiltins;
+    } else if (ctx.threadContextAbi != ThreadContextAbi::ComponentModelBuiltins) {
+      error("thread context ABI mismatch: " + obj->getName() +
+            " uses component-model-thread-context but other files disallow it");
+    }
+  } else {
+    if (ctx.threadContextAbi == ThreadContextAbi::Undetermined) {
+      ctx.threadContextAbi = ThreadContextAbi::Globals;
+    } else if (ctx.threadContextAbi != ThreadContextAbi::Globals) {
+      error("thread context ABI mismatch: " + obj->getName() +
+            " disallows component-model-thread-context but other files use it"); 
+    }
   }
 }
 
diff --git a/lld/wasm/SymbolTable.h b/lld/wasm/SymbolTable.h
index 5d09d8b685716..ef6119223c9c8 100644
--- a/lld/wasm/SymbolTable.h
+++ b/lld/wasm/SymbolTable.h
@@ -104,6 +104,8 @@ class SymbolTable {
   void handleWeakUndefines();
   DefinedFunction *createUndefinedStub(const WasmSignature &sig);
 
+  void validateThreadContextAbi(const ObjFile *obj);
+
 private:
   std::pair<Symbol *, bool> insert(StringRef name, const InputFile *file);
   std::pair<Symbol *, bool> insertName(StringRef name);
diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp
index f71aeef7975f3..d315b544fe9dd 100644
--- a/lld/wasm/SyntheticSections.cpp
+++ b/lld/wasm/SyntheticSections.cpp
@@ -523,7 +523,7 @@ void GlobalSection::writeBody() {
         mutable_ = true;
       // With multi-threading any TLS globals must be mutable since they get
       // set during `__wasm_apply_global_tls_relocs`
-      if (ctx.arg.isMultithreaded() && sym->isTLS())
+      if (ctx.isMultithreaded() && sym->isTLS())
         mutable_ = true;
     }
     WasmGlobalType type{itype, mutable_};
@@ -564,7 +564,7 @@ void GlobalSection::writeBody() {
         // `__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.isMultithreaded()), is64);
+            intConst(d->getVA(/*absolute=*/!ctx.isMultithreaded()), is64);
       else if (auto *f = dyn_cast<FunctionSymbol>(sym))
         initExpr = intConst(f->isStub ? 0 : f->getTableIndex(), is64);
       else {
@@ -665,7 +665,7 @@ void DataCountSection::writeBody() {
 }
 
 bool DataCountSection::isNeeded() const {
-  return numSegments && ctx.arg.isMultithreaded();
+  return numSegments && ctx.isMultithreaded();
 }
 
 void LinkingSection::writeBody() {
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index b5496f8c4a208..5b470f9c77020 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -647,17 +647,7 @@ void Writer::populateTargetFeatures() {
               "' 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"] +
-            " 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.");
-
-  if (tlsUsed && !ctx.arg.componentModelThreadContext) {
+  if (tlsUsed && !ctx.componentModelThreadContext()) {
     for (auto feature : {"atomics", "bulk-memory"})
       if (!allowed.contains(feature))
         error(StringRef("'") + feature +
@@ -1039,7 +1029,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.isMultithreaded())
+  if (ctx.isMultithreaded())
     s->initFlags = WASM_DATA_SEGMENT_IS_PASSIVE;
   if (!ctx.arg.relocatable && name.starts_with(".bss"))
     s->isBss = true;
@@ -1173,14 +1163,14 @@ void Writer::createSyntheticInitFunctions() {
         "__wasm_init_memory", WASM_SYMBOL_VISIBILITY_HIDDEN,
         make<SyntheticFunction>(nullSignature, "__wasm_init_memory"));
     ctx.sym.initMemory->markLive();
-    if (ctx.arg.isMultithreaded()) {
+    if (ctx.isMultithreaded()) {
       // This global is assigned during  __wasm_init_memory in the shared memory
       // case.
       ctx.sym.tlsBase->markLive();
     }
   }
 
-  if (ctx.arg.isMultithreaded()) {
+  if (ctx.isMultithreaded()) {
     if (out.globalSec->needsTLSRelocations()) {
       ctx.sym.applyGlobalTLSRelocs = symtab->addSyntheticFunction(
           "__wasm_apply_global_tls_relocs", WASM_SYMBOL_VISIBILITY_HIDDEN,
@@ -1431,7 +1421,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.isMultithreaded() && s->isTLS())
+        if (ctx.isMultithreaded() && s->isTLS())
           continue;
         // data.drop instruction
         writeU8(os, WASM_OPCODE_MISC_PREFIX, "bulk-memory prefix");
@@ -1640,7 +1630,7 @@ void Writer::createInitTLSFunction() {
     if (tlsSeg) {
       // 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) {
+      if (!ctx.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 541863f800488..f877149a77a94 100644
--- a/lld/wasm/WriterUtils.cpp
+++ b/lld/wasm/WriterUtils.cpp
@@ -272,7 +272,7 @@ void writeExport(raw_ostream &os, const WasmExport &export_) {
 }
 
 void writeGetTLSBase(const Ctx &ctx, raw_ostream &os) {
-  if (ctx.arg.componentModelThreadContext) {
+  if (ctx.componentModelThreadContext()) {
     writeU8(os, WASM_OPCODE_CALL, "call");
     writeUleb128(os, ctx.sym.contextGet1->getFunctionIndex(), "function index");
   } else {
@@ -282,7 +282,7 @@ void writeGetTLSBase(const Ctx &ctx, raw_ostream &os) {
 }
 
 void writeSetTLSBase(const Ctx &ctx, raw_ostream &os) {
-  if (ctx.arg.componentModelThreadContext) {
+  if (ctx.componentModelThreadContext()) {
     writeU8(os, WASM_OPCODE_CALL, "call");
     writeUleb128(os, ctx.sym.contextSet1->getFunctionIndex(), "function index");
   } else {

>From dbae3efdcb2c3124a236b287f164bbdce1459606 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 12:34:33 +0000
Subject: [PATCH 27/64] Determine thread context ABI automatically

---
 .../disallow-component-model-thread-context.s |  6 ++
 lld/test/wasm/Inputs/stack-pointer.s          | 11 +++
 .../use-component-model-thread-context.s      |  6 ++
 lld/test/wasm/archive-export.test             |  6 +-
 lld/test/wasm/comdats.ll                      |  6 +-
 ...nent-model-threading-features-disallowed.s | 20 ----
 .../wasm/component-model-threading-features.s | 20 ----
 lld/test/wasm/mutable-global-exports.s        | 12 +--
 lld/test/wasm/pie.s                           |  4 +
 lld/test/wasm/shared-weak-symbols.s           |  8 +-
 lld/test/wasm/stack-pointer-abi.s             | 16 +--
 lld/test/wasm/thread-context-abi-mismatch.s   | 28 ++++++
 lld/test/wasm/visibility-hidden.ll            |  6 +-
 lld/test/wasm/weak-undefined-pic.s            | 10 ++
 lld/wasm/Config.h                             | 13 +--
 lld/wasm/Driver.cpp                           | 99 ++++++++++++++++---
 lld/wasm/SymbolTable.cpp                      | 52 ----------
 lld/wasm/SymbolTable.h                        |  2 -
 lld/wasm/Writer.cpp                           |  4 +-
 lld/wasm/WriterUtils.cpp                      |  4 +-
 20 files changed, 187 insertions(+), 146 deletions(-)
 create mode 100644 lld/test/wasm/Inputs/disallow-component-model-thread-context.s
 create mode 100644 lld/test/wasm/Inputs/stack-pointer.s
 create mode 100644 lld/test/wasm/Inputs/use-component-model-thread-context.s
 delete mode 100644 lld/test/wasm/component-model-threading-features-disallowed.s
 delete mode 100644 lld/test/wasm/component-model-threading-features.s
 create mode 100644 lld/test/wasm/thread-context-abi-mismatch.s

diff --git a/lld/test/wasm/Inputs/disallow-component-model-thread-context.s b/lld/test/wasm/Inputs/disallow-component-model-thread-context.s
new file mode 100644
index 0000000000000..1af0292d2ec4b
--- /dev/null
+++ b/lld/test/wasm/Inputs/disallow-component-model-thread-context.s
@@ -0,0 +1,6 @@
+# Mark the feature as DISALLOWED
+.section  .custom_section.target_features,"",@
+  .int8 1
+  .int8 45
+  .int8 30
+  .ascii  "component-model-thread-context"
\ No newline at end of file
diff --git a/lld/test/wasm/Inputs/stack-pointer.s b/lld/test/wasm/Inputs/stack-pointer.s
new file mode 100644
index 0000000000000..cfd76578a5e07
--- /dev/null
+++ b/lld/test/wasm/Inputs/stack-pointer.s
@@ -0,0 +1,11 @@
+.globaltype __stack_pointer, i32
+
+.globl  _start
+_start:
+  .functype _start () -> (i32)
+  global.get __stack_pointer
+  i32.const 16
+  i32.sub
+  drop
+  i32.const 0
+  end_function
\ No newline at end of file
diff --git a/lld/test/wasm/Inputs/use-component-model-thread-context.s b/lld/test/wasm/Inputs/use-component-model-thread-context.s
new file mode 100644
index 0000000000000..1c9554edb7e55
--- /dev/null
+++ b/lld/test/wasm/Inputs/use-component-model-thread-context.s
@@ -0,0 +1,6 @@
+# Mark the feature as USED
+.section  .custom_section.target_features,"",@
+  .int8 1
+  .int8 43
+  .int8 30
+  .ascii  "component-model-thread-context"
\ No newline at end of file
diff --git a/lld/test/wasm/archive-export.test b/lld/test/wasm/archive-export.test
index c67e500e46dd2..1432214fb1dc2 100644
--- a/lld/test/wasm/archive-export.test
+++ b/lld/test/wasm/archive-export.test
@@ -14,9 +14,6 @@ CHECK:         Exports:
 CHECK-NEXT:       - Name:            memory
 CHECK-NEXT:         Kind:            MEMORY
 CHECK-NEXT:         Index:           0
-CHECK-NEXT:       - Name:            __stack_pointer
-CHECK-NEXT:         Kind:            GLOBAL
-CHECK-NEXT:         Index:           0
 CHECK-NEXT:       - Name:            foo
 CHECK-NEXT:         Kind:            FUNCTION
 CHECK-NEXT:         Index:           1
@@ -29,6 +26,9 @@ CHECK-NEXT:         Index:           3
 CHECK-NEXT:       - Name:            _start
 CHECK-NEXT:         Kind:            FUNCTION
 CHECK-NEXT:         Index:           0
+CHECK-NEXT:       - Name:            __stack_pointer
+CHECK-NEXT:         Kind:            GLOBAL
+CHECK-NEXT:         Index:           0
 CHECK-NEXT:   - Type:            CODE
 
 NOEXPORT:         Exports:
diff --git a/lld/test/wasm/comdats.ll b/lld/test/wasm/comdats.ll
index 1662a983698ac..249ad279f8acc 100644
--- a/lld/test/wasm/comdats.ll
+++ b/lld/test/wasm/comdats.ll
@@ -35,9 +35,6 @@ entry:
 ; CHECK-NEXT:      - Name:            memory
 ; CHECK-NEXT:        Kind:            MEMORY
 ; CHECK-NEXT:        Index:           0
-; CHECK-NEXT:      - Name:            __stack_pointer
-; CHECK-NEXT:        Kind:            GLOBAL
-; CHECK-NEXT:        Index:           0
 ; CHECK-NEXT:      - Name:            _start
 ; CHECK-NEXT:        Kind:            FUNCTION
 ; CHECK-NEXT:        Index:           1
@@ -53,6 +50,9 @@ entry:
 ; CHECK-NEXT:      - Name:            callComdatFn2
 ; CHECK-NEXT:        Kind:            FUNCTION
 ; CHECK-NEXT:        Index:           5
+; CHECK-NEXT:      - Name:            __stack_pointer
+; CHECK-NEXT:        Kind:            GLOBAL
+; CHECK-NEXT:        Index:           0
 ; CHECK-NEXT:  - Type:            ELEM
 ; CHECK-NEXT:    Segments:
 ; CHECK-NEXT:      - Offset:
diff --git a/lld/test/wasm/component-model-threading-features-disallowed.s b/lld/test/wasm/component-model-threading-features-disallowed.s
deleted file mode 100644
index 9644b7e7caedd..0000000000000
--- a/lld/test/wasm/component-model-threading-features-disallowed.s
+++ /dev/null
@@ -1,20 +0,0 @@
-# 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
deleted file mode 100644
index dd617c6f8fec5..0000000000000
--- a/lld/test/wasm/component-model-threading-features.s
+++ /dev/null
@@ -1,20 +0,0 @@
-# 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"
diff --git a/lld/test/wasm/mutable-global-exports.s b/lld/test/wasm/mutable-global-exports.s
index 3cf1ae746b30a..af2dd428d8d78 100644
--- a/lld/test/wasm/mutable-global-exports.s
+++ b/lld/test/wasm/mutable-global-exports.s
@@ -57,12 +57,12 @@ _start:
 # CHECK-SP-NEXT:      - Name:            memory
 # CHECK-SP-NEXT:        Kind:            MEMORY
 # CHECK-SP-NEXT:        Index:           0
-# CHECK-SP-NEXT:      - Name:            __stack_pointer
-# CHECK-SP-NEXT:        Kind:            GLOBAL
-# CHECK-SP-NEXT:        Index:           0
 # CHECK-SP-NEXT:      - Name:            _start
 # CHECK-SP-NEXT:        Kind:            FUNCTION
 # CHECK-SP-NEXT:        Index:           0
+# CHECK-SP-NEXT:      - Name:            __stack_pointer
+# CHECK-SP-NEXT:        Kind:            GLOBAL
+# CHECK-SP-NEXT:        Index:           0
 # CHECK-SP-NEXT:  - Type:            CODE
 
 #      CHECK-ALL:  - Type:            EXPORT
@@ -73,9 +73,6 @@ _start:
 # CHECK-ALL-NEXT:      - Name:            __wasm_call_ctors
 # CHECK-ALL-NEXT:        Kind:            FUNCTION
 # CHECK-ALL-NEXT:        Index:           0
-# CHECK-ALL-NEXT:      - Name:            __stack_pointer
-# CHECK-ALL-NEXT:        Kind:            GLOBAL
-# CHECK-ALL-NEXT:        Index:           0
 # CHECK-ALL-NEXT:      - Name:            _start
 # CHECK-ALL-NEXT:        Kind:            FUNCTION
 # CHECK-ALL-NEXT:        Index:           1
@@ -85,6 +82,9 @@ _start:
 # CHECK-ALL-NEXT:      - Name:            bar_global
 # CHECK-ALL-NEXT:        Kind:            GLOBAL
 # CHECK-ALL-NEXT:        Index:           5
+# CHECK-ALL-NEXT:      - Name:            __stack_pointer
+# CHECK-ALL-NEXT:        Kind:            GLOBAL
+# CHECK-ALL-NEXT:        Index:           0
 # CHECK-ALL-NEXT:      - Name:            __dso_handle
 # CHECK-ALL-NEXT:        Kind:            GLOBAL
 # CHECK-ALL-NEXT:        Index:           6
diff --git a/lld/test/wasm/pie.s b/lld/test/wasm/pie.s
index 21eac79207318..41f405062fba9 100644
--- a/lld/test/wasm/pie.s
+++ b/lld/test/wasm/pie.s
@@ -98,6 +98,10 @@ _start:
 # CHECK-NEXT:         GlobalType:      I32
 # CHECK-NEXT:         GlobalMutable:   true
 # CHECK-NEXT:       - Module:          env
+# CHECK-NEXT:         Field:           external_func
+# CHECK-NEXT:         Kind:            FUNCTION
+# CHECK-NEXT:         SigIndex:        1
+# CHECK-NEXT:       - Module:          env
 # CHECK-NEXT:         Field:           __memory_base
 # CHECK-NEXT:         Kind:            GLOBAL
 # CHECK-NEXT:         GlobalType:      I32
diff --git a/lld/test/wasm/shared-weak-symbols.s b/lld/test/wasm/shared-weak-symbols.s
index df049ce4600fe..62c46d8b9285f 100644
--- a/lld/test/wasm/shared-weak-symbols.s
+++ b/lld/test/wasm/shared-weak-symbols.s
@@ -42,6 +42,10 @@ call_weak:
 # CHECK-NEXT:        Memory:
 # CHECK-NEXT:          Minimum:         0x0
 # CHECK-NEXT:      - Module:          env
+# CHECK-NEXT:        Field:           weak_func
+# CHECK-NEXT:        Kind:            FUNCTION
+# CHECK-NEXT:        SigIndex:        0
+# CHECK-NEXT:      - Module:          env
 # CHECK-NEXT:        Field:           __memory_base
 # CHECK-NEXT:        Kind:            GLOBAL
 # CHECK-NEXT:        GlobalType:      I32
@@ -51,10 +55,6 @@ call_weak:
 # CHECK-NEXT:        Kind:            GLOBAL
 # CHECK-NEXT:        GlobalType:      I32
 # CHECK-NEXT:        GlobalMutable:   false
-# CHECK-NEXT:      - Module:          env
-# CHECK-NEXT:        Field:           weak_func
-# CHECK-NEXT:        Kind:            FUNCTION
-# CHECK-NEXT:        SigIndex:        0
 # CHECK-NEXT:  - Type:            FUNCTION
 
 # CHECK:        - Type:            EXPORT
diff --git a/lld/test/wasm/stack-pointer-abi.s b/lld/test/wasm/stack-pointer-abi.s
index 11355a9c4586e..fdffe30ce295a 100644
--- a/lld/test/wasm/stack-pointer-abi.s
+++ b/lld/test/wasm/stack-pointer-abi.s
@@ -1,13 +1,15 @@
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t-component-model.o %S/Inputs/use-component-model-thread-context.s
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t-global.o %S/Inputs/disallow-component-model-thread-context.s
 # 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
+# RUN: wasm-ld --component-model-thread-context -o %t-component-model.wasm %t-component-model.o %t.o
+# RUN: obj2yaml %t-component-model.wasm | FileCheck %s --check-prefix=COMPONENT-MODEL
+# RUN: wasm-ld -o %t-original.wasm %t-global.o %t.o
+# RUN: obj2yaml %t-original.wasm | FileCheck %s --check-prefix=GLOBAL
 
-.globl _start
+  .globl  _start
 _start:
   .functype _start () -> ()
   end_function
 
-# WITH: Name: __init_stack_pointer
-# WITHOUT: Name: __stack_pointer
\ No newline at end of file
+# COMPONENT-MODEL: Name: __init_stack_pointer
+# GLOBAL: Name: __stack_pointer
\ No newline at end of file
diff --git a/lld/test/wasm/thread-context-abi-mismatch.s b/lld/test/wasm/thread-context-abi-mismatch.s
new file mode 100644
index 0000000000000..3fe89a353134b
--- /dev/null
+++ b/lld/test/wasm/thread-context-abi-mismatch.s
@@ -0,0 +1,28 @@
+# Test that linking object files with mismatched thread context ABIs fails with an error.
+
+# Test that the presence of an import of __stack_pointer from the env module is treated 
+# as an indication that the global thread context ABI is being used, even if the
+# component-model-thread-context feature is not disallowed.
+
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t-with.o %s
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t-without.o %S/Inputs/stack-pointer.s
+# RUN: not wasm-ld %t-with.o %t-without.o -o %t.wasm 2>&1 | FileCheck %s
+
+# Test that explicitly disallowing the component-model-thread-context feature causes linking to fail 
+# with an error when other files use the feature.
+
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t-disallow.o %S/Inputs/disallow-component-model-thread-context.s
+# RUN: not wasm-ld %t-with.o %t-disallow.o -o %t.wasm 2>&1 | FileCheck %s
+
+# CHECK: error: thread context ABI mismatch: {{.*}} disallows component-model-thread-context but other files use it 
+.globl _start
+_start:
+  .functype _start () -> ()
+  end_function
+
+# Mark the feature as USED
+.section  .custom_section.target_features,"",@
+  .int8 1
+  .int8 43
+  .int8 30
+  .ascii  "component-model-thread-context"
diff --git a/lld/test/wasm/visibility-hidden.ll b/lld/test/wasm/visibility-hidden.ll
index 6ed7ba3afdc02..657748676849d 100644
--- a/lld/test/wasm/visibility-hidden.ll
+++ b/lld/test/wasm/visibility-hidden.ll
@@ -43,9 +43,6 @@ entry:
 ; CHECK-NEXT:       - Name:            memory
 ; CHECK-NEXT:         Kind:            MEMORY
 ; CHECK-NEXT:         Index:           0
-; CHECK-NEXT:       - Name:            __stack_pointer
-; CHECK-NEXT:         Kind:            GLOBAL
-; CHECK-NEXT:         Index:           0
 ; CHECK-NEXT:       - Name:            objectDefault
 ; CHECK-NEXT:         Kind:            FUNCTION
 ; CHECK-NEXT:         Index:           1
@@ -55,6 +52,9 @@ entry:
 ; CHECK-NEXT:       - Name:            archiveDefault
 ; CHECK-NEXT:         Kind:            FUNCTION
 ; CHECK-NEXT:         Index:           4
+; CHECK-NEXT:       - Name:            __stack_pointer
+; CHECK-NEXT:         Kind:            GLOBAL
+; CHECK-NEXT:         Index:           0
 ; CHECK-NEXT:   - Type:
 
 
diff --git a/lld/test/wasm/weak-undefined-pic.s b/lld/test/wasm/weak-undefined-pic.s
index 1a3a1715b4bb9..4be482c72728d 100644
--- a/lld/test/wasm/weak-undefined-pic.s
+++ b/lld/test/wasm/weak-undefined-pic.s
@@ -81,6 +81,16 @@ _start:
 #      IMPORT:        Field:           foo
 # IMPORT-NEXT:        Kind:            FUNCTION
 # IMPORT-NEXT:        SigIndex:        0
+# IMPORT-NEXT:      - Module:          env
+# IMPORT-NEXT:        Field:           __memory_base
+# IMPORT-NEXT:        Kind:            GLOBAL
+# IMPORT-NEXT:        GlobalType:      I32
+# IMPORT-NEXT:        GlobalMutable:   false
+# IMPORT-NEXT:      - Module:          env
+# IMPORT-NEXT:        Field:           __table_base
+# IMPORT-NEXT:        Kind:            GLOBAL
+# IMPORT-NEXT:        GlobalType:      I32
+# IMPORT-NEXT:        GlobalMutable:   false
 # IMPORT-NEXT:      - Module:          GOT.func
 # IMPORT-NEXT:        Field:           foo
 # IMPORT-NEXT:        Kind:            GLOBAL
diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index 5514b1d6d7de2..2c4954e68bab4 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -282,19 +282,12 @@ struct Ctx {
                     0>
       whyExtractRecords;
 
-  ThreadContextAbi threadContextAbi = ThreadContextAbi::Undetermined;
+  // Whether to use component model thread context intrinsics for the stack pointer and TLS base.
+  bool componentModelThreadContext = false;
 
   Ctx();
   void reset();
-  bool componentModelThreadContext() const {
-    return threadContextAbi == ThreadContextAbi::ComponentModelBuiltins;
-  }
-  bool globalsThreadContext() const {
-    // Use the global thread context ABI by default, even if we can't determine
-    // the ABI from the object files passed.
-    return !componentModelThreadContext();
-  }
-  bool isMultithreaded() const { return componentModelThreadContext() || arg.sharedMemory; }
+  bool isMultithreaded() const { return componentModelThreadContext || arg.sharedMemory; }
 };
 
 extern Ctx ctx;
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index b1aacfe680451..e1609b2cd6eb2 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -828,7 +828,7 @@ static void checkOptions(opt::InputArgList &args) {
       error("--table-base may not be used with -shared/-pie");
   }
 
-  if (ctx.arg.sharedMemory && ctx.componentModelThreadContext()) {
+  if (ctx.arg.sharedMemory && ctx.componentModelThreadContext) {
     error("--shared-memory is currently incompatible with component model thread context intrinsics");
   }
 }
@@ -942,12 +942,23 @@ static DefinedGlobal *createOptionalGlobal(StringRef name, bool isMutable) {
   return symtab->addOptionalGlobalSymbol(name, g);
 }
 
-// Create ABI-defined synthetic symbols
-static void createSyntheticSymbols() {
+// Create ABI-defined synthetic symbols that are needed early, before LTO.
+static void createEarlySyntheticSymbols() {
   if (ctx.arg.relocatable)
     return;
 
   static WasmSignature nullSignature = {{}, {}};
+  ctx.sym.callCtors = symtab->addSyntheticFunction(
+      "__wasm_call_ctors", WASM_SYMBOL_VISIBILITY_HIDDEN,
+      make<SyntheticFunction>(nullSignature, "__wasm_call_ctors"));
+}
+
+// Create synthetic symbols that rely on information that is only available
+// after LTO, e.g. __stack_pointer, __tls_base.
+static void createPostLTOSymbols() {
+  if (ctx.arg.relocatable)
+    return;
+
   static WasmSignature i32ArgSignature = {{}, {ValType::I32}};
   static WasmSignature i64ArgSignature = {{}, {ValType::I64}};
   static llvm::wasm::WasmGlobalType globalTypeI32 = {WASM_TYPE_I32, false};
@@ -956,14 +967,11 @@ static void createSyntheticSymbols() {
                                                             true};
   static llvm::wasm::WasmGlobalType mutableGlobalTypeI64 = {WASM_TYPE_I64,
                                                             true};
-  ctx.sym.callCtors = symtab->addSyntheticFunction(
-      "__wasm_call_ctors", WASM_SYMBOL_VISIBILITY_HIDDEN,
-      make<SyntheticFunction>(nullSignature, "__wasm_call_ctors"));
 
   bool is64 = ctx.arg.is64.value_or(false);
 
   auto stack_pointer_name =
-      ctx.componentModelThreadContext() ? "__init_stack_pointer" : "__stack_pointer";
+      ctx.componentModelThreadContext ? "__init_stack_pointer" : "__stack_pointer";
   if (ctx.isPic) {
     ctx.sym.stackPointer =
         createUndefinedGlobal(stack_pointer_name, ctx.arg.is64.value_or(false)
@@ -987,7 +995,7 @@ static void createSyntheticSymbols() {
 
   if (ctx.isMultithreaded()) {
     // TLS symbols are all hidden/dso-local
-    auto tls_base_name = ctx.componentModelThreadContext() ? "__init_tls_base" : "__tls_base";
+    auto tls_base_name = ctx.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,
@@ -998,7 +1006,7 @@ static void createSyntheticSymbols() {
         "__wasm_init_tls", WASM_SYMBOL_VISIBILITY_HIDDEN,
         make<SyntheticFunction>(is64 ? i64ArgSignature : i32ArgSignature,
                                 "__wasm_init_tls"));
-    if (ctx.componentModelThreadContext()) {
+    if (ctx.componentModelThreadContext) {
       ctx.sym.tlsBase->markLive();
       ctx.sym.tlsSize->markLive();
       ctx.sym.tlsAlign->markLive();
@@ -1051,7 +1059,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.componentModelThreadContext())
+  if (!ctx.arg.sharedMemory && !ctx.componentModelThreadContext)
     ctx.sym.tlsBase = createOptionalGlobal("__tls_base", false);
 }
 
@@ -1306,6 +1314,67 @@ static void checkZOptions(opt::InputArgList &args) {
       warn("unknown -z value: " + StringRef(arg->getValue()));
 }
 
+// Determine the thread context ABI based on object file features.
+// This must be called after LTO, since LTO object files are needed.
+static void determineThreadContextAbi(ArrayRef<ObjFile *> files) {
+  // A complication is that a user may attempt to link together object files
+  // compiled with different versions of LLVM, where one does not specifiy
+  // -component-model-thread-context when using the global thread context ABI.
+  // They may also attempt to link object files with the global ABI compiled with
+  // older LLVM versions, but link them with a newer wasm-ld. To ensure the correct behavior
+  // in both of these cases, we treat the import of a __stack_pointer global from the env module
+  // as an indication that the global thread context ABI is being used.
+
+  enum class ThreadContextAbi {
+    Undetermined,
+    ComponentModelBuiltins,
+    Globals
+  };
+
+  ThreadContextAbi threadContextAbi = ThreadContextAbi::Undetermined;
+
+  for (ObjFile *obj : files) {
+    auto targetFeatures = obj->getWasmObj()->getTargetFeatures();
+    auto threadContextFeature = llvm::find_if(targetFeatures,
+                              [](const auto &f) {
+                                return f.Name == "component-model-thread-context";
+                              });
+
+    bool usesComponentModelThreadContext = threadContextFeature != targetFeatures.end() &&
+                                    threadContextFeature->Prefix == WASM_FEATURE_PREFIX_USED;
+
+    if (threadContextFeature == targetFeatures.end()) {
+      // If the feature is not explicitly used or disallowed, check for the presence of __stack_pointer
+      // to determine if the global thread context ABI is being used.
+      auto sym = symtab->find("__stack_pointer");
+      if (!sym || sym->kind() != Symbol::UndefinedGlobalKind || sym->importModule != "env") {
+        // No __stack_pointer import, so this is probably an object file compiled from assembly or
+        // some other source that doesn't care about the thread context ABI. As such, we let it pass.
+        break;
+      }
+    }
+
+    if (usesComponentModelThreadContext) {
+      if (threadContextAbi == ThreadContextAbi::Undetermined) {
+        threadContextAbi = ThreadContextAbi::ComponentModelBuiltins;
+      } else if (threadContextAbi != ThreadContextAbi::ComponentModelBuiltins) {
+        error("thread context ABI mismatch: " + obj->getName() +
+              " uses component-model-thread-context but other files disallow it");
+      }
+    } else {
+      if (threadContextAbi == ThreadContextAbi::Undetermined) {
+        threadContextAbi = ThreadContextAbi::Globals;
+      } else if (threadContextAbi != ThreadContextAbi::Globals) {
+        error("thread context ABI mismatch: " + obj->getName() +
+              " disallows component-model-thread-context but other files use it"); 
+      }
+    }
+  }
+ 
+  // If the ABI is undetermined at this point, default to the globals ABI
+  ctx.componentModelThreadContext = (threadContextAbi == ThreadContextAbi::ComponentModelBuiltins);
+}
+
 LinkerDriver::LinkerDriver(Ctx &ctx) : ctx(ctx) {}
 
 void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
@@ -1395,14 +1464,14 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
     ctx.arg.requiredExports.push_back(arg->getValue());
   }
 
+  createEarlySyntheticSymbols();
+
   // Add all files to the symbol table. This will add almost all
   // symbols that we need to the symbol table.
   for (InputFile *f : files)
     symtab->addFile(f);
   if (errorCount())
     return;
-  
-  createSyntheticSymbols();
 
   // Handle the `--undefined <sym>` options.
   for (auto *arg : args.filtered(OPT_undefined))
@@ -1480,6 +1549,12 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   if (errorCount())
     return;
 
+  // Now that LTO is complete and all object files are available, determine the 
+  // thread context ABI and create symbols (__stack_pointer, __tls_base, etc.) based
+  // on the determined ABI.
+  determineThreadContextAbi(ctx.objectFiles);
+  createPostLTOSymbols();
+
   // The LTO process can generate new undefined symbols, specifically libcall
   // functions.  Because those symbols might be declared in a stub library we
   // need the process the stub libraries once again after LTO to handle all
diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp
index 74e8f6440778a..011e4341519cd 100644
--- a/lld/wasm/SymbolTable.cpp
+++ b/lld/wasm/SymbolTable.cpp
@@ -69,7 +69,6 @@ void SymbolTable::addFile(InputFile *file, StringRef symName) {
   auto *f = cast<ObjFile>(file);
   f->parse(false);
   ctx.objectFiles.push_back(f);
-  validateThreadContextAbi(f);
 }
 
 // This function is where all the optimizations of link-time
@@ -92,57 +91,6 @@ void SymbolTable::compileBitcodeFiles() {
     auto *obj = cast<ObjFile>(file);
     obj->parse(true);
     ctx.objectFiles.push_back(obj);
-    validateThreadContextAbi(obj);
-  }
-}
-
-void SymbolTable::validateThreadContextAbi(const ObjFile *obj) {
-  // Check for the component-model-thread-context feature and validate that all
-  // files are consistent in whether they use it or disallow it. 
-
-  // A complication is that a user may attempt to link together object files
-  // compiled with different versions of LLVM, where one does not specifiy
-  // -component-model-thread-context when using the global thread context ABI.
-  // They may also attempt to link object files with the global ABI compiled with
-  // older LLVM versions, but link them with a newer wasm-ld. To ensure the correct behavior
-  // in both of these cases, we treat the presence of an __stack_pointer global as an indication 
-  // that the global thread context ABI is being used, even if the component-model-thread-context 
-  // feature is not disallowed. 
-
-  auto targetFeatures = obj->getWasmObj()->getTargetFeatures();
-  auto threadContextFeature = llvm::find_if(targetFeatures,
-                            [](const auto &f) {
-                              return f.Name == "component-model-thread-context";
-                            });
-
-  bool usesComponentModelThreadContext = threadContextFeature != targetFeatures.end() &&
-                                  threadContextFeature->Prefix == WASM_FEATURE_PREFIX_USED;
-
-  if (threadContextFeature == targetFeatures.end()) {
-    // If the feature is not explicitly used or disallowed, check for the presence of __stack_pointer
-    // to determine if the global thread context ABI is being used.
-    auto sym = find("__stack_pointer");
-    if (!sym || !sym->isDefined()) {
-      // No __stack_pointer global, so this is probably an object file compiled from assembly or
-      // some other source that doesn't care about the thread context ABI. As such, we let it pass.
-      return;
-    }
-  }
-
-  if (usesComponentModelThreadContext) {
-    if (ctx.threadContextAbi == ThreadContextAbi::Undetermined) {
-      ctx.threadContextAbi = ThreadContextAbi::ComponentModelBuiltins;
-    } else if (ctx.threadContextAbi != ThreadContextAbi::ComponentModelBuiltins) {
-      error("thread context ABI mismatch: " + obj->getName() +
-            " uses component-model-thread-context but other files disallow it");
-    }
-  } else {
-    if (ctx.threadContextAbi == ThreadContextAbi::Undetermined) {
-      ctx.threadContextAbi = ThreadContextAbi::Globals;
-    } else if (ctx.threadContextAbi != ThreadContextAbi::Globals) {
-      error("thread context ABI mismatch: " + obj->getName() +
-            " disallows component-model-thread-context but other files use it"); 
-    }
   }
 }
 
diff --git a/lld/wasm/SymbolTable.h b/lld/wasm/SymbolTable.h
index ef6119223c9c8..5d09d8b685716 100644
--- a/lld/wasm/SymbolTable.h
+++ b/lld/wasm/SymbolTable.h
@@ -104,8 +104,6 @@ class SymbolTable {
   void handleWeakUndefines();
   DefinedFunction *createUndefinedStub(const WasmSignature &sig);
 
-  void validateThreadContextAbi(const ObjFile *obj);
-
 private:
   std::pair<Symbol *, bool> insert(StringRef name, const InputFile *file);
   std::pair<Symbol *, bool> insertName(StringRef name);
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 5b470f9c77020..25330f45c82dc 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -647,7 +647,7 @@ void Writer::populateTargetFeatures() {
               "' feature must be used in order to use shared memory");
   }
 
-  if (tlsUsed && !ctx.componentModelThreadContext()) {
+  if (tlsUsed && !ctx.componentModelThreadContext) {
     for (auto feature : {"atomics", "bulk-memory"})
       if (!allowed.contains(feature))
         error(StringRef("'") + feature +
@@ -1630,7 +1630,7 @@ void Writer::createInitTLSFunction() {
     if (tlsSeg) {
       // 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.componentModelThreadContext()) {
+      if (!ctx.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 f877149a77a94..40c0883dc650e 100644
--- a/lld/wasm/WriterUtils.cpp
+++ b/lld/wasm/WriterUtils.cpp
@@ -272,7 +272,7 @@ void writeExport(raw_ostream &os, const WasmExport &export_) {
 }
 
 void writeGetTLSBase(const Ctx &ctx, raw_ostream &os) {
-  if (ctx.componentModelThreadContext()) {
+  if (ctx.componentModelThreadContext) {
     writeU8(os, WASM_OPCODE_CALL, "call");
     writeUleb128(os, ctx.sym.contextGet1->getFunctionIndex(), "function index");
   } else {
@@ -282,7 +282,7 @@ void writeGetTLSBase(const Ctx &ctx, raw_ostream &os) {
 }
 
 void writeSetTLSBase(const Ctx &ctx, raw_ostream &os) {
-  if (ctx.componentModelThreadContext()) {
+  if (ctx.componentModelThreadContext) {
     writeU8(os, WASM_OPCODE_CALL, "call");
     writeUleb128(os, ctx.sym.contextSet1->getFunctionIndex(), "function index");
   } else {

>From e8babc79196f0e32d4650b139d209b72912ff193 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 13:14:23 +0000
Subject: [PATCH 28/64] Fix comment

---
 clang/lib/Driver/ToolChains/WebAssembly.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp b/clang/lib/Driver/ToolChains/WebAssembly.cpp
index dc66113de9af2..53a22f3a9f9c6 100644
--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp
+++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp
@@ -311,7 +311,7 @@ void WebAssembly::addClangTargetOptions(const ArgList &DriverArgs,
                           options::OPT_fno_use_init_array, true))
     CC1Args.push_back("-fno-use-init-array");
 
-  // '-pthread' implies bulk-memory, and shared memory is also used,
+  // '-pthread' implies bulk-memory, and, if shared memory is also used,
   // also implies atomics, mutable-globals, and sign-ext.
   if (WantsPthread(getTriple(), DriverArgs)) {
     if (DriverArgs.hasFlag(options::OPT_mno_bulk_memory,
@@ -321,6 +321,7 @@ void WebAssembly::addClangTargetOptions(const ArgList &DriverArgs,
           << "-mno-bulk-memory";
     CC1Args.push_back("-target-feature");
     CC1Args.push_back("+bulk-memory");
+    
     if (WantsSharedMemory(getTriple(), DriverArgs)) {
       if (DriverArgs.hasFlag(options::OPT_mno_atomics, options::OPT_matomics,
                              false))

>From 822ea98e4a7c8bef60170e4d3d1ffc089c462f18 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 13:14:34 +0000
Subject: [PATCH 29/64] Test updates

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

diff --git a/clang/test/Driver/wasm-toolchain.c b/clang/test/Driver/wasm-toolchain.c
index 8654021e9a959..4a22ea8a02417 100644
--- a/clang/test/Driver/wasm-toolchain.c
+++ b/clang/test/Driver/wasm-toolchain.c
@@ -304,16 +304,6 @@
 // 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"
+// `-target=wasm32-wasip2` sets -component-model-thread-context
+// RUN: %clang --target=wasm32-wasip2 %s -### 2>&1 | FileCheck -check-prefix=WASIP3_DISABLED %s
 // WASIP3_DISABLED: "-target-feature" "-component-model-thread-context"
\ No newline at end of file

>From cb2fb8d14c2976c864d95a0ddc079c36efcdfc36 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 13:14:55 +0000
Subject: [PATCH 30/64] TLS fixes

---
 lld/wasm/Driver.cpp                           | 32 +++++++++----------
 lld/wasm/SyntheticSections.cpp                | 11 +------
 lld/wasm/Writer.cpp                           | 21 ++++--------
 .../AsmParser/WebAssemblyAsmParser.cpp        |  1 +
 4 files changed, 25 insertions(+), 40 deletions(-)

diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 5973ca47dd80e..a9f9566b3efb4 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -827,10 +827,6 @@ 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.componentModelThreadContext) {
-    error("--shared-memory is currently incompatible with component model thread context intrinsics");
-  }
 }
 
 static const char *getReproduceOption(opt::InputArgList &args) {
@@ -996,7 +992,7 @@ static void createPostLTOSymbols() {
   if (ctx.isMultithreaded()) {
     // TLS symbols are all hidden/dso-local
     auto tls_base_name = ctx.componentModelThreadContext ? "__init_tls_base" : "__tls_base";
-    ctx.sym.tlsBase = createGlobalVariable(tls_base_name, true,
+    ctx.sym.tlsBase = createGlobalVariable(tls_base_name, !ctx.componentModelThreadContext,
                                            WASM_SYMBOL_VISIBILITY_HIDDEN);
     ctx.sym.tlsSize = createGlobalVariable("__tls_size", false,
                                            WASM_SYMBOL_VISIBILITY_HIDDEN);
@@ -1010,6 +1006,7 @@ static void createPostLTOSymbols() {
       ctx.sym.tlsBase->markLive();
       ctx.sym.tlsSize->markLive();
       ctx.sym.tlsAlign->markLive();
+      ctx.sym.initTLS->markLive();
       static WasmSignature contextSet1Signature{{}, {ValType::I32}};
       ctx.sym.contextSet1 = createUndefinedFunction(
           "__wasm_component_model_builtin_context_set_1", "[context-set-1]",
@@ -1316,7 +1313,7 @@ static void checkZOptions(opt::InputArgList &args) {
 
 // Determine the thread context ABI based on object file features.
 // This must be called after LTO, since LTO object files are needed.
-static void determineThreadContextAbi(ArrayRef<ObjFile *> files) {
+static void determineThreadContextABI(ArrayRef<ObjFile *> files) {
   // A complication is that a user may attempt to link together object files
   // compiled with different versions of LLVM, where one does not specifiy
   // -component-model-thread-context when using the global thread context ABI.
@@ -1325,13 +1322,13 @@ static void determineThreadContextAbi(ArrayRef<ObjFile *> files) {
   // in both of these cases, we treat the import of a __stack_pointer global from the env module
   // as an indication that the global thread context ABI is being used.
 
-  enum class ThreadContextAbi {
+  enum class ThreadContextABI {
     Undetermined,
     ComponentModelBuiltins,
     Globals
   };
 
-  ThreadContextAbi threadContextAbi = ThreadContextAbi::Undetermined;
+  ThreadContextABI threadContextABI = ThreadContextABI::Undetermined;
 
   for (ObjFile *obj : files) {
     auto targetFeatures = obj->getWasmObj()->getTargetFeatures();
@@ -1355,16 +1352,16 @@ static void determineThreadContextAbi(ArrayRef<ObjFile *> files) {
     }
 
     if (usesComponentModelThreadContext) {
-      if (threadContextAbi == ThreadContextAbi::Undetermined) {
-        threadContextAbi = ThreadContextAbi::ComponentModelBuiltins;
-      } else if (threadContextAbi != ThreadContextAbi::ComponentModelBuiltins) {
+      if (threadContextABI == ThreadContextABI::Undetermined) {
+        threadContextABI = ThreadContextABI::ComponentModelBuiltins;
+      } else if (threadContextABI != ThreadContextABI::ComponentModelBuiltins) {
         error("thread context ABI mismatch: " + obj->getName() +
               " uses component-model-thread-context but other files disallow it");
       }
     } else {
-      if (threadContextAbi == ThreadContextAbi::Undetermined) {
-        threadContextAbi = ThreadContextAbi::Globals;
-      } else if (threadContextAbi != ThreadContextAbi::Globals) {
+      if (threadContextABI == ThreadContextABI::Undetermined) {
+        threadContextABI = ThreadContextABI::Globals;
+      } else if (threadContextABI != ThreadContextABI::Globals) {
         error("thread context ABI mismatch: " + obj->getName() +
               " disallows component-model-thread-context but other files use it"); 
       }
@@ -1372,7 +1369,10 @@ static void determineThreadContextAbi(ArrayRef<ObjFile *> files) {
   }
  
   // If the ABI is undetermined at this point, default to the globals ABI
-  ctx.componentModelThreadContext = (threadContextAbi == ThreadContextAbi::ComponentModelBuiltins);
+  ctx.componentModelThreadContext = (threadContextABI == ThreadContextABI::ComponentModelBuiltins);
+  if (ctx.arg.sharedMemory && ctx.componentModelThreadContext) {
+    error("--shared-memory is currently incompatible with component model thread context intrinsics");
+  }
 }
 
 LinkerDriver::LinkerDriver(Ctx &ctx) : ctx(ctx) {}
@@ -1552,7 +1552,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   // Now that LTO is complete and all object files are available, determine the 
   // thread context ABI and create symbols (__stack_pointer, __tls_base, etc.) based
   // on the determined ABI.
-  determineThreadContextAbi(ctx.objectFiles);
+  determineThreadContextABI(ctx.objectFiles);
   createPostLTOSymbols();
 
   // The LTO process can generate new undefined symbols, specifically libcall
diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp
index bd20a5d06216d..e00f769a21ce7 100644
--- a/lld/wasm/SyntheticSections.cpp
+++ b/lld/wasm/SyntheticSections.cpp
@@ -466,12 +466,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);
-<<<<<<< sy/wasip3
-  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;
->>>>>>> main
 
   for (const Symbol *sym : internalGotSymbols) {
     if (TLS != sym->isTLS())
@@ -669,9 +664,6 @@ void DataCountSection::writeBody() {
 }
 
 bool DataCountSection::isNeeded() const {
-<<<<<<< sy/wasip3
-  return numSegments && ctx.isMultithreaded();
-=======
   // The datacount section is only required under certain circumstance.
   // Specifically, when the module includes bulk memory instructions that deal
   // with passive data segments. i.e. memory.init/data.drop.
@@ -679,8 +671,7 @@ bool DataCountSection::isNeeded() const {
   // instructions are not yet supported in input files.  However, in the case
   // of shared memory, lld itself will generate these instructions as part of
   // `__wasm_init_memory`. See Writer::createInitMemoryFunction.
-  return numSegments && ctx.arg.sharedMemory;
->>>>>>> main
+  return numSegments && ctx.isMultithreaded();
 }
 
 void LinkingSection::writeBody() {
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 811aec740d446..23a9f3ddf8623 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -649,11 +649,13 @@ void Writer::populateTargetFeatures() {
               "' feature must be used in order to use shared memory");
   }
 
-  if (tlsUsed && !ctx.componentModelThreadContext) {
-    for (auto feature : {"atomics", "bulk-memory"})
-      if (!allowed.contains(feature))
-        error(StringRef("'") + feature +
-              "' feature must be used in order to use thread-local storage");
+  if (tlsUsed) {
+    if (!allowed.contains("bulk-memory")) {
+      error("bulk-memory feature must be used in order to use thread-local storage");
+    }
+    if (!ctx.componentModelThreadContext && !allowed.contains("atomics")) {
+      error("atomics feature must be used in order to use thread-local storage");
+    }
   }
 
   // Validate that used features are allowed in output
@@ -1185,11 +1187,7 @@ void Writer::createSyntheticInitFunctions() {
 
     auto hasTLSRelocs = [](const OutputSegment *segment) {
       if (segment->isTLS())
-<<<<<<< sy/wasip3
-        for (const auto* is : segment->inputSegments)
-=======
         for (const auto *is : segment->inputSegments)
->>>>>>> main
           if (is->getRelocations().size())
             return true;
       return false;
@@ -1644,13 +1642,8 @@ void Writer::createInitTLSFunction() {
         writeUleb128(os, ctx.sym.tlsBase->getGlobalIndex(), "global index");
       }
 
-<<<<<<< sy/wasip3
-      // 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.
->>>>>>> main
       writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get");
       writeUleb128(os, 0, "local index");
 
diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
index 25704c9ef0ac4..7a9f675548a5c 100644
--- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
+++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
@@ -430,6 +430,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
       return Name;
     }
     error("Expected string or identifier, got: ", Lexer.getTok());
+    return StringRef();
   }
 
   bool parseRegTypeList(SmallVectorImpl<wasm::ValType> &Types) {

>From 6cf7c2a3e1830100f7c38973bc015c7c690b7feb Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 13:17:02 +0000
Subject: [PATCH 31/64] TLS test

---
 lld/test/wasm/tls-component-model.s | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)
 create mode 100644 lld/test/wasm/tls-component-model.s

diff --git a/lld/test/wasm/tls-component-model.s b/lld/test/wasm/tls-component-model.s
new file mode 100644
index 0000000000000..d347ec0c1f2dc
--- /dev/null
+++ b/lld/test/wasm/tls-component-model.s
@@ -0,0 +1,29 @@
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
+# RUN: wasm-ld -o %t.wasm %t.o
+# RUN: obj2yaml %t.wasm | FileCheck %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t.wasm | FileCheck %s --check-prefix=DIS
+
+.globl _start
+_start:
+  .functype _start () -> (i32)
+  global.get tls_sym at GOT@TLS
+  end_function
+
+.section  .tdata.tls_sym,"",@
+.globl  tls_sym
+tls_sym:
+  .int32  1
+  .size tls_sym, 4
+
+.section  .custom_section.target_features,"",@
+  .int8 2
+  .int8 43
+  .int8 30
+  .ascii  "component-model-thread-context"
+  .int8 43
+  .int8 11
+  .ascii  "bulk-memory"
+
+
+# CHECK: Name: __init_tls_base
+# DIS: __wasm_init_tls
\ No newline at end of file

>From fa7eea8ec4af4a799efb592ce4ff8c211fb6e138 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 13:27:27 +0000
Subject: [PATCH 32/64] Continue rather than break when determiting thread
 context ABI

---
 lld/wasm/Driver.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index a9f9566b3efb4..f4924329e99c2 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -1347,7 +1347,7 @@ static void determineThreadContextABI(ArrayRef<ObjFile *> files) {
       if (!sym || sym->kind() != Symbol::UndefinedGlobalKind || sym->importModule != "env") {
         // No __stack_pointer import, so this is probably an object file compiled from assembly or
         // some other source that doesn't care about the thread context ABI. As such, we let it pass.
-        break;
+        continue;
       }
     }
 

>From cc1ea2f624645452e5aa28fff131c8ce89c56bfd Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 13:45:43 +0000
Subject: [PATCH 33/64] Only consider the current object file's symbols for
 determining thread context ABI

---
 lld/wasm/Driver.cpp | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index f4924329e99c2..53497685c2f6b 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -1342,14 +1342,20 @@ static void determineThreadContextABI(ArrayRef<ObjFile *> files) {
 
     if (threadContextFeature == targetFeatures.end()) {
       // If the feature is not explicitly used or disallowed, check for the presence of __stack_pointer
-      // to determine if the global thread context ABI is being used.
-      auto sym = symtab->find("__stack_pointer");
-      if (!sym || sym->kind() != Symbol::UndefinedGlobalKind || sym->importModule != "env") {
+      // import in this specific file to determine if the global thread context ABI is being used.
+      bool hasStackPointerImport = llvm::any_of(obj->getSymbols(), [](const auto &sym) {
+        return sym && sym->getName() == "__stack_pointer" && 
+               sym->kind() == Symbol::UndefinedGlobalKind &&
+               sym->importModule && sym->importModule == "env";
+      });
+      if (!hasStackPointerImport) {
         // No __stack_pointer import, so this is probably an object file compiled from assembly or
         // some other source that doesn't care about the thread context ABI. As such, we let it pass.
         continue;
       }
-    }
+      // Treat this as using the globals ABI
+      usesComponentModelThreadContext = false;
+    }     
 
     if (usesComponentModelThreadContext) {
       if (threadContextABI == ThreadContextABI::Undetermined) {

>From b9039ebfeb0966a6b9aa6f35d770f36572552fef Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 13:46:01 +0000
Subject: [PATCH 34/64] Typo

---
 lld/wasm/Driver.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 53497685c2f6b..1b2cab417c426 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -1341,7 +1341,7 @@ static void determineThreadContextABI(ArrayRef<ObjFile *> files) {
                                     threadContextFeature->Prefix == WASM_FEATURE_PREFIX_USED;
 
     if (threadContextFeature == targetFeatures.end()) {
-      // If the feature is not explicitly used or disallowed, check for the presence of __stack_pointer
+      // If the feature is not explicitly used or disallowed, check for the presence of a __stack_pointer
       // import in this specific file to determine if the global thread context ABI is being used.
       bool hasStackPointerImport = llvm::any_of(obj->getSymbols(), [](const auto &sym) {
         return sym && sym->getName() == "__stack_pointer" && 

>From 1b490cdd9578877336d653b45180ccf5fc71eb2f Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 13:46:48 +0000
Subject: [PATCH 35/64] Formatting

---
 lld/wasm/Driver.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 1b2cab417c426..19881d1ae50df 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -1376,6 +1376,7 @@ static void determineThreadContextABI(ArrayRef<ObjFile *> files) {
  
   // If the ABI is undetermined at this point, default to the globals ABI
   ctx.componentModelThreadContext = (threadContextABI == ThreadContextABI::ComponentModelBuiltins);
+  
   if (ctx.arg.sharedMemory && ctx.componentModelThreadContext) {
     error("--shared-memory is currently incompatible with component model thread context intrinsics");
   }

>From b55ae63edc37da979002ffd72c5a528caad25575 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 14:32:19 +0000
Subject: [PATCH 36/64] TLS fixes

---
 lld/test/wasm/tls-component-model.s | 2 +-
 lld/wasm/Driver.cpp                 | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/lld/test/wasm/tls-component-model.s b/lld/test/wasm/tls-component-model.s
index d347ec0c1f2dc..3f10d497b237f 100644
--- a/lld/test/wasm/tls-component-model.s
+++ b/lld/test/wasm/tls-component-model.s
@@ -1,5 +1,5 @@
 # RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
-# RUN: wasm-ld -o %t.wasm %t.o
+# RUN: wasm-ld -no-gc-sections -o %t.wasm %t.o
 # RUN: obj2yaml %t.wasm | FileCheck %s
 # RUN: llvm-objdump -d --no-show-raw-insn %t.wasm | FileCheck %s --check-prefix=DIS
 
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 19881d1ae50df..22a2c5ba17e07 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -1006,7 +1006,6 @@ static void createPostLTOSymbols() {
       ctx.sym.tlsBase->markLive();
       ctx.sym.tlsSize->markLive();
       ctx.sym.tlsAlign->markLive();
-      ctx.sym.initTLS->markLive();
       static WasmSignature contextSet1Signature{{}, {ValType::I32}};
       ctx.sym.contextSet1 = createUndefinedFunction(
           "__wasm_component_model_builtin_context_set_1", "[context-set-1]",
@@ -1376,7 +1375,7 @@ static void determineThreadContextABI(ArrayRef<ObjFile *> files) {
  
   // If the ABI is undetermined at this point, default to the globals ABI
   ctx.componentModelThreadContext = (threadContextABI == ThreadContextABI::ComponentModelBuiltins);
-  
+
   if (ctx.arg.sharedMemory && ctx.componentModelThreadContext) {
     error("--shared-memory is currently incompatible with component model thread context intrinsics");
   }

>From a1c563b9d2e251a0977727da86f7b2ed7df91276 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 15:01:09 +0000
Subject: [PATCH 37/64] Tighten up Toolchain driver

---
 clang/lib/Driver/ToolChains/WebAssembly.cpp | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp b/clang/lib/Driver/ToolChains/WebAssembly.cpp
index 53a22f3a9f9c6..6d3619b00471b 100644
--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp
+++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp
@@ -79,8 +79,17 @@ static bool WantsPthread(const llvm::Triple &Triple, const ArgList &Args) {
   return WantsPthread;
 }
 
+static bool WantsComponentModelThreadContext(const llvm::Triple &Triple, const ArgList &Args) {
+  // If the target is WASIP3, then enable the
+  // component-model-thread-context feature by default, unless explicitly
+  // disabled.
+  return Triple.getOSName() == "wasip3" &&
+         Args.hasFlag(options::OPT_mcomponent_model_thread_context,
+                     options::OPT_mno_component_model_thread_context, true);
+}
+
 static bool WantsSharedMemory(const llvm::Triple &Triple, const ArgList &Args) {
-  return WantsPthread(Triple, Args) && !TargetBuildsComponents(Triple);
+  return WantsPthread(Triple, Args) && !WantsComponentModelThreadContext(Triple, Args);
 }
 
 void wasm::Linker::ConstructJob(Compilation &C, const JobAction &JA,
@@ -321,7 +330,7 @@ void WebAssembly::addClangTargetOptions(const ArgList &DriverArgs,
           << "-mno-bulk-memory";
     CC1Args.push_back("-target-feature");
     CC1Args.push_back("+bulk-memory");
-    
+
     if (WantsSharedMemory(getTriple(), DriverArgs)) {
       if (DriverArgs.hasFlag(options::OPT_mno_atomics, options::OPT_matomics,
                              false))

>From 69654ef006db053c8e787a54b34ae35757dd7676 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 15:29:29 +0000
Subject: [PATCH 38/64] Fix clang test

---
 clang/test/Driver/wasm-toolchain.c | 10 +++-------
 1 file changed, 3 insertions(+), 7 deletions(-)

diff --git a/clang/test/Driver/wasm-toolchain.c b/clang/test/Driver/wasm-toolchain.c
index 4a22ea8a02417..a1c726a0aae09 100644
--- a/clang/test/Driver/wasm-toolchain.c
+++ b/clang/test/Driver/wasm-toolchain.c
@@ -77,13 +77,13 @@
 // '-pthread' sets +atomics, +bulk-memory, +mutable-globals, +sign-ext, and --shared-memory
 // RUN: %clang -### --target=wasm32-unknown-unknown --sysroot=/foo %s -pthread 2>&1 \
 // RUN:  | FileCheck -check-prefix=PTHREAD %s
-// PTHREAD: "-cc1" {{.*}} "-target-feature" "+atomics" "-target-feature" "+bulk-memory" "-target-feature" "+mutable-globals" "-target-feature" "+sign-ext"
+// PTHREAD: "-cc1" {{.*}} "-target-feature" "+bulk-memory" "-target-feature" "+atomics" "-target-feature" "+mutable-globals" "-target-feature" "+sign-ext"
 // PTHREAD: wasm-ld{{.*}}" "--shared-memory" "-lpthread"
 //
 // '-pthread' with '-nostdlib' should still set '--shared-memory' but not include '-lpthread'
 // RUN: %clang -### --target=wasm32-unknown-unknown --sysroot=/foo %s -pthread -nostdlib 2>&1 \
 // RUN:  | FileCheck -check-prefix=PTHREAD-NOSTDLIB %s
-// PTHREAD-NOSTDLIB: "-cc1" {{.*}} "-target-feature" "+atomics" "-target-feature" "+bulk-memory" "-target-feature" "+mutable-globals" "-target-feature" "+sign-ext"
+// PTHREAD-NOSTDLIB: "-cc1" {{.*}} "-target-feature" "+bulk-memory" "-target-feature" "+atomics" "-target-feature" "+mutable-globals" "-target-feature" "+sign-ext"
 // PTHREAD-NOSTDLIB: wasm-ld{{.*}}" "--shared-memory" "-o" "a.out"
 
 // '-pthread' not allowed with '-mno-atomics'
@@ -113,7 +113,7 @@
 // 'wasm32-wasi-threads' does the same thing as '-pthread'
 // RUN: %clang -### --target=wasm32-wasi-threads --sysroot=/foo %s 2>&1 \
 // RUN:  | FileCheck -check-prefix=WASI_THREADS %s
-// WASI_THREADS: "-cc1" {{.*}} "-target-feature" "+atomics" "-target-feature" "+bulk-memory" "-target-feature" "+mutable-globals" "-target-feature" "+sign-ext"
+// WASI_THREADS: "-cc1" {{.*}} "-target-feature" "+bulk-memory" "-target-feature" "+atomics" "-target-feature" "+mutable-globals" "-target-feature" "+sign-ext"
 // WASI_THREADS: wasm-ld{{.*}}" "--shared-memory" "-lpthread"
 
 // '-mllvm -emscripten-cxx-exceptions-allowed=foo,bar' sets
@@ -303,7 +303,3 @@
 // 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"
-
-// `-target=wasm32-wasip2` sets -component-model-thread-context
-// RUN: %clang --target=wasm32-wasip2 %s -### 2>&1 | FileCheck -check-prefix=WASIP3_DISABLED %s
-// WASIP3_DISABLED: "-target-feature" "-component-model-thread-context"
\ No newline at end of file

>From b98b9ab199b87cd597912d1871da807b28c4ab4b Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 15:39:04 +0000
Subject: [PATCH 39/64] Add wasm-target-features test for preprocessor defines

---
 clang/test/Preprocessor/wasm-target-features.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/clang/test/Preprocessor/wasm-target-features.c b/clang/test/Preprocessor/wasm-target-features.c
index 3edaf9e7cd594..86a2ebd59c115 100644
--- a/clang/test/Preprocessor/wasm-target-features.c
+++ b/clang/test/Preprocessor/wasm-target-features.c
@@ -239,3 +239,12 @@
 // RUN:   | FileCheck %s -check-prefix=WIDE-ARITHMETIC
 //
 // WIDE-ARITHMETIC: #define __wasm_wide_arithmetic__ 1{{$}}
+
+// RUN: %clang -E -dM %s -o - 2>&1 \
+// RUN:     -target wasm32-unknown-unknown -mcomponent-model-thread-context \
+// RUN:   | FileCheck %s -check-prefix=COMPONENT-MODEL-THREAD-CONTEXT
+// RUN: %clang -E -dM %s -o - 2>&1 \
+// RUN:     -target wasm64-unknown-unknown -mcomponent-model-thread-context \
+// RUN:   | FileCheck %s -check-prefix=COMPONENT-MODEL-THREAD-CONTEXT
+
+// COMPONENT-MODEL-THREAD-CONTEXT: #define __wasm_component_model_thread_context__ 1{{$}}
\ No newline at end of file

>From 7121ea5b659abd23af7e9d6a4ab3656bf729dc61 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 17:02:41 +0000
Subject: [PATCH 40/64] Remove debug code

---
 llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
index 7a9f675548a5c..07defee925877 100644
--- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
+++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
@@ -426,7 +426,6 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
     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());

>From 70b3b93176162d563e5f494557ebd16b8b1dfae6 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Wed, 18 Feb 2026 17:47:28 +0000
Subject: [PATCH 41/64] Complete asm printing/parsing changes

---
 .../AsmParser/WebAssemblyAsmParser.cpp        |  5 ++-
 .../WebAssemblyTargetStreamer.cpp             | 31 +++++++++++++++----
 llvm/test/MC/WebAssembly/export-name.s        | 12 +++++++
 llvm/test/MC/WebAssembly/import-module.s      | 14 +++++++++
 4 files changed, 53 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
index 07defee925877..b48abe85aca91 100644
--- a/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
+++ b/llvm/lib/Target/WebAssembly/AsmParser/WebAssemblyAsmParser.cpp
@@ -1058,7 +1058,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
         return ParseStatus::Failure;
       if (expect(AsmToken::Comma, ","))
         return ParseStatus::Failure;
-      auto ExportName = expectIdent();
+      auto ExportName = expectStringOrIdent();
       if (ExportName.empty())
         return ParseStatus::Failure;
       auto *WasmSym =
@@ -1074,8 +1074,7 @@ class WebAssemblyAsmParser final : public MCTargetAsmParser {
         return ParseStatus::Failure;
       if (expect(AsmToken::Comma, ","))
         return ParseStatus::Failure;
-
-      StringRef ImportModule = expectStringOrIdent();
+      auto ImportModule = expectStringOrIdent();
       if (ImportModule.empty())
         return ParseStatus::Failure;
       auto *WasmSym =
diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp
index c18de5fc1939e..916a42664279f 100644
--- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp
+++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp
@@ -22,6 +22,15 @@
 #include "llvm/Support/FormattedStream.h"
 using namespace llvm;
 
+static bool shouldQuoteName(StringRef Name) {
+  // Wasm export/import names and import module names can contain characters
+  // that are not allowed in identifiers, so we need to quote them.
+  auto isValidStartChar = [](char C) { return isalpha(C) || C == '_' || C == '.'; };
+  auto isValidChar = [&](char C) { return isValidStartChar(C) || isdigit(C); };
+  return !(Name.size() > 0 && isValidStartChar(Name.front())) || 
+        llvm::any_of(Name, [&](char C) { return !isValidChar(C); });
+}
+
 WebAssemblyTargetStreamer::WebAssemblyTargetStreamer(MCStreamer &S)
     : MCTargetStreamer(S) {}
 
@@ -94,22 +103,32 @@ void WebAssemblyTargetAsmStreamer::emitTagType(const MCSymbolWasm *Sym) {
   OS << "\n";
 }
 
+static void emitNameWithQuoting(formatted_raw_ostream &OS, StringRef Name) {
+  if (shouldQuoteName(Name))
+    OS << '"' << Name << '"';
+  else
+    OS << Name;
+}
+
 void WebAssemblyTargetAsmStreamer::emitImportModule(const MCSymbolWasm *Sym,
                                                     StringRef ImportModule) {
-  OS << "\t.import_module\t" << Sym->getName() << ", "
-                             << ImportModule << '\n';
+  OS << "\t.import_module\t" << Sym->getName() << ", ";
+  emitNameWithQuoting(OS, ImportModule);
+  OS << '\n';
 }
 
 void WebAssemblyTargetAsmStreamer::emitImportName(const MCSymbolWasm *Sym,
                                                   StringRef ImportName) {
-  OS << "\t.import_name\t" << Sym->getName() << ", "
-                           << ImportName << '\n';
+  OS << "\t.import_name\t" << Sym->getName() << ", ";
+  emitNameWithQuoting(OS, ImportName);
+  OS << '\n';
 }
 
 void WebAssemblyTargetAsmStreamer::emitExportName(const MCSymbolWasm *Sym,
                                                   StringRef ExportName) {
-  OS << "\t.export_name\t" << Sym->getName() << ", "
-                           << ExportName << '\n';
+  OS << "\t.export_name\t" << Sym->getName() << ", ";
+  emitNameWithQuoting(OS, ExportName);
+  OS << '\n';
 }
 
 void WebAssemblyTargetAsmStreamer::emitIndIdx(const MCExpr *Value) {
diff --git a/llvm/test/MC/WebAssembly/export-name.s b/llvm/test/MC/WebAssembly/export-name.s
index 51e1bcf73dba2..915cd3307a4b0 100644
--- a/llvm/test/MC/WebAssembly/export-name.s
+++ b/llvm/test/MC/WebAssembly/export-name.s
@@ -8,7 +8,14 @@ foo:
     .export_name foo, bar
     end_function
 
+baz:
+    .globl baz
+    .functype baz () -> ()
+    .export_name baz, "[baz]"
+    end_function
+
 # CHECK: .export_name foo, bar
+# CHECK: .export_name baz, "[baz]"
 
 # CHECK-OBJ:        - Type:            EXPORT
 # CHECK-OBJ-NEXT:     Exports:
@@ -24,3 +31,8 @@ foo:
 # CHECK-OBJ-NEXT:         Name:            foo
 # CHECK-OBJ-NEXT:         Flags:           [ EXPORTED ]
 # CHECK-OBJ-NEXT:         Function:        0
+# CHECK-OBJ-NEXT:       - Index:           1
+# CHECK-OBJ-NEXT:         Kind:            FUNCTION
+# CHECK-OBJ-NEXT:         Name:            baz
+# CHECK-OBJ-NEXT:         Flags:           [ EXPORTED ]
+# CHECK-OBJ-NEXT:         Function:        1
diff --git a/llvm/test/MC/WebAssembly/import-module.s b/llvm/test/MC/WebAssembly/import-module.s
index 5d28d5b9c0b92..75090001755a9 100644
--- a/llvm/test/MC/WebAssembly/import-module.s
+++ b/llvm/test/MC/WebAssembly/import-module.s
@@ -3,16 +3,22 @@
 
 .functype foo () -> ()
 .functype plain () -> ()
+.functype __wasm_component_model_builtin_context_get_0 () -> (i32)
 
 test:
   .functype test () -> ()
   call      foo
   call      plain
+  call     __wasm_component_model_builtin_context_get_0
+  drop
   end_function
 
   .import_module  foo, bar
   .import_name  foo, qux
 
+  .import_module __wasm_component_model_builtin_context_get_0, "$root"
+  .import_name __wasm_component_model_builtin_context_get_0, "[context-get-0]"
+
 # CHECK-ASM: .import_module  foo, bar
 # CHECK-ASM: .import_name  foo, qux
 
@@ -26,9 +32,17 @@ test:
 # CHECK-NEXT:         Field:           plain
 # CHECK-NEXT:         Kind:            FUNCTION
 
+# CHECK:            - Module:          '$root'
+# CHECK-NEXT:         Field:           '[context-get-0]'
+# CHECK-NEXT:         Kind:            FUNCTION
+# CHECK-NEXT:         SigIndex:        1
+
 # CHECK:        - Type:            CUSTOM
 # CHECK:              Name:            foo
 # CHECK-NEXT:         Flags:           [ UNDEFINED, EXPLICIT_NAME ]
 
 # CHECK:              Name:            plain
 # CHECK-NEXT:         Flags:           [ UNDEFINED ]
+
+# CHECK:              Name:            __wasm_component_model_builtin_context_get_0
+# CHECK-NEXT:         Flags:           [ UNDEFINED, EXPLICIT_NAME ]
\ No newline at end of file

>From 43f756f5618414baef33d5d77db028a9ce1f2656 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Fri, 20 Feb 2026 08:57:46 +0000
Subject: [PATCH 42/64] Clean up target feature decision

---
 clang/lib/Basic/Targets/WebAssembly.cpp              | 4 ----
 clang/lib/Driver/ToolChains/WebAssembly.cpp          | 5 +++++
 llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp | 8 --------
 3 files changed, 5 insertions(+), 12 deletions(-)

diff --git a/clang/lib/Basic/Targets/WebAssembly.cpp b/clang/lib/Basic/Targets/WebAssembly.cpp
index bced84f317c76..785bec699f925 100644
--- a/clang/lib/Basic/Targets/WebAssembly.cpp
+++ b/clang/lib/Basic/Targets/WebAssembly.cpp
@@ -215,10 +215,6 @@ bool WebAssemblyTargetInfo::initFeatureMap(
     addBleedingEdgeFeatures();
   }
 
-  if (getTriple().getOSName() == "wasip3") {
-    Features["component-model-thread-context"] = true;
-  }
-
   return TargetInfo::initFeatureMap(Features, Diags, CPU, FeaturesVec);
 }
 
diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp b/clang/lib/Driver/ToolChains/WebAssembly.cpp
index 6d3619b00471b..4618a62f01cd6 100644
--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp
+++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp
@@ -320,6 +320,11 @@ void WebAssembly::addClangTargetOptions(const ArgList &DriverArgs,
                           options::OPT_fno_use_init_array, true))
     CC1Args.push_back("-fno-use-init-array");
 
+  if (WantsComponentModelThreadContext(getTriple(), DriverArgs)) {
+    CC1Args.push_back("-target-feature");
+    CC1Args.push_back("+component-model-thread-context");
+  }
+
   // '-pthread' implies bulk-memory, and, if shared memory is also used,
   // also implies atomics, mutable-globals, and sign-ext.
   if (WantsPthread(getTriple(), DriverArgs)) {
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
index fa99249840dd9..b0a8be810caa5 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
@@ -40,14 +40,6 @@ WebAssemblySubtarget::initializeSubtargetDependencies(StringRef CPU,
 
   ParseSubtargetFeatures(CPU, /*TuneCPU*/ CPU, FS);  
   
-  // WASIP3 implies using the component model thread context intrinsics by default.
-  if (!FS.contains("component-model-thread-context") && 
-      !HasComponentModelThreadContext && 
-      TargetTriple.getOSName() == "wasip3") {
-    ToggleFeature(WebAssembly::FeatureComponentModelThreadContext);
-    HasComponentModelThreadContext = true;
-  }
-
   FeatureBitset Bits = getFeatureBits();
 
   // bulk-memory implies bulk-memory-opt

>From 4ffe62386876ceb65bb0a8cb808a045274bd190f Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Fri, 20 Feb 2026 09:01:36 +0000
Subject: [PATCH 43/64] Use llvm::Triple::WASIp3 rather than a string

---
 clang/lib/Driver/ToolChains/WebAssembly.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp b/clang/lib/Driver/ToolChains/WebAssembly.cpp
index 4618a62f01cd6..84b9b00bf4c23 100644
--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp
+++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp
@@ -83,7 +83,7 @@ static bool WantsComponentModelThreadContext(const llvm::Triple &Triple, const A
   // If the target is WASIP3, then enable the
   // component-model-thread-context feature by default, unless explicitly
   // disabled.
-  return Triple.getOSName() == "wasip3" &&
+  return Triple.getOS() == llvm::Triple::WASIp3 &&
          Args.hasFlag(options::OPT_mcomponent_model_thread_context,
                      options::OPT_mno_component_model_thread_context, true);
 }

>From 36c39bff010e1da9aca7f05632435b5dc12385b0 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Fri, 20 Feb 2026 09:16:39 +0000
Subject: [PATCH 44/64] More driver tests

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

diff --git a/clang/test/Driver/wasm-toolchain.c b/clang/test/Driver/wasm-toolchain.c
index a1c726a0aae09..7dbf1d6ce6aa5 100644
--- a/clang/test/Driver/wasm-toolchain.c
+++ b/clang/test/Driver/wasm-toolchain.c
@@ -303,3 +303,15 @@
 // 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"
+
+// `wasm32-wasip3` passes `+component-model-thread-context` by default.
+
+// RUN: %clang -### --target=wasm32-wasip3 --sysroot=/foo %s 2>&1 \
+// RUN:   | FileCheck -check-prefix=LINK_WASIP3_THREAD_CONTEXT %s
+// LINK_WASIP3_THREAD_CONTEXT: "-cc1" {{.*}} "-target-feature" "+component-model-thread-context"
+
+// `wasm32-wasip3` does not pass `+component-model-thread-context` when `-mno-component-model-thread-context` is used.
+
+// RUN: %clang -### --target=wasm32-wasip3 --sysroot=/foo -mno-component-model-thread-context %s 2>&1 \
+// RUN:   | FileCheck -check-prefix=LINK_WASIP3_NO_THREAD_CONTEXT %s
+// LINK_WASIP3_NO_THREAD_CONTEXT: "-cc1" {{.*}} "-target-feature" "-component-model-thread-context"

>From d9535100967bc9227e23d4ad18370449ec7ae478 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Fri, 20 Feb 2026 09:57:54 +0000
Subject: [PATCH 45/64] Fix tests

---
 .../CodeGen/WebAssembly/memory64-feature.ll   |  5 ++-
 llvm/test/CodeGen/WebAssembly/multivalue.ll   |  5 ++-
 .../CodeGen/WebAssembly/mutable-globals.ll    |  5 ++-
 .../CodeGen/WebAssembly/reference-types.ll    |  5 ++-
 llvm/test/CodeGen/WebAssembly/tailcall.ll     |  5 ++-
 .../WebAssembly/target-features-attrs.ll      | 14 +++++--
 .../WebAssembly/target-features-cpus.ll       | 29 ++++++++++----
 .../WebAssembly/target-features-tls.ll        | 40 ++++++++++++++++++-
 8 files changed, 90 insertions(+), 18 deletions(-)

diff --git a/llvm/test/CodeGen/WebAssembly/memory64-feature.ll b/llvm/test/CodeGen/WebAssembly/memory64-feature.ll
index bd277dfdc37d3..c27756a00b4f7 100644
--- a/llvm/test/CodeGen/WebAssembly/memory64-feature.ll
+++ b/llvm/test/CodeGen/WebAssembly/memory64-feature.ll
@@ -9,7 +9,10 @@ define void @foo() {
 }
 
 ; CHECK-LABEL: .custom_section.target_features
-; CHECK-NEXT: .int8 1
+; CHECK-NEXT: .int8 2
+; CHECK-NEXT: .int8 45
+; CHECK-NEXT: .int8 30
+; CHECK-NEXT: .ascii "component-model-thread-context"
 ; CHECK-NEXT: .int8 43
 ; CHECK-NEXT: .int8 8
 ; CHECK-NEXT: .ascii "memory64"
diff --git a/llvm/test/CodeGen/WebAssembly/multivalue.ll b/llvm/test/CodeGen/WebAssembly/multivalue.ll
index 5001db7e57a1e..2b64ebf2f1680 100644
--- a/llvm/test/CodeGen/WebAssembly/multivalue.ll
+++ b/llvm/test/CodeGen/WebAssembly/multivalue.ll
@@ -252,7 +252,10 @@ loop:
 }
 
 ; CHECK-LABEL: .section .custom_section.target_features
-; CHECK-NEXT: .int8 2
+; CHECK-NEXT: .int8 3
+; CHECK-NEXT: .int8 45
+; CHECK-NEXT: .int8 30
+; CHECK-NEXT: .ascii "component-model-thread-context"
 ; CHECK-NEXT: .int8 43
 ; CHECK-NEXT: .int8 10
 ; CHECK-NEXT: .ascii "multivalue"
diff --git a/llvm/test/CodeGen/WebAssembly/mutable-globals.ll b/llvm/test/CodeGen/WebAssembly/mutable-globals.ll
index 93962f7e6d92c..24e28c52f98cc 100644
--- a/llvm/test/CodeGen/WebAssembly/mutable-globals.ll
+++ b/llvm/test/CodeGen/WebAssembly/mutable-globals.ll
@@ -9,7 +9,10 @@ define void @foo() {
 }
 
 ; CHECK-LABEL: .custom_section.target_features
-; CHECK-NEXT: .int8 1
+; CHECK-NEXT: .int8 2
+; CHECK-NEXT: .int8 45
+; CHECK-NEXT: .int8 30
+; CHECK-NEXT: .ascii "component-model-thread-context"
 ; CHECK-NEXT: .int8 43
 ; CHECK-NEXT: .int8 15
 ; CHECK-NEXT: .ascii "mutable-globals"
diff --git a/llvm/test/CodeGen/WebAssembly/reference-types.ll b/llvm/test/CodeGen/WebAssembly/reference-types.ll
index 3df383b023726..dbe1a4b029312 100644
--- a/llvm/test/CodeGen/WebAssembly/reference-types.ll
+++ b/llvm/test/CodeGen/WebAssembly/reference-types.ll
@@ -8,10 +8,13 @@ define void @reference-types() {
 }
 
 ; CHECK: .section .custom_section.target_features,"",@
-; CHECK-NEXT: .int8 2
+; CHECK-NEXT: .int8 3
 ; CHECK-NEXT: .int8 43
 ; CHECK-NEXT: .int8 22
 ; CHECK-NEXT: .ascii "call-indirect-overlong"
+; CHECK-NEXT: .int8 45
+; CHECK-NEXT: .int8 30
+; CHECK-NEXT: .ascii "component-model-thread-context"
 ; CHECK-NEXT: .int8 43
 ; CHECK-NEXT: .int8 15
 ; CHECK-NEXT: .ascii "reference-types"
diff --git a/llvm/test/CodeGen/WebAssembly/tailcall.ll b/llvm/test/CodeGen/WebAssembly/tailcall.ll
index 84bd142462e37..ce94fc9a22615 100644
--- a/llvm/test/CodeGen/WebAssembly/tailcall.ll
+++ b/llvm/test/CodeGen/WebAssembly/tailcall.ll
@@ -584,7 +584,10 @@ define i32 @unique_caller(ptr %p) {
 }
 
 ; CHECK-LABEL: .section .custom_section.target_features
-; CHECK-NEXT: .int8 1
+; CHECK-NEXT: .int8 2
+; CHECK-NEXT: .int8 45
+; CHECK-NEXT: .int8 30
+; CHECK-NEXT: .ascii "component-model-thread-context"
 ; CHECK-NEXT: .int8 43
 ; CHECK-NEXT: .int8 9
 ; CHECK-NEXT: .ascii "tail-call"
diff --git a/llvm/test/CodeGen/WebAssembly/target-features-attrs.ll b/llvm/test/CodeGen/WebAssembly/target-features-attrs.ll
index 0e46b96591816..cec038aae490d 100644
--- a/llvm/test/CodeGen/WebAssembly/target-features-attrs.ll
+++ b/llvm/test/CodeGen/WebAssembly/target-features-attrs.ll
@@ -53,15 +53,18 @@ attributes #2 = { "target-features"="+reference-types" }
 ; CHECK:       i32.store
 
 ; Features in function attributes:
-; +atomics, +nontrapping-fptoint, +reference-types
+; +atomics, +nontrapping-fptoint, +reference-types, -component-model-thread-context
 ; CHECK-LABEL: .custom_section.target_features,"",@
-; CHECK-NEXT: .int8  4
+; CHECK-NEXT: .int8  5
 ; CHECK-NEXT: .int8  43
 ; CHECK-NEXT: .int8  7
 ; CHECK-NEXT: .ascii  "atomics"
 ; CHECK-NEXT: .int8  43
 ; CHECK-NEXT: .int8  22
 ; CHECK-NEXT: .ascii  "call-indirect-overlong"
+; CHECK-NEXT: .int8  45
+; CHECK-NEXT: .int8  30
+; CHECK-NEXT: .ascii  "component-model-thread-context"
 ; CHECK-NEXT: .int8  43
 ; CHECK-NEXT: .int8  19
 ; CHECK-NEXT: .ascii  "nontrapping-fptoint"
@@ -70,15 +73,18 @@ attributes #2 = { "target-features"="+reference-types" }
 ; CHECK-NEXT: .ascii  "reference-types"
 
 ; Features in function attributes + features specified by -mattr= option:
-; +atomics, +nontrapping-fptoint, +reference-types, +simd128
+; +atomics, +nontrapping-fptoint, +reference-types, +simd128, -component-model-thread-context
 ; SIMD128-LABEL: .custom_section.target_features,"",@
-; SIMD128-NEXT: .int8  5
+; SIMD128-NEXT: .int8  6
 ; SIMD128-NEXT: .int8  43
 ; SIMD128-NEXT: .int8  7
 ; SIMD128-NEXT: .ascii  "atomics"
 ; SIMD128-NEXT: .int8  43
 ; SIMD128-NEXT: .int8  22
 ; SIMD128-NEXT: .ascii  "call-indirect-overlong"
+; SIMD128-NEXT: .int8  45
+; SIMD128-NEXT: .int8  30
+; SIMD128-NEXT: .ascii  "component-model-thread-context"
 ; SIMD128-NEXT: .int8  43
 ; SIMD128-NEXT: .int8  19
 ; SIMD128-NEXT: .ascii  "nontrapping-fptoint"
diff --git a/llvm/test/CodeGen/WebAssembly/target-features-cpus.ll b/llvm/test/CodeGen/WebAssembly/target-features-cpus.ll
index 4a4973b034637..ff82e2ae6894a 100644
--- a/llvm/test/CodeGen/WebAssembly/target-features-cpus.ll
+++ b/llvm/test/CodeGen/WebAssembly/target-features-cpus.ll
@@ -9,12 +9,17 @@
 
 target triple = "wasm32-unknown-unknown"
 
-; mvp: should not contain the target features section
-; MVP-NOT: .custom_section.target_features,"",@
+; mvp: -component_model_thread_context
+; MVP-LABEL: .section        .custom_section.target_features,"",@
+; MVP-NEXT:  .int8   1
+; MVP-NEXT:  .int8   45
+; MVP-NEXT:  .int8   30
+; MVP-NEXT:  .ascii  "component-model-thread-context"
+; MVP-NEXT:  .text
 
-; generic: +call-indirect-overlong, +multivalue, +mutable-globals, +reference-types, +sign-ext
+; generic: +call-indirect-overlong, +multivalue, +mutable-globals, +reference-types, +sign-ext, -component-model-thread-context
 ; GENERIC-LABEL: .custom_section.target_features,"",@
-; GENERIC-NEXT: .int8  8
+; GENERIC-NEXT: .int8  9
 ; GENERIC-NEXT: .int8  43
 ; GENERIC-NEXT: .int8  11
 ; GENERIC-NEXT: .ascii  "bulk-memory"
@@ -24,6 +29,9 @@ target triple = "wasm32-unknown-unknown"
 ; GENERIC-NEXT: .int8  43
 ; GENERIC-NEXT: .int8  22
 ; GENERIC-NEXT: .ascii  "call-indirect-overlong"
+; GENERIC-NEXT: .int8  45
+; GENERIC-NEXT: .int8  30
+; GENERIC-NEXT: .ascii  "component-model-thread-context"
 ; GENERIC-NEXT: .int8  43
 ; GENERIC-NEXT: .int8  10
 ; GENERIC-NEXT: .ascii  "multivalue"
@@ -41,15 +49,18 @@ target triple = "wasm32-unknown-unknown"
 ; GENERIC-NEXT: .ascii  "sign-ext"
 
 ; lime1: +bulk-memory-opt, +call-indirect-overlong, +extended-const, +multivalue,
-;        +mutable-globals, +nontrapping-fptoint, +sign-ext
+;        +mutable-globals, +nontrapping-fptoint, +sign-ext, -component-model-thread-context
 ; LIME1-LABEL: .custom_section.target_features,"",@
-; LIME1-NEXT: .int8  7
+; LIME1-NEXT: .int8  8
 ; LIME1-NEXT: .int8  43
 ; LIME1-NEXT: .int8  15
 ; LIME1-NEXT: .ascii  "bulk-memory-opt"
 ; LIME1-NEXT: .int8  43
 ; LIME1-NEXT: .int8  22
 ; LIME1-NEXT: .ascii  "call-indirect-overlong"
+; LIME1-NEXT: .int8  45
+; LIME1-NEXT: .int8  30
+; LIME1-NEXT: .ascii  "component-model-thread-context"
 ; LIME1-NEXT: .int8  43
 ; LIME1-NEXT: .int8  14
 ; LIME1-NEXT: .ascii  "extended-const"
@@ -71,8 +82,9 @@ target triple = "wasm32-unknown-unknown"
 ;                +extended-const, +fp16, +gc, +multimemory, +multivalue,
 ;                +mutable-globals, +nontrapping-fptoint, +relaxed-simd,
 ;                +reference-types, +simd128, +sign-ext, +tail-call
+;                -component-model-thread-context
 ; BLEEDING-EDGE-LABEL: .section  .custom_section.target_features,"",@
-; BLEEDING-EDGE-NEXT: .int8  17
+; BLEEDING-EDGE-NEXT: .int8  18
 ; BLEEDING-EDGE-NEXT: .int8  43
 ; BLEEDING-EDGE-NEXT: .int8  7
 ; BLEEDING-EDGE-NEXT: .ascii  "atomics"
@@ -85,6 +97,9 @@ target triple = "wasm32-unknown-unknown"
 ; BLEEDING-EDGE-NEXT: .int8  43
 ; BLEEDING-EDGE-NEXT: .int8  22
 ; BLEEDING-EDGE-NEXT: .ascii  "call-indirect-overlong"
+; BLEEDING-EDGE-NEXT: .int8  45
+; BLEEDING-EDGE-NEXT: .int8  30
+; BLEEDING-EDGE-NEXT: .ascii  "component-model-thread-context"
 ; BLEEDING-EDGE-NEXT: .int8  43
 ; BLEEDING-EDGE-NEXT: .int8  18
 ; BLEEDING-EDGE-NEXT: .ascii  "exception-handling"
diff --git a/llvm/test/CodeGen/WebAssembly/target-features-tls.ll b/llvm/test/CodeGen/WebAssembly/target-features-tls.ll
index 4abe01a73aeee..5730d2fd9a57d 100644
--- a/llvm/test/CodeGen/WebAssembly/target-features-tls.ll
+++ b/llvm/test/CodeGen/WebAssembly/target-features-tls.ll
@@ -1,5 +1,7 @@
 ; RUN: llc < %s -mcpu=mvp -mattr=-bulk-memory,atomics | FileCheck %s --check-prefixes NO-BULK-MEM
 ; RUN: llc < %s -mcpu=mvp -mattr=+bulk-memory,atomics | FileCheck %s --check-prefixes BULK-MEM
+; RUN: llc < %s -mcpu=mvp -mattr=+component-model-thread-context,-bulk-memory,atomics | FileCheck %s --check-prefixes NO-BULK-MEM-CMTC
+; RUN: llc < %s -mcpu=mvp -mattr=+component-model-thread-context,bulk-memory,atomics | FileCheck %s --check-prefixes BULK-MEM-CMTC
 
 ; Test that the target features section contains -atomics or +atomics
 ; for modules that have thread local storage in their source.
@@ -10,18 +12,21 @@ target triple = "wasm32-unknown-unknown"
 
 ; -bulk-memory
 ; NO-BULK-MEM-LABEL: .custom_section.target_features,"",@
-; NO-BULK-MEM-NEXT: .int8 2
+; NO-BULK-MEM-NEXT: .int8 3
 ; NO-BULK-MEM-NEXT: .int8 43
 ; NO-BULK-MEM-NEXT: .int8 7
 ; NO-BULK-MEM-NEXT: .ascii "atomics"
 ; NO-BULK-MEM-NEXT: .int8 45
+; NO-BULK-MEM-NEXT: .int8 30
+; NO-BULK-MEM-NEXT: .ascii "component-model-thread-context"
+; NO-BULK-MEM-NEXT: .int8 45
 ; NO-BULK-MEM-NEXT: .int8 10
 ; NO-BULK-MEM-NEXT: .ascii "shared-mem"
 ; NO-BULK-MEM-NEXT: .bss.foo,"",@
 
 ; +bulk-memory
 ; BULK-MEM-LABEL: .custom_section.target_features,"",@
-; BULK-MEM-NEXT: .int8 3
+; BULK-MEM-NEXT: .int8 4
 ; BULK-MEM-NEXT: .int8 43
 ; BULK-MEM-NEXT: .int8 7
 ; BULK-MEM-NEXT: .ascii "atomics"
@@ -31,4 +36,35 @@ target triple = "wasm32-unknown-unknown"
 ; BULK-MEM-NEXT: .int8 43
 ; BULK-MEM-NEXT: .int8 15
 ; BULK-MEM-NEXT: .ascii "bulk-memory-opt"
+; BULK-MEM-NEXT: .int8 45
+; BULK-MEM-NEXT: .int8 30
+; BULK-MEM-NEXT: .ascii "component-model-thread-context"
 ; BULK-MEM-NEXT: .tbss.foo,"T",@
+
+; -bulk-memory,+component-model-thread-context
+; NO-BULK-MEM-CMTC-LABEL: .custom_section.target_features,"",@
+; NO-BULK-MEM-CMTC-NEXT: .int8 2
+; NO-BULK-MEM-CMTC-NEXT: .int8 43
+; NO-BULK-MEM-CMTC-NEXT: .int8 7
+; NO-BULK-MEM-CMTC-NEXT: .ascii "atomics"
+; NO-BULK-MEM-CMTC-NEXT: .int8 43
+; NO-BULK-MEM-CMTC-NEXT: .int8 30
+; NO-BULK-MEM-CMTC-NEXT: .ascii "component-model-thread-context"
+; NO-BULK-MEM-CMTC-NEXT: .tbss.foo,"T",@
+
+; +bulk-memory,+component-model-thread-context
+; BULK-MEM-CMTC-LABEL: .custom_section.target_features,"",@
+; BULK-MEM-CMTC-NEXT: .int8 4
+; BULK-MEM-CMTC-NEXT: .int8 43
+; BULK-MEM-CMTC-NEXT: .int8 7
+; BULK-MEM-CMTC-NEXT: .ascii "atomics"
+; BULK-MEM-CMTC-NEXT: .int8 43
+; BULK-MEM-CMTC-NEXT: .int8 11
+; BULK-MEM-CMTC-NEXT: .ascii "bulk-memory"
+; BULK-MEM-CMTC-NEXT: .int8 43
+; BULK-MEM-CMTC-NEXT: .int8 15
+; BULK-MEM-CMTC-NEXT: .ascii "bulk-memory-opt"
+; BULK-MEM-CMTC-NEXT: .int8 43
+; BULK-MEM-CMTC-NEXT: .int8 30
+; BULK-MEM-CMTC-NEXT: .ascii "component-model-thread-context"
+; BULK-MEM-CMTC-NEXT: .tbss.foo,"T",@
\ No newline at end of file

>From 0656bbf5ee8a1e798645b3dd8636e29ccdaf5549 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Fri, 20 Feb 2026 10:07:10 +0000
Subject: [PATCH 46/64] Strip thread locals if no bulk memory

---
 llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
index b484468d95a1e..3c620b4bda6f8 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
@@ -294,6 +294,9 @@ class CoalesceFeaturesAndStripAtomics final : public ModulePass {
       if (!Features[WebAssembly::FeatureAtomics]) {
         StrippedAtomics = stripAtomics(M);
       }
+      if (!Features[WebAssembly::FeatureBulkMemory]) {
+        StrippedTLS = stripThreadLocals(M);
+      }
     } else {
       if (!Features[WebAssembly::FeatureAtomics]) {
         StrippedAtomics = stripAtomics(M);

>From c5190fff2cbdb40b076d861fd0cb2047cd7e8680 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Fri, 20 Feb 2026 10:07:22 +0000
Subject: [PATCH 47/64] Thread pointer test

---
 llvm/test/CodeGen/WebAssembly/thread_pointer.ll | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/llvm/test/CodeGen/WebAssembly/thread_pointer.ll b/llvm/test/CodeGen/WebAssembly/thread_pointer.ll
index 18716988673db..7655859c2938a 100644
--- a/llvm/test/CodeGen/WebAssembly/thread_pointer.ll
+++ b/llvm/test/CodeGen/WebAssembly/thread_pointer.ll
@@ -1,6 +1,7 @@
 ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
 ; RUN: llc < %s -mtriple=wasm32-unknown-unknown | FileCheck %s --check-prefix=WASM32
 ; RUN: llc < %s -mtriple=wasm64-unknown-unknown | FileCheck %s --check-prefix=WASM64
+; RUN: llc < %s -mtriple=wasm32-unknown-unknown -mattr=+component-model-thread-context | FileCheck %s --check-prefix=WASM32-CMTC
 
 declare ptr @llvm.thread.pointer()
 
@@ -16,6 +17,13 @@ define ptr @thread_pointer() nounwind {
 ; WASM64-NEXT:  # %bb.0:
 ; WASM64-NEXT:    global.get __tls_base
 ; WASM64-NEXT:    # fallthrough-return
+;
+; WASM32-CMTC-LABEL: thread_pointer:
+; WASM32-CMTC:         .functype thread_pointer () -> (i32)
+; WASM32-CMTC-NEXT:  # %bb.0:
+; WASM32-CMTC-NEXT:    call __wasm_component_model_builtin_context_get_1
+; WASM32-CMTC-NEXT:    # fallthrough-return
+;
   %1 = tail call ptr @llvm.thread.pointer()
   ret ptr %1
 }

>From 6e0c9c036ac2ea0949f71d3b7e3ac089a548d5ac Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Fri, 20 Feb 2026 10:14:50 +0000
Subject: [PATCH 48/64] TLS local exec test

---
 .../CodeGen/WebAssembly/tls-local-exec.ll     | 31 +++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/llvm/test/CodeGen/WebAssembly/tls-local-exec.ll b/llvm/test/CodeGen/WebAssembly/tls-local-exec.ll
index dc0d40c7973ad..4e2a7fd79e9c7 100644
--- a/llvm/test/CodeGen/WebAssembly/tls-local-exec.ll
+++ b/llvm/test/CodeGen/WebAssembly/tls-local-exec.ll
@@ -1,13 +1,16 @@
 ; Run the tests with the `localexec` TLS mode specified.
 ; RUN: sed -e 's/\[\[TLS_MODE\]\]/(localexec)/' %s | llc -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -mattr=+bulk-memory,atomics - | FileCheck --check-prefixes=CHECK,TLS %s
 ; RUN: sed -e 's/\[\[TLS_MODE\]\]/(localexec)/' %s | llc -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -mattr=+bulk-memory,atomics -fast-isel - | FileCheck --check-prefixes=CHECK,TLS %s
+; RUN: sed -e 's/\[\[TLS_MODE\]\]/(localexec)/' %s | llc -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -mattr=+component-model-thread-context,bulk-memory,atomics -fast-isel - | FileCheck --check-prefixes=CHECK,TLS-CMTC %s
 
 ; Also, run the same tests without a specified TLS mode--this should still emit `localexec` code on non-Emscripten targtes which don't currently support dynamic linking.
 ; RUN: sed -e 's/\[\[TLS_MODE\]\]//' %s | llc -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -mattr=+bulk-memory,atomics - | FileCheck --check-prefixes=CHECK,TLS %s
 ; RUN: sed -e 's/\[\[TLS_MODE\]\]//' %s | llc -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -mattr=+bulk-memory,atomics -fast-isel - | FileCheck --check-prefixes=CHECK,TLS %s
+; RUN: sed -e 's/\[\[TLS_MODE\]\]//' %s | llc -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -mattr=+component-model-thread-context,bulk-memory,atomics -fast-isel - | FileCheck --check-prefixes=CHECK,TLS-CMTC %s
 
 ; Finally, when bulk memory is disabled, no TLS code should be generated.
 ; RUN: sed -e 's/\[\[TLS_MODE\]\]/(localexec)/' %s | llc -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -mattr=-bulk-memory,atomics - | FileCheck --check-prefixes=CHECK,NO-TLS %s
+; RUN: sed -e 's/\[\[TLS_MODE\]\]/(localexec)/' %s | llc -asm-verbose=false -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -mattr=+component-model-thread-context,-bulk-memory,atomics - | FileCheck --check-prefixes=CHECK,NO-TLS %s
 target triple = "wasm32-unknown-unknown"
 
 ; CHECK-LABEL: address_of_tls:
@@ -18,6 +21,11 @@ define i32 @address_of_tls() {
   ; TLS-NEXT: i32.add
   ; TLS-NEXT: return
 
+  ; TLS-CMTC-DAG: call __wasm_component_model_builtin_context_get_1
+  ; TLS-CMTC-DAG: i32.const tls at TLSREL
+  ; TLS-CMTC-NEXT: i32.add
+  ; TLS-CMTC-NEXT: return
+
   ; NO-TLS-NEXT: i32.const tls
   ; NO-TLS-NEXT: return
   %p = call ptr @llvm.threadlocal.address.p0(ptr @tls)
@@ -33,6 +41,11 @@ define i32 @address_of_tls_external() {
   ; TLS-NEXT: i32.add
   ; TLS-NEXT: return
 
+  ; TLS-CMTC-DAG: call __wasm_component_model_builtin_context_get_1
+  ; TLS-CMTC-DAG: i32.const tls_external at TLSREL
+  ; TLS-CMTC-NEXT: i32.add
+  ; TLS-CMTC-NEXT: return
+
   ; NO-TLS-NEXT: i32.const tls_external
   ; NO-TLS-NEXT: return
   %p = call ptr @llvm.threadlocal.address.p0(ptr @tls_external)
@@ -48,6 +61,11 @@ define ptr @ptr_to_tls() {
   ; TLS-NEXT: i32.add
   ; TLS-NEXT: return
 
+  ; TLS-CMTC-DAG: call __wasm_component_model_builtin_context_get_1
+  ; TLS-CMTC-DAG: i32.const tls at TLSREL
+  ; TLS-CMTC-NEXT: i32.add
+  ; TLS-CMTC-NEXT: return
+
   ; NO-TLS-NEXT: i32.const tls
   ; NO-TLS-NEXT: return
   %p = call ptr @llvm.threadlocal.address.p0(ptr @tls)
@@ -63,6 +81,12 @@ define i32 @tls_load() {
   ; TLS-NEXT: i32.load 0
   ; TLS-NEXT: return
 
+  ; TLS-CMTC-DAG: call __wasm_component_model_builtin_context_get_1
+  ; TLS-CMTC-DAG: i32.const tls at TLSREL
+  ; TLS-CMTC-NEXT: i32.add
+  ; TLS-CMTC-NEXT: i32.load 0
+  ; TLS-CMTC-NEXT: return
+
   ; NO-TLS-NEXT: i32.const 0
   ; NO-TLS-NEXT: i32.load tls
   ; NO-TLS-NEXT: return
@@ -80,6 +104,12 @@ define void @tls_store(i32 %x) {
   ; TLS-NEXT: i32.store 0
   ; TLS-NEXT: return
 
+  ; TLS-CMTC-DAG: call __wasm_component_model_builtin_context_get_1
+  ; TLS-CMTC-DAG: i32.const tls at TLSREL
+  ; TLS-CMTC-NEXT: i32.add
+  ; TLS-CMTC-NEXT: i32.store 0
+  ; TLS-CMTC-NEXT: return
+
   ; NO-TLS-NEXT: i32.const 0
   ; NO-TLS-NEXT: i32.store tls
   ; NO-TLS-NEXT: return
@@ -99,6 +129,7 @@ define i32 @tls_size() {
 
 ; CHECK: .type tls, at object
 ; TLS-NEXT: .section .tbss.tls,"T",@
+; TLS-CMTC-NEXT: .section .tbss.tls,"T",@
 ; NO-TLS-NEXT: .section .bss.tls,"",@
 ; CHECK-NEXT: .p2align 2
 ; CHECK-NEXT: tls:

>From 3386a110968c5bcda20dada319f326a9214ce6d4 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Fri, 20 Feb 2026 10:26:25 +0000
Subject: [PATCH 49/64] Add stack abi test

---
 llvm/test/CodeGen/WebAssembly/stack-abi.ll | 24 ++++++++++++++++++++++
 1 file changed, 24 insertions(+)
 create mode 100644 llvm/test/CodeGen/WebAssembly/stack-abi.ll

diff --git a/llvm/test/CodeGen/WebAssembly/stack-abi.ll b/llvm/test/CodeGen/WebAssembly/stack-abi.ll
new file mode 100644
index 0000000000000..4f434082c85b2
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/stack-abi.ll
@@ -0,0 +1,24 @@
+; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=+component-model-thread-context | FileCheck --check-prefix=CMTC %s
+; RUN: llc < %s --mtriple=wasm32-unknown-unknown -asm-verbose=false -mattr=-component-model-thread-context | FileCheck --check-prefix=GLOBAL %s
+
+declare void @force_sp_save()
+define void @use_stack() #0 {
+  %1 = alloca i32, align 4
+  %2 = alloca ptr, align 4
+  store ptr %1, ptr %2, align 4
+  call void @force_sp_save()
+  ret void
+}
+
+; CMTC-LABEL: use_stack:
+; CMTC: call __wasm_component_model_builtin_context_get_0
+; CMTC: call __wasm_component_model_builtin_context_set_0
+; CMTC-NOT: global.get __stack_pointer
+; CMTC-NOT: global.set __stack_pointer
+
+; GLOBAL-LABEL: use_stack:
+; GLOBAL: global.get __stack_pointer
+; GLOBAL: global.set __stack_pointer
+; GLOBAL-NOT: call __wasm_component_model_builtin_context_get_0
+; GLOBAL-NOT: call __wasm_component_model_builtin_context_set_0
+

>From 57755921a327960069811bc5a61ee8a2aa00e06b Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Fri, 20 Feb 2026 10:41:51 +0000
Subject: [PATCH 50/64] Shared memory fixes

---
 lld/wasm/Writer.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 23a9f3ddf8623..119206017ce12 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -1167,7 +1167,7 @@ void Writer::createSyntheticInitFunctions() {
         "__wasm_init_memory", WASM_SYMBOL_VISIBILITY_HIDDEN,
         make<SyntheticFunction>(nullSignature, "__wasm_init_memory"));
     ctx.sym.initMemory->markLive();
-    if (ctx.isMultithreaded()) {
+    if (ctx.arg.sharedMemory) {
       // This global is assigned during  __wasm_init_memory in the shared memory
       // case.
       ctx.sym.tlsBase->markLive();

>From 4d7a81e0f0d2cdc7ac7188ce3ed4482154c37b89 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Fri, 20 Feb 2026 11:11:32 +0000
Subject: [PATCH 51/64] Enable component model context in WASIP3 in LLVM

---
 .../WebAssembly/WebAssemblySubtarget.cpp      |  9 +++
 .../target-features-thread-context.ll         | 70 +++++++++++++++++++
 2 files changed, 79 insertions(+)
 create mode 100644 llvm/test/CodeGen/WebAssembly/target-features-thread-context.ll

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
index b0a8be810caa5..20bd0c3b1b923 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
@@ -40,6 +40,15 @@ WebAssemblySubtarget::initializeSubtargetDependencies(StringRef CPU,
 
   ParseSubtargetFeatures(CPU, /*TuneCPU*/ CPU, FS);  
   
+  // WASIP3 implies using the component model thread context intrinsics by
+  // default, unless explicitly disabled.
+  if (!FS.contains("component-model-thread-context") && 
+      !HasComponentModelThreadContext && 
+      TargetTriple.getOS() == Triple::WASIp3) {
+    ToggleFeature(WebAssembly::FeatureComponentModelThreadContext);
+    HasComponentModelThreadContext = true;
+  }
+
   FeatureBitset Bits = getFeatureBits();
 
   // bulk-memory implies bulk-memory-opt
diff --git a/llvm/test/CodeGen/WebAssembly/target-features-thread-context.ll b/llvm/test/CodeGen/WebAssembly/target-features-thread-context.ll
new file mode 100644
index 0000000000000..9592c35081c2a
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/target-features-thread-context.ll
@@ -0,0 +1,70 @@
+; RUN: llc < %s -mtriple=wasm32-wasip3 | FileCheck %s --check-prefix=WASIP3
+; RUN: llc < %s -mtriple=wasm32-wasip3 -mattr=-component-model-thread-context | FileCheck %s --check-prefix=EXPLICIT-DISABLE
+; RUN: llc < %s -mtriple=wasm32-wasip1 | FileCheck %s --check-prefix=WASIP1
+; RUN: llc < %s -mtriple=wasm32-wasip2 | FileCheck %s --check-prefix=WASIP2
+
+; Test that wasip3 target automatically enables component-model-thread-context
+
+; WASIP3:        .section        .custom_section.target_features,"",@
+; WASIP3-NEXT:   .int8   9
+; WASIP3-NEXT:   .int8   43
+; WASIP3-NEXT:   .int8   11
+; WASIP3-NEXT:   .ascii  "bulk-memory"
+; WASIP3-NEXT:   .int8   43
+; WASIP3-NEXT:   .int8   15
+; WASIP3-NEXT:   .ascii  "bulk-memory-opt"
+; WASIP3-NEXT:   .int8   43
+; WASIP3-NEXT:   .int8   22
+; WASIP3-NEXT:   .ascii  "call-indirect-overlong"
+; WASIP3-NEXT:   .int8   43
+; WASIP3-NEXT:   .int8   30
+; WASIP3-NEXT:   .ascii  "component-model-thread-context"
+
+; EXPLICIT-DISABLE:        .section        .custom_section.target_features,"",@
+; EXPLICIT-DISABLE-NEXT:   .int8   9
+; EXPLICIT-DISABLE-NEXT:   .int8   43
+; EXPLICIT-DISABLE-NEXT:   .int8   11
+; EXPLICIT-DISABLE-NEXT:   .ascii  "bulk-memory"
+; EXPLICIT-DISABLE-NEXT:   .int8   43
+; EXPLICIT-DISABLE-NEXT:   .int8   15
+; EXPLICIT-DISABLE-NEXT:   .ascii  "bulk-memory-opt"
+; EXPLICIT-DISABLE-NEXT:   .int8   43
+; EXPLICIT-DISABLE-NEXT:   .int8   22
+; EXPLICIT-DISABLE-NEXT:   .ascii  "call-indirect-overlong"
+; EXPLICIT-DISABLE-NEXT:   .int8   45
+; EXPLICIT-DISABLE-NEXT:   .int8   30
+; EXPLICIT-DISABLE-NEXT:   .ascii  "component-model-thread-context"
+
+; WASIP1:        .section        .custom_section.target_features,"",@
+; WASIP1-NEXT:   .int8   9
+; WASIP1-NEXT:   .int8   43
+; WASIP1-NEXT:   .int8   11
+; WASIP1-NEXT:   .ascii  "bulk-memory"
+; WASIP1-NEXT:   .int8   43
+; WASIP1-NEXT:   .int8   15
+; WASIP1-NEXT:   .ascii  "bulk-memory-opt"
+; WASIP1-NEXT:   .int8   43
+; WASIP1-NEXT:   .int8   22
+; WASIP1-NEXT:   .ascii  "call-indirect-overlong"
+; WASIP1-NEXT:   .int8   45
+; WASIP1-NEXT:   .int8   30
+; WASIP1-NEXT:   .ascii  "component-model-thread-context"
+
+; WASIP2:        .section        .custom_section.target_features,"",@
+; WASIP2-NEXT:   .int8   9
+; WASIP2-NEXT:   .int8   43
+; WASIP2-NEXT:   .int8   11
+; WASIP2-NEXT:   .ascii  "bulk-memory"
+; WASIP2-NEXT:   .int8   43
+; WASIP2-NEXT:   .int8   15
+; WASIP2-NEXT:   .ascii  "bulk-memory-opt"
+; WASIP2-NEXT:   .int8   43
+; WASIP2-NEXT:   .int8   22
+; WASIP2-NEXT:   .ascii  "call-indirect-overlong"
+; WASIP2-NEXT:   .int8   45
+; WASIP2-NEXT:   .int8   30
+; WASIP2-NEXT:   .ascii  "component-model-thread-context"
+
+define void @test() {
+  ret void
+}
\ No newline at end of file

>From 7a447417f1096222c2a09db3adf88db373519206 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Fri, 20 Feb 2026 11:48:15 +0000
Subject: [PATCH 52/64] Tighten up linker changes

---
 lld/wasm/Driver.cpp | 2 +-
 lld/wasm/Writer.cpp | 6 ++++--
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 22a2c5ba17e07..0c50e12f66e32 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -985,7 +985,7 @@ static void createPostLTOSymbols() {
     ctx.sym.tableBase->markLive();
   } else {
     // For non-PIC code
-    ctx.sym.stackPointer = createGlobalVariable(stack_pointer_name, true);
+    ctx.sym.stackPointer = createGlobalVariable(stack_pointer_name, !ctx.componentModelThreadContext);
     ctx.sym.stackPointer->markLive();
   }
 
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 119206017ce12..fca033f7b5658 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -1181,8 +1181,10 @@ void Writer::createSyntheticInitFunctions() {
           make<SyntheticFunction>(nullSignature,
                                   "__wasm_apply_global_tls_relocs"));
       ctx.sym.applyGlobalTLSRelocs->markLive();
-      // TLS relocations depend on  the __tls_base symbols
-      ctx.sym.tlsBase->markLive();
+      // Shared memory TLS relocations depend on  the __tls_base symbols
+      if (ctx.arg.sharedMemory) {
+        ctx.sym.tlsBase->markLive();
+      }
     }
 
     auto hasTLSRelocs = [](const OutputSegment *segment) {

>From 4c62c814ca8ec2929b4c561da64b093017f2b1b8 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Fri, 20 Feb 2026 21:25:05 +0000
Subject: [PATCH 53/64] Fix LLVM tests

---
 llvm/test/CodeGen/WebAssembly/target-features-tls.ll | 7 +++++--
 llvm/test/MC/WebAssembly/array-fill.ll               | 7 ++++++-
 llvm/test/MC/WebAssembly/assembler-binary.ll         | 7 ++++++-
 llvm/test/MC/WebAssembly/bss.ll                      | 7 ++++++-
 llvm/test/MC/WebAssembly/comdat.ll                   | 7 ++++++-
 llvm/test/MC/WebAssembly/debug-info.ll               | 6 ++++++
 llvm/test/MC/WebAssembly/debug-info64.ll             | 6 ++++++
 llvm/test/MC/WebAssembly/explicit-sections.ll        | 5 +++++
 llvm/test/MC/WebAssembly/export-name-invalid.s       | 2 +-
 llvm/test/MC/WebAssembly/global-ctor-dtor.ll         | 5 +++++
 llvm/test/MC/WebAssembly/import-module-invalid.s     | 2 +-
 llvm/test/MC/WebAssembly/import-name-invalid.s       | 2 +-
 llvm/test/MC/WebAssembly/unnamed-data.ll             | 5 +++++
 llvm/test/MC/WebAssembly/visibility.ll               | 5 +++++
 14 files changed, 64 insertions(+), 9 deletions(-)

diff --git a/llvm/test/CodeGen/WebAssembly/target-features-tls.ll b/llvm/test/CodeGen/WebAssembly/target-features-tls.ll
index 5730d2fd9a57d..e9d42036a0772 100644
--- a/llvm/test/CodeGen/WebAssembly/target-features-tls.ll
+++ b/llvm/test/CodeGen/WebAssembly/target-features-tls.ll
@@ -43,14 +43,17 @@ target triple = "wasm32-unknown-unknown"
 
 ; -bulk-memory,+component-model-thread-context
 ; NO-BULK-MEM-CMTC-LABEL: .custom_section.target_features,"",@
-; NO-BULK-MEM-CMTC-NEXT: .int8 2
+; NO-BULK-MEM-CMTC-NEXT: .int8 3
 ; NO-BULK-MEM-CMTC-NEXT: .int8 43
 ; NO-BULK-MEM-CMTC-NEXT: .int8 7
 ; NO-BULK-MEM-CMTC-NEXT: .ascii "atomics"
 ; NO-BULK-MEM-CMTC-NEXT: .int8 43
 ; NO-BULK-MEM-CMTC-NEXT: .int8 30
 ; NO-BULK-MEM-CMTC-NEXT: .ascii "component-model-thread-context"
-; NO-BULK-MEM-CMTC-NEXT: .tbss.foo,"T",@
+; NO-BULK-MEM-CMTC-NEXT: .int8 45
+; NO-BULK-MEM-CMTC-NEXT: .int8 10
+; NO-BULK-MEM-CMTC-NEXT: .ascii "shared-mem"
+; NO-BULK-MEM-CMTC-NEXT: .bss.foo,"",@
 
 ; +bulk-memory,+component-model-thread-context
 ; BULK-MEM-CMTC-LABEL: .custom_section.target_features,"",@
diff --git a/llvm/test/MC/WebAssembly/array-fill.ll b/llvm/test/MC/WebAssembly/array-fill.ll
index 4725d4eda065b..0ef4754fc89d6 100644
--- a/llvm/test/MC/WebAssembly/array-fill.ll
+++ b/llvm/test/MC/WebAssembly/array-fill.ll
@@ -23,4 +23,9 @@ target triple = "wasm32-unknown-unknown"
 ; CHECK-NEXT:         Name:            .data
 ; CHECK-NEXT:         Alignment:       0
 ; CHECK-NEXT:         Flags:           [ ]
-; CHECK-NEXT: ...
+; CHECK-NEXT:    - Type:           CUSTOM
+; CHECK-NEXT:         Name:            target_features 
+; CHECK-NEXT:         Features: 
+; CHECK-NEXT:           - Prefix:           DISALLOWED 
+; CHECK-NEXT:         Name:            component-model-thread-context 
+; CHECK-NEXT:        ... 
diff --git a/llvm/test/MC/WebAssembly/assembler-binary.ll b/llvm/test/MC/WebAssembly/assembler-binary.ll
index 32f7d679b8968..7981b01354120 100644
--- a/llvm/test/MC/WebAssembly/assembler-binary.ll
+++ b/llvm/test/MC/WebAssembly/assembler-binary.ll
@@ -78,4 +78,9 @@ entry:
 ; CHECK-NEXT:         Name:            bar
 ; CHECK-NEXT:         Flags:           [ UNDEFINED ]
 ; CHECK-NEXT:         Function:        0
-; CHECK-NEXT: ...
+; CHECK-NEXT:    - Type:           CUSTOM
+; CHECK-NEXT:         Name:            target_features 
+; CHECK-NEXT:         Features: 
+; CHECK-NEXT:           - Prefix:          DISALLOWED 
+; CHECK-NEXT:         Name:            component-model-thread-context 
+; CHECK-NEXT:        ... 
diff --git a/llvm/test/MC/WebAssembly/bss.ll b/llvm/test/MC/WebAssembly/bss.ll
index 4f0c7c8525562..c0b15fc85d389 100644
--- a/llvm/test/MC/WebAssembly/bss.ll
+++ b/llvm/test/MC/WebAssembly/bss.ll
@@ -78,4 +78,9 @@ target triple = "wasm32-unknown-unknown"
 ; CHECK-NEXT:         Name:            .bss.bar
 ; CHECK-NEXT:         Alignment:       0
 ; CHECK-NEXT:         Flags:           [ ]
-; CHECK-NEXT: ...
+; CHECK-NEXT:    - Type:           CUSTOM
+; CHECK-NEXT:         Name: target_features 
+; CHECK-NEXT:         Features: 
+; CHECK-NEXT:       - Prefix: DISALLOWED 
+; CHECK-NEXT:         Name: component-model-thread-context 
+; CHECK-NEXT:        ... 
diff --git a/llvm/test/MC/WebAssembly/comdat.ll b/llvm/test/MC/WebAssembly/comdat.ll
index 0886301597471..2d68bf9baf81b 100644
--- a/llvm/test/MC/WebAssembly/comdat.ll
+++ b/llvm/test/MC/WebAssembly/comdat.ll
@@ -119,7 +119,12 @@ define linkonce_odr i32 @sharedFn() #1 comdat($sharedComdat) {
 ; CHECK-NEXT:            Index:           3
 ; CHECK-NEXT:          - Kind:            DATA
 ; CHECK-NEXT:            Index:           0
-; CHECK-NEXT: ...
+; CHECK-NEXT:  - Type:            CUSTOM
+; CHECK-NEXT:         Name:           target_features 
+; CHECK-NEXT:         Features: 
+; CHECK-NEXT:           - Prefix:         DISALLOWED 
+; CHECK-NEXT:         Name:           component-model-thread-context 
+; CHECK-NEXT:        ... 
 
 
 ; ASM:        .section        .text.basicInlineFn,"G",@,basicInlineFn,comdat
diff --git a/llvm/test/MC/WebAssembly/debug-info.ll b/llvm/test/MC/WebAssembly/debug-info.ll
index a65ce0ee83920..a9b8ee71ad352 100644
--- a/llvm/test/MC/WebAssembly/debug-info.ll
+++ b/llvm/test/MC/WebAssembly/debug-info.ll
@@ -141,6 +141,12 @@
 ; CHECK-NEXT:    Offset: 1164
 ; CHECK-NEXT:    Name: producers
 ; CHECK-NEXT:  }
+; CHECK-NEXT:  Section {
+; CHECK-NEXT:    Type: CUSTOM (0x0) 
+; CHECK-NEXT:    Size: 33 
+; CHECK-NEXT:    Offset: 1257 
+; CHECK-NEXT:    Name: target_features 
+; CHECK-NEXT:  } 
 ; CHECK-NEXT:]
 ; CHECK-NEXT:Relocations [
 ; CHECK-NEXT:  Section (7) DATA {
diff --git a/llvm/test/MC/WebAssembly/debug-info64.ll b/llvm/test/MC/WebAssembly/debug-info64.ll
index d0081164d73ee..b0a0b9de1d458 100644
--- a/llvm/test/MC/WebAssembly/debug-info64.ll
+++ b/llvm/test/MC/WebAssembly/debug-info64.ll
@@ -147,6 +147,12 @@
 ; CHECK-NEXT:     Offset: 1317
 ; CHECK-NEXT:     Name: target_features
 ; CHECK-NEXT:   }
+; CHECK-NEXT:  Section {
+; CHECK-NEXT:    Type: CUSTOM (0x0) 
+; CHECK-NEXT:    Size: 33 
+; CHECK-NEXT:    Offset: 1257 
+; CHECK-NEXT:    Name: target_features 
+; CHECK-NEXT:  } 
 ; CHECK-NEXT: ]
 ; CHECK-NEXT: Relocations [
 ; CHECK-NEXT:   Section (7) DATA {
diff --git a/llvm/test/MC/WebAssembly/explicit-sections.ll b/llvm/test/MC/WebAssembly/explicit-sections.ll
index a65172b22d467..8ca3b434b7997 100644
--- a/llvm/test/MC/WebAssembly/explicit-sections.ll
+++ b/llvm/test/MC/WebAssembly/explicit-sections.ll
@@ -70,4 +70,9 @@ target triple = "wasm32-unknown-unknown"
 ; CHECK-NEXT:         Name:            .sec2
 ; CHECK-NEXT:         Alignment:       3
 ; CHECK-NEXT:         Flags:           [ ]
+; CHECK-NEXT:   - Type:            CUSTOM
+; CHECK-NEXT:     Name:            target_features
+; CHECK-NEXT:     Features:
+; CHECK-NEXT:       - Prefix:          DISALLOWED
+; CHECK-NEXT:         Name:            component-model-thread-context
 ; CHECK-NEXT: ...
diff --git a/llvm/test/MC/WebAssembly/export-name-invalid.s b/llvm/test/MC/WebAssembly/export-name-invalid.s
index ad322ce949f5e..1272bf66fa143 100644
--- a/llvm/test/MC/WebAssembly/export-name-invalid.s
+++ b/llvm/test/MC/WebAssembly/export-name-invalid.s
@@ -6,7 +6,7 @@
 # CHECK: [[#@LINE+1]]:17: error: Expected ,, instead got:
 .export_name foo
 
-# CHECK: [[#@LINE+1]]:18: error: Expected identifier, got:
+# CHECK: [[#@LINE+1]]:18: error: Expected string or identifier, got:
 .export_name foo,
 
 # CHECK: [[#@LINE+1]]:22: error: Expected EOL, instead got: ,
diff --git a/llvm/test/MC/WebAssembly/global-ctor-dtor.ll b/llvm/test/MC/WebAssembly/global-ctor-dtor.ll
index f1ec71da1ebb6..a8300e865997d 100644
--- a/llvm/test/MC/WebAssembly/global-ctor-dtor.ll
+++ b/llvm/test/MC/WebAssembly/global-ctor-dtor.ll
@@ -184,4 +184,9 @@ declare void @func3()
 ; CHECK-NEXT:         Symbol: 10
 ; CHECK-NEXT:       - Priority: 65535
 ; CHECK-NEXT:         Symbol: 7
+; CHECK-NEXT:   - Type:            CUSTOM
+; CHECK-NEXT:     Name:            target_features
+; CHECK-NEXT:     Features:
+; CHECK-NEXT:       - Prefix:          DISALLOWED
+; CHECK-NEXT:         Name:            component-model-thread-context
 ; CHECK-NEXT: ...
diff --git a/llvm/test/MC/WebAssembly/import-module-invalid.s b/llvm/test/MC/WebAssembly/import-module-invalid.s
index a9f93e83dba39..daf18f2e6a505 100644
--- a/llvm/test/MC/WebAssembly/import-module-invalid.s
+++ b/llvm/test/MC/WebAssembly/import-module-invalid.s
@@ -6,7 +6,7 @@
 # CHECK: [[#@LINE+1]]:19: error: Expected ,, instead got:
 .import_module foo
 
-# CHECK: [[#@LINE+1]]:20: error: Expected identifier, got:
+# CHECK: [[#@LINE+1]]:20: error: Expected string or identifier, got:
 .import_module foo,
 
 # CHECK: [[#@LINE+1]]:24: error: Expected EOL, instead got: ,
diff --git a/llvm/test/MC/WebAssembly/import-name-invalid.s b/llvm/test/MC/WebAssembly/import-name-invalid.s
index da8ed0d4617b0..df709f6380a06 100644
--- a/llvm/test/MC/WebAssembly/import-name-invalid.s
+++ b/llvm/test/MC/WebAssembly/import-name-invalid.s
@@ -6,7 +6,7 @@
 # CHECK: [[#@LINE+1]]:17: error: Expected ,, instead got:
 .import_name foo
 
-# CHECK: [[#@LINE+1]]:18: error: Expected identifier, got:
+# CHECK: [[#@LINE+1]]:18: error: Expected string or identifier, got:
 .import_name foo,
 
 # CHECK: [[#@LINE+1]]:22: error: Expected EOL, instead got: ,
diff --git a/llvm/test/MC/WebAssembly/unnamed-data.ll b/llvm/test/MC/WebAssembly/unnamed-data.ll
index 0887622277bb3..c476e95f02d3a 100644
--- a/llvm/test/MC/WebAssembly/unnamed-data.ll
+++ b/llvm/test/MC/WebAssembly/unnamed-data.ll
@@ -87,4 +87,9 @@ target triple = "wasm32-unknown-unknown"
 ; CHECK-NEXT:         Name:        .data.b
 ; CHECK-NEXT:         Alignment:   3
 ; CHECK-NEXT:         Flags:       [ ]
+; CHECK-NEXT:   - Type:            CUSTOM
+; CHECK-NEXT:     Name:            target_features
+; CHECK-NEXT:     Features:
+; CHECK-NEXT:       - Prefix:          DISALLOWED
+; CHECK-NEXT:         Name:            component-model-thread-context
 ; CHECK-NEXT:   ...
diff --git a/llvm/test/MC/WebAssembly/visibility.ll b/llvm/test/MC/WebAssembly/visibility.ll
index 69b273ecbf25e..9e5d97cd128a5 100644
--- a/llvm/test/MC/WebAssembly/visibility.ll
+++ b/llvm/test/MC/WebAssembly/visibility.ll
@@ -25,4 +25,9 @@ entry:
 ; CHECK-NEXT:         Name:            hiddenVis
 ; CHECK-NEXT:         Flags:           [ VISIBILITY_HIDDEN ]
 ; CHECK-NEXT:         Function:        1
+; CHECK-NEXT:   - Type:            CUSTOM
+; CHECK-NEXT:     Name:            target_features
+; CHECK-NEXT:     Features:
+; CHECK-NEXT:       - Prefix:          DISALLOWED
+; CHECK-NEXT:         Name:            component-model-thread-context
 ; CHECK-NEXT: ...

>From e9fa9fc30d84a504241499f60c088be1512b3c0c Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Mon, 23 Feb 2026 09:42:51 +0000
Subject: [PATCH 54/64] Remove unused code

---
 lld/wasm/Config.h | 2 --
 1 file changed, 2 deletions(-)

diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index 2c4954e68bab4..be04827dd1930 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -135,8 +135,6 @@ struct Config {
   llvm::SmallVector<uint8_t, 0> buildIdVector;
 };
 
-enum class ThreadContextAbi { Undetermined, Globals, ComponentModelBuiltins };
-
 // The Ctx object hold all other (non-configuration) global state.
 struct Ctx {
   Config arg;

>From 10ed134e52430839cb7ac2d3e14db448339678b4 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Mon, 23 Feb 2026 09:43:30 +0000
Subject: [PATCH 55/64] Change asserts for symbol creation

---
 lld/wasm/SymbolTable.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp
index 9bd93f317c3c5..e4ca7c56a7602 100644
--- a/lld/wasm/SymbolTable.cpp
+++ b/lld/wasm/SymbolTable.cpp
@@ -222,7 +222,7 @@ DefinedFunction *SymbolTable::addSyntheticFunction(StringRef name,
                                                    uint32_t flags,
                                                    InputFunction *function) {
   LLVM_DEBUG(dbgs() << "addSyntheticFunction: " << name << "\n");
-  assert(!find(name));
+  assert(!find(name) || find(name)->isUndefined());
   ctx.syntheticFunctions.emplace_back(function);
   return replaceSymbol<DefinedFunction>(insertName(name).first, name, flags,
                                         nullptr, function);
@@ -250,7 +250,7 @@ DefinedData *SymbolTable::addOptionalDataSymbol(StringRef name,
 DefinedData *SymbolTable::addSyntheticDataSymbol(StringRef name,
                                                  uint32_t flags) {
   LLVM_DEBUG(dbgs() << "addSyntheticDataSymbol: " << name << "\n");
-  assert(!find(name));
+  assert(!find(name) || find(name)->isUndefined());
   return replaceSymbol<DefinedData>(insertName(name).first, name,
                                     flags | WASM_SYMBOL_ABSOLUTE);
 }
@@ -259,7 +259,7 @@ DefinedGlobal *SymbolTable::addSyntheticGlobal(StringRef name, uint32_t flags,
                                                InputGlobal *global) {
   LLVM_DEBUG(dbgs() << "addSyntheticGlobal: " << name << " -> " << global
                     << "\n");
-  assert(!find(name));
+  assert(!find(name) || find(name)->isUndefined());
   ctx.syntheticGlobals.emplace_back(global);
   return replaceSymbol<DefinedGlobal>(insertName(name).first, name, flags,
                                       nullptr, global);

>From 2ee984530db4198312dae546fb626e67af1486b7 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Mon, 23 Feb 2026 09:44:54 +0000
Subject: [PATCH 56/64] Fix frame debug info

---
 .../WebAssembly/WebAssemblyFrameLowering.cpp  | 23 +++++++++++++------
 .../WebAssembly/WebAssemblyLateEHPrepare.cpp  | 10 ++++----
 2 files changed, 21 insertions(+), 12 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
index eb176161d1fdf..532155c30b2ad 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
@@ -135,13 +135,21 @@ bool WebAssemblyFrameLowering::needsSPForLocalFrame(
       any_of(MRI.use_operands(getSPReg(MF)),
              [](MachineOperand &MO) { return !MO.isImplicit(); });
 
+  // With component model thread context, we need SP in the prolog when debug
+  // info is present so we can allocate a local for DWARF to reference.
+  bool NeedsSPForDebug = MF.getFunction().getSubprogram() &&
+                         MF.getSubtarget<WebAssemblySubtarget>()
+                             .hasComponentModelThreadContext();
+
+  
+
   return MFI.getStackSize() || MFI.adjustsStack() || hasFP(MF) ||
-         HasExplicitSPUse;
+         HasExplicitSPUse || NeedsSPForDebug;
 }
 
 // In function with EH pads, we need to make a copy of the value of
-// __stack_pointer global in SP32/64 register, in order to use it when
-// restoring __stack_pointer after an exception is caught.
+// the stack pointer in the SP32/64 register, in order to use it when
+// restoring the stack pointer after an exception is caught.
 bool WebAssemblyFrameLowering::needsPrologForEH(
     const MachineFunction &MF) const {
   auto EHType = MF.getTarget().getMCAsmInfo()->getExceptionHandlingType();
@@ -151,13 +159,14 @@ bool WebAssemblyFrameLowering::needsPrologForEH(
 
 /// Returns true if this function needs a local user-space stack pointer.
 /// Unlike a machine stack pointer, the wasm user stack pointer is a global
-/// variable, so it is loaded into a register in the prolog.
+/// variable or stored in the component model thread context, so it is loaded 
+/// into a register in the prolog.
 bool WebAssemblyFrameLowering::needsSP(const MachineFunction &MF) const {
   return needsSPForLocalFrame(MF) || needsPrologForEH(MF);
 }
 
 /// Returns true if the local user-space stack pointer needs to be written back
-/// to __stack_pointer global by this function (this is not meaningful if
+/// to the stack pointer global/thread context by this function (this is not meaningful if
 /// needsSP is false). If false, the stack red zone can be used and only a local
 /// SP is needed.
 bool WebAssemblyFrameLowering::needsSPWriteback(
@@ -294,8 +303,8 @@ void WebAssemblyFrameLowering::emitPrologue(MachineFunction &MF,
   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)
-        .addExternalSymbol(SPSymbol);
+      BuildMI(MBB, InsertPt, DL, TII->get(WebAssembly::CALL), SPReg)
+          .addExternalSymbol(SPSymbol);
   } else {
     const char *ES = "__stack_pointer";
     auto *SPSymbol = MF.createExternalSymbolName(ES);
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
index 24d5c19399bdb..9396cd23662d4 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
@@ -377,8 +377,8 @@ bool WebAssemblyLateEHPrepare::removeUnnecessaryUnreachables(
 }
 
 // After the stack is unwound due to a thrown exception, the __stack_pointer
-// global can point to an invalid address. This inserts instructions that
-// restore __stack_pointer global.
+// global/context[0] can point to an invalid address. This inserts instructions that
+// restore the stack pointer state..
 bool WebAssemblyLateEHPrepare::restoreStackPointer(MachineFunction &MF) {
   const auto *FrameLowering = static_cast<const WebAssemblyFrameLowering *>(
       MF.getSubtarget().getFrameLowering());
@@ -391,11 +391,11 @@ bool WebAssemblyLateEHPrepare::restoreStackPointer(MachineFunction &MF) {
       continue;
     Changed = true;
 
-    // Insert __stack_pointer restoring instructions at the beginning of each EH
+    // Insert stack pointer restoring instructions at the beginning of each EH
     // pad, after the catch instruction. Here it is safe to assume that SP32
-    // holds the latest value of __stack_pointer, because the only exception for
+    // holds the latest value of the stack pointer, because the only exception for
     // this case is when a function uses the red zone, but that only happens
-    // with leaf functions, and we don't restore __stack_pointer in leaf
+    // with leaf functions, and we don't restore the stack pointer in leaf
     // functions anyway.
     auto InsertPos = MBB.begin();
     // Skip EH_LABELs in the beginning of an EH pad if present.

>From 557bfac15a7b32ae6e6fa73ad429ef2be3f403c7 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Mon, 23 Feb 2026 10:10:53 +0000
Subject: [PATCH 57/64] Tighten up linker changes

---
 lld/wasm/Writer.cpp | 21 +++++++++++++--------
 1 file changed, 13 insertions(+), 8 deletions(-)

diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index fca033f7b5658..c4074269f510a 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -1033,7 +1033,12 @@ 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.isMultithreaded())
+  // In the shared memory case, all data segments must be passive since they will be initialized once
+  // by the main thread and then shared with other threads. In the non-shared memory case, we use
+  // passive segments only for TLS segments, so that they can be reused, and for .bss segments, which
+  // don't need to be included in the binary at all. 
+  bool passiveForCMTC = ctx.componentModelThreadContext && (s->isTLS() || s->name.startswith(".bss"));
+  if (ctx.arg.sharedMemory || passiveForCMTC)
     s->initFlags = WASM_DATA_SEGMENT_IS_PASSIVE;
   if (!ctx.arg.relocatable && name.starts_with(".bss"))
     s->isBss = true;
@@ -1158,10 +1163,10 @@ void Writer::createSyntheticInitFunctions() {
   createApplyDataRelocationsFunction();
 
   // Passive segments are used to avoid memory being reinitialized on each
-  // thread's instantiation. These passive segments are initialized and
-  // dropped in __wasm_init_memory, which is registered as the start function
-  // We also initialize bss segments (using memory.fill) as part of this
-  // function.
+  // thread's instantiation for wasi-threads, and for TLS when using cooperative threads. 
+  // These passive segments are initialized and dropped in __wasm_init_memory, which is 
+  // registered as the start function.
+  // We also initialize bss segments (using memory.fill) as part of this function.
   if (hasPassiveInitializedSegments()) {
     ctx.sym.initMemory = symtab->addSyntheticFunction(
         "__wasm_init_memory", WASM_SYMBOL_VISIBILITY_HIDDEN,
@@ -1354,9 +1359,9 @@ void Writer::createInitMemoryFunction() {
         }
 
         // When we initialize the TLS segment we also set the `__tls_base`
-        // global.  This allows the runtime to use this static copy of the
-        // TLS data for the first/main thread.
-        if (ctx.arg.sharedMemory && s->isTLS()) {
+        // global/thread.context[1]. This allows the runtime to use this 
+        // static copy of the TLS data for the first/main thread.
+        if (ctx.isMultithreaded() && s->isTLS()) {
           if (ctx.isPic) {
             // Cache the result of the addionion in local 0
             writeU8(os, WASM_OPCODE_LOCAL_TEE, "local.tee");

>From 61c25c826423ccab735540be70c65167b8eca27b Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Mon, 23 Feb 2026 10:16:41 +0000
Subject: [PATCH 58/64] Typo

---
 lld/wasm/Writer.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index c4074269f510a..7d7b4cbec039e 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -1037,7 +1037,7 @@ OutputSegment *Writer::createOutputSegment(StringRef name) {
   // by the main thread and then shared with other threads. In the non-shared memory case, we use
   // passive segments only for TLS segments, so that they can be reused, and for .bss segments, which
   // don't need to be included in the binary at all. 
-  bool passiveForCMTC = ctx.componentModelThreadContext && (s->isTLS() || s->name.startswith(".bss"));
+  bool passiveForCMTC = ctx.componentModelThreadContext && (s->isTLS() || s->name.starts_with(".bss"));
   if (ctx.arg.sharedMemory || passiveForCMTC)
     s->initFlags = WASM_DATA_SEGMENT_IS_PASSIVE;
   if (!ctx.arg.relocatable && name.starts_with(".bss"))

>From 0fe07f018d6bea8f15fa3b2bf9962f415caaa18f Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Mon, 23 Feb 2026 13:53:09 +0000
Subject: [PATCH 59/64] Improve TLS test

---
 lld/test/wasm/tls-component-model.s | 64 +++++++++++++++++++++++++----
 1 file changed, 56 insertions(+), 8 deletions(-)

diff --git a/lld/test/wasm/tls-component-model.s b/lld/test/wasm/tls-component-model.s
index 3f10d497b237f..71e6442435426 100644
--- a/lld/test/wasm/tls-component-model.s
+++ b/lld/test/wasm/tls-component-model.s
@@ -1,19 +1,37 @@
 # RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
 # RUN: wasm-ld -no-gc-sections -o %t.wasm %t.o
 # RUN: obj2yaml %t.wasm | FileCheck %s
-# RUN: llvm-objdump -d --no-show-raw-insn %t.wasm | FileCheck %s --check-prefix=DIS
+# RUN: llvm-objdump -d --no-print-imm-hex --no-show-raw-insn %t.wasm | FileCheck %s --check-prefix=DIS
+
+.functype       __wasm_component_model_builtin_context_get_1 () -> (i32)
+.import_module  __wasm_component_model_builtin_context_get_1, "$root"
+.import_name    __wasm_component_model_builtin_context_get_1, "[context-get-1]"
 
 .globl _start
 _start:
   .functype _start () -> (i32)
-  global.get tls_sym at GOT@TLS
+  call __wasm_component_model_builtin_context_get_1
+  i32.const tls1 at TLSREL
+  i32.add
+  i32.load 0
+  call __wasm_component_model_builtin_context_get_1
+  i32.const tls2 at TLSREL
+  i32.add
+  i32.load 0
+  i32.add
   end_function
 
-.section  .tdata.tls_sym,"",@
-.globl  tls_sym
-tls_sym:
+.section  .tdata.tls1,"",@
+.globl  tls1
+tls1:
   .int32  1
-  .size tls_sym, 4
+  .size tls1, 4
+
+.section  .tdata.tls2,"",@
+.globl  tls2
+tls2:
+  .int32  2
+  .size tls2, 4
 
 .section  .custom_section.target_features,"",@
   .int8 2
@@ -25,5 +43,35 @@ tls_sym:
   .ascii  "bulk-memory"
 
 
-# CHECK: Name: __init_tls_base
-# DIS: __wasm_init_tls
\ No newline at end of file
+# CHECK:      GlobalNames:
+# CHECK-NEXT:      - Index:           0
+# CHECK-NEXT:        Name:            __init_stack_pointer
+# CHECK-NEXT:      - Index:           1
+# CHECK-NEXT:        Name:            __init_tls_base
+# CHECK-NEXT:      - Index:           2
+# CHECK-NEXT:        Name:            __tls_size
+# CHECK-NEXT:      - Index:           3
+# CHECK-NEXT:        Name:            __tls_align
+
+# DIS-LABEL: <__wasm_init_memory>:
+# DIS-EMPTY:
+# DIS-NEXT:       i32.const       65536
+# DIS-NEXT:       i32.const       65536
+# DIS-NEXT:       call    1
+# DIS-NEXT:       i32.const       0
+# DIS-NEXT:       i32.const       8
+# DIS-NEXT:       memory.init     0, 0
+# DIS-NEXT:       end
+
+# DIS-LABEL: <_start>:
+# DIS-EMPTY:
+# DIS-NEXT:       call    0
+# DIS-NEXT:       i32.const       0
+# DIS-NEXT:       i32.add 
+# DIS-NEXT:       i32.load        0
+# DIS-NEXT:       call    0
+# DIS-NEXT:       i32.const       4
+# DIS-NEXT:       i32.add 
+# DIS-NEXT:       i32.load        0
+# DIS-NEXT:       i32.add 
+# DIS-NEXT:       end
\ No newline at end of file

>From 7a58fcea7d3d8f267a3daad1382d2683feeda5f3 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Mon, 23 Feb 2026 16:42:25 +0000
Subject: [PATCH 60/64] Fix TLS in shared objects

---
 llvm/lib/Object/WasmObjectFile.cpp | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/llvm/lib/Object/WasmObjectFile.cpp b/llvm/lib/Object/WasmObjectFile.cpp
index bd69941e5664e..098902fdfe5a0 100644
--- a/llvm/lib/Object/WasmObjectFile.cpp
+++ b/llvm/lib/Object/WasmObjectFile.cpp
@@ -1486,6 +1486,15 @@ Error WasmObjectFile::parseExportSection(ReadContext &Ctx) {
     wasm::WasmSymbolInfo Info;
     Info.Name = Ex.Name;
     Info.Flags = 0;
+    // For shared objects, look up flags (e.g. TLS) from dylink export info
+    if (HasDylinkSection) {
+      for (const auto &ExportInfo : DylinkInfo.ExportInfo) {
+        if (ExportInfo.Name == Ex.Name) {
+          Info.Flags = ExportInfo.Flags;
+          break;
+        }
+      }
+    }
     switch (Ex.Kind) {
     case wasm::WASM_EXTERNAL_FUNCTION: {
       if (!isValidFunctionIndex(Ex.Index))

>From 8047c7634ef1cfddd78e5bc15c0ad7972cf08ef8 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Mon, 23 Feb 2026 16:44:09 +0000
Subject: [PATCH 61/64] Update comment

---
 .../Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h  | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h
index 5dc0e3aa91622..403ce3bfcb504 100644
--- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h
+++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h
@@ -106,7 +106,8 @@ enum TOF {
   MO_MEMORY_BASE_REL,
 
   // On a symbol operand this indicates that the immediate is the symbol
-  // address relative the __tls_base wasm global.
+  // address relative to the TLS base. This is stored in thread.context[1] 
+  // when using the component model thread context, and the __tls_base global otherwise.
   // Only applicable to data symbols.
   MO_TLS_BASE_REL,
 

>From 24cb457d6615fbc39e03099690da0fc096b1246b Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Mon, 23 Feb 2026 17:53:43 +0000
Subject: [PATCH 62/64] Remove symbol file changes for now

---
 llvm/lib/Object/WasmObjectFile.cpp | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/llvm/lib/Object/WasmObjectFile.cpp b/llvm/lib/Object/WasmObjectFile.cpp
index 098902fdfe5a0..bd69941e5664e 100644
--- a/llvm/lib/Object/WasmObjectFile.cpp
+++ b/llvm/lib/Object/WasmObjectFile.cpp
@@ -1486,15 +1486,6 @@ Error WasmObjectFile::parseExportSection(ReadContext &Ctx) {
     wasm::WasmSymbolInfo Info;
     Info.Name = Ex.Name;
     Info.Flags = 0;
-    // For shared objects, look up flags (e.g. TLS) from dylink export info
-    if (HasDylinkSection) {
-      for (const auto &ExportInfo : DylinkInfo.ExportInfo) {
-        if (ExportInfo.Name == Ex.Name) {
-          Info.Flags = ExportInfo.Flags;
-          break;
-        }
-      }
-    }
     switch (Ex.Kind) {
     case wasm::WASM_EXTERNAL_FUNCTION: {
       if (!isValidFunctionIndex(Ex.Index))

>From d11664a233eb8b9947675b4f23772cf7d128c5a4 Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 3 Mar 2026 16:05:42 +0000
Subject: [PATCH 63/64] Clang format

---
 clang/lib/Basic/Targets/WebAssembly.cpp       | 10 +-
 clang/lib/Driver/ToolChains/WebAssembly.cpp   |  8 +-
 lld/wasm/Config.h                             |  7 +-
 lld/wasm/Driver.cpp                           | 91 ++++++++++---------
 lld/wasm/Options.td                           |  7 +-
 lld/wasm/Writer.cpp                           | 34 ++++---
 .../MCTargetDesc/WebAssemblyMCTargetDesc.h    |  6 +-
 .../WebAssembly/WebAssemblyFrameLowering.cpp  | 23 +++--
 .../WebAssembly/WebAssemblyFrameLowering.h    |  7 +-
 .../WebAssembly/WebAssemblyLateEHPrepare.cpp  | 12 +--
 .../WebAssembly/WebAssemblySubtarget.cpp      |  8 +-
 .../Target/WebAssembly/WebAssemblySubtarget.h |  4 +-
 .../WebAssembly/WebAssemblyTargetMachine.cpp  |  8 +-
 .../Target/WebAssembly/WebAssemblyUtilities.h |  4 +-
 14 files changed, 126 insertions(+), 103 deletions(-)

diff --git a/clang/lib/Basic/Targets/WebAssembly.cpp b/clang/lib/Basic/Targets/WebAssembly.cpp
index 785bec699f925..a8bc7c13ffc78 100644
--- a/clang/lib/Basic/Targets/WebAssembly.cpp
+++ b/clang/lib/Basic/Targets/WebAssembly.cpp
@@ -418,11 +418,11 @@ WebAssemblyTargetInfo::getTargetBuiltins() const {
 void WebAssemblyTargetInfo::adjust(DiagnosticsEngine &Diags, LangOptions &Opts,
                                    const TargetInfo *Aux) {
   TargetInfo::adjust(Diags, Opts, Aux);
-  // 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)) {
+  // 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);
     Opts.ThreadsafeStatics = false;
diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp b/clang/lib/Driver/ToolChains/WebAssembly.cpp
index 84b9b00bf4c23..151d289d17e13 100644
--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp
+++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp
@@ -79,17 +79,19 @@ static bool WantsPthread(const llvm::Triple &Triple, const ArgList &Args) {
   return WantsPthread;
 }
 
-static bool WantsComponentModelThreadContext(const llvm::Triple &Triple, const ArgList &Args) {
+static bool WantsComponentModelThreadContext(const llvm::Triple &Triple,
+                                             const ArgList &Args) {
   // If the target is WASIP3, then enable the
   // component-model-thread-context feature by default, unless explicitly
   // disabled.
   return Triple.getOS() == llvm::Triple::WASIp3 &&
          Args.hasFlag(options::OPT_mcomponent_model_thread_context,
-                     options::OPT_mno_component_model_thread_context, true);
+                      options::OPT_mno_component_model_thread_context, true);
 }
 
 static bool WantsSharedMemory(const llvm::Triple &Triple, const ArgList &Args) {
-  return WantsPthread(Triple, Args) && !WantsComponentModelThreadContext(Triple, Args);
+  return WantsPthread(Triple, Args) &&
+         !WantsComponentModelThreadContext(Triple, Args);
 }
 
 void wasm::Linker::ConstructJob(Compilation &C, const JobAction &JA,
diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index be04827dd1930..ac8734d9c0e7f 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -280,12 +280,15 @@ struct Ctx {
                     0>
       whyExtractRecords;
 
-  // Whether to use component model thread context intrinsics for the stack pointer and TLS base.
+  // Whether to use component model thread context intrinsics for the stack
+  // pointer and TLS base.
   bool componentModelThreadContext = false;
 
   Ctx();
   void reset();
-  bool isMultithreaded() const { return componentModelThreadContext || arg.sharedMemory; }
+  bool isMultithreaded() const {
+    return componentModelThreadContext || arg.sharedMemory;
+  }
 };
 
 extern Ctx ctx;
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 0c50e12f66e32..6687733bd8b77 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -966,8 +966,9 @@ static void createPostLTOSymbols() {
 
   bool is64 = ctx.arg.is64.value_or(false);
 
-  auto stack_pointer_name =
-      ctx.componentModelThreadContext ? "__init_stack_pointer" : "__stack_pointer";
+  auto stack_pointer_name = ctx.componentModelThreadContext
+                                ? "__init_stack_pointer"
+                                : "__stack_pointer";
   if (ctx.isPic) {
     ctx.sym.stackPointer =
         createUndefinedGlobal(stack_pointer_name, ctx.arg.is64.value_or(false)
@@ -985,15 +986,18 @@ static void createPostLTOSymbols() {
     ctx.sym.tableBase->markLive();
   } else {
     // For non-PIC code
-    ctx.sym.stackPointer = createGlobalVariable(stack_pointer_name, !ctx.componentModelThreadContext);
+    ctx.sym.stackPointer = createGlobalVariable(
+        stack_pointer_name, !ctx.componentModelThreadContext);
     ctx.sym.stackPointer->markLive();
   }
 
   if (ctx.isMultithreaded()) {
     // TLS symbols are all hidden/dso-local
-    auto tls_base_name = ctx.componentModelThreadContext ? "__init_tls_base" : "__tls_base";
-    ctx.sym.tlsBase = createGlobalVariable(tls_base_name, !ctx.componentModelThreadContext,
-                                           WASM_SYMBOL_VISIBILITY_HIDDEN);
+    auto tls_base_name =
+        ctx.componentModelThreadContext ? "__init_tls_base" : "__tls_base";
+    ctx.sym.tlsBase =
+        createGlobalVariable(tls_base_name, !ctx.componentModelThreadContext,
+                             WASM_SYMBOL_VISIBILITY_HIDDEN);
     ctx.sym.tlsSize = createGlobalVariable("__tls_size", false,
                                            WASM_SYMBOL_VISIBILITY_HIDDEN);
     ctx.sym.tlsAlign = createGlobalVariable("__tls_align", false,
@@ -1316,68 +1320,73 @@ static void determineThreadContextABI(ArrayRef<ObjFile *> files) {
   // A complication is that a user may attempt to link together object files
   // compiled with different versions of LLVM, where one does not specifiy
   // -component-model-thread-context when using the global thread context ABI.
-  // They may also attempt to link object files with the global ABI compiled with
-  // older LLVM versions, but link them with a newer wasm-ld. To ensure the correct behavior
-  // in both of these cases, we treat the import of a __stack_pointer global from the env module
-  // as an indication that the global thread context ABI is being used.
+  // They may also attempt to link object files with the global ABI compiled
+  // with older LLVM versions, but link them with a newer wasm-ld. To ensure the
+  // correct behavior in both of these cases, we treat the import of a
+  // __stack_pointer global from the env module as an indication that the global
+  // thread context ABI is being used.
 
-  enum class ThreadContextABI {
-    Undetermined,
-    ComponentModelBuiltins,
-    Globals
-  };
+  enum class ThreadContextABI { Undetermined, ComponentModelBuiltins, Globals };
 
   ThreadContextABI threadContextABI = ThreadContextABI::Undetermined;
 
   for (ObjFile *obj : files) {
     auto targetFeatures = obj->getWasmObj()->getTargetFeatures();
-    auto threadContextFeature = llvm::find_if(targetFeatures,
-                              [](const auto &f) {
-                                return f.Name == "component-model-thread-context";
-                              });
+    auto threadContextFeature =
+        llvm::find_if(targetFeatures, [](const auto &f) {
+          return f.Name == "component-model-thread-context";
+        });
 
-    bool usesComponentModelThreadContext = threadContextFeature != targetFeatures.end() &&
-                                    threadContextFeature->Prefix == WASM_FEATURE_PREFIX_USED;
+    bool usesComponentModelThreadContext =
+        threadContextFeature != targetFeatures.end() &&
+        threadContextFeature->Prefix == WASM_FEATURE_PREFIX_USED;
 
     if (threadContextFeature == targetFeatures.end()) {
-      // If the feature is not explicitly used or disallowed, check for the presence of a __stack_pointer
-      // import in this specific file to determine if the global thread context ABI is being used.
-      bool hasStackPointerImport = llvm::any_of(obj->getSymbols(), [](const auto &sym) {
-        return sym && sym->getName() == "__stack_pointer" && 
-               sym->kind() == Symbol::UndefinedGlobalKind &&
-               sym->importModule && sym->importModule == "env";
-      });
+      // If the feature is not explicitly used or disallowed, check for the
+      // presence of a __stack_pointer import in this specific file to determine
+      // if the global thread context ABI is being used.
+      bool hasStackPointerImport =
+          llvm::any_of(obj->getSymbols(), [](const auto &sym) {
+            return sym && sym->getName() == "__stack_pointer" &&
+                   sym->kind() == Symbol::UndefinedGlobalKind &&
+                   sym->importModule && sym->importModule == "env";
+          });
       if (!hasStackPointerImport) {
-        // No __stack_pointer import, so this is probably an object file compiled from assembly or
-        // some other source that doesn't care about the thread context ABI. As such, we let it pass.
+        // No __stack_pointer import, so this is probably an object file
+        // compiled from assembly or some other source that doesn't care about
+        // the thread context ABI. As such, we let it pass.
         continue;
       }
       // Treat this as using the globals ABI
       usesComponentModelThreadContext = false;
-    }     
+    }
 
     if (usesComponentModelThreadContext) {
       if (threadContextABI == ThreadContextABI::Undetermined) {
         threadContextABI = ThreadContextABI::ComponentModelBuiltins;
       } else if (threadContextABI != ThreadContextABI::ComponentModelBuiltins) {
-        error("thread context ABI mismatch: " + obj->getName() +
-              " uses component-model-thread-context but other files disallow it");
+        error(
+            "thread context ABI mismatch: " + obj->getName() +
+            " uses component-model-thread-context but other files disallow it");
       }
     } else {
       if (threadContextABI == ThreadContextABI::Undetermined) {
         threadContextABI = ThreadContextABI::Globals;
       } else if (threadContextABI != ThreadContextABI::Globals) {
-        error("thread context ABI mismatch: " + obj->getName() +
-              " disallows component-model-thread-context but other files use it"); 
+        error(
+            "thread context ABI mismatch: " + obj->getName() +
+            " disallows component-model-thread-context but other files use it");
       }
     }
   }
- 
+
   // If the ABI is undetermined at this point, default to the globals ABI
-  ctx.componentModelThreadContext = (threadContextABI == ThreadContextABI::ComponentModelBuiltins);
+  ctx.componentModelThreadContext =
+      (threadContextABI == ThreadContextABI::ComponentModelBuiltins);
 
   if (ctx.arg.sharedMemory && ctx.componentModelThreadContext) {
-    error("--shared-memory is currently incompatible with component model thread context intrinsics");
+    error("--shared-memory is currently incompatible with component model "
+          "thread context intrinsics");
   }
 }
 
@@ -1555,9 +1564,9 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   if (errorCount())
     return;
 
-  // Now that LTO is complete and all object files are available, determine the 
-  // thread context ABI and create symbols (__stack_pointer, __tls_base, etc.) based
-  // on the determined ABI.
+  // Now that LTO is complete and all object files are available, determine the
+  // thread context ABI and create symbols (__stack_pointer, __tls_base, etc.)
+  // based on the determined ABI.
   determineThreadContextABI(ctx.objectFiles);
   createPostLTOSymbols();
 
diff --git a/lld/wasm/Options.td b/lld/wasm/Options.td
index a9def1bc47c9a..4b6d800854a08 100644
--- a/lld/wasm/Options.td
+++ b/lld/wasm/Options.td
@@ -190,8 +190,11 @@ 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">;
+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">;
 
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 7d7b4cbec039e..f7334672dad2b 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -651,10 +651,12 @@ void Writer::populateTargetFeatures() {
 
   if (tlsUsed) {
     if (!allowed.contains("bulk-memory")) {
-      error("bulk-memory feature must be used in order to use thread-local storage");
+      error("bulk-memory feature must be used in order to use thread-local "
+            "storage");
     }
     if (!ctx.componentModelThreadContext && !allowed.contains("atomics")) {
-      error("atomics feature must be used in order to use thread-local storage");
+      error(
+          "atomics feature must be used in order to use thread-local storage");
     }
   }
 
@@ -1033,11 +1035,13 @@ static StringRef getOutputDataSegmentName(const InputChunk &seg) {
 OutputSegment *Writer::createOutputSegment(StringRef name) {
   LLVM_DEBUG(dbgs() << "new segment: " << name << "\n");
   OutputSegment *s = make<OutputSegment>(name);
-  // In the shared memory case, all data segments must be passive since they will be initialized once
-  // by the main thread and then shared with other threads. In the non-shared memory case, we use
-  // passive segments only for TLS segments, so that they can be reused, and for .bss segments, which
-  // don't need to be included in the binary at all. 
-  bool passiveForCMTC = ctx.componentModelThreadContext && (s->isTLS() || s->name.starts_with(".bss"));
+  // In the shared memory case, all data segments must be passive since they
+  // will be initialized once by the main thread and then shared with other
+  // threads. In the non-shared memory case, we use passive segments only for
+  // TLS segments, so that they can be reused, and for .bss segments, which
+  // don't need to be included in the binary at all.
+  bool passiveForCMTC = ctx.componentModelThreadContext &&
+                        (s->isTLS() || s->name.starts_with(".bss"));
   if (ctx.arg.sharedMemory || passiveForCMTC)
     s->initFlags = WASM_DATA_SEGMENT_IS_PASSIVE;
   if (!ctx.arg.relocatable && name.starts_with(".bss"))
@@ -1163,10 +1167,10 @@ void Writer::createSyntheticInitFunctions() {
   createApplyDataRelocationsFunction();
 
   // Passive segments are used to avoid memory being reinitialized on each
-  // thread's instantiation for wasi-threads, and for TLS when using cooperative threads. 
-  // These passive segments are initialized and dropped in __wasm_init_memory, which is 
-  // registered as the start function.
-  // We also initialize bss segments (using memory.fill) as part of this function.
+  // thread's instantiation for wasi-threads, and for TLS when using cooperative
+  // threads. These passive segments are initialized and dropped in
+  // __wasm_init_memory, which is registered as the start function. We also
+  // initialize bss segments (using memory.fill) as part of this function.
   if (hasPassiveInitializedSegments()) {
     ctx.sym.initMemory = symtab->addSyntheticFunction(
         "__wasm_init_memory", WASM_SYMBOL_VISIBILITY_HIDDEN,
@@ -1359,7 +1363,7 @@ void Writer::createInitMemoryFunction() {
         }
 
         // When we initialize the TLS segment we also set the `__tls_base`
-        // global/thread.context[1]. This allows the runtime to use this 
+        // global/thread.context[1]. This allows the runtime to use this
         // static copy of the TLS data for the first/main thread.
         if (ctx.isMultithreaded() && s->isTLS()) {
           if (ctx.isPic) {
@@ -1639,8 +1643,10 @@ void Writer::createInitTLSFunction() {
 
     writeUleb128(os, 0, "num locals");
     if (tlsSeg) {
-      // 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.
+      // 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.componentModelThreadContext) {
         writeU8(os, WASM_OPCODE_LOCAL_GET, "local.get");
         writeUleb128(os, 0, "local index");
diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h
index 403ce3bfcb504..d642ef2ccf2a4 100644
--- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h
+++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyMCTargetDesc.h
@@ -106,9 +106,9 @@ enum TOF {
   MO_MEMORY_BASE_REL,
 
   // On a symbol operand this indicates that the immediate is the symbol
-  // address relative to the TLS base. This is stored in thread.context[1] 
-  // when using the component model thread context, and the __tls_base global otherwise.
-  // Only applicable to data symbols.
+  // address relative to the TLS base. This is stored in thread.context[1]
+  // when using the component model thread context, and the __tls_base global
+  // otherwise. Only applicable to data symbols.
   MO_TLS_BASE_REL,
 
   // On a symbol operand this indicates that the immediate is the symbol
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
index 532155c30b2ad..77489f6f8e517 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.cpp
@@ -137,11 +137,9 @@ bool WebAssemblyFrameLowering::needsSPForLocalFrame(
 
   // With component model thread context, we need SP in the prolog when debug
   // info is present so we can allocate a local for DWARF to reference.
-  bool NeedsSPForDebug = MF.getFunction().getSubprogram() &&
-                         MF.getSubtarget<WebAssemblySubtarget>()
-                             .hasComponentModelThreadContext();
-
-  
+  bool NeedsSPForDebug =
+      MF.getFunction().getSubprogram() &&
+      MF.getSubtarget<WebAssemblySubtarget>().hasComponentModelThreadContext();
 
   return MFI.getStackSize() || MFI.adjustsStack() || hasFP(MF) ||
          HasExplicitSPUse || NeedsSPForDebug;
@@ -159,16 +157,16 @@ bool WebAssemblyFrameLowering::needsPrologForEH(
 
 /// Returns true if this function needs a local user-space stack pointer.
 /// Unlike a machine stack pointer, the wasm user stack pointer is a global
-/// variable or stored in the component model thread context, so it is loaded 
+/// variable or stored in the component model thread context, so it is loaded
 /// into a register in the prolog.
 bool WebAssemblyFrameLowering::needsSP(const MachineFunction &MF) const {
   return needsSPForLocalFrame(MF) || needsPrologForEH(MF);
 }
 
 /// Returns true if the local user-space stack pointer needs to be written back
-/// to the stack pointer global/thread context by this function (this is not meaningful if
-/// needsSP is false). If false, the stack red zone can be used and only a local
-/// SP is needed.
+/// to the stack pointer global/thread context by this function (this is not
+/// meaningful if needsSP is false). If false, the stack red zone can be used
+/// and only a local SP is needed.
 bool WebAssemblyFrameLowering::needsSPWriteback(
     const MachineFunction &MF) const {
   auto &MFI = MF.getFrameInfo();
@@ -241,7 +239,8 @@ void WebAssemblyFrameLowering::writeBackSP(
     MachineBasicBlock::iterator &InsertStore, const DebugLoc &DL) const {
   const auto *TII = MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
 
-  if (MF.getSubtarget<WebAssemblySubtarget>().hasComponentModelThreadContext()) {
+  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))
@@ -303,8 +302,8 @@ void WebAssemblyFrameLowering::emitPrologue(MachineFunction &MF,
   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)
-          .addExternalSymbol(SPSymbol);
+    BuildMI(MBB, InsertPt, DL, TII->get(WebAssembly::CALL), SPReg)
+        .addExternalSymbol(SPSymbol);
   } else {
     const char *ES = "__stack_pointer";
     auto *SPSymbol = MF.createExternalSymbolName(ES);
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.h b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.h
index e567345e93773..98133d48a9eaa 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyFrameLowering.h
@@ -48,10 +48,9 @@ class WebAssemblyFrameLowering final : public TargetFrameLowering {
   bool needsPrologForEH(const MachineFunction &MF) const;
 
   /// 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;
+  void writeBackSP(unsigned SrcReg, MachineFunction &MF, MachineBasicBlock &MBB,
+                   MachineBasicBlock::iterator &InsertStore,
+                   const DebugLoc &DL) const;
 
   // Returns the index of the WebAssembly local to which the stack object
   // FrameIndex in MF should be allocated, or std::nullopt.
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
index 9396cd23662d4..c846215ce0e7c 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
@@ -377,8 +377,8 @@ bool WebAssemblyLateEHPrepare::removeUnnecessaryUnreachables(
 }
 
 // After the stack is unwound due to a thrown exception, the __stack_pointer
-// global/context[0] can point to an invalid address. This inserts instructions that
-// restore the stack pointer state..
+// global/context[0] can point to an invalid address. This inserts instructions
+// that restore the stack pointer state..
 bool WebAssemblyLateEHPrepare::restoreStackPointer(MachineFunction &MF) {
   const auto *FrameLowering = static_cast<const WebAssemblyFrameLowering *>(
       MF.getSubtarget().getFrameLowering());
@@ -393,8 +393,8 @@ bool WebAssemblyLateEHPrepare::restoreStackPointer(MachineFunction &MF) {
 
     // Insert stack pointer restoring instructions at the beginning of each EH
     // pad, after the catch instruction. Here it is safe to assume that SP32
-    // holds the latest value of the stack pointer, because the only exception for
-    // this case is when a function uses the red zone, but that only happens
+    // holds the latest value of the stack pointer, because the only exception
+    // for this case is when a function uses the red zone, but that only happens
     // with leaf functions, and we don't restore the stack pointer in leaf
     // functions anyway.
     auto InsertPos = MBB.begin();
@@ -405,8 +405,8 @@ 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->writeBackSP(FrameLowering->getSPReg(MF), MF, MBB,
-                                   InsertPos, MBB.begin()->getDebugLoc());
+    FrameLowering->writeBackSP(FrameLowering->getSPReg(MF), MF, MBB, InsertPos,
+                               MBB.begin()->getDebugLoc());
   }
   return Changed;
 }
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
index 20bd0c3b1b923..662e01022cc87 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.cpp
@@ -38,12 +38,12 @@ WebAssemblySubtarget::initializeSubtargetDependencies(StringRef CPU,
   if (CPU.empty())
     CPU = "generic";
 
-  ParseSubtargetFeatures(CPU, /*TuneCPU*/ CPU, FS);  
-  
+  ParseSubtargetFeatures(CPU, /*TuneCPU*/ CPU, FS);
+
   // WASIP3 implies using the component model thread context intrinsics by
   // default, unless explicitly disabled.
-  if (!FS.contains("component-model-thread-context") && 
-      !HasComponentModelThreadContext && 
+  if (!FS.contains("component-model-thread-context") &&
+      !HasComponentModelThreadContext &&
       TargetTriple.getOS() == Triple::WASIp3) {
     ToggleFeature(WebAssembly::FeatureComponentModelThreadContext);
     HasComponentModelThreadContext = true;
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
index 05009b388a4db..4b82172e29e39 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
@@ -112,7 +112,9 @@ class WebAssemblySubtarget final : public WebAssemblyGenSubtargetInfo {
   bool hasBulkMemoryOpt() const { return HasBulkMemoryOpt; }
   bool hasCallIndirectOverlong() const { return HasCallIndirectOverlong; }
   bool hasCompactImports() const { return HasCompactImports; }
-  bool hasComponentModelThreadContext() const { return HasComponentModelThreadContext; }
+  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 3c620b4bda6f8..f8d3b4a69ea57 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyTargetMachine.cpp
@@ -268,8 +268,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 using component model threading intrinsics which allow thread local storage 
-  // without atomics, in which case only strip atomics.
+  // is using component model threading intrinsics which allow thread local
+  // storage without atomics, in which case only strip atomics.
   static char ID;
   WebAssemblyTargetMachine *WasmTM;
 
@@ -289,7 +289,7 @@ class CoalesceFeaturesAndStripAtomics final : public ModulePass {
     bool StrippedTLS = false;
 
     if (Features[WebAssembly::FeatureComponentModelThreadContext]) {
-      // Using component model threading intrinsics allows TLS without 
+      // 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);
@@ -432,7 +432,7 @@ 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]) {
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
index 5b4e9cff19a55..c7de7f9971cdc 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyUtilities.h
@@ -77,8 +77,8 @@ bool canLowerMultivalueReturn(const WebAssemblySubtarget *Subtarget);
 bool canLowerReturn(size_t ResultSize, const WebAssemblySubtarget *Subtarget);
 
 // Get the TLS base value for the current target
-// If using component model threading intrinsics: calls __wasm_component_model_builtin_context_get_1
-// Otherwise: global.get __tls_base
+// 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 f04b74ac5341c24f9f4a72d1c69df5056c7f800f Mon Sep 17 00:00:00 2001
From: Sy Brand <sy.brand at fastly.com>
Date: Tue, 3 Mar 2026 16:21:02 +0000
Subject: [PATCH 64/64] Format

---
 .../WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp  | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp
index 916a42664279f..c6f3959d02af5 100644
--- a/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp
+++ b/llvm/lib/Target/WebAssembly/MCTargetDesc/WebAssemblyTargetStreamer.cpp
@@ -25,10 +25,12 @@ using namespace llvm;
 static bool shouldQuoteName(StringRef Name) {
   // Wasm export/import names and import module names can contain characters
   // that are not allowed in identifiers, so we need to quote them.
-  auto isValidStartChar = [](char C) { return isalpha(C) || C == '_' || C == '.'; };
+  auto isValidStartChar = [](char C) {
+    return isalpha(C) || C == '_' || C == '.';
+  };
   auto isValidChar = [&](char C) { return isValidStartChar(C) || isdigit(C); };
   return !(Name.size() > 0 && isValidStartChar(Name.front())) || 
-        llvm::any_of(Name, [&](char C) { return !isValidChar(C); });
+         llvm::any_of(Name, [&](char C) { return !isValidChar(C); });
 }
 
 WebAssemblyTargetStreamer::WebAssemblyTargetStreamer(MCStreamer &S)



More information about the cfe-commits mailing list