diff --git a/src/parser/parsers.h b/src/parser/parsers.h index b1eb6c71b9d..bf45566830a 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -384,6 +384,7 @@ template MaybeResult<> typedef_(Ctx&); template MaybeResult<> rectype(Ctx&); template MaybeResult locals(Ctx&); template MaybeResult<> import_(Ctx&); +template MaybeResult<> importItem(Ctx&, Name module); template MaybeResult<> func(Ctx&); template MaybeResult<> table(Ctx&); template MaybeResult<> memory(Ctx&); @@ -3308,7 +3309,12 @@ template MaybeResult locals(Ctx& ctx) { return {}; } +template MaybeResult<> importItem(Ctx& ctx, Name name) { + return Ok{}; +} + // import ::= '(' 'import' mod:name nm:name importdesc ')' +// '(' 'import' mod:name compactimportdesc ')' // importdesc ::= '(' 'func' id? exacttypeuse ')' // | '(' 'table' id? tabletype ')' // | '(' 'memory' id? memtype ')' @@ -3326,6 +3332,16 @@ template MaybeResult<> import_(Ctx& ctx) { return ctx.in.err("expected import module name"); } + if (ctx.in.peekSExprStart("item"sv)) { + while (ctx.in.peekSExprStart("item"sv)) { + CHECK_ERR(importItem(ctx, *mod)); + } + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of import"); + } + return Ok{}; + } + auto nm = ctx.in.takeName(); if (!nm) { return ctx.in.err("expected import name"); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 5a1f618da3d..eee2811535f 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -461,6 +461,7 @@ extern const char* BulkMemoryOptFeature; extern const char* CallIndirectOverlongFeature; extern const char* CustomDescriptorsFeature; extern const char* RelaxedAtomicsFeature; +extern const char* CompactImportsFeature; enum Subsection { NameModule = 0, @@ -1637,6 +1638,12 @@ class WasmBinaryReader { Address defaultIfNoMax); void readImports(); + void addImport(uint32_t kind, std::unique_ptr importable); + std::unique_ptr + readImportDetails(Name module, Name field, uint32_t kind); + std::unique_ptr copyImportable(uint32_t kind, + Importable& details); + // The signatures of each function, including imported functions, given in the // import and function sections. Store HeapTypes instead of Signatures because // reconstructing the HeapTypes from the Signatures is expensive. diff --git a/src/wasm-features.h b/src/wasm-features.h index 7f4b0a451af..8446c47b3e1 100644 --- a/src/wasm-features.h +++ b/src/wasm-features.h @@ -56,11 +56,12 @@ struct FeatureSet { CallIndirectOverlong = 1 << 20, CustomDescriptors = 1 << 21, RelaxedAtomics = 1 << 22, + CompactImports = 1 << 23, MVP = None, // Keep in sync with llvm default features: // https://github.com/llvm/llvm-project/blob/c7576cb89d6c95f03968076e902d3adfd1996577/clang/lib/Basic/Targets/WebAssembly.cpp#L150-L153 Default = SignExt | MutableGlobals, - All = (1 << 23) - 1, + All = (1 << 24) - 1, }; static std::string toString(Feature f) { @@ -111,6 +112,8 @@ struct FeatureSet { return "custom-descriptors"; case RelaxedAtomics: return "relaxed-atomics"; + case CompactImports: + return "compact-imports"; case MVP: case Default: case All: @@ -172,6 +175,7 @@ struct FeatureSet { return (features & CustomDescriptors) != 0; } bool hasRelaxedAtomics() const { return (features & RelaxedAtomics) != 0; } + bool hasCompactImports() const { return (features & CompactImports) != 0; } bool hasAll() const { return (features & All) != 0; } void set(FeatureSet f, bool v = true) { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 1aa002f4e03..8932265b829 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1453,6 +1453,8 @@ void WasmBinaryWriter::writeFeaturesSection() { return BinaryConsts::CustomSections::CustomDescriptorsFeature; case FeatureSet::RelaxedAtomics: return BinaryConsts::CustomSections::RelaxedAtomicsFeature; + case FeatureSet::CompactImports: + return BinaryConsts::CustomSections::CompactImportsFeature; case FeatureSet::None: case FeatureSet::Default: case FeatureSet::All: @@ -2906,124 +2908,217 @@ void WasmBinaryReader::getResizableLimits(Address& initial, } } +void WasmBinaryReader::addImport(uint32_t kind, + std::unique_ptr details) { + switch (kind) { + case ExternalKind::Function: + case ExternalKind::Function | BinaryConsts::ExactImport: { + std::unique_ptr func(static_cast(details.release())); + auto [name, isExplicit] = + getOrMakeName(functionNames, + wasm.functions.size(), + makeName("fimport$", wasm.functions.size()), + usedFunctionNames); + func->name = name; + func->hasExplicitName = isExplicit; + functionTypes.push_back(func->type.getHeapType()); + wasm.addFunction(std::move(func)); + break; + } + case ExternalKind::Table: { + std::unique_ptr table(static_cast(details.release())); + auto [name, isExplicit] = + getOrMakeName(tableNames, + wasm.tables.size(), + makeName("timport$", wasm.tables.size()), + usedTableNames); + table->name = name; + table->hasExplicitName = isExplicit; + wasm.addTable(std::move(table)); + break; + } + case ExternalKind::Memory: { + std::unique_ptr memory(static_cast(details.release())); + auto [name, isExplicit] = + getOrMakeName(memoryNames, + wasm.memories.size(), + makeName("mimport$", wasm.memories.size()), + usedMemoryNames); + memory->name = name; + memory->hasExplicitName = isExplicit; + wasm.addMemory(std::move(memory)); + break; + } + case ExternalKind::Global: { + std::unique_ptr global(static_cast(details.release())); + auto [name, isExplicit] = + getOrMakeName(globalNames, + wasm.globals.size(), + makeName("gimport$", wasm.globals.size()), + usedGlobalNames); + global->name = name; + global->hasExplicitName = isExplicit; + wasm.addGlobal(std::move(global)); + break; + } + case ExternalKind::Tag: { + std::unique_ptr tag(static_cast(details.release())); + auto [name, isExplicit] = + getOrMakeName(tagNames, + wasm.tags.size(), + makeName("eimport$", wasm.tags.size()), + usedTagNames); + tag->name = name; + tag->hasExplicitName = isExplicit; + wasm.addTag(std::move(tag)); + break; + } + default: + WASM_UNREACHABLE("unexpected kind"); + } +} + +std::unique_ptr +WasmBinaryReader::copyImportable(uint32_t kind, Importable& details) { + switch (kind) { + case ExternalKind::Function: + case ExternalKind::Function | BinaryConsts::ExactImport: { + auto func = std::make_unique(); + *func = static_cast(details); + return func; + } + case ExternalKind::Table: { + auto table = std::make_unique
(); + *table = static_cast(details); + return table; + } + case ExternalKind::Memory: { + auto memory = std::make_unique(); + *memory = static_cast(details); + return memory; + } + case ExternalKind::Global: { + auto global = std::make_unique(); + *global = static_cast(details); + return global; + } + case ExternalKind::Tag: { + auto tag = std::make_unique(); + *tag = static_cast(details); + return tag; + } + } + WASM_UNREACHABLE("unexpected kind"); +} + +std::unique_ptr +WasmBinaryReader::readImportDetails(Name module, Name base, uint32_t kind) { + Builder builder(wasm); + // We set a unique prefix for the name based on the kind. This ensures no + // collisions between them, which can't occur here (due to the index i) but + // could occur later due to the names section. + switch (kind) { + case ExternalKind::Function: + case ExternalKind::Function | BinaryConsts::ExactImport: { + auto index = getU32LEB(); + auto type = getTypeByIndex(index); + if (!type.isSignature()) { + throwError(std::string("Imported function ") + module.toString() + '.' + + base.toString() + + "'s type must be a signature. Given: " + type.toString()); + } + auto exact = (kind & BinaryConsts::ExactImport) ? Exact : Inexact; + auto curr = builder.makeFunction("", Type(type, NonNullable, exact), {}); + curr->module = module; + curr->base = base; + setLocalNames(*curr, wasm.functions.size()); + return curr; + } + case ExternalKind::Table: { + auto table = builder.makeTable(""); + table->module = module; + table->base = base; + table->type = getType(); + + bool is_shared; + getResizableLimits(table->initial, + table->max, + is_shared, + table->addressType, + Table::kUnlimitedSize); + if (is_shared) { + throwError("Tables may not be shared"); + } + return table; + } + case ExternalKind::Memory: { + auto memory = builder.makeMemory(""); + memory->module = module; + memory->base = base; + getResizableLimits(memory->initial, + memory->max, + memory->shared, + memory->addressType, + Memory::kUnlimitedSize); + return memory; + } + case ExternalKind::Global: { + auto type = getConcreteType(); + auto mutable_ = getU32LEB(); + if (mutable_ & ~1) { + throwError("Global mutability must be 0 or 1"); + } + auto curr = builder.makeGlobal( + "", type, nullptr, mutable_ ? Builder::Mutable : Builder::Immutable); + curr->module = module; + curr->base = base; + return curr; + } + case ExternalKind::Tag: { + getInt8(); // Reserved 'attribute' field + auto index = getU32LEB(); + auto curr = builder.makeTag("", getSignatureByTypeIndex(index)); + curr->module = module; + curr->base = base; + return curr; + } + default: { + throwError("bad import kind"); + } + } +} + void WasmBinaryReader::readImports() { size_t num = getU32LEB(); - Builder builder(wasm); for (size_t i = 0; i < num; i++) { auto module = getInlineString(); auto base = getInlineString(); - auto kind = getU32LEB(); - // We set a unique prefix for the name based on the kind. This ensures no - // collisions between them, which can't occur here (due to the index i) but - // could occur later due to the names section. - switch (kind) { - case ExternalKind::Function: - case ExternalKind::Function | BinaryConsts::ExactImport: { - auto [name, isExplicit] = - getOrMakeName(functionNames, - wasm.functions.size(), - makeName("fimport$", wasm.functions.size()), - usedFunctionNames); - auto index = getU32LEB(); - functionTypes.push_back(getTypeByIndex(index)); - auto type = getTypeByIndex(index); - if (!type.isSignature()) { - throwError(std::string("Imported function ") + module.toString() + - '.' + base.toString() + - "'s type must be a signature. Given: " + type.toString()); - } - auto exact = (kind & BinaryConsts::ExactImport) ? Exact : Inexact; - auto curr = - builder.makeFunction(name, Type(type, NonNullable, exact), {}); - curr->hasExplicitName = isExplicit; - curr->module = module; - curr->base = base; - setLocalNames(*curr, wasm.functions.size()); - wasm.addFunction(std::move(curr)); - break; + auto kind = getInt8(); + if (base == "" && (kind == 0x7F || kind == 0x7E)) { + if (!wasm.features.hasCompactImports()) { + throwError("compact imports not supported"); } - case ExternalKind::Table: { - auto [name, isExplicit] = - getOrMakeName(tableNames, - wasm.tables.size(), - makeName("timport$", wasm.tables.size()), - usedTableNames); - auto table = builder.makeTable(name); - table->hasExplicitName = isExplicit; - table->module = module; - table->base = base; - table->type = getType(); - - bool is_shared; - getResizableLimits(table->initial, - table->max, - is_shared, - table->addressType, - Table::kUnlimitedSize); - if (is_shared) { - throwError("Tables may not be shared"); + if (kind == 0x7F) { + size_t numCompactImports = getU32LEB(); + while (numCompactImports--) { + base = getInlineString(); + kind = getInt8(); + auto details = readImportDetails(module, base, kind); + addImport(kind, std::move(details)); } - wasm.addTable(std::move(table)); - break; - } - case ExternalKind::Memory: { - auto [name, isExplicit] = - getOrMakeName(memoryNames, - wasm.memories.size(), - makeName("mimport$", wasm.memories.size()), - usedMemoryNames); - auto memory = builder.makeMemory(name); - memory->hasExplicitName = isExplicit; - memory->module = module; - memory->base = base; - getResizableLimits(memory->initial, - memory->max, - memory->shared, - memory->addressType, - Memory::kUnlimitedSize); - wasm.addMemory(std::move(memory)); - break; - } - case ExternalKind::Global: { - auto [name, isExplicit] = - getOrMakeName(globalNames, - wasm.globals.size(), - makeName("gimport$", wasm.globals.size()), - usedGlobalNames); - auto type = getConcreteType(); - auto mutable_ = getU32LEB(); - if (mutable_ & ~1) { - throwError("Global mutability must be 0 or 1"); + } else { + kind = getU32LEB(); + auto base_details = readImportDetails(module, base, kind); + size_t numCompactImports = getU32LEB(); + while (numCompactImports--) { + auto details = copyImportable(kind, *base_details); + details->base = getInlineString(); + addImport(kind, std::move(details)); } - auto curr = - builder.makeGlobal(name, - type, - nullptr, - mutable_ ? Builder::Mutable : Builder::Immutable); - curr->hasExplicitName = isExplicit; - curr->module = module; - curr->base = base; - wasm.addGlobal(std::move(curr)); - break; - } - case ExternalKind::Tag: { - auto [name, isExplicit] = - getOrMakeName(tagNames, - wasm.tags.size(), - makeName("eimport$", wasm.tags.size()), - usedTagNames); - getInt8(); // Reserved 'attribute' field - auto index = getU32LEB(); - auto curr = builder.makeTag(name, getSignatureByTypeIndex(index)); - curr->hasExplicitName = isExplicit; - curr->module = module; - curr->base = base; - wasm.addTag(std::move(curr)); - break; - } - default: { - throwError("bad import kind"); } + } else { + auto details = readImportDetails(module, base, kind); + addImport(kind, std::move(details)); } } numFuncImports = wasm.functions.size(); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 447847c18d7..4cb0f6f3741 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -62,6 +62,7 @@ const char* BulkMemoryOptFeature = "bulk-memory-opt"; const char* CallIndirectOverlongFeature = "call-indirect-overlong"; const char* CustomDescriptorsFeature = "custom-descriptors"; const char* RelaxedAtomicsFeature = "relaxed-atomics"; +const char* CompactImportsFeature = "compact-imports"; } // namespace BinaryConsts::CustomSections diff --git a/test/spec/compact-import-section/binary-compact-imports.wast b/test/spec/compact-import-section/binary-compact-imports.wast new file mode 100644 index 00000000000..bf5d9e02130 --- /dev/null +++ b/test/spec/compact-import-section/binary-compact-imports.wast @@ -0,0 +1,174 @@ +;; Auxiliary modules to import + +(module + (func (export "b") (result i32) (i32.const 0x0f)) + (func (export "c") (result i32) (i32.const 0xf0)) +) +(register "a") +(module + (func (export "") (result i32) (i32.const 0xab)) +) +(register "") + + +;; Valid compact encodings + +(module binary + "\00asm" "\01\00\00\00" + "\01\05\01\60\00\01\7f" ;; Type section: (type (func (result i32))) + "\02\0e" ;; Import section + "\01" ;; 1 group + "\01a" ;; "a" + "\00" "\7f" ;; "" + 0x7f (compact encoding) + "\02" ;; 2 items + "\01b" "\00\00" ;; "b" (func (type 0)) + "\01c" "\00\00" ;; "c" (func (type 0)) + "\03\02" "\01" ;; Function section, 1 func + "\00" ;; func 2: type 0 + "\07\08" "\01" ;; Export section, 1 export + "\04test" "\00\02" ;; "test" func 2 + "\0a\09" "\01" ;; Code section, 1 func + "\07" "\00" ;; len, 0 locals + "\10\00" ;; call 0 + "\10\01" ;; call 1 + "\6a" ;; i32.add + "\0b" ;; end +) +(assert_return (invoke "test") (i32.const 0xff)) + +(module binary + "\00asm" "\01\00\00\00" + "\01\05\01\60\00\01\7f" ;; Type section: (type (func (result i32))) + "\02\0c" ;; Import section + "\01" ;; 1 group + "\01a" ;; "a" + "\00" "\7e" ;; "" + 0x7e (compact encoding) + "\00\00" ;; (func (type 0)) + "\02" ;; 2 items + "\01b" ;; "b" + "\01c" ;; "c" + "\03\02" "\01" ;; Function section, 1 func + "\00" ;; func 2: type 0 + "\07\08" "\01" ;; Export section, 1 export + "\04test" "\00\02" ;; "test" func 2 + "\0a\09" "\01" ;; Code section, 1 func + "\07" "\00" ;; len, 0 locals + "\10\00" ;; call 0 + "\10\01" ;; call 1 + "\6a" ;; i32.add + "\0b" ;; end +) +(assert_return (invoke "test") (i32.const 0xff)) + + +;; Overly-long empty name encodings are valid + +(module binary + "\00asm" "\01\00\00\00" + "\01\05\01\60\00\01\7f" ;; Type section: (type (func (result i32))) + "\02\11" ;; Import section + "\01" ;; 1 group + "\01a" ;; "a" + "\80\80\80\00" "\7f" ;; "" (long encoding) + 0x7f + "\02" ;; 2 items + "\01b" "\00\00" ;; "b" (func (type 0)) + "\01c" "\00\00" ;; "c" (func (type 0)) +) +(module binary + "\00asm" "\01\00\00\00" + "\01\05\01\60\00\01\7f" ;; Type section: (type (func (result i32))) + "\02\0f" ;; Import section + "\01" ;; 1 group + "\01a" ;; "a" + "\80\80\80\00" "\7e" ;; "" (long encoding) + 0x7e + "\00\00" ;; (func (type 0)) + "\02" ;; 2 items + "\01b" ;; "b" + "\01c" ;; "c" +) + + +;; Discriminator is not valid except after empty names + +(assert_malformed + (module binary + "\00asm" "\01\00\00\00" + "\01\05\01\60\00\01\7f" ;; Type section: (type (func (result i32))) + "\02\12" ;; Import section + "\01" ;; 1 group + "\01a" ;; "a" + "\01b" "\7f" ;; "b" + 0x7f + "\02" ;; 2 items + "\01b" "\00\00" ;; "b" (func (type 0)) + "\01c" "\00\00" ;; "c" (func (type 0)) + ) + "malformed import kind" +) +(assert_malformed + (module binary + "\00asm" "\01\00\00\00" + "\01\05\01\60\00\01\7f" ;; Type section: (type (func (result i32))) + "\02\10" ;; Import section + "\01" ;; 1 group + "\01a" ;; "a" + "\01b" "\7e" ;; "" + 0x7e (long encoding) + "\00\00" ;; (func (type 0)) + "\02" ;; 2 items + "\01b" ;; "b" + "\01c" ;; "c" + ) + "malformed import kind" +) + + +;; Discriminator is not to be interpreted as LEB128 + +(assert_malformed + (module binary + "\00asm" "\01\00\00\00" + "\01\05\01\60\00\01\7f" ;; Type section: (type (func (result i32))) + "\02\11" ;; Import section + "\01" ;; 1 group + "\01a" ;; "a" + "\00\ff\80\80\00" ;; "" + 0x7f (long encoding) + "\02" ;; 2 items + "\01b" "\00\00" ;; "b" (func (type 0)) + "\01c" "\00\00" ;; "c" (func (type 0)) + ) + "malformed import kind" +) +(assert_malformed + (module binary + "\00asm" "\01\00\00\00" + "\01\05\01\60\00\01\7f" ;; Type section: (type (func (result i32))) + "\02\0f" ;; Import section + "\01" ;; 1 group + "\01a" ;; "a" + "\00\fe\80\80\00" ;; "" + 0x7e (long encoding) + "\00\00" ;; (func (type 0)) + "\02" ;; 2 items + "\01b" ;; "b" + "\01c" ;; "c" + ) + "malformed import kind" +) + + +;; Empty names are still valid if not followed by a discriminator + +(module binary + "\00asm" "\01\00\00\00" + "\01\05\01\60\00\01\7f" ;; Type section: (type (func (result i32))) + "\02\05" ;; Import section + "\01" ;; 1 group + "\00\00\00\00" ;; "" "" (func (type 0)) + "\03\02" "\01" ;; Function section, 1 func + "\00" ;; func 1: type 0 + "\07\08" "\01" ;; Export section, 1 export + "\04test" "\00\01" ;; "test" func 1 + "\0a\06" "\01" ;; Code section, 1 func + "\04" "\00" ;; len, 0 locals + "\10\00" ;; call 0 + "\0b" ;; end +) +(assert_return (invoke "test") (i32.const 0xab)) diff --git a/test/spec/compact-import-section/imports-compact.wast b/test/spec/compact-import-section/imports-compact.wast new file mode 100644 index 00000000000..43422356b16 --- /dev/null +++ b/test/spec/compact-import-section/imports-compact.wast @@ -0,0 +1,124 @@ +;; Auxiliary module to import from + +(module + (func (export "func->11i") (result i32) (i32.const 11)) + (func (export "func->22f") (result f32) (f32.const 22)) + (global (export "global->1") i32 (i32.const 1)) + (global (export "global->20") i32 (i32.const 20)) + (global (export "global->300") i32 (i32.const 300)) + (global (export "global->4000") i32 (i32.const 4000)) +) +(register "test") + + +;; Basic behavior + +(module + (import "test" + (item "func->11i" (func (result i32))) + (item "func->22f" (func (result f32))) + ) + (import "test" + (item "global->1") + (item "global->20") + (item "global->300") + (item "global->4000") + (global i32) + ) + + (global i32 (i32.const 50000)) + + (func (export "sum1") (result i32) + (local i32) + + call 0 + (i32.trunc_f32_s (call 1)) + i32.add + ) + (func (export "sum2") (result i32) + (local i32) + + global.get 0 + global.get 1 + global.get 2 + global.get 3 + i32.add + i32.add + i32.add + ) + + ;; Tests that indices were tracked correctly + (func (export "sum3") (result i32) + call 2 ;; sum1 + call 3 ;; sum2 + i32.add + + global.get 4 + i32.add + ) +) + +(assert_return (invoke "sum1") (i32.const 33)) +(assert_return (invoke "sum2") (i32.const 4321)) +(assert_return (invoke "sum3") (i32.const 54354)) + +(module (import "test" (item "func->11i" (func (result i32))))) +(assert_unlinkable + (module (import "test" (item "unknown" (func (result i32))))) + "unknown import" +) +(assert_unlinkable + (module (import "test" (item "func->11i" (func (result i32))) (item "unknown" (func (result i32))))) + "unknown import" +) + +(module (import "test" (item "func->11i") (func (result i32)))) +(assert_unlinkable + (module (import "test" (item "unknown") (func (result i32)))) + "unknown import" +) +(assert_unlinkable + (module (import "test" (item "func->11i") (item "unknown") (func (result i32)))) + "unknown import" +) + +(assert_unlinkable + (module (import "test" (item "func->11i" (func)))) + "incompatible import type" +) +(assert_unlinkable + (module (import "test" (item "func->11i" (func (result i32))) (item "func->22f" (func)))) + "incompatible import type" +) + +(assert_unlinkable + (module (import "test" (item "func->11i") (item "func->22f") (func (result i32)))) + "incompatible import type" +) + + +;; Identifiers + +(module + (import "test" "func->11i" (func $f11i (result i32))) + (import "test" + (item "global->1" (global $g1 i32)) + (item "global->20" (global $g20 i32)) + ) + ;; Shared-type form does not allow identifiers + + (func (export "sum") (result i32) + call $f11i + global.get $g1 + global.get $g20 + i32.add + i32.add + ) +) + +(assert_return (invoke "sum") (i32.const 32)) + +(assert_malformed + (module quote "(import \"test\" (item \"foo\") (func $foo))") + "identifier not allowed" +)