diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 09604a37821..5dc0e2a63d7 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -101,6 +101,9 @@ 'stack_switching_resume.wast', 'stack_switching_resume_throw.wast', 'stack_switching_switch.wast', + # TODO: fuzzer support for type imports + 'type-imports.wast', + 'type-imports.wat' # TODO: fuzzer support for exact references 'exact-references.wast', 'optimize-instructions-exact.wast', diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index b32b7d6ff22..38d9dd2e1b7 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4940,6 +4940,13 @@ BinaryenExportRef BinaryenAddTagExport(BinaryenModuleRef module, ((Module*)module)->addExport(ret); return ret; } +BinaryenExportRef BinaryenAddTypeExport(BinaryenModuleRef module, + BinaryenHeapType type, + const char* externalName) { + auto* ret = new Export(externalName, ExternalKind::Type, HeapType(type)); + ((Module*)module)->addExport(ret); + return ret; +} BinaryenExportRef BinaryenGetExport(BinaryenModuleRef module, const char* externalName) { return ((Module*)module)->getExportOrNull(externalName); @@ -5924,6 +5931,9 @@ const char* BinaryenExportGetName(BinaryenExportRef export_) { const char* BinaryenExportGetValue(BinaryenExportRef export_) { return ((Export*)export_)->getInternalName()->str.data(); } +BinaryenHeapType BinaryenExportGetHeapType(BinaryenExportRef export_) { + return ((Export*)export_)->getHeapType()->getID(); +} // // ========= Custom sections ========= diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 5feca3ba2e3..7af3db1bca5 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -2717,6 +2717,10 @@ BINARYEN_API BinaryenExportRef BinaryenAddGlobalExport( BINARYEN_API BinaryenExportRef BinaryenAddTagExport(BinaryenModuleRef module, const char* internalName, const char* externalName); +// Adds a type export to the module. +BINARYEN_API BinaryenExportRef BinaryenAddTypeExport(BinaryenModuleRef module, + BinaryenHeapType type, + const char* externalName); // Gets an export reference by external name. Returns NULL if the export does // not exist. BINARYEN_API BinaryenExportRef BinaryenGetExport(BinaryenModuleRef module, @@ -3319,6 +3323,9 @@ BinaryenExportGetKind(BinaryenExportRef export_); BINARYEN_API const char* BinaryenExportGetName(BinaryenExportRef export_); // Gets the internal name of the specified export. BINARYEN_API const char* BinaryenExportGetValue(BinaryenExportRef export_); +// Gets the heap type of the specified type export. +BINARYEN_API BinaryenHeapType +BinaryenTypeExportGetHeapType(BinaryenExportRef export_); // // ========= Custom sections ========= diff --git a/src/ir/gc-type-utils.h b/src/ir/gc-type-utils.h index 6ac6db3ba06..a1790941b41 100644 --- a/src/ir/gc-type-utils.h +++ b/src/ir/gc-type-utils.h @@ -156,6 +156,7 @@ inline std::optional getField(HeapType type, Index index = 0) { return type.getArray().element; case HeapTypeKind::Func: case HeapTypeKind::Cont: + case HeapTypeKind::Import: case HeapTypeKind::Basic: break; } diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 7f2dfcc089c..12060f52a45 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -489,6 +489,11 @@ InsertOrderedMap collectHeapTypeInfo( for (auto& curr : wasm.elementSegments) { info.note(curr->type); } + for (auto& curr : wasm.exports) { + if (auto* heapType = curr->getHeapType()) { + info.note(*heapType); + } + } // Collect info from functions in parallel. ModuleUtils::ParallelFunctionAnalysis @@ -655,6 +660,9 @@ void classifyTypeVisibility(Module& wasm, case ExternalKind::Tag: notePublic(wasm.getTag(*ex->getInternalName())->type); continue; + case ExternalKind::Type: + notePublic(*ex->getHeapType()); + continue; case ExternalKind::Invalid: break; } @@ -730,9 +738,9 @@ std::vector getPrivateHeapTypes(Module& wasm) { return types; } -IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm) { - auto counts = collectHeapTypeInfo(wasm, TypeInclusion::BinaryTypes); - +IndexedHeapTypes sortHeapTypes(Module& wasm, + InsertOrderedMap& counts, + std::function map) { // Collect the rec groups. std::unordered_map groupIndices; std::vector groups; @@ -759,6 +767,7 @@ IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm) { for (size_t i = 0; i < groups.size(); ++i) { for (auto type : groups[i]) { for (auto child : type.getReferencedHeapTypes()) { + child = map(child); if (child.isBasic()) { continue; } @@ -793,6 +802,11 @@ IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm) { } } + std::vector isImport(groups.size()); + for (size_t i = 0; i < groups.size(); ++i) { + isImport[i] = groups[i][0].isImport(); + } + // If we've preserved the input type order on the module, we have to respect // that first. Use the index of the first type from each group. In principle // we could try to do something more robust like take the minimum index of all @@ -812,7 +826,11 @@ IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm) { } } - auto order = TopologicalSort::minSort(deps, [&](size_t a, size_t b) { + auto order = TopologicalSort::minSort(deps, [&](size_t a, size_t b) -> bool { + // Imports should be first + if (isImport[a] != isImport[b]) { + return isImport[a]; + } auto indexA = groupTypeIndices[a]; auto indexB = groupTypeIndices[b]; // Groups with indices must be sorted before groups without indices to @@ -846,4 +864,10 @@ IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm) { return indexedTypes; } +IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm) { + auto counts = collectHeapTypeInfo(wasm, TypeInclusion::BinaryTypes); + return sortHeapTypes( + wasm, counts, [](HeapType type) -> HeapType { return type; }); +} + } // namespace wasm::ModuleUtils diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index bb8b6ae439d..8154bbf65e2 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -492,6 +492,12 @@ struct IndexedHeapTypes { std::unordered_map indices; }; +// Orders the types to be valid (after renaming by the map function) +// and sorts the types by frequency of use to minimize code size. +IndexedHeapTypes sortHeapTypes(Module& wasm, + InsertOrderedMap& counts, + std::function map); + // Similar to `collectHeapTypes`, but provides fast lookup of the index for each // type as well. Also orders the types to be valid and sorts the types by // frequency of use to minimize code size. diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h index c69250043b8..8a25c939396 100644 --- a/src/ir/subtypes.h +++ b/src/ir/subtypes.h @@ -127,6 +127,9 @@ struct SubTypes { break; case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Import: + basic = type.getImport().bound; + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 6dd91e0960e..84291c316d5 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -152,6 +152,9 @@ GlobalTypeRewriter::TypeMap GlobalTypeRewriter::rebuildTypes( } case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Import: { + break; + } case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -302,6 +305,11 @@ void GlobalTypeRewriter::mapTypes(const TypeMap& oldToNewTypes) { for (auto& tag : wasm.tags) { tag->type = updater.getNew(tag->type); } + for (auto& exp : wasm.exports) { + if (auto* heapType = exp->getHeapType()) { + *heapType = updater.getNew(*heapType); + } + } } void GlobalTypeRewriter::mapTypeNamesAndIndices(const TypeMap& oldToNewTypes) { diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 3b9cb21c3e0..2af115abe20 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -940,6 +940,10 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { std::vector dataDefs; std::vector tagDefs; + // Type imports: name, export names, import names, and positions. + std::vector, ImportNames, Index>> + typeImports; + // Positions of export definitions. std::vector exportDefs; @@ -962,6 +966,7 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { // Used to verify that all imports come before all non-imports. bool hasNonImport = false; + bool hasTypeDefinition = false; Result<> checkImport(Index pos, ImportNames* import) { if (import) { @@ -983,9 +988,10 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { void setOpen() {} void setShared() {} Result<> addSubtype(HeapTypeT) { return Ok{}; } - void finishTypeDef(Name name, Index pos) { + void finishTypeDef(Name name, const std::vector& exports, Index pos) { // TODO: type annotations typeDefs.push_back({name, pos, Index(typeDefs.size()), {}}); + hasTypeDefinition = true; } size_t getRecGroupStartIndex() { return 0; } void addRecGroup(Index, size_t) {} @@ -1097,10 +1103,28 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { TypeUseT type, Index pos); + Result<> addTypeImport(Name name, + const std::vector& exports, + ImportNames* import, + Index pos, + Index typePos) { + if (hasTypeDefinition) { + return in.err(pos, "type import after type definitions"); + } + typeDefs.push_back({name, pos, Index(typeDefs.size()), {}}); + typeImports.push_back({name, exports, *import, typePos}); + return Ok{}; + } + Result<> addExport(Index pos, Ok, Name, ExternalKind) { exportDefs.push_back(pos); return Ok{}; } + + Result<> addTypeExport(Index pos, Ok, Name) { + exportDefs.push_back(pos); + return Ok{}; + } }; // Phase 2: Parse type definitions into a TypeBuilder. @@ -1113,12 +1137,15 @@ struct ParseTypeDefsCtx : TypeParserCtx { // Parse the names of types and fields as we go. std::vector names; + // Keep track of type exports + std::vector> typeExports; + // The index of the subtype definition we are parsing. Index index = 0; ParseTypeDefsCtx(Lexer& in, TypeBuilder& builder, const IndexMap& typeIndices) : TypeParserCtx(typeIndices), in(in), builder(builder), - names(builder.size()) {} + names(builder.size()), typeExports(builder.size()) {} TypeT makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) { @@ -1162,7 +1189,18 @@ struct ParseTypeDefsCtx : TypeParserCtx { return Ok{}; } - void finishTypeDef(Name name, Index pos) { names[index++].name = name; } + void finishTypeDef(Name name, const std::vector& exports, Index pos) { + typeExports[index] = exports; + names[index++].name = name; + } + + Result<> addTypeImport(Name name, + const std::vector& exports, + ImportNames* import, + Index pos, + Index typePos) { + return Ok{}; + } size_t getRecGroupStartIndex() { return index; } @@ -1411,6 +1449,14 @@ struct ParseModuleTypesCtx : TypeParserCtx, t->type = use.type; return Ok{}; } + + Result<> addTypeImport(Name name, + const std::vector& exports, + ImportNames* import, + Index pos, + Index typePos) { + return Ok{}; + } }; // Phase 5: Parse module element definitions, including instructions. @@ -1765,6 +1811,14 @@ struct ParseDefsCtx : TypeParserCtx { return Ok{}; } + Result<> addTypeImport(Name, + const std::vector exports, + ImportNames* import, + Index pos, + Index typePos) { + return Ok{}; + } + Result<> addExport(Index pos, Name value, Name name, ExternalKind kind) { if (wasm.getExportOrNull(name)) { return in.err(pos, "duplicate export"); @@ -1773,6 +1827,14 @@ struct ParseDefsCtx : TypeParserCtx { return Ok{}; } + Result<> addTypeExport(Index pos, HeapType heaptype, Name name) { + if (wasm.getExportOrNull(name)) { + return in.err(pos, "duplicate export"); + } + wasm.addExport(builder.makeExport(name, heaptype, ExternalKind::Type)); + return Ok{}; + } + Result addScratchLocal(Index pos, Type type) { if (!func) { return in.err(pos, diff --git a/src/parser/parse-2-typedefs.cpp b/src/parser/parse-2-typedefs.cpp index 83e10ec5b8a..653e92c6d26 100644 --- a/src/parser/parse-2-typedefs.cpp +++ b/src/parser/parse-2-typedefs.cpp @@ -26,6 +26,14 @@ Result<> parseTypeDefs( std::unordered_map>& typeNames) { TypeBuilder builder(decls.typeDefs.size()); ParseTypeDefsCtx ctx(input, builder, typeIndices); + for (auto& [name, exports, importNames, pos] : decls.typeImports) { + WithPosition with(ctx, pos); + auto heaptype = typetype(ctx); + CHECK_ERR(heaptype); + builder[ctx.index] = TypeImport(importNames.mod, importNames.nm, *heaptype); + ctx.typeExports[ctx.index] = exports; + ctx.names[ctx.index++].name = name; + } for (auto& recType : decls.recTypeDefs) { WithPosition with(ctx, recType.pos); CHECK_ERR(rectype(ctx)); @@ -49,6 +57,16 @@ Result<> parseTypeDefs( } } } + for (size_t i = 0; i < types.size(); ++i) { + for (Name& name : ctx.typeExports[i]) { + if (decls.wasm.getExportOrNull(name)) { + // TODO: Fix error location + return ctx.in.err("repeated export name"); + } + decls.wasm.addExport( + Builder(decls.wasm).makeExport(name, types[i], ExternalKind::Type)); + } + } return Ok{}; } diff --git a/src/parser/parsers.h b/src/parser/parsers.h index b54de9979c6..945e7d0dad5 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -433,15 +433,10 @@ Result absheaptype(Ctx& ctx, Shareability share) { return ctx.in.err("expected abstract heap type"); } -// heaptype ::= x:typeidx => types[x] -// | t:absheaptype => unshared t -// | '(' 'shared' t:absheaptype ')' => shared t -template Result heaptype(Ctx& ctx) { - if (auto t = maybeTypeidx(ctx)) { - CHECK_ERR(t); - return *t; - } - +// shareabsheaptype ::= t:absheaptype => unshared t +// | '(' 'shared' t:absheaptype ')' => shared t +template +Result sharedabsheaptype(Ctx& ctx) { auto share = ctx.in.takeSExprStart("shared"sv) ? Shared : Unshared; auto t = absheaptype(ctx, share); CHECK_ERR(t); @@ -451,6 +446,17 @@ template Result heaptype(Ctx& ctx) { return *t; } +// heaptype ::= x:typeidx => types[x] +// | t:sharedabsheaptype => t +template Result heaptype(Ctx& ctx) { + if (auto t = maybeTypeidx(ctx)) { + CHECK_ERR(t); + return *t; + } + + return sharedabsheaptype(ctx); +} + // reftype ::= 'funcref' => funcref // | 'externref' => externref // | 'anyref' => anyref @@ -886,6 +892,22 @@ template Result globaltype(Ctx& ctx) { return ctx.makeGlobalType(mutability, *type); } +// typetype ::= ('(' 'sub' t:absheaptype ')')? => heaptype t +template Result typetype(Ctx& ctx) { + if (!ctx.in.takeSExprStart("sub"sv)) { + return ctx.makeAnyType(Unshared); + } + + auto type = sharedabsheaptype(ctx); + CHECK_ERR(type); + + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of typetype"); + } + + return type; +} + // arity ::= x:u32 (if x >=2 ) template Result tupleArity(Ctx& ctx) { auto arity = ctx.in.takeU32(); @@ -2977,7 +2999,9 @@ template Result<> subtype(Ctx& ctx) { return Ok{}; } -// typedef ::= '(' 'type' id? subtype ')' +// typedef ::= '(' 'type' id? ('(' 'export' name ')')* subtype ')' +// | '(' 'type' id? ('(' 'export' name ')')* +// '(' 'import' mod:name nm:name ')' typetype ')' template MaybeResult<> typedef_(Ctx& ctx) { auto pos = ctx.in.getPos(); @@ -2990,14 +3014,27 @@ template MaybeResult<> typedef_(Ctx& ctx) { name = *id; } - auto sub = subtype(ctx); - CHECK_ERR(sub); + auto exports = inlineExports(ctx.in); + CHECK_ERR(exports); + + auto import = inlineImport(ctx.in); + CHECK_ERR(import); + + if (import) { + auto typePos = ctx.in.getPos(); + auto type = typetype(ctx); + CHECK_ERR(type); + CHECK_ERR(ctx.addTypeImport(name, *exports, import.getPtr(), pos, typePos)); + } else { + auto sub = subtype(ctx); + CHECK_ERR(sub); + ctx.finishTypeDef(name, *exports, pos); + } if (!ctx.in.takeRParen()) { return ctx.in.err("expected end of type definition"); } - ctx.finishTypeDef(name, pos); return Ok{}; } @@ -3064,6 +3101,7 @@ template MaybeResult locals(Ctx& ctx) { // | '(' 'memory' id? memtype ')' // | '(' 'global' id? globaltype ')' // | '(' 'tag' id? typeuse ')' +// | '(' 'type' id? typetype ')' template MaybeResult<> import_(Ctx& ctx) { auto pos = ctx.in.getPos(); @@ -3110,6 +3148,13 @@ template MaybeResult<> import_(Ctx& ctx) { auto type = typeuse(ctx); CHECK_ERR(type); CHECK_ERR(ctx.addTag(name ? *name : Name{}, {}, &names, *type, pos)); + } else if (ctx.in.takeSExprStart("type"sv)) { + auto name = ctx.in.takeID(); + auto typePos = ctx.in.getPos(); + auto type = typetype(ctx); + CHECK_ERR(type); + CHECK_ERR( + ctx.addTypeImport(name ? *name : Name{}, {}, &names, pos, typePos)); } else { return ctx.in.err("expected import description"); } @@ -3364,6 +3409,7 @@ template MaybeResult<> global(Ctx& ctx) { // | '(' 'memory' x:memidx ')' // | '(' 'global' x:globalidx ')' // | '(' 'tag' x:tagidx ')' +// | '(' 'type' x:typeidx ')' template MaybeResult<> export_(Ctx& ctx) { auto pos = ctx.in.getPos(); if (!ctx.in.takeSExprStart("export"sv)) { @@ -3395,6 +3441,10 @@ template MaybeResult<> export_(Ctx& ctx) { auto idx = tagidx(ctx); CHECK_ERR(idx); CHECK_ERR(ctx.addExport(pos, *idx, *name, ExternalKind::Tag)); + } else if (ctx.in.takeSExprStart("type"sv)) { + auto idx = heaptype(ctx); + CHECK_ERR(idx); + CHECK_ERR(ctx.addTypeExport(pos, *idx, *name)); } else { return ctx.in.err("expected export description"); } diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index 83a17d36608..881838ccfd0 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -99,6 +99,7 @@ set(passes_SOURCES StringLowering.cpp Strip.cpp StripTargetFeatures.cpp + StripTypeExports.cpp TraceCalls.cpp RedundantSetElimination.cpp RemoveImports.cpp diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 90eecef2eef..cd3725c8e51 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -3083,12 +3083,19 @@ void PrintSExpression::visitExport(Export* curr) { case ExternalKind::Tag: o << "tag"; break; + case ExternalKind::Type: + o << "type"; + break; case ExternalKind::Invalid: WASM_UNREACHABLE("invalid ExternalKind"); } o << ' '; - // TODO: specific case for type exports - curr->getInternalName()->print(o) << "))"; + if (auto* name = curr->getInternalName()) { + name->print(o); + } else { + printHeapType(*curr->getHeapType()); + } + o << "))"; } void PrintSExpression::emitImportHeader(Importable* curr) { diff --git a/src/passes/StripTypeExports.cpp b/src/passes/StripTypeExports.cpp new file mode 100644 index 00000000000..c1a0fd665d4 --- /dev/null +++ b/src/passes/StripTypeExports.cpp @@ -0,0 +1,32 @@ +/* + * Copyright 2025 WebAssembly Community Group participants + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "pass.h" + +namespace wasm { + +struct StripTypeExports : public Pass { + bool requiresNonNullableLocalFixups() override { return false; } + + void run(Module* module) override { + module->removeExports( + [&](Export* curr) { return curr->kind == ExternalKind::Type; }); + } +}; + +Pass* createStripTypeExportsPass() { return new StripTypeExports; } + +} // namespace wasm diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index e7a25cf372c..95c257fd8a8 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -181,6 +181,7 @@ struct TypeMerging : public Pass { bool shapeEq(HeapType a, HeapType b); bool shapeEq(const Struct& a, const Struct& b); bool shapeEq(Array a, Array b); +bool shapeEq(TypeImport a, TypeImport b); bool shapeEq(Signature a, Signature b); bool shapeEq(Field a, Field b); bool shapeEq(Type a, Type b); @@ -190,6 +191,7 @@ size_t shapeHash(HeapType a); size_t shapeHash(const Struct& a); size_t shapeHash(Array a); size_t shapeHash(Signature a); +size_t shapeHash(TypeImport a); size_t shapeHash(Field a); size_t shapeHash(Type a); size_t shapeHash(const Tuple& a); @@ -572,6 +574,8 @@ bool shapeEq(HeapType a, HeapType b) { return shapeEq(a.getArray(), b.getArray()); case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Import: + return shapeEq(a.getImport(), b.getImport()); case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -595,6 +599,9 @@ size_t shapeHash(HeapType a) { return digest; case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Import: + hash_combine(digest, shapeHash(a.getImport())); + return digest; case HeapTypeKind::Basic: break; } @@ -635,6 +642,17 @@ size_t shapeHash(Signature a) { return digest; } +bool shapeEq(TypeImport a, TypeImport b) { + return a.module == b.module && a.base == b.base && shapeEq(a.bound, b.bound); +} + +size_t shapeHash(TypeImport a) { + size_t digest = shapeHash(a.bound); + hash_combine(digest, wasm::hash(a.module)); + hash_combine(digest, wasm::hash(a.base)); + return digest; +} + bool shapeEq(Field a, Field b) { return a.packedType == b.packedType && a.mutable_ == b.mutable_ && shapeEq(a.type, b.type); diff --git a/src/passes/TypeSSA.cpp b/src/passes/TypeSSA.cpp index 3d68c991396..ab192d14824 100644 --- a/src/passes/TypeSSA.cpp +++ b/src/passes/TypeSSA.cpp @@ -251,6 +251,7 @@ struct TypeSSA : public Pass { break; case HeapTypeKind::Func: case HeapTypeKind::Cont: + case HeapTypeKind::Import: case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } diff --git a/src/passes/Unsubtyping.cpp b/src/passes/Unsubtyping.cpp index 8d76f348a5a..18173d8c977 100644 --- a/src/passes/Unsubtyping.cpp +++ b/src/passes/Unsubtyping.cpp @@ -287,6 +287,8 @@ struct Unsubtyping } case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Import: + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index e2137a8c98b..2ea155225b1 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -545,6 +545,9 @@ void PassRegistry::registerPasses() { registerPass("strip-target-features", "strip the wasm target features section", createStripTargetFeaturesPass); + registerPass("strip-type-exports", + "strip the wasm type exports", + createStripTypeExportsPass); registerPass("translate-to-new-eh", "deprecated; same as translate-to-exnref", createTranslateToExnrefPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 81d7cdcc7ff..75f6ea802cf 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -168,6 +168,7 @@ Pass* createStripDebugPass(); Pass* createStripDWARFPass(); Pass* createStripProducersPass(); Pass* createStripTargetFeaturesPass(); +Pass* createStripTypeExportsPass(); Pass* createSouperifyPass(); Pass* createSouperifySingleUsePass(); Pass* createSpillPointersPass(); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index bc3bc9fc938..b684474a395 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -516,6 +516,14 @@ void TranslateToFuzzReader::setupHeapTypes() { break; case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Import: { + for (std::optional heaptype = type.getImport().bound; + heaptype; + heaptype = getSuperType(*heaptype)) { + interestingHeapSubTypes[*heaptype].push_back(type); + } + break; + } case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -3646,6 +3654,9 @@ Expression* TranslateToFuzzReader::makeCompoundRef(Type type) { } case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Import: + assert(funcContext); + return _makeunreachable(); case HeapTypeKind::Basic: break; } diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index dc6e574c7e9..3d983ff38a3 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -146,6 +146,7 @@ struct HeapTypeGeneratorImpl { case wasm::HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); case wasm::HeapTypeKind::Basic: + case wasm::HeapTypeKind::Import: WASM_UNREACHABLE("unexpected kind"); } } @@ -942,6 +943,8 @@ std::vector Inhabitator::build() { } case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Import: + WASM_UNREACHABLE("TODO: type imports"); case HeapTypeKind::Basic: break; } @@ -1032,6 +1035,8 @@ bool isUninhabitable(HeapType type, switch (type.getKind()) { case HeapTypeKind::Basic: return false; + case HeapTypeKind::Import: + return true; case HeapTypeKind::Func: case HeapTypeKind::Cont: // Function types are always inhabitable. @@ -1063,6 +1068,7 @@ bool isUninhabitable(HeapType type, case HeapTypeKind::Basic: case HeapTypeKind::Func: case HeapTypeKind::Cont: + case HeapTypeKind::Import: WASM_UNREACHABLE("unexpected kind"); } visiting.erase(it); diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index 3c42b6b1f13..b92f789e948 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -108,6 +108,7 @@ struct ToolOptions : public Options { .addFeature(FeatureSet::FP16, "float 16 operations") .addFeature(FeatureSet::CustomDescriptors, "custom descriptors (RTTs) and exact references") + .addFeature(FeatureSet::TypeImports, "type imports") .add("--enable-typed-function-references", "", "Deprecated compatibility flag", diff --git a/src/tools/wasm-fuzz-types.cpp b/src/tools/wasm-fuzz-types.cpp index 7ba341e09df..7246e53bb63 100644 --- a/src/tools/wasm-fuzz-types.cpp +++ b/src/tools/wasm-fuzz-types.cpp @@ -309,6 +309,7 @@ void Fuzzer::checkCanonicalization() { continue; case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Import: case HeapTypeKind::Basic: break; } diff --git a/src/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp index cc35b072b49..a56638d2743 100644 --- a/src/tools/wasm-merge.cpp +++ b/src/tools/wasm-merge.cpp @@ -94,6 +94,7 @@ #include "ir/module-utils.h" #include "ir/names.h" +#include "ir/type-updating.h" #include "support/colors.h" #include "support/file.h" #include "wasm-builder.h" @@ -133,6 +134,8 @@ enum ExportMergeMode { SkipExportConflicts, } exportMergeMode = ErrorOnExportConflicts; +bool stripTypeExports = false; + // Merging two modules is mostly straightforward: copy the functions etc. of the // first module into the second, with some renaming to avoid name collisions. // The only other thing we need to handle is the mapping of imports to exports, @@ -416,6 +419,139 @@ void checkLimit(bool& valid, const char* kind, T* export_, T* import) { } } +// Find pairs of matching type imports and type exports, and make uses +// of the import refer to the exported item (which has been merged +// into the module). +void fuseTypeImportsAndTypeExports() { + // First, build for each module a mapping from each type export + // name to the exported heap type. + using ModuleTypeExportMap = + std::unordered_map>; + ModuleTypeExportMap moduleTypeExportMap; + for (auto& ex : merged.exports) { + if (ex->kind == ExternalKind::Type) { + assert(exportModuleMap.count(ex.get())); + ExportInfo& exportInfo = exportModuleMap[ex.get()]; + moduleTypeExportMap[exportInfo.moduleName][exportInfo.baseName] = + *ex->getHeapType(); + } + } + if (moduleTypeExportMap.size() == 0) { + return; + } + + auto heapTypeInfo = ModuleUtils::collectHeapTypeInfo(merged); + + // For each type import, see whether it has a corresponding + // export, check that the imported type is a subtype of the import + // bound. Record the corresponding mapping. + bool valid = true; + std::unordered_map typeUpdates; + for (auto& [heapType, _] : heapTypeInfo) { + if (heapType.isImport()) { + TypeImport import = heapType.getImport(); + if (auto newType = moduleTypeExportMap[import.module].find(import.base); + newType != moduleTypeExportMap[import.module].end()) { + // We found something to fuse! Add it to the maps for renaming. + typeUpdates[heapType] = newType->second; + if (!HeapType::isSubType(newType->second, import.bound)) { + Importable importable; + importable.module = import.module; + importable.base = import.base; + importable.name = merged.typeNames[heapType].name; + reportTypeMismatch(valid, "type", &importable); + std::cerr << "type " << newType->second << " is not a subtype of " + << import.bound << ".\n"; + } + } + } + } + if (!valid) { + Fatal() << "import/export mismatches"; + } + if (typeUpdates.size() == 0) { + return; + } + + // Resolve chains of imports/exports + for (auto& [oldType, newType] : typeUpdates) { + // Iteratively lookup the updated type. + std::unordered_set visited; + auto type = newType; + while (1) { + auto iter = typeUpdates.find(type); + if (iter == typeUpdates.end()) { + break; + } + if (visited.count(type)) { + // This is a loop of imports, which means we cannot resolve a useful + // type. Report an error. + Fatal() << "wasm-merge: infinite loop of imports on " << oldType; + } + visited.insert(type); + type = iter->second; + } + newType = type; + } + + // Map from a heap type to the heap type it stands for + auto initialMap = [&](HeapType type) -> HeapType { + if (type.isBasic()) { + return type; + } + auto iter = typeUpdates.find(type); + if (iter != typeUpdates.end()) { + return iter->second; + } + return type; + }; + + // Sort heap types so that children are before + ModuleUtils::IndexedHeapTypes indexedTypes = + ModuleUtils::sortHeapTypes(merged, heapTypeInfo, initialMap); + + TypeBuilder typeBuilder(indexedTypes.types.size()); + + // Map from a heap type to the corresponding temporary type + auto map = [&](HeapType type) -> HeapType { + type = initialMap(type); + if (type.isBasic()) { + return type; + } + return typeBuilder[indexedTypes.indices[type]]; + }; + + // Build new types + std::optional lastGroup = std::nullopt; + for (size_t i = 0; i < indexedTypes.types.size(); i++) { + HeapType heapType = indexedTypes.types[i]; + typeBuilder[i].copy(heapType, map); + auto currGroup = heapType.getRecGroup(); + if (lastGroup != currGroup && currGroup.size() > 1) { + typeBuilder.createRecGroup(i, currGroup.size()); + lastGroup = currGroup; + } + } + auto buildResults = typeBuilder.build(); + auto& newTypes = *buildResults; + + // Map old types to the new ones. + GlobalTypeRewriter::TypeMap oldToNewTypes; + for (HeapType heapType : indexedTypes.types) { + HeapType type = heapType; + type = initialMap(type); + if (!type.isBasic()) { + type = newTypes[indexedTypes.indices[type]]; + } + oldToNewTypes[heapType] = type; + } + + // Replace types everywhere + GlobalTypeRewriter rewriter(merged); + rewriter.mapTypeNamesAndIndices(oldToNewTypes); + rewriter.mapTypes(oldToNewTypes); +} + // Find pairs of matching imports and exports, and make uses of the import refer // to the exported item (which has been merged into the module). void fuseImportsAndExports() { @@ -653,6 +789,13 @@ Input source maps can be specified by adding an -ism option right after the modu [&](Options* o, const std::string& argument) { exportMergeMode = SkipExportConflicts; }) + .add( + "--strip-type-exports", + "-ste", + "Do not emit type exports", + WasmMergeOption, + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { stripTypeExports = true; }) .add("--emit-text", "-S", "Emit text instead of binary for the output file", @@ -741,6 +884,7 @@ Input source maps can be specified by adding an -ism option right after the modu // Fuse imports and exports now that everything is all together in the merged // module. + fuseTypeImportsAndTypeExports(); fuseImportsAndExports(); { @@ -755,6 +899,12 @@ Input source maps can be specified by adding an -ism option right after the modu // optimized out (while if we didn't optimize it out then instantiating the // module would still be forced to provide something for that import). passRunner.add("remove-unused-module-elements"); + if (stripTypeExports) { + // Remove type exports. Useful if we have composed together + // several modules using type imports and exports, but want to + // emit a module which does not requires this extension. + passRunner.add("strip-type-exports"); + } passRunner.run(); } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 77e13326999..9369ae9f0f9 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -407,6 +407,7 @@ extern const char* FP16Feature; extern const char* BulkMemoryOptFeature; extern const char* CallIndirectOverlongFeature; extern const char* CustomDescriptorsFeature; +extern const char* TypeImportsFeature; enum Subsection { NameModule = 0, @@ -1460,6 +1461,7 @@ class WasmBinaryReader { size_t codeSectionLocation; std::unordered_set seenSections; + TypeBuilder typebuilder; IRBuilder builder; SourceMapReader sourceMapReader; diff --git a/src/wasm-features.h b/src/wasm-features.h index a7c3ce0c4f5..147a8937ebc 100644 --- a/src/wasm-features.h +++ b/src/wasm-features.h @@ -55,11 +55,12 @@ struct FeatureSet { // it does nothing. Binaryen always accepts LEB call-indirect encodings. CallIndirectOverlong = 1 << 20, CustomDescriptors = 1 << 21, + TypeImports = 1 << 22, 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 << 22) - 1, + All = (1 << 23) - 1, }; static std::string toString(Feature f) { @@ -108,6 +109,8 @@ struct FeatureSet { return "call-indirect-overlong"; case CustomDescriptors: return "custom-descriptors"; + case TypeImports: + return "type-imports"; case MVP: case Default: case All: @@ -168,6 +171,7 @@ struct FeatureSet { bool hasCustomDescriptors() const { return (features & CustomDescriptors) != 0; } + bool hasTypeImports() const { return (features & TypeImports) != 0; } bool hasAll() const { return (features & All) != 0; } void set(FeatureSet f, bool v = true) { @@ -195,6 +199,7 @@ struct FeatureSet { void setBulkMemoryOpt(bool v = true) { set(BulkMemoryOpt, v); } void setCustomDescriptors(bool v = true) { set(CustomDescriptors, v); } void setMVP() { features = MVP; } + void setTypeImports(bool v = true) { set(TypeImports, v); } void setAll() { features = All; } void enable(const FeatureSet& other) { features |= other.features; } diff --git a/src/wasm-type-printing.h b/src/wasm-type-printing.h index 11afde88212..0432192d818 100644 --- a/src/wasm-type-printing.h +++ b/src/wasm-type-printing.h @@ -56,6 +56,7 @@ struct DefaultTypeNameGenerator size_t contCount = 0; size_t structCount = 0; size_t arrayCount = 0; + size_t importCount = 0; // Cached names for types that have already been seen. std::unordered_map nameCache; diff --git a/src/wasm-type.h b/src/wasm-type.h index 62177af2c08..a5623bca248 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -53,6 +53,7 @@ class HeapType; class RecGroup; struct Signature; struct Continuation; +struct TypeImport; struct Field; struct Struct; struct Array; @@ -87,6 +88,7 @@ enum class HeapTypeKind { Struct, Array, Cont, + Import, }; class HeapType { @@ -165,6 +167,7 @@ class HeapType { bool isArray() const { return getKind() == HeapTypeKind::Array; } bool isExn() const { return isMaybeShared(HeapType::exn); } bool isString() const { return isMaybeShared(HeapType::string); } + bool isImport() const { return getKind() == HeapTypeKind::Import; } bool isBottom() const; bool isOpen() const; bool isShared() const { return getShared() == Shared; } @@ -182,6 +185,7 @@ class HeapType { const Struct& getStruct() const; Array getArray() const; + TypeImport getImport() const; // If there is a nontrivial (i.e. non-basic, one that was declared by the // module) nominal supertype, return it, else an empty optional. @@ -605,6 +609,17 @@ struct Continuation { std::string toString() const; }; +struct TypeImport { + Name module, base; + HeapType bound; + TypeImport(Name module, Name base, HeapType bound) + : module(module), base(base), bound(bound) {} + bool operator==(const TypeImport& other) const { + return module == other.module && base == other.base && bound == other.bound; + } + std::string toString() const; +}; + struct Field { Type type; enum PackedType { @@ -703,6 +718,7 @@ struct TypeBuilder { void setHeapType(size_t i, const Struct& struct_); void setHeapType(size_t i, Struct&& struct_); void setHeapType(size_t i, Array array); + void setHeapType(size_t i, TypeImport import); // Sets the heap type at index `i` to be a copy of the given heap type with // its referenced HeapTypes to be replaced according to the provided mapping @@ -761,6 +777,9 @@ struct TypeBuilder { case HeapTypeKind::Cont: setHeapType(i, Continuation(map(type.getContinuation().type))); return; + case HeapTypeKind::Import: + setHeapType(i, type.getImport()); + return; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -800,6 +819,10 @@ struct TypeBuilder { ForwardChildReference, // A continuation reference that does not refer to a function type. InvalidFuncType, + // A type import has an invalid bound. + InvalidBoundType, + // Type import in recursive group + ImportInRecGroup, // A non-shared field of a shared heap type. InvalidUnsharedField, }; @@ -854,6 +877,10 @@ struct TypeBuilder { builder.setHeapType(index, array); return *this; } + Entry& operator=(TypeImport import) { + builder.setHeapType(index, import); + return *this; + } Entry& subTypeOf(std::optional other) { builder.setSubType(index, other); return *this; @@ -904,6 +931,7 @@ std::ostream& operator<<(std::ostream&, Continuation); std::ostream& operator<<(std::ostream&, Field); std::ostream& operator<<(std::ostream&, Struct); std::ostream& operator<<(std::ostream&, Array); +std::ostream& operator<<(std::ostream&, TypeImport); std::ostream& operator<<(std::ostream&, TypeBuilder::ErrorReason); // Inline some nontrivial methods here for performance reasons. diff --git a/src/wasm.h b/src/wasm.h index e3f53379a9d..df01398628f 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2235,6 +2235,7 @@ enum class ExternalKind { Memory = 2, Global = 3, Tag = 4, + Type = 5, Invalid = -1 }; @@ -2267,9 +2268,10 @@ class Export { public: Export(Name name, ExternalKind kind, std::variant value) : name(name), kind(kind), value(value) { - assert(std::get_if(&value)); + assert((kind == ExternalKind::Type) == !std::get_if(&value)); } Name* getInternalName() { return std::get_if(&value); } + HeapType* getHeapType() { return std::get_if(&value); } }; class ElementSegment : public Named { diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index de6a5867b93..7868dd94c0b 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -236,13 +236,37 @@ void WasmBinaryWriter::writeTypes() { // Count the number of recursion groups, which is the number of elements in // the type section. size_t numGroups = 0; + size_t numImports = 0; { std::optional lastGroup; for (auto type : indexedTypes.types) { auto currGroup = type.getRecGroup(); numGroups += lastGroup != currGroup; lastGroup = currGroup; + numImports += type.isImport(); + } + } + + if (numImports > 0) { + auto start = startSection(BinaryConsts::Section::Import); + o << U32LEB(numImports); + for (Index i = 0; i < indexedTypes.types.size(); ++i) { + auto type = indexedTypes.types[i]; + if (type.isImport()) { + TypeImport import = type.getImport(); + writeInlineString(import.module.str); + writeInlineString(import.base.str); + o << U32LEB(int32_t(ExternalKind::Type)); + o << uint8_t(0); // Reserved 'kind' field. Always 0. + writeHeapType(import.bound); + } } + finishSection(start); + } + + numGroups -= numImports; + if (numGroups == 0) { + return; } // As a temporary measure, detect which types have subtypes and always use @@ -262,6 +286,10 @@ void WasmBinaryWriter::writeTypes() { std::optional lastGroup = std::nullopt; for (Index i = 0; i < indexedTypes.types.size(); ++i) { auto type = indexedTypes.types[i]; + if (type.isImport()) { + // Type imports have already been written + continue; + } // Check whether we need to start a new recursion group. Recursion groups of // size 1 are implicit, so only emit a group header for larger groups. auto currGroup = type.getRecGroup(); @@ -316,6 +344,8 @@ void WasmBinaryWriter::writeTypes() { o << uint8_t(BinaryConsts::EncodedType::Cont); writeHeapType(type.getContinuation().type); break; + case HeapTypeKind::Import: + WASM_UNREACHABLE("unexpected kind"); case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -617,6 +647,9 @@ void WasmBinaryWriter::writeExports() { case ExternalKind::Tag: o << U32LEB(getTagIndex(*curr->getInternalName())); break; + case ExternalKind::Type: + writeHeapType(*curr->getHeapType()); + break; default: WASM_UNREACHABLE("unexpected extern kind"); } @@ -1360,6 +1393,8 @@ void WasmBinaryWriter::writeFeaturesSection() { return BinaryConsts::CustomSections::CallIndirectOverlongFeature; case FeatureSet::CustomDescriptors: return BinaryConsts::CustomSections::CustomDescriptorsFeature; + case FeatureSet::TypeImports: + return BinaryConsts::CustomSections::TypeImportsFeature; case FeatureSet::None: case FeatureSet::Default: case FeatureSet::All: @@ -1840,7 +1875,13 @@ void WasmBinaryReader::read() { // Note the section in the list of seen sections, as almost no sections can // appear more than once, and verify those that shouldn't do not. + // We can have a import section containing type imports before the + // type section. + // TODO: We should check that the sections are ordered properly and + // that there is at most two import sections (one with only type + // imports, the other with no type imports). if (sectionCode != BinaryConsts::Section::Custom && + sectionCode != BinaryConsts::Section::Import && !seenSections.insert(sectionCode).second) { throwError("section seen more than once: " + std::to_string(sectionCode)); } @@ -2336,7 +2377,8 @@ void WasmBinaryReader::readMemories() { } void WasmBinaryReader::readTypes() { - TypeBuilder builder(getU32LEB()); + int typeImportCount = typebuilder.size(); + typebuilder.grow(getU32LEB()); auto readHeapType = [&]() -> HeapType { int64_t htCode = getS64LEB(); // TODO: Actually s33 @@ -2349,10 +2391,10 @@ void WasmBinaryReader::readTypes() { if (getBasicHeapType(htCode, ht)) { return ht.getBasic(share); } - if (size_t(htCode) >= builder.size()) { + if (size_t(htCode) >= typebuilder.size()) { throwError("invalid type index: " + std::to_string(htCode)); } - return builder.getTempHeapType(size_t(htCode)); + return typebuilder.getTempHeapType(size_t(htCode)); }; auto makeTypeNoExact = [&](int32_t typeCode) { Type type; @@ -2372,7 +2414,7 @@ void WasmBinaryReader::readTypes() { return Type(ht, nullability); } - return builder.getTempRefType(ht, nullability); + return typebuilder.getTempRefType(ht, nullability); } default: throwError("unexpected type index: " + std::to_string(typeCode)); @@ -2385,7 +2427,7 @@ void WasmBinaryReader::readTypes() { if (!type.isRef()) { throwError("unexpected exact prefix on non-reference type"); } - return builder.getTempRefType( + return typebuilder.getTempRefType( type.getHeapType(), type.getNullability(), Exact); } return makeTypeNoExact(typeCode); @@ -2403,8 +2445,8 @@ void WasmBinaryReader::readTypes() { for (size_t j = 0; j < numResults; j++) { results.push_back(readType()); } - return Signature(builder.getTempTupleType(params), - builder.getTempTupleType(results)); + return Signature(typebuilder.getTempTupleType(params), + typebuilder.getTempTupleType(results)); }; auto readContinuationDef = [&]() { @@ -2453,7 +2495,7 @@ void WasmBinaryReader::readTypes() { return Struct(std::move(fields)); }; - for (size_t i = 0; i < builder.size(); i++) { + for (size_t i = typeImportCount; i < typebuilder.size(); i++) { auto form = getInt8(); if (form == BinaryConsts::EncodedType::Rec) { uint32_t groupSize = getU32LEB(); @@ -2463,15 +2505,15 @@ void WasmBinaryReader::readTypes() { } // The group counts as one element in the type section, so we have to // allocate space for the extra types. - builder.grow(groupSize - 1); - builder.createRecGroup(i, groupSize); + typebuilder.grow(groupSize - 1); + typebuilder.createRecGroup(i, groupSize); form = getInt8(); } std::optional superIndex; if (form == BinaryConsts::EncodedType::Sub || form == BinaryConsts::EncodedType::SubFinal) { if (form == BinaryConsts::EncodedType::Sub) { - builder[i].setOpen(); + typebuilder[i].setOpen(); } uint32_t supers = getU32LEB(); if (supers > 0) { @@ -2484,30 +2526,30 @@ void WasmBinaryReader::readTypes() { form = getInt8(); } if (form == BinaryConsts::SharedDef) { - builder[i].setShared(); + typebuilder[i].setShared(); form = getInt8(); } if (form == BinaryConsts::EncodedType::Func) { - builder[i] = readSignatureDef(); + typebuilder[i] = readSignatureDef(); } else if (form == BinaryConsts::EncodedType::Cont) { - builder[i] = readContinuationDef(); + typebuilder[i] = readContinuationDef(); } else if (form == BinaryConsts::EncodedType::Struct) { - builder[i] = readStructDef(); + typebuilder[i] = readStructDef(); } else if (form == BinaryConsts::EncodedType::Array) { - builder[i] = Array(readFieldDef()); + typebuilder[i] = Array(readFieldDef()); } else { throwError("Bad type form " + std::to_string(form)); } if (superIndex) { - if (*superIndex > builder.size()) { + if (*superIndex > typebuilder.size()) { throwError("Out of bounds supertype index " + std::to_string(*superIndex)); } - builder[i].subTypeOf(builder[*superIndex]); + typebuilder[i].subTypeOf(typebuilder[*superIndex]); } } - auto result = builder.build(); + auto result = typebuilder.build(); if (auto* err = result.getError()) { Fatal() << "Invalid type: " << err->reason << " at index " << err->index; } @@ -2751,6 +2793,29 @@ void WasmBinaryReader::readImports() { wasm.addTag(std::move(curr)); break; } + case ExternalKind::Type: { + if (seenSections.count(BinaryConsts::Section::Type)) { + throwError("type import after type section"); + } + auto kind = getInt8(); // Reserved 'kind' field + if (kind != 0) { + throwError("type import with non-zero kind"); + } + int64_t htCode = getS64LEB(); // TODO: Actually s33 + auto share = Unshared; + if (htCode == BinaryConsts::EncodedType::Shared) { + share = Shared; + htCode = getS64LEB(); // TODO: Actually s33 + } + HeapType ht; + if (!getBasicHeapType(htCode, ht)) { + throwError("expected an abstract heap type"); + } + typebuilder.grow(1); + typebuilder[typebuilder.size() - 1] = + TypeImport(module, base, ht.getBasic(share)); + break; + } default: { throwError("bad import kind"); } @@ -4393,22 +4458,24 @@ void WasmBinaryReader::readExports() { } ExternalKind kind = (ExternalKind)getU32LEB(); std::variant value; - auto index = getU32LEB(); switch (kind) { case ExternalKind::Function: - value = getFunctionName(index); + value = getFunctionName(getU32LEB()); break; case ExternalKind::Table: - value = getTableName(index); + value = getTableName(getU32LEB()); break; case ExternalKind::Memory: - value = getMemoryName(index); + value = getMemoryName(getU32LEB()); break; case ExternalKind::Global: - value = getGlobalName(index); + value = getGlobalName(getU32LEB()); break; case ExternalKind::Tag: - value = getTagName(index); + value = getTagName(getU32LEB()); + break; + case ExternalKind::Type: + value = getHeapType(); break; case ExternalKind::Invalid: throwError("invalid export kind"); @@ -4979,6 +5046,8 @@ void WasmBinaryReader::readFeatures(size_t payloadLen) { feature = FeatureSet::SharedEverything; } else if (name == BinaryConsts::CustomSections::FP16Feature) { feature = FeatureSet::FP16; + } else if (name == BinaryConsts::CustomSections::TypeImportsFeature) { + feature = FeatureSet::TypeImports; } else { // Silently ignore unknown features (this may be and old binaryen running // on a new wasm). diff --git a/src/wasm/wasm-type-shape.cpp b/src/wasm/wasm-type-shape.cpp index 734c5e4b903..2b108ba39c9 100644 --- a/src/wasm/wasm-type-shape.cpp +++ b/src/wasm/wasm-type-shape.cpp @@ -86,6 +86,8 @@ template struct RecGroupComparator { case HeapTypeKind::Cont: assert(a.isContinuation() && b.isContinuation()); return compare(a.getContinuation(), b.getContinuation()); + case HeapTypeKind::Import: + return compare(a.getImport(), b.getImport()); case HeapTypeKind::Basic: break; } @@ -117,6 +119,16 @@ template struct RecGroupComparator { return compare(a.type, b.type); } + Comparison compare(TypeImport a, TypeImport b) { + if (a.module != b.module) { + return a.module < b.module ? LT : GT; + } + if (a.base != b.base) { + return a.base < b.base ? LT : GT; + } + return compare(a.bound, b.bound); + } + Comparison compare(Field a, Field b) { if (a.mutable_ != b.mutable_) { return a.mutable_ < b.mutable_ ? LT : GT; @@ -242,6 +254,11 @@ struct RecGroupHasher { wasm::rehash(digest, 2381496927); hash_combine(digest, hash(type.getContinuation())); return digest; + case HeapTypeKind::Import: + assert(type.isImport()); + wasm::rehash(digest, 1077427175); + hash_combine(digest, hash(type.getImport())); + return digest; case HeapTypeKind::Basic: break; } @@ -266,6 +283,13 @@ struct RecGroupHasher { size_t hash(Continuation cont) { return hash(cont.type); } + size_t hash(TypeImport import) { + size_t digest = hash(import.bound); + hash_combine(digest, wasm::hash(import.module)); + hash_combine(digest, wasm::hash(import.base)); + return digest; + } + size_t hash(Field field) { size_t digest = wasm::hash(field.mutable_); wasm::rehash(digest, field.packedType); diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index d35d1537c47..03e1c37fa52 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -30,6 +30,8 @@ #include "wasm-features.h" #include "wasm-type-printing.h" #include "wasm-type.h" +#include +#include #define TRACE_CANONICALIZATION 0 @@ -62,6 +64,7 @@ struct HeapTypeInfo { Continuation continuation; Struct struct_; Array array; + TypeImport import; }; HeapTypeInfo(Signature sig) : kind(HeapTypeKind::Func), signature(sig) {} @@ -72,12 +75,15 @@ struct HeapTypeInfo { HeapTypeInfo(Struct&& struct_) : kind(HeapTypeKind::Struct), struct_(std::move(struct_)) {} HeapTypeInfo(Array array) : kind(HeapTypeKind::Array), array(array) {} + HeapTypeInfo(TypeImport import) + : kind(HeapTypeKind::Import), import(import) {} ~HeapTypeInfo(); constexpr bool isSignature() const { return kind == HeapTypeKind::Func; } constexpr bool isContinuation() const { return kind == HeapTypeKind::Cont; } constexpr bool isStruct() const { return kind == HeapTypeKind::Struct; } constexpr bool isArray() const { return kind == HeapTypeKind::Array; } + constexpr bool isImport() const { return kind == HeapTypeKind::Import; } constexpr bool isData() const { return isStruct() || isArray(); } }; @@ -125,6 +131,7 @@ struct TypePrinter { std::ostream& print(const Struct& struct_, const std::unordered_map& fieldNames); std::ostream& print(const Array& array); + std::ostream& print(const TypeImport& import); }; struct RecGroupHasher { @@ -150,6 +157,7 @@ struct RecGroupHasher { size_t hash(const Continuation& sig) const; size_t hash(const Struct& struct_) const; size_t hash(const Array& array) const; + size_t hash(const TypeImport& import) const; }; struct RecGroupEquator { @@ -176,6 +184,7 @@ struct RecGroupEquator { bool eq(const Continuation& a, const Continuation& b) const; bool eq(const Struct& a, const Struct& b) const; bool eq(const Array& a, const Array& b) const; + bool eq(const TypeImport& a, const TypeImport& b) const; }; // A wrapper around a RecGroup that provides equality and hashing based on the @@ -291,6 +300,9 @@ template struct TypeGraphWalkerBase { case HeapTypeKind::Array: taskList.push_back(Task::scan(&info->array.element.type)); break; + case HeapTypeKind::Import: + taskList.push_back(Task::scan(&info->import.bound)); + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -375,6 +387,8 @@ HeapType::BasicHeapType getBasicHeapSupertype(HeapType type) { return HeapTypes::struct_.getBasic(info->share); case HeapTypeKind::Array: return HeapTypes::array.getBasic(info->share); + case HeapTypeKind::Import: + return info->import.bound.getBasic(info->share); case HeapTypeKind::Basic: break; } @@ -464,6 +478,9 @@ HeapTypeInfo::~HeapTypeInfo() { case HeapTypeKind::Array: array.~Array(); return; + case HeapTypeKind::Import: + import.~TypeImport(); + return; case HeapTypeKind::Basic: break; } @@ -917,6 +934,11 @@ Array HeapType::getArray() const { return getHeapTypeInfo(*this)->array; } +TypeImport HeapType::getImport() const { + assert(isImport()); + return getHeapTypeInfo(*this)->import; +} + std::optional HeapType::getDeclaredSuperType() const { if (isBasic()) { return {}; @@ -970,6 +992,8 @@ std::optional HeapType::getSuperType() const { return HeapType(struct_).getBasic(share); case HeapTypeKind::Array: return HeapType(array).getBasic(share); + case HeapTypeKind::Import: + return info->import.bound; case HeapTypeKind::Basic: break; } @@ -1016,6 +1040,7 @@ size_t HeapType::getDepth() const { break; case HeapTypeKind::Func: case HeapTypeKind::Cont: + case HeapTypeKind::Import: ++depth; break; case HeapTypeKind::Struct: @@ -1068,6 +1093,8 @@ HeapType::BasicHeapType HeapType::getUnsharedBottom() const { case HeapTypeKind::Struct: case HeapTypeKind::Array: return none; + case HeapTypeKind::Import: + return info->import.bound.getUnsharedBottom(); case HeapTypeKind::Basic: break; } @@ -1134,6 +1161,8 @@ std::vector HeapType::getTypeChildren() const { return {getArray().element.type}; case HeapTypeKind::Cont: return {}; + case HeapTypeKind::Import: + return {}; } WASM_UNREACHABLE("unexpected kind"); } @@ -1297,6 +1326,8 @@ FeatureSet HeapType::getFeatures() const { } } else if (heapType.isContinuation()) { feats |= FeatureSet::StackSwitching; + } else if (heapType.isImport()) { + feats |= FeatureSet::TypeImports; } // In addition, scan their non-ref children, to add dependencies on @@ -1355,6 +1386,9 @@ TypeNames DefaultTypeNameGenerator::getNames(HeapType type) { case HeapTypeKind::Cont: stream << "cont." << contCount++; break; + case HeapTypeKind::Import: + stream << "import." << importCount++; + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -1374,6 +1408,7 @@ std::string Signature::toString() const { return genericToString(*this); } std::string Continuation::toString() const { return genericToString(*this); } std::string Struct::toString() const { return genericToString(*this); } std::string Array::toString() const { return genericToString(*this); } +std::string TypeImport::toString() const { return genericToString(*this); } std::ostream& operator<<(std::ostream& os, Type type) { return TypePrinter(os).print(type); @@ -1405,6 +1440,9 @@ std::ostream& operator<<(std::ostream& os, Struct struct_) { std::ostream& operator<<(std::ostream& os, Array array) { return TypePrinter(os).print(array); } +std::ostream& operator<<(std::ostream& os, TypeImport import) { + return TypePrinter(os).print(import); +} std::ostream& operator<<(std::ostream& os, TypeBuilder::ErrorReason reason) { switch (reason) { case TypeBuilder::ErrorReason::SelfSupertype: @@ -1417,6 +1455,10 @@ std::ostream& operator<<(std::ostream& os, TypeBuilder::ErrorReason reason) { return os << "Heap type has an undeclared child"; case TypeBuilder::ErrorReason::InvalidFuncType: return os << "Continuation has invalid function type"; + case TypeBuilder::ErrorReason::InvalidBoundType: + return os << "Type import has invalid bound type"; + case TypeBuilder::ErrorReason::ImportInRecGroup: + return os << "Type import in recursive group"; case TypeBuilder::ErrorReason::InvalidUnsharedField: return os << "Heap type has an invalid unshared field"; } @@ -1477,6 +1519,9 @@ bool SubTyper::isSubType(HeapType a, HeapType b) { if (a.isShared() != b.isShared()) { return false; } + if (a.isImport()) { + return isSubType(a.getImport().bound, b); + } if (b.isBasic()) { auto aTop = a.getUnsharedTop(); auto aUnshared = a.isBasic() ? a.getBasic(Unshared) : a; @@ -1742,6 +1787,21 @@ std::ostream& TypePrinter::print(HeapType type) { auto names = generator(type); + if (type.isImport()) { + TypeImport import = type.getImport(); + os << "("; + printMedium(os, "import "); + std::stringstream escapedModule, escapedBase; + String::printEscaped(escapedModule, import.module.str); + String::printEscaped(escapedBase, import.base.str); + printText(os, escapedModule.str(), false) << ' '; + printText(os, escapedBase.str(), false) << ' '; + os << "(type "; + names.name.print(os) << ' '; + print(import); + return os << "))"; + } + os << "(type "; names.name.print(os) << ' '; @@ -1778,6 +1838,9 @@ std::ostream& TypePrinter::print(HeapType type) { case HeapTypeKind::Cont: print(type.getContinuation()); break; + case HeapTypeKind::Import: + print(type.getImport()); + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -1871,6 +1934,12 @@ std::ostream& TypePrinter::print(const Array& array) { return os << ')'; } +std::ostream& TypePrinter::print(const TypeImport& import) { + os << "(sub "; + printHeapTypeName(import.bound); + return os << ')'; +} + size_t RecGroupHasher::operator()() const { size_t digest = wasm::hash(group.size()); for (auto type : group) { @@ -1946,6 +2015,9 @@ size_t RecGroupHasher::hash(const HeapTypeInfo& info) const { case HeapTypeKind::Array: hash_combine(digest, hash(info.array)); return digest; + case HeapTypeKind::Import: + hash_combine(digest, hash(info.import)); + return digest; case HeapTypeKind::Basic: break; } @@ -1993,6 +2065,13 @@ size_t RecGroupHasher::hash(const Array& array) const { return hash(array.element); } +size_t RecGroupHasher::hash(const TypeImport& import) const { + size_t digest = hash(import.bound); + hash_combine(digest, wasm::hash(import.module)); + hash_combine(digest, wasm::hash(import.base)); + return digest; +} + bool RecGroupEquator::operator()() const { if (newGroup == otherGroup) { return true; @@ -2079,6 +2158,8 @@ bool RecGroupEquator::eq(const HeapTypeInfo& a, const HeapTypeInfo& b) const { return eq(a.struct_, b.struct_); case HeapTypeKind::Array: return eq(a.array, b.array); + case HeapTypeKind::Import: + return eq(a.import, b.import); case HeapTypeKind::Basic: break; } @@ -2117,6 +2198,10 @@ bool RecGroupEquator::eq(const Array& a, const Array& b) const { return eq(a.element, b.element); } +bool RecGroupEquator::eq(const TypeImport& a, const TypeImport& b) const { + return a.module == b.module && a.base == b.base && eq(a.bound, b.bound); +} + } // anonymous namespace struct TypeBuilder::Impl { @@ -2153,6 +2238,9 @@ struct TypeBuilder::Impl { case HeapTypeKind::Array: info->array = hti.array; break; + case HeapTypeKind::Import: + info->import = hti.import; + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -2207,6 +2295,11 @@ void TypeBuilder::setHeapType(size_t i, Array array) { impl->entries[i].set(array); } +void TypeBuilder::setHeapType(size_t i, TypeImport import) { + assert(i < size() && "index out of bounds"); + impl->entries[i].set(import); +} + HeapType TypeBuilder::getTempHeapType(size_t i) { assert(i < size() && "index out of bounds"); return impl->entries[i].get(); @@ -2279,6 +2372,8 @@ bool isValidSupertype(const HeapTypeInfo& sub, const HeapTypeInfo& super) { return typer.isSubType(sub.struct_, super.struct_); case HeapTypeKind::Array: return typer.isSubType(sub.array, super.array); + case HeapTypeKind::Import: + return false; case HeapTypeKind::Basic: break; } @@ -2286,7 +2381,9 @@ bool isValidSupertype(const HeapTypeInfo& sub, const HeapTypeInfo& super) { } std::optional -validateType(HeapTypeInfo& info, std::unordered_set& seenTypes) { +validateType(HeapTypeInfo& info, + std::unordered_set& seenTypes, + bool inRecGroup) { if (auto* super = info.supertype) { // The supertype must be canonical (i.e. defined in a previous rec group) // or have already been defined in this rec group. @@ -2303,6 +2400,14 @@ validateType(HeapTypeInfo& info, std::unordered_set& seenTypes) { return TypeBuilder::ErrorReason::InvalidFuncType; } } + if (info.isImport()) { + if (!info.import.bound.isBasic()) { + return TypeBuilder::ErrorReason::InvalidBoundType; + } + if (inRecGroup) { + return TypeBuilder::ErrorReason::ImportInRecGroup; + } + } if (info.share == Shared) { switch (info.kind) { case HeapTypeKind::Func: @@ -2327,6 +2432,10 @@ validateType(HeapTypeInfo& info, std::unordered_set& seenTypes) { } break; } + case HeapTypeKind::Import: + // The sharedness is inherited (see buildRecGroup below). + assert(info.import.bound.isShared()); + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -2398,7 +2507,11 @@ buildRecGroup(std::unique_ptr&& groupInfo, std::unordered_set seenTypes; for (size_t i = 0; i < typeInfos.size(); ++i) { auto& info = typeInfos[i]; - if (auto err = validateType(*info, seenTypes)) { + if (info->isImport()) { + // Sharedness is inherited from the bound + info->share = info->import.bound.getShared(); + } + if (auto err = validateType(*info, seenTypes, typeInfos.size() > 1)) { return {TypeBuilder::Error{i, *err}}; } seenTypes.insert(asHeapType(info)); diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 840abfe1a38..ecf13eca57e 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4044,6 +4044,18 @@ static void validateExports(Module& module, ValidationInfo& info) { Name name = *exp->getInternalName(); info.shouldBeTrue( module.getTagOrNull(name), name, "module tag exports must be found"); + } else if (exp->kind == ExternalKind::Type) { + info.shouldBeTrue(module.features.hasTypeImports(), + exp->name, + "Exported type requires type-imports " + "[--enable-type-imports]"); + auto feats = exp->getHeapType()->getFeatures(); + if (!info.shouldBeTrue(feats <= module.features, + exp->name, + "Export type requires additional features")) { + info.getStream(nullptr) + << getMissingFeaturesList(module, feats) << '\n'; + } } else { WASM_UNREACHABLE("invalid ExternalKind"); } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index ca9fcc93328..d3bdabf18dc 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -60,6 +60,7 @@ const char* FP16Feature = "fp16"; const char* BulkMemoryOptFeature = "bulk-memory-opt"; const char* CallIndirectOverlongFeature = "call-indirect-overlong"; const char* CustomDescriptorsFeature = "custom-descriptors"; +const char* TypeImportsFeature = "type-imports"; } // namespace BinaryConsts::CustomSections diff --git a/src/wasm2js.h b/src/wasm2js.h index f59180a8237..c438a11ab7c 100644 --- a/src/wasm2js.h +++ b/src/wasm2js.h @@ -842,6 +842,7 @@ void Wasm2JSBuilder::addExports(Ref ast, Module* wasm) { break; } case ExternalKind::Tag: + case ExternalKind::Type: case ExternalKind::Invalid: Fatal() << "unsupported export type: " << export_->name << "\n"; } diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 711972ad48b..16f8fab6982 100644 --- a/test/binaryen.js/kitchen-sink.js.txt +++ b/test/binaryen.js/kitchen-sink.js.txt @@ -33,7 +33,7 @@ Features.RelaxedSIMD: 4096 Features.ExtendedConst: 8192 Features.Strings: 16384 Features.MultiMemory: 32768 -Features.All: 4194303 +Features.All: 8388607 InvalidId: 0 BlockId: 1 IfId: 2 diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 0f9ee08a0f7..34eb427c5bc 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -47,7 +47,7 @@ BinaryenFeatureMemory64: 2048 BinaryenFeatureRelaxedSIMD: 4096 BinaryenFeatureExtendedConst: 8192 BinaryenFeatureStrings: 16384 -BinaryenFeatureAll: 4194303 +BinaryenFeatureAll: 8388607 (f32.neg (f32.const -33.61199951171875) ) diff --git a/test/lit/basic/type-imports.wast b/test/lit/basic/type-imports.wast new file mode 100644 index 00000000000..1c9cae0cc1b --- /dev/null +++ b/test/lit/basic/type-imports.wast @@ -0,0 +1,130 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s -all -o %t.text.wast -g -S +;; RUN: wasm-as %s -all -g -o %t.wasm +;; RUN: wasm-dis %t.wasm -all -o %t.bin.wast +;; RUN: wasm-as %s -all -o %t.nodebug.wasm +;; RUN: wasm-dis %t.nodebug.wasm -all -o %t.bin.nodebug.wast +;; RUN: cat %t.text.wast | filecheck %s --check-prefix=CHECK +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +(module + + ;; Simple import + ;; CHECK: (import "env" "t1" (type $t1 (sub eq))) + (import "env" "t1" (type $t1 (sub eq))) + + ;; Import with omitted typetype + ;; CHECK: (import "env" "t2" (type $t2 (sub any))) + (import "env" "t2" (type $t2)) + + ;; Alternative syntax with both import and export + (type $t3 (export "t3") (import "env" "t3") (sub struct)) + + ;; Use an imported type in a type + ;; CHECK: (import "env" "t3" (type $t3 (sub struct))) + + ;; CHECK: (type $t4 (array (ref $t1))) + (type $t4 (array (field (ref $t1)))) + + ;; CHECK: (type $t5 (struct (field (ref $t1)))) + (type $t5 (export "t5") (struct (field (ref $t1)))) + + ;; Import function with imported types + ;; CHECK: (type $5 (func (param (ref $t1)) (result (ref $t2)))) + + ;; CHECK: (type $6 (func (param (ref eq) (ref $t2) (ref $t3) (ref $t4)) (result (ref $t2)))) + + ;; CHECK: (type $7 (func (param (ref $t3)) (result (ref struct)))) + + ;; CHECK: (import "env" "g" (func $g (type $5) (param (ref $t1)) (result (ref $t2)))) + (import "env" "g" (func $g (param (ref $t1)) (result (ref $t2)))) + + ;; Cast and function call involving imported types + ;; CHECK: (export "f1" (func $f1)) + + ;; CHECK: (export "f2" (func $f2)) + + ;; CHECK: (export "t3" (type $t3)) + + ;; CHECK: (export "t5" (type $t5)) + + ;; CHECK: (export "t2" (type $t2)) + + ;; CHECK: (export "t4" (type $t4)) + + ;; CHECK: (func $f1 (type $6) (param $x (ref eq)) (param $1 (ref $t2)) (param $2 (ref $t3)) (param $3 (ref $t4)) (result (ref $t2)) + ;; CHECK-NEXT: (call $g + ;; CHECK-NEXT: (ref.cast (ref $t1) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $f1 (export "f1") + (param $x (ref eq)) (param (ref $t2) (ref $t3) (ref $t4)) (result (ref $t2)) + (call $g + (ref.cast (ref $t1) + (local.get $x) + ) + ) + ) + + ;; Check that the imported type is a subtype of its bound + ;; CHECK: (func $f2 (type $7) (param $x (ref $t3)) (result (ref struct)) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + (func $f2 (export "f2") (param $x (ref $t3)) (result (ref struct)) + (local.get $x) + ) + + ;; Reexport an imported type + (export "t2" (type $t2)) + + ;; Export a type defined in this module + (export "t4" (type $t4)) + + ;; Export an abstract heap type + (export "t6" (type extern)) +) +;; CHECK-BIN-NODEBUG: (import "env" "t1" (type $0 (sub eq))) + +;; CHECK-BIN-NODEBUG: (import "env" "t2" (type $1 (sub any))) + +;; CHECK-BIN-NODEBUG: (import "env" "t3" (type $2 (sub struct))) + +;; CHECK-BIN-NODEBUG: (type $3 (array (ref $0))) + +;; CHECK-BIN-NODEBUG: (type $4 (struct (field (ref $0)))) + +;; CHECK-BIN-NODEBUG: (type $5 (func (param (ref $0)) (result (ref $1)))) + +;; CHECK-BIN-NODEBUG: (type $6 (func (param (ref eq) (ref $1) (ref $2) (ref $3)) (result (ref $1)))) + +;; CHECK-BIN-NODEBUG: (type $7 (func (param (ref $2)) (result (ref struct)))) + +;; CHECK-BIN-NODEBUG: (import "env" "g" (func $fimport$0 (type $5) (param (ref $0)) (result (ref $1)))) + +;; CHECK-BIN-NODEBUG: (export "f1" (func $0)) + +;; CHECK-BIN-NODEBUG: (export "f2" (func $1)) + +;; CHECK-BIN-NODEBUG: (export "t3" (type $2)) + +;; CHECK-BIN-NODEBUG: (export "t5" (type $4)) + +;; CHECK-BIN-NODEBUG: (export "t2" (type $1)) + +;; CHECK-BIN-NODEBUG: (export "t4" (type $3)) + +;; CHECK-BIN-NODEBUG: (func $0 (type $6) (param $0 (ref eq)) (param $1 (ref $1)) (param $2 (ref $2)) (param $3 (ref $3)) (result (ref $1)) +;; CHECK-BIN-NODEBUG-NEXT: (call $fimport$0 +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref $0) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $1 (type $7) (param $0 (ref $2)) (result (ref struct)) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index 7514841a7a6..1ab6e3a549d 100644 --- a/test/lit/help/wasm-as.test +++ b/test/lit/help/wasm-as.test @@ -134,6 +134,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-type-imports Enable type imports +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-type-imports Disable type imports +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-ctor-eval.test b/test/lit/help/wasm-ctor-eval.test index bed94623224..3cf8f92f84f 100644 --- a/test/lit/help/wasm-ctor-eval.test +++ b/test/lit/help/wasm-ctor-eval.test @@ -141,6 +141,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-type-imports Enable type imports +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-type-imports Disable type imports +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-dis.test b/test/lit/help/wasm-dis.test index a10b41a4f3c..bf0fb6b08ad 100644 --- a/test/lit/help/wasm-dis.test +++ b/test/lit/help/wasm-dis.test @@ -127,6 +127,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-type-imports Enable type imports +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-type-imports Disable type imports +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-emscripten-finalize.test b/test/lit/help/wasm-emscripten-finalize.test index 4cb9ef940a0..7c57ef5bd2d 100644 --- a/test/lit/help/wasm-emscripten-finalize.test +++ b/test/lit/help/wasm-emscripten-finalize.test @@ -169,6 +169,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-type-imports Enable type imports +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-type-imports Disable type imports +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-merge.test b/test/lit/help/wasm-merge.test index 0c51047952b..cf43c21b8ce 100644 --- a/test/lit/help/wasm-merge.test +++ b/test/lit/help/wasm-merge.test @@ -40,6 +40,8 @@ ;; CHECK-NEXT: --skip-export-conflicts,-sec Skip exports that conflict with previous ;; CHECK-NEXT: ones ;; CHECK-NEXT: +;; CHECK-NEXT: --strip-type-exports,-ste Do not emit type exports +;; CHECK-NEXT: ;; CHECK-NEXT: --emit-text,-S Emit text instead of binary for the ;; CHECK-NEXT: output file ;; CHECK-NEXT: @@ -157,6 +159,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-type-imports Enable type imports +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-type-imports Disable type imports +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 1a7c495fabd..37e83784cbc 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -518,6 +518,8 @@ ;; CHECK-NEXT: --strip-target-features strip the wasm target features ;; CHECK-NEXT: section ;; CHECK-NEXT: +;; CHECK-NEXT: --strip-type-exports strip the wasm type exports +;; CHECK-NEXT: ;; CHECK-NEXT: --stub-unsupported-js stub out unsupported JS ;; CHECK-NEXT: operations ;; CHECK-NEXT: @@ -781,6 +783,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors ;; CHECK-NEXT: (RTTs) and exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-type-imports Enable type imports +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-type-imports Disable type imports +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 1a6be04783d..1b74bc8ee75 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -530,6 +530,8 @@ ;; CHECK-NEXT: --strip-target-features strip the wasm target features ;; CHECK-NEXT: section ;; CHECK-NEXT: +;; CHECK-NEXT: --strip-type-exports strip the wasm type exports +;; CHECK-NEXT: ;; CHECK-NEXT: --stub-unsupported-js stub out unsupported JS ;; CHECK-NEXT: operations ;; CHECK-NEXT: @@ -793,6 +795,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors ;; CHECK-NEXT: (RTTs) and exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-type-imports Enable type imports +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-type-imports Disable type imports +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test index 1a04f2f5878..3a750908c21 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -166,6 +166,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-type-imports Enable type imports +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-type-imports Disable type imports +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index 095c66047ac..e8e96afbc9c 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -266,6 +266,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors (RTTs) and ;; CHECK-NEXT: exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-type-imports Enable type imports +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-type-imports Disable type imports +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 6db49301db1..5e94881b579 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -481,6 +481,8 @@ ;; CHECK-NEXT: --strip-target-features strip the wasm target features ;; CHECK-NEXT: section ;; CHECK-NEXT: +;; CHECK-NEXT: --strip-type-exports strip the wasm type exports +;; CHECK-NEXT: ;; CHECK-NEXT: --stub-unsupported-js stub out unsupported JS ;; CHECK-NEXT: operations ;; CHECK-NEXT: @@ -744,6 +746,10 @@ ;; CHECK-NEXT: --disable-custom-descriptors Disable custom descriptors ;; CHECK-NEXT: (RTTs) and exact references ;; CHECK-NEXT: +;; CHECK-NEXT: --enable-type-imports Enable type imports +;; CHECK-NEXT: +;; CHECK-NEXT: --disable-type-imports Disable type imports +;; CHECK-NEXT: ;; CHECK-NEXT: --enable-typed-function-references Deprecated compatibility flag ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-typed-function-references Deprecated compatibility flag diff --git a/test/lit/merge/type-imports.wat b/test/lit/merge/type-imports.wat new file mode 100644 index 00000000000..16565512feb --- /dev/null +++ b/test/lit/merge/type-imports.wat @@ -0,0 +1,129 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-merge %s first %s.second second -all -S -o - | filecheck %s + +(module + ;; Bound to an abstract heap type + (import "second" "t1" (type $t1)) + + ;; Bound to a concrete heap type + (import "second" "t2" (type $t2)) + + ;; Bound to an imported type + ;; CHECK: (import "third" "t3" (type $t3 (sub struct))) + (import "second" "t3" (type $t3)) + + ;; Left unbound + ;; CHECK: (import "third" "t4" (type $t4 (sub any))) + (import "third" "t4" (type $t4)) + + ;; Check the import of a function using imported types + (import "second" "g" (func $g (param (ref $t2)))) + + ;; Check that the function parameters are updated + ;; CHECK: (type $t2 (array i8)) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $r1 (struct (field (ref $r1)) (field (ref $r2)) (field (ref eq)))) + + ;; CHECK: (type $r2 (struct (field (ref $r1)) (field (ref $r2)) (field (ref $t2)))) + + ;; CHECK: (type $5 (func (param (ref eq) (ref $t2) (ref $t3) (ref $t4)))) + + ;; CHECK: (type $6 (func (param (ref any)) (result (ref eq)))) + + ;; CHECK: (type $7 (func (param (ref any)) (result (ref $t2)))) + + ;; CHECK: (type $8 (func (param (ref any)) (result (ref $t3)))) + + ;; CHECK: (type $9 (func (param (ref any)) (result (ref $t4)))) + + ;; CHECK: (type $10 (func (param (ref eq)) (result (ref $r1)))) + + ;; CHECK: (type $11 (func (param (ref $t2)))) + + ;; CHECK: (export "f" (func $f)) + + ;; CHECK: (export "g1" (func $g1)) + + ;; CHECK: (export "g2" (func $g2)) + + ;; CHECK: (export "g3" (func $g3)) + + ;; CHECK: (export "g4" (func $g4)) + + ;; CHECK: (export "h" (func $h)) + + ;; CHECK: (export "g" (func $g_7)) + + ;; CHECK: (export "t2" (type $t2)) + + ;; CHECK: (export "t3" (type $t3)) + + ;; CHECK: (func $f (type $5) (param $0 (ref eq)) (param $1 (ref $t2)) (param $2 (ref $t3)) (param $3 (ref $t4)) + ;; CHECK-NEXT: ) + (func $f (export "f") (param (ref $t1) (ref $t2) (ref $t3) (ref $t4)) + ) + + ;; Check that types in instructions are also updated + ;; CHECK: (func $g1 (type $6) (param $x (ref any)) (result (ref eq)) + ;; CHECK-NEXT: (ref.cast (ref eq) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $g1 (export "g1") (param $x (ref any)) (result (ref $t1)) + (ref.cast (ref $t1) + (local.get $x) + ) + ) + + ;; CHECK: (func $g2 (type $7) (param $x (ref any)) (result (ref $t2)) + ;; CHECK-NEXT: (ref.cast (ref $t2) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $g2 (export "g2") (param $x (ref any)) (result (ref $t2)) + (ref.cast (ref $t2) + (local.get $x) + ) + ) + + ;; CHECK: (func $g3 (type $8) (param $x (ref any)) (result (ref $t3)) + ;; CHECK-NEXT: (ref.cast (ref $t3) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $g3 (export "g3") (param $x (ref any)) (result (ref $t3)) + (ref.cast (ref $t3) + (local.get $x) + ) + ) + + ;; CHECK: (func $g4 (type $9) (param $x (ref any)) (result (ref $t4)) + ;; CHECK-NEXT: (ref.cast (ref $t4) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $g4 (export "g4") (param $x (ref any)) (result (ref $t4)) + (ref.cast (ref $t4) + (local.get $x) + ) + ) + + ;; Check that recursive types are preserved + (rec + (type $r1 (struct (field (ref $r1) (ref $r2) (ref $t1)))) + (type $r2 (struct (field (ref $r1) (ref $r2) (ref $t2)))) + ) + + ;; CHECK: (func $h (type $10) (param $x (ref eq)) (result (ref $r1)) + ;; CHECK-NEXT: (ref.cast (ref $r1) + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $h (export "h") (param $x (ref eq)) (result (ref $r1)) + (ref.cast (ref $r1) (local.get $x))) +) + +;; CHECK: (func $g_7 (type $11) (param $0 (ref $t2)) +;; CHECK-NEXT: ) diff --git a/test/lit/merge/type-imports.wat.second b/test/lit/merge/type-imports.wat.second new file mode 100644 index 00000000000..e50f7274c0f --- /dev/null +++ b/test/lit/merge/type-imports.wat.second @@ -0,0 +1,7 @@ +(module + (export "t1" (type eq)) + (import "third" "t3" (type $t3 (sub struct))) + (type $t2 (export "t2") (array i8)) + (export "t3" (type $t3)) + (func $g (export "g") (param (ref $t2))) +) 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 34976434e9d..5ed4d5fef39 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 @@ -20,6 +20,7 @@ --enable-bulk-memory-opt --enable-call-indirect-overlong --enable-custom-descriptors +--enable-type-imports (module (type $0 (func (result v128 externref))) (func $foo (type $0) (result v128 externref) diff --git a/test/unit/test_features.py b/test/unit/test_features.py index 0a232da0fb4..584430e1069 100644 --- a/test/unit/test_features.py +++ b/test/unit/test_features.py @@ -60,6 +60,10 @@ def check_stack_switching(self, module, error): self.check_feature(module, error, '--enable-stack-switching', ['--enable-gc', '--enable-reference-types', '--enable-exception-handling']) + def check_type_imports(self, module, error): + self.check_feature(module, error, '--enable-type-imports', + ['--enable-reference-types']) + def test_v128_signature(self): module = ''' (module @@ -329,6 +333,23 @@ def check_nop(flag): check_nop('--enable-call-indirect-overlong') check_nop('--disable-call-indirect-overlong') + def test_type_imports_export(self): + module = ''' + (module + (export "foo" (type extern)) + ) + ''' + self.check_type_imports(module, 'Exported type requires type-imports') + + def test_type_imports_import(self): + module = ''' + (module + (import "env" "foo" (type $foo (sub extern))) + (func $f (param (ref $foo))) + ) + ''' + self.check_type_imports(module, 'all used types should be allowed') + class TargetFeaturesSectionTest(utils.BinaryenTestCase): def test_atomics(self): @@ -451,4 +472,5 @@ def test_emit_all_features(self): '--enable-shared-everything', '--enable-fp16', '--enable-bulk-memory-opt', + '--enable-type-imports', ], p2.stdout.splitlines())