blob: 13b416b1bddd93891f1340937b1542f9f5fd4f92 [file] [log] [blame] [edit]
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
;; RUN: foreach %s %t wasm-opt --gto --closed-world -all -S -o - | filecheck %s
;; (remove-unused-names is added to test fallthrough values without a block
;; name getting in the way)
(module
;; The struct here has three fields, and the second of them has no struct.set
;; which means we can make it immutable.
;; CHECK: (rec
;; CHECK-NEXT: (type $0 (func (param (ref null $struct))))
;; CHECK: (type $two-params (func (param (ref $struct) (ref $struct))))
;; CHECK: (type $2 (func (result (ref null $struct))))
;; CHECK: (type $struct (struct (field (mut funcref)) (field funcref) (field (mut funcref))))
(type $struct (struct (field (mut funcref)) (field (mut funcref)) (field (mut funcref))))
(type $two-params (func (param (ref $struct)) (param (ref $struct))))
;; Test that we update tag types properly.
(table 0 funcref)
;; CHECK: (type $4 (func (param (ref $struct))))
;; CHECK: (type $5 (func (param (ref $struct))))
;; CHECK: (table $0 0 funcref)
;; CHECK: (elem declare func $func-two-params)
;; CHECK: (tag $tag (param (ref $struct)))
(tag $tag (param (ref $struct)))
;; CHECK: (func $func (type $4) (param $x (ref $struct))
;; CHECK-NEXT: (local $temp (ref null $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (ref.null nofunc)
;; CHECK-NEXT: (ref.null nofunc)
;; CHECK-NEXT: (ref.null nofunc)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (ref.null nofunc)
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $struct 2
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (ref.null nofunc)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $temp
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 1
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $func (param $x (ref $struct))
(local $temp (ref null $struct))
;; The presence of a struct.new does not prevent this optimization: we just
;; care about writes using struct.set.
(drop
(struct.new $struct
(ref.null func)
(ref.null func)
(ref.null func)
)
)
(struct.set $struct 0
(local.get $x)
(ref.null func)
)
(struct.set $struct 2
(local.get $x)
(ref.null func)
)
;; Test that local types remain valid after our work (otherwise, we'd get a
;; validation error).
(local.set $temp
(local.get $x)
)
;; Test that struct.get types remain valid after our work.
(drop
(struct.get $struct 0
(local.get $x)
)
)
(drop
(struct.get $struct 1
(local.get $x)
)
)
)
;; CHECK: (func $foo (type $2) (result (ref null $struct))
;; CHECK-NEXT: (try
;; CHECK-NEXT: (do
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $tag
;; CHECK-NEXT: (return
;; CHECK-NEXT: (pop (ref $struct))
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
(func $foo (result (ref null $struct))
;; Use a tag so that we test proper updating of its type after making
;; changes.
(try
(do
(nop)
)
(catch $tag
(return
(pop (ref $struct))
)
)
)
(ref.null $struct)
)
;; CHECK: (func $func-two-params (type $two-params) (param $x (ref $struct)) (param $y (ref $struct))
;; CHECK-NEXT: (local $z (ref null $two-params))
;; CHECK-NEXT: (local.set $z
;; CHECK-NEXT: (ref.func $func-two-params)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_indirect $0 (type $two-params)
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (local.get $y)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $func-two-params (param $x (ref $struct)) (param $y (ref $struct))
;; This function has two params, which means a tuple type is used for its
;; signature, which we must also update. To verify the update is correct,
;; assign it to a local.
(local $z (ref null $two-params))
(local.set $z
(ref.func $func-two-params)
)
;; Also check that a call_indirect still validates after the rewriting.
(call_indirect (type $two-params)
(local.get $x)
(local.get $y)
(i32.const 0)
)
)
;; CHECK: (func $field-keepalive (type $0) (param $struct (ref null $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 2
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $field-keepalive (param $struct (ref null $struct))
;; --gto will remove fields that are not read from, so add reads to any
;; that don't already have them.
(drop (struct.get $struct 2 (local.get $struct)))
)
)
(module
;; Test recursion between structs where we only modify one. Specifically $B
;; has no writes to either of its fields.
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $B (struct (field (ref null $A)) (field f64)))
;; CHECK: (type $1 (func (param (ref null $A) (ref null $B))))
;; CHECK: (type $A (struct (field (mut (ref null $B))) (field (mut i32))))
(type $A (struct (field (mut (ref null $B))) (field (mut i32)) ))
(type $B (struct (field (mut (ref null $A))) (field (mut f64)) ))
)
;; CHECK: (type $3 (func (param (ref $A))))
;; CHECK: (func $func (type $3) (param $x (ref $A))
;; CHECK-NEXT: (struct.set $A 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $A 1
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $func (param $x (ref $A))
(struct.set $A 0
(local.get $x)
(ref.null $B)
)
(struct.set $A 1
(local.get $x)
(i32.const 20)
)
)
;; CHECK: (func $field-keepalive (type $1) (param $A (ref null $A)) (param $B (ref null $B))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $A 0
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $A 1
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $B 0
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $B 1
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $field-keepalive (param $A (ref null $A)) (param $B (ref null $B))
(drop (struct.get $A 0 (local.get $A)))
(drop (struct.get $A 1 (local.get $A)))
(drop (struct.get $B 0 (local.get $B)))
(drop (struct.get $B 1 (local.get $B)))
)
)
(module
;; As before, but flipped so that $A's fields can become immutable.
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $A (struct (field (ref null $B)) (field i32)))
;; CHECK: (type $1 (func (param (ref null $A) (ref null $B))))
;; CHECK: (type $B (struct (field (mut (ref null $A))) (field (mut f64))))
(type $B (struct (field (mut (ref null $A))) (field (mut f64)) ))
(type $A (struct (field (mut (ref null $B))) (field (mut i32)) ))
)
;; CHECK: (type $3 (func (param (ref $B))))
;; CHECK: (func $func (type $3) (param $x (ref $B))
;; CHECK-NEXT: (struct.set $B 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $B 1
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (f64.const 3.14159)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $func (param $x (ref $B))
(struct.set $B 0
(local.get $x)
(ref.null $A)
)
(struct.set $B 1
(local.get $x)
(f64.const 3.14159)
)
)
;; CHECK: (func $field-keepalive (type $1) (param $A (ref null $A)) (param $B (ref null $B))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $A 0
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $A 1
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $B 0
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $B 1
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $field-keepalive (param $A (ref null $A)) (param $B (ref null $B))
(drop (struct.get $A 0 (local.get $A)))
(drop (struct.get $A 1 (local.get $A)))
(drop (struct.get $B 0 (local.get $B)))
(drop (struct.get $B 1 (local.get $B)))
)
)
(module
;; As before, but now one field in each can become immutable.
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $0 (func (param (ref null $A) (ref null $B))))
;; CHECK: (type $B (struct (field (ref null $A)) (field (mut f64))))
(type $B (struct (field (mut (ref null $A))) (field (mut f64)) ))
;; CHECK: (type $A (struct (field (mut (ref null $B))) (field i32)))
(type $A (struct (field (mut (ref null $B))) (field (mut i32)) ))
)
;; CHECK: (type $3 (func (param (ref $A) (ref $B))))
;; CHECK: (func $func (type $3) (param $x (ref $A)) (param $y (ref $B))
;; CHECK-NEXT: (struct.set $A 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $B 1
;; CHECK-NEXT: (local.get $y)
;; CHECK-NEXT: (f64.const 3.14159)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $func (param $x (ref $A)) (param $y (ref $B))
(struct.set $A 0
(local.get $x)
(ref.null $B)
)
(struct.set $B 1
(local.get $y)
(f64.const 3.14159)
)
)
;; CHECK: (func $field-keepalive (type $0) (param $A (ref null $A)) (param $B (ref null $B))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $A 0
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $A 1
;; CHECK-NEXT: (local.get $A)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $B 0
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $B 1
;; CHECK-NEXT: (local.get $B)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $field-keepalive (param $A (ref null $A)) (param $B (ref null $B))
(drop (struct.get $A 0 (local.get $A)))
(drop (struct.get $A 1 (local.get $A)))
(drop (struct.get $B 0 (local.get $B)))
(drop (struct.get $B 1 (local.get $B)))
)
)
(module
;; Field #0 is already immutable.
;; Field #1 is mutable and can become so.
;; Field #2 is mutable and must remain so.
;; CHECK: (rec
;; CHECK-NEXT: (type $0 (func (param (ref null $struct))))
;; CHECK: (type $struct (struct (field i32) (field i32) (field (mut i32))))
(type $struct (struct (field i32) (field (mut i32)) (field (mut i32))))
;; CHECK: (type $2 (func (param (ref $struct))))
;; CHECK: (func $func (type $2) (param $x (ref $struct))
;; CHECK-NEXT: (struct.set $struct 2
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $func (param $x (ref $struct))
(struct.set $struct 2
(local.get $x)
(i32.const 1)
)
)
;; CHECK: (func $field-keepalive (type $0) (param $struct (ref null $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 0
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 1
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 2
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $field-keepalive (param $struct (ref null $struct))
(drop (struct.get $struct 0 (local.get $struct)))
(drop (struct.get $struct 1 (local.get $struct)))
(drop (struct.get $struct 2 (local.get $struct)))
)
)
(module
;; Subtyping. Without a write in either supertype or subtype, we can
;; optimize the field to be immutable.
;; CHECK: (rec
;; CHECK-NEXT: (type $super (sub (struct (field i32))))
(type $super (sub (struct (field (mut i32)))))
;; CHECK: (type $1 (func (param (ref null $super) (ref null $sub))))
;; CHECK: (type $sub (sub $super (struct (field i32))))
(type $sub (sub $super (struct (field (mut i32)))))
;; CHECK: (type $3 (func))
;; CHECK: (func $func (type $3)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $super
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $sub
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $func
;; The presence of struct.new do not prevent us optimizing
(drop
(struct.new $super
(i32.const 1)
)
)
(drop
(struct.new $sub
(i32.const 1)
)
)
)
;; CHECK: (func $field-keepalive (type $1) (param $super (ref null $super)) (param $sub (ref null $sub))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $super 0
;; CHECK-NEXT: (local.get $super)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $sub 0
;; CHECK-NEXT: (local.get $sub)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $field-keepalive (param $super (ref null $super)) (param $sub (ref null $sub))
(drop (struct.get $super 0 (local.get $super)))
(drop (struct.get $sub 0 (local.get $sub)))
)
)
(module
;; As above, but add a write in the super, which prevents optimization.
;; CHECK: (type $super (sub (struct (field (mut i32)))))
(type $super (sub (struct (field (mut i32)))))
;; CHECK: (type $sub (sub $super (struct (field (mut i32)))))
(type $sub (sub $super (struct (field (mut i32)))))
;; CHECK: (type $2 (func (param (ref $super))))
;; CHECK: (type $3 (func (param (ref null $super) (ref null $sub))))
;; CHECK: (func $func (type $2) (param $x (ref $super))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $super
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $sub
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $super 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $func (param $x (ref $super))
;; The presence of struct.new do not prevent us optimizing
(drop
(struct.new $super
(i32.const 1)
)
)
(drop
(struct.new $sub
(i32.const 1)
)
)
(struct.set $super 0
(local.get $x)
(i32.const 2)
)
)
;; CHECK: (func $field-keepalive (type $3) (param $super (ref null $super)) (param $sub (ref null $sub))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $super 0
;; CHECK-NEXT: (local.get $super)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $sub 0
;; CHECK-NEXT: (local.get $sub)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $field-keepalive (param $super (ref null $super)) (param $sub (ref null $sub))
(drop (struct.get $super 0 (local.get $super)))
(drop (struct.get $sub 0 (local.get $sub)))
)
)
(module
;; As above, but add a write in the sub, which prevents optimization.
;; CHECK: (type $super (sub (struct (field (mut i32)))))
(type $super (sub (struct (field (mut i32)))))
;; CHECK: (type $sub (sub $super (struct (field (mut i32)))))
(type $sub (sub $super (struct (field (mut i32)))))
;; CHECK: (type $2 (func (param (ref $sub))))
;; CHECK: (type $3 (func (param (ref null $super) (ref null $sub))))
;; CHECK: (func $func (type $2) (param $x (ref $sub))
;; CHECK-NEXT: (struct.set $sub 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $func (param $x (ref $sub))
(struct.set $sub 0
(local.get $x)
(i32.const 2)
)
)
;; CHECK: (func $field-keepalive (type $3) (param $super (ref null $super)) (param $sub (ref null $sub))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $super 0
;; CHECK-NEXT: (local.get $super)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $sub 0
;; CHECK-NEXT: (local.get $sub)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $field-keepalive (param $super (ref null $super)) (param $sub (ref null $sub))
(drop (struct.get $super 0 (local.get $super)))
(drop (struct.get $sub 0 (local.get $sub)))
)
)
;; This pass does not refine array types yet. Verify that we do not. This is
;; particularly important for array of i8 and i6, which we special-case as they
;; are used for string interop even in closed world. For now, however, we do not
;; optimize even arrays of i32.
;;
;; Nothing should change in these array types. They have no writes, but they'll
;; stay mutable. Also, this test verifies that we can even validate this module
;; in closed world, even though it contains imports of i8 and i16 arrays.
;;
;; The test also verifies that while we refine the mutability of a struct type,
;; which causes us to rewrite types, that we keep the i8 and i16 arrays in
;; their own size-1 rec groups by themselves, unmodified. The i32 array will be
;; moved into a new big rec group, together with the struct type (that is also
;; refined to be immutable).
(module
;; CHECK: (type $array8 (array (mut i8)))
(type $array8 (array (mut i8)))
;; CHECK: (type $array16 (array (mut i16)))
(type $array16 (array (mut i16)))
;; CHECK: (rec
;; CHECK-NEXT: (type $struct (struct (field funcref)))
;; CHECK: (type $array32 (array (mut i32)))
(type $array32 (array (mut i32)))
(type $struct (struct (field (mut funcref))))
;; CHECK: (type $4 (func (param funcref)))
;; CHECK: (import "a" "b" (global $i8 (ref $array8)))
(import "a" "b" (global $i8 (ref $array8)))
;; CHECK: (import "a" "c" (global $i16 (ref $array16)))
(import "a" "c" (global $i16 (ref $array16)))
;; CHECK: (func $use (type $4) (param $funcref funcref)
;; CHECK-NEXT: (local $array8 (ref $array8))
;; CHECK-NEXT: (local $array16 (ref $array16))
;; CHECK-NEXT: (local $array32 (ref $array32))
;; CHECK-NEXT: (local $struct (ref $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 0
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (local.get $funcref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $use (param $funcref funcref)
(local $array8 (ref $array8))
(local $array16 (ref $array16))
(local $array32 (ref $array32))
(local $struct (ref $struct))
(drop
(struct.get $struct 0
(struct.new $struct
(local.get $funcref)
)
)
)
)
)