diff --git a/scripts/test/fuzzing.py b/scripts/test/fuzzing.py index 0a4fa6f7d76..a091689f7e2 100644 --- a/scripts/test/fuzzing.py +++ b/scripts/test/fuzzing.py @@ -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 diff --git a/src/ir/properties.cpp b/src/ir/properties.cpp index 535f50dace4..a3dc9c45dc9 100644 --- a/src/ir/properties.cpp +++ b/src/ir/properties.cpp @@ -47,9 +47,23 @@ struct GenerativityScanner : public PostWalker { 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 diff --git a/test/lit/passes/optimize-instructions-atomics.wast b/test/lit/passes/optimize-instructions-atomics.wast index e59c6a4e79b..3086fe26896 100644 --- a/test/lit/passes/optimize-instructions-atomics.wast +++ b/test/lit/passes/optimize-instructions-atomics.wast @@ -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) + ) + ) ) diff --git a/test/lit/passes/optimize-instructions-gc-atomics.wast b/test/lit/passes/optimize-instructions-gc-atomics.wast index a0283390c9a..c9d878010bd 100644 --- a/test/lit/passes/optimize-instructions-gc-atomics.wast +++ b/test/lit/passes/optimize-instructions-gc-atomics.wast @@ -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 @@ -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) + ) + ) ) diff --git a/test/lit/passes/optimize-instructions-waitqueue.wast b/test/lit/passes/optimize-instructions-waitqueue.wast new file mode 100644 index 00000000000..8abe1860daa --- /dev/null +++ b/test/lit/passes/optimize-instructions-waitqueue.wast @@ -0,0 +1,70 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --optimize-instructions -S -o - | filecheck %s + +(module + ;; CHECK: (type $shared (shared (struct (field (mut i32))))) + (type $shared (shared (struct (field (mut i32))))) + + ;; CHECK: (func $select-struct-wait (type $1) (param $x (ref $shared)) (param $wq (ref (shared waitqueue))) (result i32) + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (struct.wait $shared 0 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $wq) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.wait $shared 0 + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (local.get $wq) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $select-struct-wait (param $x (ref $shared)) (param $wq (ref (shared waitqueue))) (result i32) + (select + (struct.wait $shared 0 + (local.get $x) + (local.get $wq) + (i32.const 0) + (i64.const -1) + ) + (struct.wait $shared 0 + (local.get $x) + (local.get $wq) + (i32.const 0) + (i64.const -1) + ) + (i32.const 1) + ) + ) + + ;; CHECK: (func $select-waitqueue-notify (type $2) (param $wq (ref (shared waitqueue))) (result i32) + ;; CHECK-NEXT: (select + ;; CHECK-NEXT: (waitqueue.notify + ;; CHECK-NEXT: (local.get $wq) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (waitqueue.notify + ;; CHECK-NEXT: (local.get $wq) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $select-waitqueue-notify (param $wq (ref (shared waitqueue))) (result i32) + (select + (waitqueue.notify + (local.get $wq) + (i32.const 1) + ) + (waitqueue.notify + (local.get $wq) + (i32.const 1) + ) + (i32.const 1) + ) + ) +)