blob: bd731d89150cd69549ed39da5fed0bab42abc6a0 [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 --remove-unused-names --gufa -all -S -o - | filecheck %s
;; (remove-unused-names is added to test fallthrough values without a block
;; name getting in the way)
;; This is almost identical to cfp.wast, and is meant to facilitate comparisons
;; between the passes - in particular, gufa should do everything cfp can do,
;; although it may do it differently. Changes include:
;;
;; * Tests must avoid things gufa optimizes away that would make the test
;; irrelevant. In particular, parameters to functions that are never called
;; will be turned to unreachable by gufa, so instead make those calls to
;; imports. Gufa will also realize that passing ref.null as the reference of
;; a struct.get/set will trap, so we must actually allocate something.
;; * Gufa optimizes in a more general way. Cfp will turn a struct.get whose
;; value it infers into a ref.as_non_null (to preserve the trap if the ref is
;; null) followed by the constant. Gufa has no special handling for
;; struct.get, so it will use its normal pattern there, of a drop of the
;; struct.get followed by the constant. (Other passes can remove the
;; dropped operation, like vacuum in trapsNeverHappen mode).
;; * Gufa's more general optimizations can remove more unreachable code, as it
;; checks for effects (and removes effectless code).
;;
;; This file could also run cfp in addition to gufa, but the aforementioned
;; changes cause cfp to behave differently in some cases, which could lead to
;; more confusion than benefit - the reader would not be able to compare the two
;; outputs and see cfp as "correct" which gufa should match.
;;
;; Note that there is some overlap with gufa-refs.wast in some places, but
;; intentionally no tests are removed here compared to cfp.wast, to make it
;; simple to map the original cfp tests to their ported versions here.
(module
(type $struct (struct i32))
;; CHECK: (type $0 (func))
;; CHECK: (func $impossible-get (type $0)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $impossible-get
(drop
;; This type is never created, so a get is impossible, and we will trap
;; anyhow. So we can turn this into an unreachable.
(struct.get $struct 0
(ref.null $struct)
)
)
)
)
(module
(type $struct (struct i64))
;; CHECK: (type $0 (func))
;; CHECK: (func $test (type $0)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i64.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; The only place this type is created is with a default value, and so we
;; can optimize the get into a constant (note that no drop of the
;; ref is needed: the optimizer can see that the struct.get cannot trap, as
;; its reference is non-nullable).
(drop
(struct.get $struct 0
(struct.new_default $struct)
)
)
)
)
(module
(type $struct (struct f32))
;; CHECK: (type $0 (func))
;; CHECK: (func $test (type $0)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (f32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; The only place this type is created is with a constant value, and so we
;; can optimize to a constant, the same as above (but the constant was
;; passed in, as opposed to being a default value as in the last testcase).
(drop
(struct.get $struct 0
(struct.new $struct
(f32.const 42)
)
)
)
)
)
(module
;; CHECK: (type $struct (struct (field f32)))
(type $struct (struct f32))
;; CHECK: (type $1 (func (result f32)))
;; CHECK: (type $2 (func))
;; CHECK: (import "a" "b" (func $import (type $1) (result f32)))
(import "a" "b" (func $import (result f32)))
;; CHECK: (func $test (type $2)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 0
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; The value given is not a constant, and so we cannot optimize.
(drop
(struct.get $struct 0
(struct.new $struct
(call $import)
)
)
)
)
)
;; Create in one function, get in another. The 10 should be forwarded to the
;; get.
(module
;; CHECK: (type $struct (struct (field i32)))
(type $struct (struct i32))
;; CHECK: (type $1 (func (result (ref $struct))))
;; CHECK: (type $2 (func))
;; CHECK: (func $create (type $1) (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create (result (ref $struct))
(struct.new $struct
(i32.const 10)
)
)
;; CHECK: (func $get (type $2)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get
;; The reference will be dropped here, and not removed entirely, because
;; the optimizer thinks it might have side effects (since it has a call).
;; But the forwarded value, 10, is applied after that drop.
(drop
(struct.get $struct 0
(call $create)
)
)
)
)
;; As before, but with the order of functions reversed to check for any ordering
;; issues.
(module
;; CHECK: (type $struct (struct (field i32)))
(type $struct (struct i32))
;; CHECK: (type $1 (func))
;; CHECK: (type $2 (func (result (ref $struct))))
;; CHECK: (func $get (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get
(drop
(struct.get $struct 0
(call $create)
)
)
)
;; CHECK: (func $create (type $2) (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create (result (ref $struct))
(struct.new $struct
(i32.const 10)
)
)
)
;; Different values assigned in the same function, in different struct.news,
;; so we cannot optimize the struct.get away.
(module
;; CHECK: (type $struct (struct (field f32)))
(type $struct (struct f32))
;; CHECK: (type $1 (func))
;; CHECK: (func $test (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (f32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 0
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (f32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(drop
(struct.new $struct
(f32.const 42)
)
)
;; (A better analysis could see that the first struct.new is dropped and its
;; value cannot reach this struct.get.)
(drop
(struct.get $struct 0
(struct.new $struct
(f32.const 1337)
)
)
)
)
)
;; Different values assigned in different functions, and one is a struct.set.
(module
;; CHECK: (type $struct (struct (field (mut f32))))
(type $struct (struct (mut f32)))
;; CHECK: (type $1 (func))
;; CHECK: (type $2 (func (result (ref $struct))))
;; CHECK: (func $create (type $2) (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (f32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create (result (ref $struct))
(struct.new $struct
(f32.const 42)
)
)
;; CHECK: (func $set (type $1)
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: (f32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $set
(struct.set $struct 0
(call $create)
(f32.const 1337)
)
)
;; CHECK: (func $get (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 0
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get
;; (A better analysis could see that only $create's value can reach here.)
(drop
(struct.get $struct 0
(call $create)
)
)
)
)
;; As the last testcase, but the values happen to coincide, so we can optimize
;; the get into a constant.
(module
;; CHECK: (type $struct (struct (field (mut f32))))
(type $struct (struct (mut f32)))
;; CHECK: (type $1 (func))
;; CHECK: (type $2 (func (result (ref $struct))))
;; CHECK: (func $create (type $2) (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (f32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create (result (ref $struct))
(struct.new $struct
(f32.const 42)
)
)
;; CHECK: (func $set (type $1)
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: (f32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $set
(struct.set $struct 0
(call $create)
(f32.const 42) ;; The last testcase had 1337 here.
)
)
;; CHECK: (func $get (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result f32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: (f32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get
(drop
(struct.get $struct 0
(call $create)
)
)
)
)
;; Check that we look into the fallthrough value that is assigned.
(module
;; CHECK: (type $struct (struct (field (mut f32))))
(type $struct (struct (mut f32)))
;; CHECK: (type $1 (func))
;; CHECK: (type $2 (func (result i32)))
;; CHECK: (type $3 (func (result (ref $struct))))
;; CHECK: (import "a" "b" (func $import (type $2) (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (func $create (type $3) (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (f32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create (result (ref $struct))
(struct.new $struct
;; Fall though a 42. The block can be optimized to a constant.
(block $named (result f32)
(nop)
(f32.const 42)
)
)
)
;; CHECK: (func $set (type $1)
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: (block (result f32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (if (result f32)
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: (then
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (else
;; CHECK-NEXT: (f32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (f32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $set
(struct.set $struct 0
(call $create)
;; Fall though a 42 via an if.
(if (result f32)
(call $import)
(then
(unreachable)
)
(else
(f32.const 42)
)
)
)
)
;; CHECK: (func $get (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result f32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: (f32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get
;; This can be inferred to be 42 since both the new and the set write that
;; value.
(drop
(struct.get $struct 0
(call $create)
)
)
)
)
;; Test a function reference instead of a number.
(module
(type $struct (struct funcref))
;; CHECK: (type $0 (func))
;; CHECK: (elem declare func $test)
;; CHECK: (func $test (type $0)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.func $test)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(drop
(struct.get $struct 0
(struct.new $struct
(ref.func $test)
)
)
)
)
)
;; Test for unreachable creations, sets, and gets.
(module
(type $struct (struct (mut i32)))
;; CHECK: (type $0 (func))
;; CHECK: (func $test (type $0)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block ;; (replaces unreachable StructNew we can't emit)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (block ;; (replaces unreachable StructSet we can't emit)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(drop
(struct.new $struct
(i32.const 10)
(unreachable)
)
)
(struct.set $struct 0
(struct.get $struct 0
(unreachable)
)
(i32.const 20)
)
)
)
;; Subtyping: Create a supertype and get a subtype. As we never create a
;; subtype, the get must trap anyhow (the reference it receives can
;; only be null in this closed world).
(module
;; CHECK: (type $struct (sub (struct (field i32))))
(type $struct (sub (struct i32)))
(type $substruct (sub $struct (struct i32)))
;; CHECK: (type $1 (func (result (ref $struct))))
;; CHECK: (type $2 (func))
;; CHECK: (func $create (type $1) (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create (result (ref $struct))
(struct.new $struct
(i32.const 10)
)
)
;; CHECK: (func $get (type $2)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get
;; As the get must trap, we can optimize to an unreachable here.
(drop
(struct.get $substruct 0
(ref.cast (ref $substruct)
(call $create)
)
)
)
)
)
;; As above, but in addition to a new of $struct also add a set. The set,
;; however, cannot write to the subtype, so we still know that any reads from
;; the subtype must trap.
(module
;; CHECK: (type $struct (sub (struct (field (mut i32)))))
(type $struct (sub (struct (mut i32))))
(type $substruct (sub $struct (struct (mut i32))))
;; CHECK: (type $1 (func))
;; CHECK: (type $2 (func (result (ref $struct))))
;; CHECK: (func $create (type $2) (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create (result (ref $struct))
(struct.new $struct
(i32.const 10)
)
)
;; CHECK: (func $set (type $1)
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $set
(struct.set $struct 0
(call $create)
(i32.const 10)
)
)
;; CHECK: (func $get (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get
(drop
(struct.get $substruct 0
(ref.cast (ref $substruct)
(call $create)
)
)
)
)
)
;; As above, pass the created supertype through a local and a cast on the way
;; to a read of the subtype. Still, no actual instance of the subtype can
;; appear in the get, so we can optimize to an unreachable.
(module
;; CHECK: (type $struct (sub (struct (field (mut i32)))))
(type $struct (sub (struct (mut i32))))
(type $substruct (sub $struct (struct (mut i32))))
;; CHECK: (type $1 (func))
;; CHECK: (func $test (type $1)
;; CHECK-NEXT: (local $ref (ref null $struct))
;; CHECK-NEXT: (local.set $ref
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(local $ref (ref null $struct))
(local.set $ref
(struct.new $struct
(i32.const 10)
)
)
(struct.set $struct 0
(local.get $ref)
(i32.const 10)
)
(drop
;; This must trap, so we can add an unreachable.
(struct.get $substruct 0
;; Only a null can pass through here, as the cast would not allow a ref
;; to $struct. But no null is possible since the local gets written a
;; non-null value before we get here, so we can optimize this to an
;; unreachable.
(ref.cast (ref null $substruct)
(local.get $ref)
)
)
)
)
)
;; Subtyping: Create a subtype and get a supertype. The get must receive a
;; reference to the subtype and so we can infer the value of the get.
(module
(type $struct (sub (struct i32)))
(type $substruct (sub $struct (struct i32 f64)))
;; CHECK: (type $0 (func))
;; CHECK: (func $test (type $0)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(drop
(struct.get $struct 0
(struct.new $substruct
(i32.const 10)
(f64.const 3.14159)
)
)
)
)
)
;; Subtyping: Create both a subtype and a supertype, with identical constants
;; for the shared field, and get the supertype.
(module
;; CHECK: (type $struct (sub (struct (field i32))))
(type $struct (sub (struct i32)))
;; CHECK: (type $1 (func (result i32)))
;; CHECK: (type $2 (func))
;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64))))
(type $substruct (sub $struct (struct i32 f64)))
;; CHECK: (import "a" "b" (func $import (type $1) (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (func $test (type $2)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (select (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.new $substruct
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: (f64.const 3.14159)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; We can infer the value here must be 10.
(drop
(struct.get $struct 0
(select
(struct.new $struct
(i32.const 10)
)
(struct.new $substruct
(i32.const 10)
(f64.const 3.14159)
)
(call $import)
)
)
)
)
)
;; Subtyping: Create both a subtype and a supertype, with different constants
;; for the shared field, preventing optimization, as a get of the
;; supertype may receive an instance of the subtype.
(module
;; CHECK: (type $struct (sub (struct (field i32))))
(type $struct (sub (struct i32)))
;; CHECK: (type $1 (func (result i32)))
;; CHECK: (type $2 (func))
;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64))))
(type $substruct (sub $struct (struct i32 f64)))
;; CHECK: (import "a" "b" (func $import (type $1) (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (func $test (type $2)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 0
;; CHECK-NEXT: (select (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.new $substruct
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: (f64.const 3.14159)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(drop
(struct.get $struct 0
(select
(struct.new $struct
(i32.const 10)
)
(struct.new $substruct
(i32.const 20) ;; this constant changed
(f64.const 3.14159)
)
(call $import)
)
)
)
)
)
;; Subtyping: Create both a subtype and a supertype, with different constants
;; for the shared field, but get from the subtype. The field is
;; shared between the types, but we only create the subtype with
;; one value, so we can optimize.
(module
;; CHECK: (type $struct (sub (struct (field i32))))
(type $struct (sub (struct i32)))
;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64))))
(type $substruct (sub $struct (struct i32 f64)))
;; CHECK: (type $2 (func (result i32)))
;; CHECK: (type $3 (func))
;; CHECK: (import "a" "b" (func $import (type $2) (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (func $test (type $3)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $substruct)
;; CHECK-NEXT: (select (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.new $substruct
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: (f64.const 3.14159)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(drop
(struct.get $struct 0
;; This cast is added, ensuring only a $substruct can reach the get.
(ref.cast (ref $substruct)
(select
(struct.new $struct
(i32.const 10)
)
(struct.new $substruct
(i32.const 20)
(f64.const 3.14159)
)
(call $import)
)
)
)
)
)
)
;; As above, but add a set of $struct. The set prevents the optimization.
(module
;; CHECK: (type $struct (sub (struct (field (mut i32)))))
(type $struct (sub (struct (mut i32))))
;; CHECK: (type $substruct (sub $struct (struct (field (mut i32)) (field f64))))
(type $substruct (sub $struct (struct (mut i32) f64)))
;; CHECK: (type $2 (func (result i32)))
;; CHECK: (type $3 (func))
;; CHECK: (import "a" "b" (func $import (type $2) (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (func $test (type $3)
;; CHECK-NEXT: (local $ref (ref null $struct))
;; CHECK-NEXT: (local.set $ref
;; CHECK-NEXT: (select (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.new $substruct
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: (f64.const 3.14159)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $substruct 0
;; CHECK-NEXT: (ref.cast (ref $substruct)
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(local $ref (ref null $struct))
(local.set $ref
(select
(struct.new $struct
(i32.const 10)
)
(struct.new $substruct
(i32.const 20)
(f64.const 3.14159)
)
(call $import)
)
)
;; This set is added. Even though the type is the super, this may write to
;; the child, and so we cannot optimize.
(struct.set $struct 0
(local.get $ref)
(i32.const 10)
)
(drop
(struct.get $substruct 0
;; This cast will be refined to be non-nullable, as the LocalGraph
;; analysis will show that it must be so.
(ref.cast (ref null $substruct)
(local.get $ref)
)
)
)
)
)
;; As above, but now the constant in the set agrees with the substruct value,
;; so we can optimize.
(module
;; CHECK: (type $struct (sub (struct (field (mut i32)))))
(type $struct (sub (struct (mut i32))))
;; CHECK: (type $substruct (sub $struct (struct (field (mut i32)) (field f64))))
(type $substruct (sub $struct (struct (mut i32) f64)))
;; CHECK: (type $2 (func (result i32)))
;; CHECK: (type $3 (func))
;; CHECK: (import "a" "b" (func $import (type $2) (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (func $test (type $3)
;; CHECK-NEXT: (local $ref (ref null $struct))
;; CHECK-NEXT: (local.set $ref
;; CHECK-NEXT: (select (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.new $substruct
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: (f64.const 3.14159)
;; CHECK-NEXT: )
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (ref.cast (ref $substruct)
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(local $ref (ref null $struct))
(local.set $ref
(select
(struct.new $struct
(i32.const 10)
)
(struct.new $substruct
(i32.const 20)
(f64.const 3.14159)
)
(call $import)
)
)
(struct.set $struct 0
(local.get $ref)
;; This now writes the same value as in the $substruct already has, 20, so
;; we can optimize the get below (which must contain a $substruct).
(i32.const 20)
)
(drop
(struct.get $substruct 0
;; This cast will be refined to be non-nullable, as the LocalGraph
;; analysis will show that it must be so. After that, the dropped
;; struct.get can be removed as it has no side effects (the only
;; possible effect was a trap on null).
(ref.cast (ref null $substruct)
(local.get $ref)
)
)
)
)
)
;; Multi-level subtyping, check that we propagate not just to the immediate
;; supertype but all the way as needed.
(module
;; CHECK: (type $struct1 (sub (struct (field i32))))
(type $struct1 (sub (struct i32)))
;; CHECK: (type $struct2 (sub $struct1 (struct (field i32) (field f64))))
(type $struct2 (sub $struct1 (struct i32 f64)))
;; CHECK: (type $struct3 (sub $struct2 (struct (field i32) (field f64) (field anyref))))
(type $struct3 (sub $struct2 (struct i32 f64 anyref)))
;; CHECK: (type $3 (func (result (ref $struct3))))
;; CHECK: (type $4 (func))
;; CHECK: (func $create (type $3) (result (ref $struct3))
;; CHECK-NEXT: (struct.new $struct3
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: (f64.const 3.14159)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create (result (ref $struct3))
(struct.new $struct3
(i32.const 20)
(f64.const 3.14159)
(ref.null any)
)
)
;; CHECK: (func $get (type $4)
;; CHECK-NEXT: (local $ref (ref null $struct3))
;; CHECK-NEXT: (local.set $ref
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct3 0
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct3 0
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result f64)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct3 1
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (f64.const 3.14159)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct3 0
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result f64)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct3 1
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (f64.const 3.14159)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result nullref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct3 2
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get
(local $ref (ref null $struct3))
(local.set $ref
(call $create)
)
;; Get field 0 from $struct1. This can be optimized to a constant since
;; we only ever created an instance of struct3 with a constant there - the
;; reference must point to a $struct3. The same happens in all the other
;; gets below as well, all optimize to constants.
(drop
(struct.get $struct1 0
(local.get $ref)
)
)
;; Get both fields of $struct2.
(drop
(struct.get $struct2 0
(local.get $ref)
)
)
(drop
(struct.get $struct2 1
(local.get $ref)
)
)
;; Get all 3 fields of $struct3
(drop
(struct.get $struct3 0
(local.get $ref)
)
)
(drop
(struct.get $struct3 1
(local.get $ref)
)
)
(drop
(struct.get $struct3 2
(local.get $ref)
)
)
)
)
;; Multi-level subtyping with conflicts. The even-numbered fields will get
;; different values in the sub-most type. Create the top and bottom types, but
;; not the middle one.
(module
;; CHECK: (type $struct1 (sub (struct (field i32) (field i32))))
(type $struct1 (sub (struct i32 i32)))
;; CHECK: (type $1 (func))
;; CHECK: (type $struct2 (sub $struct1 (struct (field i32) (field i32) (field f64) (field f64))))
(type $struct2 (sub $struct1 (struct i32 i32 f64 f64)))
;; CHECK: (type $struct3 (sub $struct2 (struct (field i32) (field i32) (field f64) (field f64) (field anyref) (field anyref))))
(type $struct3 (sub $struct2 (struct i32 i32 f64 f64 anyref anyref)))
;; CHECK: (type $4 (func (result anyref)))
;; CHECK: (type $5 (func (result (ref $struct1))))
;; CHECK: (type $6 (func (result (ref $struct3))))
;; CHECK: (import "a" "b" (func $import (type $4) (result anyref)))
(import "a" "b" (func $import (result anyref)))
;; CHECK: (func $create1 (type $5) (result (ref $struct1))
;; CHECK-NEXT: (struct.new $struct1
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create1 (result (ref $struct1))
(struct.new $struct1
(i32.const 10)
(i32.const 20)
)
)
;; CHECK: (func $create3 (type $6) (result (ref $struct3))
;; CHECK-NEXT: (struct.new $struct3
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: (i32.const 999)
;; CHECK-NEXT: (f64.const 2.71828)
;; CHECK-NEXT: (f64.const 9.9999999)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create3 (result (ref $struct3))
(struct.new $struct3
(i32.const 10)
(i32.const 999) ;; use a different value here
(f64.const 2.71828)
(f64.const 9.9999999)
(ref.null any)
(call $import) ;; use an unknown value here, which can never be
;; optimized.
)
)
;; CHECK: (func $get-1 (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get-1
;; Get all the fields of all the structs. First, create $struct1 and get
;; its fields. Even though there are subtypes with different fields for some
;; of them, we can optimize these using exact type info, as this must be a
;; $struct1 and nothing else.
(drop
(struct.get $struct1 0
(call $create1)
)
)
(drop
(struct.get $struct1 1
(call $create1)
)
)
)
;; CHECK: (func $get-2 (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 999)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result f64)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: )
;; CHECK-NEXT: (f64.const 2.71828)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result f64)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: )
;; CHECK-NEXT: (f64.const 9.9999999)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get-2
;; $struct2 is never created, instead create a $struct3. We can optimize,
;; since $struct1's values are not relevant and cannot confuse us.
;; trap.
(drop
(struct.get $struct2 0
(call $create3)
)
)
(drop
(struct.get $struct2 1
(call $create3)
)
)
(drop
(struct.get $struct2 2
(call $create3)
)
)
(drop
(struct.get $struct2 3
(call $create3)
)
)
)
;; CHECK: (func $get-3 (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 999)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result f64)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: )
;; CHECK-NEXT: (f64.const 2.71828)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result f64)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: )
;; CHECK-NEXT: (f64.const 9.9999999)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result nullref)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct3 5
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get-3
;; We can optimize all these (where the field is constant).
(drop
(struct.get $struct3 0
(call $create3)
)
)
(drop
(struct.get $struct3 1
(call $create3)
)
)
(drop
(struct.get $struct3 2
(call $create3)
)
)
(drop
(struct.get $struct3 3
(call $create3)
)
)
(drop
(struct.get $struct3 4
(call $create3)
)
)
(drop
(struct.get $struct3 5
(call $create3)
)
)
)
)
;; Multi-level subtyping with a different value in the middle of the chain.
(module
;; CHECK: (type $struct1 (sub (struct (field (mut i32)))))
(type $struct1 (sub (struct (mut i32))))
;; CHECK: (type $struct2 (sub $struct1 (struct (field (mut i32)) (field f64))))
(type $struct2 (sub $struct1 (struct (mut i32) f64)))
;; CHECK: (type $2 (func))
;; CHECK: (type $struct3 (sub $struct2 (struct (field (mut i32)) (field f64) (field anyref))))
(type $struct3 (sub $struct2 (struct (mut i32) f64 anyref)))
;; CHECK: (type $4 (func (result i32)))
;; CHECK: (type $5 (func (result (ref $struct1))))
;; CHECK: (type $6 (func (result (ref $struct2))))
;; CHECK: (type $7 (func (result (ref $struct3))))
;; CHECK: (import "a" "b" (func $import (type $4) (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (func $create1 (type $5) (result (ref $struct1))
;; CHECK-NEXT: (struct.new $struct1
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create1 (result (ref $struct1))
(struct.new $struct1
(i32.const 10)
)
)
;; CHECK: (func $create2 (type $6) (result (ref $struct2))
;; CHECK-NEXT: (struct.new $struct2
;; CHECK-NEXT: (i32.const 9999)
;; CHECK-NEXT: (f64.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create2 (result (ref $struct2))
(struct.new $struct2
(i32.const 9999) ;; use a different value here
(f64.const 0)
)
)
;; CHECK: (func $create3 (type $7) (result (ref $struct3))
;; CHECK-NEXT: (struct.new $struct3
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: (f64.const 0)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create3 (result (ref $struct3))
(struct.new $struct3
(i32.const 10)
(f64.const 0)
(ref.null any)
)
)
;; CHECK: (func $get-precise (type $2)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 9999)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get-precise
;; Get field 0 in all the types. We know precisely what the type is in each
;; case here, so we can optimize all of these.
(drop
(struct.get $struct1 0
(call $create1)
)
)
(drop
(struct.get $struct2 0
(call $create2)
)
)
(drop
(struct.get $struct3 0
(call $create3)
)
)
)
;; CHECK: (func $get-imprecise-1 (type $2)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (select (result (ref $struct1))
;; CHECK-NEXT: (call $create1)
;; CHECK-NEXT: (call $create1)
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct1 0
;; CHECK-NEXT: (select (result (ref $struct1))
;; CHECK-NEXT: (call $create1)
;; CHECK-NEXT: (call $create2)
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct1 0
;; CHECK-NEXT: (select (result (ref $struct1))
;; CHECK-NEXT: (call $create1)
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get-imprecise-1
;; Check the results of reading from a ref that can be one of two things.
;; We check all permutations in the arms of the select in this function and
;; the next two.
;;
;; Atm we can only optimize when the ref is the same in both arms, since
;; even if two different types agree on the value (like $struct1 and
;; $struct3 do), once we see two different types we already see the type as
;; imprecise, and $struct2 in the middle has a different value, so imprecise
;; info is not enough.
(drop
(struct.get $struct1 0
(select
(call $create1)
(call $create1)
(call $import)
)
)
)
(drop
(struct.get $struct1 0
(select
(call $create1)
(call $create2)
(call $import)
)
)
)
(drop
(struct.get $struct1 0
(select
(call $create1)
(call $create3)
(call $import)
)
)
)
)
;; CHECK: (func $get-imprecise-2 (type $2)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct1 0
;; CHECK-NEXT: (select (result (ref $struct1))
;; CHECK-NEXT: (call $create2)
;; CHECK-NEXT: (call $create1)
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (select (result (ref $struct2))
;; CHECK-NEXT: (call $create2)
;; CHECK-NEXT: (call $create2)
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 9999)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct2 0
;; CHECK-NEXT: (select (result (ref $struct2))
;; CHECK-NEXT: (call $create2)
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get-imprecise-2
(drop
(struct.get $struct1 0
(select
(call $create2)
(call $create1)
(call $import)
)
)
)
(drop
(struct.get $struct1 0
(select
(call $create2)
(call $create2)
(call $import)
)
)
)
(drop
(struct.get $struct1 0
(select
(call $create2)
(call $create3)
(call $import)
)
)
)
)
;; CHECK: (func $get-imprecise-3 (type $2)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct1 0
;; CHECK-NEXT: (select (result (ref $struct1))
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: (call $create1)
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct2 0
;; CHECK-NEXT: (select (result (ref $struct2))
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: (call $create2)
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (select (result (ref $struct3))
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: (call $import)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get-imprecise-3
(drop
(struct.get $struct1 0
(select
(call $create3)
(call $create1)
(call $import)
)
)
)
(drop
(struct.get $struct1 0
(select
(call $create3)
(call $create2)
(call $import)
)
)
)
(drop
(struct.get $struct1 0
(select
(call $create3)
(call $create3)
(call $import)
)
)
)
)
)
;; As above, but add not just a new of the middle class with a different value
;; but also a set. We can see that the set just affects the middle class,
;; though, so it is not a problem.
(module
;; CHECK: (type $struct1 (sub (struct (field (mut i32)))))
(type $struct1 (sub (struct (mut i32))))
;; CHECK: (type $struct2 (sub $struct1 (struct (field (mut i32)) (field f64))))
(type $struct2 (sub $struct1 (struct (mut i32) f64)))
;; CHECK: (type $struct3 (sub $struct2 (struct (field (mut i32)) (field f64) (field anyref))))
(type $struct3 (sub $struct2 (struct (mut i32) f64 anyref)))
;; CHECK: (type $3 (func (result i32)))
;; CHECK: (type $4 (func (result (ref $struct1))))
;; CHECK: (type $5 (func (result (ref $struct2))))
;; CHECK: (type $6 (func (result (ref $struct3))))
;; CHECK: (type $7 (func))
;; CHECK: (import "a" "b" (func $import (type $3) (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (func $create1 (type $4) (result (ref $struct1))
;; CHECK-NEXT: (struct.new $struct1
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create1 (result (ref $struct1))
(struct.new $struct1
(i32.const 10)
)
)
;; CHECK: (func $create2 (type $5) (result (ref $struct2))
;; CHECK-NEXT: (struct.new $struct2
;; CHECK-NEXT: (i32.const 9999)
;; CHECK-NEXT: (f64.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create2 (result (ref $struct2))
(struct.new $struct2
(i32.const 9999) ;; use a different value here
(f64.const 0)
)
)
;; CHECK: (func $create3 (type $6) (result (ref $struct3))
;; CHECK-NEXT: (struct.new $struct3
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: (f64.const 0)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create3 (result (ref $struct3))
(struct.new $struct3
(i32.const 10)
(f64.const 0)
(ref.null any)
)
)
;; CHECK: (func $get-precise (type $7)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $struct2 0
;; CHECK-NEXT: (call $create2)
;; CHECK-NEXT: (i32.const 9999)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 9999)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get-precise
;; The set only affects $struct2, exactly it and nothing else, so we can
;; optimize all the gets in this function.
(drop
(struct.get $struct1 0
(call $create1)
)
)
(struct.set $struct2 0
(call $create2)
(i32.const 9999)
)
(drop
(struct.get $struct2 0
(call $create2)
)
)
(drop
(struct.get $struct3 0
(call $create3)
)
)
)
)
;; As above, but the set is of a different value.
(module
;; CHECK: (type $struct1 (sub (struct (field (mut i32)))))
(type $struct1 (sub (struct (mut i32))))
;; CHECK: (type $struct2 (sub $struct1 (struct (field (mut i32)) (field f64))))
(type $struct2 (sub $struct1 (struct (mut i32) f64)))
;; CHECK: (type $struct3 (sub $struct2 (struct (field (mut i32)) (field f64) (field anyref))))
(type $struct3 (sub $struct2 (struct (mut i32) f64 anyref)))
;; CHECK: (type $3 (func (result i32)))
;; CHECK: (type $4 (func (result (ref $struct1))))
;; CHECK: (type $5 (func (result (ref $struct2))))
;; CHECK: (type $6 (func (result (ref $struct3))))
;; CHECK: (type $7 (func))
;; CHECK: (import "a" "b" (func $import (type $3) (result i32)))
(import "a" "b" (func $import (result i32)))
;; CHECK: (func $create1 (type $4) (result (ref $struct1))
;; CHECK-NEXT: (struct.new $struct1
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create1 (result (ref $struct1))
(struct.new $struct1
(i32.const 10)
)
)
;; CHECK: (func $create2 (type $5) (result (ref $struct2))
;; CHECK-NEXT: (struct.new $struct2
;; CHECK-NEXT: (i32.const 9999)
;; CHECK-NEXT: (f64.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create2 (result (ref $struct2))
(struct.new $struct2
(i32.const 9999) ;; use a different value here
(f64.const 0)
)
)
;; CHECK: (func $create3 (type $6) (result (ref $struct3))
;; CHECK-NEXT: (struct.new $struct3
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: (f64.const 0)
;; CHECK-NEXT: (ref.null none)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create3 (result (ref $struct3))
(struct.new $struct3
(i32.const 10)
(f64.const 0)
(ref.null any)
)
)
;; CHECK: (func $get-precise (type $7)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $struct2 0
;; CHECK-NEXT: (call $create2)
;; CHECK-NEXT: (i32.const 1234)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct2 0
;; CHECK-NEXT: (call $create2)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create3)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get-precise
(drop
(struct.get $struct1 0
(call $create1)
)
)
;; This set of a different value limits our ability to optimize the get
;; after us. But the get before us and the one at the very end remain
;; optimized - changes to $struct2 do not confuse the other types.
(struct.set $struct2 0
(call $create2)
(i32.const 1234)
)
(drop
(struct.get $struct2 0
(call $create2)
)
)
(drop
(struct.get $struct3 0
(call $create3)
)
)
)
)
;; Test for a struct with multiple fields, some of which are constant and hence
;; optimizable, and some not. Also test that some have the same type.
(module
;; CHECK: (type $struct (struct (field i32) (field f64) (field i32) (field f64) (field i32)))
(type $struct (struct i32 f64 i32 f64 i32))
;; CHECK: (type $1 (func (result (ref $struct))))
;; CHECK: (type $2 (func))
;; CHECK: (func $create (type $1) (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.eqz
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: (f64.const 3.14159)
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: (f64.abs
;; CHECK-NEXT: (f64.const 2.71828)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 30)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create (result (ref $struct))
(struct.new $struct
(i32.eqz (i32.const 10)) ;; not a constant (as far as this pass knows)
(f64.const 3.14159)
(i32.const 20)
(f64.abs (f64.const 2.71828)) ;; not a constant
(i32.const 30)
)
)
;; CHECK: (func $get (type $2)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 0
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result f64)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: (f64.const 3.14159)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 3
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 30)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 30)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get
(drop
(struct.get $struct 0
(call $create)
)
)
(drop
(struct.get $struct 1
(call $create)
)
)
(drop
(struct.get $struct 2
(call $create)
)
)
(drop
(struct.get $struct 3
(call $create)
)
)
(drop
(struct.get $struct 4
(call $create)
)
)
;; Also test for multiple gets of the same field.
(drop
(struct.get $struct 4
(call $create)
)
)
)
)
;; Never create A, but have a set to its field. A subtype B has no creates nor
;; sets, and the final subtype C has a create and a get. The set to A should
;; apply to it, preventing optimization.
(module
;; CHECK: (type $0 (func))
;; CHECK: (type $A (sub (struct (field (mut i32)))))
(type $A (sub (struct (mut i32))))
;; CHECK: (type $B (sub $A (struct (field (mut i32)))))
(type $B (sub $A (struct (mut i32))))
;; CHECK: (type $C (sub $B (struct (field (mut i32)))))
(type $C (sub $B (struct (mut i32))))
;; CHECK: (type $4 (func (result (ref $C))))
;; CHECK: (func $create-C (type $4) (result (ref $C))
;; CHECK-NEXT: (struct.new $C
;; CHECK-NEXT: (i32.const 10)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create-C (result (ref $C))
(struct.new $C
(i32.const 10)
)
)
;; CHECK: (func $set (type $0)
;; CHECK-NEXT: (struct.set $C 0
;; CHECK-NEXT: (ref.cast (ref $C)
;; CHECK-NEXT: (call $create-C)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 20)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $set
;; Set of $A, but the reference is actually a $C. We add a cast to try to
;; make sure the type is $A, which should not confuse us: this set does
;; alias the data in $C, which means we cannot optimize in the function $get
;; below. (Note that finalize will turn the cast into a cast of $C
;; automatically; that is not part of GUFA.)
(struct.set $A 0
(ref.cast (ref $A)
(call $create-C)
)
(i32.const 20) ;; different value than in $create
)
)
;; CHECK: (func $get (type $0)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $C 0
;; CHECK-NEXT: (call $create-C)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $get
(drop
(struct.get $C 0
(call $create-C)
)
)
)
)
;; Copies of a field to itself can be ignored. As a result, we can optimize both
;; of the gets here.
(module
;; CHECK: (type $struct (struct (field (mut i32))))
(type $struct (struct (mut i32)))
;; CHECK: (type $1 (func (result (ref $struct))))
;; CHECK: (type $2 (func))
;; CHECK: (func $create (type $1) (result (ref $struct))
;; CHECK-NEXT: (struct.new_default $struct)
;; CHECK-NEXT: )
(func $create (result (ref $struct))
(struct.new_default $struct)
)
;; CHECK: (func $test (type $2)
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; This copy does not actually introduce any new possible values, and so it
;; remains true that the only possible value is the default 0, so we can
;; optimize the get below to a 0 (and also the get in the set).
(struct.set $struct 0
(call $create)
(struct.get $struct 0
(call $create)
)
)
(drop
(struct.get $struct 0
(call $create)
)
)
)
)
;; Test of a near-copy, of a similar looking field (same index, and same field
;; type) but in a different struct. The value in both structs is the same, so
;; we can optimize.
(module
;; CHECK: (type $struct (struct (field (mut f32)) (field (mut i32))))
(type $struct (struct (mut f32) (mut i32)))
;; CHECK: (type $other (struct (field (mut f64)) (field (mut i32))))
(type $other (struct (mut f64) (mut i32)))
;; CHECK: (type $2 (func (result (ref $struct))))
;; CHECK: (type $3 (func (result (ref $other))))
;; CHECK: (type $4 (func))
;; CHECK: (func $create-struct (type $2) (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (f32.const 0)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create-struct (result (ref $struct))
(struct.new $struct
(f32.const 0)
(i32.const 42)
)
)
;; CHECK: (func $create-other (type $3) (result (ref $other))
;; CHECK-NEXT: (struct.new $other
;; CHECK-NEXT: (f64.const 0)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create-other (result (ref $other))
(struct.new $other
(f64.const 0)
(i32.const 42)
)
)
;; CHECK: (func $test (type $4)
;; CHECK-NEXT: (struct.set $struct 1
;; CHECK-NEXT: (call $create-struct)
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create-other)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create-struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; We copy data between the types, but the possible values of their fields
;; are the same anyhow, so we can optimize all the gets to 42.
(struct.set $struct 1
(call $create-struct)
(struct.get $other 1
(call $create-other)
)
)
(drop
(struct.get $struct 1
(call $create-struct)
)
)
)
)
;; As above, but each struct has a different value, so copying between them
;; inhibits one optimization.
(module
;; CHECK: (type $struct (struct (field (mut f32)) (field (mut i32))))
(type $struct (struct (mut f32) (mut i32)))
;; CHECK: (type $other (struct (field (mut f64)) (field (mut i32))))
(type $other (struct (mut f64) (mut i32)))
;; CHECK: (type $2 (func (result (ref $struct))))
;; CHECK: (type $3 (func (result (ref $other))))
;; CHECK: (type $4 (func))
;; CHECK: (func $create-struct (type $2) (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (f32.const 0)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create-struct (result (ref $struct))
(struct.new $struct
(f32.const 0)
(i32.const 42)
)
)
;; CHECK: (func $create-other (type $3) (result (ref $other))
;; CHECK-NEXT: (struct.new $other
;; CHECK-NEXT: (f64.const 0)
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create-other (result (ref $other))
(struct.new $other
(f64.const 0)
(i32.const 1337) ;; this changed
)
)
;; CHECK: (func $test (type $4)
;; CHECK-NEXT: (struct.set $struct 1
;; CHECK-NEXT: (call $create-struct)
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create-other)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 1
;; CHECK-NEXT: (call $create-struct)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; As this is not a copy between a struct and itself, we cannot optimize
;; the last get lower down: $struct has both 42 and 1337 written to it.
(struct.set $struct 1
(call $create-struct)
(struct.get $other 1
(call $create-other)
)
)
(drop
(struct.get $struct 1
(call $create-struct)
)
)
)
)
;; Similar to the above, but different fields within the same struct.
(module
;; CHECK: (type $struct (struct (field (mut i32)) (field (mut i32))))
(type $struct (struct (mut i32) (mut i32)))
;; CHECK: (type $1 (func (result (ref $struct))))
;; CHECK: (type $2 (func))
;; CHECK: (func $create (type $1) (result (ref $struct))
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $create (result (ref $struct))
(struct.new $struct
(i32.const 42)
(i32.const 1337)
)
)
;; CHECK: (func $test (type $2)
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 0
;; CHECK-NEXT: (call $create)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; The get from field 1 can be optimized to 1337, but field 0 has this
;; write to it, which means it can contain 42 or 1337, so we cannot
;; optimize.
(struct.set $struct 0
(call $create)
(struct.get $struct 1
(call $create)
)
)
(drop
(struct.get $struct 0
(call $create)
)
)
)
)
(module
;; CHECK: (type $A (struct))
(type $A (struct))
(type $B (struct (ref $A)))
;; CHECK: (type $1 (func))
;; CHECK: (global $global (ref $A) (struct.new_default $A))
(global $global (ref $A) (struct.new $A))
;; CHECK: (func $test (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (global.get $global)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; An immutable global is the only thing written to this field, so we can
;; propagate the value to the struct.get and replace it with a global.get.
(drop
(struct.get $B 0
(struct.new $B
(global.get $global)
)
)
)
)
)
;; As above, but with an imported global, which we can also optimize (since it
;; is still immutable).
(module
(type $struct (struct i32))
;; CHECK: (type $0 (func))
;; CHECK: (import "a" "b" (global $global i32))
(import "a" "b" (global $global i32))
;; CHECK: (func $test (type $0)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (global.get $global)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
(drop
(struct.get $struct 0
(struct.new $struct
(global.get $global)
)
)
)
)
)
(module
(type $struct (struct i32))
;; CHECK: (type $0 (func))
;; CHECK: (global $global i32 (i32.const 42))
(global $global i32 (i32.const 42))
;; CHECK: (func $test (type $0)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; An immutable global is the only thing written to this field, so we can
;; propagate the value to the struct.get to get 42 here (even better than a
;; global.get as in the last examples).
(drop
(struct.get $struct 0
(struct.new $struct
(global.get $global)
)
)
)
)
)
(module
(type $struct (struct i32))
;; CHECK: (type $0 (func))
;; CHECK: (global $global (mut i32) (i32.const 42))
(global $global (mut i32) (i32.const 42))
;; CHECK: (func $test (type $0)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; As above, but the global is *not* immutable. Still, it has no writes, so
;; we can optimize.
(drop
(struct.get $struct 0
(struct.new $struct
(global.get $global)
)
)
)
)
)
(module
;; CHECK: (type $struct (struct (field i32)))
(type $struct (struct i32))
;; CHECK: (type $1 (func))
;; CHECK: (global $global (mut i32) (i32.const 42))
(global $global (mut i32) (i32.const 42))
;; CHECK: (func $test (type $1)
;; CHECK-NEXT: (global.set $global
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 0
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (global.get $global)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; As above, but the global does have another write of another value, which
;; prevents optimization.
(global.set $global
(i32.const 1337)
)
(drop
(struct.get $struct 0
(struct.new $struct
(global.get $global)
)
)
)
)
)
(module
;; CHECK: (type $struct (struct (field (mut i32))))
(type $struct (struct (mut i32)))
;; CHECK: (type $1 (func))
;; CHECK: (global $global i32 (i32.const 42))
(global $global i32 (i32.const 42))
;; CHECK: (func $test (type $1)
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; As above, but there is another set of the field. It writes the same
;; value, though, so that is fine. Also, the struct's field is now mutable
;; as well to allow that, and that also does not prevent optimization.
(struct.set $struct 0
(struct.new $struct
(global.get $global)
)
(i32.const 42)
)
(drop
(struct.get $struct 0
(struct.new $struct
(global.get $global)
)
)
)
)
)
(module
;; CHECK: (type $struct (struct (field (mut i32))))
(type $struct (struct (mut i32)))
;; CHECK: (type $1 (func))
;; CHECK: (global $global i32 (i32.const 42))
(global $global i32 (i32.const 42))
;; CHECK: (global $global-2 i32 (i32.const 1337))
(global $global-2 i32 (i32.const 1337))
;; CHECK: (func $test (type $1)
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 0
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; As above, but set a different global, which prevents optimization of the
;; struct.get below.
(struct.set $struct 0
(struct.new $struct
(global.get $global)
)
(global.get $global-2)
)
(drop
(struct.get $struct 0
(struct.new $struct
(global.get $global)
)
)
)
)
)
(module
;; CHECK: (type $struct (struct (field (mut i32))))
(type $struct (struct (mut i32)))
;; CHECK: (type $1 (func))
;; CHECK: (global $global i32 (i32.const 42))
(global $global i32 (i32.const 42))
;; CHECK: (func $test (type $1)
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 0
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; As above, but set a constant, which means we are mixing constants with
;; globals, which prevents the optimization of the struct.get.
(struct.set $struct 0
(struct.new $struct
(global.get $global)
)
(i32.const 1337)
)
(drop
(struct.get $struct 0
(struct.new $struct
(global.get $global)
)
)
)
)
)
(module
;; Test a global type other than i32. Arrays of structs are a realistic case
;; as they are used to implement itables.
;; CHECK: (type $vtable (struct (field funcref)))
(type $vtable (struct funcref))
;; CHECK: (type $itable (array (ref $vtable)))
(type $itable (array (ref $vtable)))
(type $object (struct (field $itable (ref $itable))))
;; CHECK: (type $2 (func (result funcref)))
;; CHECK: (global $global (ref $itable) (array.new_fixed $itable 2
;; CHECK-NEXT: (struct.new $vtable
;; CHECK-NEXT: (ref.null nofunc)
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.new $vtable
;; CHECK-NEXT: (ref.func $test)
;; CHECK-NEXT: )
;; CHECK-NEXT: ))
(global $global (ref $itable) (array.new_fixed $itable 2
(struct.new $vtable
(ref.null func)
)
(struct.new $vtable
(ref.func $test)
)
))
;; CHECK: (func $test (type $2) (result funcref)
;; CHECK-NEXT: (struct.get $vtable 0
;; CHECK-NEXT: (array.get $itable
;; CHECK-NEXT: (global.get $global)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test (result funcref)
;; Realistic usage of an itable: read an item from it, then a func from
;; that, and return the value (all verifying that the types are correct
;; after optimization).
;;
;; We optimize some of this, but stop at reading from the immutable global.
;; To continue we'd need to track the fields of allocated objects, or look
;; at immutable globals directly, neither of which we do yet. TODO
(struct.get $vtable 0
(array.get $itable
(struct.get $object $itable
(struct.new $object
(global.get $global)
)
)
(i32.const 1)
)
)
)
)
;; Test we handle packed fields properly.
(module
(rec
;; CHECK: (type $0 (func))
;; CHECK: (rec
;; CHECK-NEXT: (type $A_8 (struct (field i8)))
(type $A_8 (struct (field i8)))
;; CHECK: (type $A_16 (struct (field i16)))
(type $A_16 (struct (field i16)))
;; CHECK: (type $B_16 (struct (field i16)))
(type $B_16 (struct (field i16)))
)
;; CHECK: (import "a" "b" (global $g i32))
(import "a" "b" (global $g i32))
;; CHECK: (func $test (type $0)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 120)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 22136)
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get_u $B_16 0
;; CHECK-NEXT: (struct.new $B_16
;; CHECK-NEXT: (global.get $g)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $test
;; We can infer values here, but must mask them.
(drop
(struct.get_u $A_8 0
(struct.new $A_8
(i32.const 0x12345678)
)
)
)
(drop
(struct.get_u $A_16 0
(struct.new $A_16
(i32.const 0x12345678)
)
)
)
;; Also test reading a value from an imported global, which is an unknown
;; value at compile time, but which we know must be masked as well. Atm
;; GUFA does not handle this, unlike CFP (see TODO in filterDataContents).
(drop
(struct.get_u $B_16 0
(struct.new $B_16
(global.get $g)
)
)
)
)
)