| ;; 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) |
| ) |
| ) |
| ) |
| ) |
| ) |