| ;; 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 (exact $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. | |
| ) | |
| ) |