| ;; 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.set $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)) | |
| ) |