blob: 146a25ea6081fa3e3a208635a595b5d4f3b11741 [file] [log] [blame] [edit]
;; 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)
)
)