diff --git a/test/build.sh b/test/build.sh index 960174e..86c4f5a 100755 --- a/test/build.sh +++ b/test/build.sh @@ -1,12 +1,6 @@ -#!/bin/bash +#! /bin/sh -CC=${CC:=clang} +set -e -for input in testsuite/*.c; do - output="testsuite/$(basename $input .c).wasm" - - if [ "$input" -nt "$output" ]; then - echo "Compiling $input" - $CC "$input" testsuite/wasi_thread_spawn.S -o "$output" - fi -done +./scripts/build-wat.sh +./scripts/build-c.sh diff --git a/test/scripts/build-c.sh b/test/scripts/build-c.sh new file mode 100755 index 0000000..960174e --- /dev/null +++ b/test/scripts/build-c.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +CC=${CC:=clang} + +for input in testsuite/*.c; do + output="testsuite/$(basename $input .c).wasm" + + if [ "$input" -nt "$output" ]; then + echo "Compiling $input" + $CC "$input" testsuite/wasi_thread_spawn.S -o "$output" + fi +done diff --git a/test/scripts/build-wat.sh b/test/scripts/build-wat.sh new file mode 100755 index 0000000..3df8bef --- /dev/null +++ b/test/scripts/build-wat.sh @@ -0,0 +1,6 @@ +#! /bin/sh + +WAT2WASM=${WAT2WASM:-wat2wasm} +for wat in testsuite/*.wat; do + ${WAT2WASM} --enable-threads -o ${wat%%.wat}.wasm ${wat} +done diff --git a/test/testsuite/wasi_threads_exit_main_block.json b/test/testsuite/wasi_threads_exit_main_block.json new file mode 100644 index 0000000..e8a2acf --- /dev/null +++ b/test/testsuite/wasi_threads_exit_main_block.json @@ -0,0 +1,3 @@ +{ + "exit_code": 99 +} diff --git a/test/testsuite/wasi_threads_exit_main_block.wat b/test/testsuite/wasi_threads_exit_main_block.wat new file mode 100644 index 0000000..d6e5900 --- /dev/null +++ b/test/testsuite/wasi_threads_exit_main_block.wat @@ -0,0 +1,45 @@ +;; When the main thread calls proc_exit, it should terminate +;; a thread blocking in `memory.atomic.wait32` opcode. +;; +;; linear memory usage: +;; 0: notify/wait + +(module + (memory (export "memory") (import "foo" "bar") 1 1 shared) + (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) + (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) + (func (export "wasi_thread_start") (param i32 i32) + ;; infinite wait + i32.const 0 + i32.const 0 + i64.const -1 + memory.atomic.wait32 + unreachable + ) + (func (export "_start") + ;; spawn a thread + i32.const 0 + call $thread_spawn + ;; check error + i32.const 0 + i32.le_s + if + unreachable + end + ;; wait 500ms to ensure the other thread block + i32.const 0 + i32.const 0 + i64.const 500_000_000 + memory.atomic.wait32 + ;; assert a timeout + i32.const 2 + i32.ne + if + unreachable + end + ;; exit + i32.const 99 + call $proc_exit + unreachable + ) +) diff --git a/test/testsuite/wasi_threads_exit_main_busy.json b/test/testsuite/wasi_threads_exit_main_busy.json new file mode 100644 index 0000000..e8a2acf --- /dev/null +++ b/test/testsuite/wasi_threads_exit_main_busy.json @@ -0,0 +1,3 @@ +{ + "exit_code": 99 +} diff --git a/test/testsuite/wasi_threads_exit_main_busy.wat b/test/testsuite/wasi_threads_exit_main_busy.wat new file mode 100644 index 0000000..3ea7709 --- /dev/null +++ b/test/testsuite/wasi_threads_exit_main_busy.wat @@ -0,0 +1,44 @@ +;; When the main thread calls proc_exit, it should terminate +;; a busy-looping thread. +;; +;; linear memory usage: +;; 0: wait + +(module + (memory (export "memory") (import "foo" "bar") 1 1 shared) + (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) + (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) + (func (export "wasi_thread_start") (param i32 i32) + ;; infinite loop + loop + br 0 + end + unreachable + ) + (func (export "_start") + ;; spawn a thread + i32.const 0 + call $thread_spawn + ;; check error + i32.const 0 + i32.le_s + if + unreachable + end + ;; wait 500ms to ensure the other thread to enter the busy loop + i32.const 0 + i32.const 0 + i64.const 500_000_000 + memory.atomic.wait32 + ;; assert a timeout + i32.const 2 + i32.ne + if + unreachable + end + ;; exit + i32.const 99 + call $proc_exit + unreachable + ) +) diff --git a/test/testsuite/wasi_threads_exit_main_wasi.json b/test/testsuite/wasi_threads_exit_main_wasi.json new file mode 100644 index 0000000..e8a2acf --- /dev/null +++ b/test/testsuite/wasi_threads_exit_main_wasi.json @@ -0,0 +1,3 @@ +{ + "exit_code": 99 +} diff --git a/test/testsuite/wasi_threads_exit_main_wasi.wat b/test/testsuite/wasi_threads_exit_main_wasi.wat new file mode 100644 index 0000000..34e65d6 --- /dev/null +++ b/test/testsuite/wasi_threads_exit_main_wasi.wat @@ -0,0 +1,54 @@ +;; When the main thread calls proc_exit, it should terminate +;; a thread blocking in a WASI call. (poll_oneoff) +;; +;; linear memory usage: +;; 0: wait +;; 100: poll_oneoff subscription +;; 200: poll_oneoff event +;; 300: poll_oneoff return value + +(module + (memory (export "memory") (import "foo" "bar") 1 1 shared) + (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) + (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) + (func $poll_oneoff (import "wasi_snapshot_preview1" "poll_oneoff") (param i32 i32 i32 i32) (result i32)) + (func (export "wasi_thread_start") (param i32 i32) + ;; long enough block + ;; clock_realtime, !abstime (zeros) + i32.const 124 ;; 100 + offsetof(subscription, timeout) + i64.const 1_000_000_000 ;; 1s + i64.store + i32.const 100 ;; subscription + i32.const 200 ;; event (out) + i32.const 1 ;; nsubscriptions + i32.const 300 ;; retp (out) + call $poll_oneoff + unreachable + ) + (func (export "_start") + ;; spawn a thread + i32.const 0 + call $thread_spawn + ;; check error + i32.const 0 + i32.le_s + if + unreachable + end + ;; wait 500ms to ensure the other thread block + i32.const 0 + i32.const 0 + i64.const 500_000_000 + memory.atomic.wait32 + ;; assert a timeout + i32.const 2 + i32.ne + if + unreachable + end + ;; exit + i32.const 99 + call $proc_exit + unreachable + ) +) diff --git a/test/testsuite/wasi_threads_exit_nonmain_block.json b/test/testsuite/wasi_threads_exit_nonmain_block.json new file mode 100644 index 0000000..e8a2acf --- /dev/null +++ b/test/testsuite/wasi_threads_exit_nonmain_block.json @@ -0,0 +1,3 @@ +{ + "exit_code": 99 +} diff --git a/test/testsuite/wasi_threads_exit_nonmain_block.wat b/test/testsuite/wasi_threads_exit_nonmain_block.wat new file mode 100644 index 0000000..36428bd --- /dev/null +++ b/test/testsuite/wasi_threads_exit_nonmain_block.wat @@ -0,0 +1,45 @@ +;; When a non-main thread calls proc_exit, it should terminate +;; the main thread blocking in `memory.atomic.wait32` opcode. +;; +;; linear memory usage: +;; 0: wait + +(module + (memory (export "memory") (import "foo" "bar") 1 1 shared) + (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) + (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) + (func (export "wasi_thread_start") (param i32 i32) + ;; wait 500ms to ensure the other thread block + i32.const 0 + i32.const 0 + i64.const 500_000_000 + memory.atomic.wait32 + ;; assert a timeout + i32.const 2 + i32.ne + if + unreachable + end + ;; exit + i32.const 99 + call $proc_exit + unreachable + ) + (func (export "_start") + ;; spawn a thread + i32.const 0 + call $thread_spawn + ;; check error + i32.const 0 + i32.le_s + if + unreachable + end + ;; infinite wait + i32.const 0 + i32.const 0 + i64.const -1 + memory.atomic.wait32 + unreachable + ) +) diff --git a/test/testsuite/wasi_threads_exit_nonmain_busy.json b/test/testsuite/wasi_threads_exit_nonmain_busy.json new file mode 100644 index 0000000..e8a2acf --- /dev/null +++ b/test/testsuite/wasi_threads_exit_nonmain_busy.json @@ -0,0 +1,3 @@ +{ + "exit_code": 99 +} diff --git a/test/testsuite/wasi_threads_exit_nonmain_busy.wat b/test/testsuite/wasi_threads_exit_nonmain_busy.wat new file mode 100644 index 0000000..b256b0d --- /dev/null +++ b/test/testsuite/wasi_threads_exit_nonmain_busy.wat @@ -0,0 +1,44 @@ +;; When a non-main thread calls proc_exit, it should terminate +;; the main thread which is busy-looping. +;; +;; linear memory usage: +;; 0: wait + +(module + (memory (export "memory") (import "foo" "bar") 1 1 shared) + (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) + (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) + (func (export "wasi_thread_start") (param i32 i32) + ;; wait 500ms to ensure the other thread to enter the busy loop + i32.const 0 + i32.const 0 + i64.const 500_000_000 + memory.atomic.wait32 + ;; assert a timeout + i32.const 2 + i32.ne + if + unreachable + end + ;; exit + i32.const 99 + call $proc_exit + unreachable + ) + (func (export "_start") + ;; spawn a thread + i32.const 0 + call $thread_spawn + ;; check error + i32.const 0 + i32.le_s + if + unreachable + end + ;; infinite loop + loop + br 0 + end + unreachable + ) +) diff --git a/test/testsuite/wasi_threads_exit_nonmain_wasi.json b/test/testsuite/wasi_threads_exit_nonmain_wasi.json new file mode 100644 index 0000000..e8a2acf --- /dev/null +++ b/test/testsuite/wasi_threads_exit_nonmain_wasi.json @@ -0,0 +1,3 @@ +{ + "exit_code": 99 +} diff --git a/test/testsuite/wasi_threads_exit_nonmain_wasi.wat b/test/testsuite/wasi_threads_exit_nonmain_wasi.wat new file mode 100644 index 0000000..3e1ed4a --- /dev/null +++ b/test/testsuite/wasi_threads_exit_nonmain_wasi.wat @@ -0,0 +1,54 @@ +;; When a non-main thread calls proc_exit, it should terminate +;; the main thread which is blocking in a WASI call. (poll_oneoff) +;; +;; linear memory usage: +;; 0: wait +;; 100: poll_oneoff subscription +;; 200: poll_oneoff event +;; 300: poll_oneoff return value + +(module + (memory (export "memory") (import "foo" "bar") 1 1 shared) + (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) + (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) + (func $poll_oneoff (import "wasi_snapshot_preview1" "poll_oneoff") (param i32 i32 i32 i32) (result i32)) + (func (export "wasi_thread_start") (param i32 i32) + ;; wait 500ms to ensure the other thread block + i32.const 0 + i32.const 0 + i64.const 500_000_000 + memory.atomic.wait32 + ;; assert a timeout + i32.const 2 + i32.ne + if + unreachable + end + ;; exit + i32.const 99 + call $proc_exit + unreachable + ) + (func (export "_start") + ;; spawn a thread + i32.const 0 + call $thread_spawn + ;; check error + i32.const 0 + i32.le_s + if + unreachable + end + ;; long enough block + ;; clock_realtime, !abstime (zeros) + i32.const 124 ;; 100 + offsetof(subscription, timeout) + i64.const 1_000_000_000 ;; 1s + i64.store + i32.const 100 ;; subscription + i32.const 200 ;; event (out) + i32.const 1 ;; nsubscriptions + i32.const 300 ;; retp (out) + call $poll_oneoff + unreachable + ) +) diff --git a/test/testsuite/wasi_threads_spawn.json b/test/testsuite/wasi_threads_spawn.json new file mode 100644 index 0000000..3e5ce1c --- /dev/null +++ b/test/testsuite/wasi_threads_spawn.json @@ -0,0 +1,3 @@ +{ + "exit_code": 22 +} diff --git a/test/testsuite/wasi_threads_spawn.wat b/test/testsuite/wasi_threads_spawn.wat new file mode 100644 index 0000000..74086c9 --- /dev/null +++ b/test/testsuite/wasi_threads_spawn.wat @@ -0,0 +1,78 @@ +;; Create a thread with thread-spawn and perform a few sanity checks. + +;; linear memory usage: +;; 0: notify/wait +;; 4: tid +;; 8: user_arg + +(module + (memory (export "memory") (import "foo" "bar") 1 1 shared) + (func $thread_spawn (import "wasi" "thread-spawn") (param i32) (result i32)) + (func $proc_exit (import "wasi_snapshot_preview1" "proc_exit") (param i32)) + (func (export "wasi_thread_start") (param $tid i32) (param $user_arg i32) + ;; store tid + i32.const 4 + local.get $tid + i32.store + ;; store user pointer + i32.const 8 + local.get $user_arg + i32.store + ;; store fence + atomic.fence + ;; notify the main + i32.const 0 + i32.const 1 + i32.atomic.store + i32.const 0 + i32.const 1 + memory.atomic.notify + drop + ;; returning from wasi_thread_start terminates only this thread + ) + (func (export "_start") (local $tid i32) + ;; spawn a thread + i32.const 12345 ;; user pointer + call $thread_spawn + ;; check error + local.tee $tid ;; save the tid to check later + i32.const 0 + i32.le_s + if + unreachable + end + ;; wait for the spawned thread to run + i32.const 0 + i32.const 0 + i64.const -1 + memory.atomic.wait32 + ;; assert it was not a timeout + i32.const 2 + i32.eq + if + unreachable + end + ;; load fence + atomic.fence + ;; check the tid + local.get $tid + i32.const 4 + i32.load + i32.ne + if + unreachable + end + ;; check the user pointer + i32.const 8 + i32.load + i32.const 12345 + i32.ne + if + unreachable + end + ;; exit + i32.const 22 + call $proc_exit + unreachable + ) +)