diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 53926af7680..6e8dd73ecd3 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -511,6 +511,9 @@ BinaryenFeatures BinaryenFeatureCustomPageSizes(void) { BinaryenFeatures BinaryenFeatureWideArithmetic(void) { return static_cast(FeatureSet::WideArithmetic); } +BinaryenFeatures BinaryenFeatureCompactImports(void) { + return static_cast(FeatureSet::CompactImports); +} BinaryenFeatures BinaryenFeatureAll(void) { return static_cast(FeatureSet::All); } diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 10e01fef3aa..fa1bfb37537 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -247,6 +247,7 @@ BINARYEN_API BinaryenFeatures BinaryenFeatureRelaxedAtomics(void); BINARYEN_API BinaryenFeatures BinaryenFeatureMultibyte(void); BINARYEN_API BinaryenFeatures BinaryenFeatureCustomPageSizes(void); BINARYEN_API BinaryenFeatures BinaryenFeatureWideArithmetic(void); +BINARYEN_API BinaryenFeatures BinaryenFeatureCompactImports(void); BINARYEN_API BinaryenFeatures BinaryenFeatureAll(void); // Modules diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 8c965d70ee0..a5ae4fdc0d7 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -195,6 +195,7 @@ function initializeConstants() { 'RelaxedAtomics', 'CustomPageSizes', 'WideArithmetic', + 'CompactImports', 'All' ].forEach(name => { Module['Features'][name] = Module['_BinaryenFeature' + name](); diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index 87362d455d2..2d8cbeaf0f6 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -113,6 +113,7 @@ struct ToolOptions : public Options { "acquire/release atomic memory operations") .addFeature(FeatureSet::CustomPageSizes, "custom page sizes") .addFeature(FeatureSet::WideArithmetic, "wide arithmetic") + .addFeature(FeatureSet::CompactImports, "compact import section") .add("--enable-typed-function-references", "", "Deprecated compatibility flag", diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 82363964d0b..8bc41e6e66b 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -475,6 +475,7 @@ extern const char* RelaxedAtomicsFeature; extern const char* MultibyteFeature; extern const char* CustomPageSizesFeature; extern const char* WideArithmeticFeature; +extern const char* CompactImportsFeature; enum Subsection { NameModule = 0, @@ -1671,6 +1672,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 833281c0c11..2d64953631e 100644 --- a/src/wasm-features.h +++ b/src/wasm-features.h @@ -59,11 +59,12 @@ struct FeatureSet { CustomPageSizes = 1 << 23, Multibyte = 1 << 24, WideArithmetic = 1 << 25, + CompactImports = 1 << 26, 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 << 26) - 1, + All = (1 << 27) - 1, }; static std::string toString(Feature f) { @@ -120,6 +121,8 @@ struct FeatureSet { return "multibyte"; case WideArithmetic: return "wide-arithmetic"; + case CompactImports: + return "compact-imports"; case MVP: case Default: case All: @@ -184,6 +187,7 @@ struct FeatureSet { bool hasCustomPageSizes() const { return (features & CustomPageSizes) != 0; } bool hasMultibyte() const { return (features & Multibyte) != 0; } bool hasWideArithmetic() const { return (features & WideArithmetic) != 0; } + bool hasCompactImports() const { return (features & CompactImports) != 0; } bool hasAll() const { return (features & All) != 0; } void set(FeatureSet f, bool v = true) { @@ -213,6 +217,7 @@ struct FeatureSet { void setRelaxedAtomics(bool v = true) { set(RelaxedAtomics, v); } void setMultibyte(bool v = true) { set(Multibyte, v); } void setWideArithmetic(bool v = true) { set(WideArithmetic, v); } + void setCompactImports(bool v = true) { set(CompactImports, v); } void setMVP() { features = MVP; } void setAll() { features = All; } diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index d5665fa48ec..1deda32a3b1 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1488,6 +1488,8 @@ void WasmBinaryWriter::writeFeaturesSection() { return BinaryConsts::CustomSections::CustomPageSizesFeature; case FeatureSet::WideArithmetic: return BinaryConsts::CustomSections::WideArithmeticFeature; + case FeatureSet::CompactImports: + return BinaryConsts::CustomSections::CompactImportsFeature; case FeatureSet::None: case FeatureSet::Default: case FeatureSet::All: @@ -3003,129 +3005,215 @@ 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"); + } +} + +template +std::unique_ptr copyImportable(Importable& details) { + auto item = std::make_unique(); + *item = static_cast(details); + return item; +} + +std::unique_ptr +WasmBinaryReader::copyImportable(uint32_t kind, Importable& details) { + switch (kind) { + case ExternalKind::Function: + case ExternalKind::Function | BinaryConsts::ExactImport: + return wasm::copyImportable(details); + case ExternalKind::Table: + return wasm::copyImportable
(details); + case ExternalKind::Memory: + return wasm::copyImportable(details); + case ExternalKind::Global: + return wasm::copyImportable(details); + case ExternalKind::Tag: + return wasm::copyImportable(details); + } + 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; + uint8_t page_size = 0xff; + getResizableLimits(table->initial, + table->max, + is_shared, + table->addressType, + page_size, + Table::kUnlimitedSize); + if (is_shared) { + throwError("Tables may not be shared"); + } + if (page_size != 0xff) { + throwError("Tables may not have a custom page size"); + } + 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->pageSizeLog2, + 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; - uint8_t page_size = 0xff; - getResizableLimits(table->initial, - table->max, - is_shared, - table->addressType, - page_size, - 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)); } - if (page_size != 0xff) { - throwError("Tables may not have a custom page size"); + } else { + kind = getInt8(); + 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)); } - 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->pageSizeLog2, - 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"); - } - 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(); @@ -5454,6 +5542,8 @@ void WasmBinaryReader::readFeatures(size_t sectionPos, size_t payloadLen) { feature = FeatureSet::CustomPageSizes; } else if (name == BinaryConsts::CustomSections::WideArithmeticFeature) { feature = FeatureSet::WideArithmetic; + } else if (name == BinaryConsts::CustomSections::CompactImportsFeature) { + feature = FeatureSet::CompactImports; } else { // Silently ignore unknown features (this may be and old binaryen running // on a new wasm). diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 9aae6cba291..0acdc082bd0 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -80,6 +80,7 @@ const char* RelaxedAtomicsFeature = "relaxed-atomics"; const char* MultibyteFeature = "multibyte"; const char* CustomPageSizesFeature = "custom-page-sizes"; const char* WideArithmeticFeature = "wide-arithmetic"; +const char* CompactImportsFeature = "compact-imports"; } // namespace BinaryConsts::CustomSections diff --git a/test/example/c-api-kitchen-sink.c b/test/example/c-api-kitchen-sink.c index e046b942303..9dc158cbe18 100644 --- a/test/example/c-api-kitchen-sink.c +++ b/test/example/c-api-kitchen-sink.c @@ -381,6 +381,7 @@ void test_features() { printf("BinaryenFeatureMultibyte: %d\n", BinaryenFeatureMultibyte()); printf("BinaryenFeatureWideArithmetic: %d\n", BinaryenFeatureWideArithmetic()); + printf("BinaryenFeatureCompactImports: %d\n", BinaryenFeatureCompactImports()); printf("BinaryenFeatureAll: %d\n", BinaryenFeatureAll()); } diff --git a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt index 2272fb36ff1..b61f97053bd 100644 --- a/test/passes/strip-target-features_roundtrip_print-features_all-features.txt +++ b/test/passes/strip-target-features_roundtrip_print-features_all-features.txt @@ -24,6 +24,7 @@ --enable-custom-page-sizes --enable-multibyte --enable-wide-arithmetic +--enable-compact-imports (module (type $0 (func (result v128 externref))) (func $foo (type $0) (result v128 externref) 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))