Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/test/fuzzing.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
# Not fully implemented.
'waitqueue.wast',
'gufa-waitqueue.wast',
'optimize-instructions-waitqueue.wast',
'publish.wast',
'optimize-instructions-publish.wast',
# TODO: fix handling of the non-utf8 names here
Expand Down
18 changes: 16 additions & 2 deletions src/ir/properties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,23 @@ struct GenerativityScanner : public PostWalker<GenerativityScanner> {
void visitArrayNewElem(ArrayNewElem* curr) { generative = true; }
void visitArrayNewFixed(ArrayNewFixed* curr) { generative = true; }
void visitContNew(ContNew* curr) { generative = true; }

// Notifications/waits depend on events on other threads.
void visitAtomicNotify(AtomicNotify* curr) { generative = true; }
void visitWaitqueueNotify(WaitqueueNotify* curr) { generative = true; }
void visitAtomicWait(AtomicWait* curr) { generative = true; }
void visitStructWait(StructWait* curr) { generative = true; }
void visitWaitqueueNew(WaitqueueNew* curr) { generative = true; }
// TODO: waitqueue.notify, struct.wait, atomic.notify, and atomic.wait should
// also be generative.

// Instructions that both read and write memory are generative (as they
// themselves can lead to a different value being returned from identical-
// looking instructions; no other instruction in the middle is needed).
void visitAtomicRMW(AtomicRMW* curr) { generative = true; }
void visitAtomicCmpxchg(AtomicCmpxchg* curr) { generative = true; }
void visitStructRMW(StructRMW* curr) { generative = true; }
void visitStructCmpxchg(StructCmpxchg* curr) { generative = true; }
void visitArrayRMW(ArrayRMW* curr) { generative = true; }
void visitArrayCmpxchg(ArrayCmpxchg* curr) { generative = true; }
};

} // anonymous namespace
Expand Down
148 changes: 148 additions & 0 deletions test/lit/passes/optimize-instructions-atomics.wast
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,152 @@
;; skips
(drop (i64.extend_i32_s (i32.atomic.load (local.get $x))))
)

;; CHECK: (func $select-atomic-rmw (result i32)
;; CHECK-NEXT: (select
;; CHECK-NEXT: (i32.atomic.rmw16.xchg_u
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.atomic.rmw16.xchg_u
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $select-atomic-rmw (result i32)
;; The first rmw here influences the second, and we need to return the result
;; of the first, which means we need a temp local. We avoid adding one and do
;; not optimize here. Other instructions are tested in the functions below.
(select
(i32.atomic.rmw16.xchg_u
(i32.const 0)
(i32.const 1)
)
(i32.atomic.rmw16.xchg_u
(i32.const 0)
(i32.const 1)
)
(i32.const 1)
)
)

;; CHECK: (func $select-atomic-cmpxchg (result i32)
;; CHECK-NEXT: (select
;; CHECK-NEXT: (i32.atomic.rmw.cmpxchg
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.atomic.rmw.cmpxchg
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $select-atomic-cmpxchg (result i32)
(select
(i32.atomic.rmw.cmpxchg
(i32.const 0)
(i32.const 1)
(i32.const 2)
)
(i32.atomic.rmw.cmpxchg
(i32.const 0)
(i32.const 1)
(i32.const 2)
)
(i32.const 1)
)
)

;; CHECK: (func $select-atomic-wait (result i32)
;; CHECK-NEXT: (select
;; CHECK-NEXT: (memory.atomic.wait32
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (i64.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (memory.atomic.wait32
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (i64.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $select-atomic-wait (result i32)
(select
(memory.atomic.wait32
(i32.const 0)
(i32.const 1)
(i64.const 2)
)
(memory.atomic.wait32
(i32.const 0)
(i32.const 1)
(i64.const 2)
)
(i32.const 1)
)
)

;; CHECK: (func $select-atomic-notify (result i32)
;; CHECK-NEXT: (select
;; CHECK-NEXT: (memory.atomic.notify
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (memory.atomic.notify
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $select-atomic-notify (result i32)
(select
(memory.atomic.notify
(i32.const 0)
(i32.const 1)
)
(memory.atomic.notify
(i32.const 0)
(i32.const 1)
)
(i32.const 1)
)
)

;; CHECK: (func $select-atomic-load (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.atomic.load
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.atomic.load
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $select-atomic-load (result i32)
;; For comparison with above, when the instruction is not generative, we can
;; optimize: the two atomic loads must return the same thing, so we drop the
;; first and return the second (even though the first is what the select
;; returns).
(select
(i32.atomic.load
(i32.const 0)
)
(i32.atomic.load
(i32.const 0)
)
(i32.const 1)
)
)
)
132 changes: 130 additions & 2 deletions test/lit/passes/optimize-instructions-gc-atomics.wast
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s

(module
;; CHECK: (type $unshared (struct (field (mut i32))))

;; CHECK: (type $shared (shared (struct (field (mut i32)))))
(type $shared (shared (struct (field (mut i32)))))
;; CHECK: (type $unshared (struct (field (mut i32))))
(type $unshared (struct (field (mut i32))))
;; CHECK: (type $array (shared (array (mut i32))))
(type $array (shared (array (mut i32))))

;; CHECK: (func $get-unordered-unshared (type $2) (result i32)
;; CHECK-NEXT: (struct.get $unshared 0
Expand Down Expand Up @@ -154,4 +155,131 @@
(i32.const 0)
)
)

;; CHECK: (func $select-struct-rmw (type $4) (param $x (ref $shared)) (result i32)
;; CHECK-NEXT: (select
;; CHECK-NEXT: (struct.atomic.rmw.add $shared 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.atomic.rmw.add $shared 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $select-struct-rmw (param $x (ref $shared)) (result i32)
;; The first rmw here influences the second, and we need to return the result
;; of the first, which means we need a temp local. We avoid adding one and do
;; not optimize here. Other instructions are tested in the functions below.
(select
(struct.atomic.rmw.add $shared 0
(local.get $x)
(i32.const 1)
)
(struct.atomic.rmw.add $shared 0
(local.get $x)
(i32.const 1)
)
(i32.const 1)
)
)

;; CHECK: (func $select-struct-cmpxchg (type $4) (param $x (ref $shared)) (result i32)
;; CHECK-NEXT: (select
;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $shared 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.atomic.rmw.cmpxchg $shared 0
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $select-struct-cmpxchg (param $x (ref $shared)) (result i32)
(select
(struct.atomic.rmw.cmpxchg $shared 0
(local.get $x)
(i32.const 1)
(i32.const 2)
)
(struct.atomic.rmw.cmpxchg $shared 0
(local.get $x)
(i32.const 1)
(i32.const 2)
)
(i32.const 1)
)
)

;; CHECK: (func $select-array-rmw (type $6) (param $x (ref $array)) (result i32)
;; CHECK-NEXT: (select
;; CHECK-NEXT: (array.atomic.rmw.add $array
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (array.atomic.rmw.add $array
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $select-array-rmw (param $x (ref $array)) (result i32)
(select
(array.atomic.rmw.add $array
(local.get $x)
(i32.const 0)
(i32.const 1)
)
(array.atomic.rmw.add $array
(local.get $x)
(i32.const 0)
(i32.const 1)
)
(i32.const 1)
)
)

;; CHECK: (func $select-array-cmpxchg (type $6) (param $x (ref $array)) (result i32)
;; CHECK-NEXT: (select
;; CHECK-NEXT: (array.atomic.rmw.cmpxchg $array
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (array.atomic.rmw.cmpxchg $array
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (i32.const 2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $select-array-cmpxchg (param $x (ref $array)) (result i32)
(select
(array.atomic.rmw.cmpxchg $array
(local.get $x)
(i32.const 0)
(i32.const 1)
(i32.const 2)
)
(array.atomic.rmw.cmpxchg $array
(local.get $x)
(i32.const 0)
(i32.const 1)
(i32.const 2)
)
(i32.const 1)
)
)
)
Loading
Loading