From b9ded3f0789f37ce3a56cb187d911529d412f316 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 30 Jul 2026 00:38:03 +0000 Subject: [PATCH 1/5] Tests for acquire/release atomics --- .../test/relaxed_atomic_execution_tests.py | 6 + scripts/test/support.py | 2 +- .../basic.wast} | 6 + .../acquire-release-atomics/threaded.wast | 344 ++++++++++++++++++ 4 files changed, 357 insertions(+), 1 deletion(-) rename test/spec/{relaxed-atomics.wast => acquire-release-atomics/basic.wast} (99%) create mode 100644 test/spec/acquire-release-atomics/threaded.wast diff --git a/scripts/test/relaxed_atomic_execution_tests.py b/scripts/test/relaxed_atomic_execution_tests.py index ddc76d3c82f..ca45637c01c 100644 --- a/scripts/test/relaxed_atomic_execution_tests.py +++ b/scripts/test/relaxed_atomic_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/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..1ceb437db73 --- /dev/null +++ b/test/spec/acquire-release-atomics/threaded.wast @@ -0,0 +1,344 @@ +;; 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") + (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") + (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) + (i32.load (i32.const 0)) + (i32.load (i32.const 4)) + ) +) + +;; Nothing is synchronized so all 4 interleavings are possible. +;; 1, 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) + (global (export "flag") (mut i32) (i32.const 0)) + (global (export "payload") (mut i32) (i32.const 0)) +) +(register "mem" $Mem) + +(thread $writer (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + ;; payload + (i32.store (i32.const 4) (i32.const 42)) + ;; flag 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) + (global $flag (import "mem" "flag") (mut i32)) + (global $payload (import "mem" "payload") (mut i32)) + (func (export "run") + (global.set $flag (i32.atomic.load acqrel (i32.const 0))) + (global.set $payload (i32.load (i32.const 4))) + ) + ) + (invoke "run") +) + +(wait $writer) +(wait $reader) + +(module + (global $flag (import "mem" "flag") (mut i32)) + (global $payload (import "mem" "payload") (mut i32)) + (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. + (i32.or + (i32.eqz (global.get $flag)) + (i32.eq (global.get $payload) (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) + (global (export "flag") (mut i32) (i32.const 0)) + (global (export "payload") (mut i32) (i32.const 0)) +) +(register "mem" $Mem) + +(thread $writer (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + ;; payload + (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. + (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) + (global $flag (import "mem" "flag") (mut i32)) + (global $payload (import "mem" "payload") (mut i32)) + (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. + (global.set $flag (i32.atomic.load acqrel (i32.const 0))) + + ;; Acquire barrier + (atomic.fence acqrel) + + (global.set $payload (i32.load (i32.const 4))) + ) + ) + (invoke "run") +) + +(wait $writer) +(wait $reader) + +(module + (global $flag (import "mem" "flag") (mut i32)) + (global $payload (import "mem" "payload") (mut i32)) + (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. + (i32.or + (i32.eqz (global.get $flag)) + (i32.eq (global.get $payload) (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 + (if (i32.eqz (i32.atomic.rmw.cmpxchg acqrel (i32.const 0) (i32.const 0) (i32.const 1))) + (then (return)) + ) + (pause) + (br $spin) + ) + ) + + (func $unlock + (i32.atomic.store acqrel (i32.const 0) (i32.const 0)) + ) + + (func (export "run") + (call $lock) + + (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 + (if (i32.eqz (i32.atomic.rmw.cmpxchg acqrel (i32.const 0) (i32.const 0) (i32.const 1))) + (then (return)) + ) + (pause) + (br $spin) + ) + ) + + (func $unlock + (i32.atomic.store acqrel (i32.const 0) (i32.const 0)) + ) + + (func (export "run") + (call $lock) + + (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) + (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) + (global (export "x1") (mut i32) (i32.const 0)) + (global (export "y1") (mut i32) (i32.const 0)) + (global (export "x2") (mut i32) (i32.const 0)) + (global (export "y2") (mut i32) (i32.const 0)) +) +(register "mem" $Mem) + +;; Set x = 1 +(thread $writerX (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (i32.atomic.store acqrel (i32.const 0) (i32.const 1)) + ) + ) + (invoke "run") +) + +;; Set y = 1 +(thread $writerY (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (i32.atomic.store acqrel (i32.const 4) (i32.const 1)) + ) + ) + (invoke "run") +) + +;; Read x, then y +(thread $reader1 (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (global $x1 (import "mem" "x1") (mut i32)) + (global $y1 (import "mem" "y1") (mut i32)) + (func (export "run") + (global.set $x1 (i32.atomic.load acqrel (i32.const 0))) + (global.set $y1 (i32.atomic.load acqrel (i32.const 4))) + ) + ) + (invoke "run") +) + +;; Read y, then x +(thread $reader2 (shared (module $Mem)) + (module + (memory (import "mem" "shared") 1 1 shared) + (global $x2 (import "mem" "x2") (mut i32)) + (global $y2 (import "mem" "y2") (mut i32)) + (func (export "run") + (global.set $y2 (i32.atomic.load acqrel (i32.const 4))) + (global.set $x2 (i32.atomic.load acqrel (i32.const 0))) + ) + ) + (invoke "run") +) + +(wait $writerX) +(wait $writerY) +(wait $reader1) +(wait $reader2) + +(module + (global $x1 (import "mem" "x1") (mut i32)) + (global $y1 (import "mem" "y1") (mut i32)) + (global $x2 (import "mem" "x2") (mut i32)) + (global $y2 (import "mem" "y2") (mut i32)) + (func (export "check") (result i32 i32 i32 i32) + (global.get $x1) + (global.get $y1) + (global.get $y2) + (global.get $x2) + ) +) + +;; 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)) +) From 186fde7b79f215404cacff5e49ba2569c45da7c4 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 13 Aug 2026 17:29:28 +0000 Subject: [PATCH 2/5] Rename test script --- ...tion_tests.py => acquire_release_atomics_execution_tests.py} | 0 scripts/test/generate_atomic_spec_test.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename scripts/test/{relaxed_atomic_execution_tests.py => acquire_release_atomics_execution_tests.py} (100%) diff --git a/scripts/test/relaxed_atomic_execution_tests.py b/scripts/test/acquire_release_atomics_execution_tests.py similarity index 100% rename from scripts/test/relaxed_atomic_execution_tests.py rename to scripts/test/acquire_release_atomics_execution_tests.py 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. From 0df3bff0b07f509e8fca0a6d809613961d6d6790 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 13 Aug 2026 17:29:28 +0000 Subject: [PATCH 3/5] Write to memory instead of using shared globals, which aren't supported --- .../acquire-release-atomics/threaded.wast | 64 +++++++------------ 1 file changed, 24 insertions(+), 40 deletions(-) diff --git a/test/spec/acquire-release-atomics/threaded.wast b/test/spec/acquire-release-atomics/threaded.wast index 1ceb437db73..73acc3d0cbe 100644 --- a/test/spec/acquire-release-atomics/threaded.wast +++ b/test/spec/acquire-release-atomics/threaded.wast @@ -48,8 +48,6 @@ ;; Critical section guarding an unordered memory access (module $Mem (memory (export "shared") 1 1 shared) - (global (export "flag") (mut i32) (i32.const 0)) - (global (export "payload") (mut i32) (i32.const 0)) ) (register "mem" $Mem) @@ -69,11 +67,10 @@ (thread $reader (shared (module $Mem)) (module (memory (import "mem" "shared") 1 1 shared) - (global $flag (import "mem" "flag") (mut i32)) - (global $payload (import "mem" "payload") (mut i32)) (func (export "run") - (global.set $flag (i32.atomic.load acqrel (i32.const 0))) - (global.set $payload (i32.load (i32.const 4))) + ;; Store observed flag at address 8, observed payload at address 12 + (i32.store (i32.const 8) (i32.atomic.load acqrel (i32.const 0))) + (i32.store (i32.const 12) (i32.load (i32.const 4))) ) ) (invoke "run") @@ -83,14 +80,13 @@ (wait $reader) (module - (global $flag (import "mem" "flag") (mut i32)) - (global $payload (import "mem" "payload") (mut i32)) + (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. (i32.or - (i32.eqz (global.get $flag)) - (i32.eq (global.get $payload) (i32.const 42)) + (i32.eqz (i32.load (i32.const 8))) + (i32.eq (i32.load (i32.const 12)) (i32.const 42)) ) ) ) @@ -102,8 +98,6 @@ ;; Similar to above, critical section guarding a flag (module $Mem (memory (export "shared") 1 1 shared) - (global (export "flag") (mut i32) (i32.const 0)) - (global (export "payload") (mut i32) (i32.const 0)) ) (register "mem" $Mem) @@ -130,17 +124,17 @@ (thread $reader (shared (module $Mem)) (module (memory (import "mem" "shared") 1 1 shared) - (global $flag (import "mem" "flag") (mut i32)) - (global $payload (import "mem" "payload") (mut i32)) (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. - (global.set $flag (i32.atomic.load acqrel (i32.const 0))) + ;; Observed flag - address 8 + (i32.store (i32.const 8) (i32.atomic.load acqrel (i32.const 0))) ;; Acquire barrier (atomic.fence acqrel) - (global.set $payload (i32.load (i32.const 4))) + ;; Observed payload - address 12 + (i32.store (i32.const 12) (i32.load (i32.const 4))) ) ) (invoke "run") @@ -150,14 +144,13 @@ (wait $reader) (module - (global $flag (import "mem" "flag") (mut i32)) - (global $payload (import "mem" "payload") (mut i32)) + (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. (i32.or - (i32.eqz (global.get $flag)) - (i32.eq (global.get $payload) (i32.const 42)) + (i32.eqz (i32.load (i32.const 8))) + (i32.eq (i32.load (i32.const 12)) (i32.const 42)) ) ) ) @@ -259,10 +252,6 @@ ;; independent reads of independent writes (module $Mem (memory (export "shared") 1 1 shared) - (global (export "x1") (mut i32) (i32.const 0)) - (global (export "y1") (mut i32) (i32.const 0)) - (global (export "x2") (mut i32) (i32.const 0)) - (global (export "y2") (mut i32) (i32.const 0)) ) (register "mem" $Mem) @@ -289,28 +278,26 @@ ) ;; Read x, then y +;; Store observed x1 in 8, observed y1 in 12 (thread $reader1 (shared (module $Mem)) (module (memory (import "mem" "shared") 1 1 shared) - (global $x1 (import "mem" "x1") (mut i32)) - (global $y1 (import "mem" "y1") (mut i32)) (func (export "run") - (global.set $x1 (i32.atomic.load acqrel (i32.const 0))) - (global.set $y1 (i32.atomic.load acqrel (i32.const 4))) + (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") ) ;; Read y, then x +;; Store observed x2 in 16, observed y2 in 20 (thread $reader2 (shared (module $Mem)) (module (memory (import "mem" "shared") 1 1 shared) - (global $x2 (import "mem" "x2") (mut i32)) - (global $y2 (import "mem" "y2") (mut i32)) (func (export "run") - (global.set $y2 (i32.atomic.load acqrel (i32.const 4))) - (global.set $x2 (i32.atomic.load acqrel (i32.const 0))) + (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") @@ -322,15 +309,12 @@ (wait $reader2) (module - (global $x1 (import "mem" "x1") (mut i32)) - (global $y1 (import "mem" "y1") (mut i32)) - (global $x2 (import "mem" "x2") (mut i32)) - (global $y2 (import "mem" "y2") (mut i32)) + (memory (import "mem" "shared") 1 1 shared) (func (export "check") (result i32 i32 i32 i32) - (global.get $x1) - (global.get $y1) - (global.get $y2) - (global.get $x2) + (i32.load (i32.const 8)) + (i32.load (i32.const 12)) + (i32.load (i32.const 16)) + (i32.load (i32.const 20)) ) ) From 430ad10832f8e58df5be06624b439db48a8d2c16 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Fri, 14 Aug 2026 22:01:51 +0000 Subject: [PATCH 4/5] Add some comments --- .../acquire-release-atomics/threaded.wast | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/test/spec/acquire-release-atomics/threaded.wast b/test/spec/acquire-release-atomics/threaded.wast index 73acc3d0cbe..adc8bad9318 100644 --- a/test/spec/acquire-release-atomics/threaded.wast +++ b/test/spec/acquire-release-atomics/threaded.wast @@ -8,6 +8,8 @@ (module (memory (import "mem" "shared") 1 1 shared) (func (export "run") + ;; a =rel 1 + ;; b =rel 2 (i32.atomic.store acqrel (i32.const 0) (i32.const 1)) (i32.atomic.store acqrel (i32.const 4) (i32.const 2)) ) @@ -19,6 +21,8 @@ (module (memory (import "mem" "shared") 1 1 shared) (func (export "run") + ;; b =rel 3 + ;; a =rel 4 (i32.atomic.store acqrel (i32.const 4) (i32.const 3)) (i32.atomic.store acqrel (i32.const 0) (i32.const 4)) ) @@ -32,13 +36,14 @@ (module (memory (import "mem" "shared") 1 1 shared) (func (export "check") (result i32 i32) + ;; read a, b (i32.load (i32.const 0)) (i32.load (i32.const 4)) ) ) ;; Nothing is synchronized so all 4 interleavings are possible. -;; 1, 3 is only possible with acqrel, while others are also possible with +;; a=1, b=3 is only possible with acqrel, while others are also possible with ;; seqcst. (assert_return (invoke "check") (either (i32.const 1) (i32.const 4)) @@ -55,9 +60,9 @@ (module (memory (import "mem" "shared") 1 1 shared) (func (export "run") - ;; payload + ;; payload =un 42 (i32.store (i32.const 4) (i32.const 42)) - ;; flag indicating that the payload was written + ;; flag =rel 1 indicating that the payload was written (i32.atomic.store acqrel (i32.const 0) (i32.const 1)) ) ) @@ -68,8 +73,10 @@ (module (memory (import "mem" "shared") 1 1 shared) (func (export "run") - ;; Store observed flag at address 8, observed payload at address 12 + ;; 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))) ) ) @@ -84,6 +91,7 @@ (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)) @@ -105,7 +113,7 @@ (module (memory (import "mem" "shared") 1 1 shared) (func (export "run") - ;; payload + ;; payload =un 42 (i32.store (i32.const 4) (i32.const 42)) ;; Release barrier @@ -115,6 +123,7 @@ ;; 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)) ) ) @@ -127,13 +136,13 @@ (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 - address 8 + ;; observed_flag =acq flag (i32.store (i32.const 8) (i32.atomic.load acqrel (i32.const 0))) ;; Acquire barrier (atomic.fence acqrel) - ;; Observed payload - address 12 + ;; observed_payload =un payload (i32.store (i32.const 12) (i32.load (i32.const 4))) ) ) @@ -148,6 +157,7 @@ (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)) @@ -174,6 +184,7 @@ (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)) ) @@ -183,12 +194,14 @@ ) (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)) ) @@ -206,6 +219,7 @@ (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)) ) @@ -215,12 +229,14 @@ ) (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)) ) @@ -237,6 +253,7 @@ (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)) ) @@ -255,34 +272,34 @@ ) (register "mem" $Mem) -;; Set x = 1 (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") ) -;; Set y = 1 (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") ) -;; Read x, then y -;; Store observed x1 in 8, observed y1 in 12 (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))) ) @@ -290,12 +307,12 @@ (invoke "run") ) -;; Read y, then x -;; Store observed x2 in 16, observed y2 in 20 (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))) ) @@ -311,6 +328,7 @@ (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)) From fcbdd97b20b84d8328705229cd9ff7a23932b1b5 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Sat, 15 Aug 2026 03:47:51 +0000 Subject: [PATCH 5/5] Change some variable names in comments --- test/spec/acquire-release-atomics/threaded.wast | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/spec/acquire-release-atomics/threaded.wast b/test/spec/acquire-release-atomics/threaded.wast index adc8bad9318..d6f64527f93 100644 --- a/test/spec/acquire-release-atomics/threaded.wast +++ b/test/spec/acquire-release-atomics/threaded.wast @@ -8,8 +8,8 @@ (module (memory (import "mem" "shared") 1 1 shared) (func (export "run") - ;; a =rel 1 - ;; b =rel 2 + ;; 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)) ) @@ -21,8 +21,8 @@ (module (memory (import "mem" "shared") 1 1 shared) (func (export "run") - ;; b =rel 3 - ;; a =rel 4 + ;; 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)) ) @@ -36,14 +36,14 @@ (module (memory (import "mem" "shared") 1 1 shared) (func (export "check") (result i32 i32) - ;; read a, b + ;; read x, y (i32.load (i32.const 0)) (i32.load (i32.const 4)) ) ) ;; Nothing is synchronized so all 4 interleavings are possible. -;; a=1, b=3 is only possible with acqrel, while others are also possible with +;; 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))