| ;; 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: ) | |
| ;; OPEN_WORLD: (func $target-A (type $A) | |
| ;; 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: ) | |
| ;; OPEN_WORLD: (func $target-A-sub (type $A-sub) | |
| ;; 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: ) | |
| (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: ) | |
| (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: ) | |
| ;; OPEN_WORLD: (func $target-A (type $A) | |
| ;; 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: ) | |
| ;; OPEN_WORLD: (func $target-A-1 (type $A) | |
| ;; OPEN_WORLD-NEXT: ) | |
| (func $target-A-1 (type $A) | |
| ;; This function is reachable. | |
| ) | |
| ;; CHECK: (func $target-A-2 (type $A) | |
| ;; CHECK-NEXT: ) | |
| ;; OPEN_WORLD: (func $target-A-2 (type $A) | |
| ;; 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: ) | |
| ;; OPEN_WORLD: (func $target-A-1 (type $A) | |
| ;; OPEN_WORLD-NEXT: ) | |
| (func $target-A-1 (type $A) | |
| ;; This function is reachable. | |
| ) | |
| ;; CHECK: (func $target-A-2 (type $A) | |
| ;; CHECK-NEXT: ) | |
| ;; OPEN_WORLD: (func $target-A-2 (type $A) | |
| ;; 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: ) | |
| ;; OPEN_WORLD: (func $target-keep (type $A) | |
| ;; 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: ) | |
| (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: ) | |
| ;; OPEN_WORLD: (func $target-keep (type $A) | |
| ;; OPEN_WORLD-NEXT: ) | |
| (func $target-keep (type $A) | |
| ) | |
| ;; CHECK: (func $target-keep-2 (type $A) | |
| ;; CHECK-NEXT: ) | |
| ;; OPEN_WORLD: (func $target-keep-2 (type $A) | |
| ;; 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: ) | |
| ;; OPEN_WORLD: (func $void (type $void) | |
| ;; 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: ) | |
| (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: ) | |
| ;; OPEN_WORLD: (func $b (type $void) | |
| ;; 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: ) | |
| (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: ) | |
| ;; OPEN_WORLD: (func $d (type $void) | |
| ;; 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: ) | |
| (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: ) | |
| ;; OPEN_WORLD: (func $f (type $void) | |
| ;; 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: ) | |
| (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: ) | |
| (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: ) | |
| ;; OPEN_WORLD: (func $void (type $void) | |
| ;; 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: ) | |
| (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: ) | |
| ;; OPEN_WORLD: (func $b (type $void) | |
| ;; 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: ) | |
| (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: ) | |
| ;; OPEN_WORLD: (func $subf (type $void) | |
| ;; 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: ) | |
| ;; OPEN_WORLD: (func $subsubf (type $void) | |
| ;; 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: ) | |
| ;; OPEN_WORLD: (func $f1 (type $void) | |
| ;; 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: ) | |
| (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: ) | |
| (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: ) | |
| ;; OPEN_WORLD: (func $f (type $void) | |
| ;; 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) | |
| ) | |
| ) |