blob: fc4cc7c2ee9ec16ff53c7621b418a0703b0d7b09 [file] [log] [blame] [edit]
;; 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)
)
)