diff --git a/scripts/test/relaxed_atomic_execution_tests.py b/scripts/test/acquire_release_atomics_execution_tests.py similarity index 99% rename from scripts/test/relaxed_atomic_execution_tests.py rename to scripts/test/acquire_release_atomics_execution_tests.py index ddc76d3c82f..ca45637c01c 100644 --- a/scripts/test/relaxed_atomic_execution_tests.py +++ b/scripts/test/acquire_release_atomics_execution_tests.py @@ -80,6 +80,8 @@ (func (export "i64.atomic.rmw32.cmpxchg_u") (param $addr i32) (param $expected i64) (param $value i64) (result i64) (i64.atomic.rmw32.cmpxchg_u acqrel (local.get $addr) (local.get $expected) (local.get $value))) (func (export "atomic.fence") (atomic.fence acqrel)) + + (func (export "pause") (pause)) ) ;; *.atomic.load* @@ -395,6 +397,10 @@ (invoke "atomic.fence") +;; pause + +(invoke "pause") + ;; unaligned accesses diff --git a/scripts/test/generate_atomic_spec_test.py b/scripts/test/generate_atomic_spec_test.py index df4d9014123..c4b2571500d 100644 --- a/scripts/test/generate_atomic_spec_test.py +++ b/scripts/test/generate_atomic_spec_test.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from enum import Enum -from relaxed_atomic_execution_tests import acqrel_execution_tests +from acquire_release_atomics_execution_tests import acqrel_execution_tests # Workaround for python <3.10, escape characters can't appear in f-strings. # Although we require 3.10 in some places, the formatter complains without this. diff --git a/scripts/test/support.py b/scripts/test/support.py index c79bf26ae6c..eab86044889 100644 --- a/scripts/test/support.py +++ b/scripts/test/support.py @@ -90,7 +90,7 @@ def to_end(j): ret += [(chunk, [])] elif chunk.startswith('(assert_invalid'): continue - elif chunk.startswith(('(assert', '(invoke', '(register')) and not ignoring_assertions: + elif chunk.startswith(('(assert', '(invoke', '(register', '(thread', '(wait')) and not ignoring_assertions: # ret may be empty if there are some asserts before the first # module. in that case these are asserts *without* a module, which # are valid (they may check something that doesn't refer to a module diff --git a/test/spec/relaxed-atomics.wast b/test/spec/acquire-release-atomics/basic.wast similarity index 99% rename from test/spec/relaxed-atomics.wast rename to test/spec/acquire-release-atomics/basic.wast index 5b444c9964b..a480b91e3ff 100644 --- a/test/spec/relaxed-atomics.wast +++ b/test/spec/acquire-release-atomics/basic.wast @@ -5348,6 +5348,8 @@ (func (export "i64.atomic.rmw32.cmpxchg_u") (param $addr i32) (param $expected i64) (param $value i64) (result i64) (i64.atomic.rmw32.cmpxchg_u acqrel (local.get $addr) (local.get $expected) (local.get $value))) (func (export "atomic.fence") (atomic.fence acqrel)) + + (func (export "pause") (pause)) ) ;; *.atomic.load* @@ -5663,6 +5665,10 @@ (invoke "atomic.fence") +;; pause + +(invoke "pause") + ;; unaligned accesses diff --git a/test/spec/acquire-release-atomics/threaded.wast b/test/spec/acquire-release-atomics/threaded.wast new file mode 100644 index 00000000000..d6f64527f93 --- /dev/null +++ b/test/spec/acquire-release-atomics/threaded.wast @@ -0,0 +1,346 @@ +;; Interleaving stores +(module $Mem + (memory (export "shared") 1 1 shared) +) +(register "mem" $Mem) + +(thread $T1 (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + ;; x =rel 1 + ;; y =rel 2 + (i32.atomic.store acqrel (i32.const 0) (i32.const 1)) + (i32.atomic.store acqrel (i32.const 4) (i32.const 2)) + ) + ) + (invoke "run") +) + +(thread $T2 (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + ;; y =rel 3 + ;; x =rel 4 + (i32.atomic.store acqrel (i32.const 4) (i32.const 3)) + (i32.atomic.store acqrel (i32.const 0) (i32.const 4)) + ) + ) + (invoke "run") +) + +(wait $T1) +(wait $T2) + +(module + (memory (import "mem" "shared") 1 1 shared) + (func (export "check") (result i32 i32) + ;; read x, y + (i32.load (i32.const 0)) + (i32.load (i32.const 4)) + ) +) + +;; Nothing is synchronized so all 4 interleavings are possible. +;; x=1, y=3 is only possible with acqrel, while others are also possible with +;; seqcst. +(assert_return (invoke "check") + (either (i32.const 1) (i32.const 4)) + (either (i32.const 2) (i32.const 3)) +) + +;; Critical section guarding an unordered memory access +(module $Mem + (memory (export "shared") 1 1 shared) +) +(register "mem" $Mem) + +(thread $writer (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + ;; payload =un 42 + (i32.store (i32.const 4) (i32.const 42)) + ;; flag =rel 1 indicating that the payload was written + (i32.atomic.store acqrel (i32.const 0) (i32.const 1)) + ) + ) + (invoke "run") +) + +(thread $reader (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + ;; observed_flag =acq flag + (i32.store (i32.const 8) (i32.atomic.load acqrel (i32.const 0))) + + ;; observed_payload =un payload + (i32.store (i32.const 12) (i32.load (i32.const 4))) + ) + ) + (invoke "run") +) + +(wait $writer) +(wait $reader) + +(module + (memory (import "mem" "shared") 1 1 shared) + (func (export "check") (result i32) + ;; If the flag is set, the payload must be set + ;; If the flag is unset, the payload may or may not be set. + ;; !observed_flag || observed_payload == 42 + (i32.or + (i32.eqz (i32.load (i32.const 8))) + (i32.eq (i32.load (i32.const 12)) (i32.const 42)) + ) + ) +) + +(assert_return (invoke "check") + (i32.const 1) +) + +;; Similar to above, critical section guarding a flag +(module $Mem + (memory (export "shared") 1 1 shared) +) +(register "mem" $Mem) + +(thread $writer (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + ;; payload =un 42 + (i32.store (i32.const 4) (i32.const 42)) + + ;; Release barrier + (atomic.fence acqrel) + + ;; flag indicating that the payload was written. + ;; A relaxed ordering would be sufficient here but there's no such thing + ;; at the moment. + ;; In practice this and the fence together are redundant. + ;; flag =rel 1 + (i32.atomic.store acqrel (i32.const 0) (i32.const 1)) + ) + ) + (invoke "run") +) + +(thread $reader (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + ;; A relaxed ordering would be sufficient here but we don't have it. + ;; In practice this and the fence together are redundant. + ;; observed_flag =acq flag + (i32.store (i32.const 8) (i32.atomic.load acqrel (i32.const 0))) + + ;; Acquire barrier + (atomic.fence acqrel) + + ;; observed_payload =un payload + (i32.store (i32.const 12) (i32.load (i32.const 4))) + ) + ) + (invoke "run") +) + +(wait $writer) +(wait $reader) + +(module + (memory (import "mem" "shared") 1 1 shared) + (func (export "check") (result i32) + ;; If the flag is set, the payload must be set + ;; If the flag is unset, the payload may or may not be set. + ;; !observed_flag || observed_payload == 42 + (i32.or + (i32.eqz (i32.load (i32.const 8))) + (i32.eq (i32.load (i32.const 12)) (i32.const 42)) + ) + ) +) + +(assert_return (invoke "check") + (i32.const 1) +) + +;; Spinlock +(module $Mem + ;; Address 0 - lock + ;; Address 4 - payload + (memory (export "shared") 1 1 shared) +) +(register "mem" $Mem) + +;; Add 1 to the counter atomically +(thread $addOne (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + + (func $lock + (loop $spin + ;; Try to swap 0 with 1 at the lock address 0 + (if (i32.eqz (i32.atomic.rmw.cmpxchg acqrel (i32.const 0) (i32.const 0) (i32.const 1))) + (then (return)) + ) + (pause) + (br $spin) + ) + ) + + (func $unlock + ;; lock =rel 0 + (i32.atomic.store acqrel (i32.const 0) (i32.const 0)) + ) + + (func (export "run") + (call $lock) + + ;; payload +=un 1 + (i32.store (i32.const 4) + (i32.add (i32.load (i32.const 4)) (i32.const 1)) + ) + + (call $unlock) + ) + ) + (invoke "run") +) + +;; Add 10 to the counter atomically +(thread $addTen (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + + (func $lock + (loop $spin + ;; Try to swap 0 with 1 at the lock address 0 + (if (i32.eqz (i32.atomic.rmw.cmpxchg acqrel (i32.const 0) (i32.const 0) (i32.const 1))) + (then (return)) + ) + (pause) + (br $spin) + ) + ) + + (func $unlock + ;; lock =rel 0 + (i32.atomic.store acqrel (i32.const 0) (i32.const 0)) + ) + + (func (export "run") + (call $lock) + + ;; payload +=un 10 + (i32.store (i32.const 4) + (i32.add (i32.load (i32.const 4)) (i32.const 10)) + ) + + (call $unlock) + ) + ) + (invoke "run") +) + +(wait $addOne) +(wait $addTen) + +(module + (memory (import "mem" "shared") 1 1 shared) + (func (export "check") (result i32) (result i32) + ;; read payload, lock + (i32.load (i32.const 4)) + (i32.load (i32.const 0)) + ) +) + +;; $addTen added 10 and $addOne added 1 atomically. +;; The lock was left unlocked at the end. +(assert_return (invoke "check") + (i32.const 11) + (i32.const 0) +) + +;; independent reads of independent writes +(module $Mem + (memory (export "shared") 1 1 shared) +) +(register "mem" $Mem) + +(thread $writerX (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + ;; x =rel 1 + (i32.atomic.store acqrel (i32.const 0) (i32.const 1)) + ) + ) + (invoke "run") +) + +(thread $writerY (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + ;; y =rel 1 + (i32.atomic.store acqrel (i32.const 4) (i32.const 1)) + ) + ) + (invoke "run") +) + +(thread $reader1 (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + ;; x1 =acq x + ;; y1 =acq y + (i32.store (i32.const 8) (i32.atomic.load acqrel (i32.const 0))) + (i32.store (i32.const 12) (i32.atomic.load acqrel (i32.const 4))) + ) + ) + (invoke "run") +) + +(thread $reader2 (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + ;; y2 =acq y + ;; x2 =acq x + (i32.store (i32.const 20) (i32.atomic.load acqrel (i32.const 4))) + (i32.store (i32.const 16) (i32.atomic.load acqrel (i32.const 0))) + ) + ) + (invoke "run") +) + +(wait $writerX) +(wait $writerY) +(wait $reader1) +(wait $reader2) + +(module + (memory (import "mem" "shared") 1 1 shared) + (func (export "check") (result i32 i32 i32 i32) + ;; read x1, y1, x2, y2 + (i32.load (i32.const 8)) + (i32.load (i32.const 12)) + (i32.load (i32.const 16)) + (i32.load (i32.const 20)) + ) +) + +;; All 4 combinations are possible +;; Under seqcst, x1=1, y1=0, x2=0, y2=1 isn't possible. +(assert_return (invoke "check") + (either (i32.const 0) (i32.const 1)) + (either (i32.const 0) (i32.const 1)) + (either (i32.const 0) (i32.const 1)) + (either (i32.const 0) (i32.const 1)) +)