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

Sy Brand via cfe-commits cfe-commits at lists.llvm.org
Wed Feb 18 05:16:08 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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/30] 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) {



More information about the cfe-commits mailing list