| ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. |
| |
| ;; RUN: wasm-opt %s --remove-unused-names --optimize-instructions --enable-reference-types --enable-gc -S -o - \ |
| ;; RUN: | filecheck %s |
| |
| (module |
| ;; CHECK: (type $struct (struct (field $i8 (mut i8)) (field $i16 (mut i16)) (field $i32 (mut i32)) (field $i64 (mut i64)))) |
| (type $struct (struct |
| (field $i8 (mut i8)) |
| (field $i16 (mut i16)) |
| (field $i32 (mut i32)) |
| (field $i64 (mut i64)) |
| )) |
| |
| ;; CHECK: (type $array (array (mut i8))) |
| |
| ;; CHECK: (type $A (sub (struct (field i32)))) |
| (type $A (sub (struct (field i32)))) |
| |
| (type $array (array (mut i8))) |
| |
| ;; CHECK: (type $B (sub $A (struct (field i32) (field i32) (field f32)))) |
| (type $B (sub $A (struct (field i32) (field i32) (field f32)))) |
| |
| ;; CHECK: (type $B-child (sub $B (struct (field i32) (field i32) (field f32) (field i64)))) |
| (type $B-child (sub $B (struct (field i32) (field i32) (field f32) (field i64)))) |
| |
| (type $empty (struct)) |
| |
| ;; CHECK: (type $void (sub (func))) |
| |
| ;; CHECK: (type $void2 (sub $void (func))) |
| |
| ;; CHECK: (type $C (sub $A (struct (field i32) (field i32) (field f64)))) |
| |
| ;; CHECK: (type $struct.ref (struct (field funcref))) |
| (type $struct.ref (struct (field funcref))) |
| |
| (type $C (sub $A (struct (field i32) (field i32) (field f64)))) |
| |
| (type $void (sub (func))) |
| |
| (type $void2 (sub $void (func))) |
| |
| ;; CHECK: (type $struct_i64 (func (param structref) (result i64))) |
| (type $struct_i64 (func (param (ref null struct)) (result i64))) |
| |
| ;; CHECK: (import "env" "get-i32" (func $get-i32 (type $8) (result i32))) |
| (import "env" "get-i32" (func $get-i32 (result i32))) |
| |
| ;; These functions test if an `if` with subtyped arms is correctly folded |
| ;; 1. if its `ifTrue` and `ifFalse` arms are identical (can fold) |
| ;; CHECK: (func $if-arms-subtype-fold (type $28) (result anyref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| (func $if-arms-subtype-fold (result anyref) |
| (if (result anyref) |
| (i32.const 0) |
| (then |
| (ref.null eq) |
| ) |
| (else |
| (ref.null eq) |
| ) |
| ) |
| ) |
| ;; 2. if its `ifTrue` and `ifFalse` arms are not identical (cannot fold) |
| ;; CHECK: (func $if-arms-subtype-nofold (type $29) (param $i31ref i31ref) (result anyref) |
| ;; CHECK-NEXT: (if (result anyref) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (local.get $i31ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $if-arms-subtype-nofold (param $i31ref i31ref) (result anyref) |
| (if (result anyref) |
| (i32.const 0) |
| (then |
| (ref.null none) |
| ) |
| (else |
| (local.get $i31ref) |
| ) |
| ) |
| ) |
| |
| ;; Stored values automatically truncate unneeded bytes. |
| ;; CHECK: (func $store-trunc (type $11) (param $x (ref null $struct)) |
| ;; CHECK-NEXT: (struct.set $struct $i8 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 35) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct $i16 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 9029) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct $i8 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (call $get-i32) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $store-trunc (param $x (ref null $struct)) |
| (struct.set $struct $i8 |
| (local.get $x) |
| (i32.const 0x123) ;; data over 0xff is unnecessary |
| ) |
| (struct.set $struct $i16 |
| (local.get $x) |
| (i32.const 0x12345) ;; data over 0xffff is unnecessary |
| ) |
| (struct.set $struct $i8 |
| (local.get $x) |
| (i32.and ;; truncating bits using an and is unnecessary |
| (call $get-i32) |
| (i32.const 0xff) |
| ) |
| ) |
| ) |
| |
| ;; Similar, but for arrays. |
| ;; CHECK: (func $store-trunc2 (type $16) (param $x (ref null $array)) |
| ;; CHECK-NEXT: (array.set $array |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 35) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $store-trunc2 (param $x (ref null $array)) |
| (array.set $array |
| (local.get $x) |
| (i32.const 0) |
| (i32.const 0x123) ;; data over 0xff is unnecessary |
| ) |
| ) |
| |
| ;; ref.is_null is not needed on a non-nullable value, and if something is |
| ;; cast to its own type, we don't need that either, etc. |
| ;; CHECK: (func $unneeded_test (type $17) (param $struct (ref $struct)) (param $func (ref func)) (param $i31 (ref i31)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $i31) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $unneeded_test |
| (param $struct (ref $struct)) |
| (param $func (ref func)) |
| (param $i31 (ref i31)) |
| (drop |
| (ref.is_null (local.get $struct)) |
| ) |
| (drop |
| (ref.test (ref func) (local.get $func)) |
| ) |
| (drop |
| (ref.test (ref i31) (local.get $i31)) |
| ) |
| ) |
| |
| ;; similar to $unneeded_is, but the values are nullable. we can at least |
| ;; leave just the null check. |
| ;; CHECK: (func $unneeded_test_null (type $18) (param $struct (ref null $struct)) (param $func funcref) (param $i31 i31ref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.eqz |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.eqz |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $i31) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $unneeded_test_null |
| (param $struct (ref null $struct)) |
| (param $func (ref null func)) |
| (param $i31 (ref null i31)) |
| (drop |
| (ref.is_null (local.get $struct)) |
| ) |
| ;; This can be optimized to !is_null rather than ref.test func, since we |
| ;; know the heap type is what we want, so the only possible issue is a null. |
| (drop |
| (ref.test (ref func) (local.get $func)) |
| ) |
| ;; This can be optimized similarly. |
| (drop |
| (ref.test (ref i31) (local.get $i31)) |
| ) |
| ) |
| |
| ;; ref.as_non_null is not needed on a non-nullable value, and if something is |
| ;; a func we don't need that either etc., and can just return the value. |
| ;; CHECK: (func $unneeded_cast (type $17) (param $struct (ref $struct)) (param $func (ref func)) (param $i31 (ref i31)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $i31) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $unneeded_cast |
| (param $struct (ref $struct)) |
| (param $func (ref func)) |
| (param $i31 (ref i31)) |
| (drop |
| (ref.as_non_null (local.get $struct)) |
| ) |
| (drop |
| (ref.cast (ref func) (local.get $func)) |
| ) |
| (drop |
| (ref.cast (ref i31) (local.get $i31)) |
| ) |
| ) |
| |
| ;; similar to $unneeded_cast, but the values are nullable. we can turn the |
| ;; more specific things into ref.as_non_null. |
| ;; CHECK: (func $unneeded_cast_null (type $18) (param $struct (ref null $struct)) (param $func funcref) (param $i31 i31ref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $i31) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $unneeded_cast_null |
| (param $struct (ref null $struct)) |
| (param $func (ref null func)) |
| (param $i31 (ref null i31)) |
| (drop |
| (ref.as_non_null (local.get $struct)) |
| ) |
| (drop |
| (ref.cast (ref func) (local.get $func)) |
| ) |
| (drop |
| (ref.cast (ref i31) (local.get $i31)) |
| ) |
| ) |
| |
| ;; CHECK: (func $unneeded_unreachability (type $5) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test (ref func) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block ;; (replaces unreachable RefCast we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $unneeded_unreachability |
| ;; unreachable instructions can simply be ignored |
| (drop |
| (ref.test (ref func) (unreachable)) |
| ) |
| (drop |
| (ref.cast (ref func) (unreachable)) |
| ) |
| ) |
| |
| ;; CHECK: (func $redundant-non-null-casts (type $30) (param $x (ref null $struct)) (param $y (ref null $array)) (param $f (ref null $void)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct $i8 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get_u $struct $i8 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.set $array |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (array.get_u $array |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: (i32.const 4) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (array.len |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call_ref $void |
| ;; CHECK-NEXT: (local.get $f) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $redundant-non-null-casts (param $x (ref null $struct)) (param $y (ref null $array)) (param $f (ref null $void)) |
| (drop |
| (ref.as_non_null |
| (ref.as_non_null |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| (struct.set $struct 0 |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| (i32.const 1) |
| ) |
| (drop |
| (struct.get_u $struct 0 |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| (array.set $array |
| (ref.as_non_null |
| (local.get $y) |
| ) |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| (drop |
| (array.get $array |
| (ref.as_non_null |
| (local.get $y) |
| ) |
| (i32.const 4) |
| ) |
| ) |
| (drop |
| (array.len |
| (ref.as_non_null |
| (local.get $y) |
| ) |
| ) |
| ) |
| (call_ref $void |
| (ref.as_non_null |
| (local.get $f) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $get-eqref (type $31) (result eqref) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $get-eqref (result eqref) |
| (unreachable) |
| ) |
| |
| ;; CHECK: (func $ref-eq (type $10) (param $x eqref) (param $y eqref) |
| ;; CHECK-NEXT: (local $lx eqref) |
| ;; CHECK-NEXT: (local $ly eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $lx |
| ;; CHECK-NEXT: (call $get-eqref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-eq (param $x eqref) (param $y eqref) |
| (local $lx eqref) |
| (local $ly eqref) |
| ;; identical parameters are equal |
| (drop |
| (ref.eq |
| (local.get $x) |
| (local.get $x) |
| ) |
| ) |
| ;; different ones might not be |
| (drop |
| (ref.eq |
| (local.get $x) |
| (local.get $y) |
| ) |
| ) |
| ;; identical locals are |
| (local.set $lx |
| (call $get-eqref) |
| ) |
| (drop |
| (ref.eq |
| (local.get $lx) |
| (local.get $lx) |
| ) |
| ) |
| ;; fallthroughs work ok (but we need --remove-unused-names so that we can |
| ;; trivially tell that there are no breaks) |
| (drop |
| (ref.eq |
| (block (result eqref) |
| (nop) |
| (local.get $x) |
| ) |
| (block (result eqref) |
| (nop) |
| (drop |
| (i32.const 10) |
| ) |
| (nop) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $nothing (type $5) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $nothing) |
| |
| ;; CHECK: (func $ref-eq-corner-cases (type $4) (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (call $nothing) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (call $nothing) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (struct.new_default $struct) |
| ;; CHECK-NEXT: (struct.new_default $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-eq-corner-cases (param $x eqref) |
| ;; side effects prevent optimization |
| (drop |
| (ref.eq |
| (block (result eqref) |
| (call $nothing) |
| (local.get $x) |
| ) |
| (block (result eqref) |
| (call $nothing) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ;; allocation prevents optimization |
| (drop |
| (ref.eq |
| (struct.new_default $struct) |
| (struct.new_default $struct) |
| ) |
| ) |
| ;; but irrelevant allocations do not prevent optimization |
| (drop |
| (ref.eq |
| (block (result eqref) |
| ;; an allocation that does not trouble us |
| (drop |
| (struct.new_default $struct) |
| ) |
| (local.get $x) |
| ) |
| (block (result eqref) |
| (drop |
| (struct.new_default $struct) |
| ) |
| ;; add a nop to make the two inputs to ref.eq not structurally equal, |
| ;; but in a way that does not matter (since only the value falling |
| ;; out does) |
| (nop) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ;; a tee does not prevent optimization, as we can fold the tee and the get. |
| (drop |
| (ref.eq |
| (local.tee $x |
| (local.get $x) |
| ) |
| (local.get $x) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-eq-ref-cast (type $4) (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (ref.cast (ref null $struct) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-eq-ref-cast (param $x eqref) |
| ;; it is almost valid to look through a cast, except that it might trap so |
| ;; there is a side effect |
| (drop |
| (ref.eq |
| (local.get $x) |
| (ref.cast (ref null $struct) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $flip-cast-of-as-non-null (type $19) (param $x anyref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $struct) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get_u $struct $i8 |
| ;; CHECK-NEXT: (ref.cast (ref $struct) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref none)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref i31) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $flip-cast-of-as-non-null (param $x anyref) |
| (drop |
| (ref.cast (ref $struct) |
| ;; this can be folded into the outer cast, which checks for null too |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| (drop |
| ;; an example of how this helps: the struct.get will trap on null anyhow |
| (struct.get_u $struct 0 |
| (ref.cast (ref $struct) |
| ;; this can be moved through the ref.cast null outward. |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ;; This will trap, so we can emit an unreachable. |
| (drop |
| (ref.cast (ref $struct) |
| (ref.cast (ref i31) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $flip-tee-of-as-non-null (type $19) (param $x anyref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $flip-tee-of-as-non-null (param $x anyref) |
| (drop |
| (local.tee $x |
| ;; this can be moved through the tee outward. |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $flip-tee-of-as-non-null-non-nullable (type $32) (param $x (ref any)) (param $y anyref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $flip-tee-of-as-non-null-non-nullable (param $x (ref any)) (param $y (ref null any)) |
| (drop |
| (local.tee $x |
| ;; this *cannnot* be moved through the tee outward, as the param is in |
| ;; fact non-nullable, and we depend on the ref.as_non_null in order to |
| ;; get a valid type to assign to it |
| (ref.as_non_null |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $ternary-identical-arms (type $33) (param $x i32) (param $y (ref null $struct)) (param $z (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (if (result (ref null $struct)) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (local.get $z) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ternary-identical-arms (param $x i32) (param $y (ref null $struct)) (param $z (ref null $struct)) |
| (drop |
| (if (result i32) |
| (local.get $x) |
| (then |
| (ref.is_null (local.get $y)) |
| ) |
| (else |
| (ref.is_null (local.get $z)) |
| ) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $select-identical-arms-but-side-effect (type $20) (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (select |
| ;; CHECK-NEXT: (struct.get_u $struct $i8 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.get_u $struct $i8 |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $z) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $select-identical-arms-but-side-effect (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) |
| (drop |
| (select |
| ;; the arms are equal but have side effects |
| (struct.get_u $struct 0 |
| (local.get $x) |
| ) |
| (struct.get_u $struct 0 |
| (local.get $y) |
| ) |
| (local.get $z) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $ternary-identical-arms-no-side-effect (type $34) (param $x (ref $struct)) (param $y (ref $struct)) (param $z i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get_u $struct $i8 |
| ;; CHECK-NEXT: (select (result (ref $struct)) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: (local.get $z) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ternary-identical-arms-no-side-effect (param $x (ref $struct)) (param $y (ref $struct)) (param $z i32) |
| (drop |
| (select |
| ;; the arms are equal and as the params are non-null, there are no possible side effects |
| (struct.get_u $struct 0 |
| (local.get $x) |
| ) |
| (struct.get_u $struct 0 |
| (local.get $y) |
| ) |
| (local.get $z) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $if-identical-arms-with-side-effect (type $20) (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.get_u $struct $i8 |
| ;; CHECK-NEXT: (if (result (ref null $struct)) |
| ;; CHECK-NEXT: (local.get $z) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $if-identical-arms-with-side-effect (param $x (ref null $struct)) (param $y (ref null $struct)) (param $z i32) |
| (drop |
| (if (result i32) |
| (local.get $z) |
| ;; the arms are equal and have side effects, but that is ok with an if |
| ;; which only executes one side anyhow |
| (then |
| (struct.get_u $struct 0 |
| (local.get $x) |
| ) |
| ) |
| (else |
| (struct.get_u $struct 0 |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-squared (type $4) (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $struct) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-squared (param $x eqref) |
| ;; Identical ref.casts can be folded together. |
| (drop |
| (ref.cast (ref null $struct) |
| (ref.cast (ref null $struct) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $ref-cast-squared-fallthrough (type $4) (param $x eqref) |
| ;; CHECK-NEXT: (local $1 (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (ref.cast (ref null $struct) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-squared-fallthrough (param $x eqref) |
| ;; A fallthrough in the middle does not prevent this optimization. |
| (drop |
| (ref.cast (ref null $struct) |
| (local.tee $x |
| (ref.cast (ref null $struct) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $ref-cast-cubed (type $4) (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $struct) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-cubed (param $x eqref) |
| ;; Three and more also work. |
| (drop |
| (ref.cast (ref null $struct) |
| (ref.cast (ref null $struct) |
| (ref.cast (ref null $struct) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $ref-cast-squared-different (type $4) (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast nullref |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-squared-different (param $x eqref) |
| ;; Different casts cannot be folded. We can emit a cast to null here, which |
| ;; is the only possible thing that can pass through. |
| (drop |
| (ref.cast (ref null $struct) |
| (ref.cast (ref null $empty) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-eq-null (type $4) (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-eq-null (param $x eqref) |
| ;; Equality to null can be done with ref.is_null. |
| (drop |
| (ref.eq |
| (local.get $x) |
| (ref.null eq) |
| ) |
| ) |
| (drop |
| (ref.eq |
| (ref.null eq) |
| (local.get $x) |
| ) |
| ) |
| ;; Also check that we turn a comparison of two nulls into 1, using the rule |
| ;; for comparing the same thing to itself (i.e., that we run that rule first |
| ;; and not the check for one of them being null, which would require more |
| ;; work afterwards). |
| (drop |
| (ref.eq |
| (ref.null eq) |
| (ref.null eq) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-eq-possible (type $10) (param $x eqref) (param $y eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (ref.cast (ref null $struct) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref null $array) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-eq-possible (param $x eqref) (param $y eqref) |
| ;; These casts are to types that are incompatible. However, it is possible |
| ;; they are both null, so we cannot optimize here. |
| (drop |
| (ref.eq |
| (ref.cast (ref null $struct) |
| (local.get $x) |
| ) |
| (ref.cast (ref null $array) |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-eq-impossible (type $10) (param $x eqref) (param $y eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $struct) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $array) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $struct) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $array) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $struct) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $array) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-eq-impossible (param $x eqref) (param $y eqref) |
| ;; As above but cast one of them to non-null, which proves they cannot be |
| ;; equal, and the result must be 0. |
| (drop |
| (ref.eq |
| (ref.cast (ref $struct) |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| (ref.cast (ref null $array) |
| (local.get $y) |
| ) |
| ) |
| ) |
| ;; As above but the cast is on the other one. |
| (drop |
| (ref.eq |
| (ref.cast (ref null $struct) |
| (local.get $x) |
| ) |
| (ref.cast (ref $array) |
| (ref.as_non_null |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| ;; As above but the cast is both. |
| (drop |
| (ref.eq |
| (ref.cast (ref $struct) |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| (ref.cast (ref $array) |
| (ref.as_non_null |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-eq-possible-b (type $10) (param $x eqref) (param $y eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-eq-possible-b (param $x eqref) (param $y eqref) |
| ;; As above but the casts are to things that are compatible, since B is a |
| ;; subtype of A, so we cannot optimize. |
| (drop |
| (ref.eq |
| (ref.cast (ref $A) |
| (local.get $x) |
| ) |
| (ref.cast (ref $B) |
| (local.get $y) |
| ) |
| ) |
| ) |
| ;; As above but flipped. |
| (drop |
| (ref.eq |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| (ref.cast (ref $A) |
| (local.get $y) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $hoist-LUB-danger (type $35) (param $x i32) (param $b (ref $B)) (param $c (ref $C)) (result i32) |
| ;; CHECK-NEXT: (if (result i32) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (struct.get $B 1 |
| ;; CHECK-NEXT: (local.get $b) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (struct.get $C 1 |
| ;; CHECK-NEXT: (local.get $c) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $hoist-LUB-danger (param $x i32) (param $b (ref $B)) (param $c (ref $C)) (result i32) |
| ;; In nominal typing, if we hoist the struct.get out of the if, then the if |
| ;; will have a new type, $A, but $A does not have field "1" which would be an |
| ;; error. We disallow subtyping for this reason. |
| ;; |
| ;; We also disallow subtyping in structural typing, even though atm there |
| ;; might not be a concrete risk there: future instructions might introduce |
| ;; such things, and it reduces the complexity of having differences with |
| ;; nominal typing. |
| (if (result i32) |
| (local.get $x) |
| (then |
| (struct.get $B 1 |
| (local.get $b) |
| ) |
| ) |
| (else |
| (struct.get $C 1 |
| (local.get $c) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-cast-of-non-null (type $36) (param $struct (ref $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref none)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $incompatible-cast-of-non-null (param $struct (ref $struct)) |
| (drop |
| (ref.cast (ref $array) |
| (local.get $struct) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-cast-of-null (type $11) (param $x (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $incompatible-cast-of-null (param $x (ref null $struct)) |
| (drop |
| (ref.cast (ref $array) |
| ;; The child is null, so the cast will trap. Replace it with an |
| ;; unreachable. |
| (ref.null none) |
| ) |
| ) |
| (drop |
| (ref.cast (ref $array) |
| ;; Even though the child type is non-null, it is still valid to do this |
| ;; transformation. In practice this code will trap before getting to our |
| ;; new unreachable. |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-cast-of-unknown (type $11) (param $struct (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast nullref |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $incompatible-cast-of-unknown (param $struct (ref null $struct)) |
| (drop |
| (ref.cast (ref null $array) |
| (local.get $struct) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-test (type $11) (param $struct (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $incompatible-test (param $struct (ref null $struct)) |
| (drop |
| ;; This test will definitely fail, so we can turn it into 0. |
| (ref.test (ref $array) |
| (local.get $struct) |
| ) |
| ) |
| (drop |
| ;; But this one might succeed due to a null, so don't optimize it away. |
| ;; We can however change it from ref.test to ref.is_null, as a null is the |
| ;; only possible way this will succeed. |
| (ref.test (ref null $array) |
| (local.get $struct) |
| ) |
| ) |
| (drop |
| ;; This one cannot succeed due to a null, so optimize it. |
| (ref.test (ref null $array) |
| (ref.as_non_null |
| (local.get $struct) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $subtype-compatible (type $21) (param $A (ref null $A)) (param $B (ref null $B)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test (ref $B) |
| ;; CHECK-NEXT: (local.get $A) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.eqz |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $B) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $B) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $B) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $B) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $subtype-compatible (param $A (ref null $A)) (param $B (ref null $B)) |
| (drop |
| ;; B is a subtype of A, so this can work. |
| (ref.test (ref $B) |
| (local.get $A) |
| ) |
| ) |
| (drop |
| ;; The other direction can work too. It will only fail if the input is a |
| ;; null, so we can switch to checking that. |
| (ref.test (ref $A) |
| (local.get $B) |
| ) |
| ) |
| (drop |
| ;; If the test is nullable, this will succeed. |
| (ref.test (ref null $A) |
| (local.get $B) |
| ) |
| ) |
| (drop |
| ;; We will also succeed if the input is non-nullable. |
| (ref.test (ref $A) |
| (ref.as_non_null |
| (local.get $B) |
| ) |
| ) |
| ) |
| (drop |
| ;; Or if the test is nullable and the input is non-nullable. |
| (ref.test (ref null $A) |
| (ref.as_non_null |
| (local.get $B) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $compatible-test-separate-fallthrough (type $12) (param $eqref eqref) (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $eqref |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (ref.cast i31ref |
| ;; CHECK-NEXT: (local.get $eqref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| (func $compatible-test-separate-fallthrough (param $eqref eqref) (result i32) |
| (ref.test (ref i31) |
| (local.tee $eqref |
| (block (result eqref) |
| ;; Prove that the value is non-nullable |
| (ref.as_non_null |
| (block (result eqref) |
| ;; Prove that the value is an i31 |
| (ref.cast i31ref |
| (local.get $eqref) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $improvable-test-separate-fallthrough (type $12) (param $eqref eqref) (result i32) |
| ;; CHECK-NEXT: (ref.test (ref i31) |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $eqref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $improvable-test-separate-fallthrough (param $eqref eqref) (result i32) |
| ;; There is no need to admit null here, but we don't know whether we have an i31. |
| (ref.test i31ref |
| (block (result eqref) |
| ;; Prove that the value is non-nullable |
| (ref.as_non_null |
| (local.get $eqref) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-test-separate-fallthrough (type $12) (param $eqref eqref) (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $eqref |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (ref.cast i31ref |
| ;; CHECK-NEXT: (local.get $eqref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| (func $incompatible-test-separate-fallthrough (param $eqref eqref) (result i32) |
| (ref.test structref |
| (local.tee $eqref |
| (block (result eqref) |
| ;; Prove that the value is non-nullable |
| (ref.as_non_null |
| (block (result eqref) |
| ;; Prove that the value is an i31 |
| (ref.cast i31ref |
| (local.get $eqref) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-test-heap-types-nonnullable (type $7) (param $anyref anyref) (result anyref) |
| ;; CHECK-NEXT: (block $outer (result anyref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result anyref) |
| ;; CHECK-NEXT: (br_on_cast_fail $outer anyref i31ref |
| ;; CHECK-NEXT: (block (result anyref) |
| ;; CHECK-NEXT: (br_on_cast_fail $outer anyref structref |
| ;; CHECK-NEXT: (local.get $anyref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $anyref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $incompatible-test-heap-types-nonnullable (param $anyref anyref) (result anyref) |
| (block $outer (result anyref) |
| (drop |
| ;; The value cannot be both i31 and struct, so it must be null and we |
| ;; can optimize to 0. |
| (ref.test (ref any) |
| (block (result anyref) |
| (br_on_cast_fail $outer anyref i31ref |
| (block (result anyref) |
| (br_on_cast_fail $outer anyref structref |
| (local.get $anyref) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| (local.get $anyref) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-test-heap-types-nullable (type $7) (param $anyref anyref) (result anyref) |
| ;; CHECK-NEXT: (block $outer (result anyref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result anyref) |
| ;; CHECK-NEXT: (br_on_cast_fail $outer anyref i31ref |
| ;; CHECK-NEXT: (block (result anyref) |
| ;; CHECK-NEXT: (br_on_cast_fail $outer anyref structref |
| ;; CHECK-NEXT: (local.get $anyref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $anyref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $incompatible-test-heap-types-nullable (param $anyref anyref) (result anyref) |
| (block $outer (result anyref) |
| (drop |
| ;; Same as above, but now we allow null, so we optimize to 1. |
| (ref.test anyref |
| (block (result anyref) |
| (br_on_cast_fail $outer anyref i31ref |
| (block (result anyref) |
| (br_on_cast_fail $outer anyref structref |
| (local.get $anyref) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| (local.get $anyref) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-test-heap-types-unreachable (type $7) (param $anyref anyref) (result anyref) |
| ;; CHECK-NEXT: (block $outer (result anyref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result anyref) |
| ;; CHECK-NEXT: (br_on_cast_fail $outer anyref (ref i31) |
| ;; CHECK-NEXT: (block (result anyref) |
| ;; CHECK-NEXT: (br_on_cast_fail $outer anyref structref |
| ;; CHECK-NEXT: (local.get $anyref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $anyref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $incompatible-test-heap-types-unreachable (param $anyref anyref) (result anyref) |
| (block $outer (result anyref) |
| (drop |
| ;; Same as above, but now we know the value must be non-null and bottom, |
| ;; so it cannot exist at all. |
| (ref.test anyref |
| (block (result anyref) |
| (br_on_cast_fail $outer anyref (ref i31) |
| (block (result anyref) |
| (br_on_cast_fail $outer anyref structref |
| (local.get $anyref) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| (local.get $anyref) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref.test-unreachable (type $37) (param $A (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test (ref $A) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test (ref null $A) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref.test-unreachable (param $A (ref null $A)) |
| (drop |
| ;; We should ignore unreachable ref.tests and not try to compare their |
| ;; HeapTypes. |
| (ref.test (ref $A) |
| (unreachable) |
| ) |
| ) |
| (drop |
| (ref.test (ref null $A) |
| (unreachable) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-null (type $5) |
| ;; CHECK-NEXT: (local $a (ref null $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $a |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $a |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.cast nullref |
| ;; CHECK-NEXT: (local.get $a) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.cast nullref |
| ;; CHECK-NEXT: (local.get $a) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-static-null |
| (local $a (ref null $A)) |
| ;; Casting nulls results in a null. |
| (drop |
| (ref.cast (ref null $A) |
| (ref.null none) |
| ) |
| ) |
| ;; A fallthrough works too. |
| (drop |
| (ref.cast (ref null $B) |
| (local.tee $a |
| (ref.null none) |
| ) |
| ) |
| ) |
| ;; A non-null cast of a falling-though null will trap. |
| (drop |
| (ref.cast (ref $A) |
| (local.tee $a |
| (ref.null none) |
| ) |
| ) |
| ) |
| ;; The prior two examples work even if the fallthrough is only later proven |
| ;; to be null. |
| (drop |
| (ref.cast (ref null $B) |
| (block (result (ref null $A)) |
| (ref.cast nullref |
| (local.get $a) |
| ) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast (ref $B) |
| (block (result (ref null $A)) |
| (ref.cast nullref |
| (local.get $a) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-general (type $21) (param $a (ref null $A)) (param $b (ref null $B)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $a) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $b) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $B) |
| ;; CHECK-NEXT: (local.get $a) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $a |
| ;; CHECK-NEXT: (local.get $a) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-static-general (param $a (ref null $A)) (param $b (ref null $B)) |
| ;; In the general case, a static cast of something simply succeeds if the |
| ;; type is a subtype. |
| (drop |
| (ref.cast (ref null $A) |
| (local.get $a) |
| ) |
| ) |
| (drop |
| (ref.cast (ref null $A) |
| (local.get $b) |
| ) |
| ) |
| ;; This is the only one that we cannot know for sure will succeed. |
| (drop |
| (ref.cast (ref null $B) |
| (local.get $a) |
| ) |
| ) |
| ;; A fallthrough works too. |
| (drop |
| (ref.cast (ref null $A) |
| (local.tee $a |
| (local.get $a) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-squared (type $4) (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $A) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-static-squared (param $x eqref) |
| ;; Identical ref.casts can be folded together. |
| (drop |
| (ref.cast (ref null $A) |
| (ref.cast (ref null $A) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ;; When subtypes exist, we only need the stricter one. |
| (drop |
| (ref.cast (ref null $A) |
| (ref.cast (ref null $B) |
| (local.get $x) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast (ref null $B) |
| (ref.cast (ref null $A) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-many (type $4) (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $B-child) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $B-child) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $B-child) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $B-child) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $B-child) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $B-child) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-static-many (param $x eqref) |
| ;; We should optimize a long sequence of static casts when we can. All six |
| ;; orderings of these casts should collapse into the strictest one. |
| (drop |
| (ref.cast (ref null $A) |
| (ref.cast (ref null $B) |
| (ref.cast (ref null $B-child) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast (ref null $A) |
| (ref.cast (ref null $B-child) |
| (ref.cast (ref null $B) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast (ref null $B) |
| (ref.cast (ref null $A) |
| (ref.cast (ref null $B-child) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast (ref null $B) |
| (ref.cast (ref null $B-child) |
| (ref.cast (ref null $A) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast (ref null $B-child) |
| (ref.cast (ref null $A) |
| (ref.cast (ref null $B) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast (ref null $B-child) |
| (ref.cast (ref null $B) |
| (ref.cast (ref null $A) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-very-many (type $4) (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $B-child) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-static-very-many (param $x eqref) |
| ;; We should optimize an arbitrarily-long long sequence of static casts. |
| (drop |
| (ref.cast (ref null $A) |
| (ref.cast (ref null $B) |
| (ref.cast (ref null $B-child) |
| (ref.cast (ref null $A) |
| (ref.cast (ref null $A) |
| (ref.cast (ref null $B-child) |
| (ref.cast (ref null $B-child) |
| (ref.cast (ref null $B) |
| (ref.cast (ref null $B) |
| (ref.cast (ref null $B) |
| (ref.cast (ref null $B-child) |
| (ref.cast (ref null $A) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-fallthrough-remaining (type $4) (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null $B)) |
| ;; CHECK-NEXT: (call $ref-cast-static-fallthrough-remaining |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref null $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-static-fallthrough-remaining (param $x eqref) |
| (drop |
| (ref.cast (ref null $A) |
| (block (result (ref null $B)) |
| ;; Additional contents in between redundant casts must be preserved. |
| ;; That is, when we see that the casts are redundant, by seeing that |
| ;; the fallthrough value reaching the outer cast is already cast, we |
| ;; can avoid a duplicate cast, but we do still need to keep any code |
| ;; in the middle, as it may have side effects. |
| ;; |
| ;; In this first testcase, the outer cast is not needed as the inside |
| ;; is already a more specific type. |
| (call $ref-cast-static-fallthrough-remaining |
| (local.get $x) |
| ) |
| (ref.cast (ref null $B) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-fallthrough-remaining-child (type $4) (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref null $B) |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (call $ref-cast-static-fallthrough-remaining-child |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref null $A) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-static-fallthrough-remaining-child (param $x eqref) |
| (drop |
| ;; As above, but with $A and $B flipped. Now the inner cast is not needed. |
| ;; However, we do not remove it, as it may be necessary for validation, |
| ;; and we hope other opts help out here. |
| (ref.cast (ref null $B) |
| (block (result eqref) |
| (call $ref-cast-static-fallthrough-remaining-child |
| (local.get $x) |
| ) |
| (ref.cast (ref null $A) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-fallthrough-remaining-impossible (type $22) (param $x (ref eq)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $struct)) |
| ;; CHECK-NEXT: (call $ref-cast-static-fallthrough-remaining-impossible |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.cast (ref $struct) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-static-fallthrough-remaining-impossible (param $x (ref eq)) |
| (drop |
| ;; As above, but with an impossible cast of an array to a struct. The |
| ;; block with the side effects and the inner cast must be kept around and |
| ;; dropped, and then we replace the outer cast with an unreachable. |
| (ref.cast (ref $array) |
| (block (result (ref eq)) |
| (call $ref-cast-static-fallthrough-remaining-impossible |
| (local.get $x) |
| ) |
| (ref.cast (ref $struct) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-fallthrough-remaining-nonnull (type $22) (param $x (ref eq)) |
| ;; CHECK-NEXT: (local $1 (ref $B)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $B)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $B)) |
| ;; CHECK-NEXT: (call $ref-cast-static-fallthrough-remaining |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (ref.cast (ref $B) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-static-fallthrough-remaining-nonnull (param $x (ref eq)) |
| ;; The input is non-nullable here, and the middle block is of a simpler type |
| ;; than either the parent or the child. This checks that we do not |
| ;; mis-optimize this case: The outer cast is not needed, so we can optimize |
| ;; it out, but we have to be careful not to remove any side effects. |
| (drop |
| (ref.cast (ref $A) |
| (block (result (ref eq)) |
| (call $ref-cast-static-fallthrough-remaining |
| (local.get $x) |
| ) |
| (ref.cast (ref $B) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-static-squared-impossible (type $4) (param $x eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast nullref |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref none)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $array) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref none)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $array) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref none)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $array) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-static-squared-impossible (param $x eqref) |
| ;; Impossible casts will trap unless the input is null. Only the first one |
| ;; here, which lets a null get through, will not trap. |
| (drop |
| (ref.cast (ref null $struct) |
| (ref.cast (ref null $array) |
| (local.get $x) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast (ref $struct) |
| (ref.cast (ref null $array) |
| (local.get $x) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast (ref null $struct) |
| (ref.cast (ref $array) |
| (local.get $x) |
| ) |
| ) |
| ) |
| (drop |
| (ref.cast (ref $struct) |
| (ref.cast (ref $array) |
| (ref.as_non_null (local.get $x)) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-test-static-same-type (type $23) (param $nullable (ref null $A)) (param $non-nullable (ref $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.eqz |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $non-nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-test-static-same-type (param $nullable (ref null $A)) (param $non-nullable (ref $A)) |
| ;; A nullable value cannot be optimized here even though it is the same |
| ;; type. But we can at least use !ref.is_null rather than ref.test. |
| (drop |
| (ref.test (ref $A) |
| (local.get $nullable) |
| ) |
| ) |
| ;; But if it is non-nullable, it must succeed. |
| (drop |
| (ref.test (ref $A) |
| (local.get $non-nullable) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-test-static-subtype (type $13) (param $nullable (ref null $B)) (param $non-nullable (ref $B)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.eqz |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $non-nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-test-static-subtype (param $nullable (ref null $B)) (param $non-nullable (ref $B)) |
| ;; As above, but the input is a subtype, so the same things happen. |
| (drop |
| (ref.test (ref $A) |
| (local.get $nullable) |
| ) |
| ) |
| (drop |
| (ref.test (ref $A) |
| (local.get $non-nullable) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-test-static-supertype (type $23) (param $nullable (ref null $A)) (param $non-nullable (ref $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test (ref $B) |
| ;; CHECK-NEXT: (local.get $nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test (ref $B) |
| ;; CHECK-NEXT: (local.get $non-nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-test-static-supertype (param $nullable (ref null $A)) (param $non-nullable (ref $A)) |
| ;; As above, but the input is a supertype. We can't know at compile time |
| ;; what to do here. |
| (drop |
| (ref.test (ref $B) |
| (local.get $nullable) |
| ) |
| ) |
| (drop |
| (ref.test (ref $B) |
| (local.get $non-nullable) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-test-static-impossible (type $38) (param $nullable (ref null $array)) (param $non-nullable (ref $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $non-nullable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-test-static-impossible (param $nullable (ref null $array)) (param $non-nullable (ref $array)) |
| ;; Testing an impossible cast will definitely fail. |
| (drop |
| (ref.test (ref $struct) |
| (local.get $nullable) |
| ) |
| ) |
| (drop |
| (ref.test (ref $struct) |
| (local.get $non-nullable) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-boolean (type $10) (param $x eqref) (param $y eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test (ref $A) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-boolean (param $x eqref) (param $y eqref) |
| ;; ref.eq returns a boolean, so &1 on it is not needed. |
| (drop |
| (i32.and |
| (ref.eq |
| (local.get $x) |
| (local.get $y) |
| ) |
| (i32.const 1) |
| ) |
| ) |
| ;; likewise ref.is and ref.test |
| (drop |
| (i32.and |
| (ref.is_null |
| (local.get $x) |
| ) |
| (i32.const 1) |
| ) |
| ) |
| (drop |
| (i32.and |
| (ref.test (ref $A) |
| (local.get $x) |
| ) |
| (i32.const 1) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $impossible (type $39) (result (ref none)) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $impossible (result (ref none)) |
| (unreachable) |
| ) |
| |
| ;; CHECK: (func $bottom-type-accessors (type $40) (param $bot (ref none)) (param $null nullref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (call $impossible) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $bottom-type-accessors (param $bot (ref none)) (param $null nullref) |
| (drop |
| (struct.get $A 0 |
| (local.get $bot) |
| ) |
| ) |
| (drop |
| (array.get $array |
| (local.get $null) |
| (i32.const 0) |
| ) |
| ) |
| (struct.set $A 0 |
| (ref.null none) |
| (i32.const 42) |
| ) |
| (array.set $array |
| (call $impossible) |
| (i32.const 1) |
| (i32.const 2) |
| ) |
| (call_ref $void |
| (ref.null nofunc) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-heap-type (type $13) (param $null-b (ref null $B)) (param $b (ref $B)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $b) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $null-b) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $b) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $null-b) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-heap-type (param $null-b (ref null $B)) (param $b (ref $B)) |
| ;; We are casting a heap type to a supertype, which always succeeds. However |
| ;; we need to check for nullability. |
| |
| ;; Non-nullable casts. When the input is non-nullable we must succeed. |
| (drop |
| (ref.cast (ref $A) |
| (local.get $b) |
| ) |
| ) |
| ;; When the input can be null, we might fail if it is a null. But we can |
| ;; switch to checking only that. |
| (drop |
| (ref.cast (ref $A) |
| (local.get $null-b) |
| ) |
| ) |
| |
| ;; Null casts. Both of these must succeed. |
| (drop |
| (ref.cast (ref null $A) |
| (local.get $b) |
| ) |
| ) |
| (drop |
| (ref.cast (ref null $A) |
| (local.get $null-b) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref-cast-heap-type-incompatible (type $13) (param $null-b (ref null $B)) (param $b (ref $B)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref none)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $b) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref none)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $null-b) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref none)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $b) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast nullref |
| ;; CHECK-NEXT: (local.get $null-b) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-cast-heap-type-incompatible (param $null-b (ref null $B)) (param $b (ref $B)) |
| ;; As above, but replacing $A with $struct. Now we have two incompatible |
| ;; types, $B and $struct, so the only possible way the cast succeeds is if |
| ;; the cast allows null and the input is a null. |
| (drop |
| (ref.cast (ref $struct) |
| (local.get $b) |
| ) |
| ) |
| (drop |
| (ref.cast (ref $struct) |
| (local.get $null-b) |
| ) |
| ) |
| (drop |
| (ref.cast (ref null $struct) |
| (local.get $b) |
| ) |
| ) |
| ;; This last case is the only one that can succeed. We turn it into a cast |
| ;; to a null. |
| (drop |
| (ref.cast (ref null $struct) |
| (local.get $null-b) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $compatible-cast-separate-fallthrough (type $24) (param $eqref eqref) (result (ref i31)) |
| ;; CHECK-NEXT: (local $1 i31ref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $eqref |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (ref.cast i31ref |
| ;; CHECK-NEXT: (local.get $eqref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $compatible-cast-separate-fallthrough (param $eqref eqref) (result (ref i31)) |
| ;; This cast will succeed even though no individual fallthrough value is sufficiently refined. |
| (ref.cast (ref i31) |
| (local.tee $eqref |
| (block (result eqref) |
| ;; Prove that the value is non-nullable |
| (ref.as_non_null |
| (block (result eqref) |
| ;; Prove that the value is an i31 |
| (ref.cast i31ref |
| (local.get $eqref) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $compatible-cast-fallthrough-null-check (type $24) (param $eqref eqref) (result (ref i31)) |
| ;; CHECK-NEXT: (local $1 i31ref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $eqref |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (ref.cast i31ref |
| ;; CHECK-NEXT: (local.get $eqref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $compatible-cast-fallthrough-null-check (param $eqref eqref) (result (ref i31)) |
| ;; Similar to above, but now we no longer know whether the value going into |
| ;; the cast is null or not. |
| (ref.cast (ref i31) |
| (local.tee $eqref |
| (block (result eqref) |
| ;; Prove that the value is an i31 |
| (ref.cast i31ref |
| (local.get $eqref) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $compatible-cast-separate-fallthrough-multiple-options-1 (type $25) (param $eqref eqref) (result (ref eq)) |
| ;; CHECK-NEXT: (local $1 i31ref) |
| ;; CHECK-NEXT: (block $outer (result (ref eq)) |
| ;; CHECK-NEXT: (block (result (ref i31)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $eqref |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (br_on_cast_fail $outer eqref i31ref |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (ref.cast i31ref |
| ;; CHECK-NEXT: (local.get $eqref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $compatible-cast-separate-fallthrough-multiple-options-1 |
| (param $eqref eqref) (result (ref eq)) |
| ;; There are multiple "best" values we could tee and propagate. Choose the |
| ;; shallowest. |
| (block $outer (result (ref eq)) |
| (ref.cast (ref i31) |
| (local.tee $eqref |
| (block (result eqref) |
| ;; Prove that the value is an i31 a second time. This one will be |
| ;; propagated. |
| (br_on_cast_fail $outer eqref i31ref |
| (block (result eqref) |
| ;; Prove that the value is non-nullable |
| (ref.as_non_null |
| (block (result eqref) |
| ;; Prove that the value is an i31 |
| (ref.cast i31ref |
| (local.get $eqref) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $compatible-cast-separate-fallthrough-multiple-options-2 (type $25) (param $eqref eqref) (result (ref eq)) |
| ;; CHECK-NEXT: (local $1 (ref i31)) |
| ;; CHECK-NEXT: (block $outer (result (ref eq)) |
| ;; CHECK-NEXT: (block (result (ref i31)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $eqref |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (br_on_cast_fail $outer eqref i31ref |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (local.tee $1 |
| ;; CHECK-NEXT: (ref.cast (ref i31) |
| ;; CHECK-NEXT: (local.get $eqref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $compatible-cast-separate-fallthrough-multiple-options-2 |
| (param $eqref eqref) (result (ref eq)) |
| (block $outer (result (ref eq)) |
| (ref.cast (ref i31) |
| (local.tee $eqref |
| (block (result eqref) |
| ;; Prove that the value is an i31 a second time, but not that it is |
| ;; non-null at the same time. |
| (br_on_cast_fail $outer eqref i31ref |
| (block (result eqref) |
| ;; Prove that the value is non-nullable but not i31. |
| (ref.as_non_null |
| (block (result eqref) |
| ;; Now this is non-nullable and an exact match, so we |
| ;; propagate this one. |
| (ref.cast (ref i31) |
| (local.get $eqref) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-cast-separate-fallthrough (type $41) (param $eqref eqref) (result structref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $eqref |
| ;; CHECK-NEXT: (block (result (ref i31)) |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (block (result i31ref) |
| ;; CHECK-NEXT: (ref.cast i31ref |
| ;; CHECK-NEXT: (local.get $eqref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $incompatible-cast-separate-fallthrough (param $eqref eqref) (result structref) |
| (ref.cast structref |
| (local.tee $eqref |
| (block (result eqref) |
| ;; Prove that the value is non-nullable |
| (ref.as_non_null |
| (block (result eqref) |
| ;; Prove that the value is an i31 |
| (ref.cast i31ref |
| (local.get $eqref) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-cast-heap-types-nonnullable (type $7) (param $anyref anyref) (result anyref) |
| ;; CHECK-NEXT: (block $outer (result (ref any)) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (br_on_cast_fail $outer structref nullref |
| ;; CHECK-NEXT: (block (result structref) |
| ;; CHECK-NEXT: (br_on_cast_fail $outer anyref structref |
| ;; CHECK-NEXT: (local.get $anyref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $incompatible-cast-heap-types-nonnullable (param $anyref anyref) (result anyref) |
| (block $outer (result anyref) |
| ;; The value cannot be both an i31 and a struct, so it must be null, so |
| ;; the cast will fail. |
| (ref.cast (ref struct) |
| (block (result anyref) |
| (br_on_cast_fail $outer anyref i31ref |
| (block (result anyref) |
| (br_on_cast_fail $outer anyref structref |
| (local.get $anyref) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-cast-heap-types-nullable (type $7) (param $anyref anyref) (result anyref) |
| ;; CHECK-NEXT: (block $outer (result anyref) |
| ;; CHECK-NEXT: (ref.cast nullref |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (br_on_cast_fail $outer structref nullref |
| ;; CHECK-NEXT: (block (result structref) |
| ;; CHECK-NEXT: (br_on_cast_fail $outer anyref structref |
| ;; CHECK-NEXT: (local.get $anyref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $incompatible-cast-heap-types-nullable (param $anyref anyref) (result anyref) |
| (block $outer (result anyref) |
| ;; As above, but now the cast might succeed because we allow null. |
| (ref.cast structref |
| (block (result anyref) |
| (br_on_cast_fail $outer anyref i31ref |
| (block (result anyref) |
| (br_on_cast_fail $outer anyref structref |
| (local.get $anyref) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $incompatible-cast-heap-types-unreachable (type $7) (param $anyref anyref) (result anyref) |
| ;; CHECK-NEXT: (block $outer (result anyref) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref none)) |
| ;; CHECK-NEXT: (br_on_cast_fail $outer structref (ref none) |
| ;; CHECK-NEXT: (block (result structref) |
| ;; CHECK-NEXT: (br_on_cast_fail $outer anyref structref |
| ;; CHECK-NEXT: (local.get $anyref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $incompatible-cast-heap-types-unreachable (param $anyref anyref) (result anyref) |
| (block $outer (result anyref) |
| ;; As above, but now we know the value is not null, so the cast is unreachable. |
| (ref.cast structref |
| (block (result anyref) |
| (br_on_cast_fail $outer anyref (ref i31) |
| (block (result anyref) |
| (br_on_cast_fail $outer anyref structref |
| (local.get $anyref) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $as_of_unreachable (type $42) (result (ref $A)) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $as_of_unreachable (result (ref $A)) |
| ;; The cast will definitely fail, so we can turn it into an unreachable. The |
| ;; ref.as must then ignore the unreachable input and not error on trying to |
| ;; infer anything about it. |
| (ref.as_non_null |
| (ref.cast (ref $A) |
| (ref.null none) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cast-internalized-extern (type $43) (param $externref externref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $A) |
| ;; CHECK-NEXT: (any.convert_extern |
| ;; CHECK-NEXT: (local.get $externref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cast-internalized-extern (param $externref externref) |
| ;; We cannot optimize this cast, and in particular we should not treat the |
| ;; externref as falling through to the cast and incorrectly inferring that |
| ;; the cast cannot succeed. |
| (drop |
| (ref.cast (ref $A) |
| (any.convert_extern |
| (local.get $externref) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $struct.set.null.fallthrough (type $5) |
| ;; CHECK-NEXT: (local $temp (ref null $struct)) |
| ;; CHECK-NEXT: (block ;; (replaces unreachable StructSet we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $temp |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 100) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $struct.set.null.fallthrough |
| (local $temp (ref null $struct)) |
| ;; The value falling through the tee shows the struct.set will trap. We can |
| ;; append an unreachable after it. While doing so we must not emit a drop of |
| ;; the struct.set (which would be valid for a struct.get etc.). |
| (struct.set $struct 0 |
| (local.tee $temp |
| (ref.as_non_null |
| (ref.null none) |
| ) |
| ) |
| (i32.const 100) |
| ) |
| ) |
| |
| ;; CHECK: (func $set.array.null (type $5) |
| ;; CHECK-NEXT: (local $temp (ref none)) |
| ;; CHECK-NEXT: (block ;; (replaces unreachable ArraySet we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $temp |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $set.array.null |
| (local $temp (ref none)) |
| |
| ;; The cast of none will be inferred to be an unreachable. That does not |
| ;; propagate through the tee during this pass, however, as it only |
| ;; happens during the refinalize at the very end. We must be careful not to |
| ;; hit an internal error while processing the array.set, as its reference's |
| ;; fallthrough value has null type and not array type. |
| (array.set $array |
| (local.tee $temp |
| (ref.as_non_null |
| (ref.null none) |
| ) |
| ) |
| (i32.const 2) |
| (i32.const 3) |
| ) |
| ) |
| |
| ;; CHECK: (func $refinalize.select.arm (type $void) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $void2) |
| ;; CHECK-NEXT: (ref.func $refinalize.select.arm) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $refinalize.select.arm (type $void) |
| ;; Pick one of the two select sides using the condition. This changes the |
| ;; type (the arms are more refined than the declared type), so we must |
| ;; refinalize or we'll error. |
| (drop |
| (ref.cast (ref null $void2) |
| (select (result (ref null $void)) |
| (ref.func $refinalize.select.arm) |
| (ref.func $refinalize.select.arm) |
| (i32.const 1) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $refinalize.select.arm.flip (type $5) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $void2) |
| ;; CHECK-NEXT: (ref.func $refinalize.select.arm) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $refinalize.select.arm.flip |
| ;; Flipped of the above. |
| (drop |
| (ref.cast (ref null $void2) |
| (select (result (ref null $void)) |
| (ref.func $refinalize.select.arm) |
| (ref.func $refinalize.select.arm) |
| (i32.const 0) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $refinalize.select.arm.unknown (type $26) (param $x i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref $void2) |
| ;; CHECK-NEXT: (ref.func $refinalize.select.arm) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $refinalize.select.arm.unknown (param $x i32) |
| ;; As above but use an unknown value at compile time for the condition. |
| (drop |
| (ref.cast (ref null $void2) |
| (select (result (ref null $void)) |
| (ref.func $refinalize.select.arm) |
| (ref.func $refinalize.select.arm) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $non-null-bottom-ref (type $44) (result (ref func)) |
| ;; CHECK-NEXT: (local $0 funcref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $0 |
| ;; CHECK-NEXT: (loop (result (ref nofunc)) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $non-null-bottom-ref (result (ref func)) |
| (local $0 (ref null func)) |
| ;; The reference is uninhabitable, a non-null bottom type. The cast is not |
| ;; even reached, but we need to be careful: The tee makes this a corner case |
| ;; since it makes the type nullable again, so if we thought the cast would |
| ;; succeed, and replaced the cast with its child, we'd fail to validate. |
| ;; Instead, since the cast fails, we can replace it with an unreachable |
| ;; (after the dropped child). |
| (ref.cast (ref func) |
| (local.tee $0 |
| (loop (result (ref nofunc)) |
| (unreachable) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $non-null-bottom-cast (type $45) (result (ref nofunc)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.func $non-null-bottom-cast) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $non-null-bottom-cast (result (ref nofunc)) |
| ;; As above, but now the cast is uninhabitable. |
| (ref.cast (ref nofunc) |
| (ref.func $non-null-bottom-cast) |
| ) |
| ) |
| |
| ;; CHECK: (func $non-null-bottom-ref-test (type $8) (result i32) |
| ;; CHECK-NEXT: (local $0 funcref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $0 |
| ;; CHECK-NEXT: (loop (result (ref nofunc)) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $non-null-bottom-ref-test (result i32) |
| (local $0 (ref null func)) |
| ;; As above, but now it's a ref.test instead of cast. |
| (ref.test (ref func) |
| (local.tee $0 |
| (loop (result (ref nofunc)) |
| (unreachable) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $non-null-bottom-ref-test-notee (type $8) (result i32) |
| ;; CHECK-NEXT: (local $0 funcref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (loop (result (ref nofunc)) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $non-null-bottom-ref-test-notee (result i32) |
| (local $0 (ref null func)) |
| ;; As above, but without an intermediate local.tee. Now ref.test will see |
| ;; that it is unreachable, as the input is uninhabitable. |
| (ref.test (ref func) |
| (loop (result (ref nofunc)) |
| (unreachable) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $non-null-bottom-test (type $8) (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.func $non-null-bottom-cast) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| (func $non-null-bottom-test (result i32) |
| ;; As above, but now the cast type is uninhabitable, and also use ref.test. |
| ;; This cast cannot succeed, so return 0. |
| (ref.test (ref nofunc) |
| (ref.func $non-null-bottom-cast) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref.test-fallthrough (type $5) |
| ;; CHECK-NEXT: (local $A (ref $A)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.test (ref $B) |
| ;; CHECK-NEXT: (local.tee $A |
| ;; CHECK-NEXT: (struct.new $A |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $A |
| ;; CHECK-NEXT: (struct.new $B |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: (i32.const 30) |
| ;; CHECK-NEXT: (f32.const 40.5) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref.test-fallthrough |
| (local $A (ref $A)) |
| ;; The test will fail, but this pass does not have exact type info, so it |
| ;; thinks it can succeed and nothing happens here (GUFA can optimize this, |
| ;; however). |
| (drop |
| (ref.test (ref $B) |
| (local.tee $A |
| (struct.new $A |
| (i32.const 10) |
| ) |
| ) |
| ) |
| ) |
| ;; This test will succeed, even though we tee to the parent type in the |
| ;; middle. |
| (drop |
| (ref.test (ref $B) |
| (local.tee $A |
| (struct.new $B |
| (i32.const 20) |
| (i32.const 30) |
| (f32.const 40.50) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref.test-then-optimizeAddedConstants (type $8) (result i32) |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref.test-then-optimizeAddedConstants (result i32) |
| ;; The cast will become unreachable, and then the test as well. We should |
| ;; not error in the subsequent optimizeAddedConstants function that will be |
| ;; called on the adds. The risk here is that that code does not expect an |
| ;; unreachable to appear inside a non-unreachable add, which can happen as |
| ;; we delay updating types til the end of the pass. To avoid that, the |
| ;; ref.test should not change its type, but only replace itself with a block |
| ;; containing an unreachable (but declared as the old type; leaving |
| ;; optimizing it further for other passes). |
| (i32.add |
| (i32.const 1) |
| (i32.add |
| (i32.const 2) |
| (ref.test (ref func) |
| (ref.cast (ref func) |
| (ref.null nofunc) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $gc_to_unreachable_in_added_constants (type $5) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.wrap_i64 |
| ;; CHECK-NEXT: (i64.add |
| ;; CHECK-NEXT: (call $struct_i64_helper |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i64.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $gc_to_unreachable_in_added_constants |
| (drop |
| (i32.wrap_i64 |
| (i64.add |
| (i64.const 1) |
| (i64.add |
| (i64.const 1) |
| (call_ref $struct_i64 |
| ;; This will turn into an unreachable. That should not cause an |
| ;; error in later i64 add optimizations (optimizeAddedConstants), |
| ;; which should not assume this is an i64-typed expression. |
| (ref.as_non_null |
| (ref.null none) |
| ) |
| (ref.func $struct_i64_helper) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $struct_i64_helper (type $struct_i64) (param $0 structref) (result i64) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $struct_i64_helper (type $struct_i64) (param $0 (ref null struct)) (result i64) |
| (unreachable) |
| ) |
| |
| ;; CHECK: (func $array-copy-non-null (type $16) (param $x (ref null $array)) |
| ;; CHECK-NEXT: (block $block |
| ;; CHECK-NEXT: (array.copy $array $array |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if (result i32) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (br $block) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: (i32.const 1337) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $array-copy-non-null (param $x (ref null $array)) |
| (block $block |
| (array.copy $array $array |
| ;; This cast cannot be removed: while the array.copy will trap anyhow |
| ;; if $x is null, we might branch out in the if, so removing a trap |
| ;; here could be noticeable. |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| (if (result i32) |
| (i32.const 1) |
| (then |
| (br $block) |
| ) |
| (else |
| (i32.const 10) |
| ) |
| ) |
| ;; There are no tricky effects after this, so this cast can be removed. |
| (ref.as_non_null |
| (local.get $x) |
| ) |
| (i32.const 42) |
| (i32.const 1337) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $struct.new (type $5) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $struct.new) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.new_default $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.new_default $struct.ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i64.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (call $get-i32) |
| ;; CHECK-NEXT: (i64.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.new $struct.ref |
| ;; CHECK-NEXT: (ref.func $struct.new) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $struct.new |
| ;; Convert struct.new with default values into struct.new_default. |
| (drop |
| (struct.new $struct |
| (i32.const 0) |
| (block (result i32) |
| ;; A block in the middle, even with side effects, is no problem (it |
| ;; will be dropped). |
| (call $struct.new) |
| (i32.const 0) |
| ) |
| (i32.const 0) |
| (i64.const 0) |
| ) |
| ) |
| |
| ;; Refs work too. |
| (drop |
| (struct.new $struct.ref |
| (ref.null func) |
| ) |
| ) |
| |
| ;; But a single non-default value is enough to prevent this. Test various |
| ;; cases of that. |
| (drop |
| (struct.new $struct |
| (i32.const 0) |
| (i32.const 0) |
| (i32.const 1) ;; constant, but non-default |
| (i64.const 0) |
| ) |
| ) |
| (drop |
| (struct.new $struct |
| (i32.const 0) |
| (i32.const 0) |
| (call $get-i32) ;; non-constant |
| (i64.const 0) |
| ) |
| ) |
| (drop |
| (struct.new $struct.ref |
| (ref.func $struct.new) ;; func constant, but non-default |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $array.new (type $5) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $array.new) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.new_default $array |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (array.new $array |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (array.new_fixed $array 1 |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (array.new $array |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $array.new |
| ;; Convert array.new with the default value into array.new_default. |
| (drop |
| (array.new $array |
| (block (result i32) |
| (call $array.new) |
| (i32.const 0) |
| ) |
| (i32.const 42) |
| ) |
| ) |
| |
| ;; Ignore any non-default value. |
| (drop |
| (array.new $array |
| (i32.const 1) |
| (i32.const 42) |
| ) |
| ) |
| |
| ;; array.new_fixed is preferable when the size is exactly 1. |
| (drop |
| (array.new $array |
| (i32.const 42) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; Do nothing for size 0, for now (see TODO in code). |
| (drop |
| (array.new $array |
| (i32.const 42) |
| (i32.const 0) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $array.new_fixed (type $5) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $array.new_fixed) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.new_default $array |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (array.new_fixed $array 3 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (array.new_fixed $array 3 |
| ;; CHECK-NEXT: (call $get-i32) |
| ;; CHECK-NEXT: (call $get-i32) |
| ;; CHECK-NEXT: (call $get-i32) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $array)) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $array.new_fixed) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $array.new_fixed) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.new $array |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (array.new_fixed $array 1 |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $array.new_fixed |
| ;; Convert array.new_fixed with default values into array.new_default. |
| (drop |
| (array.new_fixed $array 3 |
| (i32.const 0) |
| (block (result i32) |
| (call $array.new_fixed) |
| (i32.const 0) |
| ) |
| (i32.const 0) |
| ) |
| ) |
| |
| ;; Ignore when the values are not equal. |
| (drop |
| (array.new_fixed $array 3 |
| (i32.const 0) |
| (i32.const 1) |
| (i32.const 0) |
| ) |
| ) |
| (drop |
| (array.new_fixed $array 3 |
| (call $get-i32) |
| (call $get-i32) |
| (call $get-i32) |
| ) |
| ) |
| |
| ;; If they are equal but not default, we can optimize to array.new, even |
| ;; with effects. |
| (drop |
| (array.new_fixed $array 2 |
| (block (result i32) |
| (call $array.new_fixed) |
| (i32.const 42) |
| ) |
| (block (result i32) |
| (call $array.new_fixed) |
| (i32.const 42) |
| ) |
| ) |
| ) |
| |
| ;; Do nothing for size 1 (this is better than array.new as-is). |
| (drop |
| (array.new_fixed $array 1 |
| (i32.const 42) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $array.new_fixed_fallthrough (type $5) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $array)) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.new $array |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $array)) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.new $array |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $array)) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $array.new_fixed) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.new $array |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (array.new_fixed $array 2 |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $array.new_fixed) |
| ;; CHECK-NEXT: (i32.const 42) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) |
| ;; CHECK-NEXT: (i32.const 43) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $array.new_fixed_fallthrough |
| ;; The fallthroughs are identical. The call in the middle must only happen |
| ;; once, which we achieve by storing it to a local. |
| (drop |
| (array.new_fixed $array 2 |
| (i32.const 42) |
| (block (result i32) |
| (call $array.new_fixed_fallthrough) |
| (i32.const 42) |
| ) |
| ) |
| ) |
| ;; As above with order flipped. |
| (drop |
| (array.new_fixed $array 2 |
| (block (result i32) |
| (call $array.new_fixed_fallthrough) |
| (i32.const 42) |
| ) |
| (i32.const 42) |
| ) |
| ) |
| ;; Still identical fallthroughs, but different effects now. |
| (drop |
| (array.new_fixed $array 2 |
| (block (result i32) |
| (call $array.new_fixed) |
| (i32.const 42) |
| ) |
| (block (result i32) |
| (call $array.new_fixed_fallthrough) |
| (i32.const 42) |
| ) |
| ) |
| ) |
| ;; Different fallthrough, so we cannot optimize. |
| (drop |
| (array.new_fixed $array 2 |
| (block (result i32) |
| (call $array.new_fixed) |
| (i32.const 42) |
| ) |
| (block (result i32) |
| (call $array.new_fixed_fallthrough) |
| (i32.const 43) ;; this changed |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $array.new_fixed_fallthrough_local (type $26) (param $x i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (local $4 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $array)) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.new $array |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $array)) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (call $array.new_fixed_fallthrough) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.new $array |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $array)) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $4 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.new $array |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (array.new_fixed $array 2 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $array.new_fixed_fallthrough_local (param $x i32) |
| ;; The fallthroughs are identical local.gets. |
| (drop |
| (array.new_fixed $array 2 |
| (local.get $x) |
| (block (result i32) |
| (call $array.new_fixed_fallthrough) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ;; Flipped order. |
| (drop |
| (array.new_fixed $array 2 |
| (block (result i32) |
| (call $array.new_fixed_fallthrough) |
| (local.get $x) |
| ) |
| (local.get $x) |
| ) |
| ) |
| ;; The effect is now a set. We can still optimize. |
| (drop |
| (array.new_fixed $array 2 |
| (block (result i32) |
| (local.set $x |
| (i32.const 2) |
| ) |
| (local.get $x) |
| ) |
| (local.get $x) |
| ) |
| ) |
| ;; Flipped order, and now the set invalidates the get after it, preventing |
| ;; optimization. |
| (drop |
| (array.new_fixed $array 2 |
| (local.get $x) |
| (block (result i32) |
| (local.set $x |
| (i32.const 1) |
| ) |
| (local.get $x) |
| ) |
| ) |
| ) |
| ) |
| ) |