From c066fcd890a28759bb7a3aa9c53d678b32c59dca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Mon, 24 Feb 2025 16:58:43 +0100 Subject: [PATCH 01/18] Type imports Implementation of the Type Imports and Exports proposal, which allows Wasm module to export and import types: (type $File (export "File") (struct ...)) (import "file" "File" (type $File (sub any))) wasm-merge can be used to combine several modules using this proposal, connecting type imports to corresponding type exports. To produce an output compatible with existing tools, an option --strip-type-exports can be use to omit any type export from the output. From an implementation point of view, for imports, a new kind of heap type is used: (import "module" "base" (sub absheaptype)) Including the module and base names in the type definition works well with type canonicalisation. For exports, we separate type exports from other exports, since they don't have an internal name but a heap type: class TypeExport { Name name; HeapType heaptype; // exported type }; This is somewhat error-prone. Typically, to check for repeated exports, we need to check two tables. But the alternatives do not seem much better. If we put all export together, then we have to make sure that we are never trying to access the internal name of a type import. --- src/binaryen-c.cpp | 42 +++++ src/binaryen-c.h | 31 ++++ src/ir/gc-type-utils.h | 1 + src/ir/module-utils.cpp | 18 +- src/ir/names.h | 6 +- src/ir/subtypes.h | 1 + src/ir/type-updating.cpp | 6 + src/parser/context-decls.cpp | 2 +- src/parser/contexts.h | 70 +++++++- src/parser/parse-2-typedefs.cpp | 19 +++ src/parser/parsers.h | 87 ++++++++-- src/passes/CMakeLists.txt | 1 + src/passes/MinifyImportsAndExports.cpp | 3 + src/passes/Print.cpp | 17 ++ src/passes/StripTypeExports.cpp | 31 ++++ src/passes/TypeMerging.cpp | 8 + src/passes/TypeSSA.cpp | 1 + src/passes/Unsubtyping.cpp | 2 + src/passes/pass.cpp | 3 + src/passes/passes.h | 1 + src/tools/fuzzing/fuzzing.cpp | 6 + src/tools/fuzzing/heap-types.cpp | 5 + src/tools/tool-options.h | 1 + src/tools/wasm-fuzz-types.cpp | 1 + src/tools/wasm-merge.cpp | 225 +++++++++++++++++++++++++ src/tools/wasm-metadce.cpp | 11 ++ src/wasm-binary.h | 3 + src/wasm-builder.h | 8 + src/wasm-features.h | 7 +- src/wasm-traversal.h | 4 + src/wasm-type-printing.h | 1 + src/wasm-type.h | 26 +++ src/wasm.h | 17 ++ src/wasm/wasm-binary.cpp | 161 +++++++++++++----- src/wasm/wasm-type-shape.cpp | 11 ++ src/wasm/wasm-type.cpp | 96 +++++++++++ src/wasm/wasm-validator.cpp | 13 ++ src/wasm/wasm.cpp | 24 +++ src/wasm2js.h | 1 + 39 files changed, 901 insertions(+), 70 deletions(-) create mode 100644 src/passes/StripTypeExports.cpp diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 8eb73b4c259..748ed41fc43 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -4959,6 +4959,37 @@ BinaryenExportRef BinaryenGetExportByIndex(BinaryenModuleRef module, return exports[index].get(); } +// Type Exports + +BinaryenTypeExportRef BinaryenAddTypeExport(BinaryenModuleRef module, + BinaryenHeapType type, + const char* externalName) { + auto* ret = new TypeExport(); + ret->heaptype = HeapType(type); + ret->name = externalName; + ((Module*)module)->addTypeExport(ret); + return ret; +} +BinaryenTypeExportRef BinaryenGetTypeExport(BinaryenModuleRef module, + const char* externalName) { + return ((Module*)module)->getTypeExportOrNull(externalName); +} +void BinaryenRemoveTypeExport(BinaryenModuleRef module, + const char* externalName) { + ((Module*)module)->removeTypeExport(externalName); +} +BinaryenIndex BinaryenGetNumTypeExports(BinaryenModuleRef module) { + return ((Module*)module)->typeExports.size(); +} +BinaryenTypeExportRef BinaryenGetTypeExportByIndex(BinaryenModuleRef module, + BinaryenIndex index) { + const auto& exports = ((Module*)module)->typeExports; + if (exports.size() <= index) { + Fatal() << "invalid export index."; + } + return exports[index].get(); +} + BinaryenTableRef BinaryenAddTable(BinaryenModuleRef module, const char* name, BinaryenIndex initial, @@ -5928,6 +5959,17 @@ const char* BinaryenExportGetValue(BinaryenExportRef export_) { return ((Export*)export_)->value.str.data(); } +// +// =========== Type export operations =========== +// + +const char* BinaryenTypeExportGetName(BinaryenTypeExportRef export_) { + return ((TypeExport*)export_)->name.str.data(); +} +BinaryenHeapType BinaryenTypeExportGetHeapType(BinaryenTypeExportRef export_) { + return ((TypeExport*)export_)->heaptype.getID(); +} + // // ========= Custom sections ========= // diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 8616571db59..7a051869f69 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -2725,6 +2725,26 @@ BINARYEN_API BinaryenIndex BinaryenGetNumExports(BinaryenModuleRef module); BINARYEN_API BinaryenExportRef BinaryenGetExportByIndex(BinaryenModuleRef module, BinaryenIndex index); +// Type exports + +BINARYEN_REF(TypeExport); + +// Adds a type export to the module. +BINARYEN_API BinaryenTypeExportRef BinaryenAddTypeExport( + BinaryenModuleRef module, BinaryenHeapType type, const char* externalName); +// Gets a type export reference by external name. Returns NULL if the export +// does not exist. +BINARYEN_API BinaryenTypeExportRef +BinaryenGetTypeExport(BinaryenModuleRef module, const char* externalName); +// Removes a type export by external name. +BINARYEN_API void BinaryenRemoveTypeExport(BinaryenModuleRef module, + const char* externalName); +// Gets the number of type exports in the module. +BINARYEN_API BinaryenIndex BinaryenGetNumTypeExports(BinaryenModuleRef module); +// Gets the type export at the specified index. +BINARYEN_API BinaryenTypeExportRef +BinaryenGetTypeExportByIndex(BinaryenModuleRef module, BinaryenIndex index); + // Globals BINARYEN_REF(Global); @@ -3315,6 +3335,17 @@ BINARYEN_API const char* BinaryenExportGetName(BinaryenExportRef export_); // Gets the internal name of the specified export. BINARYEN_API const char* BinaryenExportGetValue(BinaryenExportRef export_); +// +// ========== Type Export Operations ========== +// + +// Gets the external name of the specified export. +BINARYEN_API const char* +BinaryenTypeExportGetName(BinaryenTypeExportRef export_); +// Gets the internal name of the specified export. +BINARYEN_API BinaryenHeapType +BinaryenTypeExportGetHeapType(BinaryenTypeExportRef 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 328aee64d22..0f78baadf81 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -489,6 +489,9 @@ InsertOrderedMap collectHeapTypeInfo( for (auto& curr : wasm.elementSegments) { info.note(curr->type); } + for (auto& curr : wasm.typeExports) { + info.note(curr->heaptype); + } // Collect info from functions in parallel. ModuleUtils::ParallelFunctionAnalysis @@ -655,11 +658,15 @@ void classifyTypeVisibility(Module& wasm, case ExternalKind::Tag: notePublic(wasm.getTag(ex->value)->type); continue; + case ExternalKind::Type: case ExternalKind::Invalid: break; } WASM_UNREACHABLE("unexpected export kind"); } + for (auto& ex : wasm.typeExports) { + notePublic(ex->heaptype); + } // Ignorable public types are public. for (auto type : getIgnorablePublicTypes()) { @@ -793,6 +800,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 +824,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 diff --git a/src/ir/names.h b/src/ir/names.h index c10b70b5231..ca31d49189e 100644 --- a/src/ir/names.h +++ b/src/ir/names.h @@ -70,8 +70,10 @@ inline Name getValidName(Name root, inline Name getValidExportName(Module& module, Name root) { return getValidName( root, - [&](Name test) { return !module.getExportOrNull(test); }, - module.exports.size()); + [&](Name test) { + return !module.getTypeExportOrNull(test) && !module.getExportOrNull(test); + }, + module.typeExports.size() + module.exports.size()); } inline Name getValidGlobalName(Module& module, Name root) { return getValidName( diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h index c69250043b8..60d5182e741 100644 --- a/src/ir/subtypes.h +++ b/src/ir/subtypes.h @@ -127,6 +127,7 @@ struct SubTypes { break; case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Import: case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index b90d8eb8790..febc5d79933 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,9 @@ void GlobalTypeRewriter::mapTypes(const TypeMap& oldToNewTypes) { for (auto& tag : wasm.tags) { tag->type = updater.getNew(tag->type); } + for (auto& exp : wasm.typeExports) { + exp->heaptype = updater.getNew(exp->heaptype); + } } void GlobalTypeRewriter::mapTypeNamesAndIndices(const TypeMap& oldToNewTypes) { diff --git a/src/parser/context-decls.cpp b/src/parser/context-decls.cpp index c124689d334..6861c62391d 100644 --- a/src/parser/context-decls.cpp +++ b/src/parser/context-decls.cpp @@ -33,7 +33,7 @@ Result<> addExports(Lexer& in, const std::vector& exports, ExternalKind kind) { for (auto name : exports) { - if (wasm.getExportOrNull(name)) { + if (wasm.getTypeExportOrNull(name) || wasm.getExportOrNull(name)) { // TODO: Fix error location return in.err("repeated export name"); } diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 55601b174c5..f87bd8bcfe2 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -935,6 +935,10 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { std::vector dataDefs; std::vector tagDefs; + // Type imports: name, positions of type and import names. + std::vector, ImportNames, Index>> + typeImports; + // Positions of export definitions. std::vector exportDefs; @@ -957,6 +961,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) { @@ -978,9 +983,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) {} @@ -1092,10 +1098,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. @@ -1108,12 +1132,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) { return builder.getTempRefType(ht, nullability); @@ -1154,7 +1181,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; } @@ -1403,6 +1441,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. @@ -1757,14 +1803,30 @@ 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)) { + if (wasm.getTypeExportOrNull(name) || wasm.getExportOrNull(name)) { return in.err(pos, "duplicate export"); } wasm.addExport(builder.makeExport(name, value, kind)); return Ok{}; } + Result<> addTypeExport(Index pos, HeapType heaptype, Name name) { + if (wasm.getTypeExportOrNull(name) || wasm.getTypeExportOrNull(name)) { + return in.err(pos, "duplicate export"); + } + wasm.addTypeExport(builder.makeTypeExport(name, heaptype)); + 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..8ea11d6be53 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] = Import(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,17 @@ Result<> parseTypeDefs( } } } + for (size_t i = 0; i < types.size(); ++i) { + for (Name& name : ctx.typeExports[i]) { + if (decls.wasm.getTypeExportOrNull(name) || + decls.wasm.getExportOrNull(name)) { + // TODO: Fix error location + return ctx.in.err("repeated export name"); + } + decls.wasm.addTypeExport( + Builder(decls.wasm).makeTypeExport(name, types[i])); + } + } return Ok{}; } diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 4ba88fd8bf2..908892fada3 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -357,7 +357,7 @@ Result> inlineExports(Lexer&); template Result<> comptype(Ctx&); template Result<> sharecomptype(Ctx&); template Result<> subtype(Ctx&); -template MaybeResult<> typedef_(Ctx&); +template MaybeResult<> typedef_(Ctx&, bool inRecGroup); template MaybeResult<> rectype(Ctx&); template MaybeResult locals(Ctx&); template MaybeResult<> import_(Ctx&); @@ -431,15 +431,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); @@ -449,6 +444,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 @@ -867,6 +873,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(); @@ -2958,8 +2980,10 @@ template Result<> subtype(Ctx& ctx) { return Ok{}; } -// typedef ::= '(' 'type' id? subtype ')' -template MaybeResult<> typedef_(Ctx& ctx) { +// typedef ::= '(' 'type' id? ('(' 'export' name ')')* subtype ')' +// | '(' 'type' id? ('(' 'export' name ')')* +// '(' 'import' mod:name nm:name ')' typetype ')' +template MaybeResult<> typedef_(Ctx& ctx, bool inRecGroup) { auto pos = ctx.in.getPos(); if (!ctx.in.takeSExprStart("type"sv)) { @@ -2971,14 +2995,30 @@ 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) { + if (inRecGroup) { + return ctx.in.err("type import not allowed in recursive group"); + } + 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{}; } @@ -2990,7 +3030,7 @@ template MaybeResult<> rectype(Ctx& ctx) { if (ctx.in.takeSExprStart("rec"sv)) { size_t startIndex = ctx.getRecGroupStartIndex(); size_t groupLen = 0; - while (auto type = typedef_(ctx)) { + while (auto type = typedef_(ctx, true)) { CHECK_ERR(type); ++groupLen; } @@ -2998,7 +3038,7 @@ template MaybeResult<> rectype(Ctx& ctx) { return ctx.in.err("expected type definition or end of recursion group"); } ctx.addRecGroup(startIndex, groupLen); - } else if (auto type = typedef_(ctx)) { + } else if (auto type = typedef_(ctx, false)) { CHECK_ERR(type); } else { return {}; @@ -3045,6 +3085,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(); @@ -3091,6 +3132,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"); } @@ -3345,6 +3393,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)) { @@ -3376,6 +3425,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/MinifyImportsAndExports.cpp b/src/passes/MinifyImportsAndExports.cpp index 2105f025ceb..7c48366c5c0 100644 --- a/src/passes/MinifyImportsAndExports.cpp +++ b/src/passes/MinifyImportsAndExports.cpp @@ -88,6 +88,9 @@ struct MinifyImportsAndExports : public Pass { if (minifyExports) { // Minify the exported names. + for (auto& curr : module->typeExports) { + process(curr->name); + } for (auto& curr : module->exports) { process(curr->name); } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 71878c973b3..4286050e66d 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -431,6 +431,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { // Module-level visitors void handleSignature(Function* curr, bool printImplicitNames = false); void visitExport(Export* curr); + void visitTypeExport(TypeExport* curr); void emitImportHeader(Importable* curr); void visitGlobal(Global* curr); void emitGlobalType(Global* curr); @@ -3083,6 +3084,7 @@ void PrintSExpression::visitExport(Export* curr) { case ExternalKind::Tag: o << "tag"; break; + case ExternalKind::Type: case ExternalKind::Invalid: WASM_UNREACHABLE("invalid ExternalKind"); } @@ -3090,6 +3092,16 @@ void PrintSExpression::visitExport(Export* curr) { curr->value.print(o) << "))"; } +void PrintSExpression::visitTypeExport(TypeExport* curr) { + o << '('; + printMedium(o, "export "); + std::stringstream escaped; + String::printEscaped(escaped, curr->name.str); + printText(o, escaped.str(), false) << " (type "; + printHeapType(curr->heaptype); + o << "))"; +} + void PrintSExpression::emitImportHeader(Importable* curr) { printMedium(o, "import "); std::stringstream escapedModule, escapedBase; @@ -3507,6 +3519,11 @@ void PrintSExpression::visitModule(Module* curr) { o << ')' << maybeNewLine; } ModuleUtils::iterDefinedTags(*curr, [&](Tag* tag) { visitTag(tag); }); + for (auto& child : curr->typeExports) { + doIndent(o, indent); + visitTypeExport(child.get()); + o << maybeNewLine; + } for (auto& child : curr->exports) { doIndent(o, indent); visitExport(child.get()); diff --git a/src/passes/StripTypeExports.cpp b/src/passes/StripTypeExports.cpp new file mode 100644 index 00000000000..cbd3762911d --- /dev/null +++ b/src/passes/StripTypeExports.cpp @@ -0,0 +1,31 @@ +/* + * 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->removeTypeExports([&](TypeExport* curr) { return true; }); + } +}; + +Pass* createStripTypeExportsPass() { return new StripTypeExports; } + +} // namespace wasm diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index e7a25cf372c..ff603c916be 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -190,6 +190,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(Import a); size_t shapeHash(Field a); size_t shapeHash(Type a); size_t shapeHash(const Tuple& a); @@ -572,6 +573,8 @@ bool shapeEq(HeapType a, HeapType b) { return shapeEq(a.getArray(), b.getArray()); case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Import: + return false; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -595,6 +598,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 +641,8 @@ size_t shapeHash(Signature a) { return digest; } +size_t shapeHash(Import a) { return shapeHash(a.bound); } + 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 1c89383229f..761aa9667d2 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -516,6 +516,10 @@ void TranslateToFuzzReader::setupHeapTypes() { break; case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Import: + interestingHeapSubTypes[type.getImport().bound].push_back(type); + // TODO: also the supertypes of the bound? + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -3608,6 +3612,8 @@ Expression* TranslateToFuzzReader::makeCompoundRef(Type type) { } case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Import: + return _makeunreachable(); case HeapTypeKind::Basic: break; } diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index dc6e574c7e9..7be6f61954b 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,7 @@ std::vector Inhabitator::build() { } case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); + case HeapTypeKind::Import: case HeapTypeKind::Basic: break; } @@ -1032,6 +1034,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 +1067,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 bff71bb4138..9eef9c4cd33 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -106,6 +106,7 @@ struct ToolOptions : public Options { .addFeature(FeatureSet::StackSwitching, "stack switching") .addFeature(FeatureSet::SharedEverything, "shared-everything threads") .addFeature(FeatureSet::FP16, "float 16 operations") + .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 166c875aecd..506436f1cea 100644 --- a/src/tools/wasm-merge.cpp +++ b/src/tools/wasm-merge.cpp @@ -94,8 +94,10 @@ #include "ir/module-utils.h" #include "ir/names.h" +#include "ir/type-updating.h" #include "support/colors.h" #include "support/file.h" +#include "support/topological_sort.h" #include "wasm-builder.h" #include "wasm-io.h" #include "wasm-validator.h" @@ -133,6 +135,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, @@ -163,6 +167,7 @@ struct ExportInfo { Name baseName; }; std::unordered_map exportModuleMap; +std::unordered_map typeExportModuleMap; // A map of [kind of thing in the module] to [old name => new name] for things // of that kind. For example, the NameUpdates for functions is a map of old @@ -353,6 +358,28 @@ void copyModuleContents(Module& input, Name inputName) { // Add the export. merged.addExport(std::move(copy)); } + for (auto& curr : input.typeExports) { + auto copy = std::make_unique(*curr); + + // Note the module origin and original name of this export, for later fusing + // of imports to exports. + typeExportModuleMap[copy.get()] = ExportInfo{inputName, curr->name}; + + // An export may already exist with that name, so fix it up. + copy->name = Names::getValidExportName(merged, copy->name); + if (copy->name != curr->name) { + if (exportMergeMode == ErrorOnExportConflicts) { + Fatal() << "Export name conflict: " << curr->name << " (consider" + << " --rename-export-conflicts or" + << " --skip-export-conflicts)\n"; + } else if (exportMergeMode == SkipExportConflicts) { + // Skip the addTypeExport below us. + continue; + } + } + // Add the export. + merged.addTypeExport(std::move(copy)); + } // Start functions must be merged. if (input.start.is()) { @@ -413,6 +440,187 @@ void checkLimit(bool& valid, const char* kind, T* export_, T* import) { } } +// Sort heap types to put children (rewritten by map) before heap type +std::vector sortHeapTypes(std::vector& types, + std::function map) { + // Collect the rec groups. + std::unordered_map groupIndices; + std::vector groups; + for (auto& type : types) { + auto group = type.getRecGroup(); + if (groupIndices.insert({group, groups.size()}).second) { + groups.push_back(group); + } + } + + // Collect the reverse dependencies of each group. + std::vector> depSets(groups.size()); + 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; + } + auto childGroup = child.getRecGroup(); + if (childGroup == groups[i]) { + continue; + } + depSets[groupIndices.at(childGroup)].insert(i); + } + } + } + TopologicalSort::Graph deps; + deps.reserve(groups.size()); + for (size_t i = 0; i < groups.size(); ++i) { + deps.emplace_back(depSets[i].begin(), depSets[i].end()); + } + + auto sorted = TopologicalSort::sort(deps); + + std::vector sortedTypes; + sortedTypes.reserve(types.size()); + for (auto groupIndex : sorted) { + for (auto type : groups[groupIndex]) { + sortedTypes.push_back(type); + } + } + return sortedTypes; +} + +// 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() { + if (merged.typeExports.size() == 0) { + return; + } + + // 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.typeExports) { + assert(typeExportModuleMap.count(ex.get())); + ExportInfo& exportInfo = typeExportModuleMap[ex.get()]; + moduleTypeExportMap[exportInfo.moduleName][exportInfo.baseName] = + ex->heaptype; + } + // 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; + std::vector heapTypes = ModuleUtils::collectHeapTypes(merged); + + for (HeapType& heapType : heapTypes) { + if (heapType.isImport()) { + Import 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 + heapTypes = sortHeapTypes(heapTypes, initialMap); + std::unordered_map typeIndices; + TypeBuilder typeBuilder(heapTypes.size()); + for (size_t i = 0; i < heapTypes.size(); i++) { + typeIndices[heapTypes[i]] = i; + } + + // 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[typeIndices[type]]; + }; + + // Build new types + std::optional lastGroup = std::nullopt; + for (size_t i = 0; i < heapTypes.size(); i++) { + HeapType heapType = heapTypes[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 : heapTypes) { + HeapType type = heapType; + type = initialMap(type); + if (!type.isBasic()) { + type = newTypes[typeIndices[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() { @@ -647,6 +855,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", @@ -720,6 +935,9 @@ Input source maps can be specified by adding an -ism option right after the modu for (auto& curr : merged.exports) { exportModuleMap[curr.get()] = ExportInfo{inputFileName, curr->name}; } + for (auto& curr : merged.typeExports) { + typeExportModuleMap[curr.get()] = ExportInfo{inputFileName, curr->name}; + } } else { // This is a later module: do a full merge. mergeInto(*currModule, inputFileName); @@ -735,6 +953,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(); { @@ -749,6 +968,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/tools/wasm-metadce.cpp b/src/tools/wasm-metadce.cpp index 41dcf6ad4c5..75e172d637a 100644 --- a/src/tools/wasm-metadce.cpp +++ b/src/tools/wasm-metadce.cpp @@ -299,6 +299,17 @@ struct MetaDCEGraph { void apply() { // Remove the unused exports std::vector toRemove; + for (auto& exp : wasm.typeExports) { + auto name = exp->name; + auto dceName = exportToDCENode[name]; + if (reached.find(dceName) == reached.end()) { + toRemove.push_back(name); + } + } + for (auto name : toRemove) { + wasm.removeTypeExport(name); + } + toRemove.clear(); for (auto& exp : wasm.exports) { auto name = exp->name; auto dceName = exportToDCENode[name]; diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 9765afd0cbe..395599aaee9 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -398,6 +398,7 @@ extern const char* SharedEverythingFeature; extern const char* FP16Feature; extern const char* BulkMemoryOptFeature; extern const char* CallIndirectOverlongFeature; +extern const char* TypeImportsFeature; enum Subsection { NameModule = 0, @@ -1449,6 +1450,7 @@ class WasmBinaryReader { size_t codeSectionLocation; std::unordered_set seenSections; + TypeBuilder typebuilder; IRBuilder builder; SourceMapReader sourceMapReader; @@ -1516,6 +1518,7 @@ class WasmBinaryReader { Name getMemoryName(Index index); Name getGlobalName(Index index); Name getTagName(Index index); + Name getTypeName(Index index); Name getDataName(Index index); Name getElemName(Index index); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 0e28f3d5a66..6389ce9af83 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -150,6 +150,14 @@ class Builder { return export_; } + static std::unique_ptr makeTypeExport(Name name, + HeapType heaptype) { + auto export_ = std::make_unique(); + export_->name = name; + export_->heaptype = heaptype; + return export_; + } + enum Mutability { Mutable, Immutable }; static std::unique_ptr diff --git a/src/wasm-features.h b/src/wasm-features.h index 7ada02e9979..1d3ac394fec 100644 --- a/src/wasm-features.h +++ b/src/wasm-features.h @@ -54,11 +54,12 @@ struct FeatureSet { // that we can automatically generate tool flags that set it, but otherwise // it does nothing. Binaryen always accepts LEB call-indirect encodings. CallIndirectOverlong = 1 << 20, + TypeImports = 1 << 21, 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 << 21) - 1, + All = (1 << 22) - 1, }; static std::string toString(Feature f) { @@ -105,6 +106,8 @@ struct FeatureSet { return "bulk-memory-opt"; case CallIndirectOverlong: return "call-indirect-overlong"; + case TypeImports: + return "type-imports"; default: WASM_UNREACHABLE("unexpected feature"); } @@ -159,6 +162,7 @@ struct FeatureSet { assert(has || !hasBulkMemory()); return has; } + bool hasTypeImports() const { return (features & TypeImports) != 0; } bool hasAll() const { return (features & All) != 0; } void set(FeatureSet f, bool v = true) { @@ -185,6 +189,7 @@ struct FeatureSet { void setFP16(bool v = true) { set(FP16, v); } void setBulkMemoryOpt(bool v = true) { set(BulkMemoryOpt, 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-traversal.h b/src/wasm-traversal.h index db9d1c1c4f9..eab795c259f 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -46,6 +46,7 @@ template struct Visitor { // Module-level visitors ReturnType visitExport(Export* curr) { return ReturnType(); } + ReturnType visitTypeExport(TypeExport* curr) { return ReturnType(); } ReturnType visitGlobal(Global* curr) { return ReturnType(); } ReturnType visitFunction(Function* curr) { return ReturnType(); } ReturnType visitTable(Table* curr) { return ReturnType(); } @@ -208,6 +209,9 @@ struct Walker : public VisitorType { for (auto& curr : module->exports) { self->visitExport(curr.get()); } + for (auto& curr : module->typeExports) { + self->visitTypeExport(curr.get()); + } for (auto& curr : module->globals) { if (curr->imported()) { self->visitGlobal(curr.get()); 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 9e3419190d3..48b7d96ad50 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -53,6 +53,7 @@ class HeapType; class RecGroup; struct Signature; struct Continuation; +struct Import; struct Field; struct Struct; struct Array; @@ -86,6 +87,7 @@ enum class HeapTypeKind { Struct, Array, Cont, + Import, }; class HeapType { @@ -164,6 +166,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; } @@ -181,6 +184,7 @@ class HeapType { const Struct& getStruct() const; Array getArray() const; + Import 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. @@ -587,6 +591,17 @@ struct Continuation { std::string toString() const; }; +struct Import { + Name module, base; + HeapType bound; + Import(Name module, Name base, HeapType bound) + : module(module), base(base), bound(bound) {} + bool operator==(const Import& other) const { + return module == other.module && base == other.base && bound == other.bound; + } + std::string toString() const; +}; + struct Field { Type type; enum PackedType { @@ -685,6 +700,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, Import 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 @@ -742,6 +758,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"); } @@ -779,6 +798,8 @@ struct TypeBuilder { ForwardChildReference, // A continuation reference that does not refer to a function type. InvalidFuncType, + // A type import has an invalid bound. + InvalidBoundType, // A non-shared field of a shared heap type. InvalidUnsharedField, }; @@ -833,6 +854,10 @@ struct TypeBuilder { builder.setHeapType(index, array); return *this; } + Entry& operator=(Import import) { + builder.setHeapType(index, import); + return *this; + } Entry& subTypeOf(std::optional other) { builder.setSubType(index, other); return *this; @@ -883,6 +908,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&, Import); 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 7458a3d14e3..0123702bce6 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 }; @@ -2262,6 +2263,14 @@ class Export { ExternalKind kind; }; +class TypeExport { +public: + // exported name - note that this is the key, as the heap type is + // non-unique (can have multiple exports for a same heap type) + Name name; + HeapType heaptype; // exported type +}; + class ElementSegment : public Named { public: Name table; @@ -2376,6 +2385,7 @@ class Module { // wasm contents (generally you shouldn't access these from outside, except // maybe for iterating; use add*() and the get() functions) std::vector> exports; + std::vector> typeExports; std::vector> functions; std::vector> globals; std::vector> tags; @@ -2415,6 +2425,7 @@ class Module { // methods are not needed // exports map is by the *exported* name, which is unique std::unordered_map exportsMap; + std::unordered_map typeExportsMap; std::unordered_map functionsMap; std::unordered_map tablesMap; std::unordered_map memoriesMap; @@ -2427,6 +2438,7 @@ class Module { Module() = default; Export* getExport(Name name); + TypeExport* getTypeExport(Name name); Function* getFunction(Name name); Table* getTable(Name name); ElementSegment* getElementSegment(Name name); @@ -2436,6 +2448,7 @@ class Module { Tag* getTag(Name name); Export* getExportOrNull(Name name); + TypeExport* getTypeExportOrNull(Name name); Table* getTableOrNull(Name name); Memory* getMemoryOrNull(Name name); ElementSegment* getElementSegmentOrNull(Name name); @@ -2452,11 +2465,13 @@ class Module { Importable* getImportOrNull(ModuleItemKind kind, Name name); Export* addExport(Export* curr); + TypeExport* addTypeExport(TypeExport* curr); Function* addFunction(Function* curr); Global* addGlobal(Global* curr); Tag* addTag(Tag* curr); Export* addExport(std::unique_ptr&& curr); + TypeExport* addTypeExport(std::unique_ptr&& curr); Function* addFunction(std::unique_ptr&& curr); Table* addTable(std::unique_ptr&& curr); ElementSegment* addElementSegment(std::unique_ptr&& curr); @@ -2468,6 +2483,7 @@ class Module { void addStart(const Name& s); void removeExport(Name name); + void removeTypeExport(Name name); void removeFunction(Name name); void removeTable(Name name); void removeElementSegment(Name name); @@ -2477,6 +2493,7 @@ class Module { void removeTag(Name name); void removeExports(std::function pred); + void removeTypeExports(std::function pred); void removeFunctions(std::function pred); void removeTables(std::function pred); void removeElementSegments(std::function pred); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index f9d9225bbc7..55cacfc1174 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()) { + Import 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"); } @@ -597,7 +627,12 @@ void WasmBinaryWriter::writeExports() { return; } auto start = startSection(BinaryConsts::Section::Export); - o << U32LEB(wasm->exports.size()); + o << U32LEB(wasm->typeExports.size() + wasm->exports.size()); + for (auto& curr : wasm->typeExports) { + writeInlineString(curr->name.str); + o << U32LEB(int32_t(ExternalKind::Type)); + writeHeapType(curr->heaptype); + } for (auto& curr : wasm->exports) { writeInlineString(curr->name.str); o << U32LEB(int32_t(curr->kind)); @@ -1358,6 +1393,8 @@ void WasmBinaryWriter::writeFeaturesSection() { return BinaryConsts::CustomSections::BulkMemoryOptFeature; case FeatureSet::CallIndirectOverlong: return BinaryConsts::CustomSections::CallIndirectOverlongFeature; + case FeatureSet::TypeImports: + return BinaryConsts::CustomSections::TypeImportsFeature; case FeatureSet::None: case FeatureSet::Default: case FeatureSet::All: @@ -1826,7 +1863,10 @@ 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. if (sectionCode != BinaryConsts::Section::Custom && + sectionCode != BinaryConsts::Section::Import && !seenSections.insert(sectionCode).second) { throwError("section seen more than once: " + std::to_string(sectionCode)); } @@ -2311,7 +2351,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 @@ -2324,10 +2365,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 makeType = [&](int32_t typeCode) { Type type; @@ -2347,7 +2388,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)); @@ -2367,8 +2408,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 = [&]() { @@ -2417,7 +2458,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(); @@ -2427,15 +2468,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) { @@ -2448,30 +2489,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; } @@ -2715,6 +2756,26 @@ void WasmBinaryReader::readImports() { wasm.addTag(std::move(curr)); break; } + case ExternalKind::Type: { + if (seenSections.count(BinaryConsts::Section::Type)) { + throwError("type import after type section"); + } + getInt8(); // Reserved 'kind' field + 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] = + Import(module, base, ht.getBasic(share)); + break; + } default: { throwError("bad import kind"); } @@ -4337,34 +4398,44 @@ void WasmBinaryReader::readExports() { size_t num = getU32LEB(); std::unordered_set names; for (size_t i = 0; i < num; i++) { - auto curr = std::make_unique(); - curr->name = getInlineString(); - if (!names.emplace(curr->name).second) { + Name name = getInlineString(); + if (!names.emplace(name).second) { throwError("duplicate export name"); } - curr->kind = (ExternalKind)getU32LEB(); - auto* ex = wasm.addExport(std::move(curr)); - auto index = getU32LEB(); - switch (ex->kind) { - case ExternalKind::Function: - ex->value = getFunctionName(index); - continue; - case ExternalKind::Table: - ex->value = getTableName(index); - continue; - case ExternalKind::Memory: - ex->value = getMemoryName(index); - continue; - case ExternalKind::Global: - ex->value = getGlobalName(index); - continue; - case ExternalKind::Tag: - ex->value = getTagName(index); - continue; - case ExternalKind::Invalid: - break; + ExternalKind kind = (ExternalKind)getU32LEB(); + if (kind == ExternalKind::Type) { + auto curr = std::make_unique(); + curr->name = name; + auto* ex = wasm.addTypeExport(std::move(curr)); + ex->heaptype = getHeapType(); + } else { + auto curr = std::make_unique(); + curr->name = name; + curr->kind = kind; + auto* ex = wasm.addExport(std::move(curr)); + auto index = getU32LEB(); + switch (ex->kind) { + case ExternalKind::Function: + ex->value = getFunctionName(index); + continue; + case ExternalKind::Table: + ex->value = getTableName(index); + continue; + case ExternalKind::Memory: + ex->value = getMemoryName(index); + continue; + case ExternalKind::Global: + ex->value = getGlobalName(index); + continue; + case ExternalKind::Tag: + ex->value = getTagName(index); + continue; + case ExternalKind::Type: + case ExternalKind::Invalid: + break; + } + throwError("invalid export kind"); } - throwError("invalid export kind"); } } @@ -4930,6 +5001,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..e26d90aa3c3 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,8 @@ template struct RecGroupComparator { return compare(a.type, b.type); } + Comparison compare(Import a, Import b) { 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 +246,11 @@ struct RecGroupHasher { wasm::rehash(digest, 2381496927); hash_combine(digest, hash(type.getContinuation())); return digest; + case HeapTypeKind::Import: + assert(type.isContinuation()); + wasm::rehash(digest, 1077427175); + hash_combine(digest, hash(type.getImport())); + return digest; case HeapTypeKind::Basic: break; } @@ -266,6 +275,8 @@ struct RecGroupHasher { size_t hash(Continuation cont) { return hash(cont.type); } + size_t hash(Import import) { return hash(import.bound); } + 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 8710a0619d5..97c8c73d648 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; + Import import; }; HeapTypeInfo(Signature sig) : kind(HeapTypeKind::Func), signature(sig) {} @@ -72,12 +75,14 @@ struct HeapTypeInfo { HeapTypeInfo(Struct&& struct_) : kind(HeapTypeKind::Struct), struct_(std::move(struct_)) {} HeapTypeInfo(Array array) : kind(HeapTypeKind::Array), array(array) {} + HeapTypeInfo(Import 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 +130,7 @@ struct TypePrinter { std::ostream& print(const Struct& struct_, const std::unordered_map& fieldNames); std::ostream& print(const Array& array); + std::ostream& print(const Import& import); }; struct RecGroupHasher { @@ -150,6 +156,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 Import& import) const; }; struct RecGroupEquator { @@ -176,6 +183,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 Import& a, const Import& b) const; }; // A wrapper around a RecGroup that provides equality and hashing based on the @@ -291,6 +299,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 +386,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 +477,9 @@ HeapTypeInfo::~HeapTypeInfo() { case HeapTypeKind::Array: array.~Array(); return; + case HeapTypeKind::Import: + import.~Import(); + return; case HeapTypeKind::Basic: break; } @@ -902,6 +918,11 @@ Array HeapType::getArray() const { return getHeapTypeInfo(*this)->array; } +Import HeapType::getImport() const { + assert(isImport()); + return getHeapTypeInfo(*this)->import; +} + std::optional HeapType::getDeclaredSuperType() const { if (isBasic()) { return {}; @@ -955,6 +976,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; } @@ -1001,6 +1024,7 @@ size_t HeapType::getDepth() const { break; case HeapTypeKind::Func: case HeapTypeKind::Cont: + case HeapTypeKind::Import: ++depth; break; case HeapTypeKind::Struct: @@ -1053,6 +1077,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; } @@ -1119,6 +1145,8 @@ std::vector HeapType::getTypeChildren() const { return {getArray().element.type}; case HeapTypeKind::Cont: return {}; + case HeapTypeKind::Import: + return {}; } WASM_UNREACHABLE("unexpected kind"); } @@ -1282,6 +1310,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 @@ -1340,6 +1370,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"); } @@ -1359,6 +1392,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 Import::toString() const { return genericToString(*this); } std::ostream& operator<<(std::ostream& os, Type type) { return TypePrinter(os).print(type); @@ -1390,6 +1424,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, Import import) { + return TypePrinter(os).print(import); +} std::ostream& operator<<(std::ostream& os, TypeBuilder::ErrorReason reason) { switch (reason) { case TypeBuilder::ErrorReason::SelfSupertype: @@ -1402,6 +1439,8 @@ 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::InvalidUnsharedField: return os << "Heap type has an invalid unshared field"; } @@ -1452,6 +1491,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; @@ -1692,6 +1734,21 @@ std::ostream& TypePrinter::print(HeapType type) { auto names = generator(type); + if (type.isImport()) { + Import 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) << ' '; @@ -1728,6 +1785,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"); } @@ -1821,6 +1881,12 @@ std::ostream& TypePrinter::print(const Array& array) { return os << ')'; } +std::ostream& TypePrinter::print(const Import& import) { + os << "(sub "; + printHeapTypeName(import.bound); + return os << ')'; +} + size_t RecGroupHasher::operator()() const { size_t digest = wasm::hash(group.size()); for (auto type : group) { @@ -1895,6 +1961,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; } @@ -1942,6 +2011,13 @@ size_t RecGroupHasher::hash(const Array& array) const { return hash(array.element); } +size_t RecGroupHasher::hash(const Import& 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; @@ -2027,6 +2103,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; } @@ -2065,6 +2143,10 @@ bool RecGroupEquator::eq(const Array& a, const Array& b) const { return eq(a.element, b.element); } +bool RecGroupEquator::eq(const Import& a, const Import& b) const { + return a.module == b.module && a.base == b.base && eq(a.bound, b.bound); +} + } // anonymous namespace struct TypeBuilder::Impl { @@ -2101,6 +2183,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"); } @@ -2155,6 +2240,11 @@ void TypeBuilder::setHeapType(size_t i, Array array) { impl->entries[i].set(array); } +void TypeBuilder::setHeapType(size_t i, Import 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(); @@ -2225,6 +2315,7 @@ 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: case HeapTypeKind::Basic: break; } @@ -2273,6 +2364,11 @@ validateType(HeapTypeInfo& info, std::unordered_set& seenTypes) { } break; } + case HeapTypeKind::Import: + if (!info.import.bound.isShared()) { + return TypeBuilder::ErrorReason::InvalidBoundType; + } + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 3f67906f105..18be83fc6cd 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3958,6 +3958,17 @@ static void validateExports(Module& module, ValidationInfo& info) { } } std::unordered_set exportNames; + for (auto& exp : module.typeExports) { + info.shouldBeTrue(module.features.hasTypeImports(), + exp->name, + "Exported type requires type-imports " + "[--enable-type-imports]"); + Name exportName = exp->name; + info.shouldBeFalse(exportNames.count(exportName) > 0, + exportName, + "module exports must be unique"); + exportNames.insert(exportName); + } for (auto& exp : module.exports) { Name name = exp->value; if (exp->kind == ExternalKind::Function) { @@ -4292,6 +4303,8 @@ static void validateModuleMaps(Module& module, ValidationInfo& info) { // Module maps should be up to date. validateModuleMap( module, info, module.exports, &Module::getExportOrNull, "Export"); + validateModuleMap( + module, info, module.typeExports, &Module::getTypeExportOrNull, "Export"); validateModuleMap( module, info, module.functions, &Module::getFunctionOrNull, "Function"); validateModuleMap( diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index c8a82311c30..6b8d69755b9 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -59,6 +59,7 @@ const char* SharedEverythingFeature = "shared-everything"; const char* FP16Feature = "fp16"; const char* BulkMemoryOptFeature = "bulk-memory-opt"; const char* CallIndirectOverlongFeature = "call-indirect-overlong"; +const char* TypeImportsFeature = "type-imports"; } // namespace CustomSections } // namespace BinaryConsts @@ -1540,6 +1541,10 @@ Export* Module::getExport(Name name) { return getModuleElement(exportsMap, name, "getExport"); } +TypeExport* Module::getTypeExport(Name name) { + return getModuleElement(typeExportsMap, name, "getTypeExport"); +} + Function* Module::getFunction(Name name) { return getModuleElement(functionsMap, name, "getFunction"); } @@ -1581,6 +1586,10 @@ Export* Module::getExportOrNull(Name name) { return getModuleElementOrNull(exportsMap, name); } +TypeExport* Module::getTypeExportOrNull(Name name) { + return getModuleElementOrNull(typeExportsMap, name); +} + Function* Module::getFunctionOrNull(Name name) { return getModuleElementOrNull(functionsMap, name); } @@ -1692,6 +1701,10 @@ Export* Module::addExport(Export* curr) { return addModuleElement(exports, exportsMap, curr, "addExport"); } +TypeExport* Module::addTypeExport(TypeExport* curr) { + return addModuleElement(typeExports, typeExportsMap, curr, "addTypeExport"); +} + Function* Module::addFunction(Function* curr) { return addModuleElement(functions, functionsMap, curr, "addFunction"); } @@ -1708,6 +1721,11 @@ Export* Module::addExport(std::unique_ptr&& curr) { return addModuleElement(exports, exportsMap, std::move(curr), "addExport"); } +TypeExport* Module::addTypeExport(std::unique_ptr&& curr) { + return addModuleElement( + typeExports, typeExportsMap, std::move(curr), "addTypeExport"); +} + Function* Module::addFunction(std::unique_ptr&& curr) { return addModuleElement( functions, functionsMap, std::move(curr), "addFunction"); @@ -1756,6 +1774,9 @@ void removeModuleElement(Vector& v, Map& m, Name name) { void Module::removeExport(Name name) { removeModuleElement(exports, exportsMap, name); } +void Module::removeTypeExport(Name name) { + removeModuleElement(typeExports, typeExportsMap, name); +} void Module::removeFunction(Name name) { removeModuleElement(functions, functionsMap, name); } @@ -1795,6 +1816,9 @@ void removeModuleElements(Vector& v, void Module::removeExports(std::function pred) { removeModuleElements(exports, exportsMap, pred); } +void Module::removeTypeExports(std::function pred) { + removeModuleElements(typeExports, typeExportsMap, pred); +} void Module::removeFunctions(std::function pred) { removeModuleElements(functions, functionsMap, pred); } diff --git a/src/wasm2js.h b/src/wasm2js.h index 9a4416068c5..21718f28754 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"; } From 48cc48e06a64997fe97749b9368af4f4a8412d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Wed, 26 Feb 2025 00:58:20 +0100 Subject: [PATCH 02/18] Add tests --- scripts/test/fuzzing.py | 5 +- test/lit/basic/type-imports.wast | 130 ++++++++++++++++++++++++ test/lit/merge/type-imports.wat | 133 +++++++++++++++++++++++++ test/lit/merge/type-imports.wat.second | 7 ++ 4 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 test/lit/basic/type-imports.wast create mode 100644 test/lit/merge/type-imports.wat create mode 100644 test/lit/merge/type-imports.wat.second diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 270a9cf6b80..3602a7334da 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -100,7 +100,10 @@ 'stack_switching_suspend.wast', 'stack_switching_resume.wast', 'stack_switching_resume_throw.wast', - 'stack_switching_switch.wast' + 'stack_switching_switch.wast', + # TODO: fuzzer support for type imports + 'type-imports.wast', + 'type-imports.wat' ] diff --git a/test/lit/basic/type-imports.wast b/test/lit/basic/type-imports.wast new file mode 100644 index 00000000000..b705e96b26c --- /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 synyax 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 + (func (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 + (func (export "f2") (param $x (ref $t3)) (result (ref struct)) + (local.get $x) + ) + + ;; Reexport an imported type + ;; CHECK: (export "t3" (type $t3)) + + ;; CHECK: (export "t5" (type $t5)) + + ;; CHECK: (export "t2" (type $t2)) + (export "t2" (type $t2)) + + ;; Export a type defined in this module + ;; CHECK: (export "t4" (type $t4)) + (export "t4" (type $t4)) + + ;; Export an abstract heap type + (export "t6" (type extern)) +) +;; CHECK: (export "f1" (func $0)) + +;; CHECK: (export "f2" (func $1)) + +;; CHECK: (func $0 (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: ) + +;; CHECK: (func $1 (type $7) (param $x (ref $t3)) (result (ref struct)) +;; CHECK-NEXT: (local.get $x) +;; CHECK-NEXT: ) + +;; 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 "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: (export "f1" (func $0)) + +;; CHECK-BIN-NODEBUG: (export "f2" (func $1)) + +;; 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/merge/type-imports.wat b/test/lit/merge/type-imports.wat new file mode 100644 index 00000000000..2c282a9bb40 --- /dev/null +++ b/test/lit/merge/type-imports.wat @@ -0,0 +1,133 @@ +;; 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 + (func (export "f") (param (ref $t1) (ref $t2) (ref $t3) (ref $t4)) + ) + + ;; Check that types in instructions are also updated + (func (export "g1") (param $x (ref any)) (result (ref $t1)) + (ref.cast (ref $t1) + (local.get $x) + ) + ) + + (func (export "g2") (param $x (ref any)) (result (ref $t2)) + (ref.cast (ref $t2) + (local.get $x) + ) + ) + + (func (export "g3") (param $x (ref any)) (result (ref $t3)) + (ref.cast (ref $t3) + (local.get $x) + ) + ) + + (func (export "g4") (param $x (ref any)) (result (ref $t4)) + (ref.cast (ref $t4) + (local.get $x) + ) + ) + + ;; Check that recursive types are preserved + (rec + ;; CHECK: (type $t2 (array i8)) + + ;; CHECK: (rec + ;; CHECK-NEXT: (type $r1 (struct (field (ref $r1)) (field (ref $r2)) (field (ref eq)))) + (type $r1 (struct (field (ref $r1) (ref $r2) (ref $t1)))) + ;; CHECK: (type $r2 (struct (field (ref $r1)) (field (ref $r2)) (field (ref $t2)))) + (type $r2 (struct (field (ref $r1) (ref $r2) (ref $t2)))) + ) + + (func (export "h") (param $x (ref eq)) (result (ref $r1)) + (ref.cast (ref $r1) (local.get $x))) +) + +;; 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 "t2" (type $t2)) + +;; CHECK: (export "t3" (type $t3)) + +;; CHECK: (export "f" (func $0)) + +;; CHECK: (export "g1" (func $1)) + +;; CHECK: (export "g2" (func $2)) + +;; CHECK: (export "g3" (func $3)) + +;; CHECK: (export "g4" (func $4)) + +;; CHECK: (export "h" (func $5)) + +;; CHECK: (export "g" (func $g_7)) + +;; CHECK: (func $0 (type $5) (param $0 (ref eq)) (param $1 (ref $t2)) (param $2 (ref $t3)) (param $3 (ref $t4)) +;; CHECK-NEXT: ) + +;; CHECK: (func $1 (type $6) (param $x (ref any)) (result (ref eq)) +;; CHECK-NEXT: (ref.cast (ref eq) +;; CHECK-NEXT: (local.get $x) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $2 (type $7) (param $x (ref any)) (result (ref $t2)) +;; CHECK-NEXT: (ref.cast (ref $t2) +;; CHECK-NEXT: (local.get $x) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $3 (type $8) (param $x (ref any)) (result (ref $t3)) +;; CHECK-NEXT: (ref.cast (ref $t3) +;; CHECK-NEXT: (local.get $x) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $4 (type $9) (param $x (ref any)) (result (ref $t4)) +;; CHECK-NEXT: (ref.cast (ref $t4) +;; CHECK-NEXT: (local.get $x) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $5 (type $10) (param $x (ref eq)) (result (ref $r1)) +;; CHECK-NEXT: (ref.cast (ref $r1) +;; CHECK-NEXT: (local.get $x) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; 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))) +) From 9e527ef5b4d1dc2b26ddd48c6e7f12315fe58e9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Wed, 26 Feb 2025 18:21:57 +0100 Subject: [PATCH 03/18] Test updates --- test/example/c-api-kitchen-sink.txt | 2 +- test/lit/help/wasm-merge.test | 2 ++ test/lit/help/wasm-metadce.test | 2 ++ test/lit/help/wasm-opt.test | 2 ++ test/lit/help/wasm2js.test | 2 ++ ...ip-target-features_roundtrip_print-features_all-features.txt | 1 + 6 files changed, 10 insertions(+), 1 deletion(-) diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 2e319129e13..0f9ee08a0f7 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: 2097151 +BinaryenFeatureAll: 4194303 (f32.neg (f32.const -33.61199951171875) ) diff --git a/test/lit/help/wasm-merge.test b/test/lit/help/wasm-merge.test index 0e3b5f8b166..a9b874316eb 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: diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 5d01bc62c43..182126e5267 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: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index cc80f52c037..893ceae42c4 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: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 6e9c7732a3a..3e753ab1451 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: 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 2cd2573f10b..53d2bd25b6d 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 @@ -19,6 +19,7 @@ --enable-fp16 --enable-bulk-memory-opt --enable-call-indirect-overlong +--enable-type-imports (module (type $0 (func (result v128 externref))) (func $foo (type $0) (result v128 externref) From 7e56d353edc2e750fcac00d0a5b9bd0ea2d21307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Wed, 26 Feb 2025 19:43:00 +0100 Subject: [PATCH 04/18] More test updates --- src/wasm/wasm-validator.cpp | 6 ++++++ test/binaryen.js/kitchen-sink.js.txt | 2 +- test/lit/help/wasm-as.test | 4 ++++ test/lit/help/wasm-ctor-eval.test | 4 ++++ test/lit/help/wasm-dis.test | 4 ++++ test/lit/help/wasm-emscripten-finalize.test | 4 ++++ test/lit/help/wasm-merge.test | 4 ++++ test/lit/help/wasm-metadce.test | 4 ++++ test/lit/help/wasm-opt.test | 4 ++++ test/lit/help/wasm-reduce.test | 4 ++++ test/lit/help/wasm-split.test | 4 ++++ test/lit/help/wasm2js.test | 4 ++++ test/unit/test_features.py | 22 +++++++++++++++++++++ 13 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 18be83fc6cd..38dcd397b09 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -3963,6 +3963,12 @@ static void validateExports(Module& module, ValidationInfo& info) { exp->name, "Exported type requires type-imports " "[--enable-type-imports]"); + auto feats = exp->heaptype.getFeatures(); + if (!info.shouldBeTrue(feats <= module.features, + exp->name, + "Export type requires additional features")) { + info.getStream(nullptr) << getMissingFeaturesList(module, feats) << '\n'; + } Name exportName = exp->name; info.shouldBeFalse(exportNames.count(exportName) > 0, exportName, diff --git a/test/binaryen.js/kitchen-sink.js.txt b/test/binaryen.js/kitchen-sink.js.txt index 16e19a6a0c9..711972ad48b 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: 2097151 +Features.All: 4194303 InvalidId: 0 BlockId: 1 IfId: 2 diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index 63f4f03adf8..b16c0e7839b 100644 --- a/test/lit/help/wasm-as.test +++ b/test/lit/help/wasm-as.test @@ -128,6 +128,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; 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 2960ddb9ae0..62b954fb114 100644 --- a/test/lit/help/wasm-ctor-eval.test +++ b/test/lit/help/wasm-ctor-eval.test @@ -135,6 +135,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; 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 1efe7ffd82b..161b0f8aa70 100644 --- a/test/lit/help/wasm-dis.test +++ b/test/lit/help/wasm-dis.test @@ -121,6 +121,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; 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 1ed447b16b1..1507139c04b 100644 --- a/test/lit/help/wasm-emscripten-finalize.test +++ b/test/lit/help/wasm-emscripten-finalize.test @@ -163,6 +163,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; 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 a9b874316eb..730c32882ea 100644 --- a/test/lit/help/wasm-merge.test +++ b/test/lit/help/wasm-merge.test @@ -153,6 +153,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; 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 182126e5267..102d4af2f10 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -777,6 +777,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; 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 893ceae42c4..dde3ad6f325 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -789,6 +789,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; 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 9cd47473841..5bdd10f784b 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -160,6 +160,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; 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 1e4267dc26f..c9a1258dc1a 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -260,6 +260,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; 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 3e753ab1451..5f4f1dd7fc2 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -740,6 +740,10 @@ ;; CHECK-NEXT: ;; CHECK-NEXT: --disable-fp16 Disable float 16 operations ;; 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/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()) From df600790728f55c646bce3fb57c8a651dec57aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 27 Feb 2025 16:22:45 +0100 Subject: [PATCH 05/18] Fix hashing and comparison --- src/passes/TypeMerging.cpp | 14 ++++++++++++-- src/wasm/wasm-type-shape.cpp | 19 ++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index ff603c916be..d8a6724c232 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(Import a, Import b); bool shapeEq(Signature a, Signature b); bool shapeEq(Field a, Field b); bool shapeEq(Type a, Type b); @@ -574,7 +575,7 @@ bool shapeEq(HeapType a, HeapType b) { case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); case HeapTypeKind::Import: - return false; + return shapeEq(a.getImport(), b.getImport()); case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } @@ -641,7 +642,16 @@ size_t shapeHash(Signature a) { return digest; } -size_t shapeHash(Import a) { return shapeHash(a.bound); } +bool shapeEq(Import a, Import b) { + return a.module == b.module && a.base == b.base && shapeEq(a.bound, b.bound); +} + +size_t shapeHash(Import 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_ && diff --git a/src/wasm/wasm-type-shape.cpp b/src/wasm/wasm-type-shape.cpp index e26d90aa3c3..104991121c6 100644 --- a/src/wasm/wasm-type-shape.cpp +++ b/src/wasm/wasm-type-shape.cpp @@ -119,7 +119,15 @@ template struct RecGroupComparator { return compare(a.type, b.type); } - Comparison compare(Import a, Import b) { return compare(a.bound, b.bound); } + Comparison compare(Import a, Import 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_) { @@ -247,7 +255,7 @@ struct RecGroupHasher { hash_combine(digest, hash(type.getContinuation())); return digest; case HeapTypeKind::Import: - assert(type.isContinuation()); + assert(type.isImport()); wasm::rehash(digest, 1077427175); hash_combine(digest, hash(type.getImport())); return digest; @@ -275,7 +283,12 @@ struct RecGroupHasher { size_t hash(Continuation cont) { return hash(cont.type); } - size_t hash(Import import) { return hash(import.bound); } + size_t hash(Import 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_); From f098ced3adbc03334f8d898a4dea2db13dc6b882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 27 Feb 2025 16:28:09 +0100 Subject: [PATCH 06/18] Fuzzing updates --- src/tools/fuzzing/fuzzing.cpp | 11 ++++++++--- src/tools/fuzzing/heap-types.cpp | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 761aa9667d2..c5b6b8ea579 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -516,10 +516,14 @@ void TranslateToFuzzReader::setupHeapTypes() { break; case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); - case HeapTypeKind::Import: - interestingHeapSubTypes[type.getImport().bound].push_back(type); - // TODO: also the supertypes of the bound? + 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"); } @@ -3613,6 +3617,7 @@ 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 7be6f61954b..3d983ff38a3 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -944,6 +944,7 @@ std::vector Inhabitator::build() { case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); case HeapTypeKind::Import: + WASM_UNREACHABLE("TODO: type imports"); case HeapTypeKind::Basic: break; } From fce76ffd505816434f6df9df13bdf2e9a1b45b2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 27 Feb 2025 16:34:11 +0100 Subject: [PATCH 07/18] Validation fixes --- src/parser/parsers.h | 11 ++++------- src/wasm-type.h | 2 ++ src/wasm/wasm-type.cpp | 26 +++++++++++++++++++++----- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 908892fada3..b6d413ceda2 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -357,7 +357,7 @@ Result> inlineExports(Lexer&); template Result<> comptype(Ctx&); template Result<> sharecomptype(Ctx&); template Result<> subtype(Ctx&); -template MaybeResult<> typedef_(Ctx&, bool inRecGroup); +template MaybeResult<> typedef_(Ctx&); template MaybeResult<> rectype(Ctx&); template MaybeResult locals(Ctx&); template MaybeResult<> import_(Ctx&); @@ -2983,7 +2983,7 @@ template Result<> subtype(Ctx& ctx) { // typedef ::= '(' 'type' id? ('(' 'export' name ')')* subtype ')' // | '(' 'type' id? ('(' 'export' name ')')* // '(' 'import' mod:name nm:name ')' typetype ')' -template MaybeResult<> typedef_(Ctx& ctx, bool inRecGroup) { +template MaybeResult<> typedef_(Ctx& ctx) { auto pos = ctx.in.getPos(); if (!ctx.in.takeSExprStart("type"sv)) { @@ -3002,9 +3002,6 @@ template MaybeResult<> typedef_(Ctx& ctx, bool inRecGroup) { CHECK_ERR(import); if (import) { - if (inRecGroup) { - return ctx.in.err("type import not allowed in recursive group"); - } auto typePos = ctx.in.getPos(); auto type = typetype(ctx); CHECK_ERR(type); @@ -3030,7 +3027,7 @@ template MaybeResult<> rectype(Ctx& ctx) { if (ctx.in.takeSExprStart("rec"sv)) { size_t startIndex = ctx.getRecGroupStartIndex(); size_t groupLen = 0; - while (auto type = typedef_(ctx, true)) { + while (auto type = typedef_(ctx)) { CHECK_ERR(type); ++groupLen; } @@ -3038,7 +3035,7 @@ template MaybeResult<> rectype(Ctx& ctx) { return ctx.in.err("expected type definition or end of recursion group"); } ctx.addRecGroup(startIndex, groupLen); - } else if (auto type = typedef_(ctx, false)) { + } else if (auto type = typedef_(ctx)) { CHECK_ERR(type); } else { return {}; diff --git a/src/wasm-type.h b/src/wasm-type.h index 48b7d96ad50..09792af5c68 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -800,6 +800,8 @@ struct TypeBuilder { 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, }; diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 97c8c73d648..69c3733a453 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -1441,6 +1441,8 @@ std::ostream& operator<<(std::ostream& os, TypeBuilder::ErrorReason reason) { 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"; } @@ -2316,6 +2318,7 @@ bool isValidSupertype(const HeapTypeInfo& sub, const HeapTypeInfo& super) { case HeapTypeKind::Array: return typer.isSubType(sub.array, super.array); case HeapTypeKind::Import: + return false; case HeapTypeKind::Basic: break; } @@ -2323,7 +2326,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. @@ -2340,6 +2345,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: @@ -2365,9 +2378,8 @@ validateType(HeapTypeInfo& info, std::unordered_set& seenTypes) { break; } case HeapTypeKind::Import: - if (!info.import.bound.isShared()) { - return TypeBuilder::ErrorReason::InvalidBoundType; - } + // The sharedness is inherited (see buildRecGroup below). + assert(info.import.bound.isShared()); break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); @@ -2440,7 +2452,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)); From e3168071200e35abb2c8bc025c977aea2c0825e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 27 Feb 2025 17:58:13 +0100 Subject: [PATCH 08/18] Reuse the type-sorting code from ModuleUtils in wasm-merge --- src/ir/module-utils.cpp | 13 +++++-- src/ir/module-utils.h | 6 ++++ src/tools/wasm-merge.cpp | 75 +++++++--------------------------------- 3 files changed, 29 insertions(+), 65 deletions(-) diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 0f78baadf81..589bb5d0efd 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -737,9 +737,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; @@ -766,6 +766,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; } @@ -862,4 +863,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/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp index 506436f1cea..be3bc89739b 100644 --- a/src/tools/wasm-merge.cpp +++ b/src/tools/wasm-merge.cpp @@ -440,54 +440,6 @@ void checkLimit(bool& valid, const char* kind, T* export_, T* import) { } } -// Sort heap types to put children (rewritten by map) before heap type -std::vector sortHeapTypes(std::vector& types, - std::function map) { - // Collect the rec groups. - std::unordered_map groupIndices; - std::vector groups; - for (auto& type : types) { - auto group = type.getRecGroup(); - if (groupIndices.insert({group, groups.size()}).second) { - groups.push_back(group); - } - } - - // Collect the reverse dependencies of each group. - std::vector> depSets(groups.size()); - 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; - } - auto childGroup = child.getRecGroup(); - if (childGroup == groups[i]) { - continue; - } - depSets[groupIndices.at(childGroup)].insert(i); - } - } - } - TopologicalSort::Graph deps; - deps.reserve(groups.size()); - for (size_t i = 0; i < groups.size(); ++i) { - deps.emplace_back(depSets[i].begin(), depSets[i].end()); - } - - auto sorted = TopologicalSort::sort(deps); - - std::vector sortedTypes; - sortedTypes.reserve(types.size()); - for (auto groupIndex : sorted) { - for (auto type : groups[groupIndex]) { - sortedTypes.push_back(type); - } - } - return sortedTypes; -} - // 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). @@ -507,14 +459,15 @@ void fuseTypeImportsAndTypeExports() { moduleTypeExportMap[exportInfo.moduleName][exportInfo.baseName] = ex->heaptype; } + + 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; - std::vector heapTypes = ModuleUtils::collectHeapTypes(merged); - - for (HeapType& heapType : heapTypes) { + for (auto& [heapType, _] : heapTypeInfo) { if (heapType.isImport()) { Import import = heapType.getImport(); if (auto newType = moduleTypeExportMap[import.module].find(import.base); @@ -574,12 +527,10 @@ void fuseTypeImportsAndTypeExports() { }; // Sort heap types so that children are before - heapTypes = sortHeapTypes(heapTypes, initialMap); - std::unordered_map typeIndices; - TypeBuilder typeBuilder(heapTypes.size()); - for (size_t i = 0; i < heapTypes.size(); i++) { - typeIndices[heapTypes[i]] = i; - } + 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 { @@ -587,13 +538,13 @@ void fuseTypeImportsAndTypeExports() { if (type.isBasic()) { return type; } - return typeBuilder[typeIndices[type]]; + return typeBuilder[indexedTypes.indices[type]]; }; // Build new types std::optional lastGroup = std::nullopt; - for (size_t i = 0; i < heapTypes.size(); i++) { - HeapType heapType = heapTypes[i]; + 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) { @@ -606,11 +557,11 @@ void fuseTypeImportsAndTypeExports() { // Map old types to the new ones. GlobalTypeRewriter::TypeMap oldToNewTypes; - for (HeapType heapType : heapTypes) { + for (HeapType heapType : indexedTypes.types) { HeapType type = heapType; type = initialMap(type); if (!type.isBasic()) { - type = newTypes[typeIndices[type]]; + type = newTypes[indexedTypes.indices[type]]; } oldToNewTypes[heapType] = type; } From 982ab076ad6dca6a3e58a22d9db3a26e7f1b6dd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 27 Feb 2025 18:04:03 +0100 Subject: [PATCH 09/18] Comment updates --- src/parser/contexts.h | 2 +- src/wasm-binary.h | 1 - src/wasm/wasm-binary.cpp | 8 +++++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/parser/contexts.h b/src/parser/contexts.h index f87bd8bcfe2..a5d8dbe2e41 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -935,7 +935,7 @@ struct ParseDeclsCtx : NullTypeParserCtx, NullInstrParserCtx { std::vector dataDefs; std::vector tagDefs; - // Type imports: name, positions of type and import names. + // Type imports: name, export names, import names, and positions. std::vector, ImportNames, Index>> typeImports; diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 395599aaee9..4a107300932 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1518,7 +1518,6 @@ class WasmBinaryReader { Name getMemoryName(Index index); Name getGlobalName(Index index); Name getTagName(Index index); - Name getTypeName(Index index); Name getDataName(Index index); Name getElemName(Index index); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 55cacfc1174..8fa052d4404 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1865,6 +1865,9 @@ void WasmBinaryReader::read() { // 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) { @@ -2760,7 +2763,10 @@ void WasmBinaryReader::readImports() { if (seenSections.count(BinaryConsts::Section::Type)) { throwError("type import after type section"); } - getInt8(); // Reserved 'kind' field + 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) { From 6dfcdb973f4d00291574d0e28ee5e39ffcd8071c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 27 Feb 2025 20:16:56 +0100 Subject: [PATCH 10/18] Tests: name functions --- test/lit/basic/type-imports.wast | 50 +++++------ test/lit/merge/type-imports.wat | 148 +++++++++++++++---------------- 2 files changed, 97 insertions(+), 101 deletions(-) diff --git a/test/lit/basic/type-imports.wast b/test/lit/basic/type-imports.wast index b705e96b26c..40f5aac079c 100644 --- a/test/lit/basic/type-imports.wast +++ b/test/lit/basic/type-imports.wast @@ -19,7 +19,7 @@ ;; CHECK: (import "env" "t2" (type $t2 (sub any))) (import "env" "t2" (type $t2)) - ;; Alternative synyax with both import and export + ;; Alternative syntax with both import and export (type $t3 (export "t3") (import "env" "t3") (sub struct)) ;; Use an imported type in a type @@ -42,7 +42,26 @@ (import "env" "g" (func $g (param (ref $t1)) (result (ref $t2)))) ;; Cast and function call involving imported types - (func (export "f1") + ;; CHECK: (export "t3" (type $t3)) + + ;; CHECK: (export "t5" (type $t5)) + + ;; CHECK: (export "t2" (type $t2)) + + ;; CHECK: (export "t4" (type $t4)) + + ;; CHECK: (export "f1" (func $f1)) + + ;; CHECK: (export "f2" (func $f2)) + + ;; 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) @@ -52,41 +71,22 @@ ) ;; Check that the imported type is a subtype of its bound - (func (export "f2") (param $x (ref $t3)) (result (ref struct)) + ;; 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 - ;; CHECK: (export "t3" (type $t3)) - - ;; CHECK: (export "t5" (type $t5)) - - ;; CHECK: (export "t2" (type $t2)) (export "t2" (type $t2)) ;; Export a type defined in this module - ;; CHECK: (export "t4" (type $t4)) (export "t4" (type $t4)) ;; Export an abstract heap type (export "t6" (type extern)) ) -;; CHECK: (export "f1" (func $0)) - -;; CHECK: (export "f2" (func $1)) - -;; CHECK: (func $0 (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: ) - -;; CHECK: (func $1 (type $7) (param $x (ref $t3)) (result (ref struct)) -;; CHECK-NEXT: (local.get $x) -;; CHECK-NEXT: ) - ;; CHECK-BIN-NODEBUG: (import "env" "t1" (type $0 (sub eq))) ;; CHECK-BIN-NODEBUG: (import "env" "t2" (type $1 (sub any))) diff --git a/test/lit/merge/type-imports.wat b/test/lit/merge/type-imports.wat index 2c282a9bb40..61caf67a4c4 100644 --- a/test/lit/merge/type-imports.wat +++ b/test/lit/merge/type-imports.wat @@ -21,29 +21,90 @@ (import "second" "g" (func $g (param (ref $t2)))) ;; Check that the function parameters are updated - (func (export "f") (param (ref $t1) (ref $t2) (ref $t3) (ref $t4)) + ;; 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 "t2" (type $t2)) + + ;; CHECK: (export "t3" (type $t3)) + + ;; 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: (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 - (func (export "g1") (param $x (ref any)) (result (ref $t1)) + ;; 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) ) ) - (func (export "g2") (param $x (ref any)) (result (ref $t2)) + ;; 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) ) ) - (func (export "g3") (param $x (ref any)) (result (ref $t3)) + ;; 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) ) ) - (func (export "g4") (param $x (ref any)) (result (ref $t4)) + ;; 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) ) @@ -51,83 +112,18 @@ ;; Check that recursive types are preserved (rec - ;; CHECK: (type $t2 (array i8)) - - ;; CHECK: (rec - ;; CHECK-NEXT: (type $r1 (struct (field (ref $r1)) (field (ref $r2)) (field (ref eq)))) (type $r1 (struct (field (ref $r1) (ref $r2) (ref $t1)))) - ;; CHECK: (type $r2 (struct (field (ref $r1)) (field (ref $r2)) (field (ref $t2)))) (type $r2 (struct (field (ref $r1) (ref $r2) (ref $t2)))) ) - (func (export "h") (param $x (ref eq)) (result (ref $r1)) + ;; 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: (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 "t2" (type $t2)) - -;; CHECK: (export "t3" (type $t3)) - -;; CHECK: (export "f" (func $0)) - -;; CHECK: (export "g1" (func $1)) - -;; CHECK: (export "g2" (func $2)) - -;; CHECK: (export "g3" (func $3)) - -;; CHECK: (export "g4" (func $4)) - -;; CHECK: (export "h" (func $5)) - -;; CHECK: (export "g" (func $g_7)) - -;; CHECK: (func $0 (type $5) (param $0 (ref eq)) (param $1 (ref $t2)) (param $2 (ref $t3)) (param $3 (ref $t4)) -;; CHECK-NEXT: ) - -;; CHECK: (func $1 (type $6) (param $x (ref any)) (result (ref eq)) -;; CHECK-NEXT: (ref.cast (ref eq) -;; CHECK-NEXT: (local.get $x) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) - -;; CHECK: (func $2 (type $7) (param $x (ref any)) (result (ref $t2)) -;; CHECK-NEXT: (ref.cast (ref $t2) -;; CHECK-NEXT: (local.get $x) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) - -;; CHECK: (func $3 (type $8) (param $x (ref any)) (result (ref $t3)) -;; CHECK-NEXT: (ref.cast (ref $t3) -;; CHECK-NEXT: (local.get $x) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) - -;; CHECK: (func $4 (type $9) (param $x (ref any)) (result (ref $t4)) -;; CHECK-NEXT: (ref.cast (ref $t4) -;; CHECK-NEXT: (local.get $x) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) - -;; CHECK: (func $5 (type $10) (param $x (ref eq)) (result (ref $r1)) -;; CHECK-NEXT: (ref.cast (ref $r1) -;; CHECK-NEXT: (local.get $x) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) - ;; CHECK: (func $g_7 (type $11) (param $0 (ref $t2)) ;; CHECK-NEXT: ) From 22e093b73925fa41d3a97979ce8a35b1d75db430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 27 Feb 2025 20:27:49 +0100 Subject: [PATCH 11/18] Rename Import to TypeImport --- src/parser/parse-2-typedefs.cpp | 2 +- src/passes/TypeMerging.cpp | 8 ++++---- src/tools/wasm-merge.cpp | 2 +- src/wasm-type.h | 16 ++++++++-------- src/wasm/wasm-binary.cpp | 4 ++-- src/wasm/wasm-type-shape.cpp | 4 ++-- src/wasm/wasm-type.cpp | 29 +++++++++++++++-------------- 7 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/parser/parse-2-typedefs.cpp b/src/parser/parse-2-typedefs.cpp index 8ea11d6be53..c03f011e9d8 100644 --- a/src/parser/parse-2-typedefs.cpp +++ b/src/parser/parse-2-typedefs.cpp @@ -30,7 +30,7 @@ Result<> parseTypeDefs( WithPosition with(ctx, pos); auto heaptype = typetype(ctx); CHECK_ERR(heaptype); - builder[ctx.index] = Import(importNames.mod, importNames.nm, *heaptype); + builder[ctx.index] = TypeImport(importNames.mod, importNames.nm, *heaptype); ctx.typeExports[ctx.index] = exports; ctx.names[ctx.index++].name = name; } diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index d8a6724c232..95c257fd8a8 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -181,7 +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(Import a, Import 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); @@ -191,7 +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(Import a); +size_t shapeHash(TypeImport a); size_t shapeHash(Field a); size_t shapeHash(Type a); size_t shapeHash(const Tuple& a); @@ -642,11 +642,11 @@ size_t shapeHash(Signature a) { return digest; } -bool shapeEq(Import a, Import b) { +bool shapeEq(TypeImport a, TypeImport b) { return a.module == b.module && a.base == b.base && shapeEq(a.bound, b.bound); } -size_t shapeHash(Import a) { +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)); diff --git a/src/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp index be3bc89739b..16a366e760c 100644 --- a/src/tools/wasm-merge.cpp +++ b/src/tools/wasm-merge.cpp @@ -469,7 +469,7 @@ void fuseTypeImportsAndTypeExports() { std::unordered_map typeUpdates; for (auto& [heapType, _] : heapTypeInfo) { if (heapType.isImport()) { - Import import = heapType.getImport(); + 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. diff --git a/src/wasm-type.h b/src/wasm-type.h index 09792af5c68..84051d4f17f 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -53,7 +53,7 @@ class HeapType; class RecGroup; struct Signature; struct Continuation; -struct Import; +struct TypeImport; struct Field; struct Struct; struct Array; @@ -184,7 +184,7 @@ class HeapType { const Struct& getStruct() const; Array getArray() const; - Import getImport() 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. @@ -591,12 +591,12 @@ struct Continuation { std::string toString() const; }; -struct Import { +struct TypeImport { Name module, base; HeapType bound; - Import(Name module, Name base, HeapType bound) + TypeImport(Name module, Name base, HeapType bound) : module(module), base(base), bound(bound) {} - bool operator==(const Import& other) const { + bool operator==(const TypeImport& other) const { return module == other.module && base == other.base && bound == other.bound; } std::string toString() const; @@ -700,7 +700,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, Import import); + 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 @@ -856,7 +856,7 @@ struct TypeBuilder { builder.setHeapType(index, array); return *this; } - Entry& operator=(Import import) { + Entry& operator=(TypeImport import) { builder.setHeapType(index, import); return *this; } @@ -910,7 +910,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&, Import); +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/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 8fa052d4404..ebe1ca961f5 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -253,7 +253,7 @@ void WasmBinaryWriter::writeTypes() { for (Index i = 0; i < indexedTypes.types.size(); ++i) { auto type = indexedTypes.types[i]; if (type.isImport()) { - Import import = type.getImport(); + TypeImport import = type.getImport(); writeInlineString(import.module.str); writeInlineString(import.base.str); o << U32LEB(int32_t(ExternalKind::Type)); @@ -2779,7 +2779,7 @@ void WasmBinaryReader::readImports() { } typebuilder.grow(1); typebuilder[typebuilder.size() - 1] = - Import(module, base, ht.getBasic(share)); + TypeImport(module, base, ht.getBasic(share)); break; } default: { diff --git a/src/wasm/wasm-type-shape.cpp b/src/wasm/wasm-type-shape.cpp index 104991121c6..2b108ba39c9 100644 --- a/src/wasm/wasm-type-shape.cpp +++ b/src/wasm/wasm-type-shape.cpp @@ -119,7 +119,7 @@ template struct RecGroupComparator { return compare(a.type, b.type); } - Comparison compare(Import a, Import b) { + Comparison compare(TypeImport a, TypeImport b) { if (a.module != b.module) { return a.module < b.module ? LT : GT; } @@ -283,7 +283,7 @@ struct RecGroupHasher { size_t hash(Continuation cont) { return hash(cont.type); } - size_t hash(Import import) { + 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)); diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 69c3733a453..f66d23beb86 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -64,7 +64,7 @@ struct HeapTypeInfo { Continuation continuation; Struct struct_; Array array; - Import import; + TypeImport import; }; HeapTypeInfo(Signature sig) : kind(HeapTypeKind::Func), signature(sig) {} @@ -75,7 +75,8 @@ struct HeapTypeInfo { HeapTypeInfo(Struct&& struct_) : kind(HeapTypeKind::Struct), struct_(std::move(struct_)) {} HeapTypeInfo(Array array) : kind(HeapTypeKind::Array), array(array) {} - HeapTypeInfo(Import import) : kind(HeapTypeKind::Import), import(import) {} + HeapTypeInfo(TypeImport import) + : kind(HeapTypeKind::Import), import(import) {} ~HeapTypeInfo(); constexpr bool isSignature() const { return kind == HeapTypeKind::Func; } @@ -130,7 +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 Import& import); + std::ostream& print(const TypeImport& import); }; struct RecGroupHasher { @@ -156,7 +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 Import& import) const; + size_t hash(const TypeImport& import) const; }; struct RecGroupEquator { @@ -183,7 +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 Import& a, const Import& b) const; + bool eq(const TypeImport& a, const TypeImport& b) const; }; // A wrapper around a RecGroup that provides equality and hashing based on the @@ -478,7 +479,7 @@ HeapTypeInfo::~HeapTypeInfo() { array.~Array(); return; case HeapTypeKind::Import: - import.~Import(); + import.~TypeImport(); return; case HeapTypeKind::Basic: break; @@ -918,7 +919,7 @@ Array HeapType::getArray() const { return getHeapTypeInfo(*this)->array; } -Import HeapType::getImport() const { +TypeImport HeapType::getImport() const { assert(isImport()); return getHeapTypeInfo(*this)->import; } @@ -1392,7 +1393,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 Import::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); @@ -1424,7 +1425,7 @@ 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, Import import) { +std::ostream& operator<<(std::ostream& os, TypeImport import) { return TypePrinter(os).print(import); } std::ostream& operator<<(std::ostream& os, TypeBuilder::ErrorReason reason) { @@ -1737,7 +1738,7 @@ std::ostream& TypePrinter::print(HeapType type) { auto names = generator(type); if (type.isImport()) { - Import import = type.getImport(); + TypeImport import = type.getImport(); os << "("; printMedium(os, "import "); std::stringstream escapedModule, escapedBase; @@ -1883,7 +1884,7 @@ std::ostream& TypePrinter::print(const Array& array) { return os << ')'; } -std::ostream& TypePrinter::print(const Import& import) { +std::ostream& TypePrinter::print(const TypeImport& import) { os << "(sub "; printHeapTypeName(import.bound); return os << ')'; @@ -2013,7 +2014,7 @@ size_t RecGroupHasher::hash(const Array& array) const { return hash(array.element); } -size_t RecGroupHasher::hash(const Import& import) const { +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)); @@ -2145,7 +2146,7 @@ bool RecGroupEquator::eq(const Array& a, const Array& b) const { return eq(a.element, b.element); } -bool RecGroupEquator::eq(const Import& a, const Import& b) const { +bool RecGroupEquator::eq(const TypeImport& a, const TypeImport& b) const { return a.module == b.module && a.base == b.base && eq(a.bound, b.bound); } @@ -2242,7 +2243,7 @@ void TypeBuilder::setHeapType(size_t i, Array array) { impl->entries[i].set(array); } -void TypeBuilder::setHeapType(size_t i, Import import) { +void TypeBuilder::setHeapType(size_t i, TypeImport import) { assert(i < size() && "index out of bounds"); impl->entries[i].set(import); } From 60bd78944afc7b013f7d38db8092ac1bd190a88e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 27 Feb 2025 21:57:27 +0100 Subject: [PATCH 12/18] Unnecessary include --- src/tools/wasm-merge.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp index 16a366e760c..12e0e7dfc7a 100644 --- a/src/tools/wasm-merge.cpp +++ b/src/tools/wasm-merge.cpp @@ -97,7 +97,6 @@ #include "ir/type-updating.h" #include "support/colors.h" #include "support/file.h" -#include "support/topological_sort.h" #include "wasm-builder.h" #include "wasm-io.h" #include "wasm-validator.h" From bc7c3f8c68f1edee86fe5337a1e13cc6b3faeb77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 27 Feb 2025 21:57:57 +0100 Subject: [PATCH 13/18] Fix Subtypes::getMaxDepths --- src/ir/subtypes.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h index 60d5182e741..8a25c939396 100644 --- a/src/ir/subtypes.h +++ b/src/ir/subtypes.h @@ -128,6 +128,8 @@ struct SubTypes { case HeapTypeKind::Cont: WASM_UNREACHABLE("TODO: cont"); case HeapTypeKind::Import: + basic = type.getImport().bound; + break; case HeapTypeKind::Basic: WASM_UNREACHABLE("unexpected kind"); } From 7da566f8b6fab6b34fe0a7afeeba5c99f29d01cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 14 Mar 2025 00:27:17 +0100 Subject: [PATCH 14/18] fix --- src/wasm/wasm-binary.cpp | 53 +++++++++++++++------------- test/binaryen.js/kitchen-sink.js.txt | 2 +- test/example/c-api-kitchen-sink.txt | 2 +- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 4726706755d..9a13d7ee0f8 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -2429,7 +2429,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); @@ -4459,30 +4459,35 @@ void WasmBinaryReader::readExports() { throwError("duplicate export name"); } ExternalKind kind = (ExternalKind)getU32LEB(); - std::variant value; - switch (kind) { - case ExternalKind::Function: - value = getFunctionName(getU32LEB()); - break; - case ExternalKind::Table: - value = getTableName(getU32LEB()); - break; - case ExternalKind::Memory: - value = getMemoryName(getU32LEB()); - break; - case ExternalKind::Global: - value = getGlobalName(getU32LEB()); - break; - case ExternalKind::Tag: - value = getTagName(getU32LEB()); - break; - case ExternalKind::Type: - value = getHeapType(); - break; - case ExternalKind::Invalid: - throwError("invalid export kind"); + if (kind == ExternalKind::Type) { + auto curr = std::make_unique(); + curr->name = name; + auto* ex = wasm.addTypeExport(std::move(curr)); + ex->heaptype = getHeapType(); + } else { + std::variant value; + switch (kind) { + case ExternalKind::Function: + value = getFunctionName(getU32LEB()); + break; + case ExternalKind::Table: + value = getTableName(getU32LEB()); + break; + case ExternalKind::Memory: + value = getMemoryName(getU32LEB()); + break; + case ExternalKind::Global: + value = getGlobalName(getU32LEB()); + break; + case ExternalKind::Tag: + value = getTagName(getU32LEB()); + break; + case ExternalKind::Type: + case ExternalKind::Invalid: + throwError("invalid export kind"); + } + wasm.addExport(new Export(name, kind, value)); } - wasm.addExport(new Export(name, kind, value)); } } 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) ) From c989d278c226b8cb4c0dcf1acee7cbc4c39a809f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Thu, 27 Feb 2025 13:49:24 +0100 Subject: [PATCH 15/18] Put type exports with other exports --- src/binaryen-c.cpp | 50 ++++----------------- src/binaryen-c.h | 36 +++------------ src/ir/module-utils.cpp | 11 ++--- src/ir/names.h | 6 +-- src/ir/type-updating.cpp | 6 ++- src/parser/context-decls.cpp | 2 +- src/parser/contexts.h | 6 +-- src/parser/parse-2-typedefs.cpp | 7 ++- src/passes/MinifyImportsAndExports.cpp | 3 -- src/passes/Print.cpp | 24 +++------- src/passes/StripTypeExports.cpp | 3 +- src/tools/wasm-merge.cpp | 45 +++++-------------- src/tools/wasm-metadce.cpp | 11 ----- src/wasm-builder.h | 8 ---- src/wasm-traversal.h | 4 -- src/wasm.h | 19 +------- src/wasm/wasm-binary.cpp | 61 ++++++++++++-------------- src/wasm/wasm-validator.cpp | 31 +++++-------- src/wasm/wasm.cpp | 23 ---------- test/lit/basic/type-imports.wast | 16 +++---- test/lit/merge/type-imports.wat | 8 ++-- 21 files changed, 106 insertions(+), 274 deletions(-) diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index 045490ca15f..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); @@ -4959,37 +4966,6 @@ BinaryenExportRef BinaryenGetExportByIndex(BinaryenModuleRef module, return exports[index].get(); } -// Type Exports - -BinaryenTypeExportRef BinaryenAddTypeExport(BinaryenModuleRef module, - BinaryenHeapType type, - const char* externalName) { - auto* ret = new TypeExport(); - ret->heaptype = HeapType(type); - ret->name = externalName; - ((Module*)module)->addTypeExport(ret); - return ret; -} -BinaryenTypeExportRef BinaryenGetTypeExport(BinaryenModuleRef module, - const char* externalName) { - return ((Module*)module)->getTypeExportOrNull(externalName); -} -void BinaryenRemoveTypeExport(BinaryenModuleRef module, - const char* externalName) { - ((Module*)module)->removeTypeExport(externalName); -} -BinaryenIndex BinaryenGetNumTypeExports(BinaryenModuleRef module) { - return ((Module*)module)->typeExports.size(); -} -BinaryenTypeExportRef BinaryenGetTypeExportByIndex(BinaryenModuleRef module, - BinaryenIndex index) { - const auto& exports = ((Module*)module)->typeExports; - if (exports.size() <= index) { - Fatal() << "invalid export index."; - } - return exports[index].get(); -} - BinaryenTableRef BinaryenAddTable(BinaryenModuleRef module, const char* name, BinaryenIndex initial, @@ -5955,16 +5931,8 @@ const char* BinaryenExportGetName(BinaryenExportRef export_) { const char* BinaryenExportGetValue(BinaryenExportRef export_) { return ((Export*)export_)->getInternalName()->str.data(); } - -// -// =========== Type export operations =========== -// - -const char* BinaryenTypeExportGetName(BinaryenTypeExportRef export_) { - return ((TypeExport*)export_)->name.str.data(); -} -BinaryenHeapType BinaryenTypeExportGetHeapType(BinaryenTypeExportRef export_) { - return ((TypeExport*)export_)->heaptype.getID(); +BinaryenHeapType BinaryenExportGetHeapType(BinaryenExportRef export_) { + return ((Export*)export_)->getHeapType()->getID(); } // diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 310269970f9..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, @@ -2730,26 +2734,6 @@ BINARYEN_API BinaryenIndex BinaryenGetNumExports(BinaryenModuleRef module); BINARYEN_API BinaryenExportRef BinaryenGetExportByIndex(BinaryenModuleRef module, BinaryenIndex index); -// Type exports - -BINARYEN_REF(TypeExport); - -// Adds a type export to the module. -BINARYEN_API BinaryenTypeExportRef BinaryenAddTypeExport( - BinaryenModuleRef module, BinaryenHeapType type, const char* externalName); -// Gets a type export reference by external name. Returns NULL if the export -// does not exist. -BINARYEN_API BinaryenTypeExportRef -BinaryenGetTypeExport(BinaryenModuleRef module, const char* externalName); -// Removes a type export by external name. -BINARYEN_API void BinaryenRemoveTypeExport(BinaryenModuleRef module, - const char* externalName); -// Gets the number of type exports in the module. -BINARYEN_API BinaryenIndex BinaryenGetNumTypeExports(BinaryenModuleRef module); -// Gets the type export at the specified index. -BINARYEN_API BinaryenTypeExportRef -BinaryenGetTypeExportByIndex(BinaryenModuleRef module, BinaryenIndex index); - // Globals BINARYEN_REF(Global); @@ -3339,17 +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_); - -// -// ========== Type Export Operations ========== -// - -// Gets the external name of the specified export. -BINARYEN_API const char* -BinaryenTypeExportGetName(BinaryenTypeExportRef export_); -// Gets the internal name of the specified export. +// Gets the heap type of the specified type export. BINARYEN_API BinaryenHeapType -BinaryenTypeExportGetHeapType(BinaryenTypeExportRef export_); +BinaryenTypeExportGetHeapType(BinaryenExportRef export_); // // ========= Custom sections ========= diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 7c2c28b78b2..12060f52a45 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -489,8 +489,10 @@ InsertOrderedMap collectHeapTypeInfo( for (auto& curr : wasm.elementSegments) { info.note(curr->type); } - for (auto& curr : wasm.typeExports) { - info.note(curr->heaptype); + for (auto& curr : wasm.exports) { + if (auto* heapType = curr->getHeapType()) { + info.note(*heapType); + } } // Collect info from functions in parallel. @@ -659,14 +661,13 @@ void classifyTypeVisibility(Module& wasm, notePublic(wasm.getTag(*ex->getInternalName())->type); continue; case ExternalKind::Type: + notePublic(*ex->getHeapType()); + continue; case ExternalKind::Invalid: break; } WASM_UNREACHABLE("unexpected export kind"); } - for (auto& ex : wasm.typeExports) { - notePublic(ex->heaptype); - } // Ignorable public types are public. for (auto type : getIgnorablePublicTypes()) { diff --git a/src/ir/names.h b/src/ir/names.h index ca31d49189e..c10b70b5231 100644 --- a/src/ir/names.h +++ b/src/ir/names.h @@ -70,10 +70,8 @@ inline Name getValidName(Name root, inline Name getValidExportName(Module& module, Name root) { return getValidName( root, - [&](Name test) { - return !module.getTypeExportOrNull(test) && !module.getExportOrNull(test); - }, - module.typeExports.size() + module.exports.size()); + [&](Name test) { return !module.getExportOrNull(test); }, + module.exports.size()); } inline Name getValidGlobalName(Module& module, Name root) { return getValidName( diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 4e7fdda386a..84291c316d5 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -305,8 +305,10 @@ void GlobalTypeRewriter::mapTypes(const TypeMap& oldToNewTypes) { for (auto& tag : wasm.tags) { tag->type = updater.getNew(tag->type); } - for (auto& exp : wasm.typeExports) { - exp->heaptype = updater.getNew(exp->heaptype); + for (auto& exp : wasm.exports) { + if (auto* heapType = exp->getHeapType()) { + *heapType = updater.getNew(*heapType); + } } } diff --git a/src/parser/context-decls.cpp b/src/parser/context-decls.cpp index 6861c62391d..c124689d334 100644 --- a/src/parser/context-decls.cpp +++ b/src/parser/context-decls.cpp @@ -33,7 +33,7 @@ Result<> addExports(Lexer& in, const std::vector& exports, ExternalKind kind) { for (auto name : exports) { - if (wasm.getTypeExportOrNull(name) || wasm.getExportOrNull(name)) { + if (wasm.getExportOrNull(name)) { // TODO: Fix error location return in.err("repeated export name"); } diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 94cb3a87c51..2af115abe20 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -1820,7 +1820,7 @@ struct ParseDefsCtx : TypeParserCtx { } Result<> addExport(Index pos, Name value, Name name, ExternalKind kind) { - if (wasm.getTypeExportOrNull(name) || wasm.getExportOrNull(name)) { + if (wasm.getExportOrNull(name)) { return in.err(pos, "duplicate export"); } wasm.addExport(builder.makeExport(name, value, kind)); @@ -1828,10 +1828,10 @@ struct ParseDefsCtx : TypeParserCtx { } Result<> addTypeExport(Index pos, HeapType heaptype, Name name) { - if (wasm.getTypeExportOrNull(name) || wasm.getTypeExportOrNull(name)) { + if (wasm.getExportOrNull(name)) { return in.err(pos, "duplicate export"); } - wasm.addTypeExport(builder.makeTypeExport(name, heaptype)); + wasm.addExport(builder.makeExport(name, heaptype, ExternalKind::Type)); return Ok{}; } diff --git a/src/parser/parse-2-typedefs.cpp b/src/parser/parse-2-typedefs.cpp index c03f011e9d8..653e92c6d26 100644 --- a/src/parser/parse-2-typedefs.cpp +++ b/src/parser/parse-2-typedefs.cpp @@ -59,13 +59,12 @@ Result<> parseTypeDefs( } for (size_t i = 0; i < types.size(); ++i) { for (Name& name : ctx.typeExports[i]) { - if (decls.wasm.getTypeExportOrNull(name) || - decls.wasm.getExportOrNull(name)) { + if (decls.wasm.getExportOrNull(name)) { // TODO: Fix error location return ctx.in.err("repeated export name"); } - decls.wasm.addTypeExport( - Builder(decls.wasm).makeTypeExport(name, types[i])); + decls.wasm.addExport( + Builder(decls.wasm).makeExport(name, types[i], ExternalKind::Type)); } } return Ok{}; diff --git a/src/passes/MinifyImportsAndExports.cpp b/src/passes/MinifyImportsAndExports.cpp index 7c48366c5c0..2105f025ceb 100644 --- a/src/passes/MinifyImportsAndExports.cpp +++ b/src/passes/MinifyImportsAndExports.cpp @@ -88,9 +88,6 @@ struct MinifyImportsAndExports : public Pass { if (minifyExports) { // Minify the exported names. - for (auto& curr : module->typeExports) { - process(curr->name); - } for (auto& curr : module->exports) { process(curr->name); } diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index f5d40c817b0..cd3725c8e51 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -431,7 +431,6 @@ struct PrintSExpression : public UnifiedExpressionVisitor { // Module-level visitors void handleSignature(Function* curr, bool printImplicitNames = false); void visitExport(Export* curr); - void visitTypeExport(TypeExport* curr); void emitImportHeader(Importable* curr); void visitGlobal(Global* curr); void emitGlobalType(Global* curr); @@ -3085,21 +3084,17 @@ void PrintSExpression::visitExport(Export* curr) { 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) << "))"; -} - -void PrintSExpression::visitTypeExport(TypeExport* curr) { - o << '('; - printMedium(o, "export "); - std::stringstream escaped; - String::printEscaped(escaped, curr->name.str); - printText(o, escaped.str(), false) << " (type "; - printHeapType(curr->heaptype); + if (auto* name = curr->getInternalName()) { + name->print(o); + } else { + printHeapType(*curr->getHeapType()); + } o << "))"; } @@ -3520,11 +3515,6 @@ void PrintSExpression::visitModule(Module* curr) { o << ')' << maybeNewLine; } ModuleUtils::iterDefinedTags(*curr, [&](Tag* tag) { visitTag(tag); }); - for (auto& child : curr->typeExports) { - doIndent(o, indent); - visitTypeExport(child.get()); - o << maybeNewLine; - } for (auto& child : curr->exports) { doIndent(o, indent); visitExport(child.get()); diff --git a/src/passes/StripTypeExports.cpp b/src/passes/StripTypeExports.cpp index cbd3762911d..c1a0fd665d4 100644 --- a/src/passes/StripTypeExports.cpp +++ b/src/passes/StripTypeExports.cpp @@ -22,7 +22,8 @@ struct StripTypeExports : public Pass { bool requiresNonNullableLocalFixups() override { return false; } void run(Module* module) override { - module->removeTypeExports([&](TypeExport* curr) { return true; }); + module->removeExports( + [&](Export* curr) { return curr->kind == ExternalKind::Type; }); } }; diff --git a/src/tools/wasm-merge.cpp b/src/tools/wasm-merge.cpp index b86d06e014f..a56638d2743 100644 --- a/src/tools/wasm-merge.cpp +++ b/src/tools/wasm-merge.cpp @@ -166,7 +166,6 @@ struct ExportInfo { Name baseName; }; std::unordered_map exportModuleMap; -std::unordered_map typeExportModuleMap; // A map of [kind of thing in the module] to [old name => new name] for things // of that kind. For example, the NameUpdates for functions is a map of old @@ -360,28 +359,6 @@ void copyModuleContents(Module& input, Name inputName) { // Add the export. merged.addExport(std::move(copy)); } - for (auto& curr : input.typeExports) { - auto copy = std::make_unique(*curr); - - // Note the module origin and original name of this export, for later fusing - // of imports to exports. - typeExportModuleMap[copy.get()] = ExportInfo{inputName, curr->name}; - - // An export may already exist with that name, so fix it up. - copy->name = Names::getValidExportName(merged, copy->name); - if (copy->name != curr->name) { - if (exportMergeMode == ErrorOnExportConflicts) { - Fatal() << "Export name conflict: " << curr->name << " (consider" - << " --rename-export-conflicts or" - << " --skip-export-conflicts)\n"; - } else if (exportMergeMode == SkipExportConflicts) { - // Skip the addTypeExport below us. - continue; - } - } - // Add the export. - merged.addTypeExport(std::move(copy)); - } // Start functions must be merged. if (input.start.is()) { @@ -446,20 +423,21 @@ void checkLimit(bool& valid, const char* kind, T* export_, T* import) { // of the import refer to the exported item (which has been merged // into the module). void fuseTypeImportsAndTypeExports() { - if (merged.typeExports.size() == 0) { - return; - } - // 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.typeExports) { - assert(typeExportModuleMap.count(ex.get())); - ExportInfo& exportInfo = typeExportModuleMap[ex.get()]; - moduleTypeExportMap[exportInfo.moduleName][exportInfo.baseName] = - ex->heaptype; + 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); @@ -891,9 +869,6 @@ Input source maps can be specified by adding an -ism option right after the modu for (auto& curr : merged.exports) { exportModuleMap[curr.get()] = ExportInfo{inputFileName, curr->name}; } - for (auto& curr : merged.typeExports) { - typeExportModuleMap[curr.get()] = ExportInfo{inputFileName, curr->name}; - } } else { // This is a later module: do a full merge. mergeInto(*currModule, inputFileName); diff --git a/src/tools/wasm-metadce.cpp b/src/tools/wasm-metadce.cpp index 864ec341ba9..b79507f8019 100644 --- a/src/tools/wasm-metadce.cpp +++ b/src/tools/wasm-metadce.cpp @@ -303,17 +303,6 @@ struct MetaDCEGraph { void apply() { // Remove the unused exports std::vector toRemove; - for (auto& exp : wasm.typeExports) { - auto name = exp->name; - auto dceName = exportToDCENode[name]; - if (reached.find(dceName) == reached.end()) { - toRemove.push_back(name); - } - } - for (auto name : toRemove) { - wasm.removeTypeExport(name); - } - toRemove.clear(); for (auto& exp : wasm.exports) { auto name = exp->name; auto dceName = exportToDCENode[name]; diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 58323b6a29c..8344355b64a 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -146,14 +146,6 @@ class Builder { return std::make_unique(name, kind, value); } - static std::unique_ptr makeTypeExport(Name name, - HeapType heaptype) { - auto export_ = std::make_unique(); - export_->name = name; - export_->heaptype = heaptype; - return export_; - } - enum Mutability { Mutable, Immutable }; static std::unique_ptr diff --git a/src/wasm-traversal.h b/src/wasm-traversal.h index eab795c259f..db9d1c1c4f9 100644 --- a/src/wasm-traversal.h +++ b/src/wasm-traversal.h @@ -46,7 +46,6 @@ template struct Visitor { // Module-level visitors ReturnType visitExport(Export* curr) { return ReturnType(); } - ReturnType visitTypeExport(TypeExport* curr) { return ReturnType(); } ReturnType visitGlobal(Global* curr) { return ReturnType(); } ReturnType visitFunction(Function* curr) { return ReturnType(); } ReturnType visitTable(Table* curr) { return ReturnType(); } @@ -209,9 +208,6 @@ struct Walker : public VisitorType { for (auto& curr : module->exports) { self->visitExport(curr.get()); } - for (auto& curr : module->typeExports) { - self->visitTypeExport(curr.get()); - } for (auto& curr : module->globals) { if (curr->imported()) { self->visitGlobal(curr.get()); diff --git a/src/wasm.h b/src/wasm.h index fa786f1ca9d..df01398628f 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2268,17 +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); } -}; - -class TypeExport { -public: - // exported name - note that this is the key, as the heap type is - // non-unique (can have multiple exports for a same heap type) - Name name; - HeapType heaptype; // exported type + HeapType* getHeapType() { return std::get_if(&value); } }; class ElementSegment : public Named { @@ -2395,7 +2388,6 @@ class Module { // wasm contents (generally you shouldn't access these from outside, except // maybe for iterating; use add*() and the get() functions) std::vector> exports; - std::vector> typeExports; std::vector> functions; std::vector> globals; std::vector> tags; @@ -2435,7 +2427,6 @@ class Module { // methods are not needed // exports map is by the *exported* name, which is unique std::unordered_map exportsMap; - std::unordered_map typeExportsMap; std::unordered_map functionsMap; std::unordered_map tablesMap; std::unordered_map memoriesMap; @@ -2448,7 +2439,6 @@ class Module { Module() = default; Export* getExport(Name name); - TypeExport* getTypeExport(Name name); Function* getFunction(Name name); Table* getTable(Name name); ElementSegment* getElementSegment(Name name); @@ -2458,7 +2448,6 @@ class Module { Tag* getTag(Name name); Export* getExportOrNull(Name name); - TypeExport* getTypeExportOrNull(Name name); Table* getTableOrNull(Name name); Memory* getMemoryOrNull(Name name); ElementSegment* getElementSegmentOrNull(Name name); @@ -2475,13 +2464,11 @@ class Module { Importable* getImportOrNull(ModuleItemKind kind, Name name); Export* addExport(Export* curr); - TypeExport* addTypeExport(TypeExport* curr); Function* addFunction(Function* curr); Global* addGlobal(Global* curr); Tag* addTag(Tag* curr); Export* addExport(std::unique_ptr&& curr); - TypeExport* addTypeExport(std::unique_ptr&& curr); Function* addFunction(std::unique_ptr&& curr); Table* addTable(std::unique_ptr
&& curr); ElementSegment* addElementSegment(std::unique_ptr&& curr); @@ -2493,7 +2480,6 @@ class Module { void addStart(const Name& s); void removeExport(Name name); - void removeTypeExport(Name name); void removeFunction(Name name); void removeTable(Name name); void removeElementSegment(Name name); @@ -2503,7 +2489,6 @@ class Module { void removeTag(Name name); void removeExports(std::function pred); - void removeTypeExports(std::function pred); void removeFunctions(std::function pred); void removeTables(std::function pred); void removeElementSegments(std::function pred); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 9a13d7ee0f8..5cba9007ff4 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -627,12 +627,7 @@ void WasmBinaryWriter::writeExports() { return; } auto start = startSection(BinaryConsts::Section::Export); - o << U32LEB(wasm->typeExports.size() + wasm->exports.size()); - for (auto& curr : wasm->typeExports) { - writeInlineString(curr->name.str); - o << U32LEB(int32_t(ExternalKind::Type)); - writeHeapType(curr->heaptype); - } + o << U32LEB(wasm->exports.size()); for (auto& curr : wasm->exports) { writeInlineString(curr->name.str); o << U32LEB(int32_t(curr->kind)); @@ -652,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"); } @@ -4459,35 +4457,30 @@ void WasmBinaryReader::readExports() { throwError("duplicate export name"); } ExternalKind kind = (ExternalKind)getU32LEB(); - if (kind == ExternalKind::Type) { - auto curr = std::make_unique(); - curr->name = name; - auto* ex = wasm.addTypeExport(std::move(curr)); - ex->heaptype = getHeapType(); - } else { - std::variant value; - switch (kind) { - case ExternalKind::Function: - value = getFunctionName(getU32LEB()); - break; - case ExternalKind::Table: - value = getTableName(getU32LEB()); - break; - case ExternalKind::Memory: - value = getMemoryName(getU32LEB()); - break; - case ExternalKind::Global: - value = getGlobalName(getU32LEB()); - break; - case ExternalKind::Tag: - value = getTagName(getU32LEB()); - break; - case ExternalKind::Type: - case ExternalKind::Invalid: - throwError("invalid export kind"); - } - wasm.addExport(new Export(name, kind, value)); + std::variant value; + switch (kind) { + case ExternalKind::Function: + value = getFunctionName(getU32LEB()); + break; + case ExternalKind::Table: + value = getTableName(getU32LEB()); + break; + case ExternalKind::Memory: + value = getMemoryName(getU32LEB()); + break; + case ExternalKind::Global: + value = getGlobalName(getU32LEB()); + break; + case ExternalKind::Tag: + value = getTagName(getU32LEB()); + break; + case ExternalKind::Type: + value = getHeapType(); + break; + case ExternalKind::Invalid: + throwError("invalid export kind"); } + wasm.addExport(new Export(name, kind, value)); } } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index 7db5def61a0..ecf13eca57e 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -4019,23 +4019,6 @@ static void validateExports(Module& module, ValidationInfo& info) { } } std::unordered_set exportNames; - for (auto& exp : module.typeExports) { - info.shouldBeTrue(module.features.hasTypeImports(), - exp->name, - "Exported type requires type-imports " - "[--enable-type-imports]"); - auto feats = exp->heaptype.getFeatures(); - if (!info.shouldBeTrue(feats <= module.features, - exp->name, - "Export type requires additional features")) { - info.getStream(nullptr) << getMissingFeaturesList(module, feats) << '\n'; - } - Name exportName = exp->name; - info.shouldBeFalse(exportNames.count(exportName) > 0, - exportName, - "module exports must be unique"); - exportNames.insert(exportName); - } for (auto& exp : module.exports) { if (exp->kind == ExternalKind::Function) { Name name = *exp->getInternalName(); @@ -4061,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"); } @@ -4374,8 +4369,6 @@ static void validateModuleMaps(Module& module, ValidationInfo& info) { // Module maps should be up to date. validateModuleMap( module, info, module.exports, &Module::getExportOrNull, "Export"); - validateModuleMap( - module, info, module.typeExports, &Module::getTypeExportOrNull, "Export"); validateModuleMap( module, info, module.functions, &Module::getFunctionOrNull, "Function"); validateModuleMap( diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 14b9210c3e4..d3bdabf18dc 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -1557,10 +1557,6 @@ Export* Module::getExport(Name name) { return getModuleElement(exportsMap, name, "getExport"); } -TypeExport* Module::getTypeExport(Name name) { - return getModuleElement(typeExportsMap, name, "getTypeExport"); -} - Function* Module::getFunction(Name name) { return getModuleElement(functionsMap, name, "getFunction"); } @@ -1602,10 +1598,6 @@ Export* Module::getExportOrNull(Name name) { return getModuleElementOrNull(exportsMap, name); } -TypeExport* Module::getTypeExportOrNull(Name name) { - return getModuleElementOrNull(typeExportsMap, name); -} - Function* Module::getFunctionOrNull(Name name) { return getModuleElementOrNull(functionsMap, name); } @@ -1717,10 +1709,6 @@ Export* Module::addExport(Export* curr) { return addModuleElement(exports, exportsMap, curr, "addExport"); } -TypeExport* Module::addTypeExport(TypeExport* curr) { - return addModuleElement(typeExports, typeExportsMap, curr, "addTypeExport"); -} - Function* Module::addFunction(Function* curr) { return addModuleElement(functions, functionsMap, curr, "addFunction"); } @@ -1737,11 +1725,6 @@ Export* Module::addExport(std::unique_ptr&& curr) { return addModuleElement(exports, exportsMap, std::move(curr), "addExport"); } -TypeExport* Module::addTypeExport(std::unique_ptr&& curr) { - return addModuleElement( - typeExports, typeExportsMap, std::move(curr), "addTypeExport"); -} - Function* Module::addFunction(std::unique_ptr&& curr) { return addModuleElement( functions, functionsMap, std::move(curr), "addFunction"); @@ -1790,9 +1773,6 @@ void removeModuleElement(Vector& v, Map& m, Name name) { void Module::removeExport(Name name) { removeModuleElement(exports, exportsMap, name); } -void Module::removeTypeExport(Name name) { - removeModuleElement(typeExports, typeExportsMap, name); -} void Module::removeFunction(Name name) { removeModuleElement(functions, functionsMap, name); } @@ -1832,9 +1812,6 @@ void removeModuleElements(Vector& v, void Module::removeExports(std::function pred) { removeModuleElements(exports, exportsMap, pred); } -void Module::removeTypeExports(std::function pred) { - removeModuleElements(typeExports, typeExportsMap, pred); -} void Module::removeFunctions(std::function pred) { removeModuleElements(functions, functionsMap, pred); } diff --git a/test/lit/basic/type-imports.wast b/test/lit/basic/type-imports.wast index 40f5aac079c..1c9cae0cc1b 100644 --- a/test/lit/basic/type-imports.wast +++ b/test/lit/basic/type-imports.wast @@ -42,6 +42,10 @@ (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)) @@ -50,10 +54,6 @@ ;; CHECK: (export "t4" (type $t4)) - ;; CHECK: (export "f1" (func $f1)) - - ;; CHECK: (export "f2" (func $f2)) - ;; 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) @@ -105,6 +105,10 @@ ;; 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)) @@ -113,10 +117,6 @@ ;; CHECK-BIN-NODEBUG: (export "t4" (type $3)) -;; CHECK-BIN-NODEBUG: (export "f1" (func $0)) - -;; CHECK-BIN-NODEBUG: (export "f2" (func $1)) - ;; 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) diff --git a/test/lit/merge/type-imports.wat b/test/lit/merge/type-imports.wat index 61caf67a4c4..16565512feb 100644 --- a/test/lit/merge/type-imports.wat +++ b/test/lit/merge/type-imports.wat @@ -42,10 +42,6 @@ ;; CHECK: (type $11 (func (param (ref $t2)))) - ;; CHECK: (export "t2" (type $t2)) - - ;; CHECK: (export "t3" (type $t3)) - ;; CHECK: (export "f" (func $f)) ;; CHECK: (export "g1" (func $g1)) @@ -60,6 +56,10 @@ ;; 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)) From ad45a6dc2cda6e1338769d5ee37ff3f4c75ab681 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 14 Mar 2025 08:52:28 +0100 Subject: [PATCH 16/18] Fix tool options --- src/tools/tool-options.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/tool-options.h b/src/tools/tool-options.h index f6fdd144101..b92f789e948 100644 --- a/src/tools/tool-options.h +++ b/src/tools/tool-options.h @@ -106,9 +106,9 @@ struct ToolOptions : public Options { .addFeature(FeatureSet::StackSwitching, "stack switching") .addFeature(FeatureSet::SharedEverything, "shared-everything threads") .addFeature(FeatureSet::FP16, "float 16 operations") - .addFeature(FeatureSet::TypeImports, "type imports") .addFeature(FeatureSet::CustomDescriptors, "custom descriptors (RTTs) and exact references") + .addFeature(FeatureSet::TypeImports, "type imports") .add("--enable-typed-function-references", "", "Deprecated compatibility flag", From aa1e4aeb23816aeafae9059d2159a96e01a2a5c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 14 Mar 2025 08:54:59 +0100 Subject: [PATCH 17/18] format --- src/wasm/wasm-binary.cpp | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 5cba9007ff4..7868dd94c0b 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -647,7 +647,7 @@ void WasmBinaryWriter::writeExports() { case ExternalKind::Tag: o << U32LEB(getTagIndex(*curr->getInternalName())); break; - case ExternalKind::Type: + case ExternalKind::Type: writeHeapType(*curr->getHeapType()); break; default: @@ -4459,26 +4459,26 @@ void WasmBinaryReader::readExports() { ExternalKind kind = (ExternalKind)getU32LEB(); std::variant value; switch (kind) { - case ExternalKind::Function: - value = getFunctionName(getU32LEB()); - break; - case ExternalKind::Table: - value = getTableName(getU32LEB()); - break; - case ExternalKind::Memory: - value = getMemoryName(getU32LEB()); - break; - case ExternalKind::Global: - value = getGlobalName(getU32LEB()); - break; - case ExternalKind::Tag: - value = getTagName(getU32LEB()); - break; - case ExternalKind::Type: - value = getHeapType(); - break; - case ExternalKind::Invalid: - throwError("invalid export kind"); + case ExternalKind::Function: + value = getFunctionName(getU32LEB()); + break; + case ExternalKind::Table: + value = getTableName(getU32LEB()); + break; + case ExternalKind::Memory: + value = getMemoryName(getU32LEB()); + break; + case ExternalKind::Global: + value = getGlobalName(getU32LEB()); + break; + case ExternalKind::Tag: + value = getTagName(getU32LEB()); + break; + case ExternalKind::Type: + value = getHeapType(); + break; + case ExternalKind::Invalid: + throwError("invalid export kind"); } wasm.addExport(new Export(name, kind, value)); } From 682ab0f523abc5038cfccccf8eb745fc37094bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Vouillon?= Date: Fri, 14 Mar 2025 14:29:50 +0100 Subject: [PATCH 18/18] Fix tool outputs --- test/lit/help/wasm-as.test | 1 + test/lit/help/wasm-ctor-eval.test | 1 + test/lit/help/wasm-dis.test | 1 + test/lit/help/wasm-emscripten-finalize.test | 1 + test/lit/help/wasm-merge.test | 1 + test/lit/help/wasm-metadce.test | 1 + test/lit/help/wasm-opt.test | 1 + test/lit/help/wasm-reduce.test | 1 + test/lit/help/wasm-split.test | 1 + test/lit/help/wasm2js.test | 1 + 10 files changed, 10 insertions(+) diff --git a/test/lit/help/wasm-as.test b/test/lit/help/wasm-as.test index 076be3f90f4..1ab6e3a549d 100644 --- a/test/lit/help/wasm-as.test +++ b/test/lit/help/wasm-as.test @@ -133,6 +133,7 @@ ;; CHECK-NEXT: ;; 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 diff --git a/test/lit/help/wasm-ctor-eval.test b/test/lit/help/wasm-ctor-eval.test index ef8414c0b65..3cf8f92f84f 100644 --- a/test/lit/help/wasm-ctor-eval.test +++ b/test/lit/help/wasm-ctor-eval.test @@ -140,6 +140,7 @@ ;; CHECK-NEXT: ;; 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 diff --git a/test/lit/help/wasm-dis.test b/test/lit/help/wasm-dis.test index 39b66cb537b..bf0fb6b08ad 100644 --- a/test/lit/help/wasm-dis.test +++ b/test/lit/help/wasm-dis.test @@ -126,6 +126,7 @@ ;; CHECK-NEXT: ;; 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 diff --git a/test/lit/help/wasm-emscripten-finalize.test b/test/lit/help/wasm-emscripten-finalize.test index 23cc87c51bd..7c57ef5bd2d 100644 --- a/test/lit/help/wasm-emscripten-finalize.test +++ b/test/lit/help/wasm-emscripten-finalize.test @@ -168,6 +168,7 @@ ;; CHECK-NEXT: ;; 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 diff --git a/test/lit/help/wasm-merge.test b/test/lit/help/wasm-merge.test index 5812656e1bb..cf43c21b8ce 100644 --- a/test/lit/help/wasm-merge.test +++ b/test/lit/help/wasm-merge.test @@ -158,6 +158,7 @@ ;; CHECK-NEXT: ;; 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 diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 8d6a56f056b..37e83784cbc 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -782,6 +782,7 @@ ;; CHECK-NEXT: ;; 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 diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 0c1897f499c..1b74bc8ee75 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -794,6 +794,7 @@ ;; CHECK-NEXT: ;; 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 diff --git a/test/lit/help/wasm-reduce.test b/test/lit/help/wasm-reduce.test index 65bfb53bfec..3a750908c21 100644 --- a/test/lit/help/wasm-reduce.test +++ b/test/lit/help/wasm-reduce.test @@ -165,6 +165,7 @@ ;; CHECK-NEXT: ;; 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 diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index 42b98c2b416..e8e96afbc9c 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -265,6 +265,7 @@ ;; CHECK-NEXT: ;; 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 diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index b140921a2de..5e94881b579 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -745,6 +745,7 @@ ;; CHECK-NEXT: ;; 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