[lld] [llvm] [WebAssembly] Implement support for Common symbols (PR #199303)

Derek Schuff via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 20 16:56:02 PDT 2026


https://github.com/dschuff updated https://github.com/llvm/llvm-project/pull/199303

>From a58e6245b69d2c0b0e50bd6f93a692c90162b605 Mon Sep 17 00:00:00 2001
From: Derek Schuff <dschuff at chromium.org>
Date: Thu, 14 May 2026 22:18:59 +0000
Subject: [PATCH 1/5] Initial pass for common symbols

---
 lld/test/wasm/common.s                        | 111 ++++++++++++++++++
 lld/wasm/Config.h                             |   2 +
 lld/wasm/InputChunks.cpp                      |  10 ++
 lld/wasm/InputChunks.h                        |  19 +++
 lld/wasm/InputFiles.cpp                       |   6 +
 lld/wasm/SymbolTable.cpp                      |  50 ++++++++
 lld/wasm/SymbolTable.h                        |   2 +
 lld/wasm/Symbols.cpp                          |   2 +
 lld/wasm/Symbols.h                            |  26 +++-
 lld/wasm/Writer.cpp                           |  60 ++++++++++
 llvm/include/llvm/BinaryFormat/Wasm.h         |   8 ++
 llvm/include/llvm/ObjectYAML/WasmYAML.h       |   1 +
 llvm/lib/MC/MCWasmStreamer.cpp                |  16 ++-
 llvm/lib/MC/WasmObjectWriter.cpp              |  20 +++-
 llvm/lib/Object/WasmObjectFile.cpp            |  38 +++---
 llvm/lib/ObjectYAML/WasmYAML.cpp              |  14 ++-
 .../WebAssembly/WebAssemblyAsmPrinter.cpp     |   7 --
 llvm/test/CodeGen/WebAssembly/common-error.ll |   6 -
 llvm/test/CodeGen/WebAssembly/common.ll       |   9 ++
 llvm/test/MC/WebAssembly/common-error.s       |   6 -
 llvm/test/MC/WebAssembly/common.s             |  31 +++++
 llvm/tools/llvm-readobj/WasmDumper.cpp        |  12 +-
 llvm/tools/obj2yaml/wasm2yaml.cpp             |   6 +-
 23 files changed, 410 insertions(+), 52 deletions(-)
 create mode 100644 lld/test/wasm/common.s
 delete mode 100644 llvm/test/CodeGen/WebAssembly/common-error.ll
 create mode 100644 llvm/test/CodeGen/WebAssembly/common.ll
 delete mode 100644 llvm/test/MC/WebAssembly/common-error.s
 create mode 100644 llvm/test/MC/WebAssembly/common.s

diff --git a/lld/test/wasm/common.s b/lld/test/wasm/common.s
new file mode 100644
index 0000000000000..09a1c5d60e14a
--- /dev/null
+++ b/lld/test/wasm/common.s
@@ -0,0 +1,111 @@
+# RUN: split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t/common1.o %t/common1.s
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t/common2.o %t/common2.s
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t/main.o %t/main.s
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t/strong.o %t/strong.s
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t/weak.o %t/weak.s
+
+# Test 1: Basic common symbol merging (max size and max alignment)
+# RUN: wasm-ld --no-stack-first --global-base=1024 --export=foo --export=bar %t/common1.o %t/common2.o %t/main.o -o %t.wasm
+# RUN: obj2yaml %t.wasm | FileCheck %s
+
+# Test 2: Strong defined symbol takes precedence over common symbol
+# RUN: wasm-ld --no-stack-first --global-base=1024 --export=foo %t/common1.o %t/strong.o %t/main.o -o %t_strong.wasm
+# RUN: obj2yaml %t_strong.wasm | FileCheck %s --check-prefix=STRONG
+
+# Test 3: Common symbol takes precedence over weak defined symbol
+# RUN: wasm-ld --no-stack-first --global-base=1024 --export=foo %t/common1.o %t/weak.o %t/main.o -o %t_weak.wasm
+# RUN: obj2yaml %t_weak.wasm | FileCheck %s --check-prefix=WEAK
+
+#--- common1.s
+.comm foo, 4, 2
+.comm bar, 8, 3
+
+#--- common2.s
+.comm foo, 8, 3
+.comm bar, 4, 2
+
+#--- strong.s
+.globl foo
+.section .data.foo,"",@
+foo:
+  .int32 42
+.size foo, 4
+
+#--- weak.s
+.weak foo
+.section .data.foo,"",@
+foo:
+  .int32 99
+.size foo, 4
+
+#--- main.s
+.globl _start
+_start:
+  .functype _start () -> ()
+  i32.const foo
+  drop
+  i32.const bar
+  drop
+  end_function
+
+# CHECK:        - Type:            GLOBAL
+# CHECK-NEXT:     Globals:
+# CHECK-NEXT:       - Index:           0
+# CHECK-NEXT:         Type:            I32
+# CHECK-NEXT:         Mutable:         true
+# CHECK-NEXT:         InitExpr:
+# CHECK-NEXT:           Opcode:          I32_CONST
+# CHECK-NEXT:           Value:           66576
+# CHECK-NEXT:       - Index:           1
+# CHECK-NEXT:         Type:            I32
+# CHECK-NEXT:         Mutable:         false
+# CHECK-NEXT:         InitExpr:
+# CHECK-NEXT:           Opcode:          I32_CONST
+# CHECK-NEXT:           Value:           1024
+# CHECK-NEXT:       - Index:           2
+# CHECK-NEXT:         Type:            I32
+# CHECK-NEXT:         Mutable:         false
+# CHECK-NEXT:         InitExpr:
+# CHECK-NEXT:           Opcode:          I32_CONST
+# CHECK-NEXT:           Value:           1032
+
+# STRONG:        - Type:            GLOBAL
+# STRONG-NEXT:     Globals:
+# STRONG-NEXT:       - Index:           0
+# STRONG-NEXT:         Type:            I32
+# STRONG-NEXT:         Mutable:         true
+# STRONG-NEXT:         InitExpr:
+# STRONG-NEXT:           Opcode:          I32_CONST
+# STRONG-NEXT:           Value:           66576
+# STRONG-NEXT:       - Index:           1
+# STRONG-NEXT:         Type:            I32
+# STRONG-NEXT:         Mutable:         false
+# STRONG-NEXT:         InitExpr:
+# STRONG-NEXT:           Opcode:          I32_CONST
+# STRONG-NEXT:           Value:           1024
+# STRONG:        - Type:            DATA
+# STRONG-NEXT:     Segments:
+# STRONG-NEXT:       - SectionOffset:   {{[0-9]+}}
+# STRONG-NEXT:         InitFlags:       0
+# STRONG-NEXT:         Offset:
+# STRONG-NEXT:           Opcode:          I32_CONST
+# STRONG-NEXT:           Value:           1024
+# STRONG-NEXT:         Content:         2A000000
+
+# WEAK-NOT:      - SectionOffset:   3
+# WEAK-NOT:        Content:         '63000000'
+# WEAK:        - Type:            GLOBAL
+# WEAK-NEXT:     Globals:
+# WEAK-NEXT:       - Index:           0
+# WEAK-NEXT:         Type:            I32
+# WEAK-NEXT:         Mutable:         true
+# WEAK-NEXT:         InitExpr:
+# WEAK-NEXT:           Opcode:          I32_CONST
+# WEAK-NEXT:           Value:           66576
+# WEAK-NEXT:       - Index:           1
+# WEAK-NEXT:         Type:            I32
+# WEAK-NEXT:         Mutable:         false
+# WEAK-NEXT:         InitExpr:
+# WEAK-NEXT:           Opcode:          I32_CONST
+# WEAK-NEXT:           Value:           1024
diff --git a/lld/wasm/Config.h b/lld/wasm/Config.h
index 491bf9233b0cf..b2a5febeb2807 100644
--- a/lld/wasm/Config.h
+++ b/lld/wasm/Config.h
@@ -38,6 +38,7 @@ class DefinedFunction;
 class DefinedGlobal;
 class UndefinedGlobal;
 class TableSymbol;
+class InputChunk;
 
 // For --unresolved-symbols.
 enum class UnresolvedPolicy { ReportError, Warn, Ignore, ImportDynamic };
@@ -146,6 +147,7 @@ struct Ctx {
   llvm::SmallVector<InputFunction *, 0> syntheticFunctions;
   llvm::SmallVector<InputGlobal *, 0> syntheticGlobals;
   llvm::SmallVector<InputTable *, 0> syntheticTables;
+  llvm::SmallVector<InputChunk *, 0> syntheticInputSegments;
 
   // linker-generated symbols
   struct WasmSym {
diff --git a/lld/wasm/InputChunks.cpp b/lld/wasm/InputChunks.cpp
index 79bac5f444405..e5662822f41b1 100644
--- a/lld/wasm/InputChunks.cpp
+++ b/lld/wasm/InputChunks.cpp
@@ -73,6 +73,9 @@ uint32_t InputChunk::getSize() const {
     }
   }
 
+  if (const auto *sis = dyn_cast<SyntheticInputSegment>(this))
+    return sis->getSize();
+
   return data().size();
 }
 
@@ -92,6 +95,9 @@ void InputChunk::writeTo(uint8_t *buf) const {
     // Apply relocations
     ms->relocate(buf + outSecOff);
     return;
+  } else if (const auto *sis = dyn_cast<SyntheticInputSegment>(this)) {
+    sis->writeTo(buf);
+    return;
   }
 
   // Copy contents
@@ -386,6 +392,10 @@ void InputFunction::writeCompressed(uint8_t *buf) const {
   LLVM_DEBUG(dbgs() << "  total: " << (buf + chunkSize - orig) << "\n");
 }
 
+void SyntheticInputSegment::writeTo(uint8_t *buf) const {
+  memset(buf + outSecOff, 0, size);
+}
+
 uint64_t InputChunk::getChunkOffset(uint64_t offset) const {
   if (const auto *ms = dyn_cast<MergeInputChunk>(this)) {
     LLVM_DEBUG(dbgs() << "getChunkOffset(merged): " << name << "\n");
diff --git a/lld/wasm/InputChunks.h b/lld/wasm/InputChunks.h
index e772894d95456..314f21eff370b 100644
--- a/lld/wasm/InputChunks.h
+++ b/lld/wasm/InputChunks.h
@@ -45,6 +45,7 @@ class InputChunk {
     Function,
     SyntheticFunction,
     Section,
+    SyntheticDataSegment,
   };
 
   StringRef name;
@@ -341,6 +342,24 @@ class SyntheticFunction : public InputFunction {
   void setBody(ArrayRef<uint8_t> body) { rawData = body; }
 };
 
+class SyntheticInputSegment : public InputChunk {
+public:
+  SyntheticInputSegment(StringRef name, uint32_t alignment, uint32_t flags)
+      : InputChunk(nullptr, InputChunk::SyntheticDataSegment, name, alignment, flags) {}
+
+  static bool classof(const InputChunk *c) {
+    return c->kind() == SyntheticDataSegment;
+  }
+
+  void setSize(uint64_t s) { size = s; }
+  uint32_t getSize() const { return size; }
+
+  void writeTo(uint8_t *buf) const;
+
+private:
+  uint64_t size = 0;
+};
+
 // Represents a single Wasm Section within an input file.
 class InputSection : public InputChunk {
 public:
diff --git a/lld/wasm/InputFiles.cpp b/lld/wasm/InputFiles.cpp
index ad7f207259a5b..694ba5e3506fa 100644
--- a/lld/wasm/InputFiles.cpp
+++ b/lld/wasm/InputFiles.cpp
@@ -675,6 +675,12 @@ Symbol *ObjFile::createDefined(const WasmSymbol &sym) {
     return symtab->addDefinedFunction(name, flags, this, func);
   }
   case WASM_SYMBOL_TYPE_DATA: {
+    if ((flags & WASM_SYMBOL_BINDING_MASK) == WASM_SYMBOL_BINDING_COMMON) {
+      assert(!sym.isBindingLocal());
+      auto size = sym.Info.CommonRef.Size;
+      auto alignment = sym.Info.CommonRef.Alignment;
+      return symtab->addCommon(name, flags, this, size, alignment);
+    }
     InputChunk *seg = segments[sym.Info.DataRef.Segment];
     auto offset = sym.Info.DataRef.Offset;
     auto size = sym.Info.DataRef.Size;
diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp
index 88ac54302c286..3353b5a76c523 100644
--- a/lld/wasm/SymbolTable.cpp
+++ b/lld/wasm/SymbolTable.cpp
@@ -335,6 +335,16 @@ static bool shouldReplace(const Symbol *existing, InputFile *newFile,
     return true;
   }
 
+  // If existing symbol is common, it can be overridden by a strong definition.
+  if (existing->isCommon()) {
+    if ((newFlags & WASM_SYMBOL_BINDING_MASK) == WASM_SYMBOL_BINDING_WEAK) {
+      LLVM_DEBUG(dbgs() << "existing common symbol takes precedence over new weak\n");
+      return false;
+    }
+    LLVM_DEBUG(dbgs() << "replacing existing common symbol with strong definition\n");
+    return true;
+  }
+
   // Now we have two defined symbols. If the new one is weak, we can ignore it.
   if ((newFlags & WASM_SYMBOL_BINDING_MASK) == WASM_SYMBOL_BINDING_WEAK) {
     LLVM_DEBUG(dbgs() << "existing symbol takes precedence\n");
@@ -496,6 +506,46 @@ Symbol *SymbolTable::addSharedData(StringRef name, uint32_t flags,
   return s;
 }
 
+Symbol *SymbolTable::addCommon(StringRef name, uint32_t flags, InputFile *file,
+                               uint64_t size, uint32_t alignment) {
+  LLVM_DEBUG(dbgs() << "addCommon: " << name << " size:" << size
+                    << " align:" << alignment << "\n");
+  auto [s, wasInserted] = insert(name, file);
+
+  auto replaceSym = [&]() {
+    replaceSymbol<CommonSymbol>(s, name, flags, file, size, alignment);
+  };
+
+  if (wasInserted || s->isLazy()) {
+    replaceSym();
+    return s;
+  }
+
+  checkDataType(s, file);
+
+  if (auto *existingCommon = dyn_cast<CommonSymbol>(s)) {
+    uint64_t newSize = std::max(existingCommon->getSize(), size);
+    uint32_t newAlign = std::max(existingCommon->getAlignment(), alignment);
+    existingCommon->setCommon(newSize, newAlign);
+    existingCommon->flags |= flags & WASM_SYMBOL_NO_STRIP;
+    return s;
+  }
+
+  if (s->isDefined()) {
+    if (s->isWeak()) {
+       LLVM_DEBUG(dbgs() << "replacing existing weak defined symbol with common\n");
+       replaceSym();
+    } else {
+       LLVM_DEBUG(dbgs() << "existing strong defined symbol takes precedence over common\n");
+    }
+    return s;
+  }
+
+  LLVM_DEBUG(dbgs() << "resolving existing undefined symbol with common\n");
+  replaceSym();
+  return s;
+}
+
 Symbol *SymbolTable::addDefinedFunction(StringRef name, uint32_t flags,
                                         InputFile *file,
                                         InputFunction *function) {
diff --git a/lld/wasm/SymbolTable.h b/lld/wasm/SymbolTable.h
index 0667cedecdb4f..007aabdca0f35 100644
--- a/lld/wasm/SymbolTable.h
+++ b/lld/wasm/SymbolTable.h
@@ -55,6 +55,8 @@ class SymbolTable {
   Symbol *addSharedData(StringRef name, uint32_t flags, InputFile *file);
   Symbol *addSharedTag(StringRef name, uint32_t flags, InputFile *file,
                        const WasmSignature *sig);
+  Symbol *addCommon(StringRef name, uint32_t flags, InputFile *file,
+                    uint64_t size, uint32_t alignment);
   Symbol *addDefinedFunction(StringRef name, uint32_t flags, InputFile *file,
                              InputFunction *function);
   Symbol *addDefinedData(StringRef name, uint32_t flags, InputFile *file,
diff --git a/lld/wasm/Symbols.cpp b/lld/wasm/Symbols.cpp
index 9ce2a9f9f5091..d82e13b8fb2e0 100644
--- a/lld/wasm/Symbols.cpp
+++ b/lld/wasm/Symbols.cpp
@@ -62,6 +62,8 @@ std::string toString(wasm::Symbol::Kind kind) {
     return "UndefinedTable";
   case wasm::Symbol::UndefinedTagKind:
     return "UndefinedTag";
+  case wasm::Symbol::CommonKind:
+    return "CommonSymbol";
   case wasm::Symbol::LazyKind:
     return "LazyKind";
   case wasm::Symbol::SectionKind:
diff --git a/lld/wasm/Symbols.h b/lld/wasm/Symbols.h
index 47f1b3ad54ea2..d0df380394fa2 100644
--- a/lld/wasm/Symbols.h
+++ b/lld/wasm/Symbols.h
@@ -59,6 +59,7 @@ class Symbol {
     UndefinedGlobalKind,
     UndefinedTableKind,
     UndefinedTagKind,
+    CommonKind,
     LazyKind,
     SharedFunctionKind,
     SharedDataKind,
@@ -77,6 +78,7 @@ class Symbol {
   }
 
   bool isLazy() const { return symbolKind == LazyKind; }
+  bool isCommon() const { return symbolKind == CommonKind; }
   bool isShared() const {
     return symbolKind == SharedFunctionKind || symbolKind == SharedDataKind ||
            symbolKind == SharedTagKind;
@@ -294,7 +296,7 @@ class DataSymbol : public Symbol {
 public:
   static bool classof(const Symbol *s) {
     return s->kind() == DefinedDataKind || s->kind() == UndefinedDataKind ||
-           s->kind() == SharedDataKind;
+           s->kind() == SharedDataKind || s->kind() == CommonKind;
   }
 
 protected:
@@ -349,6 +351,27 @@ class UndefinedData : public DataSymbol {
   }
 };
 
+class CommonSymbol : public DataSymbol {
+public:
+  CommonSymbol(StringRef name, uint32_t flags, InputFile *file,
+               uint64_t size, uint32_t alignment)
+      : DataSymbol(name, CommonKind, flags, file), size(size), alignment(alignment) {}
+
+  static bool classof(const Symbol *s) { return s->kind() == CommonKind; }
+
+  uint64_t getSize() const { return size; }
+  uint32_t getAlignment() const { return alignment; }
+
+  void setCommon(uint64_t s, uint32_t a) {
+    size = s;
+    alignment = a;
+  }
+
+private:
+  uint64_t size;
+  uint32_t alignment;
+};
+
 class GlobalSymbol : public Symbol {
 public:
   static bool classof(const Symbol *s) {
@@ -566,6 +589,7 @@ union SymbolUnion {
   alignas(SectionSymbol) char k[sizeof(SectionSymbol)];
   alignas(SharedFunctionSymbol) char l[sizeof(SharedFunctionSymbol)];
   alignas(SharedTagSymbol) char m[sizeof(SharedTagSymbol)];
+  alignas(CommonSymbol) char n[sizeof(CommonSymbol)];
 };
 
 // It is important to keep the size of SymbolUnion small for performance and
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 038a8b3f5417d..191f47590afc0 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -84,6 +84,7 @@ class Writer {
   void calculateCustomSections();
   void calculateTypes();
   void createOutputSegments();
+  void allocateCommonSymbols();
   OutputSegment *createOutputSegment(StringRef name);
   void combineOutputSegments();
   void layoutMemory();
@@ -1046,6 +1047,50 @@ OutputSegment *Writer::createOutputSegment(StringRef name) {
   return s;
 }
 
+void Writer::allocateCommonSymbols() {
+  if (ctx.arg.relocatable)
+    return;
+
+  std::vector<CommonSymbol *> commons;
+  for (Symbol *sym : symtab->symbols()) {
+    if (auto *c = dyn_cast<CommonSymbol>(sym)) {
+      if (c->isLive()) {
+        commons.push_back(c);
+      }
+    }
+  }
+
+  if (commons.empty())
+    return;
+
+  log("-- allocateCommonSymbols");
+
+  uint64_t size = 0;
+  uint32_t alignLog2 = 0;
+
+  for (CommonSymbol *c : commons) {
+    alignLog2 = std::max(alignLog2, c->getAlignment());
+    size = alignTo(size, 1ULL << c->getAlignment());
+    size += c->getSize();
+  }
+
+  auto *commonSeg = make<SyntheticInputSegment>(".bss.common", alignLog2, 0);
+  commonSeg->setSize(size);
+  commonSeg->live = true;
+  ctx.syntheticInputSegments.push_back(commonSeg);
+
+  uint64_t offset = 0;
+  for (CommonSymbol *c : commons) {
+    uint64_t size = c->getSize();
+    uint32_t alignLog2 = c->getAlignment();
+    offset = alignTo(offset, 1ULL << alignLog2);
+    log(formatv("allocateCommonSymbol: {0} size={1} align={2} offset={3}",
+                c->getName(), size, alignLog2, offset));
+    replaceSymbol<DefinedData>(c, c->getName(), c->flags, c->getFile(), commonSeg, offset, size);
+    offset += size;
+  }
+}
+
 void Writer::createOutputSegments() {
   for (ObjFile *file : ctx.objectFiles) {
     for (InputChunk *segment : file->segments) {
@@ -1067,6 +1112,18 @@ void Writer::createOutputSegments() {
     }
   }
 
+  // Process synthetic segments
+  for (InputChunk *segment : ctx.syntheticInputSegments) {
+    if (!segment->live)
+      continue;
+    StringRef name = getOutputDataSegmentName(*segment);
+    OutputSegment *s = nullptr;
+    if (!segmentMap.contains(name))
+      segmentMap[name] = createOutputSegment(name);
+    s = segmentMap[name];
+    s->addInputSegment(segment);
+  }
+
   // Sort segments by type, placing .bss last
   llvm::stable_sort(segments,
                     [](const OutputSegment *a, const OutputSegment *b) {
@@ -1734,6 +1791,9 @@ void Writer::run() {
   if (!ctx.isPic && ctx.sym.tableBase)
     setGlobalPtr(cast<DefinedGlobal>(ctx.sym.tableBase), ctx.arg.tableBase);
 
+  log("-- allocateCommonSymbols");
+  allocateCommonSymbols();
+
   log("-- createOutputSegments");
   createOutputSegments();
   log("-- createSyntheticSections");
diff --git a/llvm/include/llvm/BinaryFormat/Wasm.h b/llvm/include/llvm/BinaryFormat/Wasm.h
index fa71b9b8480f1..1ab9b351a8342 100644
--- a/llvm/include/llvm/BinaryFormat/Wasm.h
+++ b/llvm/include/llvm/BinaryFormat/Wasm.h
@@ -251,6 +251,7 @@ const unsigned WASM_SYMBOL_VISIBILITY_MASK = 0xc;
 const unsigned WASM_SYMBOL_BINDING_GLOBAL = 0x0;
 const unsigned WASM_SYMBOL_BINDING_WEAK = 0x1;
 const unsigned WASM_SYMBOL_BINDING_LOCAL = 0x2;
+const unsigned WASM_SYMBOL_BINDING_COMMON = 0x3;
 const unsigned WASM_SYMBOL_VISIBILITY_DEFAULT = 0x0;
 const unsigned WASM_SYMBOL_VISIBILITY_HIDDEN = 0x4;
 const unsigned WASM_SYMBOL_UNDEFINED = 0x10;
@@ -456,6 +457,11 @@ struct WasmDataReference {
   uint64_t Size;
 };
 
+struct WasmCommonReference {
+  uint64_t Size;
+  uint32_t Alignment;
+};
+
 struct WasmRelocation {
   uint8_t Type;    // The type of the relocation.
   uint32_t Index;  // Index into either symbol or type index space.
@@ -486,6 +492,8 @@ struct WasmSymbolInfo {
     uint32_t ElementIndex;
     // For a data symbols, the address of the data relative to segment.
     WasmDataReference DataRef;
+    // For common symbols, the size and alignment.
+    WasmCommonReference CommonRef;
   };
 };
 
diff --git a/llvm/include/llvm/ObjectYAML/WasmYAML.h b/llvm/include/llvm/ObjectYAML/WasmYAML.h
index b0ab04d1d8ac3..f9c678a6a9d07 100644
--- a/llvm/include/llvm/ObjectYAML/WasmYAML.h
+++ b/llvm/include/llvm/ObjectYAML/WasmYAML.h
@@ -166,6 +166,7 @@ struct SymbolInfo {
   union {
     uint32_t ElementIndex;
     wasm::WasmDataReference DataRef;
+    wasm::WasmCommonReference CommonRef;
   };
 };
 
diff --git a/llvm/lib/MC/MCWasmStreamer.cpp b/llvm/lib/MC/MCWasmStreamer.cpp
index 8b4ae4cee3e79..73a0369b3993c 100644
--- a/llvm/lib/MC/MCWasmStreamer.cpp
+++ b/llvm/lib/MC/MCWasmStreamer.cpp
@@ -130,9 +130,19 @@ bool MCWasmStreamer::emitSymbolAttribute(MCSymbol *S, MCSymbolAttr Attribute) {
 
 void MCWasmStreamer::emitCommonSymbol(MCSymbol *S, uint64_t Size,
                                       Align ByteAlignment) {
-  getContext().reportError(getStartTokLoc(),
-                           "common symbols are not yet implemented for Wasm: " +
-                               S->getName());
+  auto *Symbol = static_cast<MCSymbolWasm *>(S);
+  getAssembler().registerSymbol(*Symbol);
+
+  if (!Symbol->isExternal())
+    Symbol->setExternal(true);
+
+  Symbol->setType(wasm::WASM_SYMBOL_TYPE_DATA);
+
+  if (Symbol->declareCommon(Size, ByteAlignment))
+    report_fatal_error(Twine("Symbol: ") + Symbol->getName() +
+                       " redeclared as different type");
+
+  Symbol->setSize(MCConstantExpr::create(Size, getContext()));
 }
 
 void MCWasmStreamer::emitELFSize(MCSymbol *Symbol, const MCExpr *Value) {
diff --git a/llvm/lib/MC/WasmObjectWriter.cpp b/llvm/lib/MC/WasmObjectWriter.cpp
index 44ca69f0ff302..40be313c218c4 100644
--- a/llvm/lib/MC/WasmObjectWriter.cpp
+++ b/llvm/lib/MC/WasmObjectWriter.cpp
@@ -1161,9 +1161,14 @@ void WasmObjectWriter::writeLinkingMetaDataSection(
       case wasm::WASM_SYMBOL_TYPE_DATA:
         writeString(Sym.Name);
         if ((Sym.Flags & wasm::WASM_SYMBOL_UNDEFINED) == 0) {
-          encodeULEB128(Sym.DataRef.Segment, W->OS);
-          encodeULEB128(Sym.DataRef.Offset, W->OS);
-          encodeULEB128(Sym.DataRef.Size, W->OS);
+          if ((Sym.Flags & wasm::WASM_SYMBOL_BINDING_MASK) == wasm::WASM_SYMBOL_BINDING_COMMON) {
+            encodeULEB128(Sym.CommonRef.Size, W->OS);
+            W->OS << char(Sym.CommonRef.Alignment);
+          } else {
+            encodeULEB128(Sym.DataRef.Segment, W->OS);
+            encodeULEB128(Sym.DataRef.Offset, W->OS);
+            encodeULEB128(Sym.DataRef.Size, W->OS);
+          }
         }
         break;
       case wasm::WASM_SYMBOL_TYPE_SECTION: {
@@ -1789,9 +1794,11 @@ uint64_t WasmObjectWriter::writeOneObject(MCAssembler &Asm,
       Flags |= wasm::WASM_SYMBOL_BINDING_WEAK;
     if (WS.isHidden())
       Flags |= wasm::WASM_SYMBOL_VISIBILITY_HIDDEN;
-    if (!WS.isExternal() && WS.isDefined())
+    if (WS.isCommon())
+      Flags |= wasm::WASM_SYMBOL_BINDING_COMMON;
+    else if (!WS.isExternal() && WS.isDefined())
       Flags |= wasm::WASM_SYMBOL_BINDING_LOCAL;
-    if (WS.isUndefined())
+    if (WS.isUndefined() && !WS.isCommon())
       Flags |= wasm::WASM_SYMBOL_UNDEFINED;
     if (WS.isNoStrip()) {
       Flags |= wasm::WASM_SYMBOL_NO_STRIP;
@@ -1813,6 +1820,9 @@ uint64_t WasmObjectWriter::writeOneObject(MCAssembler &Asm,
     if (!WS.isData()) {
       assert(WasmIndices.contains(&WS));
       Info.ElementIndex = WasmIndices.find(&WS)->second;
+    } else if (WS.isCommon()) {
+      Info.CommonRef.Size = WS.getCommonSize();
+      Info.CommonRef.Alignment = Log2(WS.getCommonAlignment().valueOrOne());
     } else if (WS.isDefined()) {
       assert(DataLocations.contains(&WS));
       Info.DataRef = DataLocations.find(&WS)->second;
diff --git a/llvm/lib/Object/WasmObjectFile.cpp b/llvm/lib/Object/WasmObjectFile.cpp
index 98f60bd710c7e..795ac8a7e351f 100644
--- a/llvm/lib/Object/WasmObjectFile.cpp
+++ b/llvm/lib/Object/WasmObjectFile.cpp
@@ -829,23 +829,29 @@ Error WasmObjectFile::parseLinkingSectionSymtab(ReadContext &Ctx) {
     case wasm::WASM_SYMBOL_TYPE_DATA:
       Info.Name = readString(Ctx);
       if (IsDefined) {
-        auto Index = readVaruint32(Ctx);
-        auto Offset = readVaruint64(Ctx);
-        auto Size = readVaruint64(Ctx);
-        if (!(Info.Flags & wasm::WASM_SYMBOL_ABSOLUTE)) {
-          if (Index >= DataSegments.size())
-            return make_error<GenericBinaryError>(
-                "invalid data segment index: " + Twine(Index),
-                object_error::parse_failed);
-          size_t SegmentSize = DataSegments[Index].Data.Content.size();
-          if (Offset > SegmentSize)
-            return make_error<GenericBinaryError>(
-                "invalid data symbol offset: `" + Info.Name +
-                    "` (offset: " + Twine(Offset) +
-                    " segment size: " + Twine(SegmentSize) + ")",
-                object_error::parse_failed);
+        if ((Info.Flags & wasm::WASM_SYMBOL_BINDING_MASK) == wasm::WASM_SYMBOL_BINDING_COMMON) {
+          auto Size = readVaruint64(Ctx);
+          auto Alignment = readUint8(Ctx);
+          Info.CommonRef = wasm::WasmCommonReference{Size, Alignment};
+        } else {
+          auto Index = readVaruint32(Ctx);
+          auto Offset = readVaruint64(Ctx);
+          auto Size = readVaruint64(Ctx);
+          if (!(Info.Flags & wasm::WASM_SYMBOL_ABSOLUTE)) {
+            if (Index >= DataSegments.size())
+              return make_error<GenericBinaryError>(
+                  "invalid data segment index: " + Twine(Index),
+                  object_error::parse_failed);
+            size_t SegmentSize = DataSegments[Index].Data.Content.size();
+            if (Offset > SegmentSize)
+              return make_error<GenericBinaryError>(
+                  "invalid data symbol offset: `" + Info.Name +
+                      "` (offset: " + Twine(Offset) +
+                      " segment size: " + Twine(SegmentSize) + ")",
+                  object_error::parse_failed);
+          }
+          Info.DataRef = wasm::WasmDataReference{Index, Offset, Size};
         }
-        Info.DataRef = wasm::WasmDataReference{Index, Offset, Size};
       }
       break;
 
diff --git a/llvm/lib/ObjectYAML/WasmYAML.cpp b/llvm/lib/ObjectYAML/WasmYAML.cpp
index edbcb3c6ead11..2e3476b87d4f2 100644
--- a/llvm/lib/ObjectYAML/WasmYAML.cpp
+++ b/llvm/lib/ObjectYAML/WasmYAML.cpp
@@ -523,11 +523,16 @@ void MappingTraits<WasmYAML::SymbolInfo>::mapping(IO &IO,
     IO.mapRequired("Tag", Info.ElementIndex);
   } else if (Info.Kind == wasm::WASM_SYMBOL_TYPE_DATA) {
     if ((Info.Flags & wasm::WASM_SYMBOL_UNDEFINED) == 0) {
-      if ((Info.Flags & wasm::WASM_SYMBOL_ABSOLUTE) == 0) {
-        IO.mapRequired("Segment", Info.DataRef.Segment);
+      if ((Info.Flags & wasm::WASM_SYMBOL_BINDING_MASK) == wasm::WASM_SYMBOL_BINDING_COMMON) {
+        IO.mapRequired("Size", Info.CommonRef.Size);
+        IO.mapRequired("Align", Info.CommonRef.Alignment);
+      } else {
+        if ((Info.Flags & wasm::WASM_SYMBOL_ABSOLUTE) == 0) {
+          IO.mapRequired("Segment", Info.DataRef.Segment);
+        }
+        IO.mapOptional("Offset", Info.DataRef.Offset, 0u);
+        IO.mapRequired("Size", Info.DataRef.Size);
       }
-      IO.mapOptional("Offset", Info.DataRef.Offset, 0u);
-      IO.mapRequired("Size", Info.DataRef.Size);
     }
   } else if (Info.Kind == wasm::WASM_SYMBOL_TYPE_SECTION) {
     IO.mapRequired("Section", Info.ElementIndex);
@@ -575,6 +580,7 @@ void ScalarBitSetTraits<WasmYAML::SymbolFlags>::bitset(
   // BCaseMask(BINDING_MASK, BINDING_GLOBAL);
   BCaseMask(BINDING_MASK, BINDING_WEAK);
   BCaseMask(BINDING_MASK, BINDING_LOCAL);
+  BCaseMask(BINDING_MASK, BINDING_COMMON);
   // BCaseMask(VISIBILITY_MASK, VISIBILITY_DEFAULT);
   BCaseMask(VISIBILITY_MASK, VISIBILITY_HIDDEN);
   BCaseMask(UNDEFINED, UNDEFINED);
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
index edea99e629407..b32507b7c1a10 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyAsmPrinter.cpp
@@ -180,13 +180,6 @@ MCSymbolWasm *WebAssemblyAsmPrinter::getMCSymbolForFunction(
 }
 
 void WebAssemblyAsmPrinter::emitGlobalVariable(const GlobalVariable *GV) {
-  if (GV->hasCommonLinkage()) {
-    OutContext.reportError(SMLoc(),
-                           "common symbols are not yet implemented for Wasm: " +
-                               getSymbol(GV)->getName());
-    return;
-  }
-
   if (!WebAssembly::isWasmVarAddressSpace(GV->getAddressSpace())) {
     AsmPrinter::emitGlobalVariable(GV);
     return;
diff --git a/llvm/test/CodeGen/WebAssembly/common-error.ll b/llvm/test/CodeGen/WebAssembly/common-error.ll
deleted file mode 100644
index 8fce516fbf88e..0000000000000
--- a/llvm/test/CodeGen/WebAssembly/common-error.ll
+++ /dev/null
@@ -1,6 +0,0 @@
-; RUN: not llc -mtriple=wasm32-unknown-unknown -filetype=asm %s -o - 2>&1 | FileCheck %s
-
-; CHECK: common symbols are not yet implemented for Wasm: x
-; CHECK: common symbols are not yet implemented for Wasm: y
- at x = common global i32 0, align 4
- at y = common global i32 0, align 4
diff --git a/llvm/test/CodeGen/WebAssembly/common.ll b/llvm/test/CodeGen/WebAssembly/common.ll
new file mode 100644
index 0000000000000..ea372465f926b
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/common.ll
@@ -0,0 +1,9 @@
+; RUN: llc -mtriple=wasm32-unknown-unknown -filetype=asm %s -o - | FileCheck %s
+
+; CHECK:         .type   x, at object
+; CHECK-NEXT:    .comm   x,4,2
+; CHECK:         .type   y, at object
+; CHECK-NEXT:    .comm   y,8,3
+
+ at x = common global i32 0, align 4
+ at y = common global i64 0, align 8
diff --git a/llvm/test/MC/WebAssembly/common-error.s b/llvm/test/MC/WebAssembly/common-error.s
deleted file mode 100644
index 38c8fbca352b2..0000000000000
--- a/llvm/test/MC/WebAssembly/common-error.s
+++ /dev/null
@@ -1,6 +0,0 @@
-# RUN: not llvm-mc -triple=wasm32-unknown-unknown -filetype=obj %s 2>&1 | FileCheck %s
-
-# CHECK:common-error.s:5:9: error: common symbols are not yet implemented for Wasm: x
-# CHECK:common-error.s:6:9: error: common symbols are not yet implemented for Wasm: y
-        .comm x,4,4
-        .comm y,4,4
diff --git a/llvm/test/MC/WebAssembly/common.s b/llvm/test/MC/WebAssembly/common.s
new file mode 100644
index 0000000000000..9af65dbcc4a84
--- /dev/null
+++ b/llvm/test/MC/WebAssembly/common.s
@@ -0,0 +1,31 @@
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown %s -o %t.o
+# RUN: obj2yaml %t.o | FileCheck %s
+
+        .comm x,4,4
+        .comm y,8,8
+
+        .hidden z
+        .comm z,16,16
+
+# CHECK:        - Type:            CUSTOM
+# CHECK-NEXT:     Name:            linking
+# CHECK-NEXT:     Version:         2
+# CHECK-NEXT:     SymbolTable:
+# CHECK-NEXT:       - Index:           0
+# CHECK-NEXT:         Kind:            DATA
+# CHECK-NEXT:         Name:            x
+# CHECK-NEXT:         Flags:           [ BINDING_COMMON ]
+# CHECK-NEXT:         Size:            4
+# CHECK-NEXT:         Align:           4
+# CHECK-NEXT:       - Index:           1
+# CHECK-NEXT:         Kind:            DATA
+# CHECK-NEXT:         Name:            y
+# CHECK-NEXT:         Flags:           [ BINDING_COMMON ]
+# CHECK-NEXT:         Size:            8
+# CHECK-NEXT:         Align:           8
+# CHECK-NEXT:       - Index:           2
+# CHECK-NEXT:         Kind:            DATA
+# CHECK-NEXT:         Name:            z
+# CHECK-NEXT:         Flags:           [ BINDING_COMMON, VISIBILITY_HIDDEN ]
+# CHECK-NEXT:         Size:            16
+# CHECK-NEXT:         Align:           16
diff --git a/llvm/tools/llvm-readobj/WasmDumper.cpp b/llvm/tools/llvm-readobj/WasmDumper.cpp
index 5d7639b0b70fd..f5d9010dc3704 100644
--- a/llvm/tools/llvm-readobj/WasmDumper.cpp
+++ b/llvm/tools/llvm-readobj/WasmDumper.cpp
@@ -45,6 +45,7 @@ const EnumEntry<unsigned> WasmSymbolFlags[] = {
   ENUM_ENTRY(BINDING_GLOBAL),
   ENUM_ENTRY(BINDING_WEAK),
   ENUM_ENTRY(BINDING_LOCAL),
+  ENUM_ENTRY(BINDING_COMMON),
   ENUM_ENTRY(VISIBILITY_DEFAULT),
   ENUM_ENTRY(VISIBILITY_HIDDEN),
   ENUM_ENTRY(UNDEFINED),
@@ -235,9 +236,14 @@ void WasmDumper::printSymbol(const SymbolRef &Sym) {
   if (Symbol.Info.Kind != wasm::WASM_SYMBOL_TYPE_DATA) {
     W.printHex("ElementIndex", Symbol.Info.ElementIndex);
   } else if (!(Symbol.Info.Flags & wasm::WASM_SYMBOL_UNDEFINED)) {
-    W.printHex("Offset", Symbol.Info.DataRef.Offset);
-    W.printHex("Segment", Symbol.Info.DataRef.Segment);
-    W.printHex("Size", Symbol.Info.DataRef.Size);
+    if ((Symbol.Info.Flags & wasm::WASM_SYMBOL_BINDING_MASK) == wasm::WASM_SYMBOL_BINDING_COMMON) {
+      W.printHex("Size", Symbol.Info.CommonRef.Size);
+      W.printHex("Alignment", Symbol.Info.CommonRef.Alignment);
+    } else {
+      W.printHex("Offset", Symbol.Info.DataRef.Offset);
+      W.printHex("Segment", Symbol.Info.DataRef.Segment);
+      W.printHex("Size", Symbol.Info.DataRef.Size);
+    }
   }
 }
 
diff --git a/llvm/tools/obj2yaml/wasm2yaml.cpp b/llvm/tools/obj2yaml/wasm2yaml.cpp
index 7b504faa5ae4e..f13ba564b79a9 100644
--- a/llvm/tools/obj2yaml/wasm2yaml.cpp
+++ b/llvm/tools/obj2yaml/wasm2yaml.cpp
@@ -135,7 +135,11 @@ WasmDumper::dumpCustomSection(const WasmSection &WasmSec) {
       Info.Flags = Symbol.Flags;
       switch (Symbol.Kind) {
       case wasm::WASM_SYMBOL_TYPE_DATA:
-        Info.DataRef = Symbol.DataRef;
+        if ((Symbol.Flags & wasm::WASM_SYMBOL_BINDING_MASK) == wasm::WASM_SYMBOL_BINDING_COMMON) {
+          Info.CommonRef = Symbol.CommonRef;
+        } else {
+          Info.DataRef = Symbol.DataRef;
+        }
         break;
       case wasm::WASM_SYMBOL_TYPE_FUNCTION:
       case wasm::WASM_SYMBOL_TYPE_GLOBAL:

>From 41df2214b19ab6ed62453f7fc95402470db9fdf1 Mon Sep 17 00:00:00 2001
From: Derek Schuff <dschuff at chromium.org>
Date: Fri, 22 May 2026 21:41:51 +0000
Subject: [PATCH 2/5] don't allow common | absolute

---
 llvm/lib/Object/WasmObjectFile.cpp         |  4 ++++
 llvm/test/Object/Wasm/common-absolute.yaml | 21 +++++++++++++++++++++
 2 files changed, 25 insertions(+)
 create mode 100644 llvm/test/Object/Wasm/common-absolute.yaml

diff --git a/llvm/lib/Object/WasmObjectFile.cpp b/llvm/lib/Object/WasmObjectFile.cpp
index 795ac8a7e351f..02afd5be72373 100644
--- a/llvm/lib/Object/WasmObjectFile.cpp
+++ b/llvm/lib/Object/WasmObjectFile.cpp
@@ -830,6 +830,10 @@ Error WasmObjectFile::parseLinkingSectionSymtab(ReadContext &Ctx) {
       Info.Name = readString(Ctx);
       if (IsDefined) {
         if ((Info.Flags & wasm::WASM_SYMBOL_BINDING_MASK) == wasm::WASM_SYMBOL_BINDING_COMMON) {
+          if (Info.Flags & wasm::WASM_SYMBOL_ABSOLUTE)
+            return make_error<GenericBinaryError>(
+                "common symbols cannot be absolute: " + Info.Name,
+                object_error::parse_failed);
           auto Size = readVaruint64(Ctx);
           auto Alignment = readUint8(Ctx);
           Info.CommonRef = wasm::WasmCommonReference{Size, Alignment};
diff --git a/llvm/test/Object/Wasm/common-absolute.yaml b/llvm/test/Object/Wasm/common-absolute.yaml
new file mode 100644
index 0000000000000..930584aa5dc00
--- /dev/null
+++ b/llvm/test/Object/Wasm/common-absolute.yaml
@@ -0,0 +1,21 @@
+# RUN: yaml2obj %s | not llvm-objdump -s - 2>&1 | FileCheck %s
+
+# Check that a common data symbol cannot be marked as absolute.
+
+# CHECK: common symbols cannot be absolute: foo
+
+--- !WASM
+FileHeader:
+  Version:         0x00000001
+Sections:
+  - Type:            CUSTOM
+    Name:            linking
+    Version:         2
+    SymbolTable:
+      - Index:           0
+        Kind:            DATA
+        Name:            foo
+        Flags:           [ BINDING_COMMON, ABSOLUTE ]
+        Size:            4
+        Align:           4
+...

>From f99cd8af2712bead048f1e13466ede3b386d77c9 Mon Sep 17 00:00:00 2001
From: Derek Schuff <dschuff at chromium.org>
Date: Sat, 23 May 2026 00:18:09 +0000
Subject: [PATCH 3/5] avoid C++20-ism

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

diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp
index 3353b5a76c523..73a98965bea82 100644
--- a/lld/wasm/SymbolTable.cpp
+++ b/lld/wasm/SymbolTable.cpp
@@ -510,7 +510,9 @@ Symbol *SymbolTable::addCommon(StringRef name, uint32_t flags, InputFile *file,
                                uint64_t size, uint32_t alignment) {
   LLVM_DEBUG(dbgs() << "addCommon: " << name << " size:" << size
                     << " align:" << alignment << "\n");
-  auto [s, wasInserted] = insert(name, file);
+  auto val = insert(name, file);
+  Symbol *s = val.first;
+  bool wasInserted = val.second;
 
   auto replaceSym = [&]() {
     replaceSymbol<CommonSymbol>(s, name, flags, file, size, alignment);

>From 25acd6fbd7b0984ef7c98fad5c62193b53a43e2c Mon Sep 17 00:00:00 2001
From: Derek Schuff <dschuff at chromium.org>
Date: Sat, 23 May 2026 00:20:59 +0000
Subject: [PATCH 4/5] clang-format

---
 lld/wasm/InputChunks.h                 |  3 ++-
 lld/wasm/SymbolTable.cpp               | 15 ++++++++++-----
 lld/wasm/Symbols.h                     |  7 ++++---
 lld/wasm/Writer.cpp                    |  3 ++-
 llvm/lib/MC/WasmObjectWriter.cpp       |  3 ++-
 llvm/lib/Object/WasmObjectFile.cpp     |  3 ++-
 llvm/lib/ObjectYAML/WasmYAML.cpp       |  3 ++-
 llvm/tools/llvm-readobj/WasmDumper.cpp | 18 +++++++-----------
 llvm/tools/obj2yaml/wasm2yaml.cpp      |  3 ++-
 9 files changed, 33 insertions(+), 25 deletions(-)

diff --git a/lld/wasm/InputChunks.h b/lld/wasm/InputChunks.h
index 314f21eff370b..4a7739bef4539 100644
--- a/lld/wasm/InputChunks.h
+++ b/lld/wasm/InputChunks.h
@@ -345,7 +345,8 @@ class SyntheticFunction : public InputFunction {
 class SyntheticInputSegment : public InputChunk {
 public:
   SyntheticInputSegment(StringRef name, uint32_t alignment, uint32_t flags)
-      : InputChunk(nullptr, InputChunk::SyntheticDataSegment, name, alignment, flags) {}
+      : InputChunk(nullptr, InputChunk::SyntheticDataSegment, name, alignment,
+                   flags) {}
 
   static bool classof(const InputChunk *c) {
     return c->kind() == SyntheticDataSegment;
diff --git a/lld/wasm/SymbolTable.cpp b/lld/wasm/SymbolTable.cpp
index 73a98965bea82..a178951890d26 100644
--- a/lld/wasm/SymbolTable.cpp
+++ b/lld/wasm/SymbolTable.cpp
@@ -338,10 +338,12 @@ static bool shouldReplace(const Symbol *existing, InputFile *newFile,
   // If existing symbol is common, it can be overridden by a strong definition.
   if (existing->isCommon()) {
     if ((newFlags & WASM_SYMBOL_BINDING_MASK) == WASM_SYMBOL_BINDING_WEAK) {
-      LLVM_DEBUG(dbgs() << "existing common symbol takes precedence over new weak\n");
+      LLVM_DEBUG(
+          dbgs() << "existing common symbol takes precedence over new weak\n");
       return false;
     }
-    LLVM_DEBUG(dbgs() << "replacing existing common symbol with strong definition\n");
+    LLVM_DEBUG(
+        dbgs() << "replacing existing common symbol with strong definition\n");
     return true;
   }
 
@@ -535,10 +537,13 @@ Symbol *SymbolTable::addCommon(StringRef name, uint32_t flags, InputFile *file,
 
   if (s->isDefined()) {
     if (s->isWeak()) {
-       LLVM_DEBUG(dbgs() << "replacing existing weak defined symbol with common\n");
-       replaceSym();
+      LLVM_DEBUG(
+          dbgs() << "replacing existing weak defined symbol with common\n");
+      replaceSym();
     } else {
-       LLVM_DEBUG(dbgs() << "existing strong defined symbol takes precedence over common\n");
+      LLVM_DEBUG(
+          dbgs()
+          << "existing strong defined symbol takes precedence over common\n");
     }
     return s;
   }
diff --git a/lld/wasm/Symbols.h b/lld/wasm/Symbols.h
index d0df380394fa2..1e5c85bd94954 100644
--- a/lld/wasm/Symbols.h
+++ b/lld/wasm/Symbols.h
@@ -353,9 +353,10 @@ class UndefinedData : public DataSymbol {
 
 class CommonSymbol : public DataSymbol {
 public:
-  CommonSymbol(StringRef name, uint32_t flags, InputFile *file,
-               uint64_t size, uint32_t alignment)
-      : DataSymbol(name, CommonKind, flags, file), size(size), alignment(alignment) {}
+  CommonSymbol(StringRef name, uint32_t flags, InputFile *file, uint64_t size,
+               uint32_t alignment)
+      : DataSymbol(name, CommonKind, flags, file), size(size),
+        alignment(alignment) {}
 
   static bool classof(const Symbol *s) { return s->kind() == CommonKind; }
 
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 191f47590afc0..71b3a902e368c 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -1086,7 +1086,8 @@ void Writer::allocateCommonSymbols() {
     offset = alignTo(offset, 1ULL << alignLog2);
     log(formatv("allocateCommonSymbol: {0} size={1} align={2} offset={3}",
                 c->getName(), size, alignLog2, offset));
-    replaceSymbol<DefinedData>(c, c->getName(), c->flags, c->getFile(), commonSeg, offset, size);
+    replaceSymbol<DefinedData>(c, c->getName(), c->flags, c->getFile(),
+                               commonSeg, offset, size);
     offset += size;
   }
 }
diff --git a/llvm/lib/MC/WasmObjectWriter.cpp b/llvm/lib/MC/WasmObjectWriter.cpp
index 40be313c218c4..2c780576142a5 100644
--- a/llvm/lib/MC/WasmObjectWriter.cpp
+++ b/llvm/lib/MC/WasmObjectWriter.cpp
@@ -1161,7 +1161,8 @@ void WasmObjectWriter::writeLinkingMetaDataSection(
       case wasm::WASM_SYMBOL_TYPE_DATA:
         writeString(Sym.Name);
         if ((Sym.Flags & wasm::WASM_SYMBOL_UNDEFINED) == 0) {
-          if ((Sym.Flags & wasm::WASM_SYMBOL_BINDING_MASK) == wasm::WASM_SYMBOL_BINDING_COMMON) {
+          if ((Sym.Flags & wasm::WASM_SYMBOL_BINDING_MASK) ==
+              wasm::WASM_SYMBOL_BINDING_COMMON) {
             encodeULEB128(Sym.CommonRef.Size, W->OS);
             W->OS << char(Sym.CommonRef.Alignment);
           } else {
diff --git a/llvm/lib/Object/WasmObjectFile.cpp b/llvm/lib/Object/WasmObjectFile.cpp
index 02afd5be72373..6ad9eca63a358 100644
--- a/llvm/lib/Object/WasmObjectFile.cpp
+++ b/llvm/lib/Object/WasmObjectFile.cpp
@@ -829,7 +829,8 @@ Error WasmObjectFile::parseLinkingSectionSymtab(ReadContext &Ctx) {
     case wasm::WASM_SYMBOL_TYPE_DATA:
       Info.Name = readString(Ctx);
       if (IsDefined) {
-        if ((Info.Flags & wasm::WASM_SYMBOL_BINDING_MASK) == wasm::WASM_SYMBOL_BINDING_COMMON) {
+        if ((Info.Flags & wasm::WASM_SYMBOL_BINDING_MASK) ==
+            wasm::WASM_SYMBOL_BINDING_COMMON) {
           if (Info.Flags & wasm::WASM_SYMBOL_ABSOLUTE)
             return make_error<GenericBinaryError>(
                 "common symbols cannot be absolute: " + Info.Name,
diff --git a/llvm/lib/ObjectYAML/WasmYAML.cpp b/llvm/lib/ObjectYAML/WasmYAML.cpp
index 2e3476b87d4f2..8c93d3a0763f7 100644
--- a/llvm/lib/ObjectYAML/WasmYAML.cpp
+++ b/llvm/lib/ObjectYAML/WasmYAML.cpp
@@ -523,7 +523,8 @@ void MappingTraits<WasmYAML::SymbolInfo>::mapping(IO &IO,
     IO.mapRequired("Tag", Info.ElementIndex);
   } else if (Info.Kind == wasm::WASM_SYMBOL_TYPE_DATA) {
     if ((Info.Flags & wasm::WASM_SYMBOL_UNDEFINED) == 0) {
-      if ((Info.Flags & wasm::WASM_SYMBOL_BINDING_MASK) == wasm::WASM_SYMBOL_BINDING_COMMON) {
+      if ((Info.Flags & wasm::WASM_SYMBOL_BINDING_MASK) ==
+          wasm::WASM_SYMBOL_BINDING_COMMON) {
         IO.mapRequired("Size", Info.CommonRef.Size);
         IO.mapRequired("Align", Info.CommonRef.Alignment);
       } else {
diff --git a/llvm/tools/llvm-readobj/WasmDumper.cpp b/llvm/tools/llvm-readobj/WasmDumper.cpp
index f5d9010dc3704..b8f37c7f5f2d1 100644
--- a/llvm/tools/llvm-readobj/WasmDumper.cpp
+++ b/llvm/tools/llvm-readobj/WasmDumper.cpp
@@ -42,16 +42,11 @@ const EnumEntry<uint32_t> WasmSectionTypes[] = {
 const EnumEntry<unsigned> WasmSymbolFlags[] = {
 #define ENUM_ENTRY(X)                                                          \
   { #X, wasm::WASM_SYMBOL_##X }
-  ENUM_ENTRY(BINDING_GLOBAL),
-  ENUM_ENTRY(BINDING_WEAK),
-  ENUM_ENTRY(BINDING_LOCAL),
-  ENUM_ENTRY(BINDING_COMMON),
-  ENUM_ENTRY(VISIBILITY_DEFAULT),
-  ENUM_ENTRY(VISIBILITY_HIDDEN),
-  ENUM_ENTRY(UNDEFINED),
-  ENUM_ENTRY(EXPORTED),
-  ENUM_ENTRY(EXPLICIT_NAME),
-  ENUM_ENTRY(NO_STRIP),
+    ENUM_ENTRY(BINDING_GLOBAL),     ENUM_ENTRY(BINDING_WEAK),
+    ENUM_ENTRY(BINDING_LOCAL),      ENUM_ENTRY(BINDING_COMMON),
+    ENUM_ENTRY(VISIBILITY_DEFAULT), ENUM_ENTRY(VISIBILITY_HIDDEN),
+    ENUM_ENTRY(UNDEFINED),          ENUM_ENTRY(EXPORTED),
+    ENUM_ENTRY(EXPLICIT_NAME),      ENUM_ENTRY(NO_STRIP),
 #undef ENUM_ENTRY
 };
 
@@ -236,7 +231,8 @@ void WasmDumper::printSymbol(const SymbolRef &Sym) {
   if (Symbol.Info.Kind != wasm::WASM_SYMBOL_TYPE_DATA) {
     W.printHex("ElementIndex", Symbol.Info.ElementIndex);
   } else if (!(Symbol.Info.Flags & wasm::WASM_SYMBOL_UNDEFINED)) {
-    if ((Symbol.Info.Flags & wasm::WASM_SYMBOL_BINDING_MASK) == wasm::WASM_SYMBOL_BINDING_COMMON) {
+    if ((Symbol.Info.Flags & wasm::WASM_SYMBOL_BINDING_MASK) ==
+        wasm::WASM_SYMBOL_BINDING_COMMON) {
       W.printHex("Size", Symbol.Info.CommonRef.Size);
       W.printHex("Alignment", Symbol.Info.CommonRef.Alignment);
     } else {
diff --git a/llvm/tools/obj2yaml/wasm2yaml.cpp b/llvm/tools/obj2yaml/wasm2yaml.cpp
index f13ba564b79a9..2db5387b1ec6c 100644
--- a/llvm/tools/obj2yaml/wasm2yaml.cpp
+++ b/llvm/tools/obj2yaml/wasm2yaml.cpp
@@ -135,7 +135,8 @@ WasmDumper::dumpCustomSection(const WasmSection &WasmSec) {
       Info.Flags = Symbol.Flags;
       switch (Symbol.Kind) {
       case wasm::WASM_SYMBOL_TYPE_DATA:
-        if ((Symbol.Flags & wasm::WASM_SYMBOL_BINDING_MASK) == wasm::WASM_SYMBOL_BINDING_COMMON) {
+        if ((Symbol.Flags & wasm::WASM_SYMBOL_BINDING_MASK) ==
+            wasm::WASM_SYMBOL_BINDING_COMMON) {
           Info.CommonRef = Symbol.CommonRef;
         } else {
           Info.DataRef = Symbol.DataRef;

>From 71bb22cc8978a709e2373982fdc0e358ab0c8754 Mon Sep 17 00:00:00 2001
From: Derek Schuff <dschuff at chromium.org>
Date: Fri, 19 Jun 2026 00:36:49 +0000
Subject: [PATCH 5/5] Fix generating relocatable output with common symbols.
 also fix after symbol GC change upstream

---
 lld/test/wasm/common.s         | 21 +++++++++++++++++++++
 lld/wasm/SyntheticSections.cpp |  3 +++
 2 files changed, 24 insertions(+)

diff --git a/lld/test/wasm/common.s b/lld/test/wasm/common.s
index 09a1c5d60e14a..3e4c5009a8fcc 100644
--- a/lld/test/wasm/common.s
+++ b/lld/test/wasm/common.s
@@ -17,6 +17,10 @@
 # RUN: wasm-ld --no-stack-first --global-base=1024 --export=foo %t/common1.o %t/weak.o %t/main.o -o %t_weak.wasm
 # RUN: obj2yaml %t_weak.wasm | FileCheck %s --check-prefix=WEAK
 
+# Test 4: Relocatable link preserves common symbols (merged)
+# RUN: wasm-ld -r %t/common1.o %t/common2.o -o %t/common_reloc.o
+# RUN: obj2yaml %t/common_reloc.o | FileCheck %s --check-prefix=RELOC
+
 #--- common1.s
 .comm foo, 4, 2
 .comm bar, 8, 3
@@ -40,9 +44,12 @@ foo:
 .size foo, 4
 
 #--- main.s
+.globaltype __stack_pointer, i32
 .globl _start
 _start:
   .functype _start () -> ()
+  global.get __stack_pointer
+  drop
   i32.const foo
   drop
   i32.const bar
@@ -109,3 +116,17 @@ _start:
 # WEAK-NEXT:         InitExpr:
 # WEAK-NEXT:           Opcode:          I32_CONST
 # WEAK-NEXT:           Value:           1024
+
+# RELOC:          SymbolTable:
+# RELOC-NEXT:       - Index:           0
+# RELOC-NEXT:         Kind:            DATA
+# RELOC-NEXT:         Name:            foo
+# RELOC-NEXT:         Flags:           [ BINDING_COMMON ]
+# RELOC-NEXT:         Size:            8
+# RELOC-NEXT:         Align:           3
+# RELOC-NEXT:       - Index:           1
+# RELOC-NEXT:         Kind:            DATA
+# RELOC-NEXT:         Name:            bar
+# RELOC-NEXT:         Flags:           [ BINDING_COMMON ]
+# RELOC-NEXT:         Size:            8
+# RELOC-NEXT:         Align:           3
diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp
index d1a01c7ec3f9d..30caae73a75a9 100644
--- a/lld/wasm/SyntheticSections.cpp
+++ b/lld/wasm/SyntheticSections.cpp
@@ -732,6 +732,9 @@ void LinkingSection::writeBody() {
             writeUleb128(sub.os, dataSym->getVA(), "data offset");
           }
           writeUleb128(sub.os, dataSym->getSize(), "data size");
+        } else if (auto *commonSym = dyn_cast<CommonSymbol>(sym)) {
+          writeUleb128(sub.os, commonSym->getSize(), "common size");
+          writeU8(sub.os, commonSym->getAlignment(), "common alignment");
         }
       } else {
         auto *s = cast<OutputSectionSymbol>(sym);



More information about the llvm-commits mailing list