[WebAssembly] Add initial support for compact imports proposal (#176617)

This change adds initial support to libObject for reading compact
imports and support for writing compact imports in the linker.

There is minimal testing here since to tools like lllvm-readobj, and
obj2yaml don't currently report compact imports any differently.

See https://github.com/WebAssembly/compact-import-section
This commit is contained in:
Sam Clegg 2026-02-10 10:57:45 -08:00 committed by GitHub
parent ce94d63f0f
commit 11c2613342
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 167 additions and 53 deletions

View File

@ -5603,6 +5603,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>;

View File

@ -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)
@ -119,6 +120,8 @@ void WebAssemblyTargetInfo::getTargetDefines(const LangOptions &Opts,
Builder.defineMacro("__wasm_tail_call__");
if (HasWideArithmetic)
Builder.defineMacro("__wasm_wide_arithmetic__");
// Note that not all wasm features appear here. For example,
// HasCompatctImports
Builder.defineMacro("__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1");
Builder.defineMacro("__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2");
@ -191,6 +194,7 @@ bool WebAssemblyTargetInfo::initFeatureMap(
auto addBleedingEdgeFeatures = [&]() {
addGenericFeatures();
Features["atomics"] = true;
Features["compact-imports"] = true;
Features["exception-handling"] = true;
Features["extended-const"] = true;
Features["fp16"] = true;
@ -247,6 +251,14 @@ bool WebAssemblyTargetInfo::handleTargetFeatures(
HasCallIndirectOverlong = false;
continue;
}
if (Feature == "+compact-imports") {
HasCompactImports = true;
continue;
}
if (Feature == "-compact-imports") {
HasCompactImports = false;
continue;
}
if (Feature == "+exception-handling") {
HasExceptionHandling = true;
continue;

View File

@ -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;

View File

@ -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"

View File

@ -0,0 +1,27 @@
# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
# RUN: wasm-ld --experimental-pic --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

View File

@ -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,34 @@ 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 encoding 1");
writeUleb128(os, groupSize, "num compact imports");
while (groupSize--) {
writeCompactImport(os, imports[i++]);
}
continue;
}
}
writeImport(os, imports[i++]);
}
}

View File

@ -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) {

View File

@ -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

View File

@ -250,6 +250,8 @@ private:
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);

View File

@ -284,9 +284,9 @@ static Error readInitExpr(wasm::WasmInitExpr &Expr,
Expr.Body = ArrayRef<uint8_t>(Start, Ctx.Ptr - Start);
return Error::success();
default:
return make_error<GenericBinaryError>(
Twine("invalid opcode in init_expr: ") + Twine(unsigned(Opcode)),
object_error::parse_failed);
return make_error<GenericBinaryError>("invalid opcode in init_expr: " +
Twine(unsigned(Opcode)),
object_error::parse_failed);
}
}
}
@ -1276,60 +1276,86 @@ 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;
// 0x7E/0x7F along with an empty Field signals a block of compact imports.
if (Im.Kind == 0x7E && Im.Field == "") {
return make_error<GenericBinaryError>(
"compact import format (0x7E) is not yet supported",
object_error::parse_failed);
} else 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++;
}
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);
}
Imports.push_back(Im);
}
if (Ctx.Ptr != Ctx.End)
return make_error<GenericBinaryError>("import section ended prematurely",

View File

@ -47,6 +47,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;
@ -109,6 +110,7 @@ public:
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; }