Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 45 additions & 38 deletions src/passes/Unsubtyping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
#include "support/insert_ordered.h"
#endif

#if UNSUBTYPING_DEBUG
#define DBG(x) x
#else
#define DBG(x)
#endif

// Compute and use the minimal subtype relation required to maintain module
// validity and behavior. This minimal relation will be a subset of the original
// subtype relation. Start by walking the IR and collecting pairs of types that
Expand Down Expand Up @@ -122,6 +128,16 @@ template<typename T> using Set = InsertOrderedSet<T>;
template<typename K, typename V> using Map = std::unordered_map<K, V>;
template<typename T> using Set = std::unordered_set<T>;
#endif

#if UNSUBTYPING_DEBUG
Name getTypeName(Module& wasm, HeapType type) {
if (auto it = wasm.typeNames.find(type); it != wasm.typeNames.end()) {
return it->second.name;
}
return Name("(unnamed)");
}
#endif

// A tree (or rather a forest) of types with the ability to query and set
// supertypes in constant time and efficiently iterate over supertypes and
// subtypes.
Expand Down Expand Up @@ -272,6 +288,22 @@ struct TypeTree {

Subtypes subtypes(HeapType type) { return {this, getIndex(type)}; }

#if UNSUBTYPING_DEBUG
void dump(Module& wasm) {
for (auto& node : nodes) {
std::cerr << getTypeName(wasm, node.type);
if (auto super = getSupertype(node.type)) {
std::cerr << " <: " << getTypeName(wasm, *super);
}
std::cerr << ", children:";
for (auto child : node.children) {
std::cerr << " " << getTypeName(wasm, nodes[child].type);
}
std::cerr << '\n';
}
}
#endif

private:
Index getIndex(HeapType type) {
auto [it, inserted] = indices.insert({type, nodes.size()});
Expand All @@ -293,7 +325,10 @@ struct Unsubtyping : Pass {
// Map from cast source types to their destinations.
Map<HeapType, std::vector<HeapType>> casts;

DBG(Module* wasm = nullptr);

void run(Module* wasm) override {
DBG(this->wasm = wasm);
if (!wasm->features.hasGC()) {
return;
}
Expand All @@ -310,6 +345,7 @@ struct Unsubtyping : Pass {
process(sub, super);
}

DBG(types.dump(*wasm));
rewriteTypes(*wasm);

// Cast types may be refinable if their source and target types are no
Expand All @@ -324,6 +360,8 @@ struct Unsubtyping : Pass {
if (sub == super || sub.isBottom()) {
return;
}
DBG(std::cerr << "noting " << getTypeName(*wasm, sub)
<< " <: " << getTypeName(*wasm, super) << '\n');
work.push_back({sub, super});
}

Expand Down Expand Up @@ -482,6 +520,8 @@ struct Unsubtyping : Pass {
}

void process(HeapType sub, HeapType super) {
DBG(std::cerr << "processing " << getTypeName(*wasm, sub)
<< " <: " << getTypeName(*wasm, super) << '\n');
assert(HeapType::isSubType(sub, super));
auto oldSuper = types.getSupertype(sub);
if (oldSuper) {
Expand All @@ -494,7 +534,6 @@ struct Unsubtyping : Pass {
}
if (HeapType::isSubType(*oldSuper, super)) {
// sub <: oldSuper <: super
processDescribed(sub, *oldSuper, super);
noteSubtype(*oldSuper, super);
// We already handled sub <: oldSuper, so we're done.
return;
Expand All @@ -504,7 +543,6 @@ struct Unsubtyping : Pass {
// super will already be in the same tree when we process them below, so
// when we process casts we will know that we only need to process up to
// oldSuper.
processDescribed(sub, super, *oldSuper);
process(super, *oldSuper);
}

Expand All @@ -516,42 +554,6 @@ struct Unsubtyping : Pass {
processCasts(sub, super, oldSuper);
}

void processDescribed(HeapType sub, HeapType mid, HeapType super) {
// We are establishing sub <: mid <: super. If super describes the immediate
// supertype of the type sub describes, then once we insert mid between them
// we would have this:
//
// A -> super
// ^ ^
// | mid
// | ^
// C -> sub
//
// This violates the requirement that the descriptor of C's immediate
// supertype must be the immediate supertype of C's descriptor. To fix it,
// we have to find the type B that mid describes and insert it between A and
// C:
//
// A -> super
// ^ ^
// B -> mid
// ^ ^
// C -> sub
//
// We do this eagerly before we establish sub <: mid <: super so that if
// establishing that subtyping requires recursively establishing other
// subtypings, we can depend on the invariant that the described types are
// always set up correctly beforehand.
auto subDescribed = sub.getDescribedType();
auto superDescribed = super.getDescribedType();
if (subDescribed && superDescribed &&
types.getSupertype(*subDescribed) == superDescribed) {
auto midDescribed = mid.getDescribedType();
assert(midDescribed);
process(*subDescribed, *midDescribed);
}
}

void processDefinitions(HeapType sub, HeapType super) {
if (super.isBasic()) {
return;
Expand Down Expand Up @@ -587,6 +589,11 @@ struct Unsubtyping : Pass {
noteSubtype(*desc, *superDesc);
}
}
if (auto desc = sub.getDescribedType()) {
if (auto superDesc = super.getDescribedType()) {
noteSubtype(*desc, *superDesc);
}
}
}

void
Expand Down
78 changes: 75 additions & 3 deletions test/lit/passes/unsubtyping-desc.wast
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,13 @@
(type $A (sub (descriptor $A.desc (struct))))
;; CHECK: (type $A.desc (sub (describes $A (struct))))
(type $A.desc (sub (describes $A (struct))))
;; CHECK: (type $B (sub (descriptor $B.desc (struct))))
;; CHECK: (type $B (sub $A (descriptor $B.desc (struct))))
(type $B (sub $A (descriptor $B.desc (struct))))
;; CHECK: (type $B.desc (sub $A.desc (describes $B (struct))))
(type $B.desc (sub $A.desc (describes $B (struct))))
)

;; Now we directly require B.desc <: A.desc. This does *not* imply B <: A, so
;; we can optimize $B (but not $B.desc).
;; Now we require B.desc <: A.desc, which similarly implies B <: A.
;; CHECK: (global $B.desc (ref null $B.desc) (ref.null none))
(global $B.desc (ref null $B.desc) (ref.null none))
;; CHECK: (global $A.desc (ref null $A.desc) (global.get $B.desc))
Expand Down Expand Up @@ -134,3 +133,76 @@
;; CHECK: (global $bot-mid-desc (ref null $mid.desc) (struct.new_default $bot.desc))
(global $bot-mid-desc (ref null $mid.desc) (struct.new $bot.desc))
)

(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $top (sub (descriptor $top.desc (struct))))
(type $top (sub (descriptor $top.desc (struct))))
;; CHECK: (type $mid (sub $top (descriptor $mid.desc (struct))))
(type $mid (sub $top (descriptor $mid.desc (struct))))
;; CHECK: (type $bot (sub $mid (descriptor $bot.desc (struct))))
(type $bot (sub $mid (descriptor $bot.desc (struct))))
;; CHECK: (type $top.desc (sub (describes $top (struct))))
(type $top.desc (sub (describes $top (struct))))
;; CHECK: (type $mid.desc (sub $top.desc (describes $mid (struct))))
(type $mid.desc (sub $top.desc (describes $mid (struct))))
;; CHECK: (type $bot.desc (sub $mid.desc (describes $bot (struct))))
(type $bot.desc (sub $mid.desc (describes $bot (struct))))
)

;; Now go the other direction:
;;
;; top ---> top.desc
;; ^
;; mid -> mid.desc |(2)
;; ^ (1) |
;; bot ---> bot.desc
;;
;; bot.desc <: top.desc implies bot <: top, but we already have bot <: mid, so
;; that gives us bot <: mid <: top. This is only valid if we also have
;; bot.desc <: mid.desc <: top.desc.

;; CHECK: (global $bot-mid (ref null $mid) (struct.new_default $bot
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: ))
(global $bot-mid (ref null $mid) (struct.new $bot (ref.null none)))
;; CHECK: (global $bot-top-desc (ref null $top.desc) (struct.new_default $bot.desc))
(global $bot-top-desc (ref null $top.desc) (struct.new $bot.desc))
)

(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $top (sub (descriptor $top.desc (struct))))
(type $top (sub (descriptor $top.desc (struct))))
;; CHECK: (type $mid (sub $top (descriptor $mid.desc (struct))))
(type $mid (sub $top (descriptor $mid.desc (struct))))
;; CHECK: (type $bot (sub $mid (descriptor $bot.desc (struct))))
(type $bot (sub $mid (descriptor $bot.desc (struct))))
;; CHECK: (type $top.desc (sub (describes $top (struct))))
(type $top.desc (sub (describes $top (struct))))
;; CHECK: (type $mid.desc (sub $top.desc (describes $mid (struct))))
(type $mid.desc (sub $top.desc (describes $mid (struct))))
;; CHECK: (type $bot.desc (sub $mid.desc (describes $bot (struct))))
(type $bot.desc (sub $mid.desc (describes $bot (struct))))
)

;; Same as above, but the order of the initial subtypings is reversed.
;;
;; top ---> top.desc
;; ^
;; mid -> mid.desc |(1)
;; ^ (2) |
;; bot ---> bot.desc
;;
;; bot.desc <: top.desc implies bot <: top. When we add bot <: mid, that gives
;; us bot <: mid <: top. This is only valid if we also have
;; bot.desc <: mid.desc <: top.desc.
;; CHECK: (global $bot-top-desc (ref null $top.desc) (struct.new_default $bot.desc))
(global $bot-top-desc (ref null $top.desc) (struct.new $bot.desc))
;; CHECK: (global $bot-mid (ref null $mid) (struct.new_default $bot
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: ))
(global $bot-mid (ref null $mid) (struct.new $bot (ref.null none)))
)
Loading