From 233601e6af7d9e53a698f579458b57203ce7cbbf Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 7 Apr 2025 13:47:52 -0700 Subject: [PATCH 1/3] Revert exact HeapTypes Revert the following PRs that introduced exactness on HeapType: - #7446 - #7444 - #7432 - #7412 - #7396 Keep only the changes to wasm-type-printing.cpp to fix the assertions that subclasses of TypeNameGeneratorBase correctly override getNames. Although putting exactness on heap types makes the most sense in the Custom Descriptors spec, it is not a great fit for how HeapType is used in Binaryen. In almost all cases, HeapType is used to represent heap type definitions rather than the dynamic types of values. Letting HeapType represent exact heap types is not useful in those cases and opens up a new class of possible bugs. To avoid these bugs, we had been introducing a new HeapTypeDef type to represent heap type definitions, but nearly all uses of HeapType would have had to have been replaced with HeapTypeDef. Rather than introduce HeapTypeDef as a third type alongside Type and HeapType, it will be simpler to continue using HeapType to represent heap type definitions and Type to represent the dynamic types of values, including their exactness. While exactness will syntactically be part of the heap type, it will be part of the `Type` in Binaryen IR. A follow-on PR will restore the original work on exact references, and PRs following that one will update the encoding, parsing, and printing of exact references to match the current spec. --- scripts/test/fuzzing.py | 2 - src/ir/module-utils.cpp | 38 ++-- src/ir/module-utils.h | 12 +- src/ir/subtypes.h | 6 +- src/parser/contexts.h | 45 ++--- src/parser/parse-2-typedefs.cpp | 4 +- src/parser/parse-3-implicit-types.cpp | 4 +- src/parser/parse-4-module-types.cpp | 11 +- src/parser/parse-5-defs.cpp | 6 +- src/parser/parsers.h | 10 - src/parser/wat-parser-internal.h | 25 ++- src/parser/wat-parser.cpp | 6 +- src/passes/NameTypes.cpp | 2 +- src/passes/Print.cpp | 8 +- src/passes/TypeMerging.cpp | 72 +++---- src/passes/TypeSSA.cpp | 10 +- src/tools/fuzzing.h | 5 +- src/tools/fuzzing/fuzzing.cpp | 10 +- src/tools/fuzzing/heap-types.cpp | 50 ++--- src/tools/fuzzing/heap-types.h | 8 +- src/tools/wasm-fuzz-types.cpp | 13 +- src/wasm-binary.h | 4 +- src/wasm-type-ordering.h | 8 +- src/wasm-type-printing.h | 26 ++- src/wasm-type.h | 60 ++---- src/wasm.h | 4 +- src/wasm/wasm-binary.cpp | 41 ++-- src/wasm/wasm-type.cpp | 54 ++---- test/gtest/possible-contents.cpp | 4 +- test/gtest/type-builder.cpp | 266 +------------------------- test/gtest/type-domains.cpp | 21 +- test/gtest/type-domains.h | 2 +- test/lit/basic/exact.wast | 52 ----- 33 files changed, 232 insertions(+), 657 deletions(-) delete mode 100644 test/lit/basic/exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 28b03294939..50ab56683fa 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -113,8 +113,6 @@ 'vacuum-stack-switching.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', - # TODO: fuzzer support for exact heap types - 'exact.wast', ] diff --git a/src/ir/module-utils.cpp b/src/ir/module-utils.cpp index 7cdb5913f0c..7f2dfcc089c 100644 --- a/src/ir/module-utils.cpp +++ b/src/ir/module-utils.cpp @@ -347,7 +347,7 @@ namespace { // Helper for collecting HeapTypes and their frequencies. struct TypeInfos { - InsertOrderedMap info; + InsertOrderedMap info; // Multivalue control flow structures need a function type, but the identity // of the function type (i.e. what recursion group it is in or whether it is @@ -355,7 +355,7 @@ struct TypeInfos { // existing function type with the necessary signature. InsertOrderedMap controlFlowSignatures; - void note(HeapTypeDef type) { + void note(HeapType type) { if (!type.isBasic()) { ++info[type].useCount; } @@ -366,7 +366,7 @@ struct TypeInfos { } } // Ensure a type is included without increasing its count. - void include(HeapTypeDef type) { + void include(HeapType type) { if (!type.isBasic()) { info[type]; } @@ -388,7 +388,7 @@ struct TypeInfos { note(sig.results); } } - bool contains(HeapTypeDef type) { return info.count(type); } + bool contains(HeapType type) { return info.count(type); } }; struct CodeScanner @@ -468,11 +468,11 @@ struct CodeScanner }; void classifyTypeVisibility(Module& wasm, - InsertOrderedMap& types); + InsertOrderedMap& types); } // anonymous namespace -InsertOrderedMap collectHeapTypeInfo( +InsertOrderedMap collectHeapTypeInfo( Module& wasm, TypeInclusion inclusion, VisibilityHandling visibility) { // Collect module-level info. TypeInfos info; @@ -523,9 +523,9 @@ InsertOrderedMap collectHeapTypeInfo( // track which recursion groups we've already processed to avoid quadratic // behavior when there is a single large group. // TODO: Use a vector here, since we never try to add the same type twice. - UniqueNonrepeatingDeferredQueue newTypes; - std::unordered_map seenSigs; - auto noteNewType = [&](HeapTypeDef type) { + UniqueNonrepeatingDeferredQueue newTypes; + std::unordered_map seenSigs; + auto noteNewType = [&](HeapType type) { newTypes.push(type); if (type.isSignature()) { seenSigs.insert({type.getSignature(), type}); @@ -588,14 +588,14 @@ InsertOrderedMap collectHeapTypeInfo( namespace { -void classifyTypeVisibility( - Module& wasm, InsertOrderedMap& types) { +void classifyTypeVisibility(Module& wasm, + InsertOrderedMap& types) { // We will need to traverse the types used by public types and mark them // public as well. - std::vector workList; + std::vector workList; std::unordered_set publicGroups; - auto notePublic = [&](HeapTypeDef type) { + auto notePublic = [&](HeapType type) { if (type.isBasic()) { return; } @@ -694,9 +694,9 @@ void setIndices(IndexedHeapTypes& indexedTypes) { } // anonymous namespace -std::vector collectHeapTypes(Module& wasm) { +std::vector collectHeapTypes(Module& wasm) { auto info = collectHeapTypeInfo(wasm); - std::vector types; + std::vector types; types.reserve(info.size()); for (auto& [type, _] : info) { types.push_back(type); @@ -704,10 +704,10 @@ std::vector collectHeapTypes(Module& wasm) { return types; } -std::vector getPublicHeapTypes(Module& wasm) { +std::vector getPublicHeapTypes(Module& wasm) { auto info = collectHeapTypeInfo( wasm, TypeInclusion::BinaryTypes, VisibilityHandling::FindVisibility); - std::vector types; + std::vector types; types.reserve(info.size()); for (auto& [type, typeInfo] : info) { if (typeInfo.visibility == Visibility::Public) { @@ -717,10 +717,10 @@ std::vector getPublicHeapTypes(Module& wasm) { return types; } -std::vector getPrivateHeapTypes(Module& wasm) { +std::vector getPrivateHeapTypes(Module& wasm) { auto info = collectHeapTypeInfo( wasm, TypeInclusion::UsedIRTypes, VisibilityHandling::FindVisibility); - std::vector types; + std::vector types; types.reserve(info.size()); for (auto& [type, typeInfo] : info) { if (typeInfo.visibility == Visibility::Private) { diff --git a/src/ir/module-utils.h b/src/ir/module-utils.h index 593e4d5a20f..bb8b6ae439d 100644 --- a/src/ir/module-utils.h +++ b/src/ir/module-utils.h @@ -470,26 +470,26 @@ struct HeapTypeInfo { Visibility visibility = Visibility::Unknown; }; -InsertOrderedMap collectHeapTypeInfo( +InsertOrderedMap collectHeapTypeInfo( Module& wasm, TypeInclusion inclusion = TypeInclusion::AllTypes, VisibilityHandling visibility = VisibilityHandling::NoVisibility); // Helper function for collecting all the non-basic heap types used in the // module, i.e. the types that would appear in the type section. -std::vector collectHeapTypes(Module& wasm); +std::vector collectHeapTypes(Module& wasm); // Collect all the heap types visible on the module boundary that cannot be // changed. TODO: For open world use cases, this needs to include all subtypes // of public types as well. -std::vector getPublicHeapTypes(Module& wasm); +std::vector getPublicHeapTypes(Module& wasm); // getHeapTypes - getPublicHeapTypes -std::vector getPrivateHeapTypes(Module& wasm); +std::vector getPrivateHeapTypes(Module& wasm); struct IndexedHeapTypes { - std::vector types; - std::unordered_map indices; + std::vector types; + std::unordered_map indices; }; // Similar to `collectHeapTypes`, but provides fast lookup of the index for each diff --git a/src/ir/subtypes.h b/src/ir/subtypes.h index a58617868e2..5c654ceb7be 100644 --- a/src/ir/subtypes.h +++ b/src/ir/subtypes.h @@ -28,7 +28,7 @@ namespace wasm { // // This only scans user types, and not basic types like HeapType::eq. struct SubTypes { - SubTypes(const std::vector& types) : types(types) { + SubTypes(const std::vector& types) : types(types) { for (auto type : types) { note(type); } @@ -198,11 +198,11 @@ struct SubTypes { // All the types in the program. This is computed here anyhow, and can be // useful for callers to iterate on, so it is public. - std::vector types; + std::vector types; private: // Add a type to the graph. - void note(HeapTypeDef type) { + void note(HeapType type) { if (auto super = type.getDeclaredSuperType()) { typeSubTypes[*super].push_back(type); } diff --git a/src/parser/contexts.h b/src/parser/contexts.h index c6b0481123e..4c56780323b 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -167,8 +167,6 @@ struct NullTypeParserCtx { Result getTypeIndex(Name) { return 1; } Result getHeapTypeFromIdx(Index) { return Ok{}; } - HeapTypeT makeExact(HeapTypeT) { return Ok{}; } - DataStringT makeDataString() { return Ok{}; } void appendDataString(DataStringT&, std::string_view) {} @@ -253,8 +251,6 @@ template struct TypeParserCtx { return HeapTypes::nocont.getBasic(share); } - HeapTypeT makeExact(HeapTypeT type) { return type.with(Exact); } - TypeT makeI32() { return Type::i32; } TypeT makeI64() { return Type::i64; } TypeT makeF32() { return Type::f32; } @@ -1179,19 +1175,18 @@ struct ParseImplicitTypeDefsCtx : TypeParserCtx { Lexer in; // Types parsed so far. - std::vector& types; + std::vector& types; // Map typeuse positions without an explicit type to the correct type. - std::unordered_map& implicitTypes; + std::unordered_map& implicitTypes; // Map signatures to the first defined heap type they match. - std::unordered_map sigTypes; + std::unordered_map sigTypes; - ParseImplicitTypeDefsCtx( - Lexer& in, - std::vector& types, - std::unordered_map& implicitTypes, - const IndexMap& typeIndices) + ParseImplicitTypeDefsCtx(Lexer& in, + std::vector& types, + std::unordered_map& implicitTypes, + const IndexMap& typeIndices) : TypeParserCtx(typeIndices), in(in), types(types), implicitTypes(implicitTypes) { for (auto type : types) { @@ -1232,7 +1227,7 @@ struct ParseImplicitTypeDefsCtx : TypeParserCtx { } auto sig = Signature(Type(paramTypes), Type(resultTypes)); - auto [it, inserted] = sigTypes.insert({sig, HeapType(HeapType::func)}); + auto [it, inserted] = sigTypes.insert({sig, HeapType::func}); if (inserted) { auto type = HeapType(sig); it->second = type; @@ -1260,8 +1255,8 @@ struct ParseModuleTypesCtx : TypeParserCtx, Module& wasm; - const std::vector& types; - const std::unordered_map& implicitTypes; + const std::vector& types; + const std::unordered_map& implicitTypes; const std::unordered_map& implicitElemIndices; // The index of the current type. @@ -1270,8 +1265,8 @@ struct ParseModuleTypesCtx : TypeParserCtx, ParseModuleTypesCtx( Lexer& in, Module& wasm, - const std::vector& types, - const std::unordered_map& implicitTypes, + const std::vector& types, + const std::unordered_map& implicitTypes, const std::unordered_map& implicitElemIndices, const IndexMap& typeIndices) : TypeParserCtx(typeIndices), in(in), wasm(wasm), @@ -1309,7 +1304,7 @@ struct ParseModuleTypesCtx : TypeParserCtx, return TypeUse{it->second, ids}; } - Result getBlockTypeFromTypeUse(Index pos, TypeUse use) { + Result getBlockTypeFromTypeUse(Index pos, TypeUse use) { return use.type; } @@ -1449,9 +1444,9 @@ struct ParseDefsCtx : TypeParserCtx { Module& wasm; Builder builder; - const std::vector& types; - const std::unordered_map& implicitTypes; - const std::unordered_map>& + const std::vector& types; + const std::unordered_map& implicitTypes; + const std::unordered_map>& typeNames; const std::unordered_map& implicitElemIndices; @@ -1476,9 +1471,9 @@ struct ParseDefsCtx : TypeParserCtx { ParseDefsCtx( Lexer& in, Module& wasm, - const std::vector& types, - const std::unordered_map& implicitTypes, - const std::unordered_map>& + const std::vector& types, + const std::unordered_map& implicitTypes, + const std::unordered_map>& typeNames, const std::unordered_map& implicitElemIndices, const IndexMap& typeIndices) @@ -1502,7 +1497,7 @@ struct ParseDefsCtx : TypeParserCtx { return HeapType(Signature(Type::none, results[0])); } - Result getBlockTypeFromTypeUse(Index pos, HeapType type) { + Result getBlockTypeFromTypeUse(Index pos, HeapType type) { assert(type.isSignature()); // TODO: Error if block parameters are named return type; diff --git a/src/parser/parse-2-typedefs.cpp b/src/parser/parse-2-typedefs.cpp index e5c55bf9065..83e10ec5b8a 100644 --- a/src/parser/parse-2-typedefs.cpp +++ b/src/parser/parse-2-typedefs.cpp @@ -22,8 +22,8 @@ Result<> parseTypeDefs( ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map>& typeNames) { + std::vector& types, + std::unordered_map>& typeNames) { TypeBuilder builder(decls.typeDefs.size()); ParseTypeDefsCtx ctx(input, builder, typeIndices); for (auto& recType : decls.recTypeDefs) { diff --git a/src/parser/parse-3-implicit-types.cpp b/src/parser/parse-3-implicit-types.cpp index 02caaf2d6a5..cf13ae0f7e2 100644 --- a/src/parser/parse-3-implicit-types.cpp +++ b/src/parser/parse-3-implicit-types.cpp @@ -22,8 +22,8 @@ Result<> parseImplicitTypeDefs(ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes) { + std::vector& types, + std::unordered_map& implicitTypes) { ParseImplicitTypeDefsCtx ctx(input, types, implicitTypes, typeIndices); for (Index pos : decls.implicitTypeDefs) { WithPosition with(ctx, pos); diff --git a/src/parser/parse-4-module-types.cpp b/src/parser/parse-4-module-types.cpp index 07d88d0c0a3..04d8292d0bf 100644 --- a/src/parser/parse-4-module-types.cpp +++ b/src/parser/parse-4-module-types.cpp @@ -18,12 +18,11 @@ namespace wasm::WATParser { -Result<> -parseModuleTypes(ParseDeclsCtx& decls, - Lexer& input, - IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes) { +Result<> parseModuleTypes(ParseDeclsCtx& decls, + Lexer& input, + IndexMap& typeIndices, + std::vector& types, + std::unordered_map& implicitTypes) { ParseModuleTypesCtx ctx(input, decls.wasm, types, diff --git a/src/parser/parse-5-defs.cpp b/src/parser/parse-5-defs.cpp index bc619dd748e..acc81bb75a4 100644 --- a/src/parser/parse-5-defs.cpp +++ b/src/parser/parse-5-defs.cpp @@ -22,9 +22,9 @@ Result<> parseDefinitions( ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes, - std::unordered_map>& typeNames) { + std::vector& types, + std::unordered_map& implicitTypes, + std::unordered_map>& typeNames) { // Parse definitions. // TODO: Parallelize this. ParseDefsCtx ctx(input, diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 2e2a6da7e39..33e9d20fdc9 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -434,7 +434,6 @@ Result absheaptype(Ctx& ctx, Shareability share) { } // heaptype ::= x:typeidx => types[x] -// | '(' 'exact' x:typeidx ')' => exact types[x] // | t:absheaptype => unshared t // | '(' 'shared' t:absheaptype ')' => shared t template Result heaptype(Ctx& ctx) { @@ -443,15 +442,6 @@ template Result heaptype(Ctx& ctx) { return *t; } - if (ctx.in.takeSExprStart("exact"sv)) { - auto t = typeidx(ctx); - CHECK_ERR(t); - if (!ctx.in.takeRParen()) { - return ctx.in.err("expected end of exact heap type"); - } - return ctx.makeExact(*t); - } - auto share = ctx.in.takeSExprStart("shared"sv) ? Shared : Unshared; auto t = absheaptype(ctx, share); CHECK_ERR(t); diff --git a/src/parser/wat-parser-internal.h b/src/parser/wat-parser-internal.h index 5b78d7e9632..00c96abd42e 100644 --- a/src/parser/wat-parser-internal.h +++ b/src/parser/wat-parser-internal.h @@ -28,30 +28,29 @@ Result<> parseTypeDefs( ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map>& typeNames); + std::vector& types, + std::unordered_map>& typeNames); Result<> parseImplicitTypeDefs(ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes); + std::vector& types, + std::unordered_map& implicitTypes); -Result<> -parseModuleTypes(ParseDeclsCtx& decls, - Lexer& input, - IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes); +Result<> parseModuleTypes(ParseDeclsCtx& decls, + Lexer& input, + IndexMap& typeIndices, + std::vector& types, + std::unordered_map& implicitTypes); Result<> parseDefinitions( ParseDeclsCtx& decls, Lexer& input, IndexMap& typeIndices, - std::vector& types, - std::unordered_map& implicitTypes, - std::unordered_map>& typeNames); + std::vector& types, + std::unordered_map& implicitTypes, + std::unordered_map>& typeNames); // RAII utility for temporarily changing the parsing position of a parsing // context. diff --git a/src/parser/wat-parser.cpp b/src/parser/wat-parser.cpp index a64e62b227d..26b2bbad06f 100644 --- a/src/parser/wat-parser.cpp +++ b/src/parser/wat-parser.cpp @@ -100,11 +100,11 @@ Result<> doParseModule(Module& wasm, Lexer& input, bool allowExtra) { auto typeIndices = createIndexMap(decls.in, decls.typeDefs); CHECK_ERR(typeIndices); - std::vector types; - std::unordered_map> typeNames; + std::vector types; + std::unordered_map> typeNames; CHECK_ERR(parseTypeDefs(decls, input, *typeIndices, types, typeNames)); - std::unordered_map implicitTypes; + std::unordered_map implicitTypes; CHECK_ERR( parseImplicitTypeDefs(decls, input, *typeIndices, types, implicitTypes)); diff --git a/src/passes/NameTypes.cpp b/src/passes/NameTypes.cpp index 1a6961efaec..0e18f30945a 100644 --- a/src/passes/NameTypes.cpp +++ b/src/passes/NameTypes.cpp @@ -32,7 +32,7 @@ struct NameTypes : public Pass { void run(Module* module) override { // Find all the types. - std::vector types = ModuleUtils::collectHeapTypes(*module); + std::vector types = ModuleUtils::collectHeapTypes(*module); std::unordered_set used; diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 41bbe3bbc58..35fa57ea51a 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -86,7 +86,7 @@ void printTypeOrName(Type type, std::ostream& o, Module* wasm) { Module* wasm; DefaultTypeNameGenerator fallback; Printer(Module* wasm) : wasm(wasm) {} - TypeNames getNames(HeapTypeDef type) { + TypeNames getNames(HeapType type) { if (wasm) { if (auto it = wasm->typeNames.find(type); it != wasm->typeNames.end()) { return it->second; @@ -141,7 +141,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { // Used to print delegate's depth argument when it throws to the caller int controlFlowDepth = 0; - std::vector heapTypes; + std::vector heapTypes; std::unordered_map signatureTypes; // Track the print indent so that we can see when it changes. That affects how @@ -173,7 +173,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { DefaultTypeNameGenerator fallback; std::unordered_map fallbackNames; - TypePrinter(PrintSExpression& parent, const std::vector& types) + TypePrinter(PrintSExpression& parent, const std::vector& types) : parent(parent) { if (!parent.currModule) { return; @@ -198,7 +198,7 @@ struct PrintSExpression : public UnifiedExpressionVisitor { } } - TypeNames getNames(HeapTypeDef type) { + TypeNames getNames(HeapType type) { if (parent.currModule) { if (auto it = parent.currModule->typeNames.find(type); it != parent.currModule->typeNames.end()) { diff --git a/src/passes/TypeMerging.cpp b/src/passes/TypeMerging.cpp index 79be9ebabe4..e7a25cf372c 100644 --- a/src/passes/TypeMerging.cpp +++ b/src/passes/TypeMerging.cpp @@ -66,7 +66,7 @@ constexpr int MAX_ITERATIONS = 20; // casts are never distinguished from their supertypes. // Most functions do no casts, or perhaps cast |this| and perhaps a few others. -using CastTypes = SmallUnorderedSet; +using CastTypes = SmallUnorderedSet; struct CastFinder : public PostWalker { CastTypes castTypes; @@ -115,7 +115,7 @@ struct CastFinder : public PostWalker { // split out into separate partitions. struct TypeMerging : public Pass { // A list of partitions with stable iterators. - using Partition = std::vector>; + using Partition = std::vector>; using Partitions = std::list; // Only modifies types. @@ -124,18 +124,18 @@ struct TypeMerging : public Pass { Module* module; // All private original types. - std::unordered_set privateTypes; + std::unordered_set privateTypes; // Types that are distinguished by cast instructions. CastTypes castTypes; // The list of remaining types that have not been merged into other types. // Candidates for further merging. - std::vector mergeable; + std::vector mergeable; // Map the original types to the types they will be merged into, if any. TypeMapper::TypeUpdates merges; - HeapType getMerged(HeapTypeDef type) { + HeapType getMerged(HeapType type) { for (auto it = merges.find(type); it != merges.end(); it = merges.find(type)) { type = it->second; @@ -143,10 +143,10 @@ struct TypeMerging : public Pass { return type; } - std::vector - mergeableSupertypesFirst(const std::vector& types) { + std::vector + mergeableSupertypesFirst(const std::vector& types) { return HeapTypeOrdering::supertypesFirst( - types, [&](HeapTypeDef type) -> std::optional { + types, [&](HeapType type) -> std::optional { if (auto super = type.getDeclaredSuperType()) { return getMerged(*super); } @@ -166,19 +166,19 @@ struct TypeMerging : public Pass { // Split a partition into potentially multiple partitions for each // disconnected group of types it contains. - std::vector> - splitSupertypePartition(const std::vector&); + std::vector> + splitSupertypePartition(const std::vector&); CastTypes findCastTypes(); - std::vector getPublicChildren(HeapTypeDef type); - DFA::State makeDFAState(HeapTypeDef type); + std::vector getPublicChildren(HeapType type); + DFA::State makeDFAState(HeapType type); void applyMerges(); }; // Hash and equality-compare HeapTypes based on their top-level structure (i.e. // "shape"), ignoring nontrivial heap type children that will not be // differentiated between until we run the DFA partition refinement. -bool shapeEq(HeapTypeDef a, HeapTypeDef b); +bool shapeEq(HeapType a, HeapType b); bool shapeEq(const Struct& a, const Struct& b); bool shapeEq(Array a, Array b); bool shapeEq(Signature a, Signature b); @@ -186,7 +186,7 @@ bool shapeEq(Field a, Field b); bool shapeEq(Type a, Type b); bool shapeEq(const Tuple& a, const Tuple& b); -size_t shapeHash(HeapTypeDef a); +size_t shapeHash(HeapType a); size_t shapeHash(const Struct& a); size_t shapeHash(Array a); size_t shapeHash(Signature a); @@ -195,13 +195,13 @@ size_t shapeHash(Type a); size_t shapeHash(const Tuple& a); struct ShapeEq { - bool operator()(const HeapTypeDef& a, const HeapTypeDef& b) const { + bool operator()(const HeapType& a, const HeapType& b) const { return shapeEq(a, b); } }; struct ShapeHash { - size_t operator()(const HeapTypeDef& type) const { return shapeHash(type); } + size_t operator()(const HeapType& type) const { return shapeHash(type); } }; void TypeMerging::run(Module* module_) { @@ -219,7 +219,7 @@ void TypeMerging::run(Module* module_) { // determine whether types are eligible to be merged. mergeable = ModuleUtils::getPrivateHeapTypes(*module); privateTypes = - std::unordered_set(mergeable.begin(), mergeable.end()); + std::unordered_set(mergeable.begin(), mergeable.end()); castTypes = findCastTypes(); // Merging supertypes or siblings can unlock more sibling merging @@ -274,21 +274,21 @@ bool TypeMerging::merge(MergeKind kind) { #endif // TYPE_MERGING_DEBUG // Map each type to its partition in the list. - std::unordered_map typePartitions; + std::unordered_map typePartitions; // Map the supertypes and top-level structures of each type to partitions so // that siblings that refine the supertype in the same way can be assigned to // the same partition and potentially merged. std::unordered_map< - std::optional, - std::unordered_map> + std::optional, + std::unordered_map> shapePartitions; // Ensure the type has a partition and return a reference to it. Since we // merge up the type tree and visit supertypes first, the partition usually // already exists. The exception is when the supertype is public, in which // case we might not have created a partition for it yet. - auto ensurePartition = [&](HeapTypeDef type) -> Partitions::iterator { + auto ensurePartition = [&](HeapType type) -> Partitions::iterator { auto [it, inserted] = typePartitions.insert({type, partitions.end()}); if (inserted) { it->second = partitions.insert(partitions.end(), {makeDFAState(type)}); @@ -298,7 +298,7 @@ bool TypeMerging::merge(MergeKind kind) { // Similar to the above, but look up or create a partition associated with the // type's supertype and top-level shape rather than its identity. - auto ensureShapePartition = [&](HeapTypeDef type) -> Partitions::iterator { + auto ensureShapePartition = [&](HeapType type) -> Partitions::iterator { auto super = type.getDeclaredSuperType(); if (super) { super = getMerged(*super); @@ -401,7 +401,7 @@ bool TypeMerging::merge(MergeKind kind) { // differentiatable. A type and its subtype cannot differ by referring to // different, unrelated types in the same position because then they would // not be in a valid subtype relationship. - std::vector> newPartitions; + std::vector> newPartitions; for (const auto& partitionTypes : refinedPartitions) { auto split = splitSupertypePartition(partitionTypes); newPartitions.insert(newPartitions.end(), split.begin(), split.end()); @@ -418,7 +418,7 @@ bool TypeMerging::merge(MergeKind kind) { // supertypes or siblings because if we try to merge into a subtype then we // will accidentally set that subtype to be its own supertype. Also keep track // of the remaining types. - std::vector newMergeable; + std::vector newMergeable; bool merged = false; for (const auto& partition : refinedPartitions) { auto target = mergeableSupertypesFirst(partition).front(); @@ -434,7 +434,7 @@ bool TypeMerging::merge(MergeKind kind) { #if TYPE_MERGING_DEBUG std::cerr << "Merges:\n"; - std::unordered_map> mergees; + std::unordered_map> mergees; for (auto& [mergee, target] : merges) { mergees[target].push_back(mergee); } @@ -450,15 +450,15 @@ bool TypeMerging::merge(MergeKind kind) { return merged; } -std::vector> -TypeMerging::splitSupertypePartition(const std::vector& types) { +std::vector> +TypeMerging::splitSupertypePartition(const std::vector& types) { if (types.size() == 1) { // Cannot split a partition containing just one type. return {types}; } - std::unordered_set includedTypes(types.begin(), types.end()); - std::vector> partitions; - std::unordered_map partitionIndices; + std::unordered_set includedTypes(types.begin(), types.end()); + std::vector> partitions; + std::unordered_map partitionIndices; for (auto type : mergeableSupertypesFirst(types)) { auto super = type.getDeclaredSuperType(); if (super && includedTypes.count(*super)) { @@ -503,8 +503,8 @@ CastTypes TypeMerging::findCastTypes() { return allCastTypes; } -std::vector TypeMerging::getPublicChildren(HeapTypeDef type) { - std::vector publicChildren; +std::vector TypeMerging::getPublicChildren(HeapType type) { + std::vector publicChildren; for (auto child : type.getHeapTypeChildren()) { if (!child.isBasic() && !privateTypes.count(child)) { publicChildren.push_back(child); @@ -513,8 +513,8 @@ std::vector TypeMerging::getPublicChildren(HeapTypeDef type) { return publicChildren; } -DFA::State TypeMerging::makeDFAState(HeapTypeDef type) { - std::vector succs; +DFA::State TypeMerging::makeDFAState(HeapType type) { + std::vector succs; // Both private and public heap type children participate in the DFA and are // eligible to be successors, except that public types are terminal states // that do not have successors. This is sufficient because public types are @@ -548,7 +548,7 @@ void TypeMerging::applyMerges() { TypeMapper(*module, merges).map(); } -bool shapeEq(HeapTypeDef a, HeapTypeDef b) { +bool shapeEq(HeapType a, HeapType b) { // Check whether `a` and `b` have the same top-level structure, including the // position and identity of any children that are not included as transitions // in the DFA, i.e. any children that are not nontrivial references. @@ -578,7 +578,7 @@ bool shapeEq(HeapTypeDef a, HeapTypeDef b) { return false; } -size_t shapeHash(HeapTypeDef a) { +size_t shapeHash(HeapType a) { size_t digest = hash(a.isOpen()); rehash(digest, a.isShared()); auto kind = a.getKind(); diff --git a/src/passes/TypeSSA.cpp b/src/passes/TypeSSA.cpp index 679f6a34d82..3d68c991396 100644 --- a/src/passes/TypeSSA.cpp +++ b/src/passes/TypeSSA.cpp @@ -69,11 +69,11 @@ namespace { // way to ensure that the new types are in fact in a new rec group. // // TODO: Move this outside if we find more uses. -std::vector ensureTypesAreInNewRecGroup(RecGroup recGroup, - Module& wasm) { +std::vector ensureTypesAreInNewRecGroup(RecGroup recGroup, + Module& wasm) { auto num = recGroup.size(); - std::vector types; + std::vector types; types.reserve(num); for (auto type : recGroup) { types.push_back(type); @@ -81,8 +81,8 @@ std::vector ensureTypesAreInNewRecGroup(RecGroup recGroup, // Find all the heap types present before we create the new ones. The new // types must not appear in |existingSet|. - std::vector existing = ModuleUtils::collectHeapTypes(wasm); - std::unordered_set existingSet(existing.begin(), existing.end()); + std::vector existing = ModuleUtils::collectHeapTypes(wasm); + std::unordered_set existingSet(existing.begin(), existing.end()); // Check for a collision with an existing rec group. Note that it is enough to // check one of the types: either the entire rec group gets merged, so they diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 0fcd45c00d1..383e5af70c1 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -191,12 +191,11 @@ class TranslateToFuzzReader { std::vector loggableTypes; // The heap types we can pick from to generate instructions. - std::vector interestingHeapTypes; + std::vector interestingHeapTypes; // A mapping of a heap type to the subset of interestingHeapTypes that are // subtypes of it. - std::unordered_map> - interestingHeapSubTypes; + std::unordered_map> interestingHeapSubTypes; // Type => list of struct fields that have that type. std::unordered_map> typeStructFields; diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index e1b6cf31192..683ec6849bc 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -484,11 +484,11 @@ void TranslateToFuzzReader::setupHeapTypes() { // Basic types must be handled directly, since subTypes doesn't look at // those. auto share = type.getShared(); - HeapType struct_ = HeapTypes::struct_.getBasic(share); - HeapType array = HeapTypes::array.getBasic(share); - HeapType eq = HeapTypes::eq.getBasic(share); - HeapType any = HeapTypes::any.getBasic(share); - HeapType func = HeapTypes::func.getBasic(share); + auto struct_ = HeapTypes::struct_.getBasic(share); + auto array = HeapTypes::array.getBasic(share); + auto eq = HeapTypes::eq.getBasic(share); + auto any = HeapTypes::any.getBasic(share); + auto func = HeapTypes::func.getBasic(share); switch (type.getKind()) { case HeapTypeKind::Func: interestingHeapSubTypes[func].push_back(type); diff --git a/src/tools/fuzzing/heap-types.cpp b/src/tools/fuzzing/heap-types.cpp index 991b1db9c45..65820e50d8e 100644 --- a/src/tools/fuzzing/heap-types.cpp +++ b/src/tools/fuzzing/heap-types.cpp @@ -672,7 +672,7 @@ namespace { // supertypes in which they appear. struct Inhabitator { // Uniquely identify fields as an index into a type. - using FieldPos = std::pair; + using FieldPos = std::pair; // When we make a reference nullable, we typically need to make the same // reference in other types nullable to maintain valid subtyping. Which types @@ -685,14 +685,14 @@ struct Inhabitator { enum Variance { Invariant, Covariant }; // The input types. - const std::vector& types; + const std::vector& types; // The fields we will make nullable. std::unordered_set nullables; SubTypes subtypes; - Inhabitator(const std::vector& types) + Inhabitator(const std::vector& types) : types(types), subtypes(types) {} Variance getVariance(FieldPos fieldPos); @@ -701,7 +701,7 @@ struct Inhabitator { void markExternRefsNullable(); void breakNonNullableCycles(); - std::vector build(); + std::vector build(); }; Inhabitator::Variance Inhabitator::getVariance(FieldPos fieldPos) { @@ -752,7 +752,7 @@ void Inhabitator::markNullable(FieldPos field) { // this extra `index` variable once we have C++20. It's a workaround for // lambdas being unable to capture structured bindings. const size_t index = idx; - subtypes.iterSubTypes(curr, [&](HeapTypeDef type, Index) { + subtypes.iterSubTypes(curr, [&](HeapType type, Index) { nullables.insert({type, index}); }); break; @@ -803,13 +803,13 @@ void Inhabitator::markExternRefsNullable() { // the cycle to be made non-nullable. void Inhabitator::breakNonNullableCycles() { // Types we've finished visiting. We don't need to visit them again. - std::unordered_set visited; + std::unordered_set visited; // The path of types we are currently visiting. If one of them comes back up, // we've found a cycle. Map the types to the other types they reference and // our current index into that list so we can track where we are in each level // of the search. - InsertOrderedMap, Index>> visiting; + InsertOrderedMap, Index>> visiting; for (auto root : types) { if (visited.count(root)) { @@ -884,8 +884,8 @@ void Inhabitator::breakNonNullableCycles() { } } -std::vector Inhabitator::build() { - std::unordered_map typeIndices; +std::vector Inhabitator::build() { + std::unordered_map typeIndices; for (size_t i = 0; i < types.size(); ++i) { typeIndices.insert({types[i], i}); } @@ -978,16 +978,16 @@ std::vector Inhabitator::build() { } // anonymous namespace -std::vector -HeapTypeGenerator::makeInhabitable(const std::vector& types) { +std::vector +HeapTypeGenerator::makeInhabitable(const std::vector& types) { if (types.empty()) { return {}; } // Remove duplicate and basic types. We will insert them back at the end. - std::unordered_map typeIndices; + std::unordered_map typeIndices; std::vector deduplicatedIndices; - std::vector deduplicated; + std::vector deduplicated; for (auto type : types) { if (type.isBasic()) { deduplicatedIndices.push_back(-1); @@ -1009,7 +1009,7 @@ HeapTypeGenerator::makeInhabitable(const std::vector& types) { deduplicated = inhabitator.build(); // Re-duplicate and re-insert basic types as necessary. - std::vector result; + std::vector result; for (size_t i = 0; i < types.size(); ++i) { if (deduplicatedIndices[i] == (size_t)-1) { assert(types[i].isBasic()); @@ -1024,14 +1024,14 @@ HeapTypeGenerator::makeInhabitable(const std::vector& types) { namespace { bool isUninhabitable(Type type, - std::unordered_set& visited, - std::unordered_set& visiting); + std::unordered_set& visited, + std::unordered_set& visiting); // Simple recursive DFS through non-nullable references to see if we find any // cycles. -bool isUninhabitable(HeapTypeDef type, - std::unordered_set& visited, - std::unordered_set& visiting) { +bool isUninhabitable(HeapType type, + std::unordered_set& visited, + std::unordered_set& visiting) { switch (type.getKind()) { case HeapTypeKind::Basic: return false; @@ -1074,8 +1074,8 @@ bool isUninhabitable(HeapTypeDef type, } bool isUninhabitable(Type type, - std::unordered_set& visited, - std::unordered_set& visiting) { + std::unordered_set& visited, + std::unordered_set& visiting) { if (type.isRef() && type.isNonNullable()) { if (type.getHeapType().isBottom() || type.getHeapType().isMaybeShared(HeapType::ext)) { @@ -1088,10 +1088,10 @@ bool isUninhabitable(Type type, } // anonymous namespace -std::vector -HeapTypeGenerator::getInhabitable(const std::vector& types) { - std::unordered_set visited, visiting; - std::vector inhabitable; +std::vector +HeapTypeGenerator::getInhabitable(const std::vector& types) { + std::unordered_set visited, visiting; + std::vector inhabitable; for (auto type : types) { if (!isUninhabitable(type, visited, visiting)) { inhabitable.push_back(type); diff --git a/src/tools/fuzzing/heap-types.h b/src/tools/fuzzing/heap-types.h index 119b865b540..bd30178f2b4 100644 --- a/src/tools/fuzzing/heap-types.h +++ b/src/tools/fuzzing/heap-types.h @@ -42,12 +42,12 @@ struct HeapTypeGenerator { // Given a sequence of newly-built heap types, produce a sequence of similar // or identical types that are all inhabitable, i.e. that are possible to // create values for. - static std::vector - makeInhabitable(const std::vector& types); + static std::vector + makeInhabitable(const std::vector& types); // Returns the types in the input that are inhabitable. - static std::vector - getInhabitable(const std::vector& types); + static std::vector + getInhabitable(const std::vector& types); }; } // namespace wasm diff --git a/src/tools/wasm-fuzz-types.cpp b/src/tools/wasm-fuzz-types.cpp index 8bf9fa340a3..7ba341e09df 100644 --- a/src/tools/wasm-fuzz-types.cpp +++ b/src/tools/wasm-fuzz-types.cpp @@ -39,7 +39,7 @@ struct Fuzzer { bool verbose; // Initialized by `run` for checkers and possible later inspection - std::vector types; + std::vector types; std::vector> subtypeIndices; Random rand; @@ -48,7 +48,7 @@ struct Fuzzer { // Generate types and run checkers on them. void run(uint64_t seed); - static void printTypes(const std::vector&); + static void printTypes(const std::vector&); // Checkers for various properties. void checkSubtypes() const; @@ -93,11 +93,11 @@ void Fuzzer::run(uint64_t seed) { checkRecGroupShapes(); } -void Fuzzer::printTypes(const std::vector& types) { +void Fuzzer::printTypes(const std::vector& types) { std::cout << "Built " << types.size() << " types:\n"; struct FatalTypeNameGenerator : TypeNameGeneratorBase { - TypeNames getNames(HeapTypeDef type) { + TypeNames getNames(HeapType type) { Fatal() << "trying to print unknown heap type"; } } fatalGenerator; @@ -233,7 +233,7 @@ void Fuzzer::checkCanonicalization() { // between canonical and temporary components. struct Copier { Random& rand; - const std::vector& types; + const std::vector& types; TypeBuilder& builder; // For each type, the indices in `types` at which it appears. @@ -479,8 +479,7 @@ void Fuzzer::checkCanonicalization() { } void Fuzzer::checkInhabitable() { - std::vector inhabitable = - HeapTypeGenerator::makeInhabitable(types); + std::vector inhabitable = HeapTypeGenerator::makeInhabitable(types); if (verbose) { std::cout << "\nInhabitable types:\n\n"; printTypes(inhabitable); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index eb83f3dae59..e83645837fc 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -343,8 +343,6 @@ enum EncodedType { SubFinal = 0x4f, Shared = 0x65, SharedLEB = -0x1b, // Also 0x65 as an SLEB128 - Exact = 0x62, - ExactLEB = -0x1e, // Also 0x62 as an SLEB128 Rec = 0x4e, Descriptor = 0x4d, Describes = 0x4c, @@ -1458,7 +1456,7 @@ class WasmBinaryReader { SourceMapReader sourceMapReader; // All types defined in the type section - std::vector types; + std::vector types; public: WasmBinaryReader(Module& wasm, diff --git a/src/wasm-type-ordering.h b/src/wasm-type-ordering.h index f8248bdc91f..0f23b495308 100644 --- a/src/wasm-type-ordering.h +++ b/src/wasm-type-ordering.h @@ -29,12 +29,12 @@ namespace wasm::HeapTypeOrdering { // type in the sequence comes only after its immediate supertype in the // collection is visited. template -std::vector supertypesFirst( +std::vector supertypesFirst( const T& types, - std::function(HeapTypeDef)> getSuper = - [](HeapTypeDef type) { return type.getDeclaredSuperType(); }) { + std::function(HeapType)> getSuper = + [](HeapType type) { return type.getDeclaredSuperType(); }) { - InsertOrderedMap> subtypes; + InsertOrderedMap> subtypes; for (auto type : types) { subtypes.insert({type, {}}); } diff --git a/src/wasm-type-printing.h b/src/wasm-type-printing.h index e977a07188b..a44e949dfc3 100644 --- a/src/wasm-type-printing.h +++ b/src/wasm-type-printing.h @@ -34,18 +34,16 @@ namespace wasm { template struct TypeNameGeneratorBase { TypeNameGeneratorBase() { assertValidUsage(); } - TypeNames getNames(HeapTypeDef type) { + TypeNames getNames(HeapType type) { WASM_UNREACHABLE("Derived class must implement getNames"); } - HeapType::Printed operator()(HeapTypeDef type) { - return type.print([&](HeapTypeDef ht) { - return static_cast(this)->getNames(ht); - }); + HeapType::Printed operator()(HeapType type) { + return type.print( + [&](HeapType ht) { return static_cast(this)->getNames(ht); }); } Type::Printed operator()(Type type) { - return type.print([&](HeapTypeDef ht) { - return static_cast(this)->getNames(ht); - }); + return type.print( + [&](HeapType ht) { return static_cast(this)->getNames(ht); }); } private: @@ -54,8 +52,8 @@ template struct TypeNameGeneratorBase { // Check that the subclass provides `getNames` with the correct type. using Self = TypeNameGeneratorBase; static_assert( - static_cast(&Self::getNames) != - static_cast(&Subclass::getNames), + static_cast(&Self::getNames) != + static_cast(&Subclass::getNames), "Derived class must implement getNames"); #endif } @@ -73,7 +71,7 @@ struct DefaultTypeNameGenerator // Cached names for types that have already been seen. std::unordered_map nameCache; - TypeNames getNames(HeapTypeDef type); + TypeNames getNames(HeapType type); }; // Generates names based on the indices of types in some collection, falling @@ -84,7 +82,7 @@ struct IndexedTypeNameGenerator : TypeNameGeneratorBase> { DefaultTypeNameGenerator defaultGenerator; FallbackGenerator& fallback; - std::unordered_map names; + std::unordered_map names; template IndexedTypeNameGenerator(T& types, @@ -99,7 +97,7 @@ struct IndexedTypeNameGenerator IndexedTypeNameGenerator(T& types, const std::string& prefix = "") : IndexedTypeNameGenerator(types, defaultGenerator, prefix) {} - TypeNames getNames(HeapTypeDef type) { + TypeNames getNames(HeapType type) { if (auto it = names.find(type); it != names.end()) { return it->second; } else { @@ -130,7 +128,7 @@ struct ModuleTypeNameGenerator std::enable_if_t>* = nullptr) : ModuleTypeNameGenerator(wasm, defaultGenerator) {} - TypeNames getNames(HeapTypeDef type) { + TypeNames getNames(HeapType type) { if (auto it = wasm.typeNames.find(type); it != wasm.typeNames.end()) { return it->second; } diff --git a/src/wasm-type.h b/src/wasm-type.h index fb21976dc04..a72ba9d2cfa 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -50,7 +50,6 @@ void destroyAllTypesForTestingPurposesOnly(); // data. class Type; class HeapType; -class HeapTypeDef; class RecGroup; struct Signature; struct Continuation; @@ -63,7 +62,6 @@ using Tuple = TypeList; enum Nullability { NonNullable, Nullable }; enum Mutability { Immutable, Mutable }; -enum Exactness { Inexact, Exact }; // HeapType name information used for printing. struct TypeNames { @@ -74,7 +72,7 @@ struct TypeNames { }; // Used to generate HeapType names. -using HeapTypeNameGenerator = std::function; +using HeapTypeNameGenerator = std::function; // The type used for interning IDs in the public interfaces of Type and // HeapType. @@ -100,12 +98,10 @@ class HeapType { static constexpr int TypeBits = 2; static constexpr int UsedBits = TypeBits + 1; static constexpr int SharedMask = 1 << TypeBits; - static constexpr int ExactMask = SharedMask; public: - // Bits 0-1 are used by the Type representation, so need to be left free. Bit - // 2 determines whether a basic heap type is shared (1) or unshared (0). For - // non-basic heap types, bit 2 determines whether the type is exact instead. + // Bits 0-1 are used by the Type representation, so need to be left free. + // Bit 2 determines whether the basic heap type is shared (1) or unshared (0). enum BasicHeapType : uint32_t { ext = 1 << UsedBits, func = 2 << UsedBits, @@ -130,7 +126,7 @@ class HeapType { constexpr HeapType(BasicHeapType id) : id(id) {} // But converting raw TypeID is more dangerous, so make it explicit - explicit constexpr HeapType(TypeID id) : id(id) {} + explicit HeapType(TypeID id) : id(id) {} // Choose an arbitrary heap type as the default. constexpr HeapType() : HeapType(func) {} @@ -171,12 +167,8 @@ class HeapType { bool isBottom() const; bool isOpen() const; bool isShared() const { return getShared() == Shared; } - bool isExact() const { return getExactness() == Exact; } Shareability getShared() const; - Exactness getExactness() const { - return !isBasic() && (id & ExactMask) ? Exact : Inexact; - } // Check if the type is a given basic heap type, while ignoring whether it is // shared or not. @@ -225,6 +217,8 @@ class HeapType { // Get the index of this non-basic type within its recursion group. size_t getRecGroupIndex() const; + constexpr TypeID getID() const { return id; } + // Get the shared or unshared version of this basic heap type. constexpr BasicHeapType getBasic(Shareability share) const { assert(isBasic()); @@ -232,24 +226,6 @@ class HeapType { : (id & ~SharedMask)); } - constexpr HeapType with(Exactness exactness) const { - assert((!isBasic() || exactness == Inexact) && - "abstract types cannot be exact"); - return isBasic() ? *this - : HeapType(exactness == Exact ? (id | ExactMask) - : (id & ~ExactMask)); - } - - // The ID is the numeric representation of the heap type and can be used in - // FFI or hashing applications. The "raw" ID is the numeric representation of - // the plain version of the type without exactness or any other attributes we - // might add in the future. It's useful in contexts where all heap types using - // the same type definition need to be treated identically. - constexpr TypeID getID() const { return id; } - constexpr TypeID getRawID() const { - return isBasic() ? id : with(Inexact).id; - } - // (In)equality must be defined for both HeapType and BasicHeapType because it // is otherwise ambiguous whether to convert both this and other to int or // convert other to HeapType. @@ -295,16 +271,6 @@ class HeapType { std::string toString() const; }; -// Like `HeapType`, but used to represent heap type definitions and abstract -// heap types rather than arbitrary heap types. Use this whenever it would be a -// category error to use an exact heap type. -class HeapTypeDef : public HeapType { -public: - // Allow implicit conversions from HeapType. - constexpr HeapTypeDef(HeapType type) : HeapType(type.with(Inexact)) {} - constexpr HeapTypeDef() = default; -}; - class Type { // The `id` uniquely represents each type, so type equality is just a // comparison of the ids. The basic types are packed at the bottom of the @@ -855,14 +821,14 @@ struct TypeBuilder { ErrorReason reason; }; - struct BuildResult : std::variant, Error> { + struct BuildResult : std::variant, Error> { operator bool() const { - return bool(std::get_if>(this)); + return bool(std::get_if>(this)); } - const std::vector& operator*() const { - return std::get>(*this); + const std::vector& operator*() const { + return std::get>(*this); } - const std::vector* operator->() const { return &*(*this); } + const std::vector* operator->() const { return &*(*this); } const Error* getError() const { return std::get_if(this); } }; @@ -1018,10 +984,6 @@ template<> class hash { public: size_t operator()(const wasm::HeapType&) const; }; -template<> class hash { -public: - size_t operator()(const wasm::HeapTypeDef&) const; -}; template<> class hash { public: size_t operator()(const wasm::RecGroup&) const; diff --git a/src/wasm.h b/src/wasm.h index 3d4ce837b94..b6541fc6cf5 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2426,8 +2426,8 @@ class Module { // Module name, if specified. Serves a documentary role only. Name name; - std::unordered_map typeNames; - std::unordered_map typeIndices; + std::unordered_map typeNames; + std::unordered_map typeIndices; MixedArena allocator; diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index b2c5842f4a4..a841d44638d 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -719,7 +719,7 @@ uint32_t WasmBinaryWriter::getElementSegmentIndex(Name name) const { } uint32_t WasmBinaryWriter::getTypeIndex(HeapType type) const { - auto it = indexedTypes.indices.find(type.with(Inexact)); + auto it = indexedTypes.indices.find(type); #ifndef NDEBUG if (it == indexedTypes.indices.end()) { std::cout << "Missing type: " << type << '\n'; @@ -1683,10 +1683,8 @@ void WasmBinaryWriter::writeHeapType(HeapType type) { if (!wasm->features.hasGC()) { type = type.getTop(); } + if (!type.isBasic()) { - if (type.isExact()) { - o << uint8_t(BinaryConsts::EncodedType::Exact); - } o << S64LEB(getTypeIndex(type)); // TODO: Actually s33 return; } @@ -2200,20 +2198,12 @@ Type WasmBinaryReader::getType() { return getType(getS32LEB()); } HeapType WasmBinaryReader::getHeapType() { auto type = getS64LEB(); // TODO: Actually s33 - auto exactness = Inexact; - if (type == BinaryConsts::EncodedType::ExactLEB) { - exactness = Exact; - type = getS64LEB(); // TODO: Actually s33 - } // Single heap types are negative; heap type indices are non-negative if (type >= 0) { if (size_t(type) >= types.size()) { - throwError("invalid type index: " + std::to_string(type)); + throwError("invalid signature index: " + std::to_string(type)); } - return types[type].with(exactness); - } - if (exactness == Exact) { - throwError("invalid type index: " + std::to_string(type)); + return types[type]; } auto share = Unshared; if (type == BinaryConsts::EncodedType::SharedLEB) { @@ -2223,8 +2213,10 @@ HeapType WasmBinaryReader::getHeapType() { HeapType ht; if (getBasicHeapType(type, ht)) { return ht.getBasic(share); + } else { + throwError("invalid wasm heap type: " + std::to_string(type)); } - throwError("invalid wasm heap type: " + std::to_string(type)); + WASM_UNREACHABLE("unexpected type"); } HeapType WasmBinaryReader::getIndexedHeapType() { @@ -2348,20 +2340,6 @@ void WasmBinaryReader::readTypes() { auto readHeapType = [&]() -> HeapType { int64_t htCode = getS64LEB(); // TODO: Actually s33 - auto exactness = Inexact; - if (htCode == BinaryConsts::EncodedType::ExactLEB) { - exactness = Exact; - htCode = getS64LEB(); // TODO: Actually s33 - } - if (htCode >= 0) { - if (size_t(htCode) >= builder.size()) { - throwError("invalid type index: " + std::to_string(htCode)); - } - return builder.getTempHeapType(size_t(htCode)).with(exactness); - } - if (exactness == Exact) { - throwError("invalid type index: " + std::to_string(htCode)); - } auto share = Unshared; if (htCode == BinaryConsts::EncodedType::SharedLEB) { share = Shared; @@ -2371,7 +2349,10 @@ void WasmBinaryReader::readTypes() { if (getBasicHeapType(htCode, ht)) { return ht.getBasic(share); } - throwError("invalid wasm heap type: " + std::to_string(htCode)); + if (size_t(htCode) >= builder.size()) { + throwError("invalid type index: " + std::to_string(htCode)); + } + return builder.getTempHeapType(size_t(htCode)); }; auto makeType = [&](int32_t typeCode) { Type type; diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index be732fdad41..5cdb76c19dd 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -228,7 +228,7 @@ namespace { HeapTypeInfo* getHeapTypeInfo(HeapType ht) { assert(!ht.isBasic()); - return (HeapTypeInfo*)(ht.getRawID()); + return (HeapTypeInfo*)ht.getID(); } HeapType asHeapType(std::unique_ptr& info) { @@ -798,6 +798,7 @@ Type Type::getLeastUpperBound(Type a, Type b) { } } return Type::none; + WASM_UNREACHABLE("unexpected type"); } Type Type::getGreatestLowerBound(Type a, Type b) { @@ -1194,9 +1195,6 @@ std::optional HeapType::getLeastUpperBound(HeapType a, HeapType b) { return getBasicHeapTypeLUB(getBasicHeapSupertype(a), getBasicHeapSupertype(b)); } - if (a.with(Inexact) == b.with(Inexact)) { - return a.with(Inexact); - } auto* infoA = getHeapTypeInfo(a); auto* infoB = getHeapTypeInfo(b); @@ -1249,7 +1247,7 @@ RecGroup HeapType::getRecGroup() const { } else { // Mark the low bit to signify that this is a trivial recursion group and // points to a heap type info rather than a vector of heap types. - return RecGroup(getRawID() | 1); + return RecGroup(id | 1); } } @@ -1369,7 +1367,7 @@ size_t RecGroup::size() const { } } -TypeNames DefaultTypeNameGenerator::getNames(HeapTypeDef type) { +TypeNames DefaultTypeNameGenerator::getNames(HeapType type) { auto [it, inserted] = nameCache.insert({type, {}}); if (inserted) { // Generate a new name for this type we have not previously seen. @@ -1507,7 +1505,7 @@ bool SubTyper::isSubType(HeapType a, HeapType b) { // See: // https://github.com/WebAssembly/function-references/blob/master/proposals/function-references/Overview.md#subtyping // https://github.com/WebAssembly/gc/blob/master/proposals/gc/MVP.md#defined-types - if (a == b || a.with(Inexact) == b) { + if (a == b) { return true; } if (a.isShared() != b.isShared()) { @@ -1552,11 +1550,6 @@ bool SubTyper::isSubType(HeapType a, HeapType b) { // bottom types. return a == b.getBottom(); } - if (b.isExact()) { - // The only subtypes of an exact type are itself and bottom, both of which - // we have ruled out. - return false; - } // Subtyping must be declared rather than derived from structure, so we will // not recurse. TODO: optimize this search with some form of caching. HeapTypeInfo* curr = getHeapTypeInfo(a); @@ -1615,20 +1608,14 @@ bool SubTyper::isSubType(const Array& a, const Array& b) { } void TypePrinter::printHeapTypeName(HeapType type) { - if (type.isExact()) { - os << "(exact "; - } if (type.isBasic()) { print(type); - } else { - generator(type.with(Inexact)).name.print(os); + return; + } + generator(type).name.print(os); #if TRACE_CANONICALIZATION - os << "(;" << ((type.with(Inexact).getID() >> 4) % 1000) << ";) "; + os << "(;" << ((type.getID() >> 4) % 1000) << ";) "; #endif - } - if (type.isExact()) { - os << ')'; - } } std::ostream& TypePrinter::print(Type type) { @@ -1955,10 +1942,8 @@ size_t RecGroupHasher::hash(HeapType type) const { wasm::rehash(digest, type.getID()); return digest; } - wasm::rehash(digest, type.isExact()); wasm::rehash(digest, type.getRecGroupIndex()); auto currGroup = type.getRecGroup(); - wasm::rehash(digest, currGroup != group); if (currGroup != group) { wasm::rehash(digest, currGroup.getID()); } @@ -2088,9 +2073,6 @@ bool RecGroupEquator::eq(HeapType a, HeapType b) const { if (a.isBasic() || b.isBasic()) { return a == b; } - if (a.getExactness() != b.getExactness()) { - return false; - } if (a.getRecGroupIndex() != b.getRecGroupIndex()) { return false; } @@ -2474,10 +2456,8 @@ void updateReferencedHeapTypes( isTopLevel = false; if (type->isRef()) { auto ht = type->getHeapType(); - auto exact = ht.getExactness(); - ht = ht.with(Inexact); if (auto it = canonicalized.find(ht); it != canonicalized.end()) { - *type = Type(it->second.with(exact), type->getNullability()); + *type = Type(it->second, type->getNullability()); } } else if (type->isTuple()) { TypeGraphWalkerBase::scanType(type); @@ -2485,7 +2465,6 @@ void updateReferencedHeapTypes( } void scanHeapType(HeapType* type) { - assert(!type->isExact() && "unexpected exact type in definition"); if (isTopLevel) { isTopLevel = false; TypeGraphWalkerBase::scanHeapType(type); @@ -2550,8 +2529,7 @@ buildRecGroup(std::unique_ptr&& groupInfo, for (size_t i = 0; i < typeInfos.size(); ++i) { auto type = asHeapType(typeInfos[i]); for (auto child : type.getHeapTypeChildren()) { - HeapType rawChild(child.getRawID()); - if (isTemp(rawChild) && !seenTypes.count(rawChild)) { + if (isTemp(child) && !seenTypes.count(child)) { return {TypeBuilder::Error{ i, TypeBuilder::ErrorReason::ForwardChildReference}}; } @@ -2574,7 +2552,7 @@ buildRecGroup(std::unique_ptr&& groupInfo, canonicalized.insert({group[i], canonical[i]}); } // Return the canonical types. - return {std::vector(canonical.begin(), canonical.end())}; + return {std::vector(canonical.begin(), canonical.end())}; } // The group was successfully moved to the global rec group store, so it is @@ -2588,7 +2566,7 @@ buildRecGroup(std::unique_ptr&& groupInfo, } } - std::vector results(group.begin(), group.end()); + std::vector results(group.begin(), group.end()); // We need to make the tuples canonical as well, but right now there is no way // to move them to their global store, so we have to create new tuples and @@ -2622,7 +2600,7 @@ buildRecGroup(std::unique_ptr&& groupInfo, TypeBuilder::BuildResult TypeBuilder::build() { size_t entryCount = impl->entries.size(); - std::vector results; + std::vector results; results.reserve(entryCount); // Map temporary HeapTypes to their canonicalized versions so they can be @@ -2768,10 +2746,6 @@ size_t hash::operator()(const wasm::HeapType& heapType) const { return wasm::hash(heapType.getID()); } -size_t hash::operator()(const wasm::HeapTypeDef& def) const { - return wasm::hash(def.getID()); -} - size_t hash::operator()(const wasm::RecGroup& group) const { return wasm::hash(group.getID()); } diff --git a/test/gtest/possible-contents.cpp b/test/gtest/possible-contents.cpp index 9d0767595f7..f267742a71b 100644 --- a/test/gtest/possible-contents.cpp +++ b/test/gtest/possible-contents.cpp @@ -349,7 +349,7 @@ TEST_F(PossibleContentsTest, TestIntersectWithCombinations) { std::vector vec(set.begin(), set.end()); // Find the maximum depths for the normalized cone tests later down. - std::unordered_set heapTypes; + std::unordered_set heapTypes; for (auto& contents : set) { auto type = contents.getType(); if (type.isRef()) { @@ -359,7 +359,7 @@ TEST_F(PossibleContentsTest, TestIntersectWithCombinations) { } } } - std::vector heapTypesVec(heapTypes.begin(), heapTypes.end()); + std::vector heapTypesVec(heapTypes.begin(), heapTypes.end()); SubTypes subTypes(heapTypesVec); auto maxDepths = subTypes.getMaxDepths(); diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index 66a7526f095..1e676b7194a 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -185,7 +185,7 @@ TEST_F(TypeTest, Basics) { auto result = builder.build(); ASSERT_TRUE(result); - std::vector built = *result; + std::vector built = *result; ASSERT_EQ(built.size(), size_t{3}); // The built types should have the correct kinds. @@ -428,93 +428,6 @@ TEST_F(TypeTest, CanonicalizeUses) { EXPECT_NE(built[4], built[6]); } -TEST_F(TypeTest, CanonicalizeExactHeapTypes) { - TypeBuilder builder(8); - - HeapType inexact = HeapType(builder[0]).with(Inexact); - HeapType exact = HeapType(builder[1]).with(Exact); - - Type inexactRef = builder.getTempRefType(inexact, Nullable); - Type exactRef = builder.getTempRefType(exact, Nullable); - - // Types that vary in exactness of the referenced heap type are different. - builder[0] = Struct({Field(inexactRef, Mutable)}); - builder[1] = Struct({Field(exactRef, Mutable)}); - builder[2] = Signature(Type({inexactRef, exactRef}), Type::none); - builder[3] = Signature(Type::none, Type({exactRef, inexactRef})); - - auto translate = [&](HeapType t) { - for (int i = 0; i < 4; ++i) { - if (t.with(Inexact) == builder[i]) { - return HeapType(builder[4 + i]).with(t.getExactness()); - } - } - WASM_UNREACHABLE("unexpected type"); - }; - - builder[4].copy(builder[0], translate); - builder[5].copy(builder[1], translate); - builder[6].copy(builder[2], translate); - builder[7].copy(builder[3], translate); - - auto result = builder.build(); - ASSERT_TRUE(result); - auto built = *result; - - // Different types should be different. - EXPECT_NE(built[0], built[1]); - EXPECT_NE(built[0], built[2]); - EXPECT_NE(built[0], built[3]); - EXPECT_NE(built[1], built[2]); - EXPECT_NE(built[1], built[3]); - EXPECT_NE(built[2], built[3]); - - // Copies of the types should match. - EXPECT_EQ(built[0], built[4]); - EXPECT_EQ(built[1], built[5]); - EXPECT_EQ(built[2], built[6]); - EXPECT_EQ(built[3], built[7]); - - // A type is inexact by default. - EXPECT_EQ(built[0], built[0].with(Inexact)); - EXPECT_EQ(built[1], built[1].with(Inexact)); - EXPECT_EQ(built[2], built[2].with(Inexact)); - EXPECT_EQ(built[3], built[3].with(Inexact)); - - // We can freely convert between exact and inexact. - EXPECT_EQ(built[0], built[0].with(Exact).with(Inexact)); - EXPECT_EQ(built[0].with(Exact), - built[0].with(Exact).with(Inexact).with(Exact)); - - // Conversions are idempotent. - EXPECT_EQ(built[0].with(Exact), built[0].with(Exact).with(Exact)); - EXPECT_EQ(built[0], built[0].with(Inexact)); - - // An exact version of a type is not the same as its inexact version. - EXPECT_NE(built[0].with(Exact), built[0].with(Inexact)); - - // But they have the same rec group. - EXPECT_EQ(built[0].with(Exact).getRecGroup(), - built[0].with(Inexact).getRecGroup()); - - // Looking up the inner structure works either way. - ASSERT_TRUE(built[0].with(Exact).isStruct()); - ASSERT_TRUE(built[0].with(Inexact).isStruct()); - EXPECT_EQ(built[0].with(Exact).getStruct(), - built[0].with(Inexact).getStruct()); - - // The exactness of children types is preserved. - EXPECT_EQ(built[0], built[0].getStruct().fields[0].type.getHeapType()); - EXPECT_EQ(built[1].with(Exact), - built[1].getStruct().fields[0].type.getHeapType()); - EXPECT_EQ(built[0], built[2].getSignature().params[0].getHeapType()); - EXPECT_EQ(built[1].with(Exact), - built[2].getSignature().params[1].getHeapType()); - EXPECT_EQ(built[0], built[3].getSignature().results[1].getHeapType()); - EXPECT_EQ(built[1].with(Exact), - built[3].getSignature().results[0].getHeapType()); -} - TEST_F(TypeTest, CanonicalizeSelfReferences) { TypeBuilder builder(5); // Single self-reference @@ -701,16 +614,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { HeapType nofunc = HeapType::nofunc; HeapType nocont = HeapType::nocont; HeapType defFunc = Signature(); - HeapType exactDefFunc = defFunc.with(Exact); HeapType defCont = Continuation(defFunc); - HeapType defStruct; - HeapType exactDefStruct; - HeapType subStruct; - HeapType exactSubStruct; - HeapType subStruct2; - HeapType exactSubStruct2; + HeapType defStruct = Struct(); HeapType defArray = Array(Field(Type::i32, Immutable)); - HeapType exactDefArray = defArray.with(Exact); HeapType sharedAny = any.getBasic(Shared); HeapType sharedEq = eq.getBasic(Shared); HeapType sharedI31 = i31.getBasic(Shared); @@ -721,25 +627,16 @@ TEST_F(TypeTest, TestHeapTypeRelations) { HeapType sharedDefStruct; HeapType sharedDefFunc; { - TypeBuilder builder(5); - builder[0].setShared() = Struct{}; - builder[1].setShared() = Signature(); - builder[2].setOpen() = Struct{}; - builder[3].subTypeOf(builder[2]) = Struct{}; - builder[4].copy(builder[3]); - builder.createRecGroup(3, 2); + TypeBuilder builder(2); + builder[0] = Struct{}; + builder[1] = Signature(); + builder[0].setShared(); + builder[1].setShared(); auto results = builder.build(); ASSERT_TRUE(results); auto built = *results; sharedDefStruct = built[0]; sharedDefFunc = built[1]; - defStruct = built[2]; - subStruct = built[3]; - subStruct2 = built[4]; - ASSERT_NE(subStruct, subStruct2); - exactDefStruct = defStruct.with(Exact); - exactSubStruct = subStruct.with(Exact); - exactSubStruct2 = subStruct2.with(Exact); } auto assertLUB = [](HeapType a, HeapType b, std::optional lub) { @@ -789,13 +686,8 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(ext, nofunc, {}); assertLUB(ext, nocont, {}); assertLUB(ext, defFunc, {}); - assertLUB(ext, exactDefFunc, {}); assertLUB(ext, defStruct, {}); - assertLUB(ext, exactDefStruct, {}); - assertLUB(ext, subStruct, {}); - assertLUB(ext, exactSubStruct, {}); assertLUB(ext, defArray, {}); - assertLUB(ext, exactDefArray, {}); assertLUB(ext, sharedAny, {}); assertLUB(ext, sharedEq, {}); assertLUB(ext, sharedI31, {}); @@ -818,14 +710,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(func, nofunc, func); assertLUB(func, nocont, {}); assertLUB(func, defFunc, func); - assertLUB(func, exactDefFunc, func); assertLUB(func, defCont, {}); assertLUB(func, defStruct, {}); - assertLUB(func, exactDefStruct, {}); - assertLUB(func, subStruct, {}); - assertLUB(func, exactSubStruct, {}); assertLUB(func, defArray, {}); - assertLUB(func, exactDefArray, {}); assertLUB(func, sharedAny, {}); assertLUB(func, sharedEq, {}); assertLUB(func, sharedI31, {}); @@ -848,14 +735,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(cont, nofunc, {}); assertLUB(cont, nocont, cont); assertLUB(cont, defFunc, {}); - assertLUB(cont, exactDefFunc, {}); assertLUB(cont, defCont, cont); assertLUB(cont, defStruct, {}); - assertLUB(cont, exactDefStruct, {}); - assertLUB(cont, subStruct, {}); - assertLUB(cont, exactSubStruct, {}); assertLUB(cont, defArray, {}); - assertLUB(cont, exactDefArray, {}); assertLUB(cont, sharedAny, {}); assertLUB(cont, sharedEq, {}); assertLUB(cont, sharedI31, {}); @@ -877,14 +759,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(any, nofunc, {}); assertLUB(any, nocont, {}); assertLUB(any, defFunc, {}); - assertLUB(any, exactDefFunc, {}); assertLUB(any, defCont, {}); assertLUB(any, defStruct, any); - assertLUB(any, exactDefStruct, any); - assertLUB(any, subStruct, any); - assertLUB(any, exactSubStruct, any); assertLUB(any, defArray, any); - assertLUB(any, exactDefArray, any); assertLUB(any, sharedAny, {}); assertLUB(any, sharedEq, {}); assertLUB(any, sharedI31, {}); @@ -905,14 +782,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(eq, nofunc, {}); assertLUB(eq, nocont, {}); assertLUB(eq, defFunc, {}); - assertLUB(eq, exactDefFunc, {}); assertLUB(eq, defCont, {}); assertLUB(eq, defStruct, eq); - assertLUB(eq, exactDefStruct, eq); - assertLUB(eq, subStruct, eq); - assertLUB(eq, exactSubStruct, eq); assertLUB(eq, defArray, eq); - assertLUB(eq, exactDefArray, eq); assertLUB(eq, sharedAny, {}); assertLUB(eq, sharedEq, {}); assertLUB(eq, sharedI31, {}); @@ -932,14 +804,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(i31, nofunc, {}); assertLUB(i31, nocont, {}); assertLUB(i31, defFunc, {}); - assertLUB(i31, exactDefFunc, {}); assertLUB(i31, defCont, {}); assertLUB(i31, defStruct, eq); - assertLUB(i31, exactDefStruct, eq); - assertLUB(i31, subStruct, eq); - assertLUB(i31, exactSubStruct, eq); assertLUB(i31, defArray, eq); - assertLUB(i31, exactDefArray, eq); assertLUB(i31, sharedAny, {}); assertLUB(i31, sharedEq, {}); assertLUB(i31, sharedI31, {}); @@ -958,14 +825,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(struct_, nofunc, {}); assertLUB(struct_, nocont, {}); assertLUB(struct_, defFunc, {}); - assertLUB(struct_, exactDefFunc, {}); assertLUB(struct_, defCont, {}); assertLUB(struct_, defStruct, struct_); - assertLUB(struct_, exactDefStruct, struct_); - assertLUB(struct_, subStruct, struct_); - assertLUB(struct_, exactSubStruct, struct_); assertLUB(struct_, defArray, eq); - assertLUB(struct_, exactDefArray, eq); assertLUB(struct_, sharedAny, {}); assertLUB(struct_, sharedEq, {}); assertLUB(struct_, sharedI31, {}); @@ -983,14 +845,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(array, nofunc, {}); assertLUB(array, nocont, {}); assertLUB(array, defFunc, {}); - assertLUB(array, exactDefFunc, {}); assertLUB(array, defCont, {}); assertLUB(array, defStruct, eq); - assertLUB(array, exactDefStruct, eq); - assertLUB(array, subStruct, eq); - assertLUB(array, exactSubStruct, eq); assertLUB(array, defArray, array); - assertLUB(array, exactDefArray, array); assertLUB(array, sharedAny, {}); assertLUB(array, sharedEq, {}); assertLUB(array, sharedI31, {}); @@ -1007,14 +864,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(string, nofunc, {}); assertLUB(string, nocont, {}); assertLUB(string, defFunc, {}); - assertLUB(string, exactDefFunc, {}); assertLUB(string, defCont, {}); assertLUB(string, defStruct, {}); - assertLUB(string, exactDefStruct, {}); - assertLUB(string, subStruct, {}); - assertLUB(string, exactSubStruct, {}); assertLUB(string, defArray, {}); - assertLUB(string, exactDefArray, {}); assertLUB(string, sharedAny, {}); assertLUB(string, sharedEq, {}); assertLUB(string, sharedI31, {}); @@ -1029,14 +881,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(none, nofunc, {}); assertLUB(none, nocont, {}); assertLUB(none, defFunc, {}); - assertLUB(none, exactDefFunc, {}); assertLUB(none, defCont, {}); assertLUB(none, defStruct, defStruct); - assertLUB(none, exactDefStruct, exactDefStruct); - assertLUB(none, subStruct, subStruct); - assertLUB(none, exactSubStruct, exactSubStruct); assertLUB(none, defArray, defArray); - assertLUB(none, exactDefArray, exactDefArray); assertLUB(none, sharedAny, {}); assertLUB(none, sharedEq, {}); assertLUB(none, sharedI31, {}); @@ -1050,14 +897,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(noext, nofunc, {}); assertLUB(noext, nocont, {}); assertLUB(noext, defFunc, {}); - assertLUB(noext, exactDefFunc, {}); assertLUB(noext, defCont, {}); assertLUB(noext, defStruct, {}); - assertLUB(noext, exactDefStruct, {}); - assertLUB(noext, subStruct, {}); - assertLUB(noext, exactSubStruct, {}); assertLUB(noext, defArray, {}); - assertLUB(noext, exactDefArray, {}); assertLUB(noext, sharedAny, {}); assertLUB(noext, sharedEq, {}); assertLUB(noext, sharedI31, {}); @@ -1070,14 +912,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(nofunc, nofunc, nofunc); assertLUB(nofunc, nocont, {}); assertLUB(nofunc, defFunc, defFunc); - assertLUB(nofunc, exactDefFunc, exactDefFunc); assertLUB(nofunc, defCont, {}); assertLUB(nofunc, defStruct, {}); - assertLUB(nofunc, exactDefStruct, {}); - assertLUB(nofunc, subStruct, {}); - assertLUB(nofunc, exactSubStruct, {}); assertLUB(nofunc, defArray, {}); - assertLUB(nofunc, exactDefArray, {}); assertLUB(nofunc, sharedAny, {}); assertLUB(nofunc, sharedEq, {}); assertLUB(nofunc, sharedI31, {}); @@ -1092,14 +929,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(nocont, cont, cont); assertLUB(nocont, nofunc, {}); assertLUB(nocont, defFunc, {}); - assertLUB(nocont, exactDefFunc, {}); assertLUB(nocont, defCont, defCont); assertLUB(nocont, defStruct, {}); - assertLUB(nocont, exactDefStruct, {}); - assertLUB(nocont, subStruct, {}); - assertLUB(nocont, exactSubStruct, {}); assertLUB(nocont, defArray, {}); - assertLUB(nocont, exactDefArray, {}); assertLUB(nocont, sharedAny, {}); assertLUB(nocont, sharedEq, {}); assertLUB(nocont, sharedI31, {}); @@ -1110,14 +942,9 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(nocont, sharedDefFunc, {}); assertLUB(defFunc, defFunc, defFunc); - assertLUB(defFunc, exactDefFunc, defFunc); assertLUB(defFunc, defCont, {}); assertLUB(defFunc, defStruct, {}); - assertLUB(defFunc, exactDefStruct, {}); - assertLUB(defFunc, subStruct, {}); - assertLUB(defFunc, exactSubStruct, {}); assertLUB(defFunc, defArray, {}); - assertLUB(defFunc, exactDefArray, {}); assertLUB(defFunc, sharedAny, {}); assertLUB(defFunc, sharedEq, {}); assertLUB(defFunc, sharedI31, {}); @@ -1127,31 +954,10 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(defFunc, sharedDefStruct, {}); assertLUB(defFunc, sharedDefFunc, {}); - assertLUB(exactDefFunc, exactDefFunc, exactDefFunc); - assertLUB(exactDefFunc, defCont, {}); - assertLUB(exactDefFunc, defStruct, {}); - assertLUB(exactDefFunc, exactDefStruct, {}); - assertLUB(exactDefFunc, subStruct, {}); - assertLUB(exactDefFunc, exactSubStruct, {}); - assertLUB(exactDefFunc, defArray, {}); - assertLUB(exactDefFunc, exactDefArray, {}); - assertLUB(exactDefFunc, sharedAny, {}); - assertLUB(exactDefFunc, sharedEq, {}); - assertLUB(exactDefFunc, sharedI31, {}); - assertLUB(exactDefFunc, sharedStruct, {}); - assertLUB(exactDefFunc, sharedNone, {}); - assertLUB(exactDefFunc, sharedFunc, {}); - assertLUB(exactDefFunc, sharedDefStruct, {}); - assertLUB(exactDefFunc, sharedDefFunc, {}); - assertLUB(defCont, defCont, defCont); assertLUB(defCont, defFunc, {}); assertLUB(defCont, defStruct, {}); - assertLUB(defCont, exactDefStruct, {}); - assertLUB(defCont, subStruct, {}); - assertLUB(defCont, exactSubStruct, {}); assertLUB(defCont, defArray, {}); - assertLUB(defCont, exactDefArray, {}); assertLUB(defCont, sharedAny, {}); assertLUB(defCont, sharedEq, {}); assertLUB(defCont, sharedI31, {}); @@ -1162,11 +968,7 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(defCont, sharedDefFunc, {}); assertLUB(defStruct, defStruct, defStruct); - assertLUB(defStruct, exactDefStruct, defStruct); - assertLUB(defStruct, subStruct, defStruct); - assertLUB(defStruct, exactSubStruct, defStruct); assertLUB(defStruct, defArray, eq); - assertLUB(defStruct, exactDefArray, eq); assertLUB(defStruct, sharedAny, {}); assertLUB(defStruct, sharedEq, {}); assertLUB(defStruct, sharedI31, {}); @@ -1176,51 +978,7 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(defStruct, sharedDefStruct, {}); assertLUB(defStruct, sharedDefFunc, {}); - assertLUB(exactDefStruct, exactDefStruct, exactDefStruct); - assertLUB(exactDefStruct, subStruct, defStruct); - assertLUB(exactDefStruct, exactSubStruct, defStruct); - assertLUB(exactDefStruct, defArray, eq); - assertLUB(exactDefStruct, exactDefArray, eq); - assertLUB(exactDefStruct, sharedAny, {}); - assertLUB(exactDefStruct, sharedEq, {}); - assertLUB(exactDefStruct, sharedI31, {}); - assertLUB(exactDefStruct, sharedStruct, {}); - assertLUB(exactDefStruct, sharedNone, {}); - assertLUB(exactDefStruct, sharedFunc, {}); - assertLUB(exactDefStruct, sharedDefStruct, {}); - assertLUB(exactDefStruct, sharedDefFunc, {}); - - assertLUB(subStruct, subStruct, subStruct); - assertLUB(subStruct, exactSubStruct, subStruct); - assertLUB(subStruct, subStruct2, defStruct); - assertLUB(subStruct, exactSubStruct2, defStruct); - assertLUB(subStruct, defArray, eq); - assertLUB(subStruct, exactDefArray, eq); - assertLUB(subStruct, sharedAny, {}); - assertLUB(subStruct, sharedEq, {}); - assertLUB(subStruct, sharedI31, {}); - assertLUB(subStruct, sharedStruct, {}); - assertLUB(subStruct, sharedNone, {}); - assertLUB(subStruct, sharedFunc, {}); - assertLUB(subStruct, sharedDefStruct, {}); - assertLUB(subStruct, sharedDefFunc, {}); - - assertLUB(exactSubStruct, exactSubStruct, exactSubStruct); - assertLUB(exactSubStruct, subStruct2, defStruct); - assertLUB(exactSubStruct, exactSubStruct2, defStruct); - assertLUB(exactSubStruct, defArray, eq); - assertLUB(exactSubStruct, exactDefArray, eq); - assertLUB(exactSubStruct, sharedAny, {}); - assertLUB(exactSubStruct, sharedEq, {}); - assertLUB(exactSubStruct, sharedI31, {}); - assertLUB(exactSubStruct, sharedStruct, {}); - assertLUB(exactSubStruct, sharedNone, {}); - assertLUB(exactSubStruct, sharedFunc, {}); - assertLUB(exactSubStruct, sharedDefStruct, {}); - assertLUB(exactSubStruct, sharedDefFunc, {}); - assertLUB(defArray, defArray, defArray); - assertLUB(defArray, exactDefArray, defArray); assertLUB(defArray, sharedAny, {}); assertLUB(defArray, sharedEq, {}); assertLUB(defArray, sharedI31, {}); @@ -1230,16 +988,6 @@ TEST_F(TypeTest, TestHeapTypeRelations) { assertLUB(defArray, sharedDefStruct, {}); assertLUB(defArray, sharedDefFunc, {}); - assertLUB(exactDefArray, exactDefArray, exactDefArray); - assertLUB(exactDefArray, sharedAny, {}); - assertLUB(exactDefArray, sharedEq, {}); - assertLUB(exactDefArray, sharedI31, {}); - assertLUB(exactDefArray, sharedStruct, {}); - assertLUB(exactDefArray, sharedNone, {}); - assertLUB(exactDefArray, sharedFunc, {}); - assertLUB(exactDefArray, sharedDefStruct, {}); - assertLUB(exactDefArray, sharedDefFunc, {}); - assertLUB(sharedAny, sharedAny, sharedAny); assertLUB(sharedAny, sharedEq, sharedAny); assertLUB(sharedAny, sharedI31, sharedAny); diff --git a/test/gtest/type-domains.cpp b/test/gtest/type-domains.cpp index b4f178a759e..deb9d0a1dad 100644 --- a/test/gtest/type-domains.cpp +++ b/test/gtest/type-domains.cpp @@ -940,7 +940,7 @@ fuzztest::Domain StepTypeDefinition(TypeBuilderPlan plan) { WASM_UNREACHABLE("unexpected kind"); } -std::vector BuildHeapTypes(TypeBuilderPlan plan) { +std::vector BuildHeapTypes(TypeBuilderPlan plan) { // Continuation types without reachable function types need a fallback. TypeBuilder fallbackBuilder(2); fallbackBuilder[0] = Signature(); @@ -1062,7 +1062,7 @@ auto ArbitraryDefinedHeapTypesAndPlan() { ArbitraryTypeBuilderPlan()); } -void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { +void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { auto types = std::move(pair.first); auto plan = std::move(pair.second); @@ -1138,7 +1138,7 @@ void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { } }; - auto checkDef = [&](TypeDefPlan& plan, HeapTypeDef type) { + auto checkDef = [&](TypeDefPlan& plan, HeapType type) { if (auto* f = plan.getFunc()) { checkFunc(*f, type); } else if (auto* s = plan.getStruct()) { @@ -1168,19 +1168,6 @@ void TestBuiltTypes(std::pair, TypeBuilderPlan> pair) { FUZZ_TEST(TypeBuilderDomainsTest, TestBuiltTypes) .WithDomains(ArbitraryDefinedHeapTypesAndPlan()); -fuzztest::Domain> ArbitraryDefinedHeapTypeDefs() { - return fuzztest::Map(BuildHeapTypes, ArbitraryTypeBuilderPlan()); -} - -std::vector convertToHeapTypes(std::vector defs) { - std::vector types; - types.reserve(defs.size()); - for (auto def : defs) { - types.push_back(HeapType(def)); - } - return types; -} - } // anonymous namespace fuzztest::Domain ArbitraryTypeBuilderPlan() { @@ -1190,7 +1177,7 @@ fuzztest::Domain ArbitraryTypeBuilderPlan() { } fuzztest::Domain> ArbitraryDefinedHeapTypes() { - return fuzztest::Map(convertToHeapTypes, ArbitraryDefinedHeapTypeDefs()); + return fuzztest::Map(BuildHeapTypes, ArbitraryTypeBuilderPlan()); } fuzztest::Domain> ArbitraryHeapTypePair() { diff --git a/test/gtest/type-domains.h b/test/gtest/type-domains.h index b2fc268cc75..17ad7fe9530 100644 --- a/test/gtest/type-domains.h +++ b/test/gtest/type-domains.h @@ -134,7 +134,7 @@ struct TypeBuilderPlan { std::vector defs; // Built types. - std::vector types; + std::vector types; friend std::ostream& operator<<(std::ostream& o, const TypeBuilderPlan& plan); }; diff --git a/test/lit/basic/exact.wast b/test/lit/basic/exact.wast deleted file mode 100644 index 13adca540ee..00000000000 --- a/test/lit/basic/exact.wast +++ /dev/null @@ -1,52 +0,0 @@ -;; 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-TEXT -;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN -;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG - -(module - (rec - ;; CHECK-TEXT: (rec - ;; CHECK-TEXT-NEXT: (type $a (struct (field (ref null (exact $a))) (field (ref (exact $b))))) - ;; CHECK-BIN: (rec - ;; CHECK-BIN-NEXT: (type $a (struct (field (ref null (exact $a))) (field (ref (exact $b))))) - (type $a (struct (field (ref null (exact 0)) (ref (exact 1))))) - ;; CHECK-TEXT: (type $b (struct (field (ref (exact $a))) (field (ref null (exact $b))))) - ;; CHECK-BIN: (type $b (struct (field (ref (exact $a))) (field (ref null (exact $b))))) - (type $b (struct (field (ref (exact $a)) (ref null (exact $b))))) - ) - - ;; CHECK-TEXT: (type $2 (func (param (ref null (exact $a)) (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))))) - - ;; CHECK-TEXT: (func $foo (type $2) (param $0 (ref null (exact $a))) (param $1 (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))) - ;; CHECK-TEXT-NEXT: (local $2 (ref null (exact $a))) - ;; CHECK-TEXT-NEXT: (unreachable) - ;; CHECK-TEXT-NEXT: ) - ;; CHECK-BIN: (type $2 (func (param (ref null (exact $a)) (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))))) - - ;; CHECK-BIN: (func $foo (type $2) (param $0 (ref null (exact $a))) (param $1 (ref (exact $b))) (result (ref (exact $a)) (ref null (exact $b))) - ;; CHECK-BIN-NEXT: (local $2 (ref null (exact $a))) - ;; CHECK-BIN-NEXT: (unreachable) - ;; CHECK-BIN-NEXT: ) - (func $foo (param (ref null (exact $a)) (ref (exact $b))) - (result (ref (exact $a)) (ref null (exact $b))) - (local (ref null (exact $a))) - (unreachable) - ) -) -;; CHECK-BIN-NODEBUG: (rec -;; CHECK-BIN-NODEBUG-NEXT: (type $0 (struct (field (ref null (exact $0))) (field (ref (exact $1))))) - -;; CHECK-BIN-NODEBUG: (type $1 (struct (field (ref (exact $0))) (field (ref null (exact $1))))) - -;; CHECK-BIN-NODEBUG: (type $2 (func (param (ref null (exact $0)) (ref (exact $1))) (result (ref (exact $0)) (ref null (exact $1))))) - -;; CHECK-BIN-NODEBUG: (func $0 (type $2) (param $0 (ref null (exact $0))) (param $1 (ref (exact $1))) (result (ref (exact $0)) (ref null (exact $1))) -;; CHECK-BIN-NODEBUG-NEXT: (local $2 (ref null (exact $0))) -;; CHECK-BIN-NODEBUG-NEXT: (unreachable) -;; CHECK-BIN-NODEBUG-NEXT: ) From a0633edb94b0095e980aca125579b3733f3cdd24 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Mon, 7 Apr 2025 14:58:11 -0700 Subject: [PATCH 2/3] Restore exact references Having decided that storing exactness on the Type rather than HeapType makes the most sense for our IR, revert the following PRs: - #7404 - #7402 This will restore the original work on exact references. Follow-on PRs will update it to match the current spec. --- scripts/test/fuzzing.py | 8 + src/ir/manipulation.h | 5 +- src/ir/type-updating.cpp | 1 + src/literal.h | 2 +- src/parser/contexts.h | 18 +- src/parser/parsers.h | 73 +- src/passes/OptimizeInstructions.cpp | 30 +- src/passes/RemoveUnusedBrs.cpp | 4 +- src/tools/fuzzing.h | 6 + src/tools/fuzzing/fuzzing.cpp | 75 +- src/wasm-binary.h | 11 + src/wasm-builder.h | 10 +- src/wasm-type.h | 41 +- src/wasm/literal.cpp | 4 +- src/wasm/wasm-binary.cpp | 56 +- src/wasm/wasm-stack.cpp | 52 +- src/wasm/wasm-type.cpp | 110 ++- src/wasm/wasm-validator.cpp | 4 + src/wasm/wasm.cpp | 22 +- test/example/c-api-kitchen-sink.txt | 22 +- test/gtest/type-builder.cpp | 161 ++++- test/lit/basic/exact-references.wast | 672 ++++++++++++++++++ test/lit/basic/reference-types.wast | 16 +- .../lit/ctor-eval/materialize-null-local.wast | 26 + test/lit/exec/exact.wast | 21 + test/lit/heap-types.wast | 4 +- test/lit/passes/cfp.wast | 4 +- test/lit/passes/coalesce-locals-exact.wast | 47 ++ test/lit/passes/code-pushing-gc.wast | 4 +- test/lit/passes/dae-gc-refine-params.wast | 2 +- test/lit/passes/dae-gc.wast | 2 +- test/lit/passes/flatten_all-features.wast | 6 +- test/lit/passes/global-refining.wast | 18 +- test/lit/passes/gufa-refs.wast | 52 +- test/lit/passes/gufa-vs-cfp.wast | 4 +- test/lit/passes/heap2local-rmw.wast | 38 +- test/lit/passes/heap2local.wast | 178 ++--- test/lit/passes/local-subtyping-exact.wast | 39 + test/lit/passes/local-subtyping-nn.wast | 4 +- test/lit/passes/local-subtyping.wast | 2 +- test/lit/passes/merge-blocks.wast | 2 +- test/lit/passes/monomorphize-context.wast | 4 +- .../passes/optimize-instructions-exact.wast | 33 + .../passes/optimize-instructions-gc-tnh.wast | 4 +- test/lit/passes/optimize-instructions-gc.wast | 4 +- test/lit/passes/precompute-gc.wast | 2 +- test/lit/passes/remove-unused-brs-exact.wast | 40 ++ test/lit/passes/remove-unused-brs-gc.wast | 24 +- .../lit/passes/remove-unused-types-exact.wast | 16 + test/lit/passes/signature-refining_gto.wat | 4 +- test/lit/passes/ssa.wast | 4 +- test/lit/passes/type-refining-gufa.wast | 2 +- .../passes/type-refining-isorecursive.wast | 6 +- test/lit/passes/type-refining-rmw.wast | 4 +- test/lit/passes/type-refining.wast | 14 +- test/passes/precompute_all-features.txt | 2 +- 56 files changed, 1665 insertions(+), 354 deletions(-) create mode 100644 test/lit/basic/exact-references.wast create mode 100644 test/lit/ctor-eval/materialize-null-local.wast create mode 100644 test/lit/exec/exact.wast create mode 100644 test/lit/passes/coalesce-locals-exact.wast create mode 100644 test/lit/passes/local-subtyping-exact.wast create mode 100644 test/lit/passes/optimize-instructions-exact.wast create mode 100644 test/lit/passes/remove-unused-brs-exact.wast create mode 100644 test/lit/passes/remove-unused-types-exact.wast diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 50ab56683fa..a65d3a92a21 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -111,6 +111,14 @@ 'dce-stack-switching.wast', 'precompute-stack-switching.wast', 'vacuum-stack-switching.wast', + # TODO: fuzzer support for exact references + 'exact-references.wast', + 'optimize-instructions-exact.wast', + 'local-subtyping-exact.wast', + 'remove-unused-types-exact.wast', + 'coalesce-locals-exact.wast', + 'remove-unused-brs-exact.wast', + 'exact.wast', # TODO: fuzzer support for custom descriptors 'custom-descriptors.wast', ] diff --git a/src/ir/manipulation.h b/src/ir/manipulation.h index e7816af9fce..c766fb8719e 100644 --- a/src/ir/manipulation.h +++ b/src/ir/manipulation.h @@ -40,10 +40,9 @@ template inline Nop* nop(InputType* target) { } template -inline RefNull* refNull(InputType* target, Type type) { - assert(type.isNullable() && type.getHeapType().isBottom()); +inline RefNull* refNull(InputType* target, HeapType type) { auto* ret = convert(target); - ret->finalize(type); + ret->finalize(Type(type.getBottom(), Nullable, Exact)); return ret; } diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 6ea5b0d87cd..59286734720 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -346,6 +346,7 @@ Type GlobalTypeRewriter::getTempType(Type type) { if (type.isRef()) { auto heapType = type.getHeapType(); if (auto it = typeIndices.find(heapType); it != typeIndices.end()) { + // TODO: Handle exactness. return typeBuilder.getTempRefType(typeBuilder[it->second], type.getNullability()); } diff --git a/src/literal.h b/src/literal.h index c3cf2c15f70..4f9e3d535ad 100644 --- a/src/literal.h +++ b/src/literal.h @@ -246,7 +246,7 @@ class Literal { } } static Literal makeNull(HeapType type) { - return Literal(Type(type.getBottom(), Nullable)); + return Literal(Type(type.getBottom(), Nullable, Exact)); } static Literal makeFunc(Name func, HeapType type) { return Literal(func, type); diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 4c56780323b..fa96c270670 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -127,7 +127,9 @@ struct NullTypeParserCtx { TypeT makeF64() { return Ok{}; } TypeT makeV128() { return Ok{}; } - TypeT makeRefType(HeapTypeT, Nullability) { return Ok{}; } + TypeT makeRefType(HeapTypeT, Nullability, Exactness) { return Ok{}; } + + HeapTypeT getHeapTypeFromRefType(TypeT) { return Ok{}; } TupleElemListT makeTupleElemList() { return Ok{}; } void appendTupleElem(TupleElemListT&, TypeT) {} @@ -257,10 +259,13 @@ template struct TypeParserCtx { TypeT makeF64() { return Type::f64; } TypeT makeV128() { return Type::v128; } - TypeT makeRefType(HeapTypeT ht, Nullability nullability) { - return Type(ht, nullability); + TypeT + makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) { + return Type(ht, nullability, exactness); } + HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); } + std::vector makeTupleElemList() { return {}; } void appendTupleElem(std::vector& elems, Type elem) { elems.push_back(elem); @@ -1117,10 +1122,13 @@ struct ParseTypeDefsCtx : TypeParserCtx { : TypeParserCtx(typeIndices), in(in), builder(builder), names(builder.size()) {} - TypeT makeRefType(HeapTypeT ht, Nullability nullability) { - return builder.getTempRefType(ht, nullability); + TypeT + makeRefType(HeapTypeT ht, Nullability nullability, Exactness exactness) { + return builder.getTempRefType(ht, nullability, exactness); } + HeapTypeT getHeapTypeFromRefType(TypeT t) { return t.getHeapType(); } + TypeT makeTupleType(const std::vector types) { return builder.getTempTupleType(types); } diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 33e9d20fdc9..26c20767938 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -30,6 +30,8 @@ using namespace std::string_view_literals; template Result absheaptype(Ctx&, Shareability); template Result heaptype(Ctx&); +template +MaybeResult maybeReftypeAbbrev(Ctx&); template MaybeResult maybeReftype(Ctx&); template Result reftype(Ctx&); template MaybeResult tupletype(Ctx&); @@ -458,68 +460,85 @@ template Result heaptype(Ctx& ctx) { // | 'i31ref' => i31ref // | 'structref' => structref // | 'arrayref' => arrayref -// | '(' ref null? t:heaptype ')' => ref null? t -template MaybeResult maybeReftype(Ctx& ctx) { +// | ... +template +MaybeResult maybeReftypeAbbrev(Ctx& ctx) { if (ctx.in.takeKeyword("funcref"sv)) { - return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeFuncType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("externref"sv)) { - return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeExternType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("anyref"sv)) { - return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeAnyType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("eqref"sv)) { - return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeEqType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("i31ref"sv)) { - return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable); + return ctx.makeRefType(ctx.makeI31Type(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("structref"sv)) { - return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeStructType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("arrayref"sv)) { - return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeArrayType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("exnref"sv)) { - return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeExnType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("stringref"sv)) { - return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeStringType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("contref"sv)) { - return ctx.makeRefType(ctx.makeContType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeContType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullref"sv)) { - return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNoneType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullexternref"sv)) { - return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNoextType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullfuncref"sv)) { - return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNofuncType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullexnref"sv)) { - return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNoexnType(Unshared), Nullable, Inexact); } if (ctx.in.takeKeyword("nullcontref"sv)) { - return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable); + return ctx.makeRefType(ctx.makeNocontType(Unshared), Nullable, Inexact); } + return {}; +} - if (!ctx.in.takeSExprStart("ref"sv)) { - return {}; +// reftype ::= ... +// | '(' 'exact' (ref null ht):shorthand ')' => ref null exact ht +// | '(' ref null? exact? ht:heaptype ')' => ref null? t +template MaybeResult maybeReftype(Ctx& ctx) { + if (ctx.in.takeSExprStart("exact"sv)) { + auto rt = maybeReftypeAbbrev(ctx); + CHECK_ERR(rt); + if (!rt) { + return ctx.in.err("expected reftype shorthand"); + } + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of reftype"); + } + return ctx.makeRefType(ctx.getHeapTypeFromRefType(*rt), Nullable, Exact); } - auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable; - - auto type = heaptype(ctx); - CHECK_ERR(type); - - if (!ctx.in.takeRParen()) { - return ctx.in.err("expected end of reftype"); + if (ctx.in.takeSExprStart("ref"sv)) { + auto nullability = ctx.in.takeKeyword("null"sv) ? Nullable : NonNullable; + auto exactness = ctx.in.takeKeyword("exact"sv) ? Exact : Inexact; + auto type = heaptype(ctx); + CHECK_ERR(type); + if (!ctx.in.takeRParen()) { + return ctx.in.err("expected end of reftype"); + } + return ctx.makeRefType(*type, nullability, exactness); } - return ctx.makeRefType(*type, nullability); + return maybeReftypeAbbrev(ctx); } template Result reftype(Ctx& ctx) { diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 7ade7ac3261..8c13663deba 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -2291,10 +2291,14 @@ struct OptimizeInstructions // emit a null check. bool needsNullCheck = ref->type.getNullability() == Nullable && curr->type.getNullability() == NonNullable; + // Same with exactness. + bool needsExactCast = ref->type.getExactness() == Inexact && + curr->type.getExactness() == Exact; // If the best value to propagate is the argument to the cast, we can // simply remove the cast (or downgrade it to a null check if - // necessary). - if (ref == curr->ref) { + // necessary). This does not work if we need a cast to prove + // exactness. + if (ref == curr->ref && !needsExactCast) { if (needsNullCheck) { replaceCurrent(builder.makeRefAs(RefAsNonNull, curr->ref)); } else { @@ -2303,9 +2307,9 @@ struct OptimizeInstructions return; } // Otherwise we can't just remove the cast and replace it with `ref` - // because the intermediate expressions might have had side effects. - // We can replace the cast with a drop followed by a direct return of - // the value, though. + // because the intermediate expressions might have had side effects or + // we need to check exactness. We can replace the cast with a drop + // followed by a direct return of the value, though. if (ref->type.isNull()) { // We can materialize the resulting null value directly. // @@ -2313,7 +2317,7 @@ struct OptimizeInstructions // would be, aside from the interesting corner case of // uninhabitable types: // - // (ref.cast func + // (ref.cast (ref func) // (block (result (ref nofunc)) // (unreachable) // ) @@ -2360,18 +2364,20 @@ struct OptimizeInstructions } [[fallthrough]]; case GCTypeUtils::SuccessOnlyIfNull: { - auto nullType = Type(curr->type.getHeapType().getBottom(), Nullable); // The cast either returns null or traps. In trapsNeverHappen mode // we know the result, since by assumption it will not trap. if (getPassOptions().trapsNeverHappen) { - replaceCurrent(builder.makeBlock( - {builder.makeDrop(curr->ref), builder.makeRefNull(nullType)}, - curr->type)); + replaceCurrent( + builder.makeBlock({builder.makeDrop(curr->ref), + builder.makeRefNull(curr->type.getHeapType())}, + curr->type)); return; } // Otherwise, we should have already refined the cast type to cast - // directly to null. - assert(curr->type == nullType); + // directly to null. We do not further refine the cast type to exact + // null because the extra precision is not useful and doing so would + // increase the size of the instruction encoding. + assert(curr->type.isNull()); break; } case GCTypeUtils::Unreachable: diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 6bf9114d604..6fa69e70cd7 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -859,8 +859,8 @@ struct RemoveUnusedBrs : public WalkerPass> { if (Type::isSubType(expr->type, type)) { return expr; } - if (HeapType::isSubType(expr->type.getHeapType(), - type.getHeapType())) { + if (type.isNonNullable() && expr->type.isNullable() && + Type::isSubType(expr->type.with(NonNullable), type)) { return builder.makeRefAs(RefAsNonNull, expr); } return builder.makeRefCast(expr, type); diff --git a/src/tools/fuzzing.h b/src/tools/fuzzing.h index 383e5af70c1..bb43fa39fa2 100644 --- a/src/tools/fuzzing.h +++ b/src/tools/fuzzing.h @@ -360,6 +360,10 @@ class TranslateToFuzzReader { // instruction for EH is supposed to exist only at the beginning of a 'catch' // block, so it shouldn't be moved around or deleted freely. bool canBeArbitrarilyReplaced(Expression* curr) { + // TODO: Remove this once we better support exact references. + if (curr->type.isExact()) { + return false; + } return curr->type.isDefaultable() && !EHUtils::containsValidDanglingPop(curr); } @@ -521,7 +525,9 @@ class TranslateToFuzzReader { Type getLoggableType(); bool isLoggableType(Type type); Nullability getNullability(); + Exactness getExactness(); Nullability getSubType(Nullability nullability); + Exactness getSubType(Exactness exactness); HeapType getSubType(HeapType type); Type getSubType(Type type); Nullability getSuperType(Nullability nullability); diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 683ec6849bc..864705cea83 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -1566,20 +1566,22 @@ void TranslateToFuzzReader::recombine(Function* func) { } std::vector ret; - auto heapType = type.getHeapType(); - auto nullability = type.getNullability(); + ret.push_back(type); - if (nullability == NonNullable) { - ret = getRelevantTypes(Type(heapType, Nullable)); + if (type.isNonNullable()) { + auto nullable = getRelevantTypes(type.with(Nullable)); + ret.insert(ret.end(), nullable.begin(), nullable.end()); + } + if (type.isExact()) { + auto inexact = getRelevantTypes(type.with(Inexact)); + ret.insert(ret.end(), inexact.begin(), inexact.end()); + // Do not consider exact references to supertypes. + return ret; } - while (1) { - ret.push_back(Type(heapType, nullability)); - auto super = heapType.getSuperType(); - if (!super) { - break; - } - heapType = *super; + for (auto heapType = type.getHeapType().getSuperType(); heapType; + heapType = heapType->getSuperType()) { + ret.push_back(type.with(*heapType)); } return ret; @@ -4968,9 +4970,17 @@ static auto makeArrayBoundsCheck(Expression* ref, Function* func, Builder& builder, Expression* length = nullptr) { - auto tempRef = builder.addVar(func, ref->type); + // The reference might be a RefNull, in which case its type is exact. But we + // want to avoid creating exact-typed locals until we support them more widely + // in the fuzzer, so adjust the type. TODO: remove this once exact references + // are better supported. + Type refType = ref->type; + if (refType.isExact()) { + refType = refType.with(Inexact); + } + auto tempRef = builder.addVar(func, refType); auto tempIndex = builder.addVar(func, index->type); - auto* teeRef = builder.makeLocalTee(tempRef, ref, ref->type); + auto* teeRef = builder.makeLocalTee(tempRef, ref, refType); auto* teeIndex = builder.makeLocalTee(tempIndex, index, index->type); auto* getSize = builder.makeArrayLen(teeRef); @@ -4997,7 +5007,7 @@ static auto makeArrayBoundsCheck(Expression* ref, // An additional use of the length, if it was provided. Expression* getLength = nullptr; } result = {builder.makeBinary(LtUInt32, effectiveIndex, getSize), - builder.makeLocalGet(tempRef, ref->type), + builder.makeLocalGet(tempRef, refType), builder.makeLocalGet(tempIndex, index->type), getLength}; return result; @@ -5386,6 +5396,23 @@ Nullability TranslateToFuzzReader::getNullability() { return Nullable; } +Exactness TranslateToFuzzReader::getExactness() { + // Without GC, the only heap types are func and extern, neither of which is + // exactly inhabitable. To avoid introducing uninhabitable types, only + // generate exact references when GC is enabled. We don't need custom + // descriptors to be enabled even though that is the feature that introduces + // exact references because the binary writer can always generalize the exact + // reference types away. + // + // if (wasm.features.hasGC() && oneIn(8)) { + // return Exact; + // } + // + // However, we cannot yet handle creating exact references in general, so for + // now we always generate inexact references when given the choice. TODO. + return Inexact; +} + Nullability TranslateToFuzzReader::getSubType(Nullability nullability) { if (nullability == NonNullable) { return NonNullable; @@ -5393,6 +5420,13 @@ Nullability TranslateToFuzzReader::getSubType(Nullability nullability) { return getNullability(); } +Exactness TranslateToFuzzReader::getSubType(Exactness exactness) { + if (exactness == Exact) { + return Exact; + } + return getExactness(); +} + HeapType TranslateToFuzzReader::getSubType(HeapType type) { if (oneIn(3)) { return type; @@ -5486,9 +5520,18 @@ Type TranslateToFuzzReader::getSubType(Type type) { if (!funcContext && heapType.isMaybeShared(HeapType::exn)) { return type; } - heapType = getSubType(heapType); + if (type.isExact()) { + // The only other possible heap type is bottom, but we don't want to + // generate too many bottom types. + if (!heapType.isBottom() && oneIn(20)) { + heapType = heapType.getBottom(); + } + } else { + heapType = getSubType(heapType); + } auto nullability = getSubType(type.getNullability()); - auto subType = Type(heapType, nullability); + auto exactness = getSubType(type.getExactness()); + auto subType = Type(heapType, nullability, exactness); // We don't want to emit lots of uninhabitable types like (ref none), so // avoid them with high probability. Specifically, if the original type was // inhabitable then return that; avoid adding more uninhabitability. diff --git a/src/wasm-binary.h b/src/wasm-binary.h index e83645837fc..211b15183d8 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -304,6 +304,13 @@ enum SegmentFlag { UsesExpressions = 1 << 2 }; +enum BrOnCastFlag { + InputNullable = 1 << 0, + OutputNullable = 1 << 1, + InputExact = 1 << 2, + OutputExact = 1 << 3, +}; + enum EncodedType { // value types i32 = -0x1, // 0x7f @@ -327,6 +334,7 @@ enum EncodedType { eqref = -0x13, // 0x6d nonnullable = -0x1c, // 0x64 nullable = -0x1d, // 0x63 + exact = -0x1e, // 0x62 contref = -0x18, // 0x68 nullcontref = -0x0b, // 0x75 // exception handling @@ -1127,6 +1135,8 @@ enum ASTNodes { I31GetS = 0x1d, I31GetU = 0x1e, RefI31Shared = 0x1f, + RefTestRT = 0x20, + RefCastRT = 0x21, // Shared GC Opcodes @@ -1499,6 +1509,7 @@ class WasmBinaryReader { Type getType(); // Get a type given the initial S32LEB has already been read, and is provided. Type getType(int code); + Type getTypeNoExact(int code); HeapType getHeapType(); HeapType getIndexedHeapType(); diff --git a/src/wasm-builder.h b/src/wasm-builder.h index a8a76f4740c..04b9cb2a1ad 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -670,11 +670,11 @@ class Builder { } RefNull* makeRefNull(HeapType type) { auto* ret = wasm.allocator.alloc(); - ret->finalize(Type(type.getBottom(), Nullable)); + ret->finalize(Type(type.getBottom(), Nullable, Exact)); return ret; } RefNull* makeRefNull(Type type) { - assert(type.isNullable() && type.isNull()); + assert(type.isNullable() && type.isNull() && type.isExact()); auto* ret = wasm.allocator.alloc(); ret->finalize(type); return ret; @@ -1270,7 +1270,7 @@ class Builder { return makeConst(value); } if (value.isNull()) { - return makeRefNull(type); + return makeRefNull(type.getHeapType()); } if (type.isFunction()) { return makeRefFunc(value.getFunc(), type.getHeapType()); @@ -1435,8 +1435,8 @@ class Builder { return maybeWrap(makeConstantExpression(Literal::makeZeros(curr->type))); } if (curr->type.isNullable()) { - return maybeWrap(ExpressionManipulator::refNull( - curr, Type(curr->type.getHeapType().getBottom(), Nullable))); + return maybeWrap( + ExpressionManipulator::refNull(curr, curr->type.getHeapType())); } if (curr->type.isRef() && curr->type.getHeapType().isMaybeShared(HeapType::i31)) { diff --git a/src/wasm-type.h b/src/wasm-type.h index a72ba9d2cfa..b250e38c9c8 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -62,6 +62,7 @@ using Tuple = TypeList; enum Nullability { NonNullable, Nullable }; enum Mutability { Immutable, Mutable }; +enum Exactness { Inexact, Exact }; // HeapType name information used for printing. struct TypeNames { @@ -95,13 +96,13 @@ class HeapType { // should also be passed by value. uintptr_t id; - static constexpr int TypeBits = 2; + static constexpr int TypeBits = 3; static constexpr int UsedBits = TypeBits + 1; static constexpr int SharedMask = 1 << TypeBits; public: - // Bits 0-1 are used by the Type representation, so need to be left free. - // Bit 2 determines whether the basic heap type is shared (1) or unshared (0). + // Bits 0-2 are used by the Type representation, so need to be left free. + // Bit 3 determines whether the basic heap type is shared (1) or unshared (0). enum BasicHeapType : uint32_t { ext = 1 << UsedBits, func = 2 << UsedBits, @@ -278,7 +279,7 @@ class Type { // bit 0 set. When that bit is masked off, they are pointers to the underlying // vectors of types. Otherwise, the type is a reference type, and is // represented as a heap type with bit 1 set iff the reference type is - // nullable. + // nullable and bit 2 set iff the reference type is exact. // // Since `Type` is really just a single integer, it should be passed by value. // This is a uintptr_t rather than a TypeID (uint64_t) to save memory on @@ -287,6 +288,7 @@ class Type { static constexpr int TupleMask = 1 << 0; static constexpr int NullMask = 1 << 1; + static constexpr int ExactMask = 1 << 2; public: enum BasicType : uint32_t { @@ -317,9 +319,10 @@ class Type { // Construct from a heap type description. Also covers construction from // Signature, Struct or Array via implicit conversion to HeapType. - Type(HeapType heapType, Nullability nullable) - : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0)) { - assert(heapType.isBasic() || !(heapType.getID() & (TupleMask | NullMask))); + Type(HeapType heapType, Nullability nullable, Exactness exact = Inexact) + : Type(heapType.getID() | (nullable == Nullable ? NullMask : 0) | + (exact == Exact ? ExactMask : 0)) { + assert(!(heapType.getID() & (TupleMask | NullMask | ExactMask))); } // Predicates @@ -368,9 +371,11 @@ class Type { bool isRef() const { return !isBasic() && !(id & TupleMask); } bool isNullable() const { return isRef() && (id & NullMask); } bool isNonNullable() const { return isRef() && !(id & NullMask); } + bool isExact() const { return isRef() && (id & ExactMask); } + bool isInexact() const { return isRef() && !(id & ExactMask); } HeapType getHeapType() const { assert(isRef()); - return HeapType(id & ~NullMask); + return HeapType(id & ~(NullMask | ExactMask)); } bool isFunction() const { return isRef() && getHeapType().isFunction(); } @@ -392,11 +397,20 @@ class Type { Nullability getNullability() const { return isNullable() ? Nullable : NonNullable; } + Exactness getExactness() const { + assert(isRef()); + return isExact() ? Exact : Inexact; + } // Return a new reference type with some part updated to the specified value. - Type with(HeapType heapType) { return Type(heapType, getNullability()); } + Type with(HeapType heapType) { + return Type(heapType, getNullability(), getExactness()); + } Type with(Nullability nullability) { - return Type(getHeapType(), nullability); + return Type(getHeapType(), nullability, getExactness()); + } + Type with(Exactness exactness) { + return Type(getHeapType(), getNullability(), exactness); } private: @@ -716,7 +730,8 @@ struct TypeBuilder { return t; } assert(t.isRef()); - return getTempRefType(map(t.getHeapType()), t.getNullability()); + return getTempRefType( + map(t.getHeapType()), t.getNullability(), t.getExactness()); }; auto copyType = [&](Type t) -> Type { if (t.isTuple()) { @@ -769,7 +784,9 @@ struct TypeBuilder { // TypeBuilder's HeapTypes. For Ref types, the HeapType may be a temporary // HeapType owned by this builder or a canonical HeapType. Type getTempTupleType(const Tuple&); - Type getTempRefType(HeapType heapType, Nullability nullable); + Type getTempRefType(HeapType heapType, + Nullability nullable, + Exactness exact = Inexact); // Declare the HeapType being built at index `i` to be an immediate subtype of // the given HeapType. diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 9e015501a0a..de95fe565bf 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -72,7 +72,9 @@ Literal::Literal(const uint8_t init[16]) : type(Type::v128) { } Literal::Literal(std::shared_ptr gcData, HeapType type) - : gcData(gcData), type(type, gcData ? NonNullable : Nullable) { + : gcData(gcData), + type(type, gcData ? NonNullable : Nullable, gcData ? Inexact : Exact) { + // TODO: Use exact types for more than just nulls. // The type must be a proper type for GC data: either a struct, array, or // string; or an externalized version of the same; or a null; or an // internalized string (which appears as an anyref). diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index a841d44638d..5f113a93811 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -1574,6 +1574,12 @@ void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) { void WasmBinaryWriter::writeType(Type type) { if (type.isRef()) { + // Exact references are introduced by the custom descriptors feature, but + // can be used internally even when it is not enabled. In that case, we have + // to generalize the types to be inexact before writing them. + if (!wasm->features.hasCustomDescriptors()) { + type = Type(type.getHeapType(), type.getNullability(), Inexact); + } // The only reference types allowed without GC are funcref, externref, and // exnref. We internally use more refined versions of those types, but we // cannot emit those without GC. @@ -1590,6 +1596,12 @@ void WasmBinaryWriter::writeType(Type type) { type = Type(type.getHeapType().getTop(), Nullable); } } + // If the type is exact, emit the exact prefix and continue on without + // considering exactness. + if (type.isExact()) { + o << S32LEB(BinaryConsts::EncodedType::exact); + type = Type(type.getHeapType(), type.getNullability(), Inexact); + } auto heapType = type.getHeapType(); if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) { switch (heapType.getBasic(Unshared)) { @@ -2178,7 +2190,7 @@ Signature WasmBinaryReader::getBlockType() { return Signature(Type::none, getType(code)); } -Type WasmBinaryReader::getType(int code) { +Type WasmBinaryReader::getTypeNoExact(int code) { Type type; if (getBasicType(code, type)) { return type; @@ -2194,6 +2206,17 @@ Type WasmBinaryReader::getType(int code) { WASM_UNREACHABLE("unexpected type"); } +Type WasmBinaryReader::getType(int code) { + if (code == BinaryConsts::EncodedType::exact) { + auto type = getTypeNoExact(getS32LEB()); + if (!type.isRef()) { + throwError("invalid exact prefix on non-reference type"); + } + return Type(type.getHeapType(), type.getNullability(), Exact); + } + return getTypeNoExact(code); +} + Type WasmBinaryReader::getType() { return getType(getS32LEB()); } HeapType WasmBinaryReader::getHeapType() { @@ -2354,7 +2377,7 @@ void WasmBinaryReader::readTypes() { } return builder.getTempHeapType(size_t(htCode)); }; - auto makeType = [&](int32_t typeCode) { + auto makeTypeNoExact = [&](int32_t typeCode) { Type type; if (getBasicType(typeCode, type)) { return type; @@ -2379,6 +2402,17 @@ void WasmBinaryReader::readTypes() { } WASM_UNREACHABLE("unexpected type"); }; + auto makeType = [&](int32_t typeCode) { + if (typeCode == BinaryConsts::EncodedType::exact) { + auto type = makeTypeNoExact(getS32LEB()); + if (!type.isRef()) { + throwError("unexpected exact prefix on non-reference type"); + } + return builder.getTempRefType( + type.getHeapType(), type.getNullability(), Exact); + } + return makeTypeNoExact(typeCode); + }; auto readType = [&]() { return makeType(getS32LEB()); }; auto readSignatureDef = [&]() { @@ -4252,16 +4286,30 @@ Result<> WasmBinaryReader::readInst() { return builder.makeRefTest(Type(getHeapType(), NonNullable)); case BinaryConsts::RefTestNull: return builder.makeRefTest(Type(getHeapType(), Nullable)); + case BinaryConsts::RefTestRT: + return builder.makeRefTest(getType()); case BinaryConsts::RefCast: return builder.makeRefCast(Type(getHeapType(), NonNullable)); case BinaryConsts::RefCastNull: return builder.makeRefCast(Type(getHeapType(), Nullable)); + case BinaryConsts::RefCastRT: + return builder.makeRefCast(getType()); case BinaryConsts::BrOnCast: case BinaryConsts::BrOnCastFail: { auto flags = getInt8(); auto label = getU32LEB(); - auto in = Type(getHeapType(), (flags & 1) ? Nullable : NonNullable); - auto cast = Type(getHeapType(), (flags & 2) ? Nullable : NonNullable); + auto srcNull = (flags & BinaryConsts::BrOnCastFlag::InputNullable) + ? Nullable + : NonNullable; + auto dstNull = (flags & BinaryConsts::BrOnCastFlag::OutputNullable) + ? Nullable + : NonNullable; + auto srcExact = + (flags & BinaryConsts::BrOnCastFlag::InputExact) ? Exact : Inexact; + auto dstExact = + (flags & BinaryConsts::BrOnCastFlag::OutputExact) ? Exact : Inexact; + auto in = Type(getHeapType(), srcNull, srcExact); + auto cast = Type(getHeapType(), dstNull, dstExact); auto kind = op == BinaryConsts::BrOnCast ? BrOnCast : BrOnCastFail; return builder.makeBrOn(label, kind, in, cast); } diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index 060b01b04ee..506b11a085e 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -2260,22 +2260,38 @@ void BinaryInstWriter::visitCallRef(CallRef* curr) { void BinaryInstWriter::visitRefTest(RefTest* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->castType.isNullable()) { - o << U32LEB(BinaryConsts::RefTestNull); + if (curr->castType.isExact() && + parent.getModule()->features.hasCustomDescriptors()) { + // Fall back to the general form with a reftype immediate. + o << U32LEB(BinaryConsts::RefTestRT); + parent.writeType(curr->castType); } else { - o << U32LEB(BinaryConsts::RefTest); + // Use the special-case form with heap type immediate. + if (curr->castType.isNullable()) { + o << U32LEB(BinaryConsts::RefTestNull); + } else { + o << U32LEB(BinaryConsts::RefTest); + } + parent.writeHeapType(curr->castType.getHeapType()); } - parent.writeHeapType(curr->castType.getHeapType()); } void BinaryInstWriter::visitRefCast(RefCast* curr) { o << int8_t(BinaryConsts::GCPrefix); - if (curr->type.isNullable()) { - o << U32LEB(BinaryConsts::RefCastNull); + if (curr->type.isExact() && + parent.getModule()->features.hasCustomDescriptors()) { + // Fall back to the general form with a reftype immediate. + o << U32LEB(BinaryConsts::RefCastRT); + parent.writeType(curr->type); } else { - o << U32LEB(BinaryConsts::RefCast); + // Use the special-case form with heap type immediate. + if (curr->type.isNullable()) { + o << U32LEB(BinaryConsts::RefCastNull); + } else { + o << U32LEB(BinaryConsts::RefCast); + } + parent.writeHeapType(curr->type.getHeapType()); } - parent.writeHeapType(curr->type.getHeapType()); } void BinaryInstWriter::visitBrOn(BrOn* curr) { @@ -2298,8 +2314,24 @@ void BinaryInstWriter::visitBrOn(BrOn* curr) { } assert(curr->ref->type.isRef()); assert(Type::isSubType(curr->castType, curr->ref->type)); - uint8_t flags = (curr->ref->type.isNullable() ? 1 : 0) | - (curr->castType.isNullable() ? 2 : 0); + uint8_t flags = 0; + if (curr->ref->type.isNullable()) { + flags |= BinaryConsts::BrOnCastFlag::InputNullable; + } + if (curr->castType.isNullable()) { + flags |= BinaryConsts::BrOnCastFlag::OutputNullable; + } + if (parent.getModule()->features.hasCustomDescriptors()) { + // If custom descriptors (and therefore exact references) are not + // enabled, then these flags wouldn't be recognized, and we will be + // generalizing all exact references to be non-exact anyway. + if (curr->ref->type.isExact()) { + flags |= BinaryConsts::BrOnCastFlag::InputExact; + } + if (curr->castType.isExact()) { + flags |= BinaryConsts::BrOnCastFlag::OutputExact; + } + } o << flags; o << U32LEB(getBreakIndex(curr->name)); parent.writeHeapType(curr->ref->type.getHeapType()); diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 5cdb76c19dd..ada53eb150b 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -790,11 +790,19 @@ Type Type::getLeastUpperBound(Type a, Type b) { return Type(elems); } if (a.isRef() && b.isRef()) { - if (auto heapType = - HeapType::getLeastUpperBound(a.getHeapType(), b.getHeapType())) { + auto heapTypeA = a.getHeapType(); + auto heapTypeB = b.getHeapType(); + if (auto heapType = HeapType::getLeastUpperBound(heapTypeA, heapTypeB)) { auto nullability = (a.isNullable() || b.isNullable()) ? Nullable : NonNullable; - return Type(*heapType, nullability); + auto exactness = (a.isInexact() || b.isInexact()) ? Inexact : Exact; + // The LUB can only be exact if the heap types are the same or one of them + // is bottom. + if (heapTypeA != heapTypeB && !heapTypeA.isBottom() && + !heapTypeB.isBottom()) { + exactness = Inexact; + } + return Type(*heapType, nullability, exactness); } } return Type::none; @@ -828,6 +836,7 @@ Type Type::getGreatestLowerBound(Type a, Type b) { } auto nullability = (a.isNonNullable() || b.isNonNullable()) ? NonNullable : Nullable; + auto exactness = (a.isExact() || b.isExact()) ? Exact : Inexact; HeapType heapType; if (HeapType::isSubType(heapA, heapB)) { heapType = heapA; @@ -836,7 +845,13 @@ Type Type::getGreatestLowerBound(Type a, Type b) { } else { heapType = heapA.getBottom(); } - return Type(heapType, nullability); + // If one of the types is exact, but the GLB heap type is different than its + // heap type, then we must make the GLB heap type bottom. + if ((a.isExact() && heapType != heapA) || + (b.isExact() && heapType != heapB)) { + heapType = heapA.getBottom(); + } + return Type(heapType, nullability, exactness); } const Type& Type::Iterator::operator*() const { @@ -1491,14 +1506,24 @@ bool SubTyper::isSubType(Type a, Type b) { if (a == Type::unreachable) { return true; } - if (a.isRef() && b.isRef()) { - return (a.isNullable() == b.isNullable() || !a.isNullable()) && - isSubType(a.getHeapType(), b.getHeapType()); - } if (a.isTuple() && b.isTuple()) { return isSubType(a.getTuple(), b.getTuple()); } - return false; + if (!a.isRef() || !b.isRef()) { + return false; + } + if (a.isNullable() && !b.isNullable()) { + return false; + } + if (a.isInexact() && !b.isInexact()) { + return false; + } + auto heapTypeA = a.getHeapType(); + auto heapTypeB = b.getHeapType(); + if (b.isExact() && !heapTypeA.isBottom()) { + return heapTypeA == heapTypeB; + } + return isSubType(heapTypeA, heapTypeB); } bool SubTyper::isSubType(HeapType a, HeapType b) { @@ -1646,44 +1671,69 @@ std::ostream& TypePrinter::print(Type type) { } else if (type.isRef()) { auto heapType = type.getHeapType(); if (type.isNullable() && heapType.isBasic() && !heapType.isShared()) { + if (type.isExact()) { + os << "(exact "; + } // Print shorthands for certain basic heap types. switch (heapType.getBasic(Unshared)) { case HeapType::ext: - return os << "externref"; + os << "externref"; + break; case HeapType::func: - return os << "funcref"; + os << "funcref"; + break; case HeapType::cont: - return os << "contref"; + os << "contref"; + break; case HeapType::any: - return os << "anyref"; + os << "anyref"; + break; case HeapType::eq: - return os << "eqref"; + os << "eqref"; + break; case HeapType::i31: - return os << "i31ref"; + os << "i31ref"; + break; case HeapType::struct_: - return os << "structref"; + os << "structref"; + break; case HeapType::array: - return os << "arrayref"; + os << "arrayref"; + break; case HeapType::exn: - return os << "exnref"; + os << "exnref"; + break; case HeapType::string: - return os << "stringref"; + os << "stringref"; + break; case HeapType::none: - return os << "nullref"; + os << "nullref"; + break; case HeapType::noext: - return os << "nullexternref"; + os << "nullexternref"; + break; case HeapType::nofunc: - return os << "nullfuncref"; + os << "nullfuncref"; + break; case HeapType::nocont: - return os << "nullcontref"; + os << "nullcontref"; + break; case HeapType::noexn: - return os << "nullexnref"; + os << "nullexnref"; + break; } + if (type.isExact()) { + os << ')'; + } + return os; } os << "(ref "; if (type.isNullable()) { os << "null "; } + if (type.isExact()) { + os << "exact "; + } printHeapTypeName(heapType); os << ')'; } else { @@ -1927,8 +1977,9 @@ size_t RecGroupHasher::hash(Type type) const { return digest; } assert(type.isRef()); - rehash(digest, type.getNullability()); - rehash(digest, hash(type.getHeapType())); + wasm::rehash(digest, type.getNullability()); + wasm::rehash(digest, type.getExactness()); + hash_combine(digest, hash(type.getHeapType())); return digest; } @@ -2059,6 +2110,7 @@ bool RecGroupEquator::eq(Type a, Type b) const { } if (a.isRef() && b.isRef()) { return a.getNullability() == b.getNullability() && + a.getExactness() == b.getExactness() && eq(a.getHeapType(), b.getHeapType()); } return false; @@ -2269,8 +2321,10 @@ Type TypeBuilder::getTempTupleType(const Tuple& tuple) { return impl->tupleStore.insert(tuple); } -Type TypeBuilder::getTempRefType(HeapType type, Nullability nullable) { - return Type(type, nullable); +Type TypeBuilder::getTempRefType(HeapType type, + Nullability nullable, + Exactness exact) { + return Type(type, nullable, exact); } void TypeBuilder::setSubType(size_t i, std::optional super) { diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index e33d5c3ef0d..3b7e33390f3 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2288,6 +2288,10 @@ void FunctionValidator::visitRefNull(RefNull* curr) { curr->type.isNullable(), curr, "ref.null types must be nullable")) { return; } + if (!shouldBeTrue( + curr->type.isExact(), curr, "ref.null types must be exact")) { + return; + } shouldBeTrue( curr->type.isNull(), curr, "ref.null must have a bottom heap type"); } diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 0bc888646cc..6c5694c6848 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -800,7 +800,7 @@ void MemoryGrow::finalize() { void RefNull::finalize(HeapType heapType) { assert(heapType.isBottom()); - type = Type(heapType, Nullable); + type = Type(heapType, Nullable, Exact); } void RefNull::finalize(Type type_) { type = type_; } @@ -925,6 +925,7 @@ static void populateTryTableSentTypes(TryTable* curr, Module* wasm) { // wasm spec defines when GC is enabled (=== non-nullable types are allowed). // If GC is not enabled then we emit a nullable type in the binary format in // WasmBinaryWriter::writeType. + // TODO: Make this exact. Type exnref = Type(HeapType::exn, NonNullable); for (Index i = 0; i < curr->catchTags.size(); i++) { auto tagName = curr->catchTags[i]; @@ -979,6 +980,7 @@ void RefI31::finalize() { if (value->type == Type::unreachable) { type = Type::unreachable; } else { + // TODO: Make this exact. assert(type.isRef() && type.getHeapType().isMaybeShared(HeapType::i31)); } } @@ -1014,10 +1016,12 @@ void CallRef::finalize() { // unreachable instead (and similar in other GC accessors), although this // would currently cause the parser to admit more invalid modules. if (type.isRef()) { + // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } else if (type.isTuple()) { Tuple elems; for (auto t : type) { + // TODO: Make this exact. elems.push_back( t.isRef() ? Type(t.getHeapType().getBottom(), NonNullable) : t); } @@ -1153,6 +1157,7 @@ void StructGet::finalize() { } else if (ref->type.isNull()) { // See comment on CallRef for explanation. if (type.isRef()) { + // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } } else { @@ -1228,6 +1233,7 @@ void ArrayGet::finalize() { } else if (ref->type.isNull()) { // See comment on CallRef for explanation. if (type.isRef()) { + // TODO: Make this exact. type = Type(type.getHeapType().getBottom(), NonNullable); } } else { @@ -1309,11 +1315,13 @@ void RefAs::finalize() { break; case AnyConvertExtern: type = Type(HeapTypes::any.getBasic(valHeapType.getShared()), - value->type.getNullability()); + value->type.getNullability(), + Inexact); break; case ExternConvertAny: type = Type(HeapTypes::ext.getBasic(valHeapType.getShared()), - value->type.getNullability()); + value->type.getNullability(), + Inexact); break; default: WASM_UNREACHABLE("invalid ref.as_*"); @@ -1326,11 +1334,15 @@ void StringNew::finalize() { (end && end->type == Type::unreachable)) { type = Type::unreachable; } else { + // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } -void StringConst::finalize() { type = Type(HeapType::string, NonNullable); } +void StringConst::finalize() { + // TODO: Make this exact. + type = Type(HeapType::string, NonNullable); +} void StringMeasure::finalize() { if (ref->type == Type::unreachable) { @@ -1353,6 +1365,7 @@ void StringConcat::finalize() { if (left->type == Type::unreachable || right->type == Type::unreachable) { type = Type::unreachable; } else { + // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } @@ -1378,6 +1391,7 @@ void StringSliceWTF::finalize() { end->type == Type::unreachable) { type = Type::unreachable; } else { + // TODO: Make this exact. type = Type(HeapType::string, NonNullable); } } diff --git a/test/example/c-api-kitchen-sink.txt b/test/example/c-api-kitchen-sink.txt index 78f1c73552f..9bfc45ddf72 100644 --- a/test/example/c-api-kitchen-sink.txt +++ b/test/example/c-api-kitchen-sink.txt @@ -20,17 +20,17 @@ BinaryenTypeAuto: -1 BinaryenPackedTypeNotPacked: 0 BinaryenPackedTypeInt8: 1 BinaryenPackedTypeInt16: 2 -BinaryenHeapTypeExt: 8 -BinaryenHeapTypeFunc: 16 -BinaryenHeapTypeAny: 32 -BinaryenHeapTypeEq: 40 -BinaryenHeapTypeI31: 48 -BinaryenHeapTypeStruct: 56 -BinaryenHeapTypeArray: 64 -BinaryenHeapTypeString: 80 -BinaryenHeapTypeNone: 88 -BinaryenHeapTypeNoext: 96 -BinaryenHeapTypeNofunc: 104 +BinaryenHeapTypeExt: 16 +BinaryenHeapTypeFunc: 32 +BinaryenHeapTypeAny: 64 +BinaryenHeapTypeEq: 80 +BinaryenHeapTypeI31: 96 +BinaryenHeapTypeStruct: 112 +BinaryenHeapTypeArray: 128 +BinaryenHeapTypeString: 160 +BinaryenHeapTypeNone: 176 +BinaryenHeapTypeNoext: 192 +BinaryenHeapTypeNofunc: 208 BinaryenFeatureMVP: 0 BinaryenFeatureAtomics: 1 BinaryenFeatureBulkMemory: 16 diff --git a/test/gtest/type-builder.cpp b/test/gtest/type-builder.cpp index 1e676b7194a..0f8463d55da 100644 --- a/test/gtest/type-builder.cpp +++ b/test/gtest/type-builder.cpp @@ -428,6 +428,32 @@ TEST_F(TypeTest, CanonicalizeUses) { EXPECT_NE(built[4], built[6]); } +TEST_F(TypeTest, CanonicalizeExactRefs) { + TypeBuilder builder(4); + + // Types that vary in exactness or nullability of references are different. + Type a = builder.getTempRefType(builder[0], Nullable, Inexact); + Type b = builder.getTempRefType(builder[1], NonNullable, Inexact); + Type c = builder.getTempRefType(builder[2], Nullable, Exact); + Type d = builder.getTempRefType(builder[3], NonNullable, Exact); + + builder[0] = Struct({Field(a, Mutable)}); + builder[1] = Struct({Field(b, Mutable)}); + builder[2] = Struct({Field(c, Mutable)}); + builder[3] = Struct({Field(d, Mutable)}); + + auto result = builder.build(); + ASSERT_TRUE(result); + auto built = *result; + + EXPECT_NE(built[0], built[1]); + EXPECT_NE(built[0], built[2]); + EXPECT_NE(built[0], built[3]); + EXPECT_NE(built[1], built[2]); + EXPECT_NE(built[1], built[3]); + EXPECT_NE(built[2], built[3]); +} + TEST_F(TypeTest, CanonicalizeSelfReferences) { TypeBuilder builder(5); // Single self-reference @@ -1147,18 +1173,26 @@ FUZZ_TEST(TypeFuzzTest, TestHeapTypeRelationsFuzz) #endif // FUZZTEST TEST_F(TypeTest, TestTypeRelations) { - Type any = Type(HeapType::any, NonNullable); - Type nullAny = Type(HeapType::any, Nullable); + Type any = Type(HeapType::any, NonNullable, Inexact); + Type nullAny = Type(HeapType::any, Nullable, Inexact); + Type exactAny = Type(HeapType::any, NonNullable, Exact); + Type nullExactAny = Type(HeapType::any, Nullable, Exact); HeapType defined = Struct(); - Type def = Type(defined, NonNullable); - Type nullDef = Type(defined, Nullable); + Type def = Type(defined, NonNullable, Inexact); + Type nullDef = Type(defined, Nullable, Inexact); + Type exactDef = Type(defined, NonNullable, Exact); + Type nullExactDef = Type(defined, Nullable, Exact); - Type none = Type(HeapType::none, NonNullable); - Type nullNone = Type(HeapType::none, Nullable); + Type none = Type(HeapType::none, NonNullable, Inexact); + Type nullNone = Type(HeapType::none, Nullable, Inexact); + Type exactNone = Type(HeapType::none, NonNullable, Exact); + Type nullExactNone = Type(HeapType::none, Nullable, Exact); - Type func = Type(HeapType::func, NonNullable); - Type nullFunc = Type(HeapType::func, Nullable); + Type func = Type(HeapType::func, NonNullable, Inexact); + Type nullFunc = Type(HeapType::func, Nullable, Inexact); + Type exactFunc = Type(HeapType::func, NonNullable, Exact); + Type nullExactFunc = Type(HeapType::func, Nullable, Exact); Type i32 = Type::i32; Type unreachable = Type::unreachable; @@ -1217,54 +1251,165 @@ TEST_F(TypeTest, TestTypeRelations) { assertLUB(any, any, any, any); assertLUB(any, nullAny, nullAny, any); + assertLUB(any, exactAny, any, exactAny); + assertLUB(any, nullExactAny, nullAny, exactAny); assertLUB(any, def, any, def); assertLUB(any, nullDef, nullAny, def); + assertLUB(any, exactDef, any, exactDef); + assertLUB(any, nullExactDef, nullAny, exactDef); assertLUB(any, none, any, none); assertLUB(any, nullNone, nullAny, none); + assertLUB(any, exactNone, any, exactNone); + assertLUB(any, nullExactNone, nullAny, exactNone); assertLUB(any, func, Type(Type::none), unreachable); assertLUB(any, nullFunc, Type(Type::none), unreachable); + assertLUB(any, exactFunc, Type(Type::none), unreachable); + assertLUB(any, nullExactFunc, Type(Type::none), unreachable); assertLUB(any, i32, Type(Type::none), unreachable); assertLUB(any, unreachable, any, unreachable); assertLUB(nullAny, nullAny, nullAny, nullAny); + assertLUB(nullAny, exactAny, nullAny, exactAny); + assertLUB(nullAny, nullExactAny, nullAny, nullExactAny); assertLUB(nullAny, def, nullAny, def); assertLUB(nullAny, nullDef, nullAny, nullDef); + assertLUB(nullAny, exactDef, nullAny, exactDef); + assertLUB(nullAny, nullExactDef, nullAny, nullExactDef); assertLUB(nullAny, none, nullAny, none); assertLUB(nullAny, nullNone, nullAny, nullNone); + assertLUB(nullAny, exactNone, nullAny, exactNone); + assertLUB(nullAny, nullExactNone, nullAny, nullExactNone); assertLUB(nullAny, func, Type(Type::none), unreachable); assertLUB(nullAny, nullFunc, Type(Type::none), unreachable); + assertLUB(nullAny, exactFunc, Type(Type::none), unreachable); + assertLUB(nullAny, nullExactFunc, Type(Type::none), unreachable); assertLUB(nullAny, i32, Type(Type::none), unreachable); assertLUB(nullAny, unreachable, nullAny, unreachable); + assertLUB(exactAny, exactAny, exactAny, exactAny); + assertLUB(exactAny, nullExactAny, nullExactAny, exactAny); + assertLUB(exactAny, def, any, exactNone); + assertLUB(exactAny, nullDef, nullAny, exactNone); + assertLUB(exactAny, exactDef, any, exactNone); + assertLUB(exactAny, nullExactDef, nullAny, exactNone); + assertLUB(exactAny, none, any, exactNone); + assertLUB(exactAny, nullNone, nullAny, exactNone); + assertLUB(exactAny, exactNone, exactAny, exactNone); + assertLUB(exactAny, nullExactNone, nullExactAny, exactNone); + assertLUB(exactAny, func, Type(Type::none), unreachable); + assertLUB(exactAny, nullFunc, Type(Type::none), unreachable); + assertLUB(exactAny, exactFunc, Type(Type::none), unreachable); + assertLUB(exactAny, nullExactFunc, Type(Type::none), unreachable); + assertLUB(exactAny, i32, Type(Type::none), unreachable); + assertLUB(exactAny, unreachable, exactAny, unreachable); + + assertLUB(nullExactAny, nullExactAny, nullExactAny, nullExactAny); + assertLUB(nullExactAny, def, nullAny, exactNone); + assertLUB(nullExactAny, nullDef, nullAny, nullExactNone); + assertLUB(nullExactAny, exactDef, nullAny, exactNone); + assertLUB(nullExactAny, nullExactDef, nullAny, nullExactNone); + assertLUB(nullExactAny, none, nullAny, exactNone); + assertLUB(nullExactAny, nullNone, nullAny, nullExactNone); + assertLUB(nullExactAny, exactNone, nullExactAny, exactNone); + assertLUB(nullExactAny, nullExactNone, nullExactAny, nullExactNone); + assertLUB(nullExactAny, func, Type(Type::none), unreachable); + assertLUB(nullExactAny, nullFunc, Type(Type::none), unreachable); + assertLUB(nullExactAny, exactFunc, Type(Type::none), unreachable); + assertLUB(nullExactAny, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullExactAny, i32, Type(Type::none), unreachable); + assertLUB(nullExactAny, unreachable, nullExactAny, unreachable); + assertLUB(def, def, def, def); assertLUB(def, nullDef, nullDef, def); + assertLUB(def, exactDef, def, exactDef); + assertLUB(def, nullExactDef, nullDef, exactDef); assertLUB(def, none, def, none); assertLUB(def, nullNone, nullDef, none); + assertLUB(def, exactNone, def, exactNone); + assertLUB(def, nullExactNone, nullDef, exactNone); assertLUB(def, func, Type(Type::none), unreachable); assertLUB(def, nullFunc, Type(Type::none), unreachable); + assertLUB(def, exactFunc, Type(Type::none), unreachable); + assertLUB(def, nullExactFunc, Type(Type::none), unreachable); assertLUB(def, i32, Type(Type::none), unreachable); assertLUB(def, unreachable, def, unreachable); assertLUB(nullDef, nullDef, nullDef, nullDef); + assertLUB(nullDef, exactDef, nullDef, exactDef); + assertLUB(nullDef, nullExactDef, nullDef, nullExactDef); assertLUB(nullDef, none, nullDef, none); assertLUB(nullDef, nullNone, nullDef, nullNone); + assertLUB(nullDef, exactNone, nullDef, exactNone); + assertLUB(nullDef, nullExactNone, nullDef, nullExactNone); assertLUB(nullDef, func, Type(Type::none), unreachable); assertLUB(nullDef, nullFunc, Type(Type::none), unreachable); + assertLUB(nullDef, exactFunc, Type(Type::none), unreachable); + assertLUB(nullDef, nullExactFunc, Type(Type::none), unreachable); assertLUB(nullDef, i32, Type(Type::none), unreachable); assertLUB(nullDef, unreachable, nullDef, unreachable); + assertLUB(exactDef, exactDef, exactDef, exactDef); + assertLUB(exactDef, nullExactDef, nullExactDef, exactDef); + assertLUB(exactDef, none, def, exactNone); + assertLUB(exactDef, nullNone, nullDef, exactNone); + assertLUB(exactDef, exactNone, exactDef, exactNone); + assertLUB(exactDef, nullExactNone, nullExactDef, exactNone); + assertLUB(exactDef, func, Type(Type::none), unreachable); + assertLUB(exactDef, nullFunc, Type(Type::none), unreachable); + assertLUB(exactDef, exactFunc, Type(Type::none), unreachable); + assertLUB(exactDef, nullExactFunc, Type(Type::none), unreachable); + assertLUB(exactDef, i32, Type(Type::none), unreachable); + assertLUB(exactDef, unreachable, exactDef, unreachable); + + assertLUB(nullExactDef, nullExactDef, nullExactDef, nullExactDef); + assertLUB(nullExactDef, none, nullDef, exactNone); + assertLUB(nullExactDef, nullNone, nullDef, nullExactNone); + assertLUB(nullExactDef, exactNone, nullExactDef, exactNone); + assertLUB(nullExactDef, nullExactNone, nullExactDef, nullExactNone); + assertLUB(nullExactDef, func, Type(Type::none), unreachable); + assertLUB(nullExactDef, nullFunc, Type(Type::none), unreachable); + assertLUB(nullExactDef, exactFunc, Type(Type::none), unreachable); + assertLUB(nullExactDef, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullExactDef, i32, Type(Type::none), unreachable); + assertLUB(nullExactDef, unreachable, nullExactDef, unreachable); + assertLUB(none, none, none, none); assertLUB(none, nullNone, nullNone, none); + assertLUB(none, exactNone, none, exactNone); + assertLUB(none, nullExactNone, nullNone, exactNone); assertLUB(none, func, Type(Type::none), unreachable); assertLUB(none, nullFunc, Type(Type::none), unreachable); + assertLUB(none, exactFunc, Type(Type::none), unreachable); + assertLUB(none, nullExactFunc, Type(Type::none), unreachable); assertLUB(none, i32, Type(Type::none), unreachable); assertLUB(none, unreachable, none, unreachable); assertLUB(nullNone, nullNone, nullNone, nullNone); + assertLUB(nullNone, exactNone, nullNone, exactNone); + assertLUB(nullNone, nullExactNone, nullNone, nullExactNone); assertLUB(nullNone, func, Type(Type::none), unreachable); assertLUB(nullNone, nullFunc, Type(Type::none), unreachable); + assertLUB(nullNone, exactFunc, Type(Type::none), unreachable); + assertLUB(nullNone, nullExactFunc, Type(Type::none), unreachable); assertLUB(nullNone, i32, Type(Type::none), unreachable); assertLUB(nullNone, unreachable, nullNone, unreachable); + + assertLUB(exactNone, exactNone, exactNone, exactNone); + assertLUB(exactNone, nullExactNone, nullExactNone, exactNone); + assertLUB(exactNone, func, Type(Type::none), unreachable); + assertLUB(exactNone, nullFunc, Type(Type::none), unreachable); + assertLUB(exactNone, exactFunc, Type(Type::none), unreachable); + assertLUB(exactNone, nullExactFunc, Type(Type::none), unreachable); + assertLUB(exactNone, i32, Type(Type::none), unreachable); + assertLUB(exactNone, unreachable, exactNone, unreachable); + + assertLUB(nullExactNone, nullExactNone, nullExactNone, nullExactNone); + assertLUB(nullExactNone, func, Type(Type::none), unreachable); + assertLUB(nullExactNone, nullFunc, Type(Type::none), unreachable); + assertLUB(nullExactNone, exactFunc, Type(Type::none), unreachable); + assertLUB(nullExactNone, nullExactFunc, Type(Type::none), unreachable); + assertLUB(nullExactNone, i32, Type(Type::none), unreachable); + assertLUB(nullExactNone, unreachable, nullExactNone, unreachable); } TEST_F(TypeTest, TestSubtypeErrors) { diff --git a/test/lit/basic/exact-references.wast b/test/lit/basic/exact-references.wast new file mode 100644 index 00000000000..e6c1599ea48 --- /dev/null +++ b/test/lit/basic/exact-references.wast @@ -0,0 +1,672 @@ +;; 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-TEXT +;; RUN: cat %t.bin.wast | filecheck %s --check-prefix=CHECK-BIN +;; RUN: cat %t.bin.nodebug.wast | filecheck %s --check-prefix=CHECK-BIN-NODEBUG + +;; Also check that if we emit a binary without custom descriptors enabled, the +;; types are generalized to be inexact. + +;; RUN: wasm-opt %s -all --disable-custom-descriptors -g -o %t.noexact.wasm +;; RUN: wasm-opt %t.noexact.wasm -all -S -o - | filecheck %s --check-prefix=NO-EXACT + +(module + ;; CHECK-TEXT: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo)))) + ;; CHECK-BIN: (type $foo (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $foo)) (field (ref exact $foo)))) + ;; NO-EXACT: (type $foo (struct (field anyref) (field (ref any)) (field (ref null $foo)) (field (ref $foo)))) + (type $foo (struct (field (exact anyref) (ref exact any) (ref null exact $foo) (ref exact $foo)))) + + + ;; CHECK-TEXT: (type $1 (func (param (exact anyref)) (result (ref exact any)))) + + ;; CHECK-TEXT: (type $2 (func (param anyref) (result anyref))) + + ;; CHECK-TEXT: (type $3 (func (param (exact i31ref)))) + + ;; CHECK-TEXT: (type $4 (func (param (exact eqref)))) + + ;; CHECK-TEXT: (type $5 (func (param (exact anyref)))) + + ;; CHECK-TEXT: (type $6 (func (param (exact anyref)) (result (exact anyref)))) + + ;; CHECK-TEXT: (import "" "g1" (global $g1 (exact anyref))) + ;; CHECK-BIN: (type $1 (func (param (exact anyref)) (result (ref exact any)))) + + ;; CHECK-BIN: (type $2 (func (param anyref) (result anyref))) + + ;; CHECK-BIN: (type $3 (func (param (exact i31ref)))) + + ;; CHECK-BIN: (type $4 (func (param (exact eqref)))) + + ;; CHECK-BIN: (type $5 (func (param (exact anyref)))) + + ;; CHECK-BIN: (type $6 (func (param (exact anyref)) (result (exact anyref)))) + + ;; CHECK-BIN: (import "" "g1" (global $g1 (exact anyref))) + ;; NO-EXACT: (type $1 (func (param anyref) (result anyref))) + + ;; NO-EXACT: (type $2 (func (param anyref) (result (ref any)))) + + ;; NO-EXACT: (type $3 (func (param i31ref))) + + ;; NO-EXACT: (type $4 (func (param eqref))) + + ;; NO-EXACT: (type $5 (func (param anyref))) + + ;; NO-EXACT: (import "" "g1" (global $g1 anyref)) + (import "" "g1" (global $g1 (exact anyref))) + + ;; CHECK-TEXT: (import "" "g2" (global $g2 (ref exact any))) + ;; CHECK-BIN: (import "" "g2" (global $g2 (ref exact any))) + ;; NO-EXACT: (import "" "g2" (global $g2 (ref any))) + (import "" "g2" (global $g2 (ref exact any))) + + ;; CHECK-TEXT: (import "" "g3" (global $g3 (ref null exact $foo))) + ;; CHECK-BIN: (import "" "g3" (global $g3 (ref null exact $foo))) + ;; NO-EXACT: (import "" "g3" (global $g3 (ref null $foo))) + (import "" "g3" (global $g3 (ref null exact $foo))) + + ;; CHECK-TEXT: (import "" "g4" (global $g4 (ref exact $foo))) + ;; CHECK-BIN: (import "" "g4" (global $g4 (ref exact $foo))) + ;; NO-EXACT: (import "" "g4" (global $g4 (ref $foo))) + (import "" "g4" (global $g4 (ref exact $foo))) + + ;; CHECK-TEXT: (func $ref-test (type $3) (param $0 (exact i31ref)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.test (ref exact i31) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.test (exact i31ref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-test (type $3) (param $0 (exact i31ref)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.test (ref exact i31) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.test (exact i31ref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $ref-test (type $3) (param $0 i31ref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.test (ref i31) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.test i31ref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $ref-test (param (ref null exact i31)) + (drop + (ref.test (ref exact i31) + (local.get 0) + ) + ) + (drop + (ref.test (ref null exact i31) + (local.get 0) + ) + ) + ) + + ;; CHECK-TEXT: (func $ref-cast (type $4) (param $0 (exact eqref)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.cast (ref exact eq) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.cast (exact eqref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (ref.cast (ref exact none) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $ref-cast (type $4) (param $0 (exact eqref)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.cast (ref exact eq) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.cast (exact eqref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (ref.cast (ref exact none) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $ref-cast (type $4) (param $0 eqref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.cast (ref eq) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.cast eqref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (ref.cast (ref none) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $ref-cast (param (ref null exact eq)) + (drop + (ref.cast (ref exact eq) + (local.get 0) + ) + ) + (drop + (ref.cast (ref null exact eq) + (local.get 0) + ) + ) + (drop + (ref.cast (ref exact i31) + (local.get 0) + ) + ) + ) + + ;; CHECK-TEXT: (func $br-on-cast (type $2) (param $0 anyref) (result anyref) + ;; CHECK-TEXT-NEXT: (block $label (result anyref) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (exact eqref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (ref exact eq) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast $label anyref (exact i31ref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast (type $2) (param $0 anyref) (result anyref) + ;; CHECK-BIN-NEXT: (block $block (result anyref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (exact eqref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (ref exact eq) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block anyref (exact i31ref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $br-on-cast (type $1) (param $0 anyref) (result anyref) + ;; NO-EXACT-NEXT: (block $block (result anyref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref eqref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref (ref eq) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref i31ref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $br-on-cast (param anyref) (result anyref) + (drop + (br_on_cast 0 anyref (ref null exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast 0 anyref (ref exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast 0 anyref (ref null exact i31) + (local.get 0) + ) + ) + (local.get 0) + ) + + ;; CHECK-TEXT: (func $br-on-cast-fail (type $2) (param $0 anyref) (result anyref) + ;; CHECK-TEXT-NEXT: (block $label (result anyref) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (exact eqref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (ref exact eq) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label anyref (exact i31ref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $br-on-cast-fail (type $2) (param $0 anyref) (result anyref) + ;; CHECK-BIN-NEXT: (block $block (result anyref) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (exact eqref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (ref exact eq) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block anyref (exact i31ref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $br-on-cast-fail (type $1) (param $0 anyref) (result anyref) + ;; NO-EXACT-NEXT: (block $block (result anyref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref eqref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref (ref eq) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref i31ref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $br-on-cast-fail (param anyref) (result anyref) + (drop + (br_on_cast_fail 0 anyref (ref null exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast_fail 0 anyref (ref exact eq) + (local.get 0) + ) + ) + (drop + (br_on_cast_fail 0 anyref (ref null exact i31) + (local.get 0) + ) + ) + (local.get 0) + ) + + ;; CHECK-TEXT: (func $valid-ref-as-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (ref.as_non_null + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-ref-as-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-BIN-NEXT: (ref.as_non_null + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-ref-as-non-null (type $2) (param $0 anyref) (result (ref any)) + ;; NO-EXACT-NEXT: (ref.as_non_null + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-ref-as-non-null (param (ref null exact any)) (result (ref exact any)) + (ref.as_non_null + (local.get 0) + ) + ) + + ;; CHECK-TEXT: (func $valid-br-on-null (type $5) (param $0 (exact anyref)) + ;; CHECK-TEXT-NEXT: (block $label + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (br_on_null $label + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-null (type $5) (param $0 (exact anyref)) + ;; CHECK-BIN-NEXT: (block $block + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_null $block + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-null (type $5) (param $0 anyref) + ;; NO-EXACT-NEXT: (block $block + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_null $block + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-null (param (ref null exact any)) + (drop + (block (result (ref exact any)) + (br_on_null 1 + (local.get 0) + ) + ) + ) + ) + + ;; CHECK-TEXT: (func $valid-br-on-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (block $label (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (br_on_non_null $label + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-non-null (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-BIN-NEXT: (block $block (result (ref exact any)) + ;; CHECK-BIN-NEXT: (br_on_non_null $block + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-non-null (type $2) (param $0 anyref) (result (ref any)) + ;; NO-EXACT-NEXT: (block $block (result (ref any)) + ;; NO-EXACT-NEXT: (br_on_non_null $block + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (unreachable) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-non-null (param (ref null exact any)) (result (ref exact any)) + (br_on_non_null 0 + (local.get 0) + ) + (unreachable) + ) + + ;; CHECK-TEXT: (func $valid-br-on-cast (type $6) (param $0 (exact anyref)) (result (exact anyref)) + ;; CHECK-TEXT-NEXT: (block $label (result (exact anyref)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (br_on_cast $label (exact anyref) (exact anyref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-cast (type $6) (param $0 (exact anyref)) (result (exact anyref)) + ;; CHECK-BIN-NEXT: (block $block (result (exact anyref)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast $block (exact anyref) (exact anyref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-cast (type $1) (param $0 anyref) (result anyref) + ;; NO-EXACT-NEXT: (block $block (result anyref) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast $block anyref anyref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (unreachable) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-cast (param (ref null exact any)) (result (ref null exact any)) + (drop + (block (result (ref exact any)) + (br_on_cast 1 (ref null exact any) (ref null exact any) + (local.get 0) + ) + ) + ) + (unreachable) + ) + + ;; CHECK-TEXT: (func $valid-br-on-cast-fail (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (block $label (result (ref exact any)) + ;; CHECK-TEXT-NEXT: (drop + ;; CHECK-TEXT-NEXT: (block (result (exact anyref)) + ;; CHECK-TEXT-NEXT: (br_on_cast_fail $label (exact anyref) (exact anyref) + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: (unreachable) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $valid-br-on-cast-fail (type $1) (param $0 (exact anyref)) (result (ref exact any)) + ;; CHECK-BIN-NEXT: (block $block (result (ref exact any)) + ;; CHECK-BIN-NEXT: (drop + ;; CHECK-BIN-NEXT: (br_on_cast_fail $block (exact anyref) (exact anyref) + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: (unreachable) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + ;; NO-EXACT: (func $valid-br-on-cast-fail (type $2) (param $0 anyref) (result (ref any)) + ;; NO-EXACT-NEXT: (block $block (result (ref any)) + ;; NO-EXACT-NEXT: (drop + ;; NO-EXACT-NEXT: (br_on_cast_fail $block anyref anyref + ;; NO-EXACT-NEXT: (local.get $0) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: (unreachable) + ;; NO-EXACT-NEXT: ) + ;; NO-EXACT-NEXT: ) + (func $valid-br-on-cast-fail (param (ref null exact any)) (result (ref exact any)) + (drop + (block (result (ref null exact any)) + (br_on_cast_fail 1 (ref null exact any) (ref null exact any) + (local.get 0) + ) + ) + ) + (unreachable) + ) +) +;; CHECK-BIN-NODEBUG: (type $0 (struct (field (exact anyref)) (field (ref exact any)) (field (ref null exact $0)) (field (ref exact $0)))) + +;; CHECK-BIN-NODEBUG: (type $1 (func (param (exact anyref)) (result (ref exact any)))) + +;; CHECK-BIN-NODEBUG: (type $2 (func (param anyref) (result anyref))) + +;; CHECK-BIN-NODEBUG: (type $3 (func (param (exact i31ref)))) + +;; CHECK-BIN-NODEBUG: (type $4 (func (param (exact eqref)))) + +;; CHECK-BIN-NODEBUG: (type $5 (func (param (exact anyref)))) + +;; CHECK-BIN-NODEBUG: (type $6 (func (param (exact anyref)) (result (exact anyref)))) + +;; CHECK-BIN-NODEBUG: (import "" "g1" (global $gimport$0 (exact anyref))) + +;; CHECK-BIN-NODEBUG: (import "" "g2" (global $gimport$1 (ref exact any))) + +;; CHECK-BIN-NODEBUG: (import "" "g3" (global $gimport$2 (ref null exact $0))) + +;; CHECK-BIN-NODEBUG: (import "" "g4" (global $gimport$3 (ref exact $0))) + +;; CHECK-BIN-NODEBUG: (func $0 (type $3) (param $0 (exact i31ref)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (ref exact i31) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.test (exact i31ref) +;; 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 $4) (param $0 (exact eqref)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (ref exact none) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $2 (type $2) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block anyref (exact i31ref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $3 (type $2) (param $0 anyref) (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result anyref) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (exact eqref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (ref exact eq) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block anyref (exact i31ref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $4 (type $1) (param $0 (exact anyref)) (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (ref.as_non_null +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $5 (type $5) (param $0 (exact anyref)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_null $block +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $6 (type $1) (param $0 (exact anyref)) (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (br_on_non_null $block +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $7 (type $6) (param $0 (exact anyref)) (result (exact anyref)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (exact anyref)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast $block (exact anyref) (exact anyref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $8 (type $1) (param $0 (exact anyref)) (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (block $block (result (ref exact any)) +;; CHECK-BIN-NODEBUG-NEXT: (drop +;; CHECK-BIN-NODEBUG-NEXT: (br_on_cast_fail $block (exact anyref) (exact anyref) +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: (unreachable) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/lit/basic/reference-types.wast b/test/lit/basic/reference-types.wast index 82b1f8da4e3..7c4f2a23e76 100644 --- a/test/lit/basic/reference-types.wast +++ b/test/lit/basic/reference-types.wast @@ -961,7 +961,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block2 (result eqref) - ;; CHECK-BIN-NEXT: (ref.cast nullref + ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NEXT: (br_if $block2 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -987,7 +987,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block5 (result funcref) - ;; CHECK-BIN-NEXT: (ref.cast nullfuncref + ;; CHECK-BIN-NEXT: (ref.cast (exact nullfuncref) ;; CHECK-BIN-NEXT: (br_if $block5 ;; CHECK-BIN-NEXT: (ref.null nofunc) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -1023,7 +1023,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block9 (result anyref) - ;; CHECK-BIN-NEXT: (ref.cast nullref + ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NEXT: (br_if $block9 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -1043,7 +1043,7 @@ ;; CHECK-BIN-NEXT: ) ;; CHECK-BIN-NEXT: (drop ;; CHECK-BIN-NEXT: (block $block11 (result anyref) - ;; CHECK-BIN-NEXT: (ref.cast nullref + ;; CHECK-BIN-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NEXT: (br_if $block11 ;; CHECK-BIN-NEXT: (ref.null none) ;; CHECK-BIN-NEXT: (i32.const 1) @@ -2298,7 +2298,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block2 (result eqref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block2 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2324,7 +2324,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block5 (result funcref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullfuncref +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullfuncref) ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block5 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null nofunc) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2360,7 +2360,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block9 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block9 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) @@ -2380,7 +2380,7 @@ ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: (drop ;; CHECK-BIN-NODEBUG-NEXT: (block $block11 (result anyref) -;; CHECK-BIN-NODEBUG-NEXT: (ref.cast nullref +;; CHECK-BIN-NODEBUG-NEXT: (ref.cast (exact nullref) ;; CHECK-BIN-NODEBUG-NEXT: (br_if $block11 ;; CHECK-BIN-NODEBUG-NEXT: (ref.null none) ;; CHECK-BIN-NODEBUG-NEXT: (i32.const 1) diff --git a/test/lit/ctor-eval/materialize-null-local.wast b/test/lit/ctor-eval/materialize-null-local.wast new file mode 100644 index 00000000000..1f880f6816b --- /dev/null +++ b/test/lit/ctor-eval/materialize-null-local.wast @@ -0,0 +1,26 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-ctor-eval %s -all --ctors=test --kept-exports=test --ignore-external-input -S -o - \ +;; RUN: | filecheck %s + +;; Check that materializing a non-data null local, which at time of writing uses +;; Builder::makeConstantExpression, does not trigger an assertion failure. + +(module + (func $test (export "test") (param $0 externref) + (local $3 anyref) + (local.set $3 + (any.convert_extern + (local.get $0) + ) + ) + ) +) +;; CHECK: (type $0 (func (param externref))) + +;; CHECK: (export "test" (func $test_1)) + +;; CHECK: (func $test_1 (type $0) (param $0 externref) +;; CHECK-NEXT: (local $3 anyref) +;; CHECK-NEXT: (nop) +;; CHECK-NEXT: ) diff --git a/test/lit/exec/exact.wast b/test/lit/exec/exact.wast new file mode 100644 index 00000000000..2667e0e4dce --- /dev/null +++ b/test/lit/exec/exact.wast @@ -0,0 +1,21 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s + +(module + ;; CHECK: [fuzz-exec] calling convert-null-extern + ;; CHECK-NEXT: [fuzz-exec] note result: convert-null-extern => null + (func $convert-null-extern (export "convert-null-extern") (result (exact nullref)) + (local externref) + ;; The value produced by this cast must be exact to avoid triggering an + ;; assertion. + (ref.cast (exact nullref) + (any.convert_extern + (local.get 0) + ) + ) + ) +) +;; CHECK: [fuzz-exec] calling convert-null-extern +;; CHECK-NEXT: [fuzz-exec] note result: convert-null-extern => null +;; CHECK-NEXT: [fuzz-exec] comparing convert-null-extern diff --git a/test/lit/heap-types.wast b/test/lit/heap-types.wast index 4613f4894fd..baf77196611 100644 --- a/test/lit/heap-types.wast +++ b/test/lit/heap-types.wast @@ -12,7 +12,7 @@ (type $struct.B (struct i32)) ;; CHECK: (func $test (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.test (ref none) + ;; CHECK-NEXT: (ref.test (ref exact none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -29,7 +29,7 @@ (type $struct.B (struct i32)) ;; CHECK: (func $test (type $0) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (ref.cast (exact nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast index 0bc4372ec35..ab9b7f8a745 100644 --- a/test/lit/passes/cfp.wast +++ b/test/lit/passes/cfp.wast @@ -1034,7 +1034,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $struct3) @@ -1233,7 +1233,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (local.get $struct3) diff --git a/test/lit/passes/coalesce-locals-exact.wast b/test/lit/passes/coalesce-locals-exact.wast new file mode 100644 index 00000000000..1568d3634cf --- /dev/null +++ b/test/lit/passes/coalesce-locals-exact.wast @@ -0,0 +1,47 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that TypeUpdating::handleNonDefaultableLocals handles locals with exact +;; reference types correctly, and in particular that it preserves the exactness +;; of the types. + +;; RUN: wasm-opt %s -all --coalesce-locals -S -o - | filecheck %s + +(module + ;; CHECK: (func $test (type $0) (param $0 (exact i31ref)) (result (ref exact i31)) + ;; CHECK-NEXT: (local $1 (exact i31ref)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block $l + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test (param (exact i31ref)) (result (ref exact i31)) + (local $l (ref exact i31)) + ;; This dead set will be optimized out. + (local.set $l + (ref.as_non_null + (local.get 0) + ) + ) + (block $l + ;; This remaining set does not structurally dominate the get. + (local.set $l + (ref.as_non_null + (local.get 0) + ) + ) + ) + ;; This will have to be fixed up and the local made nullable. + (local.get $l) + ) +) diff --git a/test/lit/passes/code-pushing-gc.wast b/test/lit/passes/code-pushing-gc.wast index 1aac5c55cf7..fb9ff12f7da 100644 --- a/test/lit/passes/code-pushing-gc.wast +++ b/test/lit/passes/code-pushing-gc.wast @@ -7,7 +7,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $out (result (ref func)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $out nullfuncref (ref nofunc) + ;; CHECK-NEXT: (br_on_cast $out (exact nullfuncref) (ref exact nofunc) ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -48,7 +48,7 @@ ;; CHECK-NEXT: (ref.func $br_on_no) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $out nullfuncref (ref nofunc) + ;; CHECK-NEXT: (br_on_cast $out (exact nullfuncref) (ref exact nofunc) ;; CHECK-NEXT: (ref.null nofunc) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/dae-gc-refine-params.wast b/test/lit/passes/dae-gc-refine-params.wast index 8cefbe88184..a93f3e7347f 100644 --- a/test/lit/passes/dae-gc-refine-params.wast +++ b/test/lit/passes/dae-gc-refine-params.wast @@ -262,7 +262,7 @@ ) ;; This function is called in ways that allow us to make the first parameter ;; non-nullable. - ;; CHECK: (func $various-params-null (type $13) (param $x (ref none)) (param $y (ref null $"{i32}")) + ;; CHECK: (func $various-params-null (type $13) (param $x (ref exact none)) (param $y (ref null $"{i32}")) ;; CHECK-NEXT: (local $temp i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $x) diff --git a/test/lit/passes/dae-gc.wast b/test/lit/passes/dae-gc.wast index 1f39567d146..015e515c02b 100644 --- a/test/lit/passes/dae-gc.wast +++ b/test/lit/passes/dae-gc.wast @@ -110,7 +110,7 @@ ) ;; CHECK: (func $bar (type $2) (param $0 i31ref) - ;; CHECK-NEXT: (local $1 nullref) + ;; CHECK-NEXT: (local $1 (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/flatten_all-features.wast b/test/lit/passes/flatten_all-features.wast index b601b8a1295..6c117d0a907 100644 --- a/test/lit/passes/flatten_all-features.wast +++ b/test/lit/passes/flatten_all-features.wast @@ -3581,8 +3581,8 @@ ;; CHECK: (func $subtype (type $7) (result anyref) ;; CHECK-NEXT: (local $0 eqref) ;; CHECK-NEXT: (local $1 anyref) - ;; CHECK-NEXT: (local $2 nullref) - ;; CHECK-NEXT: (local $3 nullref) + ;; CHECK-NEXT: (local $2 (exact nullref)) + ;; CHECK-NEXT: (local $3 (exact nullref)) ;; CHECK-NEXT: (local $4 eqref) ;; CHECK-NEXT: (local $5 eqref) ;; CHECK-NEXT: (local $6 eqref) @@ -3680,7 +3680,7 @@ ;; CHECK: (type $0 (func (result funcref))) ;; CHECK: (func $0 (type $0) (result funcref) - ;; CHECK-NEXT: (local $0 (ref nofunc)) + ;; CHECK-NEXT: (local $0 (ref exact nofunc)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (ref.null nofunc) diff --git a/test/lit/passes/global-refining.wast b/test/lit/passes/global-refining.wast index 927dfd1f26c..7be5a9e4e2a 100644 --- a/test/lit/passes/global-refining.wast +++ b/test/lit/passes/global-refining.wast @@ -11,8 +11,8 @@ ;; CLOSD: (type $foo_t (func)) (type $foo_t (func)) - ;; CHECK: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) - ;; CLOSD: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) + ;; CHECK: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CLOSD: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null $foo_t)) ;; CHECK: (global $func-func-init (mut (ref $foo_t)) (ref.func $foo)) ;; CLOSD: (global $func-func-init (mut (ref $foo_t)) (ref.func $foo)) @@ -32,8 +32,8 @@ ;; CLOSD: (type $foo_t (func)) (type $foo_t (func)) - ;; CHECK: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) - ;; CLOSD: (global $func-null-init (mut nullfuncref) (ref.null nofunc)) + ;; CHECK: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CLOSD: (global $func-null-init (mut (exact nullfuncref)) (ref.null nofunc)) (global $func-null-init (mut funcref) (ref.null $foo_t)) ;; CHECK: (global $func-func-init (mut (ref null $foo_t)) (ref.func $foo)) ;; CLOSD: (global $func-func-init (mut (ref null $foo_t)) (ref.func $foo)) @@ -202,16 +202,16 @@ ;; (ref null func) to nullfuncref only when not exported, and if exported, then ;; only when immutable in open world. (module - ;; CHECK: (global $mut (mut nullfuncref) (ref.null nofunc)) - ;; CLOSD: (global $mut (mut nullfuncref) (ref.null nofunc)) + ;; CHECK: (global $mut (mut (exact nullfuncref)) (ref.null nofunc)) + ;; CLOSD: (global $mut (mut (exact nullfuncref)) (ref.null nofunc)) (global $mut (mut (ref null func)) (ref.null nofunc)) - ;; CHECK: (global $imm nullfuncref (ref.null nofunc)) - ;; CLOSD: (global $imm nullfuncref (ref.null nofunc)) + ;; CHECK: (global $imm (exact nullfuncref) (ref.null nofunc)) + ;; CLOSD: (global $imm (exact nullfuncref) (ref.null nofunc)) (global $imm (ref null func) (ref.null nofunc)) ;; CHECK: (global $mut-exp (mut funcref) (ref.null nofunc)) ;; CLOSD: (global $mut-exp (mut funcref) (ref.null nofunc)) (global $mut-exp (mut (ref null func)) (ref.null nofunc)) - ;; CHECK: (global $imm-exp nullfuncref (ref.null nofunc)) + ;; CHECK: (global $imm-exp (exact nullfuncref) (ref.null nofunc)) ;; CLOSD: (global $imm-exp funcref (ref.null nofunc)) (global $imm-exp (ref null func) (ref.null nofunc)) diff --git a/test/lit/passes/gufa-refs.wast b/test/lit/passes/gufa-refs.wast index 6d48d651a5f..6142550638d 100644 --- a/test/lit/passes/gufa-refs.wast +++ b/test/lit/passes/gufa-refs.wast @@ -955,7 +955,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 1 ;; CHECK-NEXT: (local.get $child) @@ -970,7 +970,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $parent 0 ;; CHECK-NEXT: (local.get $parent) @@ -1072,9 +1072,9 @@ ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block (result nullref) + ;; CHECK-NEXT: (block $block (result (exact nullref)) ;; CHECK-NEXT: (br $block ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -1085,9 +1085,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block0 (result nullref) + ;; CHECK-NEXT: (block $block0 (result (exact nullref)) ;; CHECK-NEXT: (br $block0 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -1098,13 +1098,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block1 (result nullref) + ;; CHECK-NEXT: (block $block1 (result (exact nullref)) ;; CHECK-NEXT: (br $block1 - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (ref.cast (exact nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1237,7 +1237,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $child 0 ;; CHECK-NEXT: (local.get $child) @@ -1577,7 +1577,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $null ;; CHECK-NEXT: (array.new_default $null @@ -1775,10 +1775,10 @@ ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (global.set $x - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $storage - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $pass-through ;; CHECK-NEXT: (ref.null none) @@ -1928,7 +1928,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -2153,7 +2153,7 @@ ;; CHECK-NEXT: (pop (tuple anyref anyref)) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (tuple.drop 2 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -2213,7 +2213,7 @@ ;; CHECK: (func $func (type $1) (result (ref $"{}")) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block $block (result (ref none)) + ;; CHECK-NEXT: (block $block (result (ref exact none)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -2539,10 +2539,10 @@ ;; CHECK: (func $test-nulls (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast nullref - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) @@ -2554,9 +2554,9 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (ref.cast (exact nullref) ;; CHECK-NEXT: (select (result i31ref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.i31 @@ -3256,7 +3256,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.eq - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: ) @@ -3793,7 +3793,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (array.get $chars ;; CHECK-NEXT: (local.get $chars) @@ -6056,7 +6056,7 @@ ) (module - ;; CHECK: (type $0 (func (result i64 nullref i32))) + ;; CHECK: (type $0 (func (result i64 (exact nullref) i32))) ;; CHECK: (type $array (sub (array (mut i8)))) (type $array (sub (array (mut i8)))) @@ -6096,7 +6096,7 @@ ;; CHECK: (func $loop-tuple-br_on (type $2) ;; CHECK-NEXT: (tuple.drop 3 - ;; CHECK-NEXT: (loop $loop (type $0) (result i64 nullref i32) + ;; CHECK-NEXT: (loop $loop (type $0) (result i64 (exact nullref) i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop diff --git a/test/lit/passes/gufa-vs-cfp.wast b/test/lit/passes/gufa-vs-cfp.wast index bd731d89150..eb26820b74d 100644 --- a/test/lit/passes/gufa-vs-cfp.wast +++ b/test/lit/passes/gufa-vs-cfp.wast @@ -1087,7 +1087,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct3 2 ;; CHECK-NEXT: (local.get $ref) @@ -1329,7 +1329,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $create3) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/heap2local-rmw.wast b/test/lit/passes/heap2local-rmw.wast index 33fef1cb506..f0b397dcc99 100644 --- a/test/lit/passes/heap2local-rmw.wast +++ b/test/lit/passes/heap2local-rmw.wast @@ -50,7 +50,7 @@ ;; CHECK-NEXT: (local $1 (ref null $struct)) ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct 0 ;; CHECK-NEXT: (local.get $0) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -74,7 +74,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -107,7 +107,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -140,7 +140,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -173,7 +173,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -206,7 +206,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -239,7 +239,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -270,7 +270,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -312,7 +312,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -345,7 +345,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -378,7 +378,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -411,7 +411,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -444,7 +444,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -477,7 +477,7 @@ ;; CHECK-NEXT: (local $1 i64) ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -508,7 +508,7 @@ ;; CHECK-NEXT: (local $2 i64) ;; CHECK-NEXT: (local $3 i64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i64.const 0) ;; CHECK-NEXT: ) @@ -550,7 +550,7 @@ ;; CHECK-NEXT: (local $2 (ref null $struct)) ;; CHECK-NEXT: (local $3 (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -581,7 +581,7 @@ ;; CHECK-NEXT: (local $4 (ref null $struct)) ;; CHECK-NEXT: (local $5 (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -623,7 +623,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -657,7 +657,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/heap2local.wast b/test/lit/passes/heap2local.wast index 619a33a23c5..8b0384671c3 100644 --- a/test/lit/passes/heap2local.wast +++ b/test/lit/passes/heap2local.wast @@ -50,7 +50,7 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -74,7 +74,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -102,7 +102,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -135,7 +135,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -162,7 +162,7 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -193,7 +193,7 @@ ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (local $6 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) @@ -259,7 +259,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $5 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -318,7 +318,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) @@ -388,7 +388,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct.A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new_default $struct.A) ;; CHECK-NEXT: ) @@ -418,7 +418,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -456,7 +456,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -494,7 +494,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -559,7 +559,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -673,7 +673,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -713,7 +713,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -771,7 +771,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -821,7 +821,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -901,7 +901,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -927,7 +927,7 @@ ;; CHECK-NEXT: (local $ref (ref null $struct.recursive)) ;; CHECK-NEXT: (local $1 (ref null $struct.recursive)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -975,7 +975,7 @@ ;; CHECK-NEXT: (local $1 (ref null $struct.recursive)) ;; CHECK-NEXT: (local $2 (ref null $struct.recursive)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (struct.new_default $struct.recursive) ;; CHECK-NEXT: ) @@ -1070,7 +1070,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result (ref $struct.A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (local.get $a) ;; CHECK-NEXT: ) @@ -1104,7 +1104,7 @@ ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (loop $outer ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) @@ -1271,7 +1271,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1287,7 +1287,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1303,7 +1303,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1343,7 +1343,7 @@ ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1362,7 +1362,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1411,7 +1411,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1422,7 +1422,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1553,7 +1553,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1749,7 +1749,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1800,7 +1800,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1851,7 +1851,7 @@ ;; CHECK-NEXT: (block $block (result (ref null $struct.A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (br_if $block - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) @@ -1905,7 +1905,7 @@ ;; CHECK-NEXT: (br_if $loop ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1938,7 +1938,7 @@ ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (local $2 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -1977,7 +1977,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2061,7 +2061,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2096,7 +2096,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2138,7 +2138,7 @@ ;; CHECK-NEXT: (local.get $other) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2174,7 +2174,7 @@ ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (local $4 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2216,7 +2216,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (ref.eq - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2253,7 +2253,7 @@ ;; CHECK-NEXT: (local $3 f64) ;; CHECK-NEXT: (ref.eq ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2287,7 +2287,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2334,7 +2334,7 @@ ;; CHECK-NEXT: (local $6 i32) ;; CHECK-NEXT: (local $7 f64) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2351,7 +2351,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2396,7 +2396,7 @@ ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.is_null - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2450,7 +2450,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2472,7 +2472,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $6 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2494,7 +2494,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $10 ;; CHECK-NEXT: (i32.const 3) ;; CHECK-NEXT: ) @@ -2560,7 +2560,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2582,7 +2582,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $7 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) @@ -2648,7 +2648,7 @@ ;; CHECK-NEXT: (ref.cast (ref $B) ;; CHECK-NEXT: (block (result (ref $A)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2697,7 +2697,7 @@ ;; CHECK-NEXT: (local $2 (ref $A)) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2736,7 +2736,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2776,7 +2776,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (ref.null none) @@ -2819,9 +2819,9 @@ ;; CHECK-NEXT: (local $0 i32) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (block (result nullref) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result (exact nullref)) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -2870,7 +2870,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -2907,7 +2907,7 @@ ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $2 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -3005,7 +3005,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3151,7 +3151,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 1337) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3197,7 +3197,7 @@ ;; CHECK-NEXT: (local $4 i32) ;; CHECK-NEXT: (local $5 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) @@ -3327,11 +3327,11 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3391,7 +3391,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $8 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -3500,7 +3500,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3512,7 +3512,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3533,7 +3533,7 @@ ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (call $get-i32) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $24 ;; CHECK-NEXT: (local.get $4) ;; CHECK-NEXT: ) @@ -3706,7 +3706,7 @@ ;; CHECK: (func $array.nested.refinalize.get (type $3) (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3726,7 +3726,7 @@ ;; CHECK: (func $array.nested.refinalize.set (type $2) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -3756,7 +3756,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -3849,7 +3849,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3867,7 +3867,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3916,7 +3916,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -3983,7 +3983,7 @@ ;; CHECK-NEXT: (i32.const 2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -4030,7 +4030,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4070,7 +4070,7 @@ ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $3 ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) @@ -4173,7 +4173,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4253,7 +4253,7 @@ ;; CHECK-NEXT: (local $2 i32) ;; CHECK-NEXT: (local $3 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4319,7 +4319,7 @@ ;; CHECK: (func $array.cast.struct (type $0) (result (ref struct)) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4341,7 +4341,7 @@ ;; CHECK: (func $array.cast.struct.null (type $3) (result structref) ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4380,7 +4380,7 @@ ;; CHECK-NEXT: (local $eq (ref eq)) ;; CHECK-NEXT: (local $array (ref array)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4405,7 +4405,7 @@ ;; CHECK-NEXT: (local.tee $struct ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -4442,7 +4442,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result structref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) @@ -4513,7 +4513,7 @@ ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (local.get $2) ;; CHECK-NEXT: ) @@ -4565,7 +4565,7 @@ ;; CHECK-NEXT: (local.set $0 ;; CHECK-NEXT: (local.get $7) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (local.set $4 ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) @@ -4620,7 +4620,7 @@ ;; CHECK-NEXT: (local $0 (ref null $struct)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null (shared none))) + ;; CHECK-NEXT: (block (result (ref null exact (shared none))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) @@ -4666,7 +4666,7 @@ ;; CHECK-NEXT: (local $0 (ref null $struct)) ;; CHECK-NEXT: (local $1 i32) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref null (shared none))) + ;; CHECK-NEXT: (block (result (ref null exact (shared none))) ;; CHECK-NEXT: (local.set $1 ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/local-subtyping-exact.wast b/test/lit/passes/local-subtyping-exact.wast new file mode 100644 index 00000000000..725c7d499fb --- /dev/null +++ b/test/lit/passes/local-subtyping-exact.wast @@ -0,0 +1,39 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that LocalSubtyping handles exact references properly when it +;; determines that a local that would otherwise be non-nullable must be nullable +;; because of control flow dominance constraints. + +;; RUN: wasm-opt %s -all --local-subtyping -S -o - | filecheck %s + +(module + ;; CHECK: (func $test (type $0) (param $0 (exact nullref)) (result anyref) + ;; CHECK-NEXT: (local $1 (exact nullref)) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + (func $test (param (exact nullref)) (result anyref) + (local (exact nullref)) + (if + (i32.const 0) + (then + (local.set 1 + ;; This would let the local be (ref exact none) if it dominated the get. + (ref.as_non_null + (local.get 0) + ) + ) + ) + ) + (local.get 1) + ) +) diff --git a/test/lit/passes/local-subtyping-nn.wast b/test/lit/passes/local-subtyping-nn.wast index 3754230d82f..f2237156f01 100644 --- a/test/lit/passes/local-subtyping-nn.wast +++ b/test/lit/passes/local-subtyping-nn.wast @@ -8,7 +8,7 @@ (import "out" "i32" (func $i32 (result i32))) ;; CHECK: (func $non-nullable (type $1) - ;; CHECK-NEXT: (local $x (ref none)) + ;; CHECK-NEXT: (local $x (ref exact none)) ;; CHECK-NEXT: (local $y (ref $0)) ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (ref.as_non_null @@ -41,7 +41,7 @@ ) ;; CHECK: (func $uses-default (type $2) (param $i i32) - ;; CHECK-NEXT: (local $x nullref) + ;; CHECK-NEXT: (local $x (exact nullref)) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $i) ;; CHECK-NEXT: (then diff --git a/test/lit/passes/local-subtyping.wast b/test/lit/passes/local-subtyping.wast index 795a0a01cd1..777df93524f 100644 --- a/test/lit/passes/local-subtyping.wast +++ b/test/lit/passes/local-subtyping.wast @@ -273,7 +273,7 @@ ) ;; CHECK: (func $multiple-iterations-refinalize-call-ref-bottom (type $0) - ;; CHECK-NEXT: (local $f nullfuncref) + ;; CHECK-NEXT: (local $f (exact nullfuncref)) ;; CHECK-NEXT: (local $x (ref none)) ;; CHECK-NEXT: (local.set $f ;; CHECK-NEXT: (ref.null nofunc) diff --git a/test/lit/passes/merge-blocks.wast b/test/lit/passes/merge-blocks.wast index 86181f7a488..7a517ccbc27 100644 --- a/test/lit/passes/merge-blocks.wast +++ b/test/lit/passes/merge-blocks.wast @@ -20,7 +20,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block $label$1 (result i31ref) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label$1 nullref (ref none) + ;; CHECK-NEXT: (br_on_cast $label$1 (exact nullref) (ref exact none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/monomorphize-context.wast b/test/lit/passes/monomorphize-context.wast index 7f9cb2f566e..92b483a7ae1 100644 --- a/test/lit/passes/monomorphize-context.wast +++ b/test/lit/passes/monomorphize-context.wast @@ -277,7 +277,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (local.set $21 -;; ALWAYS-NEXT: (ref.cast nullref +;; ALWAYS-NEXT: (ref.cast (exact nullref) ;; ALWAYS-NEXT: (ref.null none) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) @@ -555,7 +555,7 @@ ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: (local.set $21 -;; ALWAYS-NEXT: (ref.cast nullref +;; ALWAYS-NEXT: (ref.cast (exact nullref) ;; ALWAYS-NEXT: (ref.null none) ;; ALWAYS-NEXT: ) ;; ALWAYS-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-exact.wast b/test/lit/passes/optimize-instructions-exact.wast new file mode 100644 index 00000000000..61769b78f34 --- /dev/null +++ b/test/lit/passes/optimize-instructions-exact.wast @@ -0,0 +1,33 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; Check that optimizations on casts involving exact reference types work +;; correctly. + +;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s + +(module + ;; CHECK: (func $cast-any-to-exact-none (type $0) (param $0 anyref) (result (exact nullref)) + ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $cast-any-to-exact-none (param anyref) (result (exact nullref)) + ;; This will not be changed, but should not trigger an assertion. + (ref.cast (exact nullref) + (local.get 0) + ) + ) + ;; CHECK: (func $cast-null-to-exact-none (type $1) (result (exact nullref)) + ;; CHECK-NEXT: (local $0 nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + (func $cast-null-to-exact-none (result (exact nullref)) + (local nullref) + (ref.cast (exact nullref) + (local.get 0) + ) + ) +) diff --git a/test/lit/passes/optimize-instructions-gc-tnh.wast b/test/lit/passes/optimize-instructions-gc-tnh.wast index c930f2fc19a..3893e048e14 100644 --- a/test/lit/passes/optimize-instructions-gc-tnh.wast +++ b/test/lit/passes/optimize-instructions-gc-tnh.wast @@ -509,7 +509,7 @@ ;; TNH-NEXT: ) ;; TNH-NEXT: (block ;; TNH-NEXT: (drop - ;; TNH-NEXT: (block (result nullref) + ;; TNH-NEXT: (block (result (exact nullref)) ;; TNH-NEXT: (drop ;; TNH-NEXT: (call $get-i32) ;; TNH-NEXT: ) @@ -532,7 +532,7 @@ ;; NO_TNH-NEXT: ) ;; NO_TNH-NEXT: (block ;; NO_TNH-NEXT: (drop - ;; NO_TNH-NEXT: (block (result nullref) + ;; NO_TNH-NEXT: (block (result (exact nullref)) ;; NO_TNH-NEXT: (drop ;; NO_TNH-NEXT: (call $get-i32) ;; NO_TNH-NEXT: ) diff --git a/test/lit/passes/optimize-instructions-gc.wast b/test/lit/passes/optimize-instructions-gc.wast index 4b88507ffc7..86804d7ddbd 100644 --- a/test/lit/passes/optimize-instructions-gc.wast +++ b/test/lit/passes/optimize-instructions-gc.wast @@ -1570,7 +1570,7 @@ ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.tee $a ;; CHECK-NEXT: (ref.null none) @@ -1590,7 +1590,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block (result nullref) ;; CHECK-NEXT: (ref.cast nullref diff --git a/test/lit/passes/precompute-gc.wast b/test/lit/passes/precompute-gc.wast index fc4cc7c2ee9..94adaa53fbb 100644 --- a/test/lit/passes/precompute-gc.wast +++ b/test/lit/passes/precompute-gc.wast @@ -28,7 +28,7 @@ ;; CHECK: (func $test-fallthrough (type $func-return-i32) (result i32) ;; CHECK-NEXT: (local $x funcref) ;; CHECK-NEXT: (local.set $x - ;; CHECK-NEXT: (block (result nullfuncref) + ;; CHECK-NEXT: (block (result (exact nullfuncref)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (call $test-fallthrough) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/remove-unused-brs-exact.wast b/test/lit/passes/remove-unused-brs-exact.wast new file mode 100644 index 00000000000..3bebd07de02 --- /dev/null +++ b/test/lit/passes/remove-unused-brs-exact.wast @@ -0,0 +1,40 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --remove-unused-brs -S -o - | filecheck %s + +;; Check that we optimize the cast correctly when the fallthrough has exact +;; type. In particular, we should not insert a ref.as_non_null, which would +;; trap. + +(module + ;; CHECK: (func $br_on_cast_fail (type $0) (param $0 (exact nullref)) + ;; CHECK-NEXT: (local $1 nullref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.cast (exact nullref) + ;; CHECK-NEXT: (local.tee $1 + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $br_on_cast_fail (param (ref null exact none)) + (local $1 nullref) + (drop + (block $block (result (ref none)) + (drop + (br_on_cast_fail $block nullref nullref + (local.tee $1 + (local.get 0) + ) + ) + ) + (return) + ) + ) + ) +) diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast index 268bbde2092..64f11f8ca0b 100644 --- a/test/lit/passes/remove-unused-brs-gc.wast +++ b/test/lit/passes/remove-unused-brs-gc.wast @@ -63,7 +63,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) @@ -119,7 +119,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (ref.cast (ref null $struct) ;; CHECK-NEXT: (local.tee $any @@ -438,7 +438,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.get $nullable-struct2) ;; CHECK-NEXT: ) @@ -509,7 +509,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.tee $any ;; CHECK-NEXT: (local.get $nullable-struct2) @@ -571,7 +571,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $block ;; CHECK-NEXT: (local.tee $any ;; CHECK-NEXT: (local.get $nullable-struct2) @@ -706,7 +706,7 @@ ;; CHECK-NEXT: (if (result i32) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (ref.test (ref none) + ;; CHECK-NEXT: (ref.test (ref exact none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -716,13 +716,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (if (result nullref) + ;; CHECK-NEXT: (if (result (exact nullref)) ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (ref.cast nullref + ;; CHECK-NEXT: (ref.cast (exact nullref) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -734,7 +734,7 @@ ;; CHECK-NEXT: (then ;; CHECK-NEXT: (block $something (result (ref null $struct)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (br_on_non_null $something ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) @@ -750,8 +750,8 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (select (result nullref) - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (select (result (exact nullref)) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (block $nothing ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block @@ -858,7 +858,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (select (result nullref) + ;; CHECK-NEXT: (select (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: (local.get $x) diff --git a/test/lit/passes/remove-unused-types-exact.wast b/test/lit/passes/remove-unused-types-exact.wast new file mode 100644 index 00000000000..6cb9c1ddd74 --- /dev/null +++ b/test/lit/passes/remove-unused-types-exact.wast @@ -0,0 +1,16 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --closed-world --remove-unused-types -S -o - | filecheck %s + +;; Test that a simple type rewrite handles exact references in heap type +;; definitions correctly. In particular, the function should continue returning +;; an exact nullref and the call expression should have the same type. + +(module + ;; CHECK: (func $return-exact (type $0) (result (exact nullref)) + ;; CHECK-NEXT: (call $return-exact) + ;; CHECK-NEXT: ) + (func $return-exact (result (exact nullref)) + (call $return-exact) + ) +) diff --git a/test/lit/passes/signature-refining_gto.wat b/test/lit/passes/signature-refining_gto.wat index c69eeb24455..1eedcd4f67c 100644 --- a/test/lit/passes/signature-refining_gto.wat +++ b/test/lit/passes/signature-refining_gto.wat @@ -9,11 +9,11 @@ ;; CHECK-NOT: (type $A (type $A (struct (field (mut (ref null $A))))) - ;; CHECK: (type $0 (func (param (ref none)))) + ;; CHECK: (type $0 (func (param (ref exact none)))) ;; CHECK: (type $1 (func (param funcref i32))) - ;; CHECK: (func $struct.get (type $0) (param $0 (ref none)) + ;; CHECK: (func $struct.get (type $0) (param $0 (ref exact none)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/ssa.wast b/test/lit/passes/ssa.wast index 0d696f75a0d..a0b18b0777d 100644 --- a/test/lit/passes/ssa.wast +++ b/test/lit/passes/ssa.wast @@ -36,9 +36,9 @@ ;; CHECK: (func $refine-to-null (type $3) (result (ref $A)) ;; CHECK-NEXT: (local $0 (ref null $A)) - ;; CHECK-NEXT: (block $label (result (ref none)) + ;; CHECK-NEXT: (block $label (result (ref exact none)) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (br_on_cast $label nullref (ref none) + ;; CHECK-NEXT: (br_on_cast $label (exact nullref) (ref exact none) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) diff --git a/test/lit/passes/type-refining-gufa.wast b/test/lit/passes/type-refining-gufa.wast index c866d80dca2..7fe9581f1ed 100644 --- a/test/lit/passes/type-refining-gufa.wast +++ b/test/lit/passes/type-refining-gufa.wast @@ -28,7 +28,7 @@ ;; NRML: (rec ;; NRML-NEXT: (type $A (sub (struct (field (mut nullref))))) ;; GUFA: (rec - ;; GUFA-NEXT: (type $A (sub (struct (field (mut nullref))))) + ;; GUFA-NEXT: (type $A (sub (struct (field (mut (exact nullref)))))) ;; O3O3: (rec ;; O3O3-NEXT: (type $A (sub (struct))) (type $A (sub (struct (field (mut anyref))))) diff --git a/test/lit/passes/type-refining-isorecursive.wast b/test/lit/passes/type-refining-isorecursive.wast index 537f99e668e..025b8cec9f5 100644 --- a/test/lit/passes/type-refining-isorecursive.wast +++ b/test/lit/passes/type-refining-isorecursive.wast @@ -5,11 +5,11 @@ ;; The types should be refined to a set of three mutually recursive types. ;; CHECK: (rec - ;; CHECK-NEXT: (type $2 (sub (struct (field nullexternref) (field (ref $0))))) + ;; CHECK-NEXT: (type $2 (sub (struct (field (exact nullexternref)) (field (ref $0))))) - ;; CHECK: (type $1 (sub (struct (field nullfuncref) (field (ref $2))))) + ;; CHECK: (type $1 (sub (struct (field (exact nullfuncref)) (field (ref $2))))) - ;; CHECK: (type $0 (sub (struct (field nullref) (field (ref $1))))) + ;; CHECK: (type $0 (sub (struct (field (exact nullref)) (field (ref $1))))) (type $0 (sub (struct nullref anyref))) (type $1 (sub (struct nullfuncref anyref))) (type $2 (sub (struct nullexternref anyref))) diff --git a/test/lit/passes/type-refining-rmw.wast b/test/lit/passes/type-refining-rmw.wast index a4bb0a85cae..7739fceb0f4 100644 --- a/test/lit/passes/type-refining-rmw.wast +++ b/test/lit/passes/type-refining-rmw.wast @@ -6,7 +6,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null (shared none))))))) + ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null exact (shared none))))))) (type $null (shared (struct (field (mut (ref null (shared any))))))) ;; CHECK: (type $i31 (shared (struct (field (mut (ref (shared i31))))))) @@ -75,7 +75,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null (shared none))))))) + ;; CHECK-NEXT: (type $null (shared (struct (field (mut (ref null exact (shared none))))))) (type $null (shared (struct (field (mut (ref null (shared eq))))))) ;; CHECK: (type $i31 (shared (struct (field (mut (ref (shared i31))))))) diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index 622e7422011..02357e8b6c0 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -1101,7 +1101,7 @@ ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result nullref) + ;; CHECK-NEXT: (block (result (exact nullref)) ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -1180,7 +1180,7 @@ (module (rec ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct (field (mut nullref)))) + ;; CHECK-NEXT: (type $A (struct (field (mut (exact nullref))))) (type $A (struct (field (mut anyref)))) ;; CHECK: (type $B (struct (field (mut nullref)))) (type $B (struct (field (mut (ref null $A))))) @@ -1234,7 +1234,7 @@ (module ;; CHECK: (rec - ;; CHECK-NEXT: (type $A (struct (field (mut (ref noextern))))) + ;; CHECK-NEXT: (type $A (struct (field (mut (ref exact noextern))))) (type $A (struct (field (mut externref)))) ;; CHECK: (type $1 (func)) @@ -1252,7 +1252,7 @@ ;; CHECK: (func $struct.new (type $2) (param $extern externref) (result anyref) ;; CHECK-NEXT: (struct.new $A - ;; CHECK-NEXT: (ref.cast (ref noextern) + ;; CHECK-NEXT: (ref.cast (ref exact noextern) ;; CHECK-NEXT: (try (result externref) ;; CHECK-NEXT: (do ;; CHECK-NEXT: (struct.get $A 0 @@ -1304,7 +1304,7 @@ ;; CHECK: (func $struct.set (type $3) (param $ref (ref $A)) (param $extern externref) ;; CHECK-NEXT: (struct.set $A 0 ;; CHECK-NEXT: (local.get $ref) - ;; CHECK-NEXT: (ref.cast (ref noextern) + ;; CHECK-NEXT: (ref.cast (ref exact noextern) ;; CHECK-NEXT: (try (result externref) ;; CHECK-NEXT: (do ;; CHECK-NEXT: (struct.get $A 0 @@ -1581,7 +1581,7 @@ (type $never (sub (struct (field i32)))) ;; CHECK: (rec - ;; CHECK-NEXT: (type $optimizable (struct (field (mut nullfuncref)))) + ;; CHECK-NEXT: (type $optimizable (struct (field (mut (exact nullfuncref))))) (type $optimizable (struct (field (mut (ref null func))))) ;; CHECK: (type $2 (func)) @@ -1604,7 +1604,7 @@ ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop - ;; CHECK-NEXT: (block (result (ref none)) + ;; CHECK-NEXT: (block (result (ref exact none)) ;; CHECK-NEXT: (ref.as_non_null ;; CHECK-NEXT: (ref.null none) ;; CHECK-NEXT: ) diff --git a/test/passes/precompute_all-features.txt b/test/passes/precompute_all-features.txt index 189adadcec1..1372035920f 100644 --- a/test/passes/precompute_all-features.txt +++ b/test/passes/precompute_all-features.txt @@ -286,7 +286,7 @@ ) ) (drop - (block $l2 (result nullexternref) + (block $l2 (result (exact nullexternref)) (drop (block $l3 (global.set $global-mut From 8587ddc293dfe1ec1390b2ea8d20847f071885bd Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Tue, 15 Apr 2025 11:22:32 -0700 Subject: [PATCH 3/3] update test --- test/lit/passes/type-refining-gufa.wast | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lit/passes/type-refining-gufa.wast b/test/lit/passes/type-refining-gufa.wast index 7fe9581f1ed..2304086078c 100644 --- a/test/lit/passes/type-refining-gufa.wast +++ b/test/lit/passes/type-refining-gufa.wast @@ -376,7 +376,7 @@ ;; the field to nullref. (module ;; NRML: (type $struct (struct (field nullfuncref))) - ;; GUFA: (type $struct (struct (field nullfuncref))) + ;; GUFA: (type $struct (struct (field (exact nullfuncref)))) (type $struct (struct (field funcref))) ;; NRML: (global $C (ref $struct) (struct.new_default $struct))