;; 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-module-elements --closed-world -all -S -o - | filecheck %s ;; RUN: foreach %s %t wasm-opt --remove-unused-module-elements -all -S -o - | filecheck %s --check-prefix OPEN_WORLD ;; Test both open world (default) and closed world. In a closed world we can do ;; more with function refs, as we assume nothing calls them on the outside, so ;; if no calls exist to the right type, the function is not reached. (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $A-super (sub (func))) ;; OPEN_WORLD: (rec ;; OPEN_WORLD-NEXT: (type $A-super (sub (func))) (type $A-super (sub (func))) ;; CHECK: (type $A (sub $A-super (func))) ;; OPEN_WORLD: (type $A (sub $A-super (func))) (type $A (sub $A-super (func))) ;; CHECK: (type $A-sub (sub $A (func))) ;; OPEN_WORLD: (type $A-sub (sub $A (func))) (type $A-sub (sub $A (func))) ;; CHECK: (type $B (func)) ;; OPEN_WORLD: (type $B (func)) (type $B (func)) ) ;; CHECK: (type $4 (func)) ;; CHECK: (elem declare func $target-A $target-A-sub $target-A-super $target-B) ;; CHECK: (export "foo" (func $foo)) ;; CHECK: (func $foo (type $4) ;; CHECK-NEXT: (local $A (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $target-A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $target-A-sub) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $target-A-super) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $target-B) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $A ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block ;; (replaces unreachable CallRef we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (type $4 (func)) ;; OPEN_WORLD: (elem declare func $target-A $target-A-sub $target-A-super $target-B) ;; OPEN_WORLD: (export "foo" (func $foo)) ;; OPEN_WORLD: (func $foo (type $4) ;; OPEN_WORLD-NEXT: (local $A (ref null $A)) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (ref.func $target-A) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (ref.func $target-A-sub) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (ref.func $target-A-super) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (ref.func $target-B) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (call_ref $A ;; OPEN_WORLD-NEXT: (local.get $A) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (block ;; (replaces unreachable CallRef we can't emit) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (unreachable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (unreachable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $foo (export "foo") (local $A (ref null $A)) ;; This export has some RefFuncs, and one CallRef. (drop (ref.func $target-A) ) (drop (ref.func $target-A-sub) ) (drop (ref.func $target-A-super) ) (drop (ref.func $target-B) ) (call_ref $A (local.get $A) ) ;; Verify that we do not crash on an unreachable call_ref, which has no ;; heap type for us to analyze. (call_ref $A (unreachable) ) ) ;; CHECK: (func $target-A (type $A) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A (type $A) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A (type $A) ;; This function is reachable from the export "foo": there is a RefFunc and ;; a CallRef for it there. ) (func $target-A-noref (type $A) ;; This function is not reachable. We have a CallRef of the right type, but ;; no RefFunc. ) ;; CHECK: (func $target-A-sub (type $A-sub) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A-sub (type $A-sub) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A-sub (type $A-sub) ;; This function is reachable because we have a CallRef of a supertype ($A). ) ;; CHECK: (func $target-A-super (type $A-super) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A-super (type $A-super) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A-super (type $A-super) ;; This function is not reachable. We have a CallRef of a subtype ($A), but ;; that is not enough. ) ;; CHECK: (func $target-B (type $B) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-B (type $B) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-B (type $B) ;; This function is not reachable. We have a RefFunc in "foo" but no ;; suitable CallRef. ;; ;; Note that we cannot remove the function, as the RefFunc must refer to ;; something in order to validate. But we can clear out the body of this ;; function with an unreachable. ;; ;; As mentioned above, in an open world we cannot optimize here, so the ;; function body will remain empty as a nop, and not turn into an ;; unreachable. ) ) ;; As above, but reverse the order inside $foo, so we see the CallRef first. (module ;; CHECK: (type $A (func)) ;; OPEN_WORLD: (type $A (func)) (type $A (func)) (type $B (func)) ;; CHECK: (elem declare func $target-A) ;; CHECK: (export "foo" (func $foo)) ;; CHECK: (func $foo (type $A) ;; CHECK-NEXT: (local $A (ref null $A)) ;; CHECK-NEXT: (call_ref $A ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $target-A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (elem declare func $target-A) ;; OPEN_WORLD: (export "foo" (func $foo)) ;; OPEN_WORLD: (func $foo (type $A) ;; OPEN_WORLD-NEXT: (local $A (ref null $A)) ;; OPEN_WORLD-NEXT: (call_ref $A ;; OPEN_WORLD-NEXT: (local.get $A) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (ref.func $target-A) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $foo (export "foo") (local $A (ref null $A)) (call_ref $A (local.get $A) ) (drop (ref.func $target-A) ) ) ;; CHECK: (func $target-A (type $A) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A (type $A) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A (type $A) ;; This function is reachable. ) (func $target-A-noref (type $A) ;; This function is not reachable. ) ) ;; As above, but interleave CallRefs with RefFuncs. (module ;; CHECK: (type $A (func)) ;; OPEN_WORLD: (type $A (func)) (type $A (func)) (type $B (func)) ;; CHECK: (elem declare func $target-A-1 $target-A-2) ;; CHECK: (export "foo" (func $foo)) ;; CHECK: (func $foo (type $A) ;; CHECK-NEXT: (local $A (ref null $A)) ;; CHECK-NEXT: (call_ref $A ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $target-A-1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $A ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $target-A-2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (elem declare func $target-A-1 $target-A-2) ;; OPEN_WORLD: (export "foo" (func $foo)) ;; OPEN_WORLD: (func $foo (type $A) ;; OPEN_WORLD-NEXT: (local $A (ref null $A)) ;; OPEN_WORLD-NEXT: (call_ref $A ;; OPEN_WORLD-NEXT: (local.get $A) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (ref.func $target-A-1) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (call_ref $A ;; OPEN_WORLD-NEXT: (local.get $A) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (ref.func $target-A-2) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $foo (export "foo") (local $A (ref null $A)) (call_ref $A (local.get $A) ) (drop (ref.func $target-A-1) ) (call_ref $A (local.get $A) ) (drop (ref.func $target-A-2) ) ) ;; WORLD_OPEN-NEXT: ) ;; CHECK: (func $target-A-1 (type $A) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A-1 (type $A) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A-1 (type $A) ;; This function is reachable. ) ;; CHECK: (func $target-A-2 (type $A) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A-2 (type $A) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A-2 (type $A) ;; This function is reachable. ) (func $target-A-3 (type $A) ;; This function is not reachable. ) ) ;; As above, with the order reversed inside $foo. The results should be the ;; same. (module ;; CHECK: (type $A (func)) ;; OPEN_WORLD: (type $A (func)) (type $A (func)) (type $B (func)) ;; CHECK: (elem declare func $target-A-1 $target-A-2) ;; CHECK: (export "foo" (func $foo)) ;; CHECK: (func $foo (type $A) ;; CHECK-NEXT: (local $A (ref null $A)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $target-A-1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $A ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $target-A-2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $A ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (elem declare func $target-A-1 $target-A-2) ;; OPEN_WORLD: (export "foo" (func $foo)) ;; OPEN_WORLD: (func $foo (type $A) ;; OPEN_WORLD-NEXT: (local $A (ref null $A)) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (ref.func $target-A-1) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (call_ref $A ;; OPEN_WORLD-NEXT: (local.get $A) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (ref.func $target-A-2) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (call_ref $A ;; OPEN_WORLD-NEXT: (local.get $A) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $foo (export "foo") (local $A (ref null $A)) (drop (ref.func $target-A-1) ) (call_ref $A (local.get $A) ) (drop (ref.func $target-A-2) ) (call_ref $A (local.get $A) ) ) ;; CHECK: (func $target-A-1 (type $A) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A-1 (type $A) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A-1 (type $A) ;; This function is reachable. ) ;; CHECK: (func $target-A-2 (type $A) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-A-2 (type $A) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-A-2 (type $A) ;; This function is reachable. ) (func $target-A-3 (type $A) ;; This function is not reachable. ) ) ;; call_indirect can reach things in the table, or that are written to the table ;; during runtime. (module ;; CHECK: (type $none_=>_none (func)) ;; OPEN_WORLD: (type $none_=>_none (func)) (type $none_=>_none (func)) ;; CHECK: (table $table 22 funcref) ;; OPEN_WORLD: (table $table 22 funcref) (table $table 22 funcref) ;; CHECK: (elem declare func $func) ;; CHECK: (export "run" (func $run)) ;; CHECK: (func $run (type $none_=>_none) ;; CHECK-NEXT: (table.set $table ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_indirect $table (type $none_=>_none) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (elem declare func $func) ;; OPEN_WORLD: (export "run" (func $run)) ;; OPEN_WORLD: (func $run (type $none_=>_none) ;; OPEN_WORLD-NEXT: (table.set $table ;; OPEN_WORLD-NEXT: (i32.const 0) ;; OPEN_WORLD-NEXT: (ref.func $func) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (call_indirect $table (type $none_=>_none) ;; OPEN_WORLD-NEXT: (i32.const 0) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $run (export "run") ;; Set something in the table, and call it. (table.set $table (i32.const 0) (ref.func $func) ) (call_indirect $table (type $none_=>_none) (i32.const 0) ) ) ;; CHECK: (func $func (type $none_=>_none) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $func (type $none_=>_none) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $func (type $none_=>_none) ;; This function will be called indirectly from |run|, so it is reachable ;; and this should not be turned into |unreachable|. (nop) ) ) ;; The call.without.effects intrinsic does a call to the reference given to it, ;; but for now other imports do not (until we add a flag for closed-world). (module ;; CHECK: (type $A (func)) ;; OPEN_WORLD: (type $A (func)) (type $A (func)) (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (param funcref))) (import "other" "import" (func $other-import (param funcref))) ;; CHECK: (type $1 (func (param funcref))) ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (type $1) (param funcref))) ;; CHECK: (import "other" "import" (func $other-import (type $1) (param funcref))) ;; CHECK: (elem declare func $target-drop $target-keep) ;; CHECK: (export "foo" (func $foo)) ;; CHECK: (func $foo (type $A) ;; CHECK-NEXT: (call $call-without-effects ;; CHECK-NEXT: (ref.func $target-keep) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $other-import ;; CHECK-NEXT: (ref.func $target-drop) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (type $1 (func (param funcref))) ;; OPEN_WORLD: (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (type $1) (param funcref))) ;; OPEN_WORLD: (import "other" "import" (func $other-import (type $1) (param funcref))) ;; OPEN_WORLD: (elem declare func $target-drop $target-keep) ;; OPEN_WORLD: (export "foo" (func $foo)) ;; OPEN_WORLD: (func $foo (type $A) ;; OPEN_WORLD-NEXT: (call $call-without-effects ;; OPEN_WORLD-NEXT: (ref.func $target-keep) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (call $other-import ;; OPEN_WORLD-NEXT: (ref.func $target-drop) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $foo (export "foo") ;; Calling the intrinsic with a reference is considered a call of the ;; reference, so we will not remove $target-keep. (call $call-without-effects (ref.func $target-keep) ) ;; The other import is not enough to keep $target-drop alive. (call $other-import (ref.func $target-drop) ) ) ;; CHECK: (func $target-keep (type $A) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-keep (type $A) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-keep (type $A) ) ;; CHECK: (func $target-drop (type $A) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-drop (type $A) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-drop (type $A) ;; In a closed world we can turn this body into unreachable. ) ) ;; As above, but now the call to the intrinsic does not let us see the exact ;; function being called. (module ;; CHECK: (type $A (func)) ;; OPEN_WORLD: (type $A (func)) (type $A (func)) (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (param funcref))) (import "other" "import" (func $other-import (param funcref))) ;; CHECK: (type $1 (func (param funcref))) ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (type $1) (param funcref))) ;; CHECK: (import "other" "import" (func $other-import (type $1) (param funcref))) ;; CHECK: (elem declare func $target-keep $target-keep-2) ;; CHECK: (export "foo" (func $foo)) ;; CHECK: (func $foo (type $A) ;; CHECK-NEXT: (local $A (ref null $A)) ;; CHECK-NEXT: (call $call-without-effects ;; CHECK-NEXT: (local.get $A) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (ref.func $target-keep) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call $other-import ;; CHECK-NEXT: (ref.func $target-keep-2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (type $1 (func (param funcref))) ;; OPEN_WORLD: (import "binaryen-intrinsics" "call.without.effects" (func $call-without-effects (type $1) (param funcref))) ;; OPEN_WORLD: (import "other" "import" (func $other-import (type $1) (param funcref))) ;; OPEN_WORLD: (elem declare func $target-keep $target-keep-2) ;; OPEN_WORLD: (export "foo" (func $foo)) ;; OPEN_WORLD: (func $foo (type $A) ;; OPEN_WORLD-NEXT: (local $A (ref null $A)) ;; OPEN_WORLD-NEXT: (call $call-without-effects ;; OPEN_WORLD-NEXT: (local.get $A) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (ref.func $target-keep) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (call $other-import ;; OPEN_WORLD-NEXT: (ref.func $target-keep-2) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $foo (export "foo") (local $A (ref null $A)) ;; Call the intrinsic without a RefFunc. All we infer here is the type, ;; which means we must assume anything with type $A (and a reference) can be ;; called, which will keep alive both $target-keep and $target-keep-2 (call $call-without-effects (local.get $A) ) (drop (ref.func $target-keep) ) (call $other-import (ref.func $target-keep-2) ) ) ;; CHECK: (func $target-keep (type $A) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-keep (type $A) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-keep (type $A) ) ;; CHECK: (func $target-keep-2 (type $A) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $target-keep-2 (type $A) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $target-keep-2 (type $A) ) ) ;; Test reachability of struct fields in globals. Only fields that have actual ;; reads need to be processed. (module ;; CHECK: (type $void (func)) ;; OPEN_WORLD: (type $void (func)) (type $void (func)) ;; CHECK: (type $vtable (sub (struct (field (ref $void)) (field (ref $void))))) ;; OPEN_WORLD: (type $vtable (sub (struct (field (ref $void)) (field (ref $void))))) (type $vtable (sub (struct (field (ref $void)) (field (ref $void))))) ;; CHECK: (global $vtable (ref $vtable) (struct.new $vtable ;; CHECK-NEXT: (ref.func $a) ;; CHECK-NEXT: (ref.func $b) ;; CHECK-NEXT: )) ;; OPEN_WORLD: (global $vtable (ref $vtable) (struct.new $vtable ;; OPEN_WORLD-NEXT: (ref.func $a) ;; OPEN_WORLD-NEXT: (ref.func $b) ;; OPEN_WORLD-NEXT: )) (global $vtable (ref $vtable) (struct.new $vtable (ref.func $a) (ref.func $b) )) (global $vtable-2 (ref $vtable) (struct.new $vtable (ref.func $c) (ref.func $d) )) ;; CHECK: (export "export" (func $export)) ;; CHECK: (func $export (type $void) ;; CHECK-NEXT: (call $b) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (export "export" (func $export)) ;; OPEN_WORLD: (func $export (type $void) ;; OPEN_WORLD-NEXT: (call $b) ;; OPEN_WORLD-NEXT: ) (func $export (export "export") ;; Call $b but not $a or $c (call $b) ) ;; CHECK: (func $a (type $void) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $a (type $void) ;; OPEN_WORLD-NEXT: (call_ref $void ;; OPEN_WORLD-NEXT: (struct.get $vtable 0 ;; OPEN_WORLD-NEXT: (global.get $vtable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $a (type $void) ;; $a calls field #0 in the vtable. ;; ;; Even though $a is in the vtable, it is dead, since the vtable is alive ;; but there is no live read of field #0 - the only read is in here, which ;; is basically an unreachable cycle that we can collect. We can empty out ;; this function since it is dead, but we cannot remove it entirely due to ;; the ref in the vtable. ;; ;; (In open world, however, we cannot do this, as we must assume reads of ;; struct fields can occur outside of our view. That is, the vtable could be ;; sent somewhere that reads field #0, which would make $a live.) (call_ref $void (struct.get $vtable 0 (global.get $vtable) ) ) ) ;; CHECK: (func $b (type $void) ;; CHECK-NEXT: (call_ref $void ;; CHECK-NEXT: (struct.get $vtable 1 ;; CHECK-NEXT: (global.get $vtable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $b (type $void) ;; OPEN_WORLD-NEXT: (call_ref $void ;; OPEN_WORLD-NEXT: (struct.get $vtable 1 ;; OPEN_WORLD-NEXT: (global.get $vtable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $b (type $void) ;; $b calls field #1 in the vtable. ;; ;; As $b is called from the export, this function is not dead. (call_ref $void (struct.get $vtable 1 (global.get $vtable) ) ) ) (func $c (type $void) ;; $c is parallel to $a, but using vtable-2, which has no other references, ;; so this is dead like $a, and can be removed entirely. (call_ref $void (struct.get $vtable 0 (global.get $vtable-2) ) ) ) (func $d (type $void) ;; $d is parallel to $b, but using vtable-2, which has no other references. ;; This is dead, even though the struct type + index have a use (due to the ;; other vtable) - there is no use of vtable-2 (except from unreachable ;; places like here), so this cannot be reached. (call_ref $void (struct.get $vtable 0 (global.get $vtable-2) ) ) ) ) ;; Test struct.news not in globals. (module ;; CHECK: (type $void (func)) ;; OPEN_WORLD: (type $void (func)) (type $void (func)) ;; CHECK: (type $vtable (sub (struct (field (ref $void)) (field (ref $void))))) ;; OPEN_WORLD: (type $vtable (sub (struct (field (ref $void)) (field (ref $void))))) (type $vtable (sub (struct (field (ref $void)) (field (ref $void))))) ;; CHECK: (type $struct (sub (struct (field (ref $vtable)) (field (ref $vtable)) (field (ref $vtable)) (field (ref $vtable))))) ;; OPEN_WORLD: (type $struct (sub (struct (field (ref $vtable)) (field (ref $vtable)) (field (ref $vtable)) (field (ref $vtable))))) (type $struct (sub (struct (field (ref $vtable)) (field (ref $vtable)) (field (ref $vtable)) (field (ref $vtable))))) ;; CHECK: (global $vtable (ref $vtable) (struct.new $vtable ;; CHECK-NEXT: (ref.func $a) ;; CHECK-NEXT: (ref.func $b) ;; CHECK-NEXT: )) ;; OPEN_WORLD: (global $vtable (ref $vtable) (struct.new $vtable ;; OPEN_WORLD-NEXT: (ref.func $a) ;; OPEN_WORLD-NEXT: (ref.func $b) ;; OPEN_WORLD-NEXT: )) (global $vtable (ref $vtable) (struct.new $vtable (ref.func $a) (ref.func $b) )) ;; CHECK: (elem declare func $c $d $e $f $g $h $void) ;; CHECK: (export "func" (func $func)) ;; CHECK: (func $func (type $void) ;; CHECK-NEXT: (local $ref (ref $struct)) ;; CHECK-NEXT: (local $vtable (ref $vtable)) ;; CHECK-NEXT: (local.set $ref ;; CHECK-NEXT: (struct.new $struct ;; CHECK-NEXT: (global.get $vtable) ;; CHECK-NEXT: (struct.new $vtable ;; CHECK-NEXT: (ref.func $c) ;; CHECK-NEXT: (ref.func $d) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.tee $vtable ;; CHECK-NEXT: (struct.new $vtable ;; CHECK-NEXT: (ref.func $e) ;; CHECK-NEXT: (ref.func $f) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.new $vtable ;; CHECK-NEXT: (ref.func $g) ;; CHECK-NEXT: (ref.func $h) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; (replaces unreachable StructNew we can't emit) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct 0 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct 1 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct 2 ;; CHECK-NEXT: (local.get $ref) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $vtable 1 ;; CHECK-NEXT: (local.get $vtable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $void ;; CHECK-NEXT: (ref.func $void) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (elem declare func $c $d $e $f $g $h $void) ;; OPEN_WORLD: (export "func" (func $func)) ;; OPEN_WORLD: (func $func (type $void) ;; OPEN_WORLD-NEXT: (local $ref (ref $struct)) ;; OPEN_WORLD-NEXT: (local $vtable (ref $vtable)) ;; OPEN_WORLD-NEXT: (local.set $ref ;; OPEN_WORLD-NEXT: (struct.new $struct ;; OPEN_WORLD-NEXT: (global.get $vtable) ;; OPEN_WORLD-NEXT: (struct.new $vtable ;; OPEN_WORLD-NEXT: (ref.func $c) ;; OPEN_WORLD-NEXT: (ref.func $d) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (local.tee $vtable ;; OPEN_WORLD-NEXT: (struct.new $vtable ;; OPEN_WORLD-NEXT: (ref.func $e) ;; OPEN_WORLD-NEXT: (ref.func $f) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (struct.new $vtable ;; OPEN_WORLD-NEXT: (ref.func $g) ;; OPEN_WORLD-NEXT: (ref.func $h) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (block ;; (replaces unreachable StructNew we can't emit) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (unreachable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (unreachable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (unreachable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (struct.get $struct 0 ;; OPEN_WORLD-NEXT: (local.get $ref) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (struct.get $struct 1 ;; OPEN_WORLD-NEXT: (local.get $ref) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (struct.get $struct 2 ;; OPEN_WORLD-NEXT: (local.get $ref) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (struct.get $vtable 1 ;; OPEN_WORLD-NEXT: (local.get $vtable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (call_ref $void ;; OPEN_WORLD-NEXT: (ref.func $void) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $func (export "func") (local $ref (ref $struct)) (local $vtable (ref $vtable)) (local.set $ref (struct.new $struct ;; Init one field using the global vtable. (global.get $vtable) ;; Init another field using a vtable we create here - a nested ;; struct.new inside this one. (struct.new $vtable (ref.func $c) (ref.func $d) ) ;; Another nested one, but now there is a side effect. Everything here ;; is considered to escape due to that. (local.tee $vtable (struct.new $vtable (ref.func $e) (ref.func $f) ) ) ;; Another nested one. This field will not be read. (struct.new $vtable (ref.func $g) (ref.func $h) ) ) ) ;; Test that we do not assert on an unreachable struct.new. (drop (struct.new $vtable (unreachable) (unreachable) ) ) ;; Read from all fields of $struct except for the last. (drop (struct.get $struct 0 (local.get $ref) ) ) (drop (struct.get $struct 1 (local.get $ref) ) ) (drop (struct.get $struct 2 (local.get $ref) ) ) ;; Read from field #1 of the vtable type, but not #0. (drop (struct.get $vtable 1 (local.get $vtable) ) ) ;; Call something of type void so we don't eliminate them all instantly. (call_ref $void (ref.func $void) ) ) ;; CHECK: (func $void (type $void) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $void (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $void (type $void) ;; Helper function. This is reached via a call_ref. ) ;; CHECK: (func $a (type $void) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $a (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $a (type $void) ;; This is unreachable (in closed world) since a reference to it only exists ;; in field #0 of the vtable type, which is never read from. ) ;; CHECK: (func $b (type $void) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $b (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $b (type $void) ;; This is reachable. It is in field #1, which is read, and the global ;; vtable is also read, and the type $void is call_reffed. ) ;; CHECK: (func $c (type $void) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $c (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $c (type $void) ;; Like $a, this is unreachable. That it is in a nested struct.new, and not ;; in a global, does not matter. ) ;; CHECK: (func $d (type $void) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $d (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $d (type $void) ;; Like $b, this is reachable. That it is in a nested struct.new, and not ;; in a global, does not matter. ) ;; CHECK: (func $e (type $void) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $e (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $e (type $void) ;; Side effects on the struct field are not enough to make this reachable: ;; there is a tee on the struct.new we are in, but field #0 is still not ;; read from the relevant struct. ) ;; CHECK: (func $f (type $void) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $f (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $f (type $void) ;; Like $b, this is reachable (the tee does not matter). ) ;; CHECK: (func $g (type $void) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $g (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $g (type $void) ;; This is in a struct written to a field that is never read in $struct, so ;; it is unreachable. ) ;; CHECK: (func $h (type $void) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $h (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $h (type $void) ;; This is in a struct written to a field that is never read in $struct, so ;; it is unreachable. ) ) ;; Test side effects causing a value to "leak." (module ;; CHECK: (type $void (func)) ;; OPEN_WORLD: (type $void (func)) (type $void (func)) ;; CHECK: (type $vtable (sub (struct (field (ref $void)) (field (ref $void))))) ;; OPEN_WORLD: (type $vtable (sub (struct (field (ref $void)) (field (ref $void))))) (type $vtable (sub (struct (field (ref $void)) (field (ref $void))))) ;; CHECK: (elem declare func $a $b $void) ;; CHECK: (export "func" (func $func)) ;; CHECK: (func $func (type $void) ;; CHECK-NEXT: (local $vtable (ref $vtable)) ;; CHECK-NEXT: (local $void (ref $void)) ;; CHECK-NEXT: (local.set $vtable ;; CHECK-NEXT: (struct.new $vtable ;; CHECK-NEXT: (ref.func $a) ;; CHECK-NEXT: (local.tee $void ;; CHECK-NEXT: (ref.func $b) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $void ;; CHECK-NEXT: (ref.func $void) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (elem declare func $a $b $void) ;; OPEN_WORLD: (export "func" (func $func)) ;; OPEN_WORLD: (func $func (type $void) ;; OPEN_WORLD-NEXT: (local $vtable (ref $vtable)) ;; OPEN_WORLD-NEXT: (local $void (ref $void)) ;; OPEN_WORLD-NEXT: (local.set $vtable ;; OPEN_WORLD-NEXT: (struct.new $vtable ;; OPEN_WORLD-NEXT: (ref.func $a) ;; OPEN_WORLD-NEXT: (local.tee $void ;; OPEN_WORLD-NEXT: (ref.func $b) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (call_ref $void ;; OPEN_WORLD-NEXT: (ref.func $void) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $func (export "func") (local $vtable (ref $vtable)) (local $void (ref $void)) ;; Init one field using a tee, and one normally. (local.set $vtable (struct.new $vtable (ref.func $a) (local.tee $void (ref.func $b) ) ) ) ;; Call the type so it is reachable. (call_ref $void (ref.func $void) ) ) ;; CHECK: (func $void (type $void) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $void (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $void (type $void) ;; Helper function. This is reached via a call_ref. ) ;; CHECK: (func $a (type $void) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $a (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $a (type $void) ;; This is unreachable (in closed world) because we have no reads from the ;; struct field it is written in. ) ;; CHECK: (func $b (type $void) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $b (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $b (type $void) ;; The local.tee makes this reachable: the value is not known to only reside ;; in the struct field, so we must assume it can be used even if the struct ;; field is not. ) ) ;; Cycles. (module (rec ;; CHECK: (rec ;; CHECK-NEXT: (type $vtable-func (func (param (ref $vtable)))) ;; OPEN_WORLD: (rec ;; OPEN_WORLD-NEXT: (type $vtable-func (func (param (ref $vtable)))) (type $vtable-func (func (param (ref $vtable)))) ;; CHECK: (type $vtable (sub (struct (field (ref $vtable-func)) (field (ref $vtable-func))))) ;; OPEN_WORLD: (type $vtable (sub (struct (field (ref $vtable-func)) (field (ref $vtable-func))))) (type $vtable (sub (struct (field (ref $vtable-func)) (field (ref $vtable-func))))) ) ;; CHECK: (type $2 (func)) ;; CHECK: (elem declare func $a $b $c $d) ;; CHECK: (export "func" (func $func)) ;; CHECK: (func $func (type $2) ;; CHECK-NEXT: (call_ref $vtable-func ;; CHECK-NEXT: (struct.new $vtable ;; CHECK-NEXT: (ref.func $a) ;; CHECK-NEXT: (ref.func $b) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.get $vtable 0 ;; CHECK-NEXT: (struct.new $vtable ;; CHECK-NEXT: (ref.func $a) ;; CHECK-NEXT: (ref.func $b) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $vtable 0 ;; CHECK-NEXT: (struct.new $vtable ;; CHECK-NEXT: (ref.func $c) ;; CHECK-NEXT: (ref.func $d) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (type $2 (func)) ;; OPEN_WORLD: (elem declare func $a $b $c $d) ;; OPEN_WORLD: (export "func" (func $func)) ;; OPEN_WORLD: (func $func (type $2) ;; OPEN_WORLD-NEXT: (call_ref $vtable-func ;; OPEN_WORLD-NEXT: (struct.new $vtable ;; OPEN_WORLD-NEXT: (ref.func $a) ;; OPEN_WORLD-NEXT: (ref.func $b) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (struct.get $vtable 0 ;; OPEN_WORLD-NEXT: (struct.new $vtable ;; OPEN_WORLD-NEXT: (ref.func $a) ;; OPEN_WORLD-NEXT: (ref.func $b) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (struct.get $vtable 0 ;; OPEN_WORLD-NEXT: (struct.new $vtable ;; OPEN_WORLD-NEXT: (ref.func $c) ;; OPEN_WORLD-NEXT: (ref.func $d) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $func (export "func") ;; Read field 0, and call it. (call_ref $vtable-func (struct.new $vtable (ref.func $a) (ref.func $b) ) (struct.get $vtable 0 ;; Duplicate the first vtable. (struct.new $vtable (ref.func $a) (ref.func $b) ) ) ) ;; Again, read field #0. No need to call it here (the call before makes the ;; type used). (drop (struct.get $vtable 0 ;; Make a new vtable with new funcs. (struct.new $vtable (ref.func $c) (ref.func $d) ) ) ) ) ;; CHECK: (func $a (type $vtable-func) (param $vtable (ref $vtable)) ;; CHECK-NEXT: (call_ref $vtable-func ;; CHECK-NEXT: (local.get $vtable) ;; CHECK-NEXT: (struct.get $vtable 0 ;; CHECK-NEXT: (local.get $vtable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $a (type $vtable-func) (param $vtable (ref $vtable)) ;; OPEN_WORLD-NEXT: (call_ref $vtable-func ;; OPEN_WORLD-NEXT: (local.get $vtable) ;; OPEN_WORLD-NEXT: (struct.get $vtable 0 ;; OPEN_WORLD-NEXT: (local.get $vtable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $a (type $vtable-func) (param $vtable (ref $vtable)) ;; $a calls $a or $c (using field #0). ;; $a is reached from $func, so it is reachable. (call_ref $vtable-func (local.get $vtable) (struct.get $vtable 0 (local.get $vtable) ) ) ) ;; CHECK: (func $b (type $vtable-func) (param $vtable (ref $vtable)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $b (type $vtable-func) (param $vtable (ref $vtable)) ;; OPEN_WORLD-NEXT: (call_ref $vtable-func ;; OPEN_WORLD-NEXT: (local.get $vtable) ;; OPEN_WORLD-NEXT: (struct.get $vtable 1 ;; OPEN_WORLD-NEXT: (local.get $vtable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $b (type $vtable-func) (param $vtable (ref $vtable)) ;; $b calls $b or $d (using field #1). ;; But $b is not reached from $func, so it remains unreachable in closed ;; world. (call_ref $vtable-func (local.get $vtable) (struct.get $vtable 1 (local.get $vtable) ) ) ) ;; CHECK: (func $c (type $vtable-func) (param $vtable (ref $vtable)) ;; CHECK-NEXT: (call_ref $vtable-func ;; CHECK-NEXT: (local.get $vtable) ;; CHECK-NEXT: (struct.get $vtable 0 ;; CHECK-NEXT: (local.get $vtable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $c (type $vtable-func) (param $vtable (ref $vtable)) ;; OPEN_WORLD-NEXT: (call_ref $vtable-func ;; OPEN_WORLD-NEXT: (local.get $vtable) ;; OPEN_WORLD-NEXT: (struct.get $vtable 0 ;; OPEN_WORLD-NEXT: (local.get $vtable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $c (type $vtable-func) (param $vtable (ref $vtable)) ;; $c forms a cycle with $a. (call_ref $vtable-func (local.get $vtable) (struct.get $vtable 0 (local.get $vtable) ) ) ) ;; CHECK: (func $d (type $vtable-func) (param $vtable (ref $vtable)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $d (type $vtable-func) (param $vtable (ref $vtable)) ;; OPEN_WORLD-NEXT: (call_ref $vtable-func ;; OPEN_WORLD-NEXT: (local.get $vtable) ;; OPEN_WORLD-NEXT: (struct.get $vtable 1 ;; OPEN_WORLD-NEXT: (local.get $vtable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $d (type $vtable-func) (param $vtable (ref $vtable)) ;; $d forms a cycle with $b. (call_ref $vtable-func (local.get $vtable) (struct.get $vtable 1 (local.get $vtable) ) ) ) ) ;; Subtyping of struct reads. (module ;; CHECK: (type $void (func)) ;; OPEN_WORLD: (type $void (func)) (type $void (func)) ;; CHECK: (type $struct (sub (struct (field funcref)))) ;; OPEN_WORLD: (type $struct (sub (struct (field funcref)))) (type $struct (sub (struct (field funcref)))) ;; CHECK: (type $substruct (sub $struct (struct (field funcref)))) ;; OPEN_WORLD: (type $substruct (sub $struct (struct (field funcref)))) (type $substruct (sub $struct (struct (field funcref)))) ;; CHECK: (type $subsubstruct (sub $substruct (struct (field funcref)))) ;; OPEN_WORLD: (type $subsubstruct (sub $substruct (struct (field funcref)))) (type $subsubstruct (sub $substruct (struct (field funcref)))) ;; CHECK: (global $g (ref $struct) (struct.new $struct ;; CHECK-NEXT: (ref.func $f) ;; CHECK-NEXT: )) ;; OPEN_WORLD: (global $g (ref $struct) (struct.new $struct ;; OPEN_WORLD-NEXT: (ref.func $f) ;; OPEN_WORLD-NEXT: )) (global $g (ref $struct) (struct.new $struct (ref.func $f) )) ;; CHECK: (global $subg (ref $substruct) (struct.new $substruct ;; CHECK-NEXT: (ref.func $subf) ;; CHECK-NEXT: )) ;; OPEN_WORLD: (global $subg (ref $substruct) (struct.new $substruct ;; OPEN_WORLD-NEXT: (ref.func $subf) ;; OPEN_WORLD-NEXT: )) (global $subg (ref $substruct) (struct.new $substruct (ref.func $subf) )) ;; CHECK: (global $subsubg (ref $subsubstruct) (struct.new $subsubstruct ;; CHECK-NEXT: (ref.func $subsubf) ;; CHECK-NEXT: )) ;; OPEN_WORLD: (global $subsubg (ref $subsubstruct) (struct.new $subsubstruct ;; OPEN_WORLD-NEXT: (ref.func $subsubf) ;; OPEN_WORLD-NEXT: )) (global $subsubg (ref $subsubstruct) (struct.new $subsubstruct (ref.func $subsubf) )) ;; CHECK: (elem declare func $func) ;; CHECK: (export "func" (func $func)) ;; CHECK: (func $func (type $void) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (global.get $g) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (global.get $subg) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (global.get $subsubg) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $substruct 0 ;; CHECK-NEXT: (block (result (ref $substruct)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $void ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (elem declare func $func) ;; OPEN_WORLD: (export "func" (func $func)) ;; OPEN_WORLD: (func $func (type $void) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (global.get $g) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (global.get $subg) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (global.get $subsubg) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (struct.get $substruct 0 ;; OPEN_WORLD-NEXT: (block (result (ref $substruct)) ;; OPEN_WORLD-NEXT: (unreachable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (call_ref $void ;; OPEN_WORLD-NEXT: (ref.func $func) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $func (export "func") ;; Refer to the globals. (drop (global.get $g) ) (drop (global.get $subg) ) (drop (global.get $subsubg) ) ;; Read from $substruct's field, but not its super or subtypes. (drop (struct.get $substruct 0 (block (result (ref $substruct)) (unreachable) ) ) ) ;; Call the function type to allow functions to be used. (call_ref $void (ref.func $func) ) ) (func $void (type $void) ;; Helper function. This is reached via a call_ref. ) ;; CHECK: (func $f (type $void) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $f (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $f (type $void) ;; This is unreachable in closed world. The global it is in has a reference ;; but the struct there has no reads of its field. ) ;; CHECK: (func $subf (type $void) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $subf (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $subf (type $void) ;; There is a read of $substruct's field, which makes this reachable. ) ;; CHECK: (func $subsubf (type $void) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $subsubf (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $subsubf (type $void) ;; There is a read of $substruct's field, which may read from any subtype, ;; which makes this reachable. ) ) ;; Test references to global function references. (module ;; CHECK: (type $void (func)) ;; OPEN_WORLD: (type $void (func)) (type $void (func)) ;; CHECK: (type $A (struct (field funcref))) ;; OPEN_WORLD: (type $A (struct (field funcref))) (type $A (struct (field funcref))) ;; CHECK: (global $g1 (ref func) (ref.func $f1)) ;; OPEN_WORLD: (global $g1 (ref func) (ref.func $f1)) (global $g1 (ref func) (ref.func $f1)) ;; CHECK: (global $g2 (ref func) (ref.func $f2)) ;; OPEN_WORLD: (global $g2 (ref func) (ref.func $f2)) (global $g2 (ref func) (ref.func $f2)) ;; CHECK: (elem declare func $func) ;; CHECK: (export "func" (func $func)) ;; CHECK: (func $func (type $void) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (global.get $g1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (global.get $g2) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $void ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (elem declare func $func) ;; OPEN_WORLD: (export "func" (func $func)) ;; OPEN_WORLD: (func $func (type $void) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (global.get $g1) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (struct.new $A ;; OPEN_WORLD-NEXT: (global.get $g2) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (call_ref $void ;; OPEN_WORLD-NEXT: (ref.func $func) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $func (export "func") ;; Refer to $g1 directly. (drop (global.get $g1) ) ;; Refer to $g2 from a struct field that is never read. (drop (struct.new $A (global.get $g2) ) ) ;; Call the function type to allow functions to be used. (call_ref $void (ref.func $func) ) ) (func $void (type $void) ;; Helper function. This is reached via a call_ref. ) ;; CHECK: (func $f1 (type $void) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $f1 (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $f1 (type $void) ;; The global containing this function's reference is used. ) ;; CHECK: (func $f2 (type $void) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $f2 (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $f2 (type $void) ;; This is unreachable in closed world as the global is referred to from a ;; struct field that is never read from. ) ) ;; As above, but now the globals are struct.news. (module ;; CHECK: (type $A (struct (field funcref))) ;; CHECK: (type $void (func)) ;; OPEN_WORLD: (type $A (struct (field funcref))) ;; OPEN_WORLD: (type $void (func)) (type $void (func)) (type $A (struct (field funcref))) ;; CHECK: (type $B (struct (field (ref $A)))) ;; OPEN_WORLD: (type $B (struct (field (ref $A)))) (type $B (struct (field (ref $A)))) ;; CHECK: (global $g (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.func $f) ;; CHECK-NEXT: )) ;; OPEN_WORLD: (global $g (ref $A) (struct.new $A ;; OPEN_WORLD-NEXT: (ref.func $f) ;; OPEN_WORLD-NEXT: )) (global $g (ref $A) (struct.new $A (ref.func $f) )) ;; CHECK: (elem declare func $func) ;; CHECK: (export "func" (func $func)) ;; CHECK: (func $func (type $void) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (global.get $g) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $void ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (block (result (ref $A)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (elem declare func $func) ;; OPEN_WORLD: (export "func" (func $func)) ;; OPEN_WORLD: (func $func (type $void) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (struct.new $B ;; OPEN_WORLD-NEXT: (global.get $g) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (call_ref $void ;; OPEN_WORLD-NEXT: (ref.func $func) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (struct.get $A 0 ;; OPEN_WORLD-NEXT: (block (result (ref $A)) ;; OPEN_WORLD-NEXT: (unreachable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $func (export "func") ;; Refer to $g from a struct field that is never read. (drop (struct.new $B (global.get $g) ) ) ;; Call the function type to allow functions to be used. (call_ref $void (ref.func $func) ) ;; Read $A's field. (drop (struct.get $A 0 (block (result (ref $A)) (unreachable) ) ) ) ) (func $void (type $void) ;; Helper function. This is reached via a call_ref. ) ;; CHECK: (func $f (type $void) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $f (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $f (type $void) ;; This is unreachable in closed world since $B's field is not read, so the ;; global it is in is only referenced and not used. ) ) ;; As above, but read $B's field. Now $f is reachable. (module ;; CHECK: (type $A (struct (field funcref))) ;; CHECK: (type $void (func)) ;; OPEN_WORLD: (type $A (struct (field funcref))) ;; OPEN_WORLD: (type $void (func)) (type $void (func)) (type $A (struct (field funcref))) ;; CHECK: (type $B (struct (field (ref $A)))) ;; OPEN_WORLD: (type $B (struct (field (ref $A)))) (type $B (struct (field (ref $A)))) ;; CHECK: (global $g (ref $A) (struct.new $A ;; CHECK-NEXT: (ref.func $f) ;; CHECK-NEXT: )) ;; OPEN_WORLD: (global $g (ref $A) (struct.new $A ;; OPEN_WORLD-NEXT: (ref.func $f) ;; OPEN_WORLD-NEXT: )) (global $g (ref $A) (struct.new $A (ref.func $f) )) ;; CHECK: (elem declare func $func) ;; CHECK: (export "func" (func $func)) ;; CHECK: (func $func (type $void) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $B ;; CHECK-NEXT: (global.get $g) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (call_ref $void ;; CHECK-NEXT: (ref.func $func) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $A 0 ;; CHECK-NEXT: (block (result (ref $A)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $B 0 ;; CHECK-NEXT: (block (result (ref $B)) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (elem declare func $func) ;; OPEN_WORLD: (export "func" (func $func)) ;; OPEN_WORLD: (func $func (type $void) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (struct.new $B ;; OPEN_WORLD-NEXT: (global.get $g) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (call_ref $void ;; OPEN_WORLD-NEXT: (ref.func $func) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (struct.get $A 0 ;; OPEN_WORLD-NEXT: (block (result (ref $A)) ;; OPEN_WORLD-NEXT: (unreachable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (struct.get $B 0 ;; OPEN_WORLD-NEXT: (block (result (ref $B)) ;; OPEN_WORLD-NEXT: (unreachable) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $func (export "func") (drop (struct.new $B (global.get $g) ) ) (call_ref $void (ref.func $func) ) (drop (struct.get $A 0 (block (result (ref $A)) (unreachable) ) ) ) ;; The change in this testcase is to read $B's field. (drop (struct.get $B 0 (block (result (ref $B)) (unreachable) ) ) ) ) (func $void (type $void) ;; Helper function. This is reached via a call_ref. ) ;; CHECK: (func $f (type $void) ;; CHECK-NEXT: (nop) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $f (type $void) ;; OPEN_WORLD-NEXT: (nop) ;; OPEN_WORLD-NEXT: ) (func $f (type $void) ) ) ;; The call.without.effects intrinsic reports itself as having no side effects. ;; We do still need to consider the target as being called, however, even if it ;; is in a struct field. (module ;; CHECK: (type $0 (func (param funcref) (result i32))) ;; CHECK: (type $1 (func)) ;; CHECK: (type $A (struct (field i32))) ;; OPEN_WORLD: (type $0 (func (param funcref) (result i32))) ;; OPEN_WORLD: (type $1 (func)) ;; OPEN_WORLD: (type $A (struct (field i32))) (type $A (struct (field i32))) ;; CHECK: (type $3 (func (result i32))) ;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (type $0) (param funcref) (result i32))) ;; OPEN_WORLD: (type $3 (func (result i32))) ;; OPEN_WORLD: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (type $0) (param funcref) (result i32))) (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (param funcref) (result i32))) ;; CHECK: (elem declare func $getter) ;; CHECK: (export "main" (func $main)) ;; CHECK: (func $main (type $1) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new $A ;; CHECK-NEXT: (call $call.without.effects ;; CHECK-NEXT: (ref.func $getter) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (elem declare func $getter) ;; OPEN_WORLD: (export "main" (func $main)) ;; OPEN_WORLD: (func $main (type $1) ;; OPEN_WORLD-NEXT: (drop ;; OPEN_WORLD-NEXT: (struct.new $A ;; OPEN_WORLD-NEXT: (call $call.without.effects ;; OPEN_WORLD-NEXT: (ref.func $getter) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) ;; OPEN_WORLD-NEXT: ) (func $main (export "main") (drop (struct.new $A (call $call.without.effects (ref.func $getter) ) ) ) ) ;; CHECK: (func $getter (type $3) (result i32) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: ) ;; OPEN_WORLD: (func $getter (type $3) (result i32) ;; OPEN_WORLD-NEXT: (i32.const 42) ;; OPEN_WORLD-NEXT: ) (func $getter (result i32) ;; This function body should not be turned into an unreachable. It is ;; reached from $main, even though the call is marked as not having effects. (i32.const 42) ) )