| ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. |
| |
| ;; RUN: wasm-opt %s --remove-unused-names --precompute-propagate --fuzz-exec -all -S -o - \ |
| ;; RUN: | filecheck %s |
| |
| (module |
| ;; CHECK: (type $empty (struct)) |
| (type $empty (struct)) |
| |
| ;; CHECK: (type $struct (struct (field (mut i32)))) |
| (type $struct (struct (mut i32))) |
| |
| ;; two incompatible struct types |
| (type $A (struct (field (mut f32)))) |
| |
| ;; CHECK: (type $func-return-i32 (func (result i32))) |
| |
| ;; CHECK: (type $B (struct (field (mut f64)))) |
| (type $B (struct (field (mut f64)))) |
| |
| (type $struct_i8 (struct (field i8))) |
| |
| (type $func-return-i32 (func (result i32))) |
| |
| ;; CHECK: (import "fuzzing-support" "log-i32" (func $log (type $4) (param i32))) |
| (import "fuzzing-support" "log-i32" (func $log (param i32))) |
| |
| ;; CHECK: (func $test-fallthrough (type $func-return-i32) (result i32) |
| ;; CHECK-NEXT: (local $x funcref) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (block (result nullfuncref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (call $test-fallthrough) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null nofunc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| (func $test-fallthrough (result i32) |
| (local $x funcref) |
| (local.set $x |
| ;; the fallthrough value should be used. for that to be possible with a block |
| ;; we need for it not to have a name, which is why --remove-unused-names is |
| ;; run |
| (block (result funcref) |
| ;; make a call so the block is not trivially removable |
| (drop |
| (call $test-fallthrough) |
| ) |
| (ref.null func) |
| ) |
| ) |
| ;; the null in the local should be propagated to here |
| (ref.is_null |
| (local.get $x) |
| ) |
| ) |
| |
| ;; CHECK: (func $load-from-struct (type $3) |
| ;; CHECK-NEXT: (local $x (ref null $struct)) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $log |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $log |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.const 3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $log |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $load-from-struct |
| (local $x (ref null $struct)) |
| (local.set $x |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| ;; we don't precompute these, as we don't know if the GC data was modified |
| ;; elsewhere (we'd need immutability or escape analysis) |
| (call $log |
| (struct.get $struct 0 (local.get $x)) |
| ) |
| ;; Assign a new struct |
| (local.set $x |
| (struct.new $struct |
| (i32.const 2) |
| ) |
| ) |
| (call $log |
| (struct.get $struct 0 (local.get $x)) |
| ) |
| ;; Assign a new value |
| (struct.set $struct 0 |
| (local.get $x) |
| (i32.const 3) |
| ) |
| (call $log |
| (struct.get $struct 0 (local.get $x)) |
| ) |
| ) |
| ;; CHECK: (func $load-from-struct-bad-merge (type $4) (param $i i32) |
| ;; CHECK-NEXT: (local $x (ref null $struct)) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (local.get $i) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (else |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $log |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $load-from-struct-bad-merge (param $i i32) |
| (local $x (ref null $struct)) |
| ;; a merge of two different $x values cannot be precomputed |
| (if |
| (local.get $i) |
| (then |
| (local.set $x |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| ) |
| (else |
| (local.set $x |
| (struct.new $struct |
| (i32.const 2) |
| ) |
| ) |
| ) |
| ) |
| (call $log |
| (struct.get $struct 0 (local.get $x)) |
| ) |
| ) |
| ;; CHECK: (func $modify-gc-heap (type $5) (param $x (ref null $struct)) |
| ;; CHECK-NEXT: (struct.set $struct 0 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $modify-gc-heap (param $x (ref null $struct)) |
| (struct.set $struct 0 |
| (local.get $x) |
| (i32.add |
| (struct.get $struct 0 |
| (local.get $x) |
| ) |
| (i32.const 1) |
| ) |
| ) |
| ) |
| ;; --fuzz-exec verifies the output of this function, checking that the change |
| ;; makde in modify-gc-heap is not ignored |
| ;; CHECK: (func $load-from-struct-bad-escape (type $3) |
| ;; CHECK-NEXT: (local $x (ref null $struct)) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $modify-gc-heap |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $log |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $load-from-struct-bad-escape (export "test") |
| (local $x (ref null $struct)) |
| (local.set $x |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| (call $modify-gc-heap |
| (local.get $x) |
| ) |
| (call $log |
| (struct.get $struct 0 (local.get $x)) |
| ) |
| ) |
| ;; CHECK: (func $load-from-struct-bad-arrive (type $5) (param $x (ref null $struct)) |
| ;; CHECK-NEXT: (call $log |
| ;; CHECK-NEXT: (struct.get $struct 0 |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $load-from-struct-bad-arrive (param $x (ref null $struct)) |
| ;; a parameter cannot be precomputed |
| (call $log |
| (struct.get $struct 0 (local.get $x)) |
| ) |
| ) |
| ;; CHECK: (func $ref-comparisons (type $11) (param $x (ref null $struct)) (param $y (ref null $struct)) |
| ;; CHECK-NEXT: (local $z (ref null $struct)) |
| ;; CHECK-NEXT: (local $w (ref null $struct)) |
| ;; CHECK-NEXT: (call $log |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $log |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $log |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $log |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref-comparisons |
| (param $x (ref null $struct)) |
| (param $y (ref null $struct)) |
| (local $z (ref null $struct)) |
| (local $w (ref null $struct)) |
| ;; incoming parameters are unknown |
| (call $log |
| (ref.eq |
| (local.get $x) |
| (local.get $y) |
| ) |
| ) |
| (call $log |
| (ref.eq |
| (local.get $x) |
| ;; locals are ref.null which are known, and will be propagated |
| (local.get $z) |
| ) |
| ) |
| (call $log |
| (ref.eq |
| (local.get $x) |
| (local.get $w) |
| ) |
| ) |
| ;; null-initialized locals are known and can be compared |
| (call $log |
| (ref.eq |
| (local.get $z) |
| (local.get $w) |
| ) |
| ) |
| ) |
| ;; CHECK: (func $new-ref-comparisons (type $func-return-i32) (result i32) |
| ;; CHECK-NEXT: (local $x (ref null $struct)) |
| ;; CHECK-NEXT: (local $y (ref null $struct)) |
| ;; CHECK-NEXT: (local $tempresult i32) |
| ;; CHECK-NEXT: (local.set $x |
| ;; CHECK-NEXT: (struct.new $struct |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $y |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $tempresult |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| (func $new-ref-comparisons (result i32) |
| (local $x (ref null $struct)) |
| (local $y (ref null $struct)) |
| (local $tempresult i32) |
| (local.set $x |
| (struct.new $struct |
| (i32.const 1) |
| ) |
| ) |
| (local.set $y |
| (local.get $x) |
| ) |
| ;; assign the result, so that propagate calculates the ref.eq. both $x and $y |
| ;; must refer to the same data, so we can precompute a 1 here. |
| (local.set $tempresult |
| (ref.eq |
| (local.get $x) |
| (local.get $y) |
| ) |
| ) |
| ;; and that 1 is propagated to here. |
| (local.get $tempresult) |
| ) |
| ;; CHECK: (func $propagate-equal (type $func-return-i32) (result i32) |
| ;; CHECK-NEXT: (local $tempresult i32) |
| ;; CHECK-NEXT: (local $tempref (ref null $empty)) |
| ;; CHECK-NEXT: (local.set $tempresult |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.tee $tempref |
| ;; CHECK-NEXT: (struct.new_default $empty) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $tempref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| (func $propagate-equal (result i32) |
| (local $tempresult i32) |
| (local $tempref (ref null $empty)) |
| ;; assign the result, so that propagate calculates the ref.eq |
| (local.set $tempresult |
| (ref.eq |
| ;; allocate one struct |
| (local.tee $tempref |
| (struct.new $empty) |
| ) |
| (local.get $tempref) |
| ) |
| ) |
| ;; we can compute a 1 here as the ref.eq compares a struct to itself. note |
| ;; that the ref.eq itself cannot be precomputed away (as it has side effects). |
| (local.get $tempresult) |
| ) |
| ;; CHECK: (func $propagate-unequal (type $func-return-i32) (result i32) |
| ;; CHECK-NEXT: (local $tempresult i32) |
| ;; CHECK-NEXT: (local $tempref (ref null $empty)) |
| ;; CHECK-NEXT: (local.set $tempresult |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| (func $propagate-unequal (result i32) |
| (local $tempresult i32) |
| (local $tempref (ref null $empty)) |
| ;; assign the result, so that propagate calculates the ref.eq. |
| ;; the structs are different, so we will precompute a 0 here, and as creating |
| ;; heap data does not have side effects, we can in fact replace the ref.eq |
| ;; with that value |
| (local.set $tempresult |
| ;; allocate two different structs |
| (ref.eq |
| (struct.new $empty) |
| (struct.new $empty) |
| ) |
| ) |
| (local.get $tempresult) |
| ) |
| |
| ;; CHECK: (func $propagate-uncertain-param (type $6) (param $input (ref $empty)) (result i32) |
| ;; CHECK-NEXT: (local $tempresult i32) |
| ;; CHECK-NEXT: (local $tempref (ref null $empty)) |
| ;; CHECK-NEXT: (local.set $tempresult |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (struct.new_default $empty) |
| ;; CHECK-NEXT: (local.get $input) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $tempresult) |
| ;; CHECK-NEXT: ) |
| (func $propagate-uncertain-param (param $input (ref $empty)) (result i32) |
| (local $tempresult i32) |
| (local $tempref (ref null $empty)) |
| (local.set $tempresult |
| ;; allocate a struct and compare it to a param, which we know nothing about, |
| ;; so we can infer nothing here at all. |
| (ref.eq |
| (struct.new $empty) |
| (local.get $input) |
| ) |
| ) |
| (local.get $tempresult) |
| ) |
| |
| ;; CHECK: (func $propagate-different-params (type $12) (param $input1 (ref $empty)) (param $input2 (ref $empty)) (result i32) |
| ;; CHECK-NEXT: (local $tempresult i32) |
| ;; CHECK-NEXT: (local.set $tempresult |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $input1) |
| ;; CHECK-NEXT: (local.get $input2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $tempresult) |
| ;; CHECK-NEXT: ) |
| (func $propagate-different-params (param $input1 (ref $empty)) (param $input2 (ref $empty)) (result i32) |
| (local $tempresult i32) |
| (local.set $tempresult |
| ;; We cannot say anything about parameters - they might alias, or not. |
| (ref.eq |
| (local.get $input1) |
| (local.get $input2) |
| ) |
| ) |
| (local.get $tempresult) |
| ) |
| |
| ;; CHECK: (func $propagate-same-param (type $6) (param $input (ref $empty)) (result i32) |
| ;; CHECK-NEXT: (local $tempresult i32) |
| ;; CHECK-NEXT: (local.set $tempresult |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $input) |
| ;; CHECK-NEXT: (local.get $input) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $tempresult) |
| ;; CHECK-NEXT: ) |
| (func $propagate-same-param (param $input (ref $empty)) (result i32) |
| (local $tempresult i32) |
| (local.set $tempresult |
| ;; We could optimize this in principle, but atm do not. |
| ;; Note that optimize-instructions can handle patterns like this. |
| (ref.eq |
| (local.get $input) |
| (local.get $input) |
| ) |
| ) |
| (local.get $tempresult) |
| ) |
| |
| ;; CHECK: (func $propagate-uncertain-local (type $func-return-i32) (result i32) |
| ;; CHECK-NEXT: (local $tempresult i32) |
| ;; CHECK-NEXT: (local $tempref (ref null $empty)) |
| ;; CHECK-NEXT: (local $stashedref (ref null $empty)) |
| ;; CHECK-NEXT: (local.set $tempref |
| ;; CHECK-NEXT: (struct.new_default $empty) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $stashedref |
| ;; CHECK-NEXT: (local.get $tempref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $tempref |
| ;; CHECK-NEXT: (struct.new_default $empty) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $tempresult |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $tempref) |
| ;; CHECK-NEXT: (local.get $stashedref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $tempresult) |
| ;; CHECK-NEXT: ) |
| (func $propagate-uncertain-local (result i32) |
| (local $tempresult i32) |
| (local $tempref (ref null $empty)) |
| (local $stashedref (ref null $empty)) |
| (local.set $tempref |
| (struct.new $empty) |
| ) |
| (local.set $stashedref |
| (local.get $tempref) |
| ) |
| ;; This if makes it impossible to know what value the ref.eq later should |
| ;; return. |
| (if |
| (call $helper |
| (i32.const 0) |
| ) |
| (then |
| (local.set $tempref |
| (struct.new $empty) |
| ) |
| ) |
| ) |
| (local.set $tempresult |
| (ref.eq |
| (local.get $tempref) |
| (local.get $stashedref) |
| ) |
| ) |
| (local.get $tempresult) |
| ) |
| |
| ;; CHECK: (func $propagate-uncertain-loop (type $3) |
| ;; CHECK-NEXT: (local $tempresult i32) |
| ;; CHECK-NEXT: (local $tempref (ref null $empty)) |
| ;; CHECK-NEXT: (local $stashedref (ref null $empty)) |
| ;; CHECK-NEXT: (local.set $tempref |
| ;; CHECK-NEXT: (struct.new_default $empty) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $stashedref |
| ;; CHECK-NEXT: (local.get $tempref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (local.set $tempresult |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $tempref) |
| ;; CHECK-NEXT: (local.get $stashedref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $tempref |
| ;; CHECK-NEXT: (struct.new_default $empty) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br_if $loop |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (local.get $tempresult) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $propagate-uncertain-loop |
| (local $tempresult i32) |
| (local $tempref (ref null $empty)) |
| (local $stashedref (ref null $empty)) |
| (local.set $tempref |
| (struct.new $empty) |
| ) |
| (local.set $stashedref |
| (local.get $tempref) |
| ) |
| (loop $loop |
| ;; Each iteration in this loop may see a different struct, so we cannot |
| ;; precompute the ref.eq here. |
| (local.set $tempresult |
| (ref.eq |
| (local.get $tempref) |
| (local.get $stashedref) |
| ) |
| ) |
| (local.set $tempref |
| (struct.new $empty) |
| ) |
| (br_if $loop |
| (call $helper |
| (local.get $tempresult) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $propagate-certain-loop (type $3) |
| ;; CHECK-NEXT: (local $tempresult i32) |
| ;; CHECK-NEXT: (local $tempref (ref null $empty)) |
| ;; CHECK-NEXT: (local $stashedref (ref null $empty)) |
| ;; CHECK-NEXT: (local.set $tempref |
| ;; CHECK-NEXT: (struct.new_default $empty) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $stashedref |
| ;; CHECK-NEXT: (local.get $tempref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (local.set $tempresult |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br_if $loop |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $propagate-certain-loop |
| (local $tempresult i32) |
| (local $tempref (ref null $empty)) |
| (local $stashedref (ref null $empty)) |
| ;; As above, but remove the new in the loop, so that each loop iteration does |
| ;; in fact have the ref locals identical, and we can precompute a 1. |
| (local.set $tempref |
| (struct.new $empty) |
| ) |
| (local.set $stashedref |
| (local.get $tempref) |
| ) |
| (loop $loop |
| (local.set $tempresult |
| (ref.eq |
| (local.get $tempref) |
| (local.get $stashedref) |
| ) |
| ) |
| (br_if $loop |
| (call $helper |
| (local.get $tempresult) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $propagate-certain-loop-2 (type $3) |
| ;; CHECK-NEXT: (local $tempresult i32) |
| ;; CHECK-NEXT: (local $tempref (ref null $empty)) |
| ;; CHECK-NEXT: (local $stashedref (ref null $empty)) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (local.set $tempref |
| ;; CHECK-NEXT: (struct.new_default $empty) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $stashedref |
| ;; CHECK-NEXT: (local.get $tempref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $tempresult |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br_if $loop |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $propagate-certain-loop-2 |
| (local $tempresult i32) |
| (local $tempref (ref null $empty)) |
| (local $stashedref (ref null $empty)) |
| (loop $loop |
| ;; Another example of a loop where we can optimize. Here the new is inside |
| ;; the loop. |
| (local.set $tempref |
| (struct.new $empty) |
| ) |
| (local.set $stashedref |
| (local.get $tempref) |
| ) |
| (local.set $tempresult |
| (ref.eq |
| (local.get $tempref) |
| (local.get $stashedref) |
| ) |
| ) |
| (br_if $loop |
| (call $helper |
| (local.get $tempresult) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $propagate-possibly-certain-loop (type $3) |
| ;; CHECK-NEXT: (local $tempresult i32) |
| ;; CHECK-NEXT: (local $tempref (ref null $empty)) |
| ;; CHECK-NEXT: (local $stashedref (ref null $empty)) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $tempref |
| ;; CHECK-NEXT: (struct.new_default $empty) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $stashedref |
| ;; CHECK-NEXT: (local.get $tempref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $tempresult |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $tempref) |
| ;; CHECK-NEXT: (local.get $stashedref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br_if $loop |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (local.get $tempresult) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $propagate-possibly-certain-loop |
| (local $tempresult i32) |
| (local $tempref (ref null $empty)) |
| (local $stashedref (ref null $empty)) |
| (loop $loop |
| ;; As above, but move the set of $stashedref below the if. That means that |
| ;; it must be identical to $tempref in each iteration. However, that is |
| ;; something we cannot infer atm (while SSA could), so we do not infer |
| ;; anything here for now. |
| (if |
| (call $helper |
| (i32.const 0) |
| ) |
| (then |
| (local.set $tempref |
| (struct.new $empty) |
| ) |
| ) |
| ) |
| (local.set $stashedref |
| (local.get $tempref) |
| ) |
| (local.set $tempresult |
| (ref.eq |
| (local.get $tempref) |
| (local.get $stashedref) |
| ) |
| ) |
| (br_if $loop |
| (call $helper |
| (local.get $tempresult) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $helper (type $13) (param $0 i32) (result i32) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $helper (param i32) (result i32) |
| (unreachable) |
| ) |
| |
| ;; CHECK: (func $odd-cast-and-get (type $3) |
| ;; CHECK-NEXT: (local $temp (ref null $B)) |
| ;; CHECK-NEXT: (local.set $temp |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $odd-cast-and-get |
| (local $temp (ref null $B)) |
| ;; Try to cast a null of A to B. While the types are incompatible, ref.cast |
| ;; returns a null when given a null (and the null must have the type that the |
| ;; ref.cast null instruction has, that is, the value is a null of type $B). So this |
| ;; is an odd cast that "works". |
| (local.set $temp |
| (ref.cast (ref null $B) |
| (ref.null $A) |
| ) |
| ) |
| (drop |
| ;; Read from the local, which precompute should set to a null with the proper |
| ;; type. |
| (struct.get $B 0 |
| (local.get $temp) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $odd-cast-and-get-tuple (type $3) |
| ;; CHECK-NEXT: (local $temp (tuple (ref null $B) i32)) |
| ;; CHECK-NEXT: (local.set $temp |
| ;; CHECK-NEXT: (tuple.make 2 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block ;; (replaces unreachable StructGet we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $odd-cast-and-get-tuple |
| (local $temp (tuple (ref null $B) i32)) |
| ;; As above, but with a tuple. |
| (local.set $temp |
| (tuple.make 2 |
| (ref.cast (ref null $B) |
| (ref.null $A) |
| ) |
| (i32.const 10) |
| ) |
| ) |
| (drop |
| (struct.get $B 0 |
| (tuple.extract 2 0 |
| (local.get $temp) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $receive-f64 (type $14) (param $0 f64) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $receive-f64 (param f64) |
| (unreachable) |
| ) |
| |
| ;; CHECK: (func $odd-cast-and-get-non-null (type $15) (param $temp (ref $func-return-i32)) |
| ;; CHECK-NEXT: (local.set $temp |
| ;; CHECK-NEXT: (ref.cast (ref nofunc) |
| ;; CHECK-NEXT: (ref.func $receive-f64) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (call_ref $func-return-i32 |
| ;; CHECK-NEXT: (local.get $temp) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $odd-cast-and-get-non-null (param $temp (ref $func-return-i32)) |
| ;; Try to cast a function to an incompatible type. |
| (local.set $temp |
| (ref.cast (ref $func-return-i32) |
| (ref.func $receive-f64) |
| ) |
| ) |
| (drop |
| ;; Read from the local, checking whether precompute set a value there (it |
| ;; should not, as the cast fails). |
| (call_ref $func-return-i32 |
| (local.get $temp) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $new_block_unreachable (type $8) (result anyref) |
| ;; CHECK-NEXT: (block ;; (replaces unreachable StructNew we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $new_block_unreachable (result anyref) |
| (struct.new $struct |
| ;; The value is a block with an unreachable. precompute will get rid of the |
| ;; block, after which fuzz-exec should not crash - this is a regression test |
| ;; for us being careful in how we execute an unreachable struct.new |
| (block $label$1 (result i32) |
| (unreachable) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $br_on_cast-on-creation (type $16) (result (ref $empty)) |
| ;; CHECK-NEXT: (block $label (result (ref $empty)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (br_on_cast $label (ref $empty) (ref $empty) |
| ;; CHECK-NEXT: (struct.new_default $empty) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $br_on_cast-on-creation (result (ref $empty)) |
| (block $label (result (ref $empty)) |
| (drop |
| (br_on_cast $label anyref (ref $empty) |
| (struct.new_default $empty) |
| ) |
| ) |
| (unreachable) |
| ) |
| ) |
| |
| ;; CHECK: (func $ref.is_null (type $4) (param $param i32) |
| ;; CHECK-NEXT: (local $ref (ref null $empty)) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new_default $empty) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (local.get $param) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $ref |
| ;; CHECK-NEXT: (struct.new_default $empty) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (call $helper |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref.is_null (param $param i32) |
| (local $ref (ref null $empty)) |
| ;; Test ref.null on references, and also test that we can infer multiple |
| ;; assignments in the same function, without confusion between them. |
| (local.set $ref |
| (struct.new $empty) |
| ) |
| (drop |
| (call $helper |
| ;; The reference here is definitely not null. |
| (ref.is_null |
| (local.get $ref) |
| ) |
| ) |
| ) |
| (local.set $ref |
| (ref.null $empty) |
| ) |
| (drop |
| (call $helper |
| ;; The reference here is definitely null. |
| (ref.is_null |
| (local.get $ref) |
| ) |
| ) |
| ) |
| (if |
| (local.get $param) |
| (then |
| (local.set $ref |
| (struct.new $empty) |
| ) |
| ) |
| ) |
| (drop |
| (call $helper |
| ;; The reference here might be null. |
| (ref.is_null |
| (local.get $ref) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $remove-set (type $17) (result (ref func)) |
| ;; CHECK-NEXT: (local $nn funcref) |
| ;; CHECK-NEXT: (local $i i32) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (local.set $i |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br $loop) |
| ;; CHECK-NEXT: (return |
| ;; CHECK-NEXT: (ref.as_non_null |
| ;; CHECK-NEXT: (local.get $nn) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $remove-set (result (ref func)) |
| (local $nn (ref func)) |
| (local $i i32) |
| (loop $loop |
| ;; Add a local.set here in the loop, just so the entire loop is not optimized |
| ;; out. |
| (local.set $i |
| (i32.const 0) |
| ) |
| ;; This entire block can be precomputed into an unconditional br. That |
| ;; removes the local.set, which means the local no longer validates since |
| ;; there is a get without a set (the get is never reached, but the validator |
| ;; does not take that into account). Fixups will turn the local nullable to |
| ;; avoid that problem. |
| (block |
| (br_if $loop |
| (i32.const 1) |
| ) |
| (local.set $nn |
| (ref.func $remove-set) |
| ) |
| ) |
| (return |
| (local.get $nn) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $strings (type $18) (param $param (ref string)) |
| ;; CHECK-NEXT: (local $s (ref string)) |
| ;; CHECK-NEXT: (local.set $s |
| ;; CHECK-NEXT: (string.const "hello, world") |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $strings |
| ;; CHECK-NEXT: (string.const "hello, world") |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $strings |
| ;; CHECK-NEXT: (string.const "hello, world") |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $strings (param $param (ref string)) |
| (local $s (ref string)) |
| (local.set $s |
| (string.const "hello, world") |
| ) |
| ;; The constant string should be propagated twice, to both of these calls. |
| (call $strings |
| (local.get $s) |
| ) |
| (call $strings |
| (local.get $s) |
| ) |
| ) |
| |
| ;; CHECK: (func $struct.new.packed (type $func-return-i32) (result i32) |
| ;; CHECK-NEXT: (i32.const 120) |
| ;; CHECK-NEXT: ) |
| (func $struct.new.packed (result i32) |
| ;; Truncation happens when we write to this packed i8 field, so the result we |
| ;; read back is 0x12345678 & 0xff which is 0x78 == 120. |
| (struct.get_s $struct_i8 0 |
| (struct.new $struct_i8 |
| (i32.const 0x12345678) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $get-nonnullable-in-unreachable (type $8) (result anyref) |
| ;; CHECK-NEXT: (local $x (ref any)) |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| (func $get-nonnullable-in-unreachable (result anyref) |
| (local $x (ref any)) |
| ;; We cannot read a non-nullable local without setting it first, but it is ok |
| ;; to do so here because we are in unreachable code. We should also not error |
| ;; about this get seeming to read the default value from the function entry |
| ;; (because it does not, as the entry is not reachable from it). Nothing is |
| ;; expected to be optimized here. |
| |
| ;; This unreachable set is needed for the later get to validate. |
| (local.set $x |
| (unreachable) |
| ) |
| ;; This if is needed so we have an interesting enough CFG that a possible |
| ;; assertion can be hit about reading the default value from the entry in a |
| ;; later block. |
| (if |
| (i32.const 1) |
| (then |
| (unreachable) |
| ) |
| ) |
| (local.get $x) |
| ) |
| |
| ;; CHECK: (func $get-nonnullable-in-unreachable-entry (type $9) (param $x i32) (param $y (ref any)) |
| ;; CHECK-NEXT: (local $0 (ref any)) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (br_if $loop |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $get-nonnullable-in-unreachable-entry (param $x i32) (param $y (ref any)) |
| (local $0 (ref any)) |
| ;; As above, but now the first basic block is unreachable, and we need to |
| ;; detect that specifically, as the block after it *does* have entries even |
| ;; though it is unreachable (it is a loop, and has itself as an entry). |
| (unreachable) |
| (local.set $0 |
| (local.get $y) |
| ) |
| (loop $loop |
| (br_if $loop |
| (local.get $x) |
| ) |
| (drop |
| (local.get $0) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $get-nonnullable-in-unreachable-later-loop (type $9) (param $x i32) (param $y (ref any)) |
| ;; CHECK-NEXT: (local $0 (ref any)) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $y) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (loop $loop |
| ;; CHECK-NEXT: (br_if $loop |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $get-nonnullable-in-unreachable-later-loop (param $x i32) (param $y (ref any)) |
| (local $0 (ref any)) |
| ;; This |if| is added, which means the loop is later in the function. |
| ;; Otherwise this is the same as before. |
| (if |
| (local.get $x) |
| (then |
| (nop) |
| ) |
| ) |
| (unreachable) |
| (local.set $0 |
| (local.get $y) |
| ) |
| (loop $loop |
| (br_if $loop |
| (local.get $x) |
| ) |
| (drop |
| (local.get $0) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $get-nonnullable-in-unreachable-tuple (type $19) (result anyref i32) |
| ;; CHECK-NEXT: (local $x (tuple (ref any) i32)) |
| ;; CHECK-NEXT: (local.tee $x |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| (func $get-nonnullable-in-unreachable-tuple (result anyref i32) |
| ;; As $get-nonnullable-in-unreachable but the local is a tuple (so we need to |
| ;; check isDefaultable, and not just isNullable). |
| (local $x (tuple (ref any) i32)) |
| (local.set $x |
| (unreachable) |
| ) |
| (if |
| (i32.const 1) |
| (then |
| (unreachable) |
| ) |
| ) |
| (local.get $x) |
| ) |
| ) |