blob: 07fc1eadac1079ff9398d8088ce40f0081fff147 [file] [edit]
;; 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))
)
)
)