blob: 0e64c4aa777ea1d449fc612233a871e161eb5413 [file] [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: (table $0 0 funcref)
;; CHECK: (elem declare func $func-two-params)
;; CHECK: (tag $tag (type $4) (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)
)
)
)
)
)
;; The parent is public, which prevents us from making any field immutable in
;; the child.
(module
;; CHECK: (type $parent (sub (struct (field (mut i32)))))
(type $parent (sub (struct (field (mut i32)))))
;; CHECK: (type $1 (func))
;; CHECK: (type $child (sub $parent (struct (field (mut i32)))))
(type $child (sub $parent (struct (field (mut i32)))))
;; CHECK: (global $global (ref $parent) (struct.new $parent
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: ))
(global $global (ref $parent) (struct.new $parent
(i32.const 0)
))
;; Make the parent public by exporting the global.
;; CHECK: (export "global" (global $global))
(export "global" (global $global))
;; CHECK: (func $func (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new_default $child)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $func
;; Create the child so the type is used. No sets to the fields exist, so
;; in theory all fields could be immutable.
(drop
(struct.new_default $child)
)
)
)
;; $sub has a field we can make immutable. That it does not exist in the super
;; should not confuse us.
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $super (sub (struct)))
(type $super (sub (struct)))
;; CHECK: (type $sub (sub $super (struct (field (ref string)))))
(type $sub (sub $super (struct (field (mut (ref string))))))
)
;; CHECK: (type $2 (func))
;; CHECK: (func $test (type $2)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $sub 0
;; CHECK-NEXT: (struct.new $sub
;; CHECK-NEXT: (string.const "foo")
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; Write and read the field.
(drop
(struct.get $sub 0
(struct.new $sub
(string.const "foo")
)
)
)
)
)
;; As above, but with another type in the middle, $mid, which also contains the
;; field. We can optimize both $mid and $sub.
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $super (sub (struct)))
(type $super (sub (struct)))
;; CHECK: (type $mid (sub $super (struct (field (ref string)))))
(type $mid (sub $super (struct (field (mut (ref string))))))
;; CHECK: (type $sub (sub $mid (struct (field (ref string)))))
(type $sub (sub $mid (struct (field (mut (ref string))))))
)
;; CHECK: (type $3 (func))
;; CHECK: (func $test (type $3)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $sub 0
;; CHECK-NEXT: (struct.new $sub
;; CHECK-NEXT: (string.const "foo")
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $mid 0
;; CHECK-NEXT: (struct.new $mid
;; CHECK-NEXT: (string.const "bar")
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(drop
(struct.get $sub 0
(struct.new $sub
(string.const "foo")
)
)
)
(drop
(struct.get $mid 0
(struct.new $mid
(string.const "bar")
)
)
)
)
)
;; As above, but add another irrelevant field first. We can still optimize the
;; string, but the new mutable i32 must remain mutable, as it has a set.
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $super (sub (struct (field (mut i32)))))
(type $super (sub (struct (field (mut i32)))))
;; CHECK: (type $mid (sub $super (struct (field (mut i32)) (field (ref string)))))
(type $mid (sub $super (struct (field (mut i32)) (field (mut (ref string))))))
;; CHECK: (type $sub (sub $mid (struct (field (mut i32)) (field (ref string)))))
(type $sub (sub $mid (struct (field (mut i32)) (field (mut (ref string))))))
)
;; CHECK: (type $3 (func))
;; CHECK: (func $test (type $3)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $sub 1
;; CHECK-NEXT: (struct.new $sub
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (string.const "foo")
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $mid 1
;; CHECK-NEXT: (struct.new $mid
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: (string.const "bar")
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $super 0
;; CHECK-NEXT: (struct.new $super
;; CHECK-NEXT: (i32.const 98765)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $super 0
;; CHECK-NEXT: (struct.new $super
;; CHECK-NEXT: (i32.const 999999)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(drop
(struct.get $sub 1
(struct.new $sub
(i32.const 42)
(string.const "foo")
)
)
)
(drop
(struct.get $mid 1
(struct.new $mid
(i32.const 1337)
(string.const "bar")
)
)
)
;; A set and get of the first field.
(struct.set $super 0
(struct.new $super
(i32.const 98765)
)
(i32.const 42)
)
(drop
(struct.get $super 0
(struct.new $super
(i32.const 999999)
)
)
)
)
)
;; As above, but without a set of the first field. Now we can optimize both
;; fields.
(module
(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $super (sub (struct (field i32))))
(type $super (sub (struct (field (mut i32)))))
;; CHECK: (type $mid (sub $super (struct (field i32) (field (ref string)))))
(type $mid (sub $super (struct (field (mut i32)) (field (mut (ref string))))))
;; CHECK: (type $sub (sub $mid (struct (field i32) (field (ref string)))))
(type $sub (sub $mid (struct (field (mut i32)) (field (mut (ref string))))))
)
;; CHECK: (type $3 (func))
;; CHECK: (func $test (type $3)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $sub 1
;; CHECK-NEXT: (struct.new $sub
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (string.const "foo")
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $mid 1
;; CHECK-NEXT: (struct.new $mid
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: (string.const "bar")
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $super 0
;; CHECK-NEXT: (struct.new $super
;; CHECK-NEXT: (i32.const 999999)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(drop
(struct.get $sub 1
(struct.new $sub
(i32.const 42)
(string.const "foo")
)
)
)
(drop
(struct.get $mid 1
(struct.new $mid
(i32.const 1337)
(string.const "bar")
)
)
)
;; Only a get of the first field.
(drop
(struct.get $super 0
(struct.new $super
(i32.const 999999)
)
)
)
)
)
;; The super is public, but we can still optimize the field in the sub.
(module
;; CHECK: (type $super (sub (struct)))
(type $super (sub (struct)))
;; CHECK: (rec
;; CHECK-NEXT: (type $sub (sub $super (struct (field stringref))))
(type $sub (sub $super (struct (field (mut stringref)))))
;; CHECK: (type $2 (func))
;; CHECK: (global $global (ref $super) (struct.new_default $super))
(global $global (ref $super) (struct.new_default $super))
;; CHECK: (export "global" (global $global))
(export "global" (global $global))
;; CHECK: (func $test (type $2)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $sub 0
;; CHECK-NEXT: (struct.new $sub
;; CHECK-NEXT: (string.const "foo")
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; Write and read the field.
(drop
(struct.get $sub 0
(struct.new $sub
(string.const "foo")
)
)
)
)
)
;; As above, and now the super has the field as well, preventing optimization.
(module
;; CHECK: (type $super (sub (struct (field (mut stringref)))))
(type $super (sub (struct (field (mut stringref)))))
;; CHECK: (type $sub (sub $super (struct (field (mut stringref)))))
(type $sub (sub $super (struct (field (mut stringref)))))
;; CHECK: (type $2 (func))
;; CHECK: (global $global (ref $super) (struct.new_default $super))
(global $global (ref $super) (struct.new_default $super))
;; CHECK: (export "global" (global $global))
(export "global" (global $global))
;; CHECK: (func $test (type $2)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $sub 0
;; CHECK-NEXT: (struct.new $sub
;; CHECK-NEXT: (string.const "foo")
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; Write and read the field.
(drop
(struct.get $sub 0
(struct.new $sub
(string.const "foo")
)
)
)
)
)
;; Two mutable fields with a chain of three subtypes. The super is public,
;; preventing optimization of the field it has (but not the other; the other
;; is removable anyhow, though, so this just checks for the lack of an error
;; when deciding not to make the fields immutable or not).
(module
;; CHECK: (type $super (sub (struct (field (mut i32)))))
(type $super (sub (struct (field (mut i32)))))
;; CHECK: (rec
;; CHECK-NEXT: (type $mid (sub $super (struct (field (mut i32)))))
(type $mid (sub $super (struct (field (mut i32)) (field (mut f64)))))
;; CHECK: (type $sub (sub $mid (struct (field (mut i32)))))
(type $sub (sub $mid (struct (field (mut i32)) (field (mut f64)))))
;; CHECK: (global $global (ref $super) (struct.new_default $sub))
(global $global (ref $super) (struct.new_default $sub))
;; CHECK: (export "global" (global $global))
(export "global" (global $global))
)