Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
337 changes: 259 additions & 78 deletions src/ir/module-utils.cpp

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/ir/module-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,8 @@ std::vector<HeapType> collectHeapTypes(Module& wasm);
// Get the types directly made public by imported or exported module items. For
// example, the types of imported or exported globals or functions, but not
// other types reachable from those types. Includes abstract heap types.
std::vector<HeapType> getExposedPublicHeapTypes(Module& wasm);
std::vector<std::pair<HeapType, Exactness>>
getExposedPublicHeapTypes(Module& wasm);

// Collect all the defined heap types visible on the module boundary that cannot
// be changed, e.g. the defined types from getExposedPublicHeapTypes and those
Expand Down
3 changes: 0 additions & 3 deletions src/passes/Unsubtyping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -567,9 +567,6 @@ struct Unsubtyping : Pass, Noter<Unsubtyping> {
if (!wasm->features.hasGC()) {
return;
}
if (getPassOptions().worldMode == WorldMode::Open) {
Fatal() << "Unsubtyping requires --closed-world";
}

// Initialize the subtype relation based on what is immediately required to
// keep the code and public types valid.
Expand Down
2 changes: 1 addition & 1 deletion src/wasm/wasm-validator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ void validateExactReferences(Module& module, ValidationInfo& info) {
return;
}

for (auto type : ModuleUtils::getExposedPublicHeapTypes(module)) {
for (auto& [type, _] : ModuleUtils::getExposedPublicHeapTypes(module)) {
for (auto child : type.getTypeChildren()) {
if (child.isExact()) {
std::string typeName;
Expand Down
71 changes: 71 additions & 0 deletions test/lit/passes/dae-minimize-rec-groups-no-cd.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
;; RUN: wasm-opt %s -all --disable-custom-descriptors --dae --minimize-rec-groups \
;; RUN: -S -o - | filecheck %s

(module
;; CHECK: (type $struct (struct))
(type $struct (struct))

;; This type is public because `func` is exposed and there is a referenced
;; function of this type. DAE will optimized the unreferenced function of this
;; type to take an exact reference, but that optimized type will _not_ be
;; public. This is important because otherwise MinimizeRecGroups would fail an
;; assertion that all public types are distinguishable given the enabled
;; features. This assertion would fail because the original and optimized
;; types differ only in the exactness of a reference, but custom descriptors
;; is disabled.
;; CHECK: (type $1 (func))

;; CHECK: (type $f (func (param (ref null $struct))))
(type $f (func (param (ref null $struct))))

;; CHECK: (rec
;; CHECK-NEXT: (type $3 (struct))

;; CHECK: (type $4 (func (param (ref null (exact $struct)))))

;; CHECK: (global $g (mut anyref) (ref.null none))
(global $g (mut anyref) (ref.null none))
;; CHECK: (global $func funcref (ref.null nofunc))
(global $func funcref (ref.null nofunc))
;; CHECK: (elem declare func $referenced)

;; CHECK: (export "func" (global $func))
(export "func" (global $func))

;; CHECK: (func $caller (type $1)
;; CHECK-NEXT: (call $unreferenced
;; CHECK-NEXT: (struct.new_default $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $unreferenced
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.func $referenced)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller
;; $unreferenced will be optimized to take (ref null (exact $struct))
(call $unreferenced (struct.new $struct))
(call $unreferenced (ref.null none))
;; $referenced will not be optimized.
(drop (ref.func $referenced))
)

;; CHECK: (func $unreferenced (type $4) (param $struct (ref null (exact $struct)))
;; CHECK-NEXT: (global.set $g
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $unreferenced (type $f) (param $struct (ref null $struct))
;; Use the parameter to keep it alive.
(global.set $g (local.get $struct))
)

;; CHECK: (func $referenced (type $f) (param $0 (ref null $struct))
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $referenced (type $f) (param $0 (ref null $struct))
(nop)
)
)
138 changes: 114 additions & 24 deletions test/lit/passes/gufa-tnh.wast
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,6 @@

;; CHECK: (type $4 (func (param anyref)))

;; CHECK: (export "out" (func $caller))

;; CHECK: (func $maker (type $2)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $A
Expand Down Expand Up @@ -213,7 +211,8 @@
)
)

;; CHECK: (func $caller (type $4) (param $any anyref)
;; CHECK: (@binaryen.js.called)
;; CHECK-NEXT: (func $caller (type $4) (param $any anyref)
;; CHECK-NEXT: (local $x (ref null $A))
;; CHECK-NEXT: (call $called
;; CHECK-NEXT: (local.tee $x
Expand Down Expand Up @@ -241,7 +240,8 @@
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller (export "out") (param $any anyref)
(@binaryen.js.called)
(func $caller (param $any anyref)
(local $x (ref null $A))
;; The called function casts to $B. This lets us infer the value of the
;; fallthrough ref.cast, which will turn into $B. Furthermore, that then
Expand Down Expand Up @@ -296,8 +296,6 @@

;; CHECK: (type $3 (func))

;; CHECK: (export "out" (func $caller))

;; CHECK: (func $maker (type $3)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $A
Expand Down Expand Up @@ -340,7 +338,8 @@
)
)

;; CHECK: (func $caller (type $2) (param $a (ref null $A))
;; CHECK: (@binaryen.js.called)
;; CHECK-NEXT: (func $caller (type $2) (param $a (ref null $A))
;; CHECK-NEXT: (local $x (ref null $A))
;; CHECK-NEXT: (call $called
;; CHECK-NEXT: (local.tee $x
Expand All @@ -351,7 +350,8 @@
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller (export "out") (param $a (ref null $A))
(@binaryen.js.called)
(func $caller (param $a (ref null $A))
(local $x (ref null $A))
;; The change compared to before is that we only have a local.tee here, and
;; no ref.cast. We can still infer the type of the tee's value, and
Expand All @@ -370,6 +370,96 @@
)
)

;; The same module as above, but now $caller is exported instead of marked
;; @binaryen.js.called. This makes $A public, and since we are in open-world
;; mode, that in turn makes its subtype $B public, so we cannot infer anything
;; about either type.
(module
;; CHECK: (type $A (sub (struct (field (mut i32)))))
(type $A (sub (struct (field (mut i32)))))

;; CHECK: (type $B (sub $A (struct (field (mut i32)))))
(type $B (sub $A (struct (field (mut i32)))))

;; CHECK: (type $2 (func (param (ref null $A))))

;; CHECK: (type $3 (func))

;; CHECK: (export "out" (func $caller))

;; CHECK: (func $maker (type $3)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $A
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $B
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $maker
;; A always contains 10, and B always contains 20, except that they are both
;; public, so they can come in from the outside with any field value.
(drop
(struct.new $A
(i32.const 10)
)
)
(drop
(struct.new $B
(i32.const 20)
)
)
)

;; CHECK: (func $called (type $2) (param $x (ref null $A))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $B)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $called (param $x (ref null $A))
;; Cast the input to a $B, which will help the caller.
(drop
(ref.cast (ref $B)
(local.get $x)
)
)
)

;; CHECK: (func $caller (type $2) (param $a (ref null $A))
;; CHECK-NEXT: (local $x (ref null $A))
;; CHECK-NEXT: (call $called
;; CHECK-NEXT: (local.tee $x
;; CHECK-NEXT: (local.get $a)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $A 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller (export "out") (param $a (ref null $A))
(local $x (ref null $A))
;; Despite inferring that the argument must be a $B, we still cannot
;; optimize.
(call $called
(local.tee $x
(local.get $a)
)
)
(drop
(struct.get $A 0
(local.get $x)
)
)
)
)

;; As above, but add a local.tee etc. in the called function.
(module
;; CHECK: (type $A (sub (struct (field (mut i32)))))
Expand Down Expand Up @@ -1146,12 +1236,6 @@

;; CHECK: (type $5 (func))

;; CHECK: (export "caller-C" (func $caller-C))

;; CHECK: (export "caller-B" (func $caller-B))

;; CHECK: (export "caller-A" (func $caller-A))

;; CHECK: (func $called (type $4) (param $x (ref null $A))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $B)
Expand Down Expand Up @@ -1204,7 +1288,8 @@
)
)

;; CHECK: (func $caller-C (type $3) (param $any anyref)
;; CHECK: (@binaryen.js.called)
;; CHECK-NEXT: (func $caller-C (type $3) (param $any anyref)
;; CHECK-NEXT: (local $temp-C (ref $C))
;; CHECK-NEXT: (local $temp-any anyref)
;; CHECK-NEXT: (call $called
Expand Down Expand Up @@ -1234,7 +1319,8 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller-C (export "caller-C") (param $any anyref)
(@binaryen.js.called)
(func $caller-C (param $any anyref)
(local $temp-C (ref $C))
(local $temp-any anyref)
(call $called
Expand Down Expand Up @@ -1276,7 +1362,8 @@
)
)

;; CHECK: (func $caller-B (type $3) (param $any anyref)
;; CHECK: (@binaryen.js.called)
;; CHECK-NEXT: (func $caller-B (type $3) (param $any anyref)
;; CHECK-NEXT: (local $temp (ref $A))
;; CHECK-NEXT: (call $called
;; CHECK-NEXT: (local.tee $temp
Expand All @@ -1291,7 +1378,8 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller-B (export "caller-B") (param $any anyref)
(@binaryen.js.called)
(func $caller-B (param $any anyref)
(local $temp (ref $A))
(call $called
(local.tee $temp
Expand All @@ -1307,7 +1395,8 @@
)
)

;; CHECK: (func $caller-A (type $3) (param $any anyref)
;; CHECK: (@binaryen.js.called)
;; CHECK-NEXT: (func $caller-A (type $3) (param $any anyref)
;; CHECK-NEXT: (local $temp (ref $A))
;; CHECK-NEXT: (call $called
;; CHECK-NEXT: (local.tee $temp
Expand All @@ -1322,7 +1411,8 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller-A (export "caller-A") (param $any anyref)
(@binaryen.js.called)
(func $caller-A (param $any anyref)
(local $temp (ref $A))
(call $called
(local.tee $temp
Expand Down Expand Up @@ -1731,8 +1821,6 @@
;; CHECK: (elem $e func)
(elem $e funcref)

;; CHECK: (export "out" (func $caller))

;; CHECK: (func $called (type $3) (param $struct.get (ref null $A)) (param $struct.set (ref null $A)) (param $array.get (ref null $B)) (param $array.set (ref null $B)) (param $array.len (ref null $B)) (param $array.copy.src (ref null $B)) (param $array.copy.dest (ref null $B)) (param $array.fill (ref null $B)) (param $array.init_data (ref null $B)) (param $array.init_elem (ref null $C)) (param $ref.test (ref null $A))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 0)
Expand Down Expand Up @@ -1859,7 +1947,8 @@
)
)

;; CHECK: (func $caller (type $4) (param $any anyref)
;; CHECK: (@binaryen.js.called)
;; CHECK-NEXT: (func $caller (type $4) (param $any anyref)
;; CHECK-NEXT: (call $called
;; CHECK-NEXT: (ref.cast (ref $A)
;; CHECK-NEXT: (local.get $any)
Expand Down Expand Up @@ -1896,7 +1985,8 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $caller (export "out") (param $any anyref)
(@binaryen.js.called)
(func $caller (param $any anyref)
;; All these casts will be refined to non-nullable, aside from the last
;; param which is but a ref.test.
(call $called
Expand Down
2 changes: 1 addition & 1 deletion test/lit/passes/gufa-vs-cfp.wast
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.

;; RUN: foreach %s %t wasm-opt --remove-unused-names --gufa -all -S -o - | filecheck %s
;; RUN: foreach %s %t wasm-opt --remove-unused-names --gufa --closed-world -all -S -o - | filecheck %s

;; (remove-unused-names is added to test fallthrough values without a block
;; name getting in the way)
Expand Down
2 changes: 1 addition & 1 deletion test/lit/passes/minimize-rec-groups.wast
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
;; RUN: foreach %s %t wasm-opt -all --minimize-rec-groups -S -o - | filecheck %s
;; RUN: foreach %s %t wasm-opt -all --closed-world --minimize-rec-groups -S -o - | filecheck %s

;; A module with no heap types at all should be ok.
(module
Expand Down
Loading
Loading