[clang] [lld] [llvm] [WebAssembly] Add initial support for compact imports proposal (PR #176617)
Sam Clegg via cfe-commits
cfe-commits at lists.llvm.org
Sat Jan 17 17:07:31 PST 2026
https://github.com/sbc100 created https://github.com/llvm/llvm-project/pull/176617
This change adds initial support to libObject for reading compact imports and support for writing compact imports in the linker.
See https://github.com/WebAssembly/compact-import-section
>From 344e8f93a6d199355cdcda4bda82d77a3fd34b01 Mon Sep 17 00:00:00 2001
From: Sam Clegg <sbc at chromium.org>
Date: Tue, 13 Jan 2026 10:10:42 -0800
Subject: [PATCH] [WebAssembly] Add initial support for compact imports
proposal
This change adds initial support to libObject for reading compact
imports and support for writing compact imports in the linker.
See https://github.com/WebAssembly/compact-import-section
---
clang/include/clang/Options/Options.td | 2 +
clang/lib/Basic/Targets/WebAssembly.cpp | 10 ++
clang/lib/Basic/Targets/WebAssembly.h | 1 +
clang/test/Driver/wasm-features.c | 6 +
lld/test/wasm/compact-imports.s | 27 +++++
lld/wasm/SyntheticSections.cpp | 37 +++++-
lld/wasm/WriterUtils.cpp | 4 +
lld/wasm/WriterUtils.h | 2 +
llvm/include/llvm/Object/Wasm.h | 2 +
llvm/lib/Object/WasmObjectFile.cpp | 114 ++++++++++--------
.../Target/WebAssembly/WebAssemblySubtarget.h | 2 +
11 files changed, 156 insertions(+), 51 deletions(-)
create mode 100644 lld/test/wasm/compact-imports.s
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 188739e72434a..133bdf32944a7 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -5554,6 +5554,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 mcompact_imports : Flag<["-"], "mcompact-imports">, Group<m_wasm_Features_Group>;
+def mno_compact_imports : Flag<["-"], "mno-compact-imports">, 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 5bbb7af4c2ca1..5c0a3ec583273 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("compact-imports", HasCompactImports)
.Case("exception-handling", HasExceptionHandling)
.Case("extended-const", HasExtendedConst)
.Case("fp16", HasFP16)
@@ -191,6 +192,7 @@ bool WebAssemblyTargetInfo::initFeatureMap(
auto addBleedingEdgeFeatures = [&]() {
addGenericFeatures();
Features["atomics"] = true;
+ Features["comptact-imports"] = true;
Features["exception-handling"] = true;
Features["extended-const"] = true;
Features["fp16"] = true;
@@ -247,6 +249,14 @@ bool WebAssemblyTargetInfo::handleTargetFeatures(
HasCallIndirectOverlong = false;
continue;
}
+ if (Feature == "+compact-imports") {
+ HasCallIndirectOverlong = true;
+ continue;
+ }
+ if (Feature == "-compact-imports") {
+ HasCallIndirectOverlong = false;
+ continue;
+ }
if (Feature == "+exception-handling") {
HasExceptionHandling = true;
continue;
diff --git a/clang/lib/Basic/Targets/WebAssembly.h b/clang/lib/Basic/Targets/WebAssembly.h
index 3634330ec6698..b0f81531b880d 100644
--- a/clang/lib/Basic/Targets/WebAssembly.h
+++ b/clang/lib/Basic/Targets/WebAssembly.h
@@ -62,6 +62,7 @@ class LLVM_LIBRARY_VISIBILITY WebAssemblyTargetInfo : public TargetInfo {
bool HasBulkMemory = false;
bool HasBulkMemoryOpt = false;
bool HasCallIndirectOverlong = false;
+ bool HasCompactImports = false;
bool HasExceptionHandling = false;
bool HasExtendedConst = false;
bool HasFP16 = false;
diff --git a/clang/test/Driver/wasm-features.c b/clang/test/Driver/wasm-features.c
index f0215ecc185c6..89ced36eeffab 100644
--- a/clang/test/Driver/wasm-features.c
+++ b/clang/test/Driver/wasm-features.c
@@ -106,3 +106,9 @@
// WIDE-ARITH: "-target-feature" "+wide-arithmetic"
// NO-WIDE-ARITH: "-target-feature" "-wide-arithmetic"
+
+// RUN: %clang --target=wasm32-unknown-unknown -### %s -mcompact-imports 2>&1 | FileCheck %s -check-prefix=COMPACT-IMPORTS
+// RUN: %clang --target=wasm32-unknown-unknown -### %s -mno-compact-imports 2>&1 | FileCheck %s -check-prefix=NO-COMPACT-IMPORTS
+
+// COMPACT-IMPORTS: "-target-feature" "+compact-imports"
+// NO-COMPACT-IMPORTS: "-target-feature" "-compact-imports"
diff --git a/lld/test/wasm/compact-imports.s b/lld/test/wasm/compact-imports.s
new file mode 100644
index 0000000000000..7c7ed198f57d4
--- /dev/null
+++ b/lld/test/wasm/compact-imports.s
@@ -0,0 +1,27 @@
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
+# RUN: wasm-ld --unresolved-symbols=import-dynamic %t.o -o %t.wasm
+
+.functype foo () -> ()
+.functype bar () -> ()
+
+.globl _start
+_start:
+ .functype _start () -> ()
+ call foo
+ call bar
+ end_function
+
+.section .custom_section.target_features,"",@
+.int8 1
+.int8 43
+.int8 15
+.ascii "compact-imports"
+
+# Neither llvm-readobj nor obj2yaml currently report compact imports differently
+# so the check here is just for the size of the import section. The Size here
+# is larger than 20 bytes without compact imports enabled.
+
+# RUN: llvm-readobj --sections %t.wasm | FileCheck %s
+
+# CHECK: Type: IMPORT (0x2)
+# CHECK-NEXT: Size: 20
diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp
index 5e7b9c229f3ed..81d8ef298d5a5 100644
--- a/lld/wasm/SyntheticSections.cpp
+++ b/lld/wasm/SyntheticSections.cpp
@@ -241,9 +241,12 @@ void ImportSection::addImport(Symbol *sym) {
void ImportSection::writeBody() {
raw_ostream &os = bodyOutputStream;
- writeUleb128(os, getNumImports(), "import count");
+ uint32_t numImports = getNumImports();
+ writeUleb128(os, numImports, "import count");
bool is64 = ctx.arg.is64.value_or(false);
+ std::vector<WasmImport> imports;
+ imports.reserve(numImports);
if (ctx.arg.memoryImport) {
WasmImport import;
@@ -264,7 +267,7 @@ void ImportSection::writeBody() {
import.Memory.Flags |= WASM_LIMITS_FLAG_HAS_PAGE_SIZE;
import.Memory.PageSize = ctx.arg.pageSize;
}
- writeImport(os, import);
+ imports.push_back(import);
}
for (const Symbol *sym : importedSymbols) {
@@ -286,7 +289,7 @@ void ImportSection::writeBody() {
import.Kind = WASM_EXTERNAL_TABLE;
import.Table = *tableSym->getTableType();
}
- writeImport(os, import);
+ imports.push_back(import);
}
for (const Symbol *sym : gotSymbols) {
@@ -299,7 +302,33 @@ void ImportSection::writeBody() {
else
import.Module = "GOT.func";
import.Field = sym->getName();
- writeImport(os, import);
+ imports.push_back(import);
+ }
+
+ bool hasCompactImports = out.targetFeaturesSec->features.contains("compact-imports");
+ uint32_t i = 0;
+ while (i < numImports) {
+ const WasmImport& import = imports[i];
+ if (hasCompactImports) {
+ uint32_t groupSize = 1;
+ for (uint32_t j = i + 1; j < numImports; j++) {
+ if (imports[j].Module == import.Module)
+ groupSize++;
+ else
+ break;
+ }
+ if (groupSize > 1) {
+ writeStr(os, import.Module, "module name");
+ writeStr(os, "", "empty field name");
+ writeU8(os, 0x7F, "compact imports constant");
+ writeUleb128(os, groupSize, "num compact imports");
+ while (groupSize--) {
+ writeCompactImport(os, imports[i++]);
+ }
+ continue;
+ }
+ }
+ writeImport(os, imports[i++]);
}
}
diff --git a/lld/wasm/WriterUtils.cpp b/lld/wasm/WriterUtils.cpp
index b78c354ffb97b..a23d0febb364f 100644
--- a/lld/wasm/WriterUtils.cpp
+++ b/lld/wasm/WriterUtils.cpp
@@ -218,6 +218,10 @@ void writeTableType(raw_ostream &os, const WasmTableType &type) {
void writeImport(raw_ostream &os, const WasmImport &import) {
writeStr(os, import.Module, "import module name");
+ writeCompactImport(os, import);
+}
+
+void writeCompactImport(raw_ostream &os, const WasmImport &import) {
writeStr(os, import.Field, "import field name");
writeU8(os, import.Kind, "import kind");
switch (import.Kind) {
diff --git a/lld/wasm/WriterUtils.h b/lld/wasm/WriterUtils.h
index 2be79d1d86e97..061199b168080 100644
--- a/lld/wasm/WriterUtils.h
+++ b/lld/wasm/WriterUtils.h
@@ -62,6 +62,8 @@ void writeTableType(raw_ostream &os, const llvm::wasm::WasmTableType &type);
void writeImport(raw_ostream &os, const llvm::wasm::WasmImport &import);
+void writeCompactImport(raw_ostream &os, const llvm::wasm::WasmImport &import);
+
void writeExport(raw_ostream &os, const llvm::wasm::WasmExport &export_);
} // namespace wasm
diff --git a/llvm/include/llvm/Object/Wasm.h b/llvm/include/llvm/Object/Wasm.h
index b4ba50778c152..55e2148e42670 100644
--- a/llvm/include/llvm/Object/Wasm.h
+++ b/llvm/include/llvm/Object/Wasm.h
@@ -250,6 +250,8 @@ class LLVM_ABI WasmObjectFile : public ObjectFile {
Error parseSection(WasmSection &Sec);
Error parseCustomSection(WasmSection &Sec, ReadContext &Ctx);
+ Error parseImport(ReadContext &Ctx, wasm::WasmImport& Im);
+
// Standard section types
Error parseTypeSection(ReadContext &Ctx);
Error parseImportSection(ReadContext &Ctx);
diff --git a/llvm/lib/Object/WasmObjectFile.cpp b/llvm/lib/Object/WasmObjectFile.cpp
index ee7a3068af91d..4111afb8005f3 100644
--- a/llvm/lib/Object/WasmObjectFile.cpp
+++ b/llvm/lib/Object/WasmObjectFile.cpp
@@ -276,7 +276,7 @@ static Error readInitExpr(wasm::WasmInitExpr &Expr,
return Error::success();
default:
return make_error<GenericBinaryError>(
- Twine("invalid opcode in init_expr: ") + Twine(unsigned(Opcode)),
+ "invalid opcode in init_expr: " + Twine(unsigned(Opcode)),
object_error::parse_failed);
}
}
@@ -1267,60 +1267,80 @@ Error WasmObjectFile::parseTypeSection(ReadContext &Ctx) {
return Error::success();
}
+Error WasmObjectFile::parseImport(ReadContext &Ctx, wasm::WasmImport& Im) {
+ switch (Im.Kind) {
+ case wasm::WASM_EXTERNAL_FUNCTION:
+ NumImportedFunctions++;
+ Im.SigIndex = readVaruint32(Ctx);
+ if (Im.SigIndex >= Signatures.size())
+ return make_error<GenericBinaryError>("invalid function type",
+ object_error::parse_failed);
+ break;
+ case wasm::WASM_EXTERNAL_GLOBAL:
+ NumImportedGlobals++;
+ Im.Global.Type = readUint8(Ctx);
+ Im.Global.Mutable = readVaruint1(Ctx);
+ break;
+ case wasm::WASM_EXTERNAL_MEMORY:
+ Im.Memory = readLimits(Ctx);
+ if (Im.Memory.Flags & wasm::WASM_LIMITS_FLAG_IS_64)
+ HasMemory64 = true;
+ break;
+ case wasm::WASM_EXTERNAL_TABLE: {
+ Im.Table = readTableType(Ctx);
+ NumImportedTables++;
+ auto ElemType = Im.Table.ElemType;
+ if (ElemType != wasm::ValType::FUNCREF &&
+ ElemType != wasm::ValType::EXTERNREF &&
+ ElemType != wasm::ValType::EXNREF &&
+ ElemType != wasm::ValType::OTHERREF)
+ return make_error<GenericBinaryError>("invalid table element type",
+ object_error::parse_failed);
+ break;
+ }
+ case wasm::WASM_EXTERNAL_TAG:
+ NumImportedTags++;
+ if (readUint8(Ctx) != 0) // Reserved 'attribute' field
+ return make_error<GenericBinaryError>("invalid attribute",
+ object_error::parse_failed);
+ Im.SigIndex = readVaruint32(Ctx);
+ if (Im.SigIndex >= Signatures.size())
+ return make_error<GenericBinaryError>("invalid tag type",
+ object_error::parse_failed);
+ break;
+ default:
+ return make_error<GenericBinaryError>("unexpected import kind: " + Twine(unsigned(Im.Kind)),
+ object_error::parse_failed);
+ }
+ Imports.push_back(Im);
+ return Error::success();
+}
+
Error WasmObjectFile::parseImportSection(ReadContext &Ctx) {
uint32_t Count = readVaruint32(Ctx);
- uint32_t NumTypes = Signatures.size();
Imports.reserve(Count);
- for (uint32_t I = 0; I < Count; I++) {
+ uint32_t I = 0;
+ while (I < Count) {
wasm::WasmImport Im;
Im.Module = readString(Ctx);
Im.Field = readString(Ctx);
Im.Kind = readUint8(Ctx);
- switch (Im.Kind) {
- case wasm::WASM_EXTERNAL_FUNCTION:
- NumImportedFunctions++;
- Im.SigIndex = readVaruint32(Ctx);
- if (Im.SigIndex >= NumTypes)
- return make_error<GenericBinaryError>("invalid function type",
- object_error::parse_failed);
- break;
- case wasm::WASM_EXTERNAL_GLOBAL:
- NumImportedGlobals++;
- Im.Global.Type = readUint8(Ctx);
- Im.Global.Mutable = readVaruint1(Ctx);
- break;
- case wasm::WASM_EXTERNAL_MEMORY:
- Im.Memory = readLimits(Ctx);
- if (Im.Memory.Flags & wasm::WASM_LIMITS_FLAG_IS_64)
- HasMemory64 = true;
- break;
- case wasm::WASM_EXTERNAL_TABLE: {
- Im.Table = readTableType(Ctx);
- NumImportedTables++;
- auto ElemType = Im.Table.ElemType;
- if (ElemType != wasm::ValType::FUNCREF &&
- ElemType != wasm::ValType::EXTERNREF &&
- ElemType != wasm::ValType::EXNREF &&
- ElemType != wasm::ValType::OTHERREF)
- return make_error<GenericBinaryError>("invalid table element type",
- object_error::parse_failed);
- break;
- }
- case wasm::WASM_EXTERNAL_TAG:
- NumImportedTags++;
- if (readUint8(Ctx) != 0) // Reserved 'attribute' field
- return make_error<GenericBinaryError>("invalid attribute",
- object_error::parse_failed);
- Im.SigIndex = readVaruint32(Ctx);
- if (Im.SigIndex >= NumTypes)
- return make_error<GenericBinaryError>("invalid tag type",
- object_error::parse_failed);
- break;
- default:
- return make_error<GenericBinaryError>("unexpected import kind",
- object_error::parse_failed);
+ // 0x7E along with and empty Field string signals a block of compact
+ // imports
+ if (Im.Kind == 0x7F && Im.Field == "") {
+ uint32_t NumCompactImports = readVaruint32(Ctx);
+ while (NumCompactImports--) {
+ Im.Field = readString(Ctx);
+ Im.Kind = readUint8(Ctx);
+ Error rtn = parseImport(Ctx, Im);
+ if (rtn) return rtn;
+ I++;
+ }
+ } else {
+ Error rtn = parseImport(Ctx, Im);
+ if (rtn) return rtn;
+ I++;
}
- Imports.push_back(Im);
}
if (Ctx.Ptr != Ctx.End)
return make_error<GenericBinaryError>("import section ended prematurely",
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h b/llvm/lib/Target/WebAssembly/WebAssemblySubtarget.h
index 2f88bbba05d00..922cffe0315d3 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 HasCompactImports = 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 hasCompactImports() const { return HasCompactImports; }
bool hasExceptionHandling() const { return HasExceptionHandling; }
bool hasExtendedConst() const { return HasExtendedConst; }
bool hasFP16() const { return HasFP16; }
More information about the cfe-commits
mailing list