| ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. |
| |
| ;; RUN: foreach %s %t wasm-opt --precompute --optimize-level=2 -all -S -o - | filecheck %s |
| |
| (module |
| ;; CHECK: (func $simple-1 (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $simple-1 (param $param i32) (result i32) |
| ;; The eqz can be applied to the select arms. |
| (i32.eqz |
| (select |
| (i32.const 42) |
| (i32.const 1337) |
| (local.get $param) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $simple-2 (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $simple-2 (param $param i32) (result i32) |
| (i32.eqz |
| (select |
| (i32.const 0) |
| (i32.const 10) |
| (local.get $param) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $simple-3 (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $simple-3 (param $param i32) (result i32) |
| (i32.eqz |
| (select |
| (i32.const 20) |
| (i32.const 0) |
| (local.get $param) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $simple-4 (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $simple-4 (param $param i32) (result i32) |
| (i32.eqz |
| (select |
| (i32.const 0) |
| (i32.const 0) |
| (local.get $param) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $not-left (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (i32.eqz |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $not-left (param $param i32) (result i32) |
| (i32.eqz |
| (select |
| (local.get $param) ;; this cannot be precomputed, so we do nothing |
| (i32.const 0) |
| (local.get $param) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $not-right (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (i32.eqz |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $not-right (param $param i32) (result i32) |
| (i32.eqz |
| (select |
| (i32.const 0) |
| (local.get $param) ;; this cannot be precomputed, so we do nothing |
| (local.get $param) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $not-neither (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (i32.eqz |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $not-neither (param $param i32) (result i32) |
| (i32.eqz |
| (select |
| (local.get $param) ;; these cannot be precomputed, |
| (local.get $param) ;; so we do nothing |
| (local.get $param) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $binary (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 11) |
| ;; CHECK-NEXT: (i32.const 21) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $binary (param $param i32) (result i32) |
| (i32.add |
| (select |
| (i32.const 10) |
| (i32.const 20) |
| (local.get $param) |
| ) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $binary-2 (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 11) |
| ;; CHECK-NEXT: (i32.const 21) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $binary-2 (param $param i32) (result i32) |
| ;; As above but with the select in the other arm. |
| (i32.add |
| (i32.const 1) |
| (select |
| (i32.const 10) |
| (i32.const 20) |
| (local.get $param) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $binary-3 (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 30) |
| ;; CHECK-NEXT: (i32.const 40) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $binary-3 (param $param i32) (result i32) |
| ;; Two selects. We do not optimize here (but in theory could, as the |
| ;; condition is identical - other passes should merge that). |
| (i32.add |
| (select |
| (i32.const 10) |
| (i32.const 20) |
| (local.get $param) |
| ) |
| (select |
| (i32.const 30) |
| (i32.const 40) |
| (local.get $param) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $unreachable (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (i32.eqz |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $unreachable (param $param i32) (result i32) |
| ;; We should ignore unreachable code like this. We would need to make sure |
| ;; to emit the proper type on the outside, and it's simpler to just defer |
| ;; this to DCE. |
| (i32.eqz |
| (select |
| (i32.const 0) |
| (i32.const 0) |
| (unreachable) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $tuple (type $1) (param $param i32) (result i32 i32) |
| ;; CHECK-NEXT: (tuple.make 2 |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $tuple (param $param i32) (result i32 i32) |
| ;; We should ignore tuples, as select outputs cannot be tuples. |
| (tuple.make 2 |
| (select |
| (i32.const 0) |
| (i32.const 1) |
| (local.get $param) |
| ) |
| (i32.const 2) |
| ) |
| ) |
| |
| ;; CHECK: (func $control-flow (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (block $target (result i32) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (br_if $target |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $control-flow (param $param i32) (result i32) |
| ;; We ignore control flow structures to avoid issues with removing them (as |
| ;; the condition might refer to them, as in this testcase). |
| (block $target (result i32) |
| (select |
| (i32.const 0) |
| (i32.const 1) |
| ;; If we precomputed the block into the select arms, this br_if |
| ;; would have nowhere to go. |
| (br_if $target |
| (i32.const 1) |
| (local.get $param) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $break (type $0) (param $x i32) (result i32) |
| ;; CHECK-NEXT: (block $label (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (br_if $label |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $break (param $x i32) (result i32) |
| ;; We should change nothing here: we do not partially precompute breaks yet |
| ;; TODO |
| (block $label (result i32) |
| (drop |
| (br_if $label |
| (select |
| (i32.const 0) |
| (i32.const 1) |
| (local.get $x) |
| ) |
| (i32.const 2) |
| ) |
| ) |
| (i32.const 3) |
| ) |
| ) |
| |
| ;; CHECK: (func $toplevel (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $toplevel (param $param i32) (result i32) |
| ;; There is nothing to do for a select with no parent, but do not error at |
| ;; least. |
| (select |
| (i32.const 0) |
| (i32.const 10) |
| (local.get $param) |
| ) |
| ) |
| |
| ;; CHECK: (func $toplevel-nested (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $toplevel-nested (param $param i32) (result i32) |
| ;; The inner select can be optimized: here we apply the outer select onto |
| ;; the inner one (the same as any other thing we apply into the select arms, |
| ;; but it happens to be a select itself). |
| (select |
| (i32.const 0) |
| (i32.const 10) |
| (select |
| (i32.const 0) |
| (i32.const 20) |
| (local.get $param) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $toplevel-nested-flip (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $toplevel-nested-flip (param $param i32) (result i32) |
| ;; As above but with inner select arms flipped. That flips the result. |
| (select |
| (i32.const 0) |
| (i32.const 10) |
| (select |
| (i32.const 20) |
| (i32.const 0) |
| (local.get $param) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $toplevel-nested-indirect (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $toplevel-nested-indirect (param $param i32) (result i32) |
| ;; As above, with an instruction in the middle. Again, we can optimize. |
| (select |
| (i32.const 0) |
| (i32.const 10) |
| (i32.eqz |
| (select |
| (i32.const 0) |
| (i32.const 20) |
| (local.get $param) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $nested (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $nested (param $param i32) (result i32) |
| ;; As above, with an outer eqz as well. Now both the outer and inner selects |
| ;; can be optimized, and after the inner one is, it can be optimized with |
| ;; the outer one as well, leaving a single select and no eqz. |
| (i32.eqz |
| (select |
| (i32.const 0) |
| (i32.const 10) |
| (i32.eqz |
| (select |
| (i32.const 0) |
| (i32.const 20) |
| (local.get $param) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $nested-arms (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (i32.eqz |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 30) |
| ;; CHECK-NEXT: (i32.const 40) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 50) |
| ;; CHECK-NEXT: (i32.const 60) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $nested-arms (param $param i32) (result i32) |
| ;; We do nothing for selects nested directly in select arms, but do not |
| ;; error at least. Note that we could apply the eqz two levels deep TODO |
| (i32.eqz |
| (select |
| (select |
| (i32.const 10) |
| (i32.const 20) |
| (local.get $param) |
| ) |
| (select |
| (i32.const 30) |
| (i32.const 40) |
| (local.get $param) |
| ) |
| (select |
| (i32.const 50) |
| (i32.const 60) |
| (local.get $param) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $nested-indirect-arms (type $0) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (i32.eqz |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $nested-indirect-arms (param $param i32) (result i32) |
| ;; As above, but now there is something in the middle, that can be optimized |
| ;; into those selects. |
| (i32.eqz |
| (select |
| (i32.eqz |
| (select |
| (i32.const 0) |
| (i32.const 10) |
| (local.get $param) |
| ) |
| ) |
| (i32.eqz |
| (select |
| (i32.const 20) |
| (i32.const 0) |
| (local.get $param) |
| ) |
| ) |
| (i32.eqz |
| (select |
| (i32.const 0) |
| (i32.const 30) |
| (local.get $param) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; References. |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $vtable (sub (struct (field funcref)))) |
| (type $vtable (sub (struct funcref))) |
| |
| ;; CHECK: (type $specific-func (sub (func (result i32)))) |
| (type $specific-func (sub (func (result i32)))) |
| |
| ;; CHECK: (type $specific-func.sub (sub $specific-func (func (result i32)))) |
| (type $specific-func.sub (sub $specific-func (func (result i32)))) |
| |
| ;; CHECK: (type $vtable.sub (sub $vtable (struct (field (ref $specific-func))))) |
| (type $vtable.sub (sub $vtable (struct (field (ref $specific-func))))) |
| ) |
| |
| ;; CHECK: (global $A$vtable (ref $vtable) (struct.new $vtable |
| ;; CHECK-NEXT: (ref.func $A$func) |
| ;; CHECK-NEXT: )) |
| (global $A$vtable (ref $vtable) (struct.new $vtable |
| (ref.func $A$func) |
| )) |
| |
| ;; CHECK: (global $B$vtable (ref $vtable) (struct.new $vtable |
| ;; CHECK-NEXT: (ref.func $B$func) |
| ;; CHECK-NEXT: )) |
| (global $B$vtable (ref $vtable) (struct.new $vtable |
| (ref.func $B$func) |
| )) |
| |
| ;; CHECK: (func $test-expanded (type $4) (param $x i32) (result funcref) |
| ;; CHECK-NEXT: (select (result (ref $specific-func)) |
| ;; CHECK-NEXT: (ref.func $A$func) |
| ;; CHECK-NEXT: (ref.func $B$func) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test-expanded (param $x i32) (result funcref) |
| ;; We can apply the struct.get to the select arms: As the globals are all |
| ;; immutable, we can read the function references from them, and emit a |
| ;; select on those values, saving the struct.get operation entirely. |
| ;; |
| ;; Note that this test also checks updating the type of the select, which |
| ;; initially returned a struct, and afterwards returns a func. |
| (struct.get $vtable 0 |
| (select |
| (global.get $A$vtable) |
| (global.get $B$vtable) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $test-subtyping (type $4) (param $x i32) (result funcref) |
| ;; CHECK-NEXT: (select (result (ref $specific-func)) |
| ;; CHECK-NEXT: (ref.func $A$func) |
| ;; CHECK-NEXT: (ref.func $B$func) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test-subtyping (param $x i32) (result funcref) |
| ;; As above, but now we have struct.news directly in the arms, and one is |
| ;; of a subtype of the final result (which should not prevent optimization). |
| (struct.get $vtable.sub 0 |
| (select |
| (struct.new $vtable.sub |
| (ref.func $A$func) |
| ) |
| (struct.new $vtable.sub |
| (ref.func $B$func) ;; this function is of a subtype of the field type |
| ) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $test-expanded-twice (type $5) (param $x i32) (result i32) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test-expanded-twice (param $x i32) (result i32) |
| ;; As $test-expanded, but we have two operations that can be applied. Both |
| ;; references are non-null, so the select arms will become 0. |
| (ref.is_null |
| (struct.get $vtable 0 |
| (select |
| (global.get $A$vtable) |
| (global.get $B$vtable) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $test-expanded-twice-stop (type $6) (param $x i32) |
| ;; CHECK-NEXT: (call $send-i32 |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test-expanded-twice-stop (param $x i32) |
| ;; As $test-expanded-twice, but we stop after two expansions when we fail on |
| ;; the call. |
| (call $send-i32 |
| (ref.is_null |
| (struct.get $vtable 0 |
| (select |
| (global.get $A$vtable) |
| (global.get $B$vtable) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $send-i32 (type $6) (param $x i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $send-i32 (param $x i32) |
| ;; Helper for above. |
| ) |
| |
| ;; CHECK: (func $binary (type $5) (param $param i32) (result i32) |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $binary (param $param i32) (result i32) |
| ;; This is a common pattern in J2Wasm output. Note that this is optimized |
| ;; because immutable globals can be compared at compile time. |
| (ref.eq |
| (select |
| (global.get $A$vtable) |
| (global.get $B$vtable) |
| (local.get $param) |
| ) |
| (global.get $A$vtable) |
| ) |
| ) |
| |
| ;; CHECK: (func $test-trap (type $4) (param $x i32) (result funcref) |
| ;; CHECK-NEXT: (struct.get $vtable 0 |
| ;; CHECK-NEXT: (select (result (ref null $vtable)) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: (global.get $B$vtable) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test-trap (param $x i32) (result funcref) |
| ;; One arm has a null, which makes the struct.get trap, so we ignore this |
| ;; for now. TODO: handle traps |
| (struct.get $vtable 0 |
| (select |
| (ref.null $vtable) |
| (global.get $B$vtable) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $A$func (type $specific-func) (result i32) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| (func $A$func (type $specific-func) (result i32) |
| ;; Helper for above. |
| (i32.const 1) |
| ) |
| |
| ;; CHECK: (func $B$func (type $specific-func.sub) (result i32) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| (func $B$func (type $specific-func.sub) (result i32) |
| ;; Helper for above. |
| (i32.const 2) |
| ) |
| ) |
| |
| ;; References with nested globals. |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $vtable (sub (struct (field funcref)))) |
| (type $vtable (sub (struct funcref))) |
| |
| ;; CHECK: (type $itable (sub (struct (field (ref $vtable))))) |
| (type $itable (sub (struct (ref $vtable)))) |
| ) |
| |
| ;; CHECK: (global $A$itable (ref $itable) (struct.new $itable |
| ;; CHECK-NEXT: (struct.new $vtable |
| ;; CHECK-NEXT: (ref.func $A$func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: )) |
| (global $A$itable (ref $itable) (struct.new $itable |
| (struct.new $vtable |
| (ref.func $A$func) |
| ) |
| )) |
| |
| ;; CHECK: (global $B$itable (ref $itable) (struct.new $itable |
| ;; CHECK-NEXT: (struct.new $vtable |
| ;; CHECK-NEXT: (ref.func $B$func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: )) |
| (global $B$itable (ref $itable) (struct.new $itable |
| (struct.new $vtable |
| (ref.func $B$func) |
| ) |
| )) |
| |
| ;; CHECK: (func $test-expanded (type $3) (param $x i32) (result funcref) |
| ;; CHECK-NEXT: (select (result (ref $2)) |
| ;; CHECK-NEXT: (ref.func $A$func) |
| ;; CHECK-NEXT: (ref.func $B$func) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test-expanded (param $x i32) (result funcref) |
| ;; Nesting in the global declarations means we cannot precompute the inner |
| ;; struct.get by itself (as that would return the inner struct.new), but |
| ;; when we do the outer struct.get, it all comes together. This is a common |
| ;; pattern in J2Wasm output. |
| (struct.get $vtable 0 |
| (struct.get $itable 0 |
| (select |
| (global.get $A$itable) |
| (global.get $B$itable) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $test-expanded-almost (type $4) (param $x i32) (result anyref) |
| ;; CHECK-NEXT: (struct.get $itable 0 |
| ;; CHECK-NEXT: (select (result (ref $itable)) |
| ;; CHECK-NEXT: (global.get $A$itable) |
| ;; CHECK-NEXT: (global.get $B$itable) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test-expanded-almost (param $x i32) (result anyref) |
| ;; As above, but without the outer struct.get. We get "stuck" with the |
| ;; inner part of the global, as explained there, and fail to optimize here. |
| (struct.get $itable 0 |
| (select |
| (global.get $A$itable) |
| (global.get $B$itable) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $A$func (type $2) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $A$func |
| ;; Helper for above. |
| ) |
| |
| ;; CHECK: (func $B$func (type $2) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $B$func |
| ;; Helper for above. |
| ) |
| ) |