| ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. |
| |
| ;; RUN: foreach %s %t wasm-opt --precompute-propagate -all -S -o - | filecheck %s |
| |
| (module |
| ;; CHECK: (type $struct-imm (struct (field i32))) |
| (type $struct-imm (struct i32)) |
| |
| ;; CHECK: (type $struct-mut (struct (field (mut i32)))) |
| (type $struct-mut (struct (mut i32))) |
| |
| ;; CHECK: (type $array-mut (array (mut i32))) |
| |
| ;; CHECK: (type $array-imm (array i32)) |
| (type $array-imm (array i32)) |
| |
| (type $array-mut (array (mut i32))) |
| |
| ;; CHECK: (func $propagate-struct (type $2) |
| ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) |
| ;; CHECK-NEXT: (local $ref-mut (ref null $struct-mut)) |
| ;; CHECK-NEXT: (local.set $ref-imm |
| ;; CHECK-NEXT: (struct.new $struct-imm |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $ref-mut |
| ;; CHECK-NEXT: (struct.new $struct-mut |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (struct.get $struct-mut 0 |
| ;; CHECK-NEXT: (local.get $ref-mut) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $propagate-struct |
| (local $ref-imm (ref null $struct-imm)) |
| (local $ref-mut (ref null $struct-mut)) |
| ;; We can propagate from an immutable field of a struct created in this |
| ;; function. |
| (local.set $ref-imm |
| (struct.new $struct-imm |
| (i32.const 1) |
| ) |
| ) |
| (call $helper |
| (struct.get $struct-imm 0 |
| (local.get $ref-imm) |
| ) |
| ) |
| ;; But the same thing on a mutable field fails. |
| (local.set $ref-mut |
| (struct.new $struct-mut |
| (i32.const 1) |
| ) |
| ) |
| (call $helper |
| (struct.get $struct-mut 0 |
| (local.get $ref-mut) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $propagate-array (type $2) |
| ;; CHECK-NEXT: (local $ref-imm (ref null $array-imm)) |
| ;; CHECK-NEXT: (local $ref-mut (ref null $array-mut)) |
| ;; CHECK-NEXT: (local.set $ref-imm |
| ;; CHECK-NEXT: (array.new_fixed $array-imm 3 |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: (i32.const 30) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 30) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $ref-mut |
| ;; CHECK-NEXT: (array.new_fixed $array-mut 3 |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: (i32.const 30) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (array.get $array-mut |
| ;; CHECK-NEXT: (local.get $ref-mut) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $propagate-array |
| (local $ref-imm (ref null $array-imm)) |
| (local $ref-mut (ref null $array-mut)) |
| ;; We can propagate from a slot in an immutable array created in this |
| ;; function, and also get the length. |
| (local.set $ref-imm |
| (array.new_fixed $array-imm 3 |
| (i32.const 10) |
| (i32.const 20) |
| (i32.const 30) |
| ) |
| ) |
| (call $helper |
| (array.get $array-imm ;; this returns 30 |
| (local.get $ref-imm) |
| (i32.const 2) |
| ) |
| ) |
| (call $helper |
| (array.len ;; this returns 3 |
| (local.get $ref-imm) |
| ) |
| ) |
| ;; But the same thing on a mutable array fails. |
| (local.set $ref-mut |
| (array.new_fixed $array-mut 3 |
| (i32.const 10) |
| (i32.const 20) |
| (i32.const 30) |
| ) |
| ) |
| (call $helper |
| (array.get $array-mut |
| (local.get $ref-mut) |
| (i32.const 2) |
| ) |
| ) |
| ;; We can, however, optimize array.len in both cases as that is an |
| ;; immutable property. |
| (call $helper |
| (array.len ;; this returns 3 |
| (local.get $ref-mut) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $non-constant (type $1) (param $param i32) |
| ;; CHECK-NEXT: (local $ref (ref null $struct-imm)) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct-imm |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (struct.get $struct-imm 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $non-constant (param $param i32) |
| (local $ref (ref null $struct-imm)) |
| (local.set $ref |
| (struct.new $struct-imm |
| ;; This value is not constant, so we have nothing to propagate. |
| (local.get $param) |
| ) |
| ) |
| (call $helper |
| (struct.get $struct-imm 0 |
| (local.get $ref) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $unreachable (type $2) |
| ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) |
| ;; CHECK-NEXT: (local.tee $ref-imm |
| ;; 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: (call $helper |
| ;; CHECK-NEXT: (struct.get $struct-imm 0 |
| ;; CHECK-NEXT: (local.get $ref-imm) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $unreachable |
| (local $ref-imm (ref null $struct-imm)) |
| ;; Test we do not error on an unreachable value. |
| (local.set $ref-imm |
| (struct.new $struct-imm |
| (unreachable) |
| ) |
| ) |
| (call $helper |
| (struct.get $struct-imm 0 |
| (local.get $ref-imm) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $param (type $6) (param $ref-imm (ref null $struct-imm)) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (struct.get $struct-imm 0 |
| ;; CHECK-NEXT: (local.get $ref-imm) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $param (param $ref-imm (ref null $struct-imm)) |
| ;; Test we ignore a param value, whose data we do not know. |
| (call $helper |
| (struct.get $struct-imm 0 |
| (local.get $ref-imm) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $local-null (type $2) |
| ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $local-null |
| (local $ref-imm (ref null $struct-imm)) |
| ;; Test we ignore a local value that is null. |
| ;; TODO: this could be precomputed to an unreachable |
| (call $helper |
| (struct.get $struct-imm 0 |
| (local.get $ref-imm) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $local-unknown (type $1) (param $x i32) |
| ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $ref-imm |
| ;; CHECK-NEXT: (struct.new $struct-imm |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (local.set $ref-imm |
| ;; CHECK-NEXT: (struct.new $struct-imm |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (struct.get $struct-imm 0 |
| ;; CHECK-NEXT: (local.get $ref-imm) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $local-unknown (param $x i32) |
| (local $ref-imm (ref null $struct-imm)) |
| ;; Do not propagate if a local has more than one possible struct.new with |
| ;; different values. |
| (if |
| (local.get $x) |
| (then |
| (local.set $ref-imm |
| (struct.new $struct-imm |
| (i32.const 1) |
| ) |
| ) |
| ) |
| (else |
| (local.set $ref-imm |
| (struct.new $struct-imm |
| (i32.const 2) |
| ) |
| ) |
| ) |
| ) |
| (call $helper |
| (struct.get $struct-imm 0 |
| (local.get $ref-imm) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $local-unknown-ref-same-value (type $1) (param $x i32) |
| ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $ref-imm |
| ;; CHECK-NEXT: (struct.new $struct-imm |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (local.set $ref-imm |
| ;; CHECK-NEXT: (struct.new $struct-imm |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (struct.get $struct-imm 0 |
| ;; CHECK-NEXT: (local.get $ref-imm) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $local-unknown-ref-same-value (param $x i32) |
| (local $ref-imm (ref null $struct-imm)) |
| ;; As above, but the two different refs have the same value, so we can in |
| ;; theory optimize. However, atm we do nothing here, as the analysis stops |
| ;; when it sees it cannot propagate the local value (the ref, which has two |
| ;; possible values). |
| (if |
| (local.get $x) |
| (then |
| (local.set $ref-imm |
| (struct.new $struct-imm |
| (i32.const 1) |
| ) |
| ) |
| ) |
| (else |
| (local.set $ref-imm |
| (struct.new $struct-imm |
| (i32.const 1) |
| ) |
| ) |
| ) |
| ) |
| (call $helper |
| (struct.get $struct-imm 0 |
| (local.get $ref-imm) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $propagate-multi-refs (type $1) (param $x i32) |
| ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $ref-imm |
| ;; CHECK-NEXT: (struct.new $struct-imm |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (local.set $ref-imm |
| ;; CHECK-NEXT: (struct.new $struct-imm |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $propagate-multi-refs (param $x i32) |
| (local $ref-imm (ref null $struct-imm)) |
| ;; Propagate more than once in a function, using the same local that is |
| ;; reused. |
| (if |
| (local.get $x) |
| (then |
| (block |
| (local.set $ref-imm |
| (struct.new $struct-imm |
| (i32.const 1) |
| ) |
| ) |
| (call $helper |
| (struct.get $struct-imm 0 |
| (local.get $ref-imm) |
| ) |
| ) |
| ) |
| ) |
| (else |
| (block |
| (local.set $ref-imm |
| (struct.new $struct-imm |
| (i32.const 2) |
| ) |
| ) |
| (call $helper |
| (struct.get $struct-imm 0 |
| (local.get $ref-imm) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $propagate-multi-values (type $1) (param $x i32) |
| ;; CHECK-NEXT: (local $ref-imm (ref null $struct-imm)) |
| ;; CHECK-NEXT: (local.set $ref-imm |
| ;; CHECK-NEXT: (struct.new $struct-imm |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $propagate-multi-values (param $x i32) |
| (local $ref-imm (ref null $struct-imm)) |
| ;; Propagate a ref's value more than once |
| (local.set $ref-imm |
| (struct.new $struct-imm |
| (i32.const 1) |
| ) |
| ) |
| (call $helper |
| (struct.get $struct-imm 0 |
| (local.get $ref-imm) |
| ) |
| ) |
| (call $helper |
| (struct.get $struct-imm 0 |
| (local.get $ref-imm) |
| ) |
| ) |
| (call $helper |
| (struct.get $struct-imm 0 |
| (local.get $ref-imm) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $helper (type $1) (param $0 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $helper (param i32)) |
| ) |
| |
| (module |
| ;; One field is immutable, the other is not, so we can only propagate the |
| ;; former. |
| ;; CHECK: (type $struct (struct (field (mut i32)) (field i32))) |
| (type $struct (struct (mut i32) i32)) |
| |
| ;; CHECK: (func $propagate (type $1) |
| ;; CHECK-NEXT: (local $ref (ref null $struct)) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $propagate |
| (local $ref (ref null $struct)) |
| ;; We can propagate from an immutable field of a struct created in this |
| ;; function. |
| (local.set $ref |
| (struct.new $struct |
| (i32.const 1) |
| (i32.const 2) |
| ) |
| ) |
| (call $helper |
| (struct.get $struct 0 |
| (local.get $ref) |
| ) |
| ) |
| (call $helper |
| (struct.get $struct 1 |
| (local.get $ref) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $helper (type $2) (param $0 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $helper (param i32)) |
| ) |
| |
| (module |
| ;; Create an immutable vtable in an immutable field, which lets us read from |
| ;; it. |
| |
| ;; CHECK: (type $vtable (struct (field funcref))) |
| (type $vtable (struct funcref)) |
| ;; CHECK: (type $object (struct (field (ref $vtable)))) |
| (type $object (struct (ref $vtable))) |
| |
| ;; CHECK: (func $nested-creations (type $2) |
| ;; CHECK-NEXT: (local $ref (ref null $object)) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $object |
| ;; CHECK-NEXT: (struct.new $vtable |
| ;; CHECK-NEXT: (ref.func $nested-creations) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (ref.func $nested-creations) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $nested-creations |
| (local $ref (ref null $object)) |
| ;; Create an object with a reference to another object, and propagate |
| ;; through both of them to a constant value, which saves two struct.gets. |
| (local.set $ref |
| (struct.new $object |
| (struct.new $vtable |
| (ref.func $nested-creations) |
| ) |
| ) |
| ) |
| (call $helper |
| (struct.get $vtable 0 |
| (struct.get $object 0 |
| (local.get $ref) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $helper (type $3) (param $0 funcref) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $helper (param funcref)) |
| ) |
| |
| (module |
| ;; As above, but make $vtable not immutable, which prevents optimization. |
| |
| ;; CHECK: (type $vtable (struct (field (mut funcref)))) |
| (type $vtable (struct (mut funcref))) |
| ;; CHECK: (type $object (struct (field (ref $vtable)))) |
| (type $object (struct (ref $vtable))) |
| |
| ;; CHECK: (func $nested-creations (type $2) |
| ;; CHECK-NEXT: (local $ref (ref null $object)) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $object |
| ;; CHECK-NEXT: (struct.new $vtable |
| ;; CHECK-NEXT: (ref.func $nested-creations) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (struct.get $vtable 0 |
| ;; CHECK-NEXT: (struct.get $object 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $nested-creations |
| (local $ref (ref null $object)) |
| (local.set $ref |
| (struct.new $object |
| (struct.new $vtable |
| (ref.func $nested-creations) |
| ) |
| ) |
| ) |
| (call $helper |
| (struct.get $vtable 0 |
| ;; Note that we *can* precompute the first struct.get here, but there |
| ;; is no constant expression we can emit for it, so we do nothing. |
| (struct.get $object 0 |
| (local.get $ref) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $helper (type $3) (param $0 funcref) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $helper (param funcref)) |
| ) |
| |
| |
| (module |
| ;; As above, but make $object not immutable, which prevents optimization. |
| |
| ;; CHECK: (type $vtable (struct (field funcref))) |
| (type $vtable (struct funcref)) |
| ;; CHECK: (type $object (struct (field (mut (ref $vtable))))) |
| (type $object (struct (mut (ref $vtable)))) |
| |
| ;; CHECK: (func $nested-creations (type $2) |
| ;; CHECK-NEXT: (local $ref (ref null $object)) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $object |
| ;; CHECK-NEXT: (struct.new $vtable |
| ;; CHECK-NEXT: (ref.func $nested-creations) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (struct.get $vtable 0 |
| ;; CHECK-NEXT: (struct.get $object 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $nested-creations |
| (local $ref (ref null $object)) |
| (local.set $ref |
| (struct.new $object |
| (struct.new $vtable |
| (ref.func $nested-creations) |
| ) |
| ) |
| ) |
| (call $helper |
| (struct.get $vtable 0 |
| (struct.get $object 0 |
| (local.get $ref) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $helper (type $3) (param $0 funcref) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $helper (param funcref)) |
| ) |
| |
| (module |
| ;; Create an immutable vtable in an immutable global, which we can optimize |
| ;; with. |
| |
| ;; CHECK: (type $vtable (struct (field funcref))) |
| (type $vtable (struct funcref)) |
| ;; CHECK: (type $object (struct (field (ref $vtable)))) |
| (type $object (struct (ref $vtable))) |
| |
| ;; CHECK: (global $vtable (ref $vtable) (struct.new $vtable |
| ;; CHECK-NEXT: (ref.func $nested-creations) |
| ;; CHECK-NEXT: )) |
| (global $vtable (ref $vtable) |
| (struct.new $vtable |
| (ref.func $nested-creations) |
| ) |
| ) |
| |
| ;; CHECK: (func $nested-creations (type $2) |
| ;; CHECK-NEXT: (local $ref (ref null $object)) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $object |
| ;; CHECK-NEXT: (global.get $vtable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (ref.func $nested-creations) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $nested-creations |
| (local $ref (ref null $object)) |
| (local.set $ref |
| (struct.new $object |
| (global.get $vtable) |
| ) |
| ) |
| (call $helper |
| (struct.get $vtable 0 |
| (struct.get $object 0 |
| (local.get $ref) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $helper (type $3) (param $0 funcref) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $helper (param funcref)) |
| ) |
| |
| (module |
| ;; Create an immutable vtable in an mutable global, whose mutability prevents |
| ;; optimization. |
| |
| ;; CHECK: (type $vtable (struct (field funcref))) |
| (type $vtable (struct funcref)) |
| ;; CHECK: (type $object (struct (field (ref $vtable)))) |
| (type $object (struct (ref $vtable))) |
| |
| ;; CHECK: (global $vtable (mut (ref $vtable)) (struct.new $vtable |
| ;; CHECK-NEXT: (ref.func $nested-creations) |
| ;; CHECK-NEXT: )) |
| (global $vtable (mut (ref $vtable)) |
| (struct.new $vtable |
| (ref.func $nested-creations) |
| ) |
| ) |
| |
| ;; CHECK: (func $nested-creations (type $2) |
| ;; CHECK-NEXT: (local $ref (ref null $object)) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $object |
| ;; CHECK-NEXT: (global.get $vtable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (struct.get $vtable 0 |
| ;; CHECK-NEXT: (struct.get $object 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $nested-creations |
| (local $ref (ref null $object)) |
| (local.set $ref |
| (struct.new $object |
| (global.get $vtable) |
| ) |
| ) |
| (call $helper |
| (struct.get $vtable 0 |
| (struct.get $object 0 |
| (local.get $ref) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $helper (type $3) (param $0 funcref) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $helper (param funcref)) |
| ) |
| |
| (module |
| ;; Create an immutable vtable in an immutable global, but using an array |
| ;; instead of a struct. |
| |
| ;; CHECK: (type $vtable (array funcref)) |
| (type $vtable (array funcref)) |
| ;; CHECK: (type $object (struct (field (ref $vtable)))) |
| (type $object (struct (ref $vtable))) |
| |
| ;; CHECK: (global $vtable (ref $vtable) (array.new_fixed $vtable 1 |
| ;; CHECK-NEXT: (ref.func $nested-creations) |
| ;; CHECK-NEXT: )) |
| (global $vtable (ref $vtable) |
| (array.new_fixed $vtable 1 |
| (ref.func $nested-creations) |
| ) |
| ) |
| |
| ;; CHECK: (func $nested-creations (type $2) (param $param i32) |
| ;; CHECK-NEXT: (local $ref (ref null $object)) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $object |
| ;; CHECK-NEXT: (global.get $vtable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (ref.func $nested-creations) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (array.get $vtable |
| ;; CHECK-NEXT: (struct.get $object 0 |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $nested-creations (param $param i32) |
| (local $ref (ref null $object)) |
| (local.set $ref |
| (struct.new $object |
| (global.get $vtable) |
| ) |
| ) |
| (call $helper |
| (array.get $vtable |
| (struct.get $object 0 |
| (local.get $ref) |
| ) |
| (i32.const 0) |
| ) |
| ) |
| ;; The second operation here uses a param for the array index, which is not |
| ;; constant. |
| (call $helper |
| (array.get $vtable |
| (struct.get $object 0 |
| (local.get $ref) |
| ) |
| (local.get $param) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $helper (type $3) (param $0 funcref) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $helper (param funcref)) |
| ) |
| |
| (module |
| ;; A j2wasm-like itable pattern: An itable is an array of (possibly-null) |
| ;; data that is filled with vtables of different types. On usage, we do a |
| ;; cast of the vtable type. |
| |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $itable (array structref)) |
| (type $itable (array (ref null struct))) |
| |
| ;; CHECK: (type $object (struct (field (ref $itable)))) |
| (type $object (struct (ref $itable))) |
| |
| ;; CHECK: (type $vtable-0 (struct (field funcref))) |
| (type $vtable-0 (struct funcref)) |
| |
| ;; CHECK: (type $vtable-1 (struct (field funcref))) |
| (type $vtable-1 (struct funcref)) |
| ) |
| |
| ;; CHECK: (global $itable (ref $itable) (array.new_fixed $itable 2 |
| ;; CHECK-NEXT: (struct.new $vtable-0 |
| ;; CHECK-NEXT: (ref.func $nested-creations) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.new $vtable-1 |
| ;; CHECK-NEXT: (ref.func $helper) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: )) |
| (global $itable (ref $itable) |
| (array.new_fixed $itable 2 |
| (struct.new $vtable-0 |
| (ref.func $nested-creations) |
| ) |
| (struct.new $vtable-1 |
| (ref.func $helper) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $nested-creations (type $4) |
| ;; CHECK-NEXT: (local $ref (ref null $object)) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new $object |
| ;; CHECK-NEXT: (global.get $itable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (ref.func $nested-creations) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (ref.func $helper) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $nested-creations |
| (local $ref (ref null $object)) |
| (local.set $ref |
| (struct.new $object |
| (global.get $itable) |
| ) |
| ) |
| ;; We can precompute all these operations away into the final constants. |
| (call $helper |
| (struct.get $vtable-0 0 |
| (ref.cast (ref null $vtable-0) |
| (array.get $itable |
| (struct.get $object 0 |
| (local.get $ref) |
| ) |
| (i32.const 0) |
| ) |
| ) |
| ) |
| ) |
| (call $helper |
| (struct.get $vtable-1 0 |
| (ref.cast (ref null $vtable-1) |
| (array.get $itable |
| (struct.get $object 0 |
| (local.get $ref) |
| ) |
| (i32.const 1) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $helper (type $5) (param $0 funcref) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $helper (param funcref)) |
| ) |