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 <[email protected]> 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; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
