;; 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 -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 $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 $0 (func)) ;; 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 $3 (func (param (ref null $substruct)))) ;; CHECK: (func $create (type $0) ;; 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 $1 (func (param (ref null $struct)))) ;; CHECK: (type $substruct (sub $struct (struct (field (mut i32))))) (type $substruct (sub $struct (struct (mut i32)))) ;; CHECK: (type $3 (func (param (ref null $substruct)))) ;; CHECK: (func $create (type $1) (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 $0 (func)) ;; 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 $3 (func (param (ref null $struct)))) ;; CHECK: (func $create (type $0) ;; 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. (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) ) ) ) ) ;; Copies of a field to itself can be ignored. As a result, we can optimize both ;; of the gets here. (module ;; CHECK: (type $struct (struct (field (mut i32)))) (type $struct (struct (mut i32))) ;; CHECK: (type $1 (func (param (ref null $struct) (ref null $struct)))) ;; CHECK: (func $test (type $1) (param $struct (ref null $struct)) (param $other (ref null $struct)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $struct 0 ;; CHECK-NEXT: (local.get $struct) ;; 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 0) ;; 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 $other) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (param $struct (ref null $struct)) (param $other (ref null $struct)) (drop (struct.new_default $struct) ) ;; This copy does not actually introduce any new possible values, and so it ;; remains true that the only possible value is the default. (struct.set $struct 0 (local.get $struct) (struct.get $struct 0 (local.get $struct) ) ) (drop (struct.get $struct 0 (local.get $other) ) ) ) ) ;; Test of a near-copy, of a similar looking field (same index, and same field ;; type) but in a different struct. (module ;; CHECK: (type $struct (struct (field (mut f32)) (field (mut i32)))) (type $struct (struct (mut f32) (mut i32))) ;; CHECK: (type $other (struct (field (mut f64)) (field (mut i32)))) (type $other (struct (mut f64) (mut i32))) ;; CHECK: (type $2 (func (param (ref null $struct) (ref null $other)))) ;; CHECK: (func $test (type $2) (param $struct (ref null $struct)) (param $other (ref null $other)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.new_default $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (struct.set $struct 1 ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: (block ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (local.get $other) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (struct.get $struct 1 ;; CHECK-NEXT: (local.get $struct) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) (func $test (param $struct (ref null $struct)) (param $other (ref null $other)) (drop (struct.new_default $struct) ) ;; As this is not a copy, we cannot optimize struct.1's get lower down. (struct.set $struct 1 (local.get $struct) (struct.get $other 1 (local.get $other) ) ) (drop (struct.get $struct 1 (local.get $struct) ) ) ) ) ;; Test of a near-copy, of a different index. (module ;; CHECK: (type $struct (struct (field (mut i32)) (field (mut i32)))) (type $struct (struct (mut i32) (mut i32))) ;; 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: (struct.set $struct 0 ;; CHECK-NEXT: (local.get $struct) ;; 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 0) ;; 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_default $struct) ) ;; As this is not a copy, we cannot optimize struct.0's get lower down. (struct.set $struct 0 (local.get $struct) (struct.get $struct 1 (local.get $struct) ) ) (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 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) ) ) ) ) ) (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: (ref.null none) ;; 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: (struct.new $A ;; CHECK-NEXT: (i32.const 10) ;; 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 ;; This select is used to keep the type that reaches the struct.set $A, ;; and not $B, so it looks like a perfect copy of $A->$A. (select (result (ref null $A)) (ref.null $A) (local.tee $B (struct.new $B (i32.const 20) ) ) (i32.const 0) ) (struct.get $A 0 (struct.new $A (i32.const 10) ) ) ) ;; 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) ) ) ) ;; 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: (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)) (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. TODO (but GUFA ;; can do this) (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: (struct.get $B1 0 ;; CHECK-NEXT: (local.get $B1) ;; 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. TODO (but GUFA ;; can do this) (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) ) ) ) )