diff options
Diffstat (limited to 'src/wasm')
-rw-r--r-- | src/wasm/wasm-type.cpp | 258 | ||||
-rw-r--r-- | src/wasm/wasm.cpp | 27 |
2 files changed, 259 insertions, 26 deletions
diff --git a/src/wasm/wasm-type.cpp b/src/wasm/wasm-type.cpp index 2c516ed98..f60f1c3a7 100644 --- a/src/wasm/wasm-type.cpp +++ b/src/wasm/wasm-type.cpp @@ -139,13 +139,38 @@ struct SubTyper { bool isSubType(HeapType a, HeapType b); bool isSubType(const Tuple& a, const Tuple& b); bool isSubType(const Field& a, const Field& b); - bool isSubType(const HeapTypeInfo& a, const HeapTypeInfo& b); bool isSubType(const Signature& a, const Signature& b); bool isSubType(const Struct& a, const Struct& b); bool isSubType(const Array& a, const Array& b); bool isSubType(const Rtt& a, const Rtt& b); }; +// Helper for finding the equirecursive least upper bound of two types. +struct TypeBounder { + TypeBuilder builder; + // The indices in `builder` at which the LUB of each pair of HeapTypes are + // being constructed. + std::unordered_map<std::pair<HeapType, HeapType>, size_t> indices; + + bool hasLeastUpperBound(Type a, Type b); + Type getLeastUpperBound(Type a, Type b); + +private: + // Return true and set `out` to be the LUB iff a LUB was found. The HeapType + // and Struct overloads are exceptional because they are infallible; + // HeapType::any is an upper bound of all HeapTypes and the empty struct is an + // upper bound of all struct types. Note that these methods can return + // temporary types, so they should never be used directly. + bool lub(Type a, Type b, Type& out); + HeapType lub(HeapType a, HeapType b); + bool lub(const Tuple& a, const Tuple& b, Tuple& out); + bool lub(const Field& a, const Field& b, Field& out); + bool lub(const Signature& a, const Signature& b, Signature& out); + Struct lub(const Struct& a, const Struct& b); + bool lub(const Array& a, const Array& b, Array& out); + bool lub(const Rtt& a, const Rtt& b, Rtt& out); +}; + // Helper for printing types without infinitely recursing on recursive types. struct TypePrinter { size_t currDepth = 0; @@ -773,6 +798,14 @@ bool Type::isSubType(Type left, Type right) { return SubTyper().isSubType(left, right); } +bool Type::hasLeastUpperBound(Type a, Type b) { + return TypeBounder().hasLeastUpperBound(a, b); +} + +Type Type::getLeastUpperBound(Type a, Type b) { + return TypeBounder().getLeastUpperBound(a, b); +} + Type::Iterator Type::end() const { if (isTuple()) { return Iterator(this, getTypeInfo(*this)->tuple.types.size()); @@ -1101,8 +1134,8 @@ bool SubTyper::isSubType(HeapType a, HeapType b) { return true; } if (seen.count({a, b})) { - // We weren't able to disprove that a is a subtype of b since we last saw - // them, so the relation holds coinductively. + // We weren't able to disprove that a == b since we last saw them, so the + // relation holds coinductively. return true; } // Everything is a subtype of any. @@ -1111,7 +1144,7 @@ bool SubTyper::isSubType(HeapType a, HeapType b) { } // Various things are subtypes of eq. if (b == HeapType::eq) { - return a == HeapType::i31 || a.isArray() || a.isStruct(); + return a == HeapType::i31 || a.isData(); } // Some are also subtypes of data. if (b == HeapType::data) { @@ -1121,8 +1154,8 @@ bool SubTyper::isSubType(HeapType a, HeapType b) { if (b == HeapType::func) { return a.isSignature(); } - // As we recurse, we will coinductively assume that a is a subtype of b unless - // proven otherwise. + // As we recurse, we will coinductively assume that a == b unless proven + // otherwise. seen.insert({a, b}); if (a.isSignature() && b.isSignature()) { return isSubType(a.getSignature(), b.getSignature()); @@ -1188,6 +1221,219 @@ bool SubTyper::isSubType(const Rtt& a, const Rtt& b) { return a.heapType == b.heapType && a.hasDepth() && !b.hasDepth(); } +bool TypeBounder::hasLeastUpperBound(Type a, Type b) { + Type tempLUB; + return lub(a, b, tempLUB); +} + +Type TypeBounder::getLeastUpperBound(Type a, Type b) { + Type tempLUB; + if (!lub(a, b, tempLUB)) { + return Type::none; + } + // `tempLUB` is a temporary type owned by `builder`. Since TypeBuilder::build + // returns HeapTypes rather than Types, create a new HeapType definition meant + // only to get `tempLUB` canonicalized in a known location. The use of an + // Array is arbitrary; it might as well have been a Struct. + builder.grow(1); + builder[builder.size() - 1] = Array(Field(tempLUB, Mutable)); + return builder.build().back().getArray().element.type; +} + +bool TypeBounder::lub(Type a, Type b, Type& out) { + if (a == b) { + out = a; + return true; + } + if (a == Type::unreachable) { + out = b; + return true; + } + if (b == Type::unreachable) { + out = a; + return true; + } + if (a.isTuple() && b.isTuple()) { + Tuple tuple; + if (!lub(a.getTuple(), b.getTuple(), tuple)) { + return false; + } + out = builder.getTempTupleType(tuple); + return true; + } else if (a.isRef() && b.isRef()) { + auto nullability = + (a.isNullable() || b.isNullable()) ? Nullable : NonNullable; + HeapType heapType = lub(a.getHeapType(), b.getHeapType()); + out = builder.getTempRefType(heapType, nullability); + return true; + } else if (a.isRtt() && b.isRtt()) { + Rtt rtt(HeapType::any); + if (!lub(a.getRtt(), b.getRtt(), rtt)) { + return false; + } + out = builder.getTempRttType(rtt); + return true; + } + return false; +} + +HeapType TypeBounder::lub(HeapType a, HeapType b) { + if (a == b) { + return a; + } + // Canonicalize to have the basic HeapType on the left. + if (b.isBasic()) { + std::swap(a, b); + } + if (a.isBasic()) { + switch (a.getBasic()) { + case HeapType::func: + if (b.isFunction()) { + return HeapType::func; + } else { + return HeapType::any; + } + case HeapType::ext: + return HeapType::any; + case HeapType::any: + return HeapType::any; + case HeapType::eq: + if (b == HeapType::i31 || b.isData()) { + return HeapType::eq; + } else { + return HeapType::any; + } + case HeapType::i31: + if (b.isData()) { + return HeapType::eq; + } else { + return HeapType::any; + } + case HeapType::data: + if (b.isData()) { + return HeapType::data; + } else if (b == HeapType::i31) { + return HeapType::eq; + } else { + return HeapType::any; + } + } + } + // Allocate a new slot to construct the LUB of this pair if we have not + // already seen it before. Canonicalize the pair to have the element with the + // smaller ID first since order does not matter. + auto pair = std::make_pair(std::min(a, b), std::max(a, b)); + size_t index = builder.size(); + auto result = indices.insert({pair, index}); + if (!result.second) { + // We've seen this pair before; stop recursing and do not allocate. + return builder[result.first->second]; + } + + builder.grow(1); + if (a.isSignature() && b.isSignature()) { + Signature sig; + if (lub(a.getSignature(), b.getSignature(), sig)) { + return builder[index] = sig; + } else { + return builder[index] = HeapType::func; + } + } else if (a.isStruct() && b.isStruct()) { + return builder[index] = lub(a.getStruct(), b.getStruct()); + } else if (a.isArray() && b.isArray()) { + Array array; + if (lub(a.getArray(), b.getArray(), array)) { + return builder[index] = array; + } else { + return builder[index] = HeapType::data; + } + } else { + // The types are not of the same kind, so the LUB is either `data` or `any`. + if (a.isSignature() || b.isSignature()) { + return builder[index] = HeapType::any; + } else { + return builder[index] = HeapType::data; + } + } +} + +bool TypeBounder::lub(const Tuple& a, const Tuple& b, Tuple& out) { + if (a.types.size() != b.types.size()) { + return false; + } + out.types.resize(a.types.size()); + for (size_t i = 0; i < a.types.size(); ++i) { + if (!lub(a.types[i], b.types[i], out.types[i])) { + return false; + } + } + return true; +} + +bool TypeBounder::lub(const Field& a, const Field& b, Field& out) { + if (a == b) { + out = a; + return true; + } + // Mutable fields are invariant, so they would have had to be the same. + if (a.mutable_ == Mutable || b.mutable_ == Mutable) { + return false; + } + // Packed types must match. + if (a.isPacked() != b.isPacked() || + (a.isPacked() && a.packedType != b.packedType)) { + return false; + } + // Either the packed types match or the types aren't packed. + Type type; + if (lub(a.type, b.type, type)) { + out = a; + out.type = type; + return true; + } else { + return false; + } +} + +bool TypeBounder::lub(const Signature& a, const Signature& b, Signature& out) { + // TODO: Implement proper signature subtyping, covariant in results and + // contravariant in params, once V8 implements it. + if (a != b) { + return false; + } else { + out = a; + return true; + } +} + +Struct TypeBounder::lub(const Struct& a, const Struct& b) { + Struct out; + size_t numFields = std::min(a.fields.size(), b.fields.size()); + for (size_t i = 0; i < numFields; ++i) { + Field field; + if (lub(a.fields[i], b.fields[i], field)) { + out.fields.push_back(field); + } else { + // Stop at the common prefix and ignore the rest. + break; + } + } + return out; +} + +bool TypeBounder::lub(const Array& a, const Array& b, Array& out) { + return lub(a.element, b.element, out.element); +} + +bool TypeBounder::lub(const Rtt& a, const Rtt& b, Rtt& out) { + if (a.heapType != b.heapType) { + return false; + } + uint32_t depth = (a.depth == b.depth) ? a.depth : Rtt::NoDepth; + out = Rtt(depth, a.heapType); + return true; +} + template<typename T, typename F> std::ostream& TypePrinter::printChild(T curr, F printer) { auto it = depths.find(curr.getID()); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 2dba93a4a..18f8594b1 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -180,9 +180,9 @@ void Block::finalize() { type = Type::none; return; } - type = list.back()->type; // The default type is what is at the end. Next we need to see if breaks and/ // or unreachabitily change that. + type = list.back()->type; if (!name.is()) { // Nothing branches here, so this is easy. handleUnreachable(this, NoBreak); @@ -199,7 +199,7 @@ void Block::finalize() { // is already correct. TODO: calculate proper LUBs to compute a new correct // type in this situation. seeker.types.insert(type); - Type::ensureSuperType(seeker.types, type); + type = Type::getLeastUpperBound(seeker.types); } else { // There are no branches, so this block may be unreachable. handleUnreachable(this, NoBreak); @@ -230,13 +230,8 @@ void If::finalize(Type type_) { } void If::finalize() { - if (ifFalse) { - // TODO: Calculate a proper LUB. - Type::ensureSuperType(std::array<Type, 2>{{ifTrue->type, ifFalse->type}}, - type); - } else { - type = Type::none; - } + type = ifFalse ? Type::getLeastUpperBound(ifTrue->type, ifFalse->type) + : Type::none; // if the arms return a value, leave it even if the condition // is unreachable, we still mark ourselves as having that type, e.g. // (if (result i32) @@ -245,9 +240,7 @@ void If::finalize() { // (i32.const 20) // ) // otherwise, if the condition is unreachable, so is the if - if ((type == Type::none && condition->type == Type::unreachable) || - (ifTrue->type == Type::unreachable && ifFalse && - ifFalse->type == Type::unreachable)) { + if (type == Type::none && condition->type == Type::unreachable) { type = Type::unreachable; } } @@ -785,9 +778,7 @@ void Select::finalize() { condition->type == Type::unreachable) { type = Type::unreachable; } else { - // TODO: Calculate a proper LUB. - Type::ensureSuperType(std::array<Type, 2>{{ifTrue->type, ifFalse->type}}, - type); + type = Type::getLeastUpperBound(ifTrue->type, ifFalse->type); } } @@ -847,11 +838,7 @@ void Try::finalize() { for (auto catchBody : catchBodies) { types.insert(catchBody->type); } - if (types.size() == 1 && *types.begin() == Type::unreachable) { - type = Type::unreachable; - } else { - Type::ensureSuperType(types, type); - } + type = Type::getLeastUpperBound(types); } void Try::finalize(Type type_) { |