| ;; 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 --cfp --closed-world -all -S -o - | filecheck %s | |
| ;; (remove-unused-names is added to test fallthrough values without a block | |
| ;; name getting in the way) | |
| (module | |
| ;; CHECK: (type $0 (func)) | |
| ;; CHECK: (type $struct (struct (field i32))) | |
| (type $struct (struct i32)) | |
| ;; CHECK: (func $impossible-get (type $0) | |
| ;; CHECK-NEXT: (local $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (unreachable) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $impossible-get (local $struct (ref null $struct)) | |
| (drop | |
| ;; This type is never created, so a get is impossible, and we will trap | |
| ;; anyhow. So we can turn this into an unreachable (plus a drop of the | |
| ;; reference). | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| (module | |
| ;; CHECK: (type $struct (struct (field i64))) | |
| (type $struct (struct i64)) | |
| ;; CHECK: (type $1 (func (param (ref null $struct)))) | |
| ;; CHECK: (func $test (type $1) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new_default $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i64) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i64.const 0) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $struct (ref null $struct)) | |
| ;; The only place this type is created is with a default value, and so we | |
| ;; can optimize the later get into a constant (plus a drop of the ref). | |
| ;; | |
| ;; (Note that the allocated reference is dropped here, so it is not actually | |
| ;; used anywhere, but this pass does not attempt to trace the paths of | |
| ;; references escaping and being stored etc. - it just thinks at the type | |
| ;; level.) | |
| (drop | |
| (struct.new_default $struct) | |
| ) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| (module | |
| ;; CHECK: (type $struct (struct (field f32))) | |
| (type $struct (struct f32)) | |
| ;; CHECK: (type $1 (func (param (ref null $struct)))) | |
| ;; CHECK: (func $test (type $1) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (f32.const 42) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result f32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (f32.const 42) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $struct (ref null $struct)) | |
| ;; The only place this type is created is with a constant value, and so we | |
| ;; can optimize the later get into a constant (plus a drop of the ref). | |
| (drop | |
| (struct.new $struct | |
| (f32.const 42) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| (module | |
| ;; CHECK: (type $struct (struct (field f32))) | |
| (type $struct (struct f32)) | |
| ;; CHECK: (type $1 (func (param f32 (ref null $struct)))) | |
| ;; CHECK: (func $test (type $1) (param $f f32) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (local.get $f) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $f f32) (param $struct (ref null $struct)) | |
| ;; The value given is not a constant, and so we cannot optimize. | |
| (drop | |
| (struct.new $struct | |
| (local.get $f) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; 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)) | |
| ;; CHECK: (type $2 (func (param (ref null $struct)))) | |
| ;; CHECK: (func $create (type $1) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create | |
| (drop | |
| (struct.new $struct | |
| (i32.const 10) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $2) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $struct (ref null $struct)) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; 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 (param (ref null $struct)))) | |
| ;; CHECK: (type $2 (func)) | |
| ;; CHECK: (func $get (type $1) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $struct (ref null $struct)) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $create (type $2) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create | |
| (drop | |
| (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 (param (ref null $struct)))) | |
| ;; CHECK: (func $test (type $1) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (f32.const 42) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (f32.const 1337) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $struct (ref null $struct)) | |
| (drop | |
| (struct.new $struct | |
| (f32.const 42) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $struct | |
| (f32.const 1337) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; 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 (param (ref null $struct)))) | |
| ;; CHECK: (type $2 (func)) | |
| ;; CHECK: (func $create (type $2) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (f32.const 42) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create | |
| (drop | |
| (struct.new $struct | |
| (f32.const 42) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $set (type $1) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (struct.set $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: (f32.const 1337) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $set (param $struct (ref null $struct)) | |
| (struct.set $struct 0 | |
| (local.get $struct) | |
| (f32.const 1337) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $1) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $struct (ref null $struct)) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; 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 (param (ref null $struct)))) | |
| ;; CHECK: (type $2 (func)) | |
| ;; CHECK: (func $create (type $2) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (f32.const 42) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create | |
| (drop | |
| (struct.new $struct | |
| (f32.const 42) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $set (type $1) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (struct.set $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: (f32.const 42) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $set (param $struct (ref null $struct)) | |
| (struct.set $struct 0 | |
| (local.get $struct) | |
| (f32.const 42) ;; The last testcase had 1337 here. | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $1) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result f32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (f32.const 42) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $struct (ref null $struct)) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; 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 (param i32 (ref null $struct)))) | |
| ;; CHECK: (type $3 (func (param (ref null $struct)))) | |
| ;; CHECK: (func $create (type $1) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (block (result f32) | |
| ;; CHECK-NEXT: (nop) | |
| ;; CHECK-NEXT: (f32.const 42) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create | |
| (drop | |
| (struct.new $struct | |
| ;; Fall though a 42 via a block. | |
| (block (result f32) | |
| (nop) | |
| (f32.const 42) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $set (type $2) (param $x i32) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (struct.set $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: (if (result f32) | |
| ;; CHECK-NEXT: (local.get $x) | |
| ;; CHECK-NEXT: (then | |
| ;; CHECK-NEXT: (unreachable) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (else | |
| ;; CHECK-NEXT: (f32.const 42) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $set (param $x i32) (param $struct (ref null $struct)) | |
| (struct.set $struct 0 | |
| (local.get $struct) | |
| ;; Fall though a 42 via an if. | |
| (if (result f32) | |
| (local.get $x) | |
| (then | |
| (unreachable) | |
| ) | |
| (else | |
| (f32.const 42) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $3) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result f32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (f32.const 42) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $struct (ref null $struct)) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; Test a function reference instead of a number. | |
| (module | |
| ;; CHECK: (type $struct (struct (field funcref))) | |
| (type $struct (struct funcref)) | |
| ;; CHECK: (type $1 (func (param (ref null $struct)))) | |
| ;; CHECK: (elem declare func $test) | |
| ;; CHECK: (func $test (type $1) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (ref.func $test) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result (ref (exact $1))) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (ref.func $test) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $struct (ref null $struct)) | |
| (drop | |
| (struct.new $struct | |
| (ref.func $test) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; 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: (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: (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: (block ;; (replaces unreachable StructSet we can't emit) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (unreachable) | |
| ;; 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 | |
| (unreachable) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct 0 | |
| (unreachable) | |
| ) | |
| ) | |
| (struct.set $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))) | |
| ;; CHECK: (type $substruct (sub $struct (struct (field i32)))) | |
| (type $substruct (sub $struct (struct i32))) | |
| ;; CHECK: (type $2 (func)) | |
| ;; CHECK: (type $3 (func (param (ref null $substruct)))) | |
| ;; CHECK: (func $create (type $2) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create | |
| (drop | |
| (struct.new $struct | |
| (i32.const 10) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $3) (param $substruct (ref null $substruct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (local.get $substruct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (unreachable) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $substruct (ref null $substruct)) | |
| (drop | |
| (struct.get $substruct 0 | |
| (local.get $substruct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; As above, but in addition to a new of $struct also add a set. The set could | |
| ;; in principle be relevant for the get as far as this pass knows, and so we | |
| ;; will optimize the result to the only possible value. (In practice, though, | |
| ;; it will trap anyhow.) | |
| (module | |
| ;; CHECK: (type $struct (sub (struct (field (mut i32))))) | |
| (type $struct (sub (struct (mut i32)))) | |
| ;; CHECK: (type $substruct (sub $struct (struct (field (mut i32))))) | |
| (type $substruct (sub $struct (struct (mut i32)))) | |
| ;; CHECK: (type $2 (func (param (ref null $struct)))) | |
| ;; CHECK: (type $3 (func (param (ref null $substruct)))) | |
| ;; CHECK: (func $create (type $2) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.set $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create (param $struct (ref null $struct)) | |
| (drop | |
| (struct.new $struct | |
| (i32.const 10) | |
| ) | |
| ) | |
| (struct.set $struct 0 | |
| (local.get $struct) | |
| (i32.const 10) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $3) (param $substruct (ref null $substruct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $substruct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $substruct (ref null $substruct)) | |
| (drop | |
| (struct.get $substruct 0 | |
| (local.get $substruct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; Subtyping: Create a subtype and get a supertype. The get must receive a | |
| ;; reference to the subtype (we never create a supertype) and so we | |
| ;; can optimize. | |
| (module | |
| ;; CHECK: (type $struct (sub (struct (field i32)))) | |
| (type $struct (sub (struct i32))) | |
| ;; CHECK: (type $1 (func)) | |
| ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64)))) | |
| (type $substruct (sub $struct (struct i32 f64))) | |
| ;; CHECK: (type $3 (func (param (ref null $struct)))) | |
| ;; CHECK: (func $create (type $1) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $substruct | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: (f64.const 3.14159) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create | |
| (drop | |
| (struct.new $substruct | |
| (i32.const 10) | |
| (f64.const 3.14159) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $3) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $struct (ref null $struct)) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; 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)) | |
| ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64)))) | |
| (type $substruct (sub $struct (struct i32 f64))) | |
| ;; CHECK: (type $3 (func (param (ref null $struct)))) | |
| ;; CHECK: (func $create (type $1) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $substruct | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: (f64.const 3.14159) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create | |
| (drop | |
| (struct.new $struct | |
| (i32.const 10) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $substruct | |
| (i32.const 10) | |
| (f64.const 3.14159) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $3) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $struct (ref null $struct)) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; 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. | |
| ;; | |
| ;; Note that this may be optimized using a ref.test, in --cfp-reftest, but not | |
| ;; in --cfp. This gives us coverage that --cfp does not do the things that | |
| ;; --cfp-reftest does (how --cfp-reftest works is tested in cfp-reftest.wast). | |
| (module | |
| ;; CHECK: (type $struct (sub (struct (field i32)))) | |
| (type $struct (sub (struct i32))) | |
| ;; CHECK: (type $1 (func)) | |
| ;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64)))) | |
| (type $substruct (sub $struct (struct i32 f64))) | |
| ;; CHECK: (type $3 (func (param (ref null $struct)))) | |
| ;; CHECK: (func $create (type $1) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $substruct | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: (f64.const 3.14159) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create | |
| (drop | |
| (struct.new $struct | |
| (i32.const 10) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $substruct | |
| (i32.const 20) | |
| (f64.const 3.14159) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $3) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $struct (ref null $struct)) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; 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 substruct 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)) | |
| ;; CHECK: (type $3 (func (param (ref null $substruct)))) | |
| ;; CHECK: (func $create (type $2) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $substruct | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: (f64.const 3.14159) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create | |
| (drop | |
| (struct.new $struct | |
| (i32.const 10) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $substruct | |
| (i32.const 20) | |
| (f64.const 3.14159) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $3) (param $substruct (ref null $substruct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $substruct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $substruct (ref null $substruct)) | |
| (drop | |
| (struct.get $substruct 0 | |
| (local.get $substruct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; 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 (param (ref null $struct)))) | |
| ;; CHECK: (type $3 (func (param (ref null $substruct)))) | |
| ;; CHECK: (func $create (type $2) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.set $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $substruct | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: (f64.const 3.14159) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create (param $struct (ref null $struct)) | |
| (drop | |
| (struct.new $struct | |
| (i32.const 10) | |
| ) | |
| ) | |
| (struct.set $struct 0 | |
| (local.get $struct) | |
| (i32.const 10) | |
| ) | |
| (drop | |
| (struct.new $substruct | |
| (i32.const 20) | |
| (f64.const 3.14159) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $3) (param $substruct (ref null $substruct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $substruct 0 | |
| ;; CHECK-NEXT: (local.get $substruct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $substruct (ref null $substruct)) | |
| (drop | |
| (struct.get $substruct 0 | |
| (local.get $substruct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; 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)) | |
| ;; CHECK: (type $4 (func (param (ref null $struct1) (ref null $struct2) (ref null $struct3)))) | |
| ;; CHECK: (func $create (type $3) | |
| ;; CHECK-NEXT: (drop | |
| ;; 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: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create | |
| (drop | |
| (struct.new $struct3 | |
| (i32.const 20) | |
| (f64.const 3.14159) | |
| (ref.null none) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $4) (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct1) | |
| ;; 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: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct2) | |
| ;; 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: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct2) | |
| ;; 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: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct3) | |
| ;; 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: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct3) | |
| ;; 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: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct3) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (ref.null none) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3)) | |
| ;; Get field 0 from the $struct1. This can be optimized to a constant | |
| ;; since we only ever created an instance of struct3 with a constant there. | |
| (drop | |
| (struct.get $struct1 0 | |
| (local.get $struct1) | |
| ) | |
| ) | |
| ;; Get both fields of $struct2. | |
| (drop | |
| (struct.get $struct2 0 | |
| (local.get $struct2) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct2 1 | |
| (local.get $struct2) | |
| ) | |
| ) | |
| ;; Get all 3 fields of $struct3 | |
| (drop | |
| (struct.get $struct3 0 | |
| (local.get $struct3) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct3 1 | |
| (local.get $struct3) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct3 2 | |
| (local.get $struct3) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; 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 $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 $3 (func (param anyref))) | |
| ;; CHECK: (type $4 (func (param (ref null $struct1) (ref null $struct2) (ref null $struct3)))) | |
| ;; CHECK: (func $create (type $3) (param $any anyref) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct1 | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; 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: (local.get $any) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create (param $any anyref) | |
| (drop | |
| (struct.new $struct1 | |
| (i32.const 10) | |
| (i32.const 20) | |
| ) | |
| ) | |
| (drop | |
| (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) | |
| (local.get $any) ;; use a non-constant value here, which can never be | |
| ;; optimized. | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $4) (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct1) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct1 1 | |
| ;; CHECK-NEXT: (local.get $struct1) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct2) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct2) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 999) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result f64) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct2) | |
| ;; CHECK-NEXT: ) | |
| ;; 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: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct2) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (f64.const 9.9999999) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct3) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct3) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 999) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result f64) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct3) | |
| ;; CHECK-NEXT: ) | |
| ;; 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: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct3) | |
| ;; CHECK-NEXT: ) | |
| ;; 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: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct3) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (ref.null none) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct3 5 | |
| ;; CHECK-NEXT: (local.get $struct3) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3)) | |
| ;; Get all the fields of all the structs. | |
| (drop | |
| (struct.get $struct1 0 | |
| (local.get $struct1) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct1 1 | |
| (local.get $struct1) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct2 0 | |
| (local.get $struct2) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct2 1 | |
| (local.get $struct2) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct2 2 | |
| (local.get $struct2) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct2 3 | |
| (local.get $struct2) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct3 0 | |
| (local.get $struct3) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct3 1 | |
| (local.get $struct3) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct3 2 | |
| (local.get $struct3) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct3 3 | |
| (local.get $struct3) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct3 4 | |
| (local.get $struct3) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct3 5 | |
| (local.get $struct3) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; Multi-level subtyping with a different value in the middle of the chain. We | |
| ;; can only optimize $struct3. | |
| (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)) | |
| ;; CHECK: (type $4 (func (param (ref null $struct1) (ref null $struct2) (ref null $struct3)))) | |
| ;; CHECK: (func $create (type $3) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct1 | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct2 | |
| ;; CHECK-NEXT: (i32.const 9999) | |
| ;; CHECK-NEXT: (f64.const 0) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct3 | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: (f64.const 0) | |
| ;; CHECK-NEXT: (ref.null none) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create | |
| (drop | |
| (struct.new $struct1 | |
| (i32.const 10) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $struct2 | |
| (i32.const 9999) ;; use a different value here | |
| (f64.const 0) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $struct3 | |
| (i32.const 10) | |
| (f64.const 0) | |
| (ref.null any) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $4) (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct1 0 | |
| ;; CHECK-NEXT: (local.get $struct1) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct2 0 | |
| ;; CHECK-NEXT: (local.get $struct2) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct3) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3)) | |
| ;; Get field 0 in all the types. | |
| (drop | |
| (struct.get $struct1 0 | |
| (local.get $struct1) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct2 0 | |
| (local.get $struct2) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct3 0 | |
| (local.get $struct3) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; As above, but add not just a new of the middle class with a different value | |
| ;; but also a set. That prevents all optimizations. | |
| (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 (param (ref null $struct2)))) | |
| ;; CHECK: (type $4 (func (param (ref null $struct1) (ref null $struct2) (ref null $struct3)))) | |
| ;; CHECK: (func $create (type $3) (param $struct2 (ref null $struct2)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct1 | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct2 | |
| ;; CHECK-NEXT: (i32.const 9999) | |
| ;; CHECK-NEXT: (f64.const 0) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.set $struct2 0 | |
| ;; CHECK-NEXT: (local.get $struct2) | |
| ;; CHECK-NEXT: (i32.const 9999) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct3 | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: (f64.const 0) | |
| ;; CHECK-NEXT: (ref.null none) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create (param $struct2 (ref null $struct2)) | |
| (drop | |
| (struct.new $struct1 | |
| (i32.const 10) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $struct2 | |
| (i32.const 9999) ;; use a different value here | |
| (f64.const 0) | |
| ) | |
| ) | |
| (struct.set $struct2 0 | |
| (local.get $struct2) | |
| (i32.const 9999) ;; use a different value here | |
| ) | |
| (drop | |
| (struct.new $struct3 | |
| (i32.const 10) | |
| (f64.const 0) | |
| (ref.null any) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $4) (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct1 0 | |
| ;; CHECK-NEXT: (local.get $struct1) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct2 0 | |
| ;; CHECK-NEXT: (local.get $struct2) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct3 0 | |
| ;; CHECK-NEXT: (local.get $struct3) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $struct1 (ref null $struct1)) (param $struct2 (ref null $struct2)) (param $struct3 (ref null $struct3)) | |
| ;; Get field 0 in all the types. | |
| (drop | |
| (struct.get $struct1 0 | |
| (local.get $struct1) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct2 0 | |
| (local.get $struct2) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct3 0 | |
| (local.get $struct3) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; 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)) | |
| ;; CHECK: (type $2 (func (param (ref null $struct)))) | |
| ;; CHECK: (func $create (type $1) | |
| ;; CHECK-NEXT: (drop | |
| ;; 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: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create | |
| (drop | |
| (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) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result f64) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; 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: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct 3 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 30) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 30) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $struct (ref null $struct)) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct 1 | |
| (local.get $struct) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct 2 | |
| (local.get $struct) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct 3 | |
| (local.get $struct) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct 4 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ;; Also test for multiple gets of the same field. | |
| (drop | |
| (struct.get $struct 4 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; 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 $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 $3 (func)) | |
| ;; CHECK: (type $4 (func (param (ref $A)))) | |
| ;; CHECK: (type $5 (func (param (ref $C)))) | |
| ;; CHECK: (func $create (type $3) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $C | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $create | |
| (drop | |
| (struct.new $C | |
| (i32.const 10) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $set (type $4) (param $a (ref $A)) | |
| ;; CHECK-NEXT: (struct.set $A 0 | |
| ;; CHECK-NEXT: (local.get $a) | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $set (param $a (ref $A)) | |
| (struct.set $A 0 | |
| (local.get $a) | |
| (i32.const 20) | |
| ) | |
| ) | |
| ;; CHECK: (func $get (type $5) (param $c (ref $C)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $C 0 | |
| ;; CHECK-NEXT: (local.get $c) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $get (param $c (ref $C)) | |
| (drop | |
| (struct.get $C 0 | |
| (local.get $c) | |
| ) | |
| ) | |
| ) | |
| ) | |
| (module | |
| ;; CHECK: (type $struct (struct (field i32))) | |
| (type $struct (struct i32)) | |
| ;; CHECK: (type $1 (func (param (ref null $struct)))) | |
| ;; CHECK: (global $global i32 (i32.const 42)) | |
| (global $global i32 (i32.const 42)) | |
| ;; CHECK: (func $test (type $1) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (global.get $global) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (global.get $global) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $struct (ref null $struct)) | |
| ;; 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.new $struct | |
| (global.get $global) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| (module | |
| ;; CHECK: (type $struct (struct (field i32))) | |
| (type $struct (struct i32)) | |
| ;; CHECK: (type $1 (func (param (ref null $struct)))) | |
| ;; CHECK: (global $global (mut i32) (i32.const 42)) | |
| (global $global (mut i32) (i32.const 42)) | |
| ;; CHECK: (func $test (type $1) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (global.get $global) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $struct (ref null $struct)) | |
| ;; As above, but the global is *not* immutable, so we cannot optimize. | |
| (drop | |
| (struct.new $struct | |
| (global.get $global) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| (module | |
| ;; CHECK: (type $struct (struct (field (mut i32)))) | |
| (type $struct (struct (mut i32))) | |
| ;; CHECK: (type $1 (func (param (ref null $struct)))) | |
| ;; CHECK: (global $global i32 (i32.const 42)) | |
| (global $global i32 (i32.const 42)) | |
| ;; CHECK: (func $test (type $1) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (global.get $global) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.set $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: (global.get $global) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (global.get $global) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $struct (ref null $struct)) | |
| (drop | |
| (struct.new $struct | |
| (global.get $global) | |
| ) | |
| ) | |
| ;; As above, but there is another set of the field. It is the same, 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 | |
| (local.get $struct) | |
| (global.get $global) | |
| ) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| (module | |
| ;; CHECK: (type $struct (struct (field (mut i32)))) | |
| (type $struct (struct (mut i32))) | |
| ;; CHECK: (type $1 (func (param (ref null $struct)))) | |
| ;; 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) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (global.get $global) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.set $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: (global.get $global-2) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $struct (ref null $struct)) | |
| (drop | |
| (struct.new $struct | |
| (global.get $global) | |
| ) | |
| ) | |
| ;; As above, but set a different global, which prevents optimization. | |
| (struct.set $struct 0 | |
| (local.get $struct) | |
| (global.get $global-2) | |
| ) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| (module | |
| ;; CHECK: (type $struct (struct (field (mut i32)))) | |
| (type $struct (struct (mut i32))) | |
| ;; CHECK: (type $1 (func (param (ref null $struct)))) | |
| ;; 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) (param $struct (ref null $struct)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $struct | |
| ;; CHECK-NEXT: (global.get $global) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.set $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: (i32.const 1337) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $struct 0 | |
| ;; CHECK-NEXT: (local.get $struct) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $struct (ref null $struct)) | |
| (drop | |
| (struct.new $struct | |
| (global.get $global) | |
| ) | |
| ) | |
| ;; As above, but set a constant, which means we are mixing constants with | |
| ;; globals, which prevents the optimization. | |
| (struct.set $struct 0 | |
| (local.get $struct) | |
| (i32.const 1337) | |
| ) | |
| (drop | |
| (struct.get $struct 0 | |
| (local.get $struct) | |
| ) | |
| ) | |
| ) | |
| ) | |
| (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))) | |
| ;; CHECK: (type $object (struct (field $itable (ref $itable)))) | |
| (type $object (struct (field $itable (ref $itable)))) | |
| ;; CHECK: (type $3 (func (param (ref null $object)) (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 $3) (param $object (ref null $object)) (result funcref) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $object | |
| ;; CHECK-NEXT: (global.get $global) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.get $vtable 0 | |
| ;; CHECK-NEXT: (array.get $itable | |
| ;; CHECK-NEXT: (block (result (ref $itable)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $object) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (global.get $global) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 1) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $object (ref null $object)) (result funcref) | |
| (drop | |
| (struct.new $object | |
| (global.get $global) | |
| ) | |
| ) | |
| ;; 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). Note how after optimization everything is lined up | |
| ;; so that precompute-propagate can infer from the global.get the specific | |
| ;; object the array.get is on, allowing us to emit a constant value for the | |
| ;; outer struct.get in principle. | |
| (struct.get $vtable 0 | |
| (array.get $itable | |
| (struct.get $object $itable | |
| (local.get $object) | |
| ) | |
| (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: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (struct.new $A_8 | |
| ;; CHECK-NEXT: (i32.const 305419896) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.and | |
| ;; CHECK-NEXT: (i32.const 305419896) | |
| ;; CHECK-NEXT: (i32.const 255) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (struct.new $A_16 | |
| ;; CHECK-NEXT: (i32.const 305419896) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.and | |
| ;; CHECK-NEXT: (i32.const 305419896) | |
| ;; CHECK-NEXT: (i32.const 65535) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (struct.new $B_16 | |
| ;; CHECK-NEXT: (global.get $g) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.and | |
| ;; CHECK-NEXT: (global.get $g) | |
| ;; CHECK-NEXT: (i32.const 65535) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test | |
| ;; We can infer values here, but must add proper masks, as the inputs get | |
| ;; truncated during packing. | |
| (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. | |
| (drop | |
| (struct.get_u $B_16 0 | |
| (struct.new $B_16 | |
| (global.get $g) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $test_signed (type $0) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (struct.new $A_8 | |
| ;; CHECK-NEXT: (i32.const 305419896) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.shr_s | |
| ;; CHECK-NEXT: (i32.shl | |
| ;; CHECK-NEXT: (i32.const 305419896) | |
| ;; CHECK-NEXT: (i32.const 24) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 24) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (struct.new $A_16 | |
| ;; CHECK-NEXT: (i32.const 305419896) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.shr_s | |
| ;; CHECK-NEXT: (i32.shl | |
| ;; CHECK-NEXT: (i32.const 305419896) | |
| ;; CHECK-NEXT: (i32.const 16) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 16) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (struct.new $B_16 | |
| ;; CHECK-NEXT: (global.get $g) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.shr_s | |
| ;; CHECK-NEXT: (i32.shl | |
| ;; CHECK-NEXT: (global.get $g) | |
| ;; CHECK-NEXT: (i32.const 16) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 16) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test_signed | |
| ;; As above, but with signed gets. | |
| (drop | |
| (struct.get_s $A_8 0 | |
| (struct.new $A_8 | |
| (i32.const 0x12345678) | |
| ) | |
| ) | |
| ) | |
| (drop | |
| (struct.get_s $A_16 0 | |
| (struct.new $A_16 | |
| (i32.const 0x12345678) | |
| ) | |
| ) | |
| ) | |
| (drop | |
| (struct.get_s $B_16 0 | |
| (struct.new $B_16 | |
| (global.get $g) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ) | |
| (module | |
| (rec | |
| ;; CHECK: (rec | |
| ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) | |
| (type $A (sub (struct (field (mut i32))))) | |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) | |
| (type $B (sub $A (struct (field (mut i32))))) | |
| ) | |
| ;; CHECK: (type $2 (func (param i32) (result i32))) | |
| ;; CHECK: (func $test (type $2) (param $0 i32) (result i32) | |
| ;; CHECK-NEXT: (local $A (ref $A)) | |
| ;; CHECK-NEXT: (local $B (ref $B)) | |
| ;; CHECK-NEXT: (struct.set $A 0 | |
| ;; CHECK-NEXT: (select (result (ref null $A)) | |
| ;; CHECK-NEXT: (block (result (ref null $A)) | |
| ;; CHECK-NEXT: (ref.null none) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (local.tee $B | |
| ;; CHECK-NEXT: (struct.new $B | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 0) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.get $A 0 | |
| ;; CHECK-NEXT: (select (result (ref null $A)) | |
| ;; CHECK-NEXT: (block (result (ref null $A)) | |
| ;; CHECK-NEXT: (ref.null none) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.new $A | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 0) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.get $B 0 | |
| ;; CHECK-NEXT: (local.get $B) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $0 i32) (result i32) | |
| (local $A (ref $A)) | |
| (local $B (ref $B)) | |
| ;; This set is part of a copy, as the value is a struct.get. The copy is on | |
| ;; $A, but the reference we operate on is an instance of $B, actually. So | |
| ;; the value read at the end is in fact 10. That is, this test verifies | |
| ;; that we track the copied value even though the copy is on $A but it | |
| ;; affects $B. | |
| (struct.set $A 0 | |
| ;; Use selects to make sure the types reaching the set and get are not more | |
| ;; precise than (ref null $A). This will look like a perfect copy of | |
| ;; $A->$A. | |
| (select (result (ref null $A)) | |
| (block (result (ref null $A)) | |
| (ref.null none) | |
| ) | |
| (local.tee $B | |
| (struct.new $B | |
| (i32.const 20) | |
| ) | |
| ) | |
| (i32.const 0) | |
| ) | |
| (struct.get $A 0 | |
| (select (result (ref null $A)) | |
| (block (result (ref null $A)) | |
| (ref.null none) | |
| ) | |
| (struct.new $A | |
| (i32.const 10) | |
| ) | |
| (i32.const 0) | |
| ) | |
| ) | |
| ) | |
| ;; This should not turn into 20, since just based on values flowing to | |
| ;; fields we cannot infer that (the value will be 10, but CFP cannot infer | |
| ;; that either - that would require tracking references through locals | |
| ;; etc.). | |
| (struct.get $B 0 | |
| (local.get $B) | |
| ) | |
| ) | |
| ) | |
| (module | |
| (rec | |
| ;; CHECK: (rec | |
| ;; CHECK-NEXT: (type $A (sub (struct (field i32)))) | |
| (type $A (sub (struct (field i32)))) | |
| ;; CHECK: (type $B (sub $A (struct (field i32)))) | |
| (type $B (sub $A (struct (field i32)))) | |
| ) | |
| ;; CHECK: (type $2 (func (param i32))) | |
| ;; CHECK: (func $test (type $2) (param $0 i32) | |
| ;; CHECK-NEXT: (local $A (ref $A)) | |
| ;; CHECK-NEXT: (local $B (ref $B)) | |
| ;; CHECK-NEXT: (local $A-exact (ref (exact $A))) | |
| ;; CHECK-NEXT: (local $B-exact (ref (exact $B))) | |
| ;; CHECK-NEXT: (local.set $A | |
| ;; CHECK-NEXT: (local.tee $A-exact | |
| ;; CHECK-NEXT: (struct.new $A | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (local.set $B | |
| ;; CHECK-NEXT: (local.tee $B-exact | |
| ;; CHECK-NEXT: (struct.new $B | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $A 0 | |
| ;; CHECK-NEXT: (local.get $A) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $B) | |
| ;; 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: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $A-exact) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $B-exact) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $0 i32) | |
| (local $A (ref $A)) | |
| (local $B (ref $B)) | |
| (local $A-exact (ref (exact $A))) | |
| (local $B-exact (ref (exact $B))) | |
| (local.set $A | |
| (local.tee $A-exact | |
| (struct.new $A | |
| (i32.const 10) | |
| ) | |
| ) | |
| ) | |
| (local.set $B | |
| (local.tee $B-exact | |
| (struct.new $B | |
| (i32.const 20) | |
| ) | |
| ) | |
| ) | |
| ;; We can optimize an inexact $B, but not $A. | |
| (drop | |
| (struct.get $A 0 | |
| (local.get $A) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $B 0 | |
| (local.get $B) | |
| ) | |
| ) | |
| ;; We can optimize both exact references. | |
| (drop | |
| (struct.get $A 0 | |
| (local.get $A-exact) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $B 0 | |
| (local.get $B-exact) | |
| ) | |
| ) | |
| ) | |
| ) | |
| (module | |
| ;; Same as above but now the fields are mutable. | |
| (rec | |
| ;; CHECK: (rec | |
| ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) | |
| (type $A (sub (struct (field (mut i32))))) | |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) | |
| (type $B (sub $A (struct (field (mut i32))))) | |
| ) | |
| ;; CHECK: (type $2 (func (param i32))) | |
| ;; CHECK: (func $test (type $2) (param $0 i32) | |
| ;; CHECK-NEXT: (local $A (ref $A)) | |
| ;; CHECK-NEXT: (local $B (ref $B)) | |
| ;; CHECK-NEXT: (local $A-exact (ref (exact $A))) | |
| ;; CHECK-NEXT: (local $B-exact (ref (exact $B))) | |
| ;; CHECK-NEXT: (local.set $A | |
| ;; CHECK-NEXT: (local.tee $A-exact | |
| ;; CHECK-NEXT: (struct.new $A | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (local.set $B | |
| ;; CHECK-NEXT: (local.tee $B-exact | |
| ;; CHECK-NEXT: (struct.new $B | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $A 0 | |
| ;; CHECK-NEXT: (local.get $A) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $B) | |
| ;; 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: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $A-exact) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $B-exact) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $0 i32) | |
| (local $A (ref $A)) | |
| (local $B (ref $B)) | |
| (local $A-exact (ref (exact $A))) | |
| (local $B-exact (ref (exact $B))) | |
| (local.set $A | |
| (local.tee $A-exact | |
| (struct.new $A | |
| (i32.const 10) | |
| ) | |
| ) | |
| ) | |
| (local.set $B | |
| (local.tee $B-exact | |
| (struct.new $B | |
| (i32.const 20) | |
| ) | |
| ) | |
| ) | |
| ;; We can optimize an inexact $B, but not $A. | |
| (drop | |
| (struct.get $A 0 | |
| (local.get $A) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $B 0 | |
| (local.get $B) | |
| ) | |
| ) | |
| ;; We should be able to optimize both exact references. | |
| (drop | |
| (struct.get $A 0 | |
| (local.get $A-exact) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $B 0 | |
| (local.get $B-exact) | |
| ) | |
| ) | |
| ) | |
| ) | |
| (module | |
| ;; Same as above but now we add no-op sets. | |
| (rec | |
| ;; CHECK: (rec | |
| ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) | |
| (type $A (sub (struct (field (mut i32))))) | |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) | |
| (type $B (sub $A (struct (field (mut i32))))) | |
| ) | |
| ;; CHECK: (type $2 (func (param i32))) | |
| ;; CHECK: (func $test (type $2) (param $0 i32) | |
| ;; CHECK-NEXT: (local $A (ref $A)) | |
| ;; CHECK-NEXT: (local $B (ref $B)) | |
| ;; CHECK-NEXT: (local $A-exact (ref (exact $A))) | |
| ;; CHECK-NEXT: (local $B-exact (ref (exact $B))) | |
| ;; CHECK-NEXT: (local.set $A | |
| ;; CHECK-NEXT: (local.tee $A-exact | |
| ;; CHECK-NEXT: (struct.new $A | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (local.set $B | |
| ;; CHECK-NEXT: (local.tee $B-exact | |
| ;; CHECK-NEXT: (struct.new $B | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.set $A 0 | |
| ;; CHECK-NEXT: (local.get $A-exact) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.set $B 0 | |
| ;; CHECK-NEXT: (local.get $B-exact) | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $A 0 | |
| ;; CHECK-NEXT: (local.get $A) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $B) | |
| ;; 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: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $A-exact) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $B-exact) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $0 i32) | |
| (local $A (ref $A)) | |
| (local $B (ref $B)) | |
| (local $A-exact (ref (exact $A))) | |
| (local $B-exact (ref (exact $B))) | |
| (local.set $A | |
| (local.tee $A-exact | |
| (struct.new $A | |
| (i32.const 10) | |
| ) | |
| ) | |
| ) | |
| (local.set $B | |
| (local.tee $B-exact | |
| (struct.new $B | |
| (i32.const 20) | |
| ) | |
| ) | |
| ) | |
| ;; No-op exact sets should not inhibit optimization. | |
| (struct.set $A 0 | |
| (local.get $A-exact) | |
| (i32.const 10) | |
| ) | |
| (struct.set $B 0 | |
| (local.get $B-exact) | |
| (i32.const 20) | |
| ) | |
| ;; We should be able to optimize an inexact $B, but not $A. | |
| (drop | |
| (struct.get $A 0 | |
| (local.get $A) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $B 0 | |
| (local.get $B) | |
| ) | |
| ) | |
| ;; We should be able to optimize both exact references TODO. | |
| (drop | |
| (struct.get $A 0 | |
| (local.get $A-exact) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $B 0 | |
| (local.get $B-exact) | |
| ) | |
| ) | |
| ) | |
| ) | |
| (module | |
| ;; Sets to a subtype should not affect exact gets of a supertype or sibling. | |
| (rec | |
| ;; CHECK: (rec | |
| ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) | |
| (type $A (sub (struct (field (mut i32))))) | |
| ;; CHECK: (type $B (sub $A (struct (field (mut i32))))) | |
| (type $B (sub $A (struct (field (mut i32))))) | |
| ;; CHECK: (type $C (sub $A (struct (field (mut i32))))) | |
| (type $C (sub $A (struct (field (mut i32))))) | |
| ) | |
| ;; CHECK: (type $3 (func)) | |
| ;; CHECK: (type $4 (func (param (ref $B)))) | |
| ;; CHECK: (type $5 (func (param (ref $A) (ref $B) (ref $C)))) | |
| ;; CHECK: (type $6 (func (param (ref (exact $A)) (ref (exact $B)) (ref (exact $C))))) | |
| ;; CHECK: (func $news (type $3) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $A | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $B | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $C | |
| ;; CHECK-NEXT: (i32.const 30) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $news | |
| (drop | |
| (struct.new $A | |
| (i32.const 10) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $B | |
| (i32.const 20) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $C | |
| (i32.const 30) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $set-B (type $4) (param $B (ref $B)) | |
| ;; CHECK-NEXT: (struct.set $B 0 | |
| ;; CHECK-NEXT: (local.get $B) | |
| ;; CHECK-NEXT: (i32.const 666) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $set-B (param $B (ref $B)) | |
| ;; Inhibits optimizations on B and inexact A only. | |
| (struct.set $B 0 | |
| (local.get $B) | |
| (i32.const 666) | |
| ) | |
| ) | |
| ;; CHECK: (func $inexact-gets (type $5) (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $A 0 | |
| ;; CHECK-NEXT: (local.get $A) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $B 0 | |
| ;; CHECK-NEXT: (local.get $B) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $C) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 30) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $inexact-gets (param $A (ref $A)) (param $B (ref $B)) (param $C (ref $C)) | |
| (drop | |
| (struct.get $A 0 | |
| (local.get $A) | |
| ) | |
| ) | |
| (drop | |
| (struct.get $B 0 | |
| (local.get $B) | |
| ) | |
| ) | |
| ;; This should be optimizable. | |
| (drop | |
| (struct.get $C 0 | |
| (local.get $C) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $exact-gets (type $6) (param $A-exact (ref (exact $A))) (param $B-exact (ref (exact $B))) (param $C-exact (ref (exact $C))) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $A-exact) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $B 0 | |
| ;; CHECK-NEXT: (local.get $B-exact) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $C-exact) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 30) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $exact-gets (param $A-exact (ref (exact $A))) | |
| (param $B-exact (ref (exact $B))) | |
| (param $C-exact (ref (exact $C))) | |
| (drop | |
| ;; This should be optimizable. | |
| (struct.get $A 0 | |
| (local.get $A-exact) | |
| ) | |
| ) | |
| ;; Not optimizable. | |
| (drop | |
| (struct.get $B 0 | |
| (local.get $B-exact) | |
| ) | |
| ) | |
| ;; This should be optimizable. | |
| (drop | |
| (struct.get $C 0 | |
| (local.get $C-exact) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; A type with two subtypes. A copy on the parent can affect either child. | |
| (module | |
| (rec | |
| ;; CHECK: (rec | |
| ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) | |
| (type $A (sub (struct (field (mut i32))))) | |
| ;; CHECK: (type $B1 (sub $A (struct (field (mut i32))))) | |
| (type $B1 (sub $A (struct (field (mut i32))))) | |
| ;; CHECK: (type $B2 (sub $A (struct (field (mut i32))))) | |
| (type $B2 (sub $A (struct (field (mut i32))))) | |
| ) | |
| ;; CHECK: (type $3 (func (param (ref null $A) (ref null $B1) (ref null $B2)))) | |
| ;; CHECK: (func $test (type $3) (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $A | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $B1 | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $B2 | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.set $A 0 | |
| ;; CHECK-NEXT: (local.get $A) | |
| ;; CHECK-NEXT: (struct.get $A 0 | |
| ;; CHECK-NEXT: (local.get $A) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $A 0 | |
| ;; CHECK-NEXT: (local.get $A) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $B1 0 | |
| ;; CHECK-NEXT: (local.get $B1) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $B2 0 | |
| ;; CHECK-NEXT: (local.get $B2) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2)) | |
| ;; A and B1 agree on their value in their construction. | |
| (drop | |
| (struct.new $A | |
| (i32.const 10) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $B1 | |
| (i32.const 10) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $B2 | |
| (i32.const 20) ;; this value is different from the others | |
| ) | |
| ) | |
| ;; Copy on $A | |
| (struct.set $A 0 | |
| (local.get $A) | |
| (struct.get $A 0 | |
| (local.get $A) | |
| ) | |
| ) | |
| ;; $A might read either child, so we can't infer. | |
| (drop | |
| (struct.get $A 0 | |
| (local.get $A) | |
| ) | |
| ) | |
| ;; $B1 should be only able to read 10, but the copy opens the possibility | |
| ;; of 20, so we can't optimize. | |
| (drop | |
| (struct.get $B1 0 | |
| (local.get $B1) | |
| ) | |
| ) | |
| ;; As with $B1, the copy stops us. | |
| (drop | |
| (struct.get $B2 0 | |
| (local.get $B2) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; As above, but now the copy is on B1. | |
| (module | |
| (rec | |
| ;; CHECK: (rec | |
| ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) | |
| (type $A (sub (struct (field (mut i32))))) | |
| ;; CHECK: (type $B1 (sub $A (struct (field (mut i32))))) | |
| (type $B1 (sub $A (struct (field (mut i32))))) | |
| ;; CHECK: (type $B2 (sub $A (struct (field (mut i32))))) | |
| (type $B2 (sub $A (struct (field (mut i32))))) | |
| ) | |
| ;; CHECK: (type $3 (func (param (ref null $A) (ref null $B1) (ref null $B2)))) | |
| ;; CHECK: (func $test (type $3) (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $A | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $B1 | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $B2 | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.set $B1 0 | |
| ;; CHECK-NEXT: (local.get $B1) | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $B1) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $A 0 | |
| ;; CHECK-NEXT: (local.get $A) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $B1) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $B2) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2)) | |
| (drop | |
| (struct.new $A | |
| (i32.const 10) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $B1 | |
| (i32.const 10) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $B2 | |
| (i32.const 20) | |
| ) | |
| ) | |
| ;; This changed from $A to $B1. | |
| (struct.set $B1 0 | |
| (local.get $B1) | |
| (struct.get $B1 0 | |
| (local.get $B1) | |
| ) | |
| ) | |
| ;; This might still read $B1 or $B2, with different values, so we can't | |
| ;; optimize. | |
| (drop | |
| (struct.get $A 0 | |
| (local.get $A) | |
| ) | |
| ) | |
| ;; The copy can only refer to $B1, so we can optimize here. | |
| (drop | |
| (struct.get $B1 0 | |
| (local.get $B1) | |
| ) | |
| ) | |
| ;; The copy can't refer to a $B2, so we can optimize here. | |
| (drop | |
| (struct.get $B2 0 | |
| (local.get $B2) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; As above, but now the copy is on B2. | |
| (module | |
| (rec | |
| ;; CHECK: (rec | |
| ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32))))) | |
| (type $A (sub (struct (field (mut i32))))) | |
| ;; CHECK: (type $B1 (sub $A (struct (field (mut i32))))) | |
| (type $B1 (sub $A (struct (field (mut i32))))) | |
| ;; CHECK: (type $B2 (sub $A (struct (field (mut i32))))) | |
| (type $B2 (sub $A (struct (field (mut i32))))) | |
| ) | |
| ;; CHECK: (type $3 (func (param (ref null $A) (ref null $B1) (ref null $B2)))) | |
| ;; CHECK: (func $test (type $3) (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $A | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $B1 | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $B2 | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.set $B2 0 | |
| ;; CHECK-NEXT: (local.get $B2) | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $B2) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $A 0 | |
| ;; CHECK-NEXT: (local.get $A) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $B1) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $B2) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2)) | |
| (drop | |
| (struct.new $A | |
| (i32.const 10) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $B1 | |
| (i32.const 10) | |
| ) | |
| ) | |
| (drop | |
| (struct.new $B2 | |
| (i32.const 20) | |
| ) | |
| ) | |
| ;; This changed from $A to $B2. | |
| (struct.set $B2 0 | |
| (local.get $B2) | |
| (struct.get $B2 0 | |
| (local.get $B2) | |
| ) | |
| ) | |
| ;; This might still read $B1 or $B2, with different values, so we can't | |
| ;; optimize. | |
| (drop | |
| (struct.get $A 0 | |
| (local.get $A) | |
| ) | |
| ) | |
| ;; The copy can't refer to a $B1, so we can optimize here. | |
| (drop | |
| (struct.get $B1 0 | |
| (local.get $B1) | |
| ) | |
| ) | |
| ;; $B2 is copied to itself, and nothing else, so we can optimize here. | |
| (drop | |
| (struct.get $B2 0 | |
| (local.get $B2) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; $C is created with two values for its field: a global.get, and a copy from | |
| ;; another $C, which does not expand the set of possible values. We should not | |
| ;; get confused about $B, its sibling, which is never created, and whose field | |
| ;; has an incompatible type. | |
| (module | |
| (rec | |
| ;; CHECK: (rec | |
| ;; CHECK-NEXT: (type $X (sub (struct))) | |
| (type $X (sub (struct))) | |
| ;; CHECK: (type $Y (sub final $X (struct))) | |
| (type $Y (sub final $X (struct))) | |
| ;; CHECK: (type $Z (sub final $X (struct))) | |
| (type $Z (sub final $X (struct))) | |
| ;; CHECK: (type $A (sub (struct (field (ref null $X))))) | |
| (type $A (sub (struct (field (ref null $X))))) | |
| ;; CHECK: (type $B (sub final $A (struct (field (ref null $Y))))) | |
| (type $B (sub final $A (struct (field (ref null $Y))))) | |
| ;; CHECK: (type $C (sub final $A (struct (field (ref null $Z))))) | |
| (type $C (sub final $A (struct (field (ref null $Z))))) | |
| ) | |
| ;; CHECK: (type $6 (func)) | |
| ;; CHECK: (type $7 (func (param (ref null $C)))) | |
| ;; CHECK: (type $8 (func (param (ref null $A)) (result (ref null $X)))) | |
| ;; CHECK: (type $9 (func (param (ref null $B)) (result (ref null $Y)))) | |
| ;; CHECK: (global $global (ref null $Z) (struct.new_default $Z)) | |
| (global $global (ref null $Z) (struct.new_default $Z)) | |
| ;; CHECK: (func $new (type $6) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $C | |
| ;; CHECK-NEXT: (global.get $global) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $new | |
| (drop | |
| (struct.new $C | |
| (global.get $global) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $copy (type $7) (param $C (ref null $C)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new $C | |
| ;; CHECK-NEXT: (block (result (ref null $Z)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $C) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (global.get $global) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $copy (param $C (ref null $C)) | |
| ;; The struct.get here can be optimized to a global.get. | |
| (drop | |
| (struct.new $C | |
| (struct.get $C 0 | |
| (local.get $C) | |
| ) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $get-A (type $8) (param $A (ref null $A)) (result (ref null $X)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $A) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (global.get $global) | |
| ;; CHECK-NEXT: ) | |
| (func $get-A (param $A (ref null $A)) (result (ref null $X)) | |
| ;; The struct.get here can be optimized to a global.get. While we never | |
| ;; create an $A, a $C might be referred to by an $A reference, and the | |
| ;; global.get is the only possible value. | |
| (struct.get $A 0 | |
| (local.get $A) | |
| ) | |
| ) | |
| ;; CHECK: (func $get-B (type $9) (param $B (ref null $B)) (result (ref null $Y)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (local.get $B) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (unreachable) | |
| ;; CHECK-NEXT: ) | |
| (func $get-B (param $B (ref null $B)) (result (ref null $Y)) | |
| ;; This should not be optimized to a global.get: no $B is created, and we | |
| ;; cannot refer to anything that is actually created. If we mistakenly | |
| ;; thought this field can contain the global.get (as we do for the parent | |
| ;; $A) then we would error here: $B's field contains $Y, but the global is | |
| ;; is of a sibling type $Z. Instead, we can add an unreachable here, as no | |
| ;; valid value is possible. | |
| (struct.get $B 0 | |
| (local.get $B) | |
| ) | |
| ) | |
| ) | |
| ;; Atomic accesses require special handling | |
| (module | |
| (rec | |
| ;; CHECK: (rec | |
| ;; CHECK-NEXT: (type $shared (shared (struct (field (mut i32))))) | |
| (type $shared (shared (struct (mut i32)))) | |
| ;; CHECK: (type $unwritten (shared (struct (field (mut i32))))) | |
| (type $unwritten (shared (struct (mut i32)))) | |
| ) | |
| ;; CHECK: (type $2 (func)) | |
| ;; CHECK: (type $3 (func (param (ref $shared)))) | |
| ;; CHECK: (type $4 (func (param (ref $unwritten)))) | |
| ;; CHECK: (func $init (type $2) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.new_default $shared) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $init | |
| (drop | |
| (struct.new_default $shared) | |
| ) | |
| ) | |
| ;; CHECK: (func $gets (type $3) (param $0 (ref $shared)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block (result i32) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (ref.as_non_null | |
| ;; CHECK-NEXT: (local.get $0) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (i32.const 0) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.atomic.get acqrel $shared 0 | |
| ;; CHECK-NEXT: (local.get $0) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.atomic.get $shared 0 | |
| ;; CHECK-NEXT: (local.get $0) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $gets (param (ref $shared)) | |
| (drop | |
| (struct.get $shared 0 | |
| (local.get 0) | |
| ) | |
| ) | |
| (drop | |
| ;; This can be optimized in principle, but our analysis cannot yet prove | |
| ;; there is no synchronization. TODO. | |
| (struct.atomic.get acqrel $shared 0 | |
| (local.get 0) | |
| ) | |
| ) | |
| (drop | |
| ;; Same as above. | |
| (struct.atomic.get $shared 0 | |
| (local.get 0) | |
| ) | |
| ) | |
| ) | |
| ;; CHECK: (func $traps (type $4) (param $0 (ref $unwritten)) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (local.get $0) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (unreachable) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (local.get $0) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (unreachable) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (block | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (local.get $0) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (unreachable) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $traps (param (ref $unwritten)) | |
| ;; This are all optimizable because they are known to trap. No fences are | |
| ;; necessary. | |
| (drop | |
| (struct.get $unwritten 0 | |
| (local.get 0) | |
| ) | |
| ) | |
| (drop | |
| (struct.atomic.get acqrel $unwritten 0 | |
| (local.get 0) | |
| ) | |
| ) | |
| (drop | |
| (struct.atomic.get $unwritten 0 | |
| (local.get 0) | |
| ) | |
| ) | |
| ) | |
| ) | |
| (module | |
| ;; CHECK: (type $A (struct (field (mut i32)))) | |
| (type $A (struct (field (mut i32)))) | |
| ;; CHECK: (type $1 (func)) | |
| ;; CHECK: (func $test (type $1) | |
| ;; CHECK-NEXT: (local $A.exact (ref (exact $A))) | |
| ;; CHECK-NEXT: (local.set $A.exact | |
| ;; CHECK-NEXT: (struct.new $A | |
| ;; CHECK-NEXT: (i32.const 10) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (struct.set $A 0 | |
| ;; CHECK-NEXT: (local.get $A.exact) | |
| ;; CHECK-NEXT: (i32.const 20) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: (drop | |
| ;; CHECK-NEXT: (struct.get $A 0 | |
| ;; CHECK-NEXT: (local.get $A.exact) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| ;; CHECK-NEXT: ) | |
| (func $test | |
| (local $A.exact (ref (exact $A))) | |
| (local.set $A.exact | |
| (struct.new $A | |
| (i32.const 10) | |
| ) | |
| ) | |
| ;; This set prevents us from optimizing. We should not be confused by the | |
| ;; exactness of the ref. | |
| (struct.set $A 0 | |
| (local.get $A.exact) | |
| (i32.const 20) | |
| ) | |
| (drop | |
| (struct.get $A 0 | |
| (local.get $A.exact) | |
| ) | |
| ) | |
| ) | |
| ) |