From 6d075a8a1644a1a2b7f25dfea3c5ec5433bea292 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 3 Mar 2026 00:02:03 +0000 Subject: [PATCH 1/2] Parsing for thread blocks --- check.py | 5 +- scripts/test/shared.py | 9 + src/parser/wast-parser.cpp | 69 ++ src/parser/wat-parser.h | 24 +- src/tools/wasm-shell.cpp | 14 + test/spec/threads/LB.wast | 63 ++ test/spec/threads/LB_atomic.wast | 65 ++ test/spec/threads/MP.wast | 60 ++ test/spec/threads/MP_atomic.wast | 62 ++ test/spec/threads/SB.wast | 63 ++ test/spec/threads/SB_atomic.wast | 65 ++ test/spec/threads/atomic.wast | 1018 ++++++++++++++++++++++++++ test/spec/threads/deeply_nested.wast | 93 +++ test/spec/threads/nested.wast | 54 ++ test/spec/threads/simple.wast | 29 + test/spec/threads/thread.wast | 48 ++ test/spec/threads/unlinkable.wast | 21 + test/spec/threads/wait_notify.wast | 41 ++ 18 files changed, 1798 insertions(+), 5 deletions(-) create mode 100644 test/spec/threads/LB.wast create mode 100644 test/spec/threads/LB_atomic.wast create mode 100644 test/spec/threads/MP.wast create mode 100644 test/spec/threads/MP_atomic.wast create mode 100644 test/spec/threads/SB.wast create mode 100644 test/spec/threads/SB_atomic.wast create mode 100644 test/spec/threads/atomic.wast create mode 100644 test/spec/threads/deeply_nested.wast create mode 100644 test/spec/threads/nested.wast create mode 100644 test/spec/threads/simple.wast create mode 100644 test/spec/threads/thread.wast create mode 100644 test/spec/threads/unlinkable.wast create mode 100644 test/spec/threads/wait_notify.wast diff --git a/check.py b/check.py index dfd67743274..0618c59c5e6 100755 --- a/check.py +++ b/check.py @@ -200,7 +200,10 @@ def check_expected(actual, expected, stdout=None): UNSPLITTABLE_TESTS = [Path(x) for x in [ "spec/testsuite/instance.wast", - "spec/instance.wast"]] + "spec/instance.wast", + + # TODO: support module splitting for (thread ...) blocks + "spec/threads/*"]] def is_splittable(wast: Path): diff --git a/scripts/test/shared.py b/scripts/test/shared.py index 48f10b5a52b..18f3bc58727 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -394,6 +394,15 @@ def get_tests(test_dir, extensions=[], recursive=False): # Test invalid 'elem.wast', + + # Requires wast `either` support + 'threads/thread.wast', + + # Requires better support for multi-threaded tests + 'threads/wait_notify.wast', + + # Non-natural alignment is invalid for atomic operations + 'threads/atomic.wast', ] SPEC_TESTSUITE_PROPOSALS_TO_SKIP = [ 'custom-page-sizes', diff --git a/src/parser/wast-parser.cpp b/src/parser/wast-parser.cpp index 721b6126948..f28ea4468f2 100644 --- a/src/parser/wast-parser.cpp +++ b/src/parser/wast-parser.cpp @@ -25,6 +25,8 @@ using namespace std::string_view_literals; namespace { +Result command(Lexer& in); + Result const_(Lexer& in) { if (in.takeSExprStart("ref.extern"sv)) { auto n = in.takeI32(); @@ -496,12 +498,79 @@ MaybeResult instantiation(Lexer& in) { return ModuleInstantiation{moduleId, instanceId}; } +// (thread name (shared (module name))? command*) +MaybeResult thread_(Lexer& in) { + if (!in.takeSExprStart("thread"sv)) { + return {}; + } + + auto name = in.takeID(); + if (!name) { + return in.err("expected thread name"); + } + + std::optional sharedModule; + if (in.takeSExprStart("shared"sv)) { + if (!in.takeSExprStart("module"sv)) { + return in.err("expected module keyword in (shared ...) block"); + } + + auto modName = in.takeID(); + if (!modName) { + return in.err("expected module name after (shared (module ...))"); + } + sharedModule = *modName; + + if (!in.takeRParen()) { + return in.err("expected end of shared module"); + } + if (!in.takeRParen()) { + return in.err("expected end of (shared ...) expression"); + } + } + + std::vector commands; + while (!in.peekRParen() && !in.empty()) { + size_t line = in.position().line; + auto cmd = command(in); + CHECK_ERR(cmd); + commands.push_back({std::move(*cmd), line}); + } + if (!in.takeRParen()) { + return in.err("expected end of thread"); + } + return ThreadBlock{*name, sharedModule, std::move(commands)}; +} + +// (wait name) +MaybeResult wait_(Lexer& in) { + if (!in.takeSExprStart("wait"sv)) { + return {}; + } + auto name = in.takeID(); + if (!name) { + return in.err("expected thread name in wait"); + } + if (!in.takeRParen()) { + return in.err("expected end of wait"); + } + return Wait{*name}; +} + // instantiate | module | register | action | assertion Result command(Lexer& in) { if (auto cmd = register_(in)) { CHECK_ERR(cmd); return *cmd; } + if (auto cmd = thread_(in)) { + CHECK_ERR(cmd); + return *cmd; + } + if (auto cmd = wait_(in)) { + CHECK_ERR(cmd); + return *cmd; + } if (auto cmd = maybeAction(in)) { CHECK_ERR(cmd); return *cmd; diff --git a/src/parser/wat-parser.h b/src/parser/wat-parser.h index ba1236ad9a5..8190db4e100 100644 --- a/src/parser/wat-parser.h +++ b/src/parser/wat-parser.h @@ -128,16 +128,32 @@ struct ModuleInstantiation { std::optional instanceName; }; -using WASTCommand = - std::variant; +struct ScriptEntry; +using WASTScript = std::vector; + +struct ThreadBlock { + Name name; + std::optional sharedModule; + WASTScript commands; +}; + +struct Wait { + Name thread; +}; + +using WASTCommand = std::variant; struct ScriptEntry { WASTCommand cmd; size_t line; }; -using WASTScript = std::vector; - Result parseScript(std::string_view in); } // namespace wasm::WATParser diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp index c07108937c0..8a87d40c17c 100644 --- a/src/tools/wasm-shell.cpp +++ b/src/tools/wasm-shell.cpp @@ -93,11 +93,25 @@ struct Shell { } else if (auto* instantiateModule = std::get_if(&cmd)) { return doInstantiate(*instantiateModule); + } else if (auto* thread = std::get_if(&cmd)) { + return doThread(*thread); + } else if (auto* wait = std::get_if(&cmd)) { + return doWait(*wait); } else { WASM_UNREACHABLE("unexpected command"); } } + // Run threads in a blocking manner for now. + // TODO: yield on blocking instructions e.g. memory.atomic.wait32. + Result<> doThread(ThreadBlock& thread) { + return run(thread.commands); + } + + Result<> doWait(Wait& wait) { + return Ok{}; + } + Result> makeModule(WASTModule& mod) { std::shared_ptr wasm; if (auto* quoted = std::get_if(&mod.module)) { diff --git a/test/spec/threads/LB.wast b/test/spec/threads/LB.wast new file mode 100644 index 00000000000..29b2a82a574 --- /dev/null +++ b/test/spec/threads/LB.wast @@ -0,0 +1,63 @@ +(module $Mem + (memory (export "shared") 1 1 shared) +) +(register "mem") + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (local i32) + (i32.load (i32.const 4)) + (local.set 0) + (i32.store (i32.const 0) (i32.const 1)) + + ;; store results for checking + (i32.store (i32.const 24) (local.get 0)) + ) + ) + (invoke "run") +) + +(thread $T2 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (local i32) + (i32.load (i32.const 0)) + (local.set 0) + (i32.store (i32.const 4) (i32.const 1)) + + ;; store results for checking + (i32.store (i32.const 32) (local.get 0)) + ) + ) + + (invoke "run") +) + +(wait $T1) +(wait $T2) + +(module $Check + (memory (import "mem" "shared") 1 1 shared) + + (func (export "check") (result i32) + (local i32 i32) + (i32.load (i32.const 24)) + (local.set 0) + (i32.load (i32.const 32)) + (local.set 1) + + ;; allowed results: (L_0 = 0 || L_0 = 1) && (L_1 = 0 || L_1 = 1) + + (i32.or (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 0) (i32.const 0))) + (i32.or (i32.eq (local.get 1) (i32.const 1)) (i32.eq (local.get 1) (i32.const 0))) + (i32.and) + (return) + ) +) + +(assert_return (invoke $Check "check") (i32.const 1)) diff --git a/test/spec/threads/LB_atomic.wast b/test/spec/threads/LB_atomic.wast new file mode 100644 index 00000000000..8b7ee4ff273 --- /dev/null +++ b/test/spec/threads/LB_atomic.wast @@ -0,0 +1,65 @@ +(module $Mem + (memory (export "shared") 1 1 shared) +) +(register "mem") + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (local i32) + (i32.atomic.load (i32.const 4)) + (local.set 0) + (i32.atomic.store (i32.const 0) (i32.const 1)) + + ;; store results for checking + (i32.store (i32.const 24) (local.get 0)) + ) + ) + (invoke "run") +) + +(thread $T2 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (local i32) + (i32.atomic.load (i32.const 0)) + (local.set 0) + (i32.atomic.store (i32.const 4) (i32.const 1)) + + ;; store results for checking + (i32.store (i32.const 32) (local.get 0)) + ) + ) + + (invoke "run") +) + +(wait $T1) +(wait $T2) + +(module $Check + (memory (import "mem" "shared") 1 1 shared) + + (func (export "check") (result i32) + (local i32 i32) + (i32.load (i32.const 24)) + (local.set 0) + (i32.load (i32.const 32)) + (local.set 1) + + ;; allowed results: (L_0 = 0 && L_1 = 0) || (L_0 = 0 && L_1 = 1) || (L_0 = 1 && L_1 = 0) + + (i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 0))) + (i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 1))) + (i32.and (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 1) (i32.const 0))) + (i32.or) + (i32.or) + (return) + ) +) + +(assert_return (invoke $Check "check") (i32.const 1)) diff --git a/test/spec/threads/MP.wast b/test/spec/threads/MP.wast new file mode 100644 index 00000000000..a054d273a41 --- /dev/null +++ b/test/spec/threads/MP.wast @@ -0,0 +1,60 @@ +(module $Mem + (memory (export "shared") 1 1 shared) +) +(register "mem") + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (i32.store (i32.const 0) (i32.const 42)) + (i32.store (i32.const 4) (i32.const 1)) + ) + ) + (invoke "run") +) + +(thread $T2 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (local i32 i32) + (i32.load (i32.const 4)) + (local.set 0) + (i32.load (i32.const 0)) + (local.set 1) + + ;; store results for checking + (i32.store (i32.const 24) (local.get 0)) + (i32.store (i32.const 32) (local.get 1)) + ) + ) + + (invoke "run") +) + +(wait $T1) +(wait $T2) + +(module $Check + (memory (import "mem" "shared") 1 1 shared) + + (func (export "check") (result i32) + (local i32 i32) + (i32.load (i32.const 24)) + (local.set 0) + (i32.load (i32.const 32)) + (local.set 1) + + ;; allowed results: (L_0 = 0 || L_0 = 1) && (L_1 = 0 || L_1 = 42) + + (i32.or (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 0) (i32.const 0))) + (i32.or (i32.eq (local.get 1) (i32.const 42)) (i32.eq (local.get 1) (i32.const 0))) + (i32.and) + (return) + ) +) + +(assert_return (invoke $Check "check") (i32.const 1)) diff --git a/test/spec/threads/MP_atomic.wast b/test/spec/threads/MP_atomic.wast new file mode 100644 index 00000000000..931ae88f2b1 --- /dev/null +++ b/test/spec/threads/MP_atomic.wast @@ -0,0 +1,62 @@ +(module $Mem + (memory (export "shared") 1 1 shared) +) +(register "mem") + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (i32.atomic.store (i32.const 0) (i32.const 42)) + (i32.atomic.store (i32.const 4) (i32.const 1)) + ) + ) + (invoke "run") +) + +(thread $T2 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (local i32 i32) + (i32.atomic.load (i32.const 4)) + (local.set 0) + (i32.atomic.load (i32.const 0)) + (local.set 1) + + ;; store results for checking + (i32.store (i32.const 24) (local.get 0)) + (i32.store (i32.const 32) (local.get 1)) + ) + ) + + (invoke "run") +) + +(wait $T1) +(wait $T2) + +(module $Check + (memory (import "mem" "shared") 1 1 shared) + + (func (export "check") (result i32) + (local i32 i32) + (i32.load (i32.const 24)) + (local.set 0) + (i32.load (i32.const 32)) + (local.set 1) + + ;; allowed results: (L_0 = 1 && L_1 = 42) || (L_0 = 0 && L_1 = 0) || (L_0 = 0 && L_1 = 42) + + (i32.and (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 1) (i32.const 42))) + (i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 0))) + (i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 42))) + (i32.or) + (i32.or) + (return) + ) +) + +(assert_return (invoke $Check "check") (i32.const 1)) diff --git a/test/spec/threads/SB.wast b/test/spec/threads/SB.wast new file mode 100644 index 00000000000..61c629c33a2 --- /dev/null +++ b/test/spec/threads/SB.wast @@ -0,0 +1,63 @@ +(module $Mem + (memory (export "shared") 1 1 shared) +) +(register "mem") + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (local i32) + (i32.store (i32.const 0) (i32.const 1)) + (i32.load (i32.const 4)) + (local.set 0) + + ;; store results for checking + (i32.store (i32.const 24) (local.get 0)) + ) + ) + (invoke "run") +) + +(thread $T2 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (local i32) + (i32.store (i32.const 4) (i32.const 1)) + (i32.load (i32.const 0)) + (local.set 0) + + ;; store results for checking + (i32.store (i32.const 32) (local.get 0)) + ) + ) + + (invoke "run") +) + +(wait $T1) +(wait $T2) + +(module $Check + (memory (import "mem" "shared") 1 1 shared) + + (func (export "check") (result i32) + (local i32 i32) + (i32.load (i32.const 24)) + (local.set 0) + (i32.load (i32.const 32)) + (local.set 1) + + ;; allowed results: (L_0 = 0 || L_0 = 1) && (L_1 = 0 || L_1 = 1) + + (i32.or (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 0) (i32.const 0))) + (i32.or (i32.eq (local.get 1) (i32.const 1)) (i32.eq (local.get 1) (i32.const 0))) + (i32.and) + (return) + ) +) + +(assert_return (invoke $Check "check") (i32.const 1)) diff --git a/test/spec/threads/SB_atomic.wast b/test/spec/threads/SB_atomic.wast new file mode 100644 index 00000000000..cc5c131d131 --- /dev/null +++ b/test/spec/threads/SB_atomic.wast @@ -0,0 +1,65 @@ +(module $Mem + (memory (export "shared") 1 1 shared) +) +(register "mem") + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (local i32) + (i32.atomic.store (i32.const 0) (i32.const 1)) + (i32.atomic.load (i32.const 4)) + (local.set 0) + + ;; store results for checking + (i32.store (i32.const 24) (local.get 0)) + ) + ) + (invoke "run") +) + +(thread $T2 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (local i32) + (i32.atomic.store (i32.const 4) (i32.const 1)) + (i32.atomic.load (i32.const 0)) + (local.set 0) + + ;; store results for checking + (i32.store (i32.const 32) (local.get 0)) + ) + ) + + (invoke "run") +) + +(wait $T1) +(wait $T2) + +(module $Check + (memory (import "mem" "shared") 1 1 shared) + + (func (export "check") (result i32) + (local i32 i32) + (i32.load (i32.const 24)) + (local.set 0) + (i32.load (i32.const 32)) + (local.set 1) + + ;; allowed results: (L_0 = 1 && L_1 = 1) || (L_0 = 0 && L_1 = 1) || (L_0 = 1 && L_1 = 0) + + (i32.and (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 1) (i32.const 1))) + (i32.and (i32.eq (local.get 0) (i32.const 0)) (i32.eq (local.get 1) (i32.const 1))) + (i32.and (i32.eq (local.get 0) (i32.const 1)) (i32.eq (local.get 1) (i32.const 0))) + (i32.or) + (i32.or) + (return) + ) +) + +(assert_return (invoke $Check "check") (i32.const 1)) diff --git a/test/spec/threads/atomic.wast b/test/spec/threads/atomic.wast new file mode 100644 index 00000000000..7c340f9884c --- /dev/null +++ b/test/spec/threads/atomic.wast @@ -0,0 +1,1018 @@ +;; atomic operations + +(module + (memory 1 1 shared) + + (func (export "init") (param $value i64) (i64.store (i32.const 0) (local.get $value))) + + (func (export "i32.atomic.load") (param $addr i32) (result i32) (i32.atomic.load (local.get $addr))) + (func (export "i64.atomic.load") (param $addr i32) (result i64) (i64.atomic.load (local.get $addr))) + (func (export "i32.atomic.load8_u") (param $addr i32) (result i32) (i32.atomic.load8_u (local.get $addr))) + (func (export "i32.atomic.load16_u") (param $addr i32) (result i32) (i32.atomic.load16_u (local.get $addr))) + (func (export "i64.atomic.load8_u") (param $addr i32) (result i64) (i64.atomic.load8_u (local.get $addr))) + (func (export "i64.atomic.load16_u") (param $addr i32) (result i64) (i64.atomic.load16_u (local.get $addr))) + (func (export "i64.atomic.load32_u") (param $addr i32) (result i64) (i64.atomic.load32_u (local.get $addr))) + + (func (export "i32.atomic.store") (param $addr i32) (param $value i32) (i32.atomic.store (local.get $addr) (local.get $value))) + (func (export "i64.atomic.store") (param $addr i32) (param $value i64) (i64.atomic.store (local.get $addr) (local.get $value))) + (func (export "i32.atomic.store8") (param $addr i32) (param $value i32) (i32.atomic.store8 (local.get $addr) (local.get $value))) + (func (export "i32.atomic.store16") (param $addr i32) (param $value i32) (i32.atomic.store16 (local.get $addr) (local.get $value))) + (func (export "i64.atomic.store8") (param $addr i32) (param $value i64) (i64.atomic.store8 (local.get $addr) (local.get $value))) + (func (export "i64.atomic.store16") (param $addr i32) (param $value i64) (i64.atomic.store16 (local.get $addr) (local.get $value))) + (func (export "i64.atomic.store32") (param $addr i32) (param $value i64) (i64.atomic.store32 (local.get $addr) (local.get $value))) + + (func (export "i32.atomic.rmw.add") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw.add (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw.add") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw.add (local.get $addr) (local.get $value))) + (func (export "i32.atomic.rmw8.add_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw8.add_u (local.get $addr) (local.get $value))) + (func (export "i32.atomic.rmw16.add_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw16.add_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw8.add_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw8.add_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw16.add_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw16.add_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw32.add_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw32.add_u (local.get $addr) (local.get $value))) + + (func (export "i32.atomic.rmw.sub") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw.sub (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw.sub") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw.sub (local.get $addr) (local.get $value))) + (func (export "i32.atomic.rmw8.sub_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw8.sub_u (local.get $addr) (local.get $value))) + (func (export "i32.atomic.rmw16.sub_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw16.sub_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw8.sub_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw8.sub_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw16.sub_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw16.sub_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw32.sub_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw32.sub_u (local.get $addr) (local.get $value))) + + (func (export "i32.atomic.rmw.and") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw.and (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw.and") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw.and (local.get $addr) (local.get $value))) + (func (export "i32.atomic.rmw8.and_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw8.and_u (local.get $addr) (local.get $value))) + (func (export "i32.atomic.rmw16.and_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw16.and_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw8.and_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw8.and_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw16.and_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw16.and_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw32.and_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw32.and_u (local.get $addr) (local.get $value))) + + (func (export "i32.atomic.rmw.or") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw.or (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw.or") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw.or (local.get $addr) (local.get $value))) + (func (export "i32.atomic.rmw8.or_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw8.or_u (local.get $addr) (local.get $value))) + (func (export "i32.atomic.rmw16.or_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw16.or_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw8.or_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw8.or_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw16.or_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw16.or_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw32.or_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw32.or_u (local.get $addr) (local.get $value))) + + (func (export "i32.atomic.rmw.xor") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw.xor (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw.xor") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw.xor (local.get $addr) (local.get $value))) + (func (export "i32.atomic.rmw8.xor_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw8.xor_u (local.get $addr) (local.get $value))) + (func (export "i32.atomic.rmw16.xor_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw16.xor_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw8.xor_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw8.xor_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw16.xor_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw16.xor_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw32.xor_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw32.xor_u (local.get $addr) (local.get $value))) + + (func (export "i32.atomic.rmw.xchg") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw.xchg (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw.xchg") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw.xchg (local.get $addr) (local.get $value))) + (func (export "i32.atomic.rmw8.xchg_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw8.xchg_u (local.get $addr) (local.get $value))) + (func (export "i32.atomic.rmw16.xchg_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw16.xchg_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw8.xchg_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw8.xchg_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw16.xchg_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw16.xchg_u (local.get $addr) (local.get $value))) + (func (export "i64.atomic.rmw32.xchg_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw32.xchg_u (local.get $addr) (local.get $value))) + + (func (export "i32.atomic.rmw.cmpxchg") (param $addr i32) (param $expected i32) (param $value i32) (result i32) (i32.atomic.rmw.cmpxchg (local.get $addr) (local.get $expected) (local.get $value))) + (func (export "i64.atomic.rmw.cmpxchg") (param $addr i32) (param $expected i64) (param $value i64) (result i64) (i64.atomic.rmw.cmpxchg (local.get $addr) (local.get $expected) (local.get $value))) + (func (export "i32.atomic.rmw8.cmpxchg_u") (param $addr i32) (param $expected i32) (param $value i32) (result i32) (i32.atomic.rmw8.cmpxchg_u (local.get $addr) (local.get $expected) (local.get $value))) + (func (export "i32.atomic.rmw16.cmpxchg_u") (param $addr i32) (param $expected i32) (param $value i32) (result i32) (i32.atomic.rmw16.cmpxchg_u (local.get $addr) (local.get $expected) (local.get $value))) + (func (export "i64.atomic.rmw8.cmpxchg_u") (param $addr i32) (param $expected i64) (param $value i64) (result i64) (i64.atomic.rmw8.cmpxchg_u (local.get $addr) (local.get $expected) (local.get $value))) + (func (export "i64.atomic.rmw16.cmpxchg_u") (param $addr i32) (param $expected i64) (param $value i64) (result i64) (i64.atomic.rmw16.cmpxchg_u (local.get $addr) (local.get $expected) (local.get $value))) + (func (export "i64.atomic.rmw32.cmpxchg_u") (param $addr i32) (param $expected i64) (param $value i64) (result i64) (i64.atomic.rmw32.cmpxchg_u (local.get $addr) (local.get $expected) (local.get $value))) + +) + +;; *.atomic.load* + +(invoke "init" (i64.const 0x0706050403020100)) + +(assert_return (invoke "i32.atomic.load" (i32.const 0)) (i32.const 0x03020100)) +(assert_return (invoke "i32.atomic.load" (i32.const 4)) (i32.const 0x07060504)) + +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x0706050403020100)) + +(assert_return (invoke "i32.atomic.load8_u" (i32.const 0)) (i32.const 0x00)) +(assert_return (invoke "i32.atomic.load8_u" (i32.const 5)) (i32.const 0x05)) + +(assert_return (invoke "i32.atomic.load16_u" (i32.const 0)) (i32.const 0x0100)) +(assert_return (invoke "i32.atomic.load16_u" (i32.const 6)) (i32.const 0x0706)) + +(assert_return (invoke "i64.atomic.load8_u" (i32.const 0)) (i64.const 0x00)) +(assert_return (invoke "i64.atomic.load8_u" (i32.const 5)) (i64.const 0x05)) + +(assert_return (invoke "i64.atomic.load16_u" (i32.const 0)) (i64.const 0x0100)) +(assert_return (invoke "i64.atomic.load16_u" (i32.const 6)) (i64.const 0x0706)) + +(assert_return (invoke "i64.atomic.load32_u" (i32.const 0)) (i64.const 0x03020100)) +(assert_return (invoke "i64.atomic.load32_u" (i32.const 4)) (i64.const 0x07060504)) + +;; *.atomic.store* + +(invoke "init" (i64.const 0x0000000000000000)) + +(assert_return (invoke "i32.atomic.store" (i32.const 0) (i32.const 0xffeeddcc))) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x00000000ffeeddcc)) + +(assert_return (invoke "i64.atomic.store" (i32.const 0) (i64.const 0x0123456789abcdef))) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x0123456789abcdef)) + +(assert_return (invoke "i32.atomic.store8" (i32.const 1) (i32.const 0x42))) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x0123456789ab42ef)) + +(assert_return (invoke "i32.atomic.store16" (i32.const 4) (i32.const 0x8844))) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x0123884489ab42ef)) + +(assert_return (invoke "i64.atomic.store8" (i32.const 1) (i64.const 0x99))) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x0123884489ab99ef)) + +(assert_return (invoke "i64.atomic.store16" (i32.const 4) (i64.const 0xcafe))) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x0123cafe89ab99ef)) + +(assert_return (invoke "i64.atomic.store32" (i32.const 4) (i64.const 0xdeadbeef))) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0xdeadbeef89ab99ef)) + +;; *.atomic.rmw*.add + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw.add" (i32.const 0) (i32.const 0x12345678)) (i32.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111123456789)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw.add" (i32.const 0) (i64.const 0x0101010102020202)) (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1212121213131313)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw8.add_u" (i32.const 0) (i32.const 0xcdcdcdcd)) (i32.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111111111de)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw16.add_u" (i32.const 0) (i32.const 0xcafecafe)) (i32.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x111111111111dc0f)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw8.add_u" (i32.const 0) (i64.const 0x4242424242424242)) (i64.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111153)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw16.add_u" (i32.const 0) (i64.const 0xbeefbeefbeefbeef)) (i64.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x111111111111d000)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw32.add_u" (i32.const 0) (i64.const 0xcabba6e5cabba6e5)) (i64.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111dbccb7f6)) + +;; *.atomic.rmw*.sub + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw.sub" (i32.const 0) (i32.const 0x12345678)) (i32.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111fedcba99)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw.sub" (i32.const 0) (i64.const 0x0101010102020202)) (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x101010100f0f0f0f)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw8.sub_u" (i32.const 0) (i32.const 0xcdcdcdcd)) (i32.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111144)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw16.sub_u" (i32.const 0) (i32.const 0xcafecafe)) (i32.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111114613)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw8.sub_u" (i32.const 0) (i64.const 0x4242424242424242)) (i64.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111111111cf)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw16.sub_u" (i32.const 0) (i64.const 0xbeefbeefbeefbeef)) (i64.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111115222)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw32.sub_u" (i32.const 0) (i64.const 0xcabba6e5cabba6e5)) (i64.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111146556a2c)) + +;; *.atomic.rmw*.and + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw.and" (i32.const 0) (i32.const 0x12345678)) (i32.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111110101010)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw.and" (i32.const 0) (i64.const 0x0101010102020202)) (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x0101010100000000)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw8.and_u" (i32.const 0) (i32.const 0xcdcdcdcd)) (i32.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111101)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw16.and_u" (i32.const 0) (i32.const 0xcafecafe)) (i32.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111110010)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw8.and_u" (i32.const 0) (i64.const 0x4242424242424242)) (i64.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111100)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw16.and_u" (i32.const 0) (i64.const 0xbeefbeefbeefbeef)) (i64.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111001)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw32.and_u" (i32.const 0) (i64.const 0xcabba6e5cabba6e5)) (i64.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111100110001)) + +;; *.atomic.rmw*.or + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw.or" (i32.const 0) (i32.const 0x12345678)) (i32.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111113355779)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw.or" (i32.const 0) (i64.const 0x0101010102020202)) (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111113131313)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw8.or_u" (i32.const 0) (i32.const 0xcdcdcdcd)) (i32.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111111111dd)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw16.or_u" (i32.const 0) (i32.const 0xcafecafe)) (i32.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x111111111111dbff)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw8.or_u" (i32.const 0) (i64.const 0x4242424242424242)) (i64.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111153)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw16.or_u" (i32.const 0) (i64.const 0xbeefbeefbeefbeef)) (i64.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x111111111111bfff)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw32.or_u" (i32.const 0) (i64.const 0xcabba6e5cabba6e5)) (i64.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111dbbbb7f5)) + +;; *.atomic.rmw*.xor + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw.xor" (i32.const 0) (i32.const 0x12345678)) (i32.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111103254769)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw.xor" (i32.const 0) (i64.const 0x0101010102020202)) (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1010101013131313)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw8.xor_u" (i32.const 0) (i32.const 0xcdcdcdcd)) (i32.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111111111dc)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw16.xor_u" (i32.const 0) (i32.const 0xcafecafe)) (i32.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x111111111111dbef)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw8.xor_u" (i32.const 0) (i64.const 0x4242424242424242)) (i64.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111153)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw16.xor_u" (i32.const 0) (i64.const 0xbeefbeefbeefbeef)) (i64.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x111111111111affe)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw32.xor_u" (i32.const 0) (i64.const 0xcabba6e5cabba6e5)) (i64.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111dbaab7f4)) + +;; *.atomic.rmw*.xchg + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw.xchg" (i32.const 0) (i32.const 0x12345678)) (i32.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111112345678)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw.xchg" (i32.const 0) (i64.const 0x0101010102020202)) (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x0101010102020202)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw8.xchg_u" (i32.const 0) (i32.const 0xcdcdcdcd)) (i32.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111111111cd)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw16.xchg_u" (i32.const 0) (i32.const 0xcafecafe)) (i32.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x111111111111cafe)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw8.xchg_u" (i32.const 0) (i64.const 0x4242424242424242)) (i64.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111142)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw16.xchg_u" (i32.const 0) (i64.const 0xbeefbeefbeefbeef)) (i64.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x111111111111beef)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw32.xchg_u" (i32.const 0) (i64.const 0xcabba6e5cabba6e5)) (i64.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111cabba6e5)) + +;; *.atomic.rmw*.cmpxchg (compare false) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw.cmpxchg" (i32.const 0) (i32.const 0) (i32.const 0x12345678)) (i32.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111111)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw.cmpxchg" (i32.const 0) (i64.const 0) (i64.const 0x0101010102020202)) (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111111)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw8.cmpxchg_u" (i32.const 0) (i32.const 0) (i32.const 0xcdcdcdcd)) (i32.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111111)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw8.cmpxchg_u" (i32.const 0) (i32.const 0x11111111) (i32.const 0xcdcdcdcd)) (i32.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111111111cd)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw16.cmpxchg_u" (i32.const 0) (i32.const 0) (i32.const 0xcafecafe)) (i32.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111111)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw16.cmpxchg_u" (i32.const 0) (i32.const 0x11111111) (i32.const 0xcafecafe)) (i32.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x111111111111cafe)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw8.cmpxchg_u" (i32.const 0) (i64.const 0) (i64.const 0x4242424242424242)) (i64.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111111)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw8.cmpxchg_u" (i32.const 0) (i64.const 0x1111111111111111) (i64.const 0x4242424242424242)) (i64.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111142)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw16.cmpxchg_u" (i32.const 0) (i64.const 0) (i64.const 0xbeefbeefbeefbeef)) (i64.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111111)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw16.cmpxchg_u" (i32.const 0) (i64.const 0x1111111111111111) (i64.const 0xbeefbeefbeefbeef)) (i64.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x111111111111beef)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw32.cmpxchg_u" (i32.const 0) (i64.const 0) (i64.const 0xcabba6e5cabba6e5)) (i64.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111111)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw32.cmpxchg_u" (i32.const 0) (i64.const 0x1111111111111111) (i64.const 0xcabba6e5cabba6e5)) (i64.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111cabba6e5)) + +;; *.atomic.rmw*.cmpxchg (compare true) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw.cmpxchg" (i32.const 0) (i32.const 0x11111111) (i32.const 0x12345678)) (i32.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111112345678)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw.cmpxchg" (i32.const 0) (i64.const 0x1111111111111111) (i64.const 0x0101010102020202)) (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x0101010102020202)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw8.cmpxchg_u" (i32.const 0) (i32.const 0x11) (i32.const 0xcdcdcdcd)) (i32.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111111111cd)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i32.atomic.rmw16.cmpxchg_u" (i32.const 0) (i32.const 0x1111) (i32.const 0xcafecafe)) (i32.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x111111111111cafe)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw8.cmpxchg_u" (i32.const 0) (i64.const 0x11) (i64.const 0x4242424242424242)) (i64.const 0x11)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x1111111111111142)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw16.cmpxchg_u" (i32.const 0) (i64.const 0x1111) (i64.const 0xbeefbeefbeefbeef)) (i64.const 0x1111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x111111111111beef)) + +(invoke "init" (i64.const 0x1111111111111111)) +(assert_return (invoke "i64.atomic.rmw32.cmpxchg_u" (i32.const 0) (i64.const 0x11111111) (i64.const 0xcabba6e5cabba6e5)) (i64.const 0x11111111)) +(assert_return (invoke "i64.atomic.load" (i32.const 0)) (i64.const 0x11111111cabba6e5)) + + +;; unaligned accesses + +(assert_trap (invoke "i32.atomic.load" (i32.const 1)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.load" (i32.const 1)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.load16_u" (i32.const 1)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.load16_u" (i32.const 1)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.load32_u" (i32.const 1)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.store" (i32.const 1) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.store" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.store16" (i32.const 1) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.store16" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.store32" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.rmw.add" (i32.const 1) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw.add" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.rmw16.add_u" (i32.const 1) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw16.add_u" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw32.add_u" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.rmw.sub" (i32.const 1) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw.sub" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.rmw16.sub_u" (i32.const 1) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw16.sub_u" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw32.sub_u" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.rmw.and" (i32.const 1) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw.and" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.rmw16.and_u" (i32.const 1) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw16.and_u" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw32.and_u" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.rmw.or" (i32.const 1) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw.or" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.rmw16.or_u" (i32.const 1) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw16.or_u" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw32.or_u" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.rmw.xor" (i32.const 1) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw.xor" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.rmw16.xor_u" (i32.const 1) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw16.xor_u" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw32.xor_u" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.rmw.xchg" (i32.const 1) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw.xchg" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.rmw16.xchg_u" (i32.const 1) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw16.xchg_u" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw32.xchg_u" (i32.const 1) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.rmw.cmpxchg" (i32.const 1) (i32.const 0) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw.cmpxchg" (i32.const 1) (i64.const 0) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i32.atomic.rmw16.cmpxchg_u" (i32.const 1) (i32.const 0) (i32.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw16.cmpxchg_u" (i32.const 1) (i64.const 0) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "i64.atomic.rmw32.cmpxchg_u" (i32.const 1) (i64.const 0) (i64.const 0)) "unaligned atomic") + +;; non-natural alignment + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.load") (param $addr i32) (result i32) (i32.atomic.load align=1 (local.get $addr))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.load") (param $addr i32) (result i64) (i64.atomic.load align=1 (local.get $addr))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.load16_u") (param $addr i32) (result i32) (i32.atomic.load16_u align=1 (local.get $addr))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.load16_u") (param $addr i32) (result i64) (i64.atomic.load16_u align=1 (local.get $addr))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.load32_u") (param $addr i32) (result i64) (i64.atomic.load32_u align=1 (local.get $addr))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.store") (param $addr i32) (param $value i32) (i32.atomic.store align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.store") (param $addr i32) (param $value i64) (i64.atomic.store align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.store16") (param $addr i32) (param $value i32) (i32.atomic.store16 align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.store16") (param $addr i32) (param $value i64) (i64.atomic.store16 align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.store32") (param $addr i32) (param $value i64) (i64.atomic.store32 align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.rmw.add") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw.add align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw.add") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw.add align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.rmw16.add_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw16.add_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw16.add_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw16.add_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw32.add_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw32.add_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.rmw.sub") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw.sub align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw.sub") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw.sub align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.rmw16.sub_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw16.sub_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw16.sub_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw16.sub_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw32.sub_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw32.sub_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.rmw.and") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw.and align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw.and") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw.and align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.rmw16.and_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw16.and_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw16.and_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw16.and_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw32.and_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw32.and_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.rmw.or") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw.or align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw.or") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw.or align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.rmw16.or_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw16.or_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw16.or_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw16.or_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw32.or_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw32.or_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.rmw.xor") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw.xor align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw.xor") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw.xor align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.rmw16.xor_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw16.xor_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw16.xor_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw16.xor_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw32.xor_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw32.xor_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.rmw.xchg") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw.xchg align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw.xchg") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw.xchg align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.rmw16.xchg_u") (param $addr i32) (param $value i32) (result i32) (i32.atomic.rmw16.xchg_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw16.xchg_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw16.xchg_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw32.xchg_u") (param $addr i32) (param $value i64) (result i64) (i64.atomic.rmw32.xchg_u align=1 (local.get $addr) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.rmw.cmpxchg") (param $addr i32) (param $expected i32) (param $value i32) (result i32) (i32.atomic.rmw.cmpxchg align=1 (local.get $addr) (local.get $expected) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw.cmpxchg") (param $addr i32) (param $expected i64) (param $value i64) (result i64) (i64.atomic.rmw.cmpxchg align=1 (local.get $addr) (local.get $expected) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i32.atomic.rmw16.cmpxchg_u") (param $addr i32) (param $expected i32) (param $value i32) (result i32) (i32.atomic.rmw16.cmpxchg_u align=1 (local.get $addr) (local.get $expected) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw16.cmpxchg_u") (param $addr i32) (param $expected i64) (param $value i64) (result i64) (i64.atomic.rmw16.cmpxchg_u align=1 (local.get $addr) (local.get $expected) (local.get $value))) + ) + "atomic alignment must be natural" +) + +(assert_invalid + (module + (memory 1 1 shared) + + (func (export "i64.atomic.rmw32.cmpxchg_u") (param $addr i32) (param $expected i64) (param $value i64) (result i64) (i64.atomic.rmw32.cmpxchg_u align=1 (local.get $addr) (local.get $expected) (local.get $value))) + ) + "atomic alignment must be natural" +) + + +;; wait/notify +(module + (memory 1 1 shared) + + (func (export "init") (param $value i64) (i64.store (i32.const 0) (local.get $value))) + + (func (export "memory.atomic.notify") (param $addr i32) (param $count i32) (result i32) + (memory.atomic.notify (local.get 0) (local.get 1))) + (func (export "memory.atomic.wait32") (param $addr i32) (param $expected i32) (param $timeout i64) (result i32) + (memory.atomic.wait32 (local.get 0) (local.get 1) (local.get 2))) + (func (export "memory.atomic.wait64") (param $addr i32) (param $expected i64) (param $timeout i64) (result i32) + (memory.atomic.wait64 (local.get 0) (local.get 1) (local.get 2))) +) + +(invoke "init" (i64.const 0xffffffffffff)) + +;; wait returns immediately if values do not match +(assert_return (invoke "memory.atomic.wait32" (i32.const 0) (i32.const 0) (i64.const 0)) (i32.const 1)) +(assert_return (invoke "memory.atomic.wait64" (i32.const 0) (i64.const 0) (i64.const 0)) (i32.const 1)) + +;; notify always returns +(assert_return (invoke "memory.atomic.notify" (i32.const 0) (i32.const 0)) (i32.const 0)) + +;; OOB wait and notify always trap +(assert_trap (invoke "memory.atomic.wait32" (i32.const 65536) (i32.const 0) (i64.const 0)) "out of bounds memory access") +(assert_trap (invoke "memory.atomic.wait64" (i32.const 65536) (i64.const 0) (i64.const 0)) "out of bounds memory access") + +;; in particular, notify always traps even if waking 0 threads +(assert_trap (invoke "memory.atomic.notify" (i32.const 65536) (i32.const 0)) "out of bounds memory access") + +;; similarly, unaligned wait and notify always trap +(assert_trap (invoke "memory.atomic.wait32" (i32.const 65531) (i32.const 0) (i64.const 0)) "unaligned atomic") +(assert_trap (invoke "memory.atomic.wait64" (i32.const 65524) (i64.const 0) (i64.const 0)) "unaligned atomic") + +(assert_trap (invoke "memory.atomic.notify" (i32.const 65531) (i32.const 0)) "unaligned atomic") + +;; atomic.wait traps on unshared memory even if it wouldn't block +(module + (memory 1 1) + + (func (export "init") (param $value i64) (i64.store (i32.const 0) (local.get $value))) + + (func (export "memory.atomic.notify") (param $addr i32) (param $count i32) (result i32) + (memory.atomic.notify (local.get 0) (local.get 1))) + (func (export "memory.atomic.wait32") (param $addr i32) (param $expected i32) (param $timeout i64) (result i32) + (memory.atomic.wait32 (local.get 0) (local.get 1) (local.get 2))) + (func (export "memory.atomic.wait64") (param $addr i32) (param $expected i64) (param $timeout i64) (result i32) + (memory.atomic.wait64 (local.get 0) (local.get 1) (local.get 2))) +) + +(invoke "init" (i64.const 0xffffffffffff)) + +(assert_trap (invoke "memory.atomic.wait32" (i32.const 0) (i32.const 0) (i64.const 0)) "expected shared memory") +(assert_trap (invoke "memory.atomic.wait64" (i32.const 0) (i64.const 0) (i64.const 0)) "expected shared memory") + +;; notify still works +(assert_return (invoke "memory.atomic.notify" (i32.const 0) (i32.const 0)) (i32.const 0)) + +;; OOB and unaligned notify still trap +(assert_trap (invoke "memory.atomic.notify" (i32.const 65536) (i32.const 0)) "out of bounds memory access") +(assert_trap (invoke "memory.atomic.notify" (i32.const 65531) (i32.const 0)) "unaligned atomic") + +;; unshared memory is OK +(module + (memory 1 1) + (func (drop (memory.atomic.notify (i32.const 0) (i32.const 0)))) + (func (drop (memory.atomic.wait32 (i32.const 0) (i32.const 0) (i64.const 0)))) + (func (drop (memory.atomic.wait64 (i32.const 0) (i64.const 0) (i64.const 0)))) + (func (drop (i32.atomic.load (i32.const 0)))) + (func (drop (i64.atomic.load (i32.const 0)))) + (func (drop (i32.atomic.load16_u (i32.const 0)))) + (func (drop (i64.atomic.load16_u (i32.const 0)))) + (func (drop (i64.atomic.load32_u (i32.const 0)))) + (func (i32.atomic.store (i32.const 0) (i32.const 0))) + (func (i64.atomic.store (i32.const 0) (i64.const 0))) + (func (i32.atomic.store16 (i32.const 0) (i32.const 0))) + (func (i64.atomic.store16 (i32.const 0) (i64.const 0))) + (func (i64.atomic.store32 (i32.const 0) (i64.const 0))) + (func (drop (i32.atomic.rmw.add (i32.const 0) (i32.const 0)))) + (func (drop (i64.atomic.rmw.add (i32.const 0) (i64.const 0)))) + (func (drop (i32.atomic.rmw16.add_u (i32.const 0) (i32.const 0)))) + (func (drop (i64.atomic.rmw16.add_u (i32.const 0) (i64.const 0)))) + (func (drop (i64.atomic.rmw32.add_u (i32.const 0) (i64.const 0)))) + (func (drop (i32.atomic.rmw.sub (i32.const 0) (i32.const 0)))) + (func (drop (i64.atomic.rmw.sub (i32.const 0) (i64.const 0)))) + (func (drop (i32.atomic.rmw16.sub_u (i32.const 0) (i32.const 0)))) + (func (drop (i64.atomic.rmw16.sub_u (i32.const 0) (i64.const 0)))) + (func (drop (i64.atomic.rmw32.sub_u (i32.const 0) (i64.const 0)))) + (func (drop (i32.atomic.rmw.and (i32.const 0) (i32.const 0)))) + (func (drop (i64.atomic.rmw.and (i32.const 0) (i64.const 0)))) + (func (drop (i32.atomic.rmw16.and_u (i32.const 0) (i32.const 0)))) + (func (drop (i64.atomic.rmw16.and_u (i32.const 0) (i64.const 0)))) + (func (drop (i64.atomic.rmw32.and_u (i32.const 0) (i64.const 0)))) + (func (drop (i32.atomic.rmw.or (i32.const 0) (i32.const 0)))) + (func (drop (i64.atomic.rmw.or (i32.const 0) (i64.const 0)))) + (func (drop (i32.atomic.rmw16.or_u (i32.const 0) (i32.const 0)))) + (func (drop (i64.atomic.rmw16.or_u (i32.const 0) (i64.const 0)))) + (func (drop (i64.atomic.rmw32.or_u (i32.const 0) (i64.const 0)))) + (func (drop (i32.atomic.rmw.xor (i32.const 0) (i32.const 0)))) + (func (drop (i64.atomic.rmw.xor (i32.const 0) (i64.const 0)))) + (func (drop (i32.atomic.rmw16.xor_u (i32.const 0) (i32.const 0)))) + (func (drop (i64.atomic.rmw16.xor_u (i32.const 0) (i64.const 0)))) + (func (drop (i64.atomic.rmw32.xor_u (i32.const 0) (i64.const 0)))) + (func (drop (i32.atomic.rmw.xchg (i32.const 0) (i32.const 0)))) + (func (drop (i64.atomic.rmw.xchg (i32.const 0) (i64.const 0)))) + (func (drop (i32.atomic.rmw16.xchg_u (i32.const 0) (i32.const 0)))) + (func (drop (i64.atomic.rmw16.xchg_u (i32.const 0) (i64.const 0)))) + (func (drop (i64.atomic.rmw32.xchg_u (i32.const 0) (i64.const 0)))) + (func (drop (i32.atomic.rmw.cmpxchg (i32.const 0) (i32.const 0) (i32.const 0)))) + (func (drop (i64.atomic.rmw.cmpxchg (i32.const 0) (i64.const 0) (i64.const 0)))) + (func (drop (i32.atomic.rmw16.cmpxchg_u (i32.const 0) (i32.const 0) (i32.const 0)))) + (func (drop (i64.atomic.rmw16.cmpxchg_u (i32.const 0) (i64.const 0) (i64.const 0)))) + (func (drop (i64.atomic.rmw32.cmpxchg_u (i32.const 0) (i64.const 0) (i64.const 0)))) +) + +;; atomic.fence: no memory is ok +(module + (func (export "fence") (atomic.fence)) +) + +(assert_return (invoke "fence")) + +;; Fails with no memory +(assert_invalid (module (func (drop (memory.atomic.notify (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (memory.atomic.wait32 (i32.const 0) (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (memory.atomic.wait64 (i32.const 0) (i64.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.load (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.load (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.load16_u (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.load16_u (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.load32_u (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (i32.atomic.store (i32.const 0) (i32.const 0)))) "unknown memory") +(assert_invalid (module (func (i64.atomic.store (i32.const 0) (i64.const 0)))) "unknown memory") +(assert_invalid (module (func (i32.atomic.store16 (i32.const 0) (i32.const 0)))) "unknown memory") +(assert_invalid (module (func (i64.atomic.store16 (i32.const 0) (i64.const 0)))) "unknown memory") +(assert_invalid (module (func (i64.atomic.store32 (i32.const 0) (i64.const 0)))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.rmw.add (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw.add (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.rmw16.add_u (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw16.add_u (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw32.add_u (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.rmw.sub (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw.sub (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.rmw16.sub_u (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw16.sub_u (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw32.sub_u (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.rmw.and (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw.and (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.rmw16.and_u (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw16.and_u (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw32.and_u (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.rmw.or (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw.or (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.rmw16.or_u (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw16.or_u (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw32.or_u (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.rmw.xor (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw.xor (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.rmw16.xor_u (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw16.xor_u (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw32.xor_u (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.rmw.xchg (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw.xchg (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.rmw16.xchg_u (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw16.xchg_u (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw32.xchg_u (i32.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.rmw.cmpxchg (i32.const 0) (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw.cmpxchg (i32.const 0) (i64.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i32.atomic.rmw16.cmpxchg_u (i32.const 0) (i32.const 0) (i32.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw16.cmpxchg_u (i32.const 0) (i64.const 0) (i64.const 0))))) "unknown memory") +(assert_invalid (module (func (drop (i64.atomic.rmw32.cmpxchg_u (i32.const 0) (i64.const 0) (i64.const 0))))) "unknown memory") diff --git a/test/spec/threads/deeply_nested.wast b/test/spec/threads/deeply_nested.wast new file mode 100644 index 00000000000..3a5d4038b75 --- /dev/null +++ b/test/spec/threads/deeply_nested.wast @@ -0,0 +1,93 @@ +(module $Mem + (memory (export "shared") 1 1 shared) +) + +(register "mem" $Mem) + + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 10 shared) + (func (export "run") + (local i32) + (i32.store (i32.const 0) (i32.const 1)) + (i32.load (i32.const 4)) + (local.set 0) + + (i32.store (i32.const 24) (local.get 0)) + ) + ) + + (thread $T11 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run_inner") + (i32.store (i32.const 24) (i32.const 42)) + ) + ) + (invoke "run_inner") + ) + + (thread $T12 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run_inner") + (i32.store (i32.const 32) (i32.const 43)) + ) + ) + + (thread $T121 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run_innermost") + (i32.store (i32.const 24) (i32.const 44)) + ) + ) + (invoke "run_innermost") + ) + (thread $T122 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run_innermost") + (i32.store (i32.const 32) (i32.const 45)) + ) + ) + (invoke "run_innermost") + ) + + (wait $T121) + (wait $T122) + + (invoke "run_inner") + ) + + + (wait $T11) + (wait $T12) + (invoke "run") +) + +(thread $T2 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (local i32) + (i32.store (i32.const 4) (i32.const 1)) + (i32.load (i32.const 0)) + (local.set 0) + + (i32.store (i32.const 32) (local.get 0)) + ) + ) + + (invoke "run") +) + +(wait $T1) +(wait $T2) diff --git a/test/spec/threads/nested.wast b/test/spec/threads/nested.wast new file mode 100644 index 00000000000..29de6bfa0d8 --- /dev/null +++ b/test/spec/threads/nested.wast @@ -0,0 +1,54 @@ +(module $Mem + (memory (export "shared") 1 1 shared) +) + +(register "mem" $Mem) + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (local i32) + (i32.store (i32.const 0) (i32.load (i32.const 20))) + (i32.load (i32.const 4)) + (local.set 0) + + (i32.store (i32.const 24) (local.get 0)) + ) + ) + + (thread $T11 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run_inner") + (i32.store (i32.const 20) (i32.const 42)) + ) + ) + (invoke "run_inner") + ) + + (wait $T11) + (invoke "run") +) + +(thread $T2 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (local i32) + (i32.store (i32.const 4) (i32.const 1)) + (i32.load (i32.const 0)) + (local.set 0) + + (i32.store (i32.const 32) (local.get 0)) + ) + ) + + (invoke "run") +) + +(wait $T1) +(wait $T2) diff --git a/test/spec/threads/simple.wast b/test/spec/threads/simple.wast new file mode 100644 index 00000000000..cf54b0036fc --- /dev/null +++ b/test/spec/threads/simple.wast @@ -0,0 +1,29 @@ +(module $Mem + (memory (export "shared") 1 1 shared) +) +(register "mem") + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (i32.atomic.store (i32.const 0) (i32.const 1)) + ) + ) + (invoke "run") +) + + +(wait $T1) + +(module $Check + (memory (import "mem" "shared") 1 1 shared) + + (func (export "check") (result i32) + (i32.load (i32.const 0)) + (return) + ) +) + +(assert_return (invoke $Check "check") (i32.const 1)) diff --git a/test/spec/threads/thread.wast b/test/spec/threads/thread.wast new file mode 100644 index 00000000000..094ba683153 --- /dev/null +++ b/test/spec/threads/thread.wast @@ -0,0 +1,48 @@ +(module $Mem + (memory (export "shared") 1 1 shared) +) +(register "mem_1") + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") + (i32.store (i32.const 0) (i32.const 42)) + ) + ) + (invoke "run") +) + +(thread $T2 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") (result i32) + (i32.load (i32.const 0)) + ) + ) + (assert_return (invoke "run") (either (i32.const 0) (i32.const 42))) +) + +(wait $T1) +(wait $T2) + + +(module (memory (import "mem_1" "shared") 1 1 shared)) + +(assert_unlinkable + (module (memory (import "mem" "shared") 1 1 shared)) + "unknown import" +) + +(register "mem" $Mem) + +(thread $T3 + (assert_unlinkable + (module (memory (import "mem" "shared") 1 1 shared)) + "unknown import" + ) +) + +(wait $T3) diff --git a/test/spec/threads/unlinkable.wast b/test/spec/threads/unlinkable.wast new file mode 100644 index 00000000000..880ed3d5275 --- /dev/null +++ b/test/spec/threads/unlinkable.wast @@ -0,0 +1,21 @@ +(module $Mem + (memory (export "shared") 1 1 shared) +) + +(thread $T2 + (assert_unlinkable + (module (memory (import "mem" "shared") 1 1 shared)) + "unknown import" + ) +) + +(wait $T2) + +(thread $T4 (shared (module $Mem)) + (assert_unlinkable + (module (memory (import "mem" "shared") 1 1 shared)) + "unknown import" + ) +) + +(wait $T4) diff --git a/test/spec/threads/wait_notify.wast b/test/spec/threads/wait_notify.wast new file mode 100644 index 00000000000..9c89d666d19 --- /dev/null +++ b/test/spec/threads/wait_notify.wast @@ -0,0 +1,41 @@ +;; test that looping notify eventually unblocks a parallel waiting thread +(module $Mem + (memory (export "shared") 1 1 shared) +) + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "run") (result i32) + (memory.atomic.wait32 (i32.const 0) (i32.const 0) (i64.const -1)) + ) + ) + ;; test that this thread eventually gets unblocked + (assert_return (invoke "run") (i32.const 0)) +) + +(thread $T2 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 1 shared) + (func (export "notify-0") (result i32) + (memory.atomic.notify (i32.const 0) (i32.const 0)) + ) + (func (export "notify-1-while") + (loop + (i32.const 1) + (memory.atomic.notify (i32.const 0) (i32.const 1)) + (i32.ne) + (br_if 0) + ) + ) + ) + ;; notifying with a count of 0 will not unblock + (assert_return (invoke "notify-0") (i32.const 0)) + ;; loop until something is notified + (assert_return (invoke "notify-1-while")) +) + +(wait $T1) +(wait $T2) From dcd21d2662c254aadd286592337f28b16c1e6593 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 3 Mar 2026 06:19:24 +0000 Subject: [PATCH 2/2] Implementation for threads in spec tests --- src/tools/wasm-shell.cpp | 131 ++++++++++++++++++++++++++++++++++- src/wasm-interpreter.h | 145 +++++++++++++++++++++++++++++++++++++-- src/wasm/wasm.cpp | 1 + test/spec/waitqueue.wast | 47 +++++++++++++ 4 files changed, 315 insertions(+), 9 deletions(-) diff --git a/src/tools/wasm-shell.cpp b/src/tools/wasm-shell.cpp index 8a87d40c17c..bff6a6bc9fb 100644 --- a/src/tools/wasm-shell.cpp +++ b/src/tools/wasm-shell.cpp @@ -53,6 +53,17 @@ struct Shell { Options& options; + struct ThreadState { + Name name; + std::vector commands; + size_t pc = 0; + bool isSuspended = false; + std::shared_ptr instance = nullptr; + std::shared_ptr suspendedCont = nullptr; + bool done = false; + }; + std::vector activeThreads; + Shell(Options& options) : options(options) { buildSpectestModule(); } Result<> run(WASTScript& script) { @@ -105,10 +116,119 @@ struct Shell { // Run threads in a blocking manner for now. // TODO: yield on blocking instructions e.g. memory.atomic.wait32. Result<> doThread(ThreadBlock& thread) { - return run(thread.commands); + ThreadState state; + state.name = thread.name; + state.commands = thread.commands; + activeThreads.push_back(std::move(state)); + return Ok{}; } Result<> doWait(Wait& wait) { + bool found = false; + for (auto& t : activeThreads) { + if (t.name == wait.thread) { + found = true; + break; + } + } + if (!found) { + return Err{"wait called for unknown thread"}; + } + + // Round-robin execution + while (true) { + bool anyProgress = false; + bool targetDone = false; + + for (auto& t : activeThreads) { + if (t.done) { + if (t.name == wait.thread) + targetDone = true; + continue; + } + + if (t.isSuspended) { + // Check if it's still waiting. WaitQueue sets `isWaiting` to false + // when notified. + bool stillWaiting = t.suspendedCont && t.suspendedCont->isWaiting; + + if (!stillWaiting) { + // It was woken up! We need to resume it. + t.isSuspended = false; + Flow flow; + try { + flow = t.instance->resumeContinuation(t.suspendedCont); + } catch (TrapException&) { + std::cerr << "Thread " << t.name << " trapped upon resume\n"; + t.done = true; + anyProgress = true; + continue; + } catch (...) { + WASM_UNREACHABLE("unexpected error during resume"); + } + t.suspendedCont = nullptr; + + if (flow.breakTo == THREAD_SUSPEND_FLOW) { + // Suspended again + t.isSuspended = true; + t.suspendedCont = t.instance->getSuspendedContinuation(); + anyProgress = true; + } else if (flow.suspendTag) { + t.instance->clearContinuationStore(); + t.done = true; // unhandled suspension + anyProgress = true; + } else { + t.pc++; // Completed the command that originally suspended! + anyProgress = true; + } + } + } else { + // Normal execution of the next command. + if (t.pc < t.commands.size()) { + auto& cmd = t.commands[t.pc].cmd; + if (auto* act = std::get_if(&cmd)) { + auto result = doAction(*act); + if (std::get_if(&result)) { + t.isSuspended = true; + if (auto* invoke = std::get_if(act)) { + t.instance = + instances[invoke->base ? *invoke->base : lastInstance]; + t.suspendedCont = t.instance->getSuspendedContinuation(); + } + anyProgress = true; + } else { + t.pc++; + anyProgress = true; + } + } else { + // Not an action, just run it (e.g. module instantiation or + // assertions inside thread) + auto res = runCommand(cmd); + if (res.getErr()) { + std::cerr << "Thread " << t.name + << " error: " << res.getErr()->msg << "\n"; + t.done = true; + } else { + t.pc++; + anyProgress = true; + } + } + } else { + t.done = true; + anyProgress = true; // finishing counts as progress + } + } + } + + if (targetDone) { + break; + } + + if (!anyProgress) { + // Find if target is still suspended + return Err{"deadlock! no threads can make progress"}; + } + } return Ok{}; } @@ -237,11 +357,13 @@ struct Shell { struct HostLimitResult {}; struct ExceptionResult {}; struct SuspensionResult {}; + struct ThreadSuspendResult {}; using ActionResult = std::variant; + SuspensionResult, + ThreadSuspendResult>; std::string resultToString(ActionResult& result) { if (std::get_if(&result)) { @@ -252,6 +374,8 @@ struct Shell { return "exception"; } else if (std::get_if(&result)) { return "suspension"; + } else if (std::get_if(&result)) { + return "thread_suspend"; } else if (auto* vals = std::get_if(&result)) { std::stringstream ss; ss << *vals; @@ -281,6 +405,9 @@ struct Shell { } catch (...) { WASM_UNREACHABLE("unexpected error"); } + if (flow.breakTo == THREAD_SUSPEND_FLOW) { + return ThreadSuspendResult{}; + } if (flow.suspendTag) { // This is an unhandled suspension. Handle it here - clear the // suspension state - so nothing else is affected. diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 48404207a60..6b2783c03b1 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -71,7 +71,8 @@ struct NonconstantException {}; // Utilities -extern Name RETURN_FLOW, RETURN_CALL_FLOW, NONCONSTANT_FLOW, SUSPEND_FLOW; +extern Name RETURN_FLOW, RETURN_CALL_FLOW, NONCONSTANT_FLOW, SUSPEND_FLOW, + THREAD_SUSPEND_FLOW; // Stuff that flows around during executing expressions: a literal, or a change // in control flow. @@ -87,13 +88,15 @@ class Flow { : values(std::move(values)), breakTo(breakTo) {} Flow(Name breakTo, Tag* suspendTag, Literals&& values) : values(std::move(values)), breakTo(breakTo), suspendTag(suspendTag) { - assert(breakTo == SUSPEND_FLOW); + assert(breakTo == SUSPEND_FLOW || breakTo == THREAD_SUSPEND_FLOW); } Literals values; Name breakTo; // if non-null, a break is going on Tag* suspendTag = nullptr; // if non-null, breakTo must be SUSPEND_FLOW, and - // this is the tag being suspended + // this is the tag being suspended. If breakTo is + // THREAD_SUSPEND_FLOW, this represents the thread + // suspending and this field is not used. // A helper function for the common case where there is only one value const Literal& getSingleValue() { @@ -281,6 +284,10 @@ struct ContData { // resume_throw_ref). Literal exception; + // If set, this continuation was suspended into a wait queue by a thread + // and has not yet been woken up. + bool isWaiting = false; + // Whether we executed. Continuations are one-shot, so they may not be // executed a second time. bool executed = false; @@ -303,6 +310,13 @@ struct ContinuationStore { // Set when we are resuming execution, that is, re-winding the stack. bool resuming = false; + + // The wait queue for threads waiting on addresses (represented by GCData and + // field index). + std::unordered_map< + std::shared_ptr, + std::unordered_map>>> + waitQueues; }; // Execute an expression @@ -2244,13 +2258,90 @@ class ExpressionRunner : public OverriddenVisitor { } Flow visitStructWait(StructWait* curr) { - WASM_UNREACHABLE("struct.wait not implemented"); - return Flow(); + VISIT(ref, curr->ref) + VISIT(expected, curr->expected) + VISIT(timeout, + curr->timeout) // We ignore timeout in the simulation for simplicity + + auto data = ref.getSingleValue().getGCData(); + if (!data) { + trap("null ref"); + } + + auto& field = data->values[curr->index]; + if (field != expected.getSingleValue()) { + return Literal(int32_t(1)); // not-equal, don't wait + } + + if (self()->isResuming()) { + // We have been notified and resumed. + // Clear the resume state and continue. + auto currContinuation = self()->getCurrContinuation(); + assert(curr == currContinuation->resumeExpr); + self()->continuationStore->resuming = false; + assert(currContinuation->resumeInfo.empty()); + assert(self()->restoredValuesMap.empty()); + return Literal(int32_t(0)); // ok, woken up + } + + // We need to wait. Create a continuation and suspend the thread. + auto old = self()->getCurrContinuationOrNull(); + if (!old) { + // Not executing within a continuation, cannot suspend. + // For wasm-shell simulation, we assume threads are started with + // ContNew/ContBind. + return Flow(THREAD_SUSPEND_FLOW); // This will cause a trap up the stack + // natively if not caught. + } + assert(old->executed); + + auto new_ = std::make_shared(); + self()->popCurrContinuation(); + self()->pushCurrContinuation(new_); + new_->resumeExpr = curr; + new_->isWaiting = true; + + self()->continuationStore->waitQueues[data][curr->index].push_back(new_); + + return Flow(THREAD_SUSPEND_FLOW); } Flow visitStructNotify(StructNotify* curr) { - WASM_UNREACHABLE("struct.notify not implemented"); - return Flow(); + VISIT(ref, curr->ref) + VISIT(count, curr->count) + + auto data = ref.getSingleValue().getGCData(); + if (!data) { + trap("null ref"); + } + + int32_t countVal = count.getSingleValue().geti32(); + int32_t woken = 0; + + auto& store = self()->continuationStore; + auto it1 = store->waitQueues.find(data); + if (it1 != store->waitQueues.end()) { + auto& fieldQueues = it1->second; + auto it2 = fieldQueues.find(curr->index); + if (it2 != fieldQueues.end()) { + auto& queue = it2->second; + while (!queue.empty() && woken < countVal) { + // The waking thread will be executed by the wasm-shell scheduler. + // In the reference interpreter, awake continuations should be + // tracked. Since wasm-shell handles interleaved threads, we don't + // automatically execute them here. Wait! wasm-shell scheduler needs + // to know which threads are ready. Our ContinuationStore wait queues + // structure just pops them. The scheduler wrapper will need a way to + // track all active threads. + auto wokeCont = queue.front(); + wokeCont->isWaiting = false; + queue.erase(queue.begin()); + woken++; + } + } + } + + return Literal(woken); } // Arbitrary deterministic limit on size. If we need to allocate a Literals @@ -2714,6 +2805,10 @@ class ExpressionRunner : public OverriddenVisitor { virtual void hostLimit(std::string_view why) { WASM_UNREACHABLE("unimp"); } + virtual void invokeMain(const std::string& startName) { + WASM_UNREACHABLE("unimp"); + } + virtual void throwException(const WasmException& exn) { WASM_UNREACHABLE("unimp"); } @@ -3257,6 +3352,42 @@ class ModuleRunnerBase : public ExpressionRunner { Flow callExport(Name name) { return callExport(name, Literals()); } + std::shared_ptr getSuspendedContinuation() { + return this->getCurrContinuationOrNull(); + } + + Flow resumeContinuation(std::shared_ptr contData, + Literals arguments = {}) { + if (contData->executed) { + this->trap("continuation already executed"); + } + contData->executed = true; + + if (contData->resumeArguments.empty()) { + contData->resumeArguments = arguments; + } + + this->pushCurrContinuation(contData); + this->continuationStore->resuming = true; +#if WASM_INTERPRETER_DEBUG + std::cout << this->indent() << "resuming func " << contData->func.getFunc() + << '\n'; +#endif + + Flow ret = contData->func.getFuncData()->doCall(arguments); + + if (this->isResuming()) { + // if we didn't suspend again natively, clear resuming flag + this->continuationStore->resuming = false; + } + + if (ret.breakTo != THREAD_SUSPEND_FLOW && !ret.suspendTag) { + // The coroutine finished normally. + this->popCurrContinuation(); + } + return ret; + } + Literal getExportedFunction(Name name) { Export* export_ = wasm.getExportOrNull(name); if (!export_ || export_->kind != ExternalKind::Function) { diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 4b27dcdb547..ef54264c617 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -28,6 +28,7 @@ Name RETURN_FLOW("*return:)*"); Name RETURN_CALL_FLOW("*return-call:)*"); Name NONCONSTANT_FLOW("*nonconstant:)*"); Name SUSPEND_FLOW("*suspend:)*"); +Name THREAD_SUSPEND_FLOW("*thread_suspend:)*"); namespace BinaryConsts::CustomSections { diff --git a/test/spec/waitqueue.wast b/test/spec/waitqueue.wast index cd0631ef1da..122a31b555d 100644 --- a/test/spec/waitqueue.wast +++ b/test/spec/waitqueue.wast @@ -96,3 +96,50 @@ (struct.get $t 0 (global.get $g)) ) ) + +(module $Mem + (type $Wq (struct (field (mut waitqueue)))) + (global $wq (export "wq") (mut (ref null $Wq)) (ref.null $Wq)) + + (func $init (export "init") + (global.set $wq (struct.new $Wq (i32.const 0))) + ) +) + +(register "mem") + +(invoke $Mem "init") + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (type $Wq (struct (field (mut waitqueue)))) + (global $wq (import "mem" "wq") (mut (ref null $Wq))) + + (func (export "run_wait") (result i32) + ;; Wait on the waitqueue, expecting value 0, infinite timeout (-1) + (struct.wait $Wq 0 (global.get $wq) (i32.const 0) (i64.const -1)) + ) + ) + ;; This thread will suspend on struct.wait + (invoke "run_wait") +) + +(thread $T2 (shared (module $Mem)) + (register "mem" $Mem) + (module + (type $Wq (struct (field (mut waitqueue)))) + (global $wq (import "mem" "wq") (mut (ref null $Wq))) + + (func (export "run_notify") (result i32) + ;; Notify 1 waiter on the waitqueue + (struct.notify $Wq 0 (global.get $wq) (i32.const 1)) + ) + ) + ;; This thread will notify the waitqueue and wake 1 thread + (assert_return (invoke "run_notify") (i32.const 1)) +) + +;; Wait for threads to complete +(wait $T1) +(wait $T2)