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