| ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. |
| |
| ;; RUN: foreach %s %t wasm-opt -all --heap2local -S -o - | filecheck %s |
| |
| (module |
| (type $i32 (struct (field (mut i32)))) |
| (type $i64 (struct (field (mut i64)))) |
| ;; CHECK: (type $struct (struct (field (mut (ref null $struct))))) |
| (type $struct (struct (field (mut (ref null $struct))))) |
| ;; CHECK: (type $1 (func (result i32))) |
| |
| ;; CHECK: (type $2 (func (result i64))) |
| |
| ;; CHECK: (type $3 (func (param (ref null $struct)) (result (ref null $struct)))) |
| |
| ;; CHECK: (type $4 (func (param (ref null $struct) (ref null $struct)) (result (ref null $struct)))) |
| |
| ;; CHECK: (type $5 (func (param i32) (result i32))) |
| |
| ;; CHECK: (type $arr (array (mut i32))) |
| (type $arr (array (mut i32))) |
| |
| ;; CHECK: (func $escape-rmw (type $3) (param $0 (ref null $struct)) (result (ref null $struct)) |
| ;; CHECK-NEXT: (struct.atomic.rmw.xchg $struct 0 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (struct.new_default $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $escape-rmw (param (ref null $struct)) (result (ref null $struct)) |
| ;; Allocations that flow into RMW modification values can be written and escape. |
| (struct.atomic.rmw.xchg $struct 0 |
| (local.get 0) |
| (struct.new_default $struct) |
| ) |
| ) |
| |
| ;; CHECK: (func $escape-cmpxchg (type $3) (param $0 (ref null $struct)) (result (ref null $struct)) |
| ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct 0 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (struct.new_default $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $escape-cmpxchg (param (ref null $struct)) (result (ref null $struct)) |
| ;; Similarly, allocations that flow into cmpxchg replacement values can escape. |
| (struct.atomic.rmw.cmpxchg $struct 0 |
| (local.get 0) |
| (local.get 0) |
| (struct.new_default $struct) |
| ) |
| ) |
| |
| ;; CHECK: (func $no-escape-cmpxchg-expected (type $3) (param $0 (ref null $struct)) (result (ref null $struct)) |
| ;; CHECK-NEXT: (local $1 (ref null $struct)) |
| ;; CHECK-NEXT: (local $2 (ref null $struct)) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (struct.atomic.get $struct 0 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $no-escape-cmpxchg-expected (param (ref null $struct)) (result (ref null $struct)) |
| ;; Allocations that flow into the cmpxchg `expected` operand do not escape |
| ;; and can be optimized, but do not require any fixups of the cmpxchg. |
| (struct.atomic.rmw.cmpxchg $struct 0 |
| (local.get 0) |
| (struct.new_default $struct) |
| (local.get 0) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-add-i32 (type $1) (result i32) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-add-i32 (result i32) |
| (struct.atomic.rmw.add $i32 0 |
| (struct.new_default $i32) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-sub-i32 (type $1) (result i32) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.sub |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-sub-i32 (result i32) |
| (struct.atomic.rmw.sub $i32 0 |
| (struct.new_default $i32) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-and-i32 (type $1) (result i32) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.and |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-and-i32 (result i32) |
| (struct.atomic.rmw.and $i32 0 |
| (struct.new_default $i32) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-or-i32 (type $1) (result i32) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.or |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-or-i32 (result i32) |
| (struct.atomic.rmw.or $i32 0 |
| (struct.new_default $i32) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-xor-i32 (type $1) (result i32) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.xor |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-xor-i32 (result i32) |
| (struct.atomic.rmw.xor $i32 0 |
| (struct.new_default $i32) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-xchg-i32 (type $1) (result i32) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-xchg-i32 (result i32) |
| (struct.atomic.rmw.xchg $i32 0 |
| (struct.new_default $i32) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-cmpxchg-i32 (type $1) (result i32) |
| ;; 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 nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.eq |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-cmpxchg-i32 (result i32) |
| (struct.atomic.rmw.cmpxchg $i32 0 |
| (struct.new_default $i32) |
| (i32.const 1) |
| (i32.const 2) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-add-i64 (type $2) (result i64) |
| ;; CHECK-NEXT: (local $0 i64) |
| ;; CHECK-NEXT: (local $1 i64) |
| ;; CHECK-NEXT: (local $2 i64) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i64.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i64.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i64.add |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-add-i64 (result i64) |
| (struct.atomic.rmw.add $i64 0 |
| (struct.new_default $i64) |
| (i64.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-sub-i64 (type $2) (result i64) |
| ;; CHECK-NEXT: (local $0 i64) |
| ;; CHECK-NEXT: (local $1 i64) |
| ;; CHECK-NEXT: (local $2 i64) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i64.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i64.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i64.sub |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-sub-i64 (result i64) |
| (struct.atomic.rmw.sub $i64 0 |
| (struct.new_default $i64) |
| (i64.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-and-i64 (type $2) (result i64) |
| ;; CHECK-NEXT: (local $0 i64) |
| ;; CHECK-NEXT: (local $1 i64) |
| ;; CHECK-NEXT: (local $2 i64) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i64.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i64.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i64.and |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-and-i64 (result i64) |
| (struct.atomic.rmw.and $i64 0 |
| (struct.new_default $i64) |
| (i64.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-or-i64 (type $2) (result i64) |
| ;; CHECK-NEXT: (local $0 i64) |
| ;; CHECK-NEXT: (local $1 i64) |
| ;; CHECK-NEXT: (local $2 i64) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i64.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i64.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i64.or |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-or-i64 (result i64) |
| (struct.atomic.rmw.or $i64 0 |
| (struct.new_default $i64) |
| (i64.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-xor-i64 (type $2) (result i64) |
| ;; CHECK-NEXT: (local $0 i64) |
| ;; CHECK-NEXT: (local $1 i64) |
| ;; CHECK-NEXT: (local $2 i64) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i64.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i64.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i64.xor |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-xor-i64 (result i64) |
| (struct.atomic.rmw.xor $i64 0 |
| (struct.new_default $i64) |
| (i64.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-xchg-i64 (type $2) (result i64) |
| ;; CHECK-NEXT: (local $0 i64) |
| ;; CHECK-NEXT: (local $1 i64) |
| ;; CHECK-NEXT: (local $2 i64) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i64.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i64.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-xchg-i64 (result i64) |
| (struct.atomic.rmw.xchg $i64 0 |
| (struct.new_default $i64) |
| (i64.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-cmpxchg-i64 (type $2) (result i64) |
| ;; CHECK-NEXT: (local $0 i64) |
| ;; CHECK-NEXT: (local $1 i64) |
| ;; CHECK-NEXT: (local $2 i64) |
| ;; CHECK-NEXT: (local $3 i64) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i64.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i64.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (i64.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i64.eq |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-cmpxchg-i64 (result i64) |
| (struct.atomic.rmw.cmpxchg $i64 0 |
| (struct.new_default $i64) |
| (i64.const 1) |
| (i64.const 2) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-xchg-ref (type $3) (param $0 (ref null $struct)) (result (ref null $struct)) |
| ;; CHECK-NEXT: (local $1 (ref null $struct)) |
| ;; CHECK-NEXT: (local $2 (ref null $struct)) |
| ;; CHECK-NEXT: (local $3 (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| (func $rmw-xchg-ref (param (ref null $struct)) (result (ref null $struct)) |
| (struct.atomic.rmw.xchg $struct 0 |
| (struct.new_default $struct) |
| (local.get 0) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-cmpxchg-ref (type $4) (param $0 (ref null $struct)) (param $1 (ref null $struct)) (result (ref null $struct)) |
| ;; CHECK-NEXT: (local $2 (ref null $struct)) |
| ;; CHECK-NEXT: (local $3 (ref null $struct)) |
| ;; CHECK-NEXT: (local $4 eqref) |
| ;; CHECK-NEXT: (local $5 (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $4 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $5 |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: (local.get $4) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (local.get $5) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| (func $rmw-cmpxchg-ref (param (ref null $struct) (ref null $struct)) (result (ref null $struct)) |
| (struct.atomic.rmw.cmpxchg $struct 0 |
| (struct.new_default $struct) |
| (local.get 0) |
| (local.get 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-acqrel (type $1) (result i32) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $rmw-acqrel (result i32) |
| (struct.atomic.rmw.add acqrel acqrel $i32 0 |
| (struct.new_default $i32) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $cmpxchg-acqrel (type $1) (result i32) |
| ;; 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 nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.eq |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| (func $cmpxchg-acqrel (result i32) |
| (struct.atomic.rmw.cmpxchg acqrel acqrel $i32 0 |
| (struct.new_default $i32) |
| (i32.const 1) |
| (i32.const 2) |
| ) |
| ) |
| |
| ;; CHECK: (func $rmw-unreachable-value (type $1) (result i32) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (block ;; (replaces unreachable StructRMW we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $rmw-unreachable-value (result i32) |
| ;; When the value is unreachable, the whole expression is unreachable. |
| ;; We should not attempt to optimize this (it would hit an assertion |
| ;; on type == field.type since unreachable != i32). |
| (struct.atomic.rmw.add $i32 0 |
| (struct.new_default $i32) |
| (unreachable) |
| ) |
| ) |
| |
| ;; CHECK: (func $cmpxchg-unreachable-expected (type $1) (result i32) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (block ;; (replaces unreachable StructCmpxchg we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cmpxchg-unreachable-expected (result i32) |
| ;; When the expected operand is unreachable, the whole expression is |
| ;; unreachable. We should not attempt to optimize this. |
| (struct.atomic.rmw.cmpxchg $i32 0 |
| (struct.new_default $i32) |
| (unreachable) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $array-rmw-add (type $1) (result i32) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (local $4 i32) |
| ;; CHECK-NEXT: (local $5 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $5 |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $4 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.add |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $5) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $4) |
| ;; CHECK-NEXT: ) |
| (func $array-rmw-add (result i32) |
| ;; Array atomic RMW on a non-escaping fixed-size array should be |
| ;; optimized: the array is converted to a struct, then to locals. |
| (array.atomic.rmw.add $arr |
| (array.new_fixed $arr 2 |
| (i32.const 0) |
| (i32.const 0) |
| ) |
| (i32.const 0) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $array-cmpxchg (type $1) (result i32) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (local $4 i32) |
| ;; CHECK-NEXT: (local $5 i32) |
| ;; CHECK-NEXT: (local $6 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $5 |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $6 |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $4 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (i32.eq |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: (local.get $5) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $6) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $4) |
| ;; CHECK-NEXT: ) |
| (func $array-cmpxchg (result i32) |
| ;; Array atomic cmpxchg on a non-escaping fixed-size array should be |
| ;; optimized similarly. |
| (array.atomic.rmw.cmpxchg $arr |
| (array.new_fixed $arr 2 |
| (i32.const 0) |
| (i32.const 0) |
| ) |
| (i32.const 0) |
| (i32.const 10) |
| (i32.const 20) |
| ) |
| ) |
| |
| ;; CHECK: (func $array-rmw-nonconstant-index (type $5) (param $idx i32) (result i32) |
| ;; CHECK-NEXT: (array.atomic.rmw.add $arr |
| ;; CHECK-NEXT: (array.new_fixed $arr 2 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $idx) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $array-rmw-nonconstant-index (param $idx i32) (result i32) |
| ;; A non-constant index prevents the optimization, since Array2Struct |
| ;; needs to know which struct field to access at compile time. |
| (array.atomic.rmw.add $arr |
| (array.new_fixed $arr 2 |
| (i32.const 0) |
| (i32.const 0) |
| ) |
| (local.get $idx) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $array-rmw-oob (type $1) (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $array-rmw-oob (result i32) |
| ;; An out-of-bounds index on a zero-size array. The access will always |
| ;; trap, so we emit drops for operands and an unreachable. |
| (array.atomic.rmw.add $arr |
| (array.new_default $arr |
| (i32.const 0) |
| ) |
| (i32.const 0) |
| (i32.const 1) |
| ) |
| ) |
| |
| ;; CHECK: (func $array-cmpxchg-oob (type $1) (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 20) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $array-cmpxchg-oob (result i32) |
| ;; As above, but for cmpxchg with an out-of-bounds index. |
| (array.atomic.rmw.cmpxchg $arr |
| (array.new_default $arr |
| (i32.const 0) |
| ) |
| (i32.const 0) |
| (i32.const 10) |
| (i32.const 20) |
| ) |
| ) |
| ) |
| |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $struct (struct (field (mut (ref null $array))))) |
| (type $struct (struct (field (mut (ref null $array))))) |
| ;; CHECK: (type $array (array (mut i32))) |
| (type $array (array (field (mut i32)))) |
| ) |
| ;; CHECK: (type $2 (func)) |
| |
| ;; CHECK: (func $cmpxchg-expected-first (type $2) |
| ;; CHECK-NEXT: (local $0 i32) |
| ;; CHECK-NEXT: (local $1 (ref null (exact $struct))) |
| ;; CHECK-NEXT: (local $2 i32) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (local $4 (ref null $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $4 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block (result (ref null $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $4) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cmpxchg-expected-first |
| (drop |
| (struct.atomic.rmw.cmpxchg $struct 0 |
| (struct.new_default $struct) |
| ;; This will be optimized out before the previous `ref` operand because |
| ;; array allocations are processed before struct allocations. If we did |
| ;; not handle flows from `expected` specially, we would incorrectly |
| ;; refinalize the cmpxchg to have the array replacement type, then get |
| ;; confused and crash when later processing the `ref` operand. |
| (array.new_default $array (i32.const 1)) |
| ;; Normally replacements escape and cannot be optimized, but the cmpxchg |
| ;; is replaced when `expected` is processed, so we end up seeing that |
| ;; this doesn't escape. |
| (array.new_default $array (i32.const 2)) |
| ) |
| ) |
| ) |
| ) |
| |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $struct (struct (field (mut (ref null $array))))) |
| (type $struct (struct (field (mut (ref null $array))))) |
| ;; CHECK: (type $array (array (mut i32))) |
| (type $array (array (field (mut i32)))) |
| ) |
| ;; CHECK: (type $2 (func (result (ref $array)))) |
| |
| ;; CHECK: (func $cmpxchg-expected-first (type $2) (result (ref $array)) |
| ;; CHECK-NEXT: (local $array (ref $array)) |
| ;; CHECK-NEXT: (local $1 i32) |
| ;; CHECK-NEXT: (local $2 (ref null (exact $struct))) |
| ;; CHECK-NEXT: (local $3 (ref null $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.tee $array |
| ;; CHECK-NEXT: (array.new_default $array |
| ;; CHECK-NEXT: (i32.const 2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block (result (ref null $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $array) |
| ;; CHECK-NEXT: ) |
| (func $cmpxchg-expected-first (result (ref $array)) |
| ;; Same as before, but now the replacement still escapes. Nothing should go |
| ;; wrong. |
| (local $array (ref $array)) |
| (drop |
| (struct.atomic.rmw.cmpxchg $struct 0 |
| (struct.new_default $struct) |
| (array.new_default $array (i32.const 1)) |
| (local.tee $array |
| (array.new_default $array (i32.const 2)) |
| ) |
| ) |
| ) |
| (local.get $array) |
| ) |
| ) |
| |
| (module |
| (rec |
| ;; CHECK: (type $0 (func)) |
| |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $inner (array i8)) |
| (type $inner (array i8)) |
| ;; CHECK: (type $struct (struct (field (mut eqref)))) |
| (type $struct (struct (field (mut eqref)))) |
| ;; CHECK: (type $array (array (mut eqref))) |
| (type $array (array (field (mut eqref)))) |
| ) |
| ;; CHECK: (func $test-cmpxchg-scratch-oob (type $0) |
| ;; CHECK-NEXT: (local $a arrayref) |
| ;; CHECK-NEXT: (local $1 (ref null (exact $struct))) |
| ;; CHECK-NEXT: (local $2 eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $test-cmpxchg-scratch-oob |
| (local $a arrayref) |
| ;; This allocation and set get optimized, creating the LocalGraph flower. |
| (local.set $a |
| (array.new_fixed $inner 0) |
| ) |
| (drop |
| ;; Then `expected` gets optimized, creating a new scratch local. Since the |
| ;; flower is already created, the index of the scratch local would be out |
| ;; of bounds if we tried to look it up in the original LocalGraph. This |
| ;; still works fine because we reset the LocalGraph after each |
| ;; optimization. |
| (struct.atomic.rmw.cmpxchg $struct 0 |
| (struct.new_default $struct) |
| (array.new_fixed $inner 0) |
| (array.new_fixed $inner 0) |
| ) |
| ) |
| (unreachable) |
| ) |
| |
| ;; CHECK: (func $test-cmpxchg-scratch-oob-array (type $0) |
| ;; CHECK-NEXT: (local $a arrayref) |
| ;; CHECK-NEXT: (local $1 eqref) |
| ;; CHECK-NEXT: (local $2 eqref) |
| ;; CHECK-NEXT: (local $3 eqref) |
| ;; CHECK-NEXT: (local $4 eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $test-cmpxchg-scratch-oob-array |
| ;; Same as above, but accessing an array type. `ref` is always processed |
| ;; first, so we do not have the same problem to avoid. |
| (local $a arrayref) |
| (local.set $a |
| (array.new_fixed $inner 0) |
| ) |
| (drop |
| (array.atomic.rmw.cmpxchg $array |
| (array.new_default $array |
| (i32.const 1) |
| ) |
| (i32.const 0) |
| (array.new_fixed $inner 0) |
| (array.new_fixed $inner 0) |
| ) |
| ) |
| (unreachable) |
| ) |
| ) |
| |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $outer (struct (field (mut (ref $inner))))) |
| (type $outer (struct (field (mut (ref $inner))))) |
| ;; CHECK: (type $inner (struct)) |
| (type $inner (struct)) |
| |
| ;; CHECK: (type $shared-outer (shared (struct (field (mut (ref $shared-inner)))))) |
| (type $shared-outer (shared (struct (field (mut (ref $shared-inner)))))) |
| ;; CHECK: (type $shared-inner (shared (struct))) |
| (type $shared-inner (shared (struct))) |
| ) |
| |
| ;; CHECK: (type $4 (func)) |
| |
| ;; CHECK: (func $cmpxchg-non-nullable-field (type $4) |
| ;; CHECK-NEXT: (local $0 (ref $inner)) |
| ;; CHECK-NEXT: (local $1 (ref $inner)) |
| ;; CHECK-NEXT: (local $2 (ref $inner)) |
| ;; CHECK-NEXT: (local $3 eqref) |
| ;; CHECK-NEXT: (local $4 (ref $inner)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $inner)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (struct.new_default $inner) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cmpxchg-non-nullable-field |
| (drop |
| (struct.atomic.rmw.cmpxchg $outer 0 |
| ;; When `ref` gets optimized, we need to make sure the scratch local for |
| ;; `expected` is nullable, even though the operand and field are both |
| ;; non-nullable. This avoids type errors when we later optimize the |
| ;; `expected` field and make it a nullref. |
| (struct.new $outer |
| (struct.new_default $inner) |
| ) |
| (struct.new_default $inner) |
| (struct.new_default $inner) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $cmpxchg-non-nullable-field-shared (type $4) |
| ;; CHECK-NEXT: (local $0 (ref $shared-inner)) |
| ;; CHECK-NEXT: (local $1 (ref $shared-inner)) |
| ;; CHECK-NEXT: (local $2 (ref $shared-inner)) |
| ;; CHECK-NEXT: (local $3 (ref null (shared eq))) |
| ;; CHECK-NEXT: (local $4 (ref $shared-inner)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref $shared-inner)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null (shared none))) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (struct.new_default $shared-inner) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null (shared none))) |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null (shared none))) |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cmpxchg-non-nullable-field-shared |
| (drop |
| (struct.atomic.rmw.cmpxchg $shared-outer 0 |
| ;; Same, but now with shared types. The scratch local must be a shared |
| ;; eqref. |
| (struct.new $shared-outer |
| (struct.new_default $shared-inner) |
| ) |
| (struct.new_default $shared-inner) |
| (struct.new_default $shared-inner) |
| ) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $struct (struct (field (mut (ref null $struct))))) |
| (type $struct (struct (field (mut (ref null $struct))))) |
| |
| ;; CHECK: (type $1 (func)) |
| |
| ;; CHECK: (export "test" (func $cmpxchg-ref-and-expected)) |
| |
| ;; CHECK: (func $cmpxchg-ref-and-expected (type $1) |
| ;; CHECK-NEXT: (local $local (ref $struct)) |
| ;; CHECK-NEXT: (local $1 (ref null $struct)) |
| ;; CHECK-NEXT: (local $2 (ref null $struct)) |
| ;; CHECK-NEXT: (local $3 eqref) |
| ;; CHECK-NEXT: (local $4 (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $4 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $4) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $cmpxchg-ref-and-expected (export "test") |
| (local $local (ref $struct)) |
| (drop |
| ;; The allocation flows to both `ref` and `expected` fields. We must |
| ;; prioritize the optimization for flows through `ref`. Otherwise, if we |
| ;; did the optimization for `expected` first, we would end up with a |
| ;; struct.get of the ref value, but the ref value would have been changed |
| ;; to a null and we would introduce a trap. |
| (struct.atomic.rmw.cmpxchg $struct 0 |
| (local.tee $local |
| (struct.new_default $struct) |
| ) |
| (local.get $local) |
| (ref.null none) |
| ) |
| ) |
| ) |
| ) |
| |
| (module |
| (type $array (shared (array i8))) |
| ;; CHECK: (type $struct (shared (struct (field (mut (ref null (shared array))))))) |
| (type $struct (shared (struct (field (mut (ref null (shared array))))))) |
| |
| ;; CHECK: (type $1 (func (param (ref $struct)))) |
| |
| ;; CHECK: (func $unreachable-shared-expected (type $1) (param $struct (ref $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $struct 0 |
| ;; CHECK-NEXT: (local.get $struct) |
| ;; CHECK-NEXT: (block (result (ref null (shared none))) |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $unreachable-shared-expected (param $struct (ref $struct)) |
| (drop |
| ;; This cmpxchg will not be optimized because it is unreachable. |
| (struct.atomic.rmw.cmpxchg $struct 0 |
| (local.get $struct) |
| ;; This `expected` allocation does not escape, so it will be optimized. |
| ;; When we convert the array to a struct, we have to make sure it |
| ;; becomes a shared struct so that when we later optimize out the |
| ;; allocation, we know to replace it with a shared null. If we don't, we |
| ;; will end up with a non-shared null here, which would be invalid. |
| (array.new_fixed $array 0) |
| (unreachable) |
| ) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $array (array (mut eqref))) |
| (type $array (array (mut eqref))) |
| |
| ;; CHECK: (type $1 (func (param (ref $array)))) |
| |
| ;; CHECK: (func $array-cmpxchg-expected (type $1) (param $array (ref $array)) |
| ;; CHECK-NEXT: (local $1 eqref) |
| ;; CHECK-NEXT: (local $2 (ref null $array)) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (local.get $array) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.atomic.get $array |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $array-cmpxchg-expected (param $array (ref $array)) |
| (drop |
| ;; We should not convert this to a struct cmpxchg because we do not |
| ;; optimize the `ref`. We should still be able to optimize the `expected` |
| ;; field, though. |
| (array.atomic.rmw.cmpxchg $array |
| (local.get $array) |
| (i32.const 0) |
| (array.new_default $array |
| (i32.const 1) |
| ) |
| (ref.null none) |
| ) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $array (array (mut eqref))) |
| (type $array (array (mut eqref))) |
| ;; CHECK: (type $1 (func (param (ref $array)))) |
| |
| ;; CHECK: (type $2 (func (result i32))) |
| |
| ;; CHECK: (type $3 (func (result eqref))) |
| |
| ;; CHECK: (import "" "" (func $effect-i32 (type $2) (result i32))) |
| (import "" "" (func $effect-i32 (result i32))) |
| ;; CHECK: (import "" "" (func $effect-eq (type $3) (result eqref))) |
| (import "" "" (func $effect-eq (result eqref))) |
| |
| ;; CHECK: (func $array-cmpxchg-expected-index-effect (type $1) (param $array (ref $array)) |
| ;; CHECK-NEXT: (local $1 eqref) |
| ;; CHECK-NEXT: (local $2 (ref null $array)) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (local.get $array) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (call $effect-i32) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (call $effect-eq) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.atomic.get $array |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $array-cmpxchg-expected-index-effect (param $array (ref $array)) |
| (drop |
| ;; The index is non-constant, but we can still optimize the expected |
| ;; field. We must preserve the index and the order of its effects. |
| (array.atomic.rmw.cmpxchg $array |
| (local.get $array) |
| (call $effect-i32) |
| (array.new_default $array (i32.const 1)) |
| (call $effect-eq) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $array-cmpxchg-expected-index-oob (type $1) (param $array (ref $array)) |
| ;; CHECK-NEXT: (local $1 eqref) |
| ;; CHECK-NEXT: (local $2 (ref null $array)) |
| ;; CHECK-NEXT: (local $3 i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result eqref) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (local.get $array) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $3 |
| ;; CHECK-NEXT: (i32.const -1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result nullref) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null none) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (call $effect-eq) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (array.atomic.get $array |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: (local.get $3) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $array-cmpxchg-expected-index-oob (param $array (ref $array)) |
| (drop |
| ;; Now the index is constant but surely out-of-bounds. We still optimize |
| ;; the same, way leaving the array.atomic.get to trap. |
| (array.atomic.rmw.cmpxchg $array |
| (local.get $array) |
| (i32.const -1) |
| (array.new_default $array (i32.const 1)) |
| (call $effect-eq) |
| ) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $struct (shared (struct (field (mut (ref null $struct)))))) |
| (type $struct (shared (struct (field (mut (ref null $struct)))))) |
| ;; CHECK: (type $1 (func (result i32))) |
| |
| ;; CHECK: (func $must-optimize-ref-eq (type $1) (result i32) |
| ;; CHECK-NEXT: (local $local (ref null $struct)) |
| ;; CHECK-NEXT: (local $1 (ref null $struct)) |
| ;; CHECK-NEXT: (local $2 (ref null $struct)) |
| ;; CHECK-NEXT: (local $3 (ref null (shared eq))) |
| ;; CHECK-NEXT: (local $4 (ref null $struct)) |
| ;; CHECK-NEXT: (local $5 (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null (shared none))) |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref null (shared none))) |
| ;; CHECK-NEXT: (local.set $5 |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $4 |
| ;; CHECK-NEXT: (struct.new_default $struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.set $2 |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (if |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (then |
| ;; CHECK-NEXT: (local.set $1 |
| ;; CHECK-NEXT: (local.get $4) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.is_null |
| ;; CHECK-NEXT: (block (result (ref null $struct)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.null (shared none)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (local.get $1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $must-optimize-ref-eq (result i32) |
| (local $local (ref null $struct)) |
| (drop |
| ;; 2. Next this will be optimized. The local from (1) representing field 0 |
| ;; will be compared with a local holding the expected value below. This |
| ;; ref.eq comparison should fail if we optimize correctly, leaving the |
| ;; local from (1) with its default null value. |
| (struct.atomic.rmw.cmpxchg acqrel acqrel $struct 0 |
| (local.tee $local |
| ;; 1. This will be optimized. The local representing field 0 will be |
| ;; set to null. |
| (struct.new_default $struct) |
| ) |
| ;; 3. This is the next allocation to be optimized. When it is replaced |
| ;; with a ref.null, it would cause the ref.eq comparison created in (2) |
| ;; to start succeeding incorrectly, except that we optimize the ref.eq |
| ;; when we see that this allocation flows only into one side of it. |
| (struct.new_default $struct) ;; Expected |
| ;; 4. This allocation is not optimized, so we would end up incorrectly |
| ;; writing this non-null value into the local from (1) if we had not |
| ;; optimized the ref.eq. |
| (struct.new_default $struct) ;; Replacement |
| ) |
| ) |
| (ref.is_null |
| ;; This is replaced with a get of the local from (1), which would |
| ;; incorrectly contain the non-null replacement value if we had not |
| ;; optimized the ref.eq. |
| (struct.get $struct 0 (local.get $local)) |
| ) |
| ) |
| ) |