diff options
author | Thomas Lively <7121787+tlively@users.noreply.github.com> | 2021-06-08 15:22:15 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-08 12:22:15 -0700 |
commit | 28f227fb90d18d8c1fc2c52b592d2934469aef8e (patch) | |
tree | 145f01aad94a4716d180aeface9f9121f141cb5f | |
parent | 811f1614edef867228edaac08bdb5179325eac27 (diff) | |
download | binaryen-28f227fb90d18d8c1fc2c52b592d2934469aef8e.tar.gz binaryen-28f227fb90d18d8c1fc2c52b592d2934469aef8e.tar.bz2 binaryen-28f227fb90d18d8c1fc2c52b592d2934469aef8e.zip |
Initial nominal typing support (#3919)
In nominal mode, HeapType constructors besides the Signature constructor always
produce fresh types distinct from any previously created types. The HeapType
constructor that takes a Signature maintains its previous behavior of
constructing a canonical representative of the given signature because it is
used frequently throughout the code base and never in a situation that would
benefit from creating a fresh type. It is left as future work to clean up this
discrepancy between the Signature HeapType constructor and other HeapType
constructors.
TypeBuilder skips shape and global canonicalization in nominal mode and always
creates a fresh type for each of its entries. For this to work without any
canonicalization, the TypeBuilder allocates temporary types on the global Type
store and does not support building basic HeapTypes in nominal mode.
The new mode is not available in any of the Binaryen tools yet because it is
still missing critical functionality like the ability to declare subtyping
relations and correctly calculate LUBs. This functionality will be implemented
in future PRs.
-rw-r--r-- | src/wasm-type.h | 26 | ||||
-rw-r--r-- | src/wasm/wasm-type.cpp | 187 | ||||
-rw-r--r-- | test/example/type-builder-nominal.cpp | 266 | ||||
-rw-r--r-- | test/example/type-builder-nominal.txt | 92 |
4 files changed, 531 insertions, 40 deletions
diff --git a/src/wasm-type.h b/src/wasm-type.h index 03e8c129f..11b23ed52 100644 --- a/src/wasm-type.h +++ b/src/wasm-type.h @@ -32,6 +32,15 @@ namespace wasm { +enum class TypeSystem { + Equirecursive, + Nominal, +}; + +// This should only ever be called before any Types or HeapTypes have been +// created. The default system is equirecursive. +void setTypeSystem(TypeSystem system); + // The types defined in this file. All of them are small and typically passed by // value except for `Tuple` and `Struct`, which may own an unbounded amount of // data. @@ -328,7 +337,16 @@ public: // Choose an arbitrary heap type as the default. constexpr HeapType() : HeapType(func) {} + // Construct a HeapType referring to the single canonical HeapType for the + // given signature. In nominal mode, this is the first HeapType created with + // this signature. HeapType(Signature signature); + + // Create a HeapType with the given structure. In equirecursive mode, this may + // be the same as a previous HeapType created with the same contents. In + // nominal mode, this will be a fresh type distinct from all previously + // created HeapTypes. + // TODO: make these explicit to differentiate them. HeapType(const Struct& struct_); HeapType(Struct&& struct_); HeapType(Array array); @@ -513,7 +531,8 @@ struct TypeBuilder { // The number of HeapType slots in the TypeBuilder. size_t size(); - // Sets the heap type at index `i`. May only be called before `build`. + // Sets the heap type at index `i`. May only be called before `build`. The + // BasicHeapType overload may not be used in nominal mode. void setHeapType(size_t i, HeapType::BasicHeapType basic); void setHeapType(size_t i, Signature signature); void setHeapType(size_t i, const Struct& struct_); @@ -531,8 +550,9 @@ struct TypeBuilder { Type getTempRefType(HeapType heapType, Nullability nullable); Type getTempRttType(Rtt rtt); - // Canonicalizes and returns all of the heap types. May only be called once - // all of the heap types have been initialized with `setHeapType`. + // Returns all of the newly constructed heap types. May only be called once + // all of the heap types have been initialized with `setHeapType`. In nominal + // mode, all of the constructed HeapTypes will be fresh and distinct. std::vector<HeapType> build(); // Utility for ergonomically using operator[] instead of explicit setHeapType diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 8f3ea535b..8d107a833 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -42,6 +42,9 @@ namespace wasm { +static TypeSystem typeSystem = TypeSystem::Equirecursive; +void setTypeSystem(TypeSystem system) { typeSystem = system; } + namespace { struct TypeInfo { @@ -403,9 +406,7 @@ public: template<> class hash<wasm::HeapTypeInfo> { public: - size_t operator()(const wasm::HeapTypeInfo& info) const { - return wasm::FiniteShapeHasher().hash(info); - } + size_t operator()(const wasm::HeapTypeInfo& info) const; }; template<typename T> class hash<reference_wrapper<const T>> { @@ -636,7 +637,24 @@ bool HeapTypeInfo::operator==(const HeapTypeInfo& other) const { // important during global canonicalization, when newly created // canonically-shaped graphs are checked against the existing globally // canonical graphs. - return FiniteShapeEquator().eq(*this, other); + if (typeSystem == TypeSystem::Equirecursive) { + return FiniteShapeEquator().eq(*this, other); + } + + if (kind != other.kind) { + return false; + } + switch (kind) { + case wasm::HeapTypeInfo::BasicKind: + return basic == other.basic; + case wasm::HeapTypeInfo::SignatureKind: + return signature == other.signature; + case wasm::HeapTypeInfo::StructKind: + return struct_ == other.struct_; + case wasm::HeapTypeInfo::ArrayKind: + return array == other.array; + } + WASM_UNREACHABLE("unexpected kind"); } template<typename Info> struct Store { @@ -652,11 +670,12 @@ template<typename Info> struct Store { bool isGlobalStore(); #endif - typename Info::type_t canonicalize(const Info& info); - typename Info::type_t canonicalize(std::unique_ptr<Info>&& info); + typename Info::type_t insert(const Info& info); + typename Info::type_t insert(std::unique_ptr<Info>&& info); + bool hasCanonical(const Info& info, typename Info::type_t& canonical); private: - TypeID recordCanonical(std::unique_ptr<Info>&& info); + TypeID doInsert(std::unique_ptr<Info>&& info); }; using TypeStore = Store<TypeInfo>; @@ -689,40 +708,60 @@ template<typename Info> bool Store<Info>::isGlobalStore() { #endif template<typename Info> -typename Info::type_t Store<Info>::canonicalize(const Info& info) { +typename Info::type_t Store<Info>::insert(const Info& info) { typename Info::type_t canonical; if (info.getCanonical(canonical)) { return canonical; } std::lock_guard<std::recursive_mutex> lock(mutex); - auto indexIt = typeIDs.find(std::cref(info)); - if (indexIt != typeIDs.end()) { - return typename Info::type_t(indexIt->second); + // Only HeapTypes in Nominal mode should be unconditionally added. In all + // other cases, deduplicate with existing types. + if (std::is_same<Info, TypeInfo>::value || + typeSystem == TypeSystem::Equirecursive) { + auto indexIt = typeIDs.find(std::cref(info)); + if (indexIt != typeIDs.end()) { + return typename Info::type_t(indexIt->second); + } } - return typename Info::type_t(recordCanonical(std::make_unique<Info>(info))); + return typename Info::type_t(doInsert(std::make_unique<Info>(info))); } template<typename Info> -typename Info::type_t Store<Info>::canonicalize(std::unique_ptr<Info>&& info) { +typename Info::type_t Store<Info>::insert(std::unique_ptr<Info>&& info) { typename Info::type_t canonical; if (info->getCanonical(canonical)) { return canonical; } std::lock_guard<std::recursive_mutex> lock(mutex); - auto indexIt = typeIDs.find(std::cref(*info)); - if (indexIt != typeIDs.end()) { - return typename Info::type_t(indexIt->second); + // Only HeapTypes in Nominal mode should be unconditionally added. In all + // other cases, deduplicate with existing types. + if (std::is_same<Info, TypeInfo>::value || + typeSystem == TypeSystem::Equirecursive) { + auto indexIt = typeIDs.find(std::cref(*info)); + if (indexIt != typeIDs.end()) { + return typename Info::type_t(indexIt->second); + } } info->isTemp = false; - return typename Info::type_t(recordCanonical(std::move(info))); + return typename Info::type_t(doInsert(std::move(info))); } template<typename Info> -TypeID Store<Info>::recordCanonical(std::unique_ptr<Info>&& info) { +bool Store<Info>::hasCanonical(const Info& info, typename Info::type_t& out) { + auto indexIt = typeIDs.find(std::cref(info)); + if (indexIt != typeIDs.end()) { + out = typename Info::type_t(indexIt->second); + return true; + } + return false; +} + +template<typename Info> +TypeID Store<Info>::doInsert(std::unique_ptr<Info>&& info) { assert((!isGlobalStore() || !info->isTemp) && "Leaking temporary type!"); TypeID id = uintptr_t(info.get()); assert(id > Info::type_t::_last_basic_type); - typeIDs[*info] = id; + typeIDs.insert({*info, id}); constructedTypes.emplace_back(std::move(info)); return id; } @@ -737,7 +776,7 @@ Type::Type(const Tuple& tuple) { assert(!isTemp(type) && "Leaking temporary type!"); } #endif - new (this) Type(globalTypeStore.canonicalize(tuple)); + new (this) Type(globalTypeStore.insert(tuple)); } Type::Type(Tuple&& tuple) { @@ -746,17 +785,17 @@ Type::Type(Tuple&& tuple) { assert(!isTemp(type) && "Leaking temporary type!"); } #endif - new (this) Type(globalTypeStore.canonicalize(std::move(tuple))); + new (this) Type(globalTypeStore.insert(std::move(tuple))); } Type::Type(HeapType heapType, Nullability nullable) { assert(!isTemp(heapType) && "Leaking temporary type!"); - new (this) Type(globalTypeStore.canonicalize(TypeInfo(heapType, nullable))); + new (this) Type(globalTypeStore.insert(TypeInfo(heapType, nullable))); } Type::Type(Rtt rtt) { assert(!isTemp(rtt.heapType) && "Leaking temporary type!"); - new (this) Type(globalTypeStore.canonicalize(rtt)); + new (this) Type(globalTypeStore.insert(rtt)); } bool Type::isTuple() const { @@ -1045,7 +1084,13 @@ const Type& Type::operator[](size_t index) const { HeapType::HeapType(Signature sig) { assert(!isTemp(sig.params) && "Leaking temporary type!"); assert(!isTemp(sig.results) && "Leaking temporary type!"); - new (this) HeapType(globalHeapTypeStore.canonicalize(sig)); + HeapType canonical; + if (typeSystem == TypeSystem::Nominal && + globalHeapTypeStore.hasCanonical(sig, canonical)) { + new (this) HeapType(canonical); + } else { + new (this) HeapType(globalHeapTypeStore.insert(sig)); + } } HeapType::HeapType(const Struct& struct_) { @@ -1054,7 +1099,7 @@ HeapType::HeapType(const Struct& struct_) { assert(!isTemp(field.type) && "Leaking temporary type!"); } #endif - new (this) HeapType(globalHeapTypeStore.canonicalize(struct_)); + new (this) HeapType(globalHeapTypeStore.insert(struct_)); } HeapType::HeapType(Struct&& struct_) { @@ -1063,12 +1108,12 @@ HeapType::HeapType(Struct&& struct_) { assert(!isTemp(field.type) && "Leaking temporary type!"); } #endif - new (this) HeapType(globalHeapTypeStore.canonicalize(std::move(struct_))); + new (this) HeapType(globalHeapTypeStore.insert(std::move(struct_))); } HeapType::HeapType(Array array) { assert(!isTemp(array.element.type) && "Leaking temporary type!"); - new (this) HeapType(globalHeapTypeStore.canonicalize(array)); + new (this) HeapType(globalHeapTypeStore.insert(array)); } bool HeapType::isFunction() const { @@ -2085,6 +2130,7 @@ size_t TypeBuilder::size() { return impl->entries.size(); } void TypeBuilder::setHeapType(size_t i, HeapType::BasicHeapType basic) { assert(i < size() && "Index out of bounds"); + assert(typeSystem != TypeSystem::Nominal); impl->entries[i].set(basic); } @@ -2114,7 +2160,10 @@ HeapType TypeBuilder::getTempHeapType(size_t i) { } Type TypeBuilder::getTempTupleType(const Tuple& tuple) { - Type ret = impl->typeStore.canonicalize(tuple); + if (typeSystem == TypeSystem::Nominal) { + return globalTypeStore.insert(tuple); + } + Type ret = impl->typeStore.insert(tuple); if (tuple.types.size() > 1) { return markTemp(ret); } else { @@ -2124,11 +2173,17 @@ Type TypeBuilder::getTempTupleType(const Tuple& tuple) { } Type TypeBuilder::getTempRefType(HeapType type, Nullability nullable) { - return markTemp(impl->typeStore.canonicalize(TypeInfo(type, nullable))); + if (typeSystem == TypeSystem::Nominal) { + return globalTypeStore.insert(TypeInfo(type, nullable)); + } + return markTemp(impl->typeStore.insert(TypeInfo(type, nullable))); } Type TypeBuilder::getTempRttType(Rtt rtt) { - return markTemp(impl->typeStore.canonicalize(rtt)); + if (typeSystem == TypeSystem::Nominal) { + return globalTypeStore.insert(rtt); + } + return markTemp(impl->typeStore.insert(rtt)); } namespace { @@ -2655,7 +2710,6 @@ void ShapeCanonicalizer::translatePartitionsToTypes() { // leaking into the global stores. std::vector<HeapType> globallyCanonicalize(std::vector<std::unique_ptr<HeapTypeInfo>>& infos) { - // Map each temporary Type and HeapType to the locations where they will // have to be replaced with canonical Types and HeapTypes. struct Locations : TypeGraphWalker<Locations> { @@ -2722,7 +2776,7 @@ globallyCanonicalize(std::vector<std::unique_ptr<HeapTypeInfo>>& infos) { continue; } HeapType original = asHeapType(info); - HeapType canonical = globalHeapTypeStore.canonicalize(std::move(info)); + HeapType canonical = globalHeapTypeStore.insert(std::move(info)); if (original != canonical) { canonicalHeapTypes[original] = canonical; } @@ -2740,7 +2794,7 @@ globallyCanonicalize(std::vector<std::unique_ptr<HeapTypeInfo>>& infos) { Type original = pair.first; auto& uses = pair.second; if (original.isTuple() == tuples) { - Type canonical = globalTypeStore.canonicalize(*getTypeInfo(original)); + Type canonical = globalTypeStore.insert(*getTypeInfo(original)); for (Type* use : uses) { *use = canonical; } @@ -2761,11 +2815,9 @@ globallyCanonicalize(std::vector<std::unique_ptr<HeapTypeInfo>>& infos) { return results; } -} // anonymous namespace - -std::vector<HeapType> TypeBuilder::build() { +std::vector<HeapType> buildEquirecursive(TypeBuilder& builder) { std::vector<HeapType> heapTypes; - for (auto& entry : impl->entries) { + for (auto& entry : builder.impl->entries) { assert(entry.initialized && "Cannot access uninitialized HeapType"); entry.info->isFinalized = true; heapTypes.push_back(entry.get()); @@ -2820,6 +2872,44 @@ std::vector<HeapType> TypeBuilder::build() { return heapTypes; } +std::vector<HeapType> buildNominal(TypeBuilder& builder) { +#if TIME_CANONICALIZATION + auto start = std::chrono::steady_clock::now(); +#endif + + // Just move the HeapTypes to the global store. The Types are already in the + // global store. + std::vector<HeapType> heapTypes; + for (auto& entry : builder.impl->entries) { + assert(entry.initialized && "Cannot access uninitialized HeapType"); + entry.info->isFinalized = true; + heapTypes.push_back(globalHeapTypeStore.insert(std::move(entry.info))); + } + +#if TIME_CANONICALIZATION + auto end = std::chrono::steady_clock::now(); + std::cerr << "Building took " + << std::chrono::duration_cast<std::chrono::milliseconds>(end - + start) + .count() + << " ms\n"; +#endif + + return heapTypes; +} + +} // anonymous namespace + +std::vector<HeapType> TypeBuilder::build() { + switch (typeSystem) { + case TypeSystem::Equirecursive: + return buildEquirecursive(*this); + case TypeSystem::Nominal: + return buildNominal(*this); + } + WASM_UNREACHABLE("unexpected type system"); +} + } // namespace wasm namespace std { @@ -2902,4 +2992,27 @@ size_t hash<wasm::TypeInfo>::operator()(const wasm::TypeInfo& info) const { WASM_UNREACHABLE("unexpected kind"); } +size_t +hash<wasm::HeapTypeInfo>::operator()(const wasm::HeapTypeInfo& info) const { + if (wasm::typeSystem == wasm::TypeSystem::Equirecursive) { + return wasm::FiniteShapeHasher().hash(info); + } + + auto digest = wasm::hash(info.kind); + switch (info.kind) { + case wasm::HeapTypeInfo::BasicKind: + WASM_UNREACHABLE("Basic HeapTypeInfo should have been canonicalized"); + case wasm::HeapTypeInfo::SignatureKind: + wasm::rehash(digest, info.signature); + return digest; + case wasm::HeapTypeInfo::StructKind: + wasm::rehash(digest, info.struct_); + return digest; + case wasm::HeapTypeInfo::ArrayKind: + wasm::rehash(digest, info.array); + return digest; + } + WASM_UNREACHABLE("unexpected kind"); +} + } // namespace std diff --git a/test/example/type-builder-nominal.cpp b/test/example/type-builder-nominal.cpp new file mode 100644 index 000000000..8da80d4bb --- /dev/null +++ b/test/example/type-builder-nominal.cpp @@ -0,0 +1,266 @@ +#include <cassert> +#include <iostream> + +#include "wasm-type.h" + +using namespace wasm; + +// Construct Signature, Struct, and Array heap types using undefined types. +void test_builder() { + std::cout << ";; Test TypeBuilder\n"; + + // (type $sig (func (param (ref $struct)) (result (ref $array) i32))) + // (type $struct (struct (field (ref null $array) (mut rtt 0 $array)))) + // (type $array (array (mut externref))) + + TypeBuilder builder; + assert(builder.size() == 0); + builder.grow(3); + assert(builder.size() == 3); + + Type refSig = builder.getTempRefType(builder[0], NonNullable); + Type refStruct = builder.getTempRefType(builder[1], NonNullable); + Type refArray = builder.getTempRefType(builder[2], NonNullable); + Type refNullArray = builder.getTempRefType(builder[2], Nullable); + Type rttArray = builder.getTempRttType(Rtt(0, builder[2])); + Type refNullExt(HeapType::ext, Nullable); + + Signature sig(refStruct, builder.getTempTupleType({refArray, Type::i32})); + Struct struct_({Field(refNullArray, Immutable), Field(rttArray, Mutable)}); + Array array(Field(refNullExt, Mutable)); + + std::cout << "Before setting heap types:\n"; + std::cout << "(ref $sig) => " << refSig << "\n"; + std::cout << "(ref $struct) => " << refStruct << "\n"; + std::cout << "(ref $array) => " << refArray << "\n"; + std::cout << "(ref null $array) => " << refNullArray << "\n"; + std::cout << "(rtt 0 $array) => " << rttArray << "\n\n"; + + builder[0] = sig; + builder[1] = struct_; + builder[2] = array; + + std::cout << "After setting heap types:\n"; + std::cout << "(ref $sig) => " << refSig << "\n"; + std::cout << "(ref $struct) => " << refStruct << "\n"; + std::cout << "(ref $array) => " << refArray << "\n"; + std::cout << "(ref null $array) => " << refNullArray << "\n"; + std::cout << "(rtt 0 $array) => " << rttArray << "\n\n"; + + std::vector<HeapType> built = builder.build(); + + Type newRefSig = Type(built[0], NonNullable); + Type newRefStruct = Type(built[1], NonNullable); + Type newRefArray = Type(built[2], NonNullable); + Type newRefNullArray = Type(built[2], Nullable); + Type newRttArray = Type(Rtt(0, built[2])); + + std::cout << "After building types:\n"; + std::cout << "(ref $sig) => " << newRefSig << "\n"; + std::cout << "(ref $struct) => " << newRefStruct << "\n"; + std::cout << "(ref $array) => " << newRefArray << "\n"; + std::cout << "(ref null $array) => " << newRefNullArray << "\n"; + std::cout << "(rtt 0 $array) => " << newRttArray << "\n\n"; +} + +// Check that the builder works when there are duplicate definitions +void test_canonicalization() { + std::cout << ";; Test canonicalization\n"; + + // (type $struct (struct (field (ref null $sig) (ref null $sig)))) + // (type $sig (func)) + HeapType sig = Signature(Type::none, Type::none); + HeapType struct_ = Struct({Field(Type(sig, Nullable), Immutable), + Field(Type(sig, Nullable), Immutable)}); + + TypeBuilder builder(4); + + Type tempSigRef1 = builder.getTempRefType(builder[2], Nullable); + Type tempSigRef2 = builder.getTempRefType(builder[3], Nullable); + + assert(tempSigRef1 != tempSigRef2); + assert(tempSigRef1 != Type(sig, Nullable)); + assert(tempSigRef2 != Type(sig, Nullable)); + + builder[0] = + Struct({Field(tempSigRef1, Immutable), Field(tempSigRef1, Immutable)}); + builder[1] = + Struct({Field(tempSigRef2, Immutable), Field(tempSigRef2, Immutable)}); + builder[2] = Signature(Type::none, Type::none); + builder[3] = Signature(Type::none, Type::none); + + std::vector<HeapType> built = builder.build(); + + assert(built[0] != struct_); + assert(built[1] != struct_); + assert(built[0] != built[1]); + assert(built[2] != sig); + assert(built[3] != sig); + assert(built[2] != built[3]); +} + +// Check that signatures created with TypeBuilders are properly recorded as +// canonical. +void test_signatures(bool warm) { + std::cout << ";; Test canonical signatures\n"; + + TypeBuilder builder(2); + Type tempRef = builder.getTempRefType(builder[0], Nullable); + builder[0] = Signature(Type::anyref, Type::i31ref); + builder[1] = Signature(tempRef, tempRef); + std::vector<HeapType> built = builder.build(); + + HeapType small = HeapType(Signature(Type::anyref, Type::i31ref)); + HeapType big = + HeapType(Signature(Type(Signature(Type::anyref, Type::i31ref), Nullable), + Type(Signature(Type::anyref, Type::i31ref), Nullable))); + if (warm) { + assert(built[0] != small); + assert(built[1] != big); + } else { + assert(built[0] == small); + assert(built[1] == big); + } +} + +void test_recursive() { + std::cout << ";; Test recursive types\n"; + + { + // Trivial recursion + std::vector<HeapType> built; + { + TypeBuilder builder(1); + Type temp = builder.getTempRefType(builder[0], Nullable); + builder[0] = Signature(Type::none, temp); + built = builder.build(); + } + std::cout << built[0] << "\n\n"; + assert(built[0] == built[0].getSignature().results.getHeapType()); + assert(Type(built[0], Nullable) == built[0].getSignature().results); + } + + { + // Mutual recursion + std::vector<HeapType> built; + { + TypeBuilder builder(2); + Type temp0 = builder.getTempRefType(builder[0], Nullable); + Type temp1 = builder.getTempRefType(builder[1], Nullable); + builder[0] = Signature(Type::none, temp1); + builder[1] = Signature(Type::none, temp0); + built = builder.build(); + } + std::cout << built[0] << "\n"; + std::cout << built[1] << "\n\n"; + assert(built[0].getSignature().results.getHeapType() == built[1]); + assert(built[1].getSignature().results.getHeapType() == built[0]); + assert(built[0] != built[1]); + } + + { + // A longer chain of recursion + std::vector<HeapType> built; + { + TypeBuilder builder(5); + Type temp0 = builder.getTempRefType(builder[0], Nullable); + Type temp1 = builder.getTempRefType(builder[1], Nullable); + Type temp2 = builder.getTempRefType(builder[2], Nullable); + Type temp3 = builder.getTempRefType(builder[3], Nullable); + Type temp4 = builder.getTempRefType(builder[4], Nullable); + builder[0] = Signature(Type::none, temp1); + builder[1] = Signature(Type::none, temp2); + builder[2] = Signature(Type::none, temp3); + builder[3] = Signature(Type::none, temp4); + builder[4] = Signature(Type::none, temp0); + built = builder.build(); + } + std::cout << built[0] << "\n"; + std::cout << built[1] << "\n"; + std::cout << built[2] << "\n"; + std::cout << built[3] << "\n"; + std::cout << built[4] << "\n\n"; + assert(built[0].getSignature().results.getHeapType() == built[1]); + assert(built[1].getSignature().results.getHeapType() == built[2]); + assert(built[2].getSignature().results.getHeapType() == built[3]); + assert(built[3].getSignature().results.getHeapType() == built[4]); + assert(built[4].getSignature().results.getHeapType() == built[0]); + assert(built[0] != built[1]); + assert(built[0] != built[2]); + assert(built[0] != built[3]); + assert(built[0] != built[4]); + assert(built[1] != built[2]); + assert(built[1] != built[3]); + assert(built[1] != built[4]); + assert(built[2] != built[3]); + assert(built[2] != built[4]); + assert(built[3] != built[4]); + } + + { + // Check canonicalization for non-recursive parents and children of + // recursive HeapTypes. + std::vector<HeapType> built; + { + TypeBuilder builder(6); + Type temp0 = builder.getTempRefType(builder[0], Nullable); + Type temp1 = builder.getTempRefType(builder[1], Nullable); + Type temp2 = builder.getTempRefType(builder[2], Nullable); + Type temp3 = builder.getTempRefType(builder[3], Nullable); + Type tuple0_2 = builder.getTempTupleType({temp0, temp2}); + Type tuple1_3 = builder.getTempTupleType({temp1, temp3}); + builder[0] = Signature(Type::none, tuple0_2); + builder[1] = Signature(Type::none, tuple1_3); + builder[2] = Signature(); + builder[3] = Signature(); + builder[4] = Signature(Type::none, temp0); + builder[5] = Signature(Type::none, temp1); + built = builder.build(); + } + std::cout << built[0] << "\n"; + std::cout << built[1] << "\n"; + std::cout << built[2] << "\n"; + std::cout << built[3] << "\n"; + std::cout << built[4] << "\n"; + std::cout << built[5] << "\n\n"; + assert(built[0] != built[1]); + assert(built[2] != built[3]); + assert(built[4] != built[5]); + assert(built[4].getSignature().results.getHeapType() == built[0]); + assert(built[5].getSignature().results.getHeapType() == built[1]); + assert(built[0].getSignature().results == + Type({Type(built[0], Nullable), Type(built[2], Nullable)})); + assert(built[1].getSignature().results == + Type({Type(built[1], Nullable), Type(built[3], Nullable)})); + } + + { + // Folded and unfolded + std::vector<HeapType> built; + { + TypeBuilder builder(2); + Type temp0 = builder.getTempRefType(builder[0], Nullable); + builder[0] = Signature(Type::none, temp0); + builder[1] = Signature(Type::none, temp0); + built = builder.build(); + } + std::cout << built[0] << "\n"; + std::cout << built[1] << "\n\n"; + assert(built[0].getSignature().results.getHeapType() == built[0]); + assert(built[1].getSignature().results.getHeapType() == built[0]); + assert(built[0] != built[1]); + } +} + +int main() { + wasm::setTypeSystem(TypeSystem::Nominal); + + // Run the tests twice to ensure things still work when the global stores are + // already populated. + for (size_t i = 0; i < 2; ++i) { + test_builder(); + test_canonicalization(); + test_signatures(i == 1); + test_recursive(); + } +} diff --git a/test/example/type-builder-nominal.txt b/test/example/type-builder-nominal.txt new file mode 100644 index 000000000..fd3c4cddb --- /dev/null +++ b/test/example/type-builder-nominal.txt @@ -0,0 +1,92 @@ +;; Test TypeBuilder +Before setting heap types: +(ref $sig) => (ref [T](func)) +(ref $struct) => (ref [T](func)) +(ref $array) => (ref [T](func)) +(ref null $array) => (ref null [T](func)) +(rtt 0 $array) => (rtt 0 [T](func)) + +After setting heap types: +(ref $sig) => (ref [T](func (param (ref [T](struct (field (ref null [T](array (mut externref))) (mut (rtt 0 [T](array (mut externref)))))))) (result (ref [T](array (mut externref))) i32))) +(ref $struct) => (ref [T](struct (field (ref null [T](array (mut externref))) (mut (rtt 0 [T](array (mut externref))))))) +(ref $array) => (ref [T](array (mut externref))) +(ref null $array) => (ref null [T](array (mut externref))) +(rtt 0 $array) => (rtt 0 [T](array (mut externref))) + +After building types: +(ref $sig) => (ref (func (param (ref (struct (field (ref null (array (mut externref))) (mut (rtt 0 (array (mut externref)))))))) (result (ref (array (mut externref))) i32))) +(ref $struct) => (ref (struct (field (ref null (array (mut externref))) (mut (rtt 0 (array (mut externref))))))) +(ref $array) => (ref (array (mut externref))) +(ref null $array) => (ref null (array (mut externref))) +(rtt 0 $array) => (rtt 0 (array (mut externref))) + +;; Test canonicalization +;; Test canonical signatures +;; Test recursive types +(func (result (ref null ...1))) + +(func (result (ref null (func (result (ref null ...3)))))) +(func (result (ref null (func (result (ref null ...3)))))) + +(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9))))))))))))))) +(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9))))))))))))))) +(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9))))))))))))))) +(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9))))))))))))))) +(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9))))))))))))))) + +(func (result (ref null ...1) (ref null (func)))) +(func (result (ref null ...1) (ref null (func)))) +(func) +(func) +(func (result (ref null (func (result ...1 (ref null (func))))))) +(func (result (ref null (func (result ...1 (ref null (func))))))) + +(func (result (ref null ...1))) +(func (result (ref null (func (result ...1))))) + +;; Test TypeBuilder +Before setting heap types: +(ref $sig) => (ref [T](func)) +(ref $struct) => (ref [T](func)) +(ref $array) => (ref [T](func)) +(ref null $array) => (ref null [T](func)) +(rtt 0 $array) => (rtt 0 [T](func)) + +After setting heap types: +(ref $sig) => (ref [T](func (param (ref [T](struct (field (ref null [T](array (mut externref))) (mut (rtt 0 [T](array (mut externref)))))))) (result (ref [T](array (mut externref))) i32))) +(ref $struct) => (ref [T](struct (field (ref null [T](array (mut externref))) (mut (rtt 0 [T](array (mut externref))))))) +(ref $array) => (ref [T](array (mut externref))) +(ref null $array) => (ref null [T](array (mut externref))) +(rtt 0 $array) => (rtt 0 [T](array (mut externref))) + +After building types: +(ref $sig) => (ref (func (param (ref (struct (field (ref null (array (mut externref))) (mut (rtt 0 (array (mut externref)))))))) (result (ref (array (mut externref))) i32))) +(ref $struct) => (ref (struct (field (ref null (array (mut externref))) (mut (rtt 0 (array (mut externref))))))) +(ref $array) => (ref (array (mut externref))) +(ref null $array) => (ref null (array (mut externref))) +(rtt 0 $array) => (rtt 0 (array (mut externref))) + +;; Test canonicalization +;; Test canonical signatures +;; Test recursive types +(func (result (ref null ...1))) + +(func (result (ref null (func (result (ref null ...3)))))) +(func (result (ref null (func (result (ref null ...3)))))) + +(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9))))))))))))))) +(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9))))))))))))))) +(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9))))))))))))))) +(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9))))))))))))))) +(func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null (func (result (ref null ...9))))))))))))))) + +(func (result (ref null ...1) (ref null (func)))) +(func (result (ref null ...1) (ref null (func)))) +(func) +(func) +(func (result (ref null (func (result ...1 (ref null (func))))))) +(func (result (ref null (func (result ...1 (ref null (func))))))) + +(func (result (ref null ...1))) +(func (result (ref null (func (result ...1))))) + |