diff --git a/src/passes/Unsubtyping.cpp b/src/passes/Unsubtyping.cpp index e44b50fb7c0..cc9b51f5c74 100644 --- a/src/passes/Unsubtyping.cpp +++ b/src/passes/Unsubtyping.cpp @@ -420,8 +420,8 @@ struct TypeTree { for (auto child : node.children) { std::cerr << " " << ModuleHeapType(wasm, nodes[child].type); } - if (node.exposedToJS) { - std::cerr << ", exposed to JS"; + if (node.subtypesExposedToJS) { + std::cerr << ", subtypes exposed to JS"; } std::cerr << '\n'; } @@ -953,8 +953,22 @@ struct Unsubtyping : Pass, Noter { types.setSupertype(sub, super); // If the supertype is exposed to JS, the subtype potentially is as well. + // `sub` may be the root of some existing subtype tree, and we have to + // propagate the exposure to JS to all those existing subtypes. We could + // just iterate over subtypes(), but manually traverse using + // immediateSubtypes() so we can avoid visiting subtrees that have already + // been marked. if (types.areSubtypesExposedToJS(super)) { - noteExposedToJS(sub); + std::vector work{{sub}}; + while (!work.empty()) { + auto curr = work.back(); + work.pop_back(); + if (!types.areSubtypesExposedToJS(curr)) { + noteExposedToJS(curr); + auto subtypes = types.immediateSubtypes(curr); + work.insert(work.end(), subtypes.begin(), subtypes.end()); + } + } } // Complete the descriptor squares to the left and right of the new diff --git a/test/lit/passes/unsubtyping-jsinterop.wast b/test/lit/passes/unsubtyping-jsinterop.wast index 9555295510f..11b678cb520 100644 --- a/test/lit/passes/unsubtyping-jsinterop.wast +++ b/test/lit/passes/unsubtyping-jsinterop.wast @@ -1027,3 +1027,53 @@ (local.get $sub-out) ) ) + +(module + ;; Regression test for a bug where we were not fully propagating exposure to + ;; JS, resulting missing prototype-configuring descriptors depending on the + ;; order in which subtypes were processed. In this example, if $struct <: + ;; $super was processed before $super <: any, then exposure to JS would not be + ;; propagated down to $struct, so its descriptor would be removed. + (rec + ;; CHECK: (rec + ;; CHECK-NEXT: (type $super (sub (struct))) + (type $super (sub (struct))) + ;; CHECK: (type $struct (sub $super (descriptor $desc) (struct))) + (type $struct (sub $super (descriptor $desc) (struct))) + ;; CHECK: (type $desc (describes $struct) (struct (field externref))) + (type $desc (describes $struct) (struct (field externref))) + ) + + ;; CHECK: (type $3 (func (result anyref))) + + ;; CHECK: (global $any (mut anyref) (ref.null none)) + (global $any (mut anyref) (ref.null none)) + ;; CHECK: (global $super (mut (ref null $super)) (ref.null none)) + (global $super (mut (ref null $super)) (ref.null none)) + ;; CHECK: (global $struct (ref null $struct) (ref.null none)) + (global $struct (ref null $struct) (ref.null none)) + + ;; any exposed to JS. + ;; CHECK: (@binaryen.js.called) + ;; CHECK-NEXT: (func $expose-anyref (type $3) (result anyref) + ;; CHECK-NEXT: (global.set $any + ;; CHECK-NEXT: (global.get $super) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $super + ;; CHECK-NEXT: (global.get $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (@binaryen.js.called) + (func $expose-anyref (result anyref) + ;; $super <: aby + (global.set $any + (global.get $super) + ) + ;; $struct <: $super + (global.set $super + (global.get $struct) + ) + (unreachable) + ) +)