From 35372298137a21e04f356ca4d20b9f8b7cb7fbc3 Mon Sep 17 00:00:00 2001 From: conrad Date: Thu, 29 Sep 2022 13:51:16 +0100 Subject: [PATCH 1/5] implement atomic.fence, add wait/notify edge case tests --- interpreter/binary/decode.ml | 1 + interpreter/binary/encode.ml | 3 ++ interpreter/exec/eval.ml | 16 +++++++++- interpreter/syntax/ast.ml | 1 + interpreter/syntax/operators.ml | 3 ++ interpreter/text/arrange.ml | 1 + interpreter/text/lexer.mll | 1 + interpreter/text/parser.mly | 3 +- interpreter/valid/valid.ml | 3 ++ test/core/atomic.wast | 52 +++++++++++++++++++++++++++++++++ 10 files changed, 82 insertions(+), 2 deletions(-) diff --git a/interpreter/binary/decode.ml b/interpreter/binary/decode.ml index 19c0b3f3..0326ab59 100644 --- a/interpreter/binary/decode.ml +++ b/interpreter/binary/decode.ml @@ -466,6 +466,7 @@ let rec instr s = | 0x00 -> let a, o = memop s in memory_atomic_notify a o | 0x01 -> let a, o = memop s in memory_atomic_wait32 a o | 0x02 -> let a, o = memop s in memory_atomic_wait64 a o + | 0x03 -> expect 0x00 s "zero flag expected"; atomic_fence | 0x10 -> let a, o = memop s in i32_atomic_load a o | 0x11 -> let a, o = memop s in i64_atomic_load a o diff --git a/interpreter/binary/encode.ml b/interpreter/binary/encode.ml index d1083cd3..902e05e3 100644 --- a/interpreter/binary/encode.ml +++ b/interpreter/binary/encode.ml @@ -221,6 +221,9 @@ let encode m = assert false | MemoryAtomicWait {ty = F32Type | F64Type; _} -> assert false + | AtomicFence -> + op 0xfe; op 0x03; op 0x00 + | AtomicLoad ({ty = I32Type; sz = None; _} as mo) -> op 0xfe; op 0x10; memop mo | AtomicLoad ({ty = I64Type; sz = None; _} as mo) -> diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index 585bb16c..89feaf29 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -132,6 +132,10 @@ let check_align addr ty sz at = if not (Memory.is_aligned addr ty sz) then Trap.error at "unaligned atomic memory access" +let check_shared mem at = + if (shared_memory_type (Memory.type_of mem)) != Shared then + Trap.error at "expected shared memory" + (* Evaluation *) @@ -319,6 +323,7 @@ let rec step_thread (t : thread) : thread = (try assert (sz = None); check_align addr ty sz e.at; + check_shared mem e.at; let v = Memory.load_value mem addr offset ty in if v = ve then assert false (* TODO *) @@ -326,11 +331,20 @@ let rec step_thread (t : thread) : thread = I32 1l :: vs', [] (* Not equal *) with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at]) - | MemoryAtomicNotify x, I32 count :: I32 i :: vs' -> + | MemoryAtomicNotify {offset; ty; sz; _}, I32 count :: I32 i :: vs' -> + let mem = memory frame.inst (0l @@ e.at) in + let addr = I64_convert.extend_i32_u i in + (try + check_align addr ty sz e.at; + let _ = Memory.load_value mem addr offset ty in if count = 0l then I32 0l :: vs', [] (* Trivial case waking 0 waiters *) else assert false (* TODO *) + with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at]) + + | AtomicFence, vs -> + vs, [] | MemorySize, vs -> let mem = memory frame.inst (0l @@ e.at) in diff --git a/interpreter/syntax/ast.ml b/interpreter/syntax/ast.ml index d7f77947..8123dba0 100644 --- a/interpreter/syntax/ast.ml +++ b/interpreter/syntax/ast.ml @@ -106,6 +106,7 @@ and instr' = | Convert of cvtop (* conversion *) | MemoryAtomicWait of atomicop (* atomically wait for notification at address *) | MemoryAtomicNotify of atomicop (* atomically notify all waiters at address *) + | AtomicFence (* perform an atomic fence *) | AtomicLoad of atomicop (* atomically read memory at address *) | AtomicStore of atomicop (* atomically write memory at address *) | AtomicRmw of rmwop * atomicop (* atomically read, modify, write memory at address *) diff --git a/interpreter/syntax/operators.ml b/interpreter/syntax/operators.ml index 24a1e60b..988f9cd7 100644 --- a/interpreter/syntax/operators.ml +++ b/interpreter/syntax/operators.ml @@ -78,6 +78,9 @@ let memory_atomic_wait32 align offset = let memory_atomic_wait64 align offset = MemoryAtomicWait {ty = I64Type; align; offset; sz = None} +let atomic_fence = + AtomicFence + let i32_atomic_load align offset = AtomicLoad {ty = I32Type; align; offset; sz = None} let i64_atomic_load align offset = diff --git a/interpreter/text/arrange.ml b/interpreter/text/arrange.ml index aa727a8f..944227e7 100644 --- a/interpreter/text/arrange.ml +++ b/interpreter/text/arrange.ml @@ -311,6 +311,7 @@ let rec instr e = | Convert op -> cvtop op, [] | MemoryAtomicWait op -> memoryatomicwaitop op, [] | MemoryAtomicNotify op -> memoryatomicnotifyop op, [] + | AtomicFence -> "atomic.fence", [] | AtomicLoad op -> atomicloadop op, [] | AtomicStore op -> atomicstoreop op, [] | AtomicRmw (rmwop, op) -> atomicrmwop op rmwop, [] diff --git a/interpreter/text/lexer.mll b/interpreter/text/lexer.mll index 4be7049c..fb2f7c57 100644 --- a/interpreter/text/lexer.mll +++ b/interpreter/text/lexer.mll @@ -259,6 +259,7 @@ rule token = parse intop ("i" ^ sz) (memory_atomic_wait32 (opt a 2)) (memory_atomic_wait64 (opt a 3)) o) } + | "atomic.fence" { ATOMIC_FENCE } | (ixx as t)".atomic.load" { ATOMIC_LOAD (fun a o -> intop t (i32_atomic_load (opt a 2)) (i64_atomic_load (opt a 3)) o) } diff --git a/interpreter/text/parser.mly b/interpreter/text/parser.mly index 6bc5cf2a..fdce0ff0 100644 --- a/interpreter/text/parser.mly +++ b/interpreter/text/parser.mly @@ -169,7 +169,7 @@ let inline_type_explicit (c : context) x ft at = %token CALL CALL_INDIRECT RETURN %token LOCAL_GET LOCAL_SET LOCAL_TEE GLOBAL_GET GLOBAL_SET %token LOAD STORE OFFSET_EQ_NAT ALIGN_EQ_NAT -%token MEMORY_ATOMIC_WAIT MEMORY_ATOMIC_NOTIFY +%token MEMORY_ATOMIC_WAIT MEMORY_ATOMIC_NOTIFY ATOMIC_FENCE %token ATOMIC_LOAD ATOMIC_STORE ATOMIC_RMW ATOMIC_RMW_CMPXCHG %token CONST UNARY BINARY TEST COMPARE CONVERT %token UNREACHABLE MEMORY_SIZE MEMORY_GROW @@ -358,6 +358,7 @@ plain_instr : | CONVERT { fun c -> $1 } | MEMORY_ATOMIC_WAIT offset_opt align_opt { fun c -> $1 $3 $2 } | MEMORY_ATOMIC_NOTIFY offset_opt align_opt { fun c -> $1 $3 $2 } + | ATOMIC_FENCE { fun c -> atomic_fence } | ATOMIC_LOAD offset_opt align_opt { fun c -> $1 $3 $2 } | ATOMIC_STORE offset_opt align_opt { fun c -> $1 $3 $2 } | ATOMIC_RMW offset_opt align_opt { fun c -> $1 $3 $2 } diff --git a/interpreter/valid/valid.ml b/interpreter/valid/valid.ml index 5e1dcfbf..3c1c5cec 100644 --- a/interpreter/valid/valid.ml +++ b/interpreter/valid/valid.ml @@ -308,6 +308,9 @@ let rec check_instr (c : context) (e : instr) (s : infer_stack_type) : op_type = check_memop c memop (fun sz -> sz) e.at; [I32Type; memop.ty; I64Type] --> [I32Type] + | AtomicFence -> + [] --> [] + | AtomicLoad memop -> check_memop c memop (fun sz -> sz) e.at; [I32Type] --> [memop.ty] diff --git a/test/core/atomic.wast b/test/core/atomic.wast index 9f3cd65d..5f04e485 100644 --- a/test/core/atomic.wast +++ b/test/core/atomic.wast @@ -417,6 +417,7 @@ (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") +;; wait/notify (module (memory 1 1 shared) @@ -431,10 +432,54 @@ ) (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) @@ -488,6 +533,13 @@ (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") From 9404b81e2b517e78018b12e0c743c9a89c17e738 Mon Sep 17 00:00:00 2001 From: Conrad Watt Date: Tue, 4 Oct 2022 14:31:52 +0100 Subject: [PATCH 2/5] Update interpreter/exec/eval.ml Co-authored-by: Andreas Rossberg --- interpreter/exec/eval.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index 89feaf29..55277d48 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -133,7 +133,7 @@ let check_align addr ty sz at = Trap.error at "unaligned atomic memory access" let check_shared mem at = - if (shared_memory_type (Memory.type_of mem)) != Shared then + if shared_memory_type (Memory.type_of mem) <> Shared then Trap.error at "expected shared memory" From 8cfa06bdb4bb3f507b2e4368f071b4c6c7abc3be Mon Sep 17 00:00:00 2001 From: Andreas Rossberg Date: Thu, 2 Dec 2021 08:19:59 +0100 Subject: [PATCH 3/5] Fix shared text syntax in spec --- document/core/text/types.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/document/core/text/types.rst b/document/core/text/types.rst index 2087922f..3c9fe76e 100644 --- a/document/core/text/types.rst +++ b/document/core/text/types.rst @@ -87,7 +87,7 @@ Memory Types \begin{array}{llclll@{\qquad\qquad}l} \production{memory type} & \Tmemtype &::=& \X{lim}{:}\Tlimits &\Rightarrow& \X{lim}~\UNSHARED \\ &&|& - \text{(}~\text{shared}~~\X{lim}{:}\Tlimits~~\text{)} &\Rightarrow& \X{lim}~\SHARED \\ + \X{lim}{:}\Tlimits~~\text{shared} &\Rightarrow& \X{lim}~\SHARED \\ \end{array} From 9d69eb33af7785d5ada268149807d5cc16a16a93 Mon Sep 17 00:00:00 2001 From: conrad Date: Thu, 29 Sep 2022 12:23:25 +0100 Subject: [PATCH 4/5] towards concurrent tests --- interpreter/exec/eval.ml | 1 + interpreter/script/js.ml | 37 +++--- interpreter/script/js.mli | 2 +- interpreter/script/run.ml | 16 ++- test/build.py | 2 +- test/core/{thread.wast => MP.wast} | 12 +- test/core/MP_atomic.wast | 40 +++++++ test/core/README.md | 11 -- test/core/{ => non-threads}/address.wast | 0 test/core/{ => non-threads}/align.wast | 0 test/core/{ => non-threads}/atomic.wast | 0 .../core/{ => non-threads}/binary-leb128.wast | 0 test/core/{ => non-threads}/binary.wast | 0 test/core/{ => non-threads}/block.wast | 0 test/core/{ => non-threads}/br.wast | 0 test/core/{ => non-threads}/br_if.wast | 0 test/core/{ => non-threads}/br_table.wast | 0 test/core/{ => non-threads}/call.wast | 0 .../core/{ => non-threads}/call_indirect.wast | 0 test/core/{ => non-threads}/comments.wast | Bin test/core/{ => non-threads}/const.wast | 0 test/core/{ => non-threads}/conversions.wast | 0 test/core/{ => non-threads}/custom.wast | 0 test/core/{ => non-threads}/data.wast | 0 test/core/{ => non-threads}/elem.wast | 0 test/core/{ => non-threads}/endianness.wast | 0 test/core/{ => non-threads}/exports.wast | 0 test/core/{ => non-threads}/f32.wast | 0 test/core/{ => non-threads}/f32_bitwise.wast | 0 test/core/{ => non-threads}/f32_cmp.wast | 0 test/core/{ => non-threads}/f64.wast | 0 test/core/{ => non-threads}/f64_bitwise.wast | 0 test/core/{ => non-threads}/f64_cmp.wast | 0 test/core/{ => non-threads}/fac.wast | 0 test/core/{ => non-threads}/float_exprs.wast | 0 .../{ => non-threads}/float_literals.wast | 0 test/core/{ => non-threads}/float_memory.wast | 0 test/core/{ => non-threads}/float_misc.wast | 0 test/core/{ => non-threads}/forward.wast | 0 test/core/{ => non-threads}/func.wast | 0 test/core/{ => non-threads}/func_ptrs.wast | 0 test/core/{ => non-threads}/global.wast | 0 test/core/{ => non-threads}/i32.wast | 0 test/core/{ => non-threads}/i64.wast | 0 test/core/{ => non-threads}/if.wast | 0 test/core/{ => non-threads}/imports.wast | 0 .../core/{ => non-threads}/inline-module.wast | 0 test/core/{ => non-threads}/int_exprs.wast | 0 test/core/{ => non-threads}/int_literals.wast | 0 test/core/{ => non-threads}/labels.wast | 0 .../core/{ => non-threads}/left-to-right.wast | 0 test/core/{ => non-threads}/linking.wast | 0 test/core/{ => non-threads}/load.wast | 0 test/core/{ => non-threads}/local_get.wast | 0 test/core/{ => non-threads}/local_set.wast | 0 test/core/{ => non-threads}/local_tee.wast | 0 test/core/{ => non-threads}/loop.wast | 0 test/core/{ => non-threads}/memory.wast | 0 test/core/{ => non-threads}/memory_grow.wast | 0 .../{ => non-threads}/memory_redundancy.wast | 0 test/core/{ => non-threads}/memory_size.wast | 0 test/core/{ => non-threads}/memory_trap.wast | 0 test/core/{ => non-threads}/names.wast | 0 test/core/{ => non-threads}/nop.wast | 0 test/core/{ => non-threads}/return.wast | 0 test/core/{ => non-threads}/select.wast | 0 .../skip-stack-guard-page.wast | 0 test/core/{ => non-threads}/stack.wast | 0 test/core/{ => non-threads}/start.wast | 0 test/core/{ => non-threads}/store.wast | 0 test/core/{ => non-threads}/switch.wast | 0 test/core/{ => non-threads}/table.wast | 0 test/core/{ => non-threads}/token.wast | 0 test/core/{ => non-threads}/traps.wast | 0 test/core/{ => non-threads}/type.wast | 0 test/core/{ => non-threads}/unreachable.wast | 0 .../{ => non-threads}/unreached-invalid.wast | 0 test/core/{ => non-threads}/unwind.wast | 0 .../utf8-custom-section-id.wast | 0 .../{ => non-threads}/utf8-import-field.wast | 0 .../{ => non-threads}/utf8-import-module.wast | 0 .../utf8-invalid-encoding.wast | 0 test/core/run.py | 109 ------------------ test/harness/async_index.js | 85 +++++++++++++- test/harness/async_worker.js | 68 +++++++++++ test/harness/sync_index.js | 8 ++ test/simple_cors_server.py | 19 +++ 87 files changed, 270 insertions(+), 140 deletions(-) rename test/core/{thread.wast => MP.wast} (55%) create mode 100644 test/core/MP_atomic.wast delete mode 100644 test/core/README.md rename test/core/{ => non-threads}/address.wast (100%) rename test/core/{ => non-threads}/align.wast (100%) rename test/core/{ => non-threads}/atomic.wast (100%) rename test/core/{ => non-threads}/binary-leb128.wast (100%) rename test/core/{ => non-threads}/binary.wast (100%) rename test/core/{ => non-threads}/block.wast (100%) rename test/core/{ => non-threads}/br.wast (100%) rename test/core/{ => non-threads}/br_if.wast (100%) rename test/core/{ => non-threads}/br_table.wast (100%) rename test/core/{ => non-threads}/call.wast (100%) rename test/core/{ => non-threads}/call_indirect.wast (100%) rename test/core/{ => non-threads}/comments.wast (100%) rename test/core/{ => non-threads}/const.wast (100%) rename test/core/{ => non-threads}/conversions.wast (100%) rename test/core/{ => non-threads}/custom.wast (100%) rename test/core/{ => non-threads}/data.wast (100%) rename test/core/{ => non-threads}/elem.wast (100%) rename test/core/{ => non-threads}/endianness.wast (100%) rename test/core/{ => non-threads}/exports.wast (100%) rename test/core/{ => non-threads}/f32.wast (100%) rename test/core/{ => non-threads}/f32_bitwise.wast (100%) rename test/core/{ => non-threads}/f32_cmp.wast (100%) rename test/core/{ => non-threads}/f64.wast (100%) rename test/core/{ => non-threads}/f64_bitwise.wast (100%) rename test/core/{ => non-threads}/f64_cmp.wast (100%) rename test/core/{ => non-threads}/fac.wast (100%) rename test/core/{ => non-threads}/float_exprs.wast (100%) rename test/core/{ => non-threads}/float_literals.wast (100%) rename test/core/{ => non-threads}/float_memory.wast (100%) rename test/core/{ => non-threads}/float_misc.wast (100%) rename test/core/{ => non-threads}/forward.wast (100%) rename test/core/{ => non-threads}/func.wast (100%) rename test/core/{ => non-threads}/func_ptrs.wast (100%) rename test/core/{ => non-threads}/global.wast (100%) rename test/core/{ => non-threads}/i32.wast (100%) rename test/core/{ => non-threads}/i64.wast (100%) rename test/core/{ => non-threads}/if.wast (100%) rename test/core/{ => non-threads}/imports.wast (100%) rename test/core/{ => non-threads}/inline-module.wast (100%) rename test/core/{ => non-threads}/int_exprs.wast (100%) rename test/core/{ => non-threads}/int_literals.wast (100%) rename test/core/{ => non-threads}/labels.wast (100%) rename test/core/{ => non-threads}/left-to-right.wast (100%) rename test/core/{ => non-threads}/linking.wast (100%) rename test/core/{ => non-threads}/load.wast (100%) rename test/core/{ => non-threads}/local_get.wast (100%) rename test/core/{ => non-threads}/local_set.wast (100%) rename test/core/{ => non-threads}/local_tee.wast (100%) rename test/core/{ => non-threads}/loop.wast (100%) rename test/core/{ => non-threads}/memory.wast (100%) rename test/core/{ => non-threads}/memory_grow.wast (100%) rename test/core/{ => non-threads}/memory_redundancy.wast (100%) rename test/core/{ => non-threads}/memory_size.wast (100%) rename test/core/{ => non-threads}/memory_trap.wast (100%) rename test/core/{ => non-threads}/names.wast (100%) rename test/core/{ => non-threads}/nop.wast (100%) rename test/core/{ => non-threads}/return.wast (100%) rename test/core/{ => non-threads}/select.wast (100%) rename test/core/{ => non-threads}/skip-stack-guard-page.wast (100%) rename test/core/{ => non-threads}/stack.wast (100%) rename test/core/{ => non-threads}/start.wast (100%) rename test/core/{ => non-threads}/store.wast (100%) rename test/core/{ => non-threads}/switch.wast (100%) rename test/core/{ => non-threads}/table.wast (100%) rename test/core/{ => non-threads}/token.wast (100%) rename test/core/{ => non-threads}/traps.wast (100%) rename test/core/{ => non-threads}/type.wast (100%) rename test/core/{ => non-threads}/unreachable.wast (100%) rename test/core/{ => non-threads}/unreached-invalid.wast (100%) rename test/core/{ => non-threads}/unwind.wast (100%) rename test/core/{ => non-threads}/utf8-custom-section-id.wast (100%) rename test/core/{ => non-threads}/utf8-import-field.wast (100%) rename test/core/{ => non-threads}/utf8-import-module.wast (100%) rename test/core/{ => non-threads}/utf8-invalid-encoding.wast (100%) delete mode 100755 test/core/run.py create mode 100644 test/harness/async_worker.js create mode 100755 test/simple_cors_server.py diff --git a/interpreter/exec/eval.ml b/interpreter/exec/eval.ml index 55277d48..8e14f0e8 100644 --- a/interpreter/exec/eval.ml +++ b/interpreter/exec/eval.ml @@ -334,6 +334,7 @@ let rec step_thread (t : thread) : thread = | MemoryAtomicNotify {offset; ty; sz; _}, I32 count :: I32 i :: vs' -> let mem = memory frame.inst (0l @@ e.at) in let addr = I64_convert.extend_i32_u i in + (try check_align addr ty sz e.at; let _ = Memory.load_value mem addr offset ty in diff --git a/interpreter/script/js.ml b/interpreter/script/js.ml index 9bc2b3a3..7cd2a9e5 100644 --- a/interpreter/script/js.ml +++ b/interpreter/script/js.ml @@ -179,19 +179,19 @@ function wait(t) { module NameMap = Map.Make(struct type t = Ast.name let compare = compare end) module Map = Map.Make(String) -type 'a defs = {mutable env : 'a Map.t; mutable current : int} +type 'a defs = {mutable env : 'a Map.t; mutable current : int; prefix : string} type exports = extern_type NameMap.t -type context = {modules : exports defs; threads : unit defs} +type context = {modules : exports defs; threads : string defs} let exports m : exports = List.fold_left (fun map exp -> NameMap.add exp.it.name (export_type m exp) map) NameMap.empty m.it.exports -let defs () : 'a defs = {env = Map.empty; current = 0} -let context () : context = {modules = defs (); threads = defs ()} +let defs (pre : string) : 'a defs = {env = Map.empty; current = 0; prefix = pre} +let context () : context = {modules = defs "_M"; threads = defs "_T"} -let current_var (defs : 'a defs) = "$" ^ string_of_int defs.current +let current_var (defs : 'a defs) = "$" ^ defs.prefix ^ string_of_int defs.current let of_var_opt (defs : 'a defs) = function | None -> current_var defs | Some x -> x.it @@ -444,7 +444,7 @@ let of_assertion c ass = | AssertExhaustion (act, _) -> of_assertion' c act "assert_exhaustion" [] None -let rec of_command c cmd = +let rec of_command stem c cmd = "\n// " ^ Filename.basename cmd.at.left.file ^ ":" ^ string_of_int cmd.at.left.line ^ "\n" ^ match cmd.it with @@ -467,16 +467,27 @@ let rec of_command c cmd = | Assertion ass -> of_assertion c ass ^ "\n" | Thread (x_opt, xs, cmds) -> + if x_opt = None then failwith "NYI: JS printing can't handle anonymous thread commands"; + let worker_contents = String.concat "" (List.map (of_command stem c) cmds) in + bind c.threads x_opt worker_contents; + let fname = Filename.remove_extension(stem) ^ of_var_opt c.threads x_opt ^ Filename.extension(stem) in "let " ^ current_var c.threads ^ " = thread([" ^ - String.concat ", " (List.map (fun x -> "\"" ^ x.it ^ "\"") xs) ^ - "], function () {" ^ - String.concat "" (List.map (of_command c) cmds) ^ - "});\n" + String.concat ", " (List.map (fun x -> "[\"" ^ x.it ^ "\", " ^ x.it ^ "]") xs) ^ + "], " ^ "\"" ^ fname ^ "\"" ^ ");\n" ^ + (if x_opt = None then "" else + "let " ^ of_var_opt c.threads x_opt ^ + " = " ^ current_var c.threads ^ ";\n") | Wait x_opt -> + if x_opt = None then failwith "NYI: JS printing can't handle anonymous thread commands"; "wait(" ^ of_var_opt c.threads x_opt ^ ");\n" | Meta _ -> assert false -let of_script scr = - (if !Flags.harness then harness else "") ^ - String.concat "" (List.map (of_command (context ())) scr) +let of_script stem scr = + let ctx = context () in + let str = ((if !Flags.harness then harness else "") ^ + String.concat "" (List.map (of_command stem ctx) scr)) in + str, + (if not (Map.is_empty (ctx.threads.env)) && !Flags.harness then failwith "NYI: JS printing can't handle thread commands with synchronous harness"; + ctx.threads.env + ) diff --git a/interpreter/script/js.mli b/interpreter/script/js.mli index c60d3c50..bc53996a 100644 --- a/interpreter/script/js.mli +++ b/interpreter/script/js.mli @@ -1 +1 @@ -val of_script : Script.script -> string +val of_script : string -> Script.script -> string * (string Map.Make(String).t) diff --git a/interpreter/script/run.ml b/interpreter/script/run.ml index eb42d5d3..ca206cf5 100644 --- a/interpreter/script/run.ml +++ b/interpreter/script/run.ml @@ -12,8 +12,9 @@ exception Abort = Abort.Error exception Assert = Assert.Error exception IO = IO.Error -let trace name = if !Flags.trace then print_endline ("-- " ^ name) +module StringMap = Map.Make(String) +let trace name = if !Flags.trace then print_endline ("-- " ^ name) (* File types *) @@ -68,10 +69,19 @@ let create_script_file mode file get_script _ = let create_js_file file get_script _ = trace ("Converting (" ^ file ^ ")..."); - let js = Js.of_script (get_script ()) in + let (js,js_workers) = Js.of_script file (get_script ()) in + StringMap.iter + (fun key scr -> + let fname = Filename.remove_extension(file) ^ key ^ Filename.extension(file) in + let oc = open_out (fname) in + try + trace ("Writing (" ^ fname ^ ")..."); + output_string oc scr; + close_out oc + with exn -> close_out oc; raise exn) js_workers; let oc = open_out file in try - trace "Writing..."; + trace ("Writing (" ^ file ^ ")..."); output_string oc js; close_out oc with exn -> close_out oc; raise exn diff --git a/test/build.py b/test/build.py index bfb39727..3e3afcec 100755 --- a/test/build.py +++ b/test/build.py @@ -60,7 +60,7 @@ def ensure_wasm_executable(path_to_wasm): def convert_one_wast_file(inputs): wast_file, js_file = inputs print('Compiling {} to JS...'.format(wast_file)) - return run(WASM_EXEC, wast_file, '-h', '-o', js_file) + return run(WASM_EXEC, wast_file, '-h', '-d', '-o', js_file) def convert_wast_to_js(out_js_dir): """Compile all the wast files to JS and store the results in the JS dir.""" diff --git a/test/core/thread.wast b/test/core/MP.wast similarity index 55% rename from test/core/thread.wast rename to test/core/MP.wast index c3456a61..836ea5b6 100644 --- a/test/core/thread.wast +++ b/test/core/MP.wast @@ -8,6 +8,7 @@ (memory (import "mem" "shared") 1 10 shared) (func (export "run") (i32.store (i32.const 0) (i32.const 42)) + (i32.store (i32.const 4) (i32.const 1)) ) ) (invoke "run") @@ -18,10 +19,19 @@ (module (memory (import "mem" "shared") 1 1 shared) (func (export "run") (result i32) + (local i32 i32) + (i32.load (i32.const 4)) + (local.set 0) (i32.load (i32.const 0)) + (local.set 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 42)) (i32.eq (local.get 0) (i32.const 0))) + (i32.and) + (return) ) ) - (assert_return (invoke "run") (either (i32.const 0) (i32.const 42))) + + (assert_return (invoke "run") (i32.const 1)) ) (wait $T1) diff --git a/test/core/MP_atomic.wast b/test/core/MP_atomic.wast new file mode 100644 index 00000000..57a8ee88 --- /dev/null +++ b/test/core/MP_atomic.wast @@ -0,0 +1,40 @@ +(module $Mem + (memory (export "shared") 1 1 shared) +) + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 10 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") (result i32) + (local i32 i32) + (i32.atomic.load (i32.const 4)) + (local.set 0) + (i32.atomic.load (i32.const 0)) + (local.set 1) + (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 "run") (i32.const 1)) +) + +(wait $T1) +(wait $T2) diff --git a/test/core/README.md b/test/core/README.md deleted file mode 100644 index 31915146..00000000 --- a/test/core/README.md +++ /dev/null @@ -1,11 +0,0 @@ -This directory contains tests for the core WebAssembly semantics, as described in [Semantics.md](https://github.com/WebAssembly/design/blob/master/Semantics.md) and specified by the [spec interpreter](https://github.com/WebAssembly/spec/blob/master/interpreter). - -Tests are written in the [S-Expression script format](https://github.com/WebAssembly/spec/blob/master/interpreter/README.md#s-expression-syntax) defined by the interpreter. - -The test suite can be run with the spec interpreter as follows: -``` -./run.py --wasm -``` -where the path points to the spec interpreter executable (or a tool that understands similar options). If the binary is in the working directory, this option can be omitted. - -In addition, the option `--js ` can be given to point to a stand-alone JavaScript interpreter supporting the WebAssembly API. If provided, all tests are also executed in JavaScript. diff --git a/test/core/address.wast b/test/core/non-threads/address.wast similarity index 100% rename from test/core/address.wast rename to test/core/non-threads/address.wast diff --git a/test/core/align.wast b/test/core/non-threads/align.wast similarity index 100% rename from test/core/align.wast rename to test/core/non-threads/align.wast diff --git a/test/core/atomic.wast b/test/core/non-threads/atomic.wast similarity index 100% rename from test/core/atomic.wast rename to test/core/non-threads/atomic.wast diff --git a/test/core/binary-leb128.wast b/test/core/non-threads/binary-leb128.wast similarity index 100% rename from test/core/binary-leb128.wast rename to test/core/non-threads/binary-leb128.wast diff --git a/test/core/binary.wast b/test/core/non-threads/binary.wast similarity index 100% rename from test/core/binary.wast rename to test/core/non-threads/binary.wast diff --git a/test/core/block.wast b/test/core/non-threads/block.wast similarity index 100% rename from test/core/block.wast rename to test/core/non-threads/block.wast diff --git a/test/core/br.wast b/test/core/non-threads/br.wast similarity index 100% rename from test/core/br.wast rename to test/core/non-threads/br.wast diff --git a/test/core/br_if.wast b/test/core/non-threads/br_if.wast similarity index 100% rename from test/core/br_if.wast rename to test/core/non-threads/br_if.wast diff --git a/test/core/br_table.wast b/test/core/non-threads/br_table.wast similarity index 100% rename from test/core/br_table.wast rename to test/core/non-threads/br_table.wast diff --git a/test/core/call.wast b/test/core/non-threads/call.wast similarity index 100% rename from test/core/call.wast rename to test/core/non-threads/call.wast diff --git a/test/core/call_indirect.wast b/test/core/non-threads/call_indirect.wast similarity index 100% rename from test/core/call_indirect.wast rename to test/core/non-threads/call_indirect.wast diff --git a/test/core/comments.wast b/test/core/non-threads/comments.wast similarity index 100% rename from test/core/comments.wast rename to test/core/non-threads/comments.wast diff --git a/test/core/const.wast b/test/core/non-threads/const.wast similarity index 100% rename from test/core/const.wast rename to test/core/non-threads/const.wast diff --git a/test/core/conversions.wast b/test/core/non-threads/conversions.wast similarity index 100% rename from test/core/conversions.wast rename to test/core/non-threads/conversions.wast diff --git a/test/core/custom.wast b/test/core/non-threads/custom.wast similarity index 100% rename from test/core/custom.wast rename to test/core/non-threads/custom.wast diff --git a/test/core/data.wast b/test/core/non-threads/data.wast similarity index 100% rename from test/core/data.wast rename to test/core/non-threads/data.wast diff --git a/test/core/elem.wast b/test/core/non-threads/elem.wast similarity index 100% rename from test/core/elem.wast rename to test/core/non-threads/elem.wast diff --git a/test/core/endianness.wast b/test/core/non-threads/endianness.wast similarity index 100% rename from test/core/endianness.wast rename to test/core/non-threads/endianness.wast diff --git a/test/core/exports.wast b/test/core/non-threads/exports.wast similarity index 100% rename from test/core/exports.wast rename to test/core/non-threads/exports.wast diff --git a/test/core/f32.wast b/test/core/non-threads/f32.wast similarity index 100% rename from test/core/f32.wast rename to test/core/non-threads/f32.wast diff --git a/test/core/f32_bitwise.wast b/test/core/non-threads/f32_bitwise.wast similarity index 100% rename from test/core/f32_bitwise.wast rename to test/core/non-threads/f32_bitwise.wast diff --git a/test/core/f32_cmp.wast b/test/core/non-threads/f32_cmp.wast similarity index 100% rename from test/core/f32_cmp.wast rename to test/core/non-threads/f32_cmp.wast diff --git a/test/core/f64.wast b/test/core/non-threads/f64.wast similarity index 100% rename from test/core/f64.wast rename to test/core/non-threads/f64.wast diff --git a/test/core/f64_bitwise.wast b/test/core/non-threads/f64_bitwise.wast similarity index 100% rename from test/core/f64_bitwise.wast rename to test/core/non-threads/f64_bitwise.wast diff --git a/test/core/f64_cmp.wast b/test/core/non-threads/f64_cmp.wast similarity index 100% rename from test/core/f64_cmp.wast rename to test/core/non-threads/f64_cmp.wast diff --git a/test/core/fac.wast b/test/core/non-threads/fac.wast similarity index 100% rename from test/core/fac.wast rename to test/core/non-threads/fac.wast diff --git a/test/core/float_exprs.wast b/test/core/non-threads/float_exprs.wast similarity index 100% rename from test/core/float_exprs.wast rename to test/core/non-threads/float_exprs.wast diff --git a/test/core/float_literals.wast b/test/core/non-threads/float_literals.wast similarity index 100% rename from test/core/float_literals.wast rename to test/core/non-threads/float_literals.wast diff --git a/test/core/float_memory.wast b/test/core/non-threads/float_memory.wast similarity index 100% rename from test/core/float_memory.wast rename to test/core/non-threads/float_memory.wast diff --git a/test/core/float_misc.wast b/test/core/non-threads/float_misc.wast similarity index 100% rename from test/core/float_misc.wast rename to test/core/non-threads/float_misc.wast diff --git a/test/core/forward.wast b/test/core/non-threads/forward.wast similarity index 100% rename from test/core/forward.wast rename to test/core/non-threads/forward.wast diff --git a/test/core/func.wast b/test/core/non-threads/func.wast similarity index 100% rename from test/core/func.wast rename to test/core/non-threads/func.wast diff --git a/test/core/func_ptrs.wast b/test/core/non-threads/func_ptrs.wast similarity index 100% rename from test/core/func_ptrs.wast rename to test/core/non-threads/func_ptrs.wast diff --git a/test/core/global.wast b/test/core/non-threads/global.wast similarity index 100% rename from test/core/global.wast rename to test/core/non-threads/global.wast diff --git a/test/core/i32.wast b/test/core/non-threads/i32.wast similarity index 100% rename from test/core/i32.wast rename to test/core/non-threads/i32.wast diff --git a/test/core/i64.wast b/test/core/non-threads/i64.wast similarity index 100% rename from test/core/i64.wast rename to test/core/non-threads/i64.wast diff --git a/test/core/if.wast b/test/core/non-threads/if.wast similarity index 100% rename from test/core/if.wast rename to test/core/non-threads/if.wast diff --git a/test/core/imports.wast b/test/core/non-threads/imports.wast similarity index 100% rename from test/core/imports.wast rename to test/core/non-threads/imports.wast diff --git a/test/core/inline-module.wast b/test/core/non-threads/inline-module.wast similarity index 100% rename from test/core/inline-module.wast rename to test/core/non-threads/inline-module.wast diff --git a/test/core/int_exprs.wast b/test/core/non-threads/int_exprs.wast similarity index 100% rename from test/core/int_exprs.wast rename to test/core/non-threads/int_exprs.wast diff --git a/test/core/int_literals.wast b/test/core/non-threads/int_literals.wast similarity index 100% rename from test/core/int_literals.wast rename to test/core/non-threads/int_literals.wast diff --git a/test/core/labels.wast b/test/core/non-threads/labels.wast similarity index 100% rename from test/core/labels.wast rename to test/core/non-threads/labels.wast diff --git a/test/core/left-to-right.wast b/test/core/non-threads/left-to-right.wast similarity index 100% rename from test/core/left-to-right.wast rename to test/core/non-threads/left-to-right.wast diff --git a/test/core/linking.wast b/test/core/non-threads/linking.wast similarity index 100% rename from test/core/linking.wast rename to test/core/non-threads/linking.wast diff --git a/test/core/load.wast b/test/core/non-threads/load.wast similarity index 100% rename from test/core/load.wast rename to test/core/non-threads/load.wast diff --git a/test/core/local_get.wast b/test/core/non-threads/local_get.wast similarity index 100% rename from test/core/local_get.wast rename to test/core/non-threads/local_get.wast diff --git a/test/core/local_set.wast b/test/core/non-threads/local_set.wast similarity index 100% rename from test/core/local_set.wast rename to test/core/non-threads/local_set.wast diff --git a/test/core/local_tee.wast b/test/core/non-threads/local_tee.wast similarity index 100% rename from test/core/local_tee.wast rename to test/core/non-threads/local_tee.wast diff --git a/test/core/loop.wast b/test/core/non-threads/loop.wast similarity index 100% rename from test/core/loop.wast rename to test/core/non-threads/loop.wast diff --git a/test/core/memory.wast b/test/core/non-threads/memory.wast similarity index 100% rename from test/core/memory.wast rename to test/core/non-threads/memory.wast diff --git a/test/core/memory_grow.wast b/test/core/non-threads/memory_grow.wast similarity index 100% rename from test/core/memory_grow.wast rename to test/core/non-threads/memory_grow.wast diff --git a/test/core/memory_redundancy.wast b/test/core/non-threads/memory_redundancy.wast similarity index 100% rename from test/core/memory_redundancy.wast rename to test/core/non-threads/memory_redundancy.wast diff --git a/test/core/memory_size.wast b/test/core/non-threads/memory_size.wast similarity index 100% rename from test/core/memory_size.wast rename to test/core/non-threads/memory_size.wast diff --git a/test/core/memory_trap.wast b/test/core/non-threads/memory_trap.wast similarity index 100% rename from test/core/memory_trap.wast rename to test/core/non-threads/memory_trap.wast diff --git a/test/core/names.wast b/test/core/non-threads/names.wast similarity index 100% rename from test/core/names.wast rename to test/core/non-threads/names.wast diff --git a/test/core/nop.wast b/test/core/non-threads/nop.wast similarity index 100% rename from test/core/nop.wast rename to test/core/non-threads/nop.wast diff --git a/test/core/return.wast b/test/core/non-threads/return.wast similarity index 100% rename from test/core/return.wast rename to test/core/non-threads/return.wast diff --git a/test/core/select.wast b/test/core/non-threads/select.wast similarity index 100% rename from test/core/select.wast rename to test/core/non-threads/select.wast diff --git a/test/core/skip-stack-guard-page.wast b/test/core/non-threads/skip-stack-guard-page.wast similarity index 100% rename from test/core/skip-stack-guard-page.wast rename to test/core/non-threads/skip-stack-guard-page.wast diff --git a/test/core/stack.wast b/test/core/non-threads/stack.wast similarity index 100% rename from test/core/stack.wast rename to test/core/non-threads/stack.wast diff --git a/test/core/start.wast b/test/core/non-threads/start.wast similarity index 100% rename from test/core/start.wast rename to test/core/non-threads/start.wast diff --git a/test/core/store.wast b/test/core/non-threads/store.wast similarity index 100% rename from test/core/store.wast rename to test/core/non-threads/store.wast diff --git a/test/core/switch.wast b/test/core/non-threads/switch.wast similarity index 100% rename from test/core/switch.wast rename to test/core/non-threads/switch.wast diff --git a/test/core/table.wast b/test/core/non-threads/table.wast similarity index 100% rename from test/core/table.wast rename to test/core/non-threads/table.wast diff --git a/test/core/token.wast b/test/core/non-threads/token.wast similarity index 100% rename from test/core/token.wast rename to test/core/non-threads/token.wast diff --git a/test/core/traps.wast b/test/core/non-threads/traps.wast similarity index 100% rename from test/core/traps.wast rename to test/core/non-threads/traps.wast diff --git a/test/core/type.wast b/test/core/non-threads/type.wast similarity index 100% rename from test/core/type.wast rename to test/core/non-threads/type.wast diff --git a/test/core/unreachable.wast b/test/core/non-threads/unreachable.wast similarity index 100% rename from test/core/unreachable.wast rename to test/core/non-threads/unreachable.wast diff --git a/test/core/unreached-invalid.wast b/test/core/non-threads/unreached-invalid.wast similarity index 100% rename from test/core/unreached-invalid.wast rename to test/core/non-threads/unreached-invalid.wast diff --git a/test/core/unwind.wast b/test/core/non-threads/unwind.wast similarity index 100% rename from test/core/unwind.wast rename to test/core/non-threads/unwind.wast diff --git a/test/core/utf8-custom-section-id.wast b/test/core/non-threads/utf8-custom-section-id.wast similarity index 100% rename from test/core/utf8-custom-section-id.wast rename to test/core/non-threads/utf8-custom-section-id.wast diff --git a/test/core/utf8-import-field.wast b/test/core/non-threads/utf8-import-field.wast similarity index 100% rename from test/core/utf8-import-field.wast rename to test/core/non-threads/utf8-import-field.wast diff --git a/test/core/utf8-import-module.wast b/test/core/non-threads/utf8-import-module.wast similarity index 100% rename from test/core/utf8-import-module.wast rename to test/core/non-threads/utf8-import-module.wast diff --git a/test/core/utf8-invalid-encoding.wast b/test/core/non-threads/utf8-invalid-encoding.wast similarity index 100% rename from test/core/utf8-invalid-encoding.wast rename to test/core/non-threads/utf8-invalid-encoding.wast diff --git a/test/core/run.py b/test/core/run.py deleted file mode 100755 index ec07929c..00000000 --- a/test/core/run.py +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env python3 - -from __future__ import print_function -import argparse -import os -import os.path -import unittest -import subprocess -import glob -import sys - - -ownDir = os.path.dirname(os.path.abspath(sys.argv[0])) -inputDir = ownDir -outputDir = os.path.join(inputDir, "_output") - -parser = argparse.ArgumentParser() -parser.add_argument("--wasm", metavar="", default=os.path.join(os.getcwd(), "wasm")) -parser.add_argument("--js", metavar="") -parser.add_argument("--out", metavar="", default=outputDir) -parser.add_argument("file", nargs='*') -arguments = parser.parse_args() -sys.argv = sys.argv[:1] - -wasmCommand = arguments.wasm -jsCommand = arguments.js -outputDir = arguments.out -inputFiles = arguments.file if arguments.file else glob.glob(os.path.join(inputDir, "*.wast")) - -if not os.path.exists(wasmCommand): - sys.stderr.write("""\ -Error: The executable '%s' does not exist. -Provide the correct path with the '--wasm' flag. - -""" % (wasmCommand)) - parser.print_help() - sys.exit(1) - - -class RunTests(unittest.TestCase): - def _runCommand(self, command, logPath, expectedExitCode = 0): - with open(logPath, 'w+') as out: - exitCode = subprocess.call(command, shell=True, stdout=out, stderr=subprocess.STDOUT) - self.assertEqual(expectedExitCode, exitCode, "failed with exit code %i (expected %i) for %s" % (exitCode, expectedExitCode, command)) - - def _auxFile(self, path): - if os.path.exists(path): - os.remove(path) - return path - - def _compareFile(self, expectFile, actualFile): - if os.path.exists(expectFile): - with open(expectFile) as expect: - with open(actualFile) as actual: - expectText = expect.read() - actualText = actual.read() - self.assertEqual(expectText, actualText) - - def _runTestFile(self, inputPath): - dir, inputFile = os.path.split(inputPath) - outputPath = os.path.join(outputDir, inputFile) - - # Run original file - expectedExitCode = 1 if ".fail." in inputFile else 0 - logPath = self._auxFile(outputPath + ".log") - self._runCommand(('%s "%s"') % (wasmCommand, inputPath), logPath, expectedExitCode) - - if expectedExitCode != 0: - return - - # Convert to binary and run again - wasmPath = self._auxFile(outputPath + ".bin.wast") - logPath = self._auxFile(wasmPath + ".log") - self._runCommand(('%s -d "%s" -o "%s"') % (wasmCommand, inputPath, wasmPath), logPath) - self._runCommand(('%s "%s"') % (wasmCommand, wasmPath), logPath) - - # Convert back to text and run again - wastPath = self._auxFile(wasmPath + ".wast") - logPath = self._auxFile(wastPath + ".log") - self._runCommand(('%s -d "%s" -o "%s"') % (wasmCommand, wasmPath, wastPath), logPath) - self._runCommand(('%s "%s"') % (wasmCommand, wastPath), logPath) - - # Convert back to binary once more and compare - wasm2Path = self._auxFile(wastPath + ".bin.wast") - logPath = self._auxFile(wasm2Path + ".log") - self._runCommand(('%s -d "%s" -o "%s"') % (wasmCommand, wastPath, wasm2Path), logPath) - self._compareFile(wasmPath, wasm2Path) - - # Convert back to text once more and compare - wast2Path = self._auxFile(wasm2Path + ".wast") - logPath = self._auxFile(wast2Path + ".log") - self._runCommand(('%s -d "%s" -o "%s"') % (wasmCommand, wasm2Path, wast2Path), logPath) - self._compareFile(wastPath, wast2Path) - - # Convert to JavaScript - jsPath = self._auxFile(outputPath.replace(".wast", ".js")) - logPath = self._auxFile(jsPath + ".log") - self._runCommand(('%s -d "%s" -o "%s"') % (wasmCommand, inputPath, jsPath), logPath) - if jsCommand != None: - self._runCommand(('%s "%s"') % (jsCommand, jsPath), logPath) - - -if __name__ == "__main__": - if not os.path.exists(outputDir): - os.makedirs(outputDir) - for fileName in inputFiles: - testName = 'test ' + os.path.basename(fileName) - setattr(RunTests, testName, lambda self, file=fileName: self._runTestFile(file)) - unittest.main() diff --git a/test/harness/async_index.js b/test/harness/async_index.js index 038d6859..c4e84b10 100644 --- a/test/harness/async_index.js +++ b/test/harness/async_index.js @@ -55,6 +55,9 @@ const EXPECT_INVALID = false; // Default imports. var registry = {}; +// Web worker array. +var worker_arr = []; + // All tests run asynchronously and return their results as promises. To ensure // that all tests execute in the correct order, we chain the promises together // so that a test is only executed when all previous tests have finished their @@ -89,6 +92,9 @@ function reinitializeRegistry() { } }; registry = new Proxy({ spectest }, handler); + + worker_arr.map(w => { w[0].onmessage = (_ => {}); w[0].terminate(); }); + worker_arr = []; }); // This function is called at the end of every generated js test file. By @@ -252,7 +258,6 @@ function assert_return(action, ...expected) { if (actual.length !== expected.length) { throw new Error(expected.length + " value(s) expected, got " + actual.length); } - for (let i = 0; i < actual.length; ++i) { assert_equals(actual[i], expected[i], loc); } @@ -385,4 +390,82 @@ function get(instance, name) { ); return chain; } +/* +//function worker_source(filename) { +// return 'function assert_true(actual, description) { assert(actual === true, "assert_true", description, "expected true got ${actual}",{actual:actual});} self.onmessage = function(e) { importScripts(e.data.url); close(); }' +} + +function blob(filename) { + return new Blob([worker_source(filename)], {type: "application/javascript"}); +} */ + +function thread(parent_scope, filename) { + chain = chain.then(_ => { + return Promise.all( + // parent_scope is a list with each element [name, Promise(instance)] + // For each element, create a promise cloning only the shared memories of the instance: + parent_scope.map(element => { + return element[1].then( instance => { + let x = {exports: {}}; + Object.keys(instance.exports).forEach(k => { + if (instance.exports[k].buffer instanceof SharedArrayBuffer) { x.exports[k] = instance.exports[k]}; + }); + return [element[0], x]; + }); + }) + ).then( scope => { + const worker = new Worker("./js/harness/async_worker.js"); + let worker_ind = worker_arr.length; + worker_arr.push([worker,false]); + worker.onmessage = (e => { + if(e.data.type === "done") { worker_arr[worker_ind] = [worker,true,true]; } + if(e.data.type === "fail") { worker_arr[worker_ind] = [worker,true,false]; uniqueTest(_ => { assert_true(false, e.data.loc); }, (filename + ": " + e.data.name)); } + }); + worker.postMessage({scope: scope, filename: "../../../"+filename}); + return worker_ind; +/* + return new Promise( accept => { + // we now have a version of parent_scope with only the shared parts + const worker = new Worker("./js/harness/async_worker.js"); + let worker_ind = worker_arr.length; + worker_arr.push([worker,false]); + + worker.onmessage = (e => { + if(e.data.type === "done") { accept(); } + if(e.data.type === "fail") { uniqueTest(_ => { assert_true(false, e.data.loc); }, (filename + ": " + e.data.name)); accept(); } + }); + worker.postMessage({scope: scope, filename: "../../../"+filename}); + }) */ + }, _ => { console.log("unreachable"); }); + }, _ => { console.log("unreachable"); } ); + return chain; +} +function wait(worker_ind_p) { + const test = "Test that the result of a thread execution can be waited on"; + const loc = new Error().stack.toString().replace("Error", ""); + chain = Promise.all([worker_ind_p, chain]).then( + values => { + let worker_ind = values[0]; + return new Promise(accept => { + if ((worker_arr[worker_ind])[1] === true) { + if ((worker_arr[worker_ind])[2] === true) { + accept(); + } else { + uniqueTest(_ => { assert_true(false, loc); }, test); + accept(); + } + } else { + let old_handler = worker_arr[worker_ind][0].onmessage; + worker_arr[worker_ind][0].onmessage = (e => { + if(e.data.type === "done") { worker_arr[worker_ind] = [worker_arr[worker_ind][0],true,true]; accept(); } + if(e.data.type === "fail") { old_handler(e); accept(); } + }); + } + }, _ => { uniqueTest(_ => { assert_true(false, loc); }, test); + }); + }, + _ => { console.log("unreachable"); } + ); + return chain; +} diff --git a/test/harness/async_worker.js b/test/harness/async_worker.js new file mode 100644 index 00000000..54756eca --- /dev/null +++ b/test/harness/async_worker.js @@ -0,0 +1,68 @@ +function assert(expected_true, function_name, description, error, substitutions) { + if (expected_true !== true) { + console.log(substitutions); + throw new Error("assert failure in worker " + description); + } +} + +function promise_test(){} + +function test(func, name){ + try{ + func(); + console.log("worker - test passed"); + } catch(e) { + console.log("worker - test failed:"); + const loc = e.stack.toString().replace("Error", ""); + self.postMessage({type: "fail", name: name, loc: loc}); + } +} + +function test_num(){ return 0 } + +function assert_true(actual, description) { + assert(actual === true, "assert_true", description, + "expected true got ${actual}", {actual:actual}); +} + +function assert_false(actual, description) { + assert(actual === false, "assert_false", description, + "expected false got ${actual}", {actual:actual}); +} + +function same_value(x, y) { +if (y !== y) { + //NaN case + return x !== x; +} +if (x === 0 && y === 0) { + //Distinguish +0 and -0 + return 1/x === 1/y; +} +return x === y; +} + +function assert_equals(actual, expected, description) { + /* + * Test if two primitives are equal or two objects + * are the same object + */ + if (typeof actual != typeof expected) { + assert(false, "assert_equals", description, + "expected (" + typeof expected + ") ${expected} but got (" + typeof actual + ") ${actual}", + {expected:expected, actual:actual}); + return; + } + assert(same_value(actual, expected), "assert_equals", description, + "expected ${expected} but got ${actual}", + {expected:expected, actual:actual}); +} + + +importScripts("async_index.js"); + +self.onmessage = function(e) { + e.data.scope.forEach(element => self[element[0]] = Promise.resolve(element[1])); + importScripts(e.data.filename); + chain.then(e => {console.log("posting done"); self.postMessage({type: "done"});}); +}; diff --git a/test/harness/sync_index.js b/test/harness/sync_index.js index fd4e7232..e75ad685 100644 --- a/test/harness/sync_index.js +++ b/test/harness/sync_index.js @@ -347,3 +347,11 @@ function assert_return_nan(action) { }; }, "A wast module that must return NaN."); } + +function thread() { + throw new Error("NYI: concurrent tests not supported in synchronous mode"); +} + +function wait() { + throw new Error("NYI: concurrent tests not supported in synchronous mode"); +} diff --git a/test/simple_cors_server.py b/test/simple_cors_server.py new file mode 100755 index 00000000..16b7e401 --- /dev/null +++ b/test/simple_cors_server.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# encoding: utf-8 +"""Use instead of `python3 -m http.server` when you need CORS""" + +from http.server import HTTPServer, SimpleHTTPRequestHandler + + +class CORSRequestHandler(SimpleHTTPRequestHandler): + def end_headers(self): + self.send_header('Cross-Origin-Opener-Policy', 'same-origin') + self.send_header('Cross-Origin-Embedder-Policy', 'require-corp') + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET') + self.send_header('Cache-Control', 'no-store, no-cache, must-revalidate') + return super(CORSRequestHandler, self).end_headers() + + +httpd = HTTPServer(('localhost', 8000), CORSRequestHandler) +httpd.serve_forever() From 2251009df0ada093deb3f924b1aa95a2d55bf7b1 Mon Sep 17 00:00:00 2001 From: conrad Date: Tue, 18 Oct 2022 22:51:28 +0100 Subject: [PATCH 5/5] more --- test/core/LB.wast | 58 ++++++++++++++++++++++++ test/core/LB_atomic.wast | 60 +++++++++++++++++++++++++ test/core/MP.wast | 29 +++++++++--- test/core/MP_atomic.wast | 33 ++++++++++---- test/core/MP_wait.wast | 57 +++++++++++++++++++++++ test/core/SB.wast | 58 ++++++++++++++++++++++++ test/core/SB_atomic.wast | 60 +++++++++++++++++++++++++ test/core/{non-threads => }/atomic.wast | 0 8 files changed, 341 insertions(+), 14 deletions(-) create mode 100644 test/core/LB.wast create mode 100644 test/core/LB_atomic.wast create mode 100644 test/core/MP_wait.wast create mode 100644 test/core/SB.wast create mode 100644 test/core/SB_atomic.wast rename test/core/{non-threads => }/atomic.wast (100%) diff --git a/test/core/LB.wast b/test/core/LB.wast new file mode 100644 index 00000000..85323247 --- /dev/null +++ b/test/core/LB.wast @@ -0,0 +1,58 @@ +(module $Mem + (memory (export "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 0) (i32.const 0))) + (i32.and) + (return) + ) +) + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 10 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) + +(assert_return (invoke $Mem "check") (i32.const 1)) diff --git a/test/core/LB_atomic.wast b/test/core/LB_atomic.wast new file mode 100644 index 00000000..10927f03 --- /dev/null +++ b/test/core/LB_atomic.wast @@ -0,0 +1,60 @@ +(module $Mem + (memory (export "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) + ) +) + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 10 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) + +(assert_return (invoke $Mem "check") (i32.const 1)) diff --git a/test/core/MP.wast b/test/core/MP.wast index 836ea5b6..76df6905 100644 --- a/test/core/MP.wast +++ b/test/core/MP.wast @@ -1,5 +1,20 @@ (module $Mem (memory (export "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 0) (i32.const 0))) + (i32.and) + (return) + ) ) (thread $T1 (shared (module $Mem)) @@ -18,21 +33,23 @@ (register "mem" $Mem) (module (memory (import "mem" "shared") 1 1 shared) - (func (export "run") (result i32) + (func (export "run") (local i32 i32) (i32.load (i32.const 4)) (local.set 0) (i32.load (i32.const 0)) (local.set 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 42)) (i32.eq (local.get 0) (i32.const 0))) - (i32.and) - (return) + + ;; store results for checking + (i32.store (i32.const 24) (local.get 0)) + (i32.store (i32.const 32) (local.get 1)) ) ) - (assert_return (invoke "run") (i32.const 1)) + (invoke "run") ) (wait $T1) (wait $T2) + +(assert_return (invoke $Mem "check") (i32.const 1)) diff --git a/test/core/MP_atomic.wast b/test/core/MP_atomic.wast index 57a8ee88..7369a266 100644 --- a/test/core/MP_atomic.wast +++ b/test/core/MP_atomic.wast @@ -1,5 +1,22 @@ (module $Mem (memory (export "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) + ) ) (thread $T1 (shared (module $Mem)) @@ -18,23 +35,23 @@ (register "mem" $Mem) (module (memory (import "mem" "shared") 1 1 shared) - (func (export "run") (result i32) + (func (export "run") (local i32 i32) (i32.atomic.load (i32.const 4)) (local.set 0) (i32.atomic.load (i32.const 0)) (local.set 1) - (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) + + ;; store results for checking + (i32.store (i32.const 24) (local.get 0)) + (i32.store (i32.const 32) (local.get 1)) ) ) - (assert_return (invoke "run") (i32.const 1)) + (invoke "run") ) (wait $T1) (wait $T2) + +(assert_return (invoke $Mem "check") (i32.const 1)) diff --git a/test/core/MP_wait.wast b/test/core/MP_wait.wast new file mode 100644 index 00000000..7369a266 --- /dev/null +++ b/test/core/MP_wait.wast @@ -0,0 +1,57 @@ +(module $Mem + (memory (export "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) + ) +) + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 10 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) + +(assert_return (invoke $Mem "check") (i32.const 1)) diff --git a/test/core/SB.wast b/test/core/SB.wast new file mode 100644 index 00000000..d0402fcf --- /dev/null +++ b/test/core/SB.wast @@ -0,0 +1,58 @@ +(module $Mem + (memory (export "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 0) (i32.const 0))) + (i32.and) + (return) + ) +) + +(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) + + ;; 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) + +(assert_return (invoke $Mem "check") (i32.const 1)) diff --git a/test/core/SB_atomic.wast b/test/core/SB_atomic.wast new file mode 100644 index 00000000..37682c74 --- /dev/null +++ b/test/core/SB_atomic.wast @@ -0,0 +1,60 @@ +(module $Mem + (memory (export "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) + ) +) + +(thread $T1 (shared (module $Mem)) + (register "mem" $Mem) + (module + (memory (import "mem" "shared") 1 10 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) + +(assert_return (invoke $Mem "check") (i32.const 1)) diff --git a/test/core/non-threads/atomic.wast b/test/core/atomic.wast similarity index 100% rename from test/core/non-threads/atomic.wast rename to test/core/atomic.wast