blob: 5d530e23e25882bfb0419cf3ccef81f28de18823 [file] [log] [blame] [edit]
;; 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.
)
)