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