feat: implement memory.atomic.notify,wait32,wait64 (#5255)
* feat: implement memory.atomic.notify,wait32,wait64 Added the parking_spot crate, which provides the needed registry for the operations. Signed-off-by: Harald Hoyer <harald@profian.com> * fix: change trap message for HeapMisaligned The threads spec test wants "unaligned atomic" instead of "misaligned memory access". Signed-off-by: Harald Hoyer <harald@profian.com> * tests: add test for atomic wait on non-shared memory Signed-off-by: Harald Hoyer <harald@profian.com> * tests: add tests/spec_testsuite/proposals/threads without pooling and reference types. Also "shared_memory" is added to the "spectest" interface. Signed-off-by: Harald Hoyer <harald@profian.com> * tests: add atomics_notify.wast checking that notify with 0 waiters returns 0 on shared and non-shared memory. Signed-off-by: Harald Hoyer <harald@profian.com> * tests: add tests for atomic wait on shared memory - return 2 - timeout for 0 - return 2 - timeout for 1000ns - return 1 - invalid value Signed-off-by: Harald Hoyer <harald@profian.com> * fixup! feat: implement memory.atomic.notify,wait32,wait64 Signed-off-by: Harald Hoyer <harald@profian.com> * fixup! feat: implement memory.atomic.notify,wait32,wait64 Signed-off-by: Harald Hoyer <harald@profian.com> Signed-off-by: Harald Hoyer <harald@profian.com>
This commit is contained in:
@@ -31,6 +31,7 @@ mod store;
|
||||
mod table;
|
||||
mod threads;
|
||||
mod traps;
|
||||
mod wait_notify;
|
||||
mod wast;
|
||||
|
||||
/// A helper to compile a module in a new store with reference types enabled.
|
||||
|
||||
120
tests/all/wait_notify.rs
Normal file
120
tests/all/wait_notify.rs
Normal file
@@ -0,0 +1,120 @@
|
||||
use anyhow::Result;
|
||||
use std::time::Instant;
|
||||
use wasmtime::*;
|
||||
|
||||
#[test]
|
||||
fn atomic_wait_timeout_length() -> Result<()> {
|
||||
let sleep_nanoseconds = 500000000;
|
||||
let wat = format!(
|
||||
r#"(module
|
||||
(import "env" "memory" (memory 1 1 shared))
|
||||
|
||||
(func (export "func1") (result i32)
|
||||
(memory.atomic.wait32 (i32.const 0) (i32.const 0) (i64.const {sleep_nanoseconds}))
|
||||
)
|
||||
|
||||
(data (i32.const 0) "\00\00\00\00")
|
||||
)"#
|
||||
);
|
||||
let mut config = Config::new();
|
||||
config.wasm_threads(true);
|
||||
let engine = Engine::new(&config)?;
|
||||
let module = Module::new(&engine, wat)?;
|
||||
let mut store = Store::new(&engine, ());
|
||||
let shared_memory = SharedMemory::new(&engine, MemoryType::shared(1, 1))?;
|
||||
let instance = Instance::new(&mut store, &module, &[shared_memory.clone().into()])?;
|
||||
let now = Instant::now();
|
||||
let func_ret = instance
|
||||
.get_typed_func::<(), i32>(&mut store, "func1")
|
||||
.unwrap()
|
||||
.call(&mut store, ())
|
||||
.unwrap();
|
||||
let duration = now.elapsed();
|
||||
assert!(
|
||||
duration.as_nanos() >= sleep_nanoseconds,
|
||||
"duration: {duration:?} < {sleep_nanoseconds:?}"
|
||||
);
|
||||
assert_eq!(func_ret, 2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn atomic_wait_notify_basic() -> Result<()> {
|
||||
let wat = r#"(module
|
||||
(import "env" "memory" (memory 1 1 shared))
|
||||
|
||||
(func (export "first_thread") (result i32)
|
||||
(drop (memory.atomic.wait32 (i32.const 4) (i32.const 0) (i64.const -1)))
|
||||
(i32.atomic.store (i32.const 0) (i32.const 42))
|
||||
(drop (memory.atomic.notify (i32.const 0) (i32.const -1)))
|
||||
(i32.atomic.load (i32.const 0))
|
||||
)
|
||||
|
||||
(func (export "second_thread") (result i32)
|
||||
(i32.atomic.store (i32.const 4) (i32.const 21))
|
||||
(drop (memory.atomic.notify (i32.const 4) (i32.const -1)))
|
||||
(drop (memory.atomic.wait32 (i32.const 0) (i32.const 0) (i64.const -1)))
|
||||
(i32.atomic.load (i32.const 0))
|
||||
)
|
||||
|
||||
(data (i32.const 0) "\00\00\00\00")
|
||||
(data (i32.const 4) "\00\00\00\00")
|
||||
)"#;
|
||||
let mut config = Config::new();
|
||||
config.wasm_threads(true);
|
||||
let engine = Engine::new(&config)?;
|
||||
let module = Module::new(&engine, wat)?;
|
||||
let mut store = Store::new(&engine, ());
|
||||
let shared_memory = SharedMemory::new(&engine, MemoryType::shared(1, 1))?;
|
||||
let instance1 = Instance::new(&mut store, &module, &[shared_memory.clone().into()])?;
|
||||
|
||||
let thread = {
|
||||
let engine = engine.clone();
|
||||
let module = module.clone();
|
||||
let shared_memory = shared_memory.clone();
|
||||
std::thread::spawn(move || {
|
||||
let mut store = Store::new(&engine, ());
|
||||
let instance2 = Instance::new(&mut store, &module, &[shared_memory.into()]).unwrap();
|
||||
|
||||
let instance2_first_word = instance2
|
||||
.get_typed_func::<(), i32>(&mut store, "second_thread")
|
||||
.unwrap()
|
||||
.call(&mut store, ())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(instance2_first_word, 42);
|
||||
})
|
||||
};
|
||||
|
||||
let instance1_first_word = instance1
|
||||
.get_typed_func::<(), i32>(&mut store, "first_thread")
|
||||
.unwrap()
|
||||
.call(&mut store, ())
|
||||
.unwrap();
|
||||
assert_eq!(instance1_first_word, 42);
|
||||
|
||||
thread.join().unwrap();
|
||||
|
||||
let data = shared_memory.data();
|
||||
// Verify that the memory is the same in all shared locations.
|
||||
let shared_memory_first_word = i32::from_le_bytes(unsafe {
|
||||
[
|
||||
*data[0].get(),
|
||||
*data[1].get(),
|
||||
*data[2].get(),
|
||||
*data[3].get(),
|
||||
]
|
||||
});
|
||||
assert_eq!(shared_memory_first_word, 42);
|
||||
|
||||
let shared_memory_second_word = i32::from_le_bytes(unsafe {
|
||||
[
|
||||
*data[4].get(),
|
||||
*data[5].get(),
|
||||
*data[6].get(),
|
||||
*data[7].get(),
|
||||
]
|
||||
});
|
||||
assert_eq!(shared_memory_second_word, 21);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
use anyhow::Context;
|
||||
use bstr::ByteSlice;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::path::Path;
|
||||
use std::sync::{Condvar, Mutex};
|
||||
use wasmtime::{
|
||||
Config, Engine, InstanceAllocationStrategy, PoolingAllocationConfig, Store, Strategy,
|
||||
};
|
||||
use wasmtime_environ::WASM_PAGE_SIZE;
|
||||
use wasmtime_wast::WastContext;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
|
||||
@@ -14,20 +17,32 @@ include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
|
||||
fn run_wast(wast: &str, strategy: Strategy, pooling: bool) -> anyhow::Result<()> {
|
||||
drop(env_logger::try_init());
|
||||
|
||||
let wast_bytes = std::fs::read(wast).with_context(|| format!("failed to read `{}`", wast))?;
|
||||
|
||||
match strategy {
|
||||
Strategy::Cranelift => {}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
|
||||
let wast = Path::new(wast);
|
||||
|
||||
let memory64 = feature_found(wast, "memory64");
|
||||
let multi_memory = feature_found(wast, "multi-memory");
|
||||
let threads = feature_found(wast, "threads");
|
||||
let reference_types = !(threads && feature_found(wast, "proposals"));
|
||||
let use_shared_memory = feature_found_src(&wast_bytes, "shared_memory")
|
||||
|| feature_found_src(&wast_bytes, "shared)");
|
||||
|
||||
if pooling && use_shared_memory {
|
||||
eprintln!("skipping pooling test with shared memory");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut cfg = Config::new();
|
||||
cfg.wasm_multi_memory(multi_memory)
|
||||
.wasm_threads(threads)
|
||||
.wasm_memory64(memory64)
|
||||
.wasm_reference_types(reference_types)
|
||||
.cranelift_debug_verifier(true);
|
||||
|
||||
cfg.wasm_component_model(feature_found(wast, "component-model"));
|
||||
@@ -62,7 +77,11 @@ fn run_wast(wast: &str, strategy: Strategy, pooling: bool) -> anyhow::Result<()>
|
||||
// Don't use 4gb address space reservations when not hogging memory, and
|
||||
// also don't reserve lots of memory after dynamic memories for growth
|
||||
// (makes growth slower).
|
||||
cfg.static_memory_maximum_size(0);
|
||||
if use_shared_memory {
|
||||
cfg.static_memory_maximum_size(2 * WASM_PAGE_SIZE as u64);
|
||||
} else {
|
||||
cfg.static_memory_maximum_size(0);
|
||||
}
|
||||
cfg.dynamic_memory_reserved_for_growth(0);
|
||||
}
|
||||
|
||||
@@ -91,8 +110,10 @@ fn run_wast(wast: &str, strategy: Strategy, pooling: bool) -> anyhow::Result<()>
|
||||
|
||||
let store = Store::new(&Engine::new(&cfg)?, ());
|
||||
let mut wast_context = WastContext::new(store);
|
||||
wast_context.register_spectest()?;
|
||||
wast_context.run_file(wast)?;
|
||||
|
||||
wast_context.register_spectest(use_shared_memory)?;
|
||||
wast_context.run_buffer(wast.to_str().unwrap(), &wast_bytes)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -103,6 +124,10 @@ fn feature_found(path: &Path, name: &str) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
fn feature_found_src(bytes: &[u8], name: &str) -> bool {
|
||||
bytes.contains_str(name)
|
||||
}
|
||||
|
||||
// The pooling tests make about 6TB of address space reservation which means
|
||||
// that we shouldn't let too many of them run concurrently at once. On
|
||||
// high-cpu-count systems (e.g. 80 threads) this leads to mmap failures because
|
||||
|
||||
18
tests/misc_testsuite/threads/atomics_notify.wast
Normal file
18
tests/misc_testsuite/threads/atomics_notify.wast
Normal file
@@ -0,0 +1,18 @@
|
||||
;; From https://github.com/bytecodealliance/wasmtime/pull/5255
|
||||
;;
|
||||
|
||||
(module
|
||||
(memory 1 1)
|
||||
(func (export "notify") (result i32) (memory.atomic.notify (i32.const 0) (i32.const -1)))
|
||||
)
|
||||
|
||||
;; notify returns 0 on unshared memories
|
||||
(assert_return (invoke "notify") (i32.const 0))
|
||||
|
||||
(module
|
||||
(memory 1 1 shared)
|
||||
(func (export "notify_shared") (result i32) (memory.atomic.notify (i32.const 0) (i32.const -1)))
|
||||
)
|
||||
|
||||
;; notify returns 0 with 0 waiters
|
||||
(assert_return (invoke "notify_shared") (i32.const 0))
|
||||
@@ -12,7 +12,7 @@
|
||||
(export "main" (func $main))
|
||||
)
|
||||
|
||||
(assert_trap (invoke "main") "misaligned memory access")
|
||||
(assert_trap (invoke "main") "unaligned atomic")
|
||||
|
||||
|
||||
(module
|
||||
@@ -48,5 +48,96 @@
|
||||
(export "wait64" (func $wait64))
|
||||
)
|
||||
|
||||
(assert_trap (invoke "wait32") "misaligned memory access")
|
||||
(assert_trap (invoke "wait64") "misaligned memory access")
|
||||
(assert_trap (invoke "wait32") "unaligned atomic")
|
||||
(assert_trap (invoke "wait64") "unaligned atomic")
|
||||
|
||||
(module
|
||||
(type (;0;) (func))
|
||||
(func $wait32 (type 0)
|
||||
i32.const 0
|
||||
i32.const 42
|
||||
i64.const 0
|
||||
memory.atomic.wait32
|
||||
unreachable)
|
||||
(func $wait64 (type 0)
|
||||
i32.const 0
|
||||
i64.const 43
|
||||
i64.const 0
|
||||
memory.atomic.wait64
|
||||
unreachable)
|
||||
(memory (;0;) 4 4)
|
||||
(export "wait32" (func $wait32))
|
||||
(export "wait64" (func $wait64))
|
||||
)
|
||||
|
||||
(assert_trap (invoke "wait32") "atomic wait on non-shared memory")
|
||||
(assert_trap (invoke "wait64") "atomic wait on non-shared memory")
|
||||
|
||||
;; not valid values for memory.atomic.wait
|
||||
(module
|
||||
(memory 1 1 shared)
|
||||
(type (;0;) (func))
|
||||
(func $wait32 (result i32)
|
||||
i32.const 0
|
||||
i32.const 42
|
||||
i64.const -1
|
||||
memory.atomic.wait32
|
||||
)
|
||||
(func $wait64 (result i32)
|
||||
i32.const 0
|
||||
i64.const 43
|
||||
i64.const -1
|
||||
memory.atomic.wait64
|
||||
)
|
||||
(export "wait32" (func $wait32))
|
||||
(export "wait64" (func $wait64))
|
||||
)
|
||||
|
||||
(assert_return (invoke "wait32") (i32.const 1))
|
||||
(assert_return (invoke "wait64") (i32.const 1))
|
||||
|
||||
;; timeout
|
||||
(module
|
||||
(memory 1 1 shared)
|
||||
(type (;0;) (func))
|
||||
(func $wait32 (result i32)
|
||||
i32.const 0
|
||||
i32.const 0
|
||||
i64.const 1000
|
||||
memory.atomic.wait32
|
||||
)
|
||||
(func $wait64 (result i32)
|
||||
i32.const 0
|
||||
i64.const 0
|
||||
i64.const 1000
|
||||
memory.atomic.wait64
|
||||
)
|
||||
(export "wait32" (func $wait32))
|
||||
(export "wait64" (func $wait64))
|
||||
)
|
||||
|
||||
(assert_return (invoke "wait32") (i32.const 2))
|
||||
(assert_return (invoke "wait64") (i32.const 2))
|
||||
|
||||
;; timeout on 0ns
|
||||
(module
|
||||
(memory 1 1 shared)
|
||||
(type (;0;) (func))
|
||||
(func $wait32 (result i32)
|
||||
i32.const 0
|
||||
i32.const 0
|
||||
i64.const 0
|
||||
memory.atomic.wait32
|
||||
)
|
||||
(func $wait64 (result i32)
|
||||
i32.const 0
|
||||
i64.const 0
|
||||
i64.const 0
|
||||
memory.atomic.wait64
|
||||
)
|
||||
(export "wait32" (func $wait32))
|
||||
(export "wait64" (func $wait64))
|
||||
)
|
||||
|
||||
(assert_return (invoke "wait32") (i32.const 2))
|
||||
(assert_return (invoke "wait64") (i32.const 2))
|
||||
|
||||
@@ -80,19 +80,19 @@
|
||||
|
||||
;; misaligned loads
|
||||
(assert_return (invoke "32.load8u" (i32.const 1)) (i32.const 0))
|
||||
(assert_trap (invoke "32.load16u" (i32.const 1)) "misaligned memory access")
|
||||
(assert_trap (invoke "32.load32u" (i32.const 1)) "misaligned memory access")
|
||||
(assert_trap (invoke "32.load16u" (i32.const 1)) "unaligned atomic")
|
||||
(assert_trap (invoke "32.load32u" (i32.const 1)) "unaligned atomic")
|
||||
(assert_return (invoke "64.load8u" (i32.const 1)) (i64.const 0))
|
||||
(assert_trap (invoke "64.load16u" (i32.const 1)) "misaligned memory access")
|
||||
(assert_trap (invoke "64.load32u" (i32.const 1)) "misaligned memory access")
|
||||
(assert_trap (invoke "64.load64u" (i32.const 1)) "misaligned memory access")
|
||||
(assert_trap (invoke "64.load16u" (i32.const 1)) "unaligned atomic")
|
||||
(assert_trap (invoke "64.load32u" (i32.const 1)) "unaligned atomic")
|
||||
(assert_trap (invoke "64.load64u" (i32.const 1)) "unaligned atomic")
|
||||
(assert_return (invoke "32.load8u o1" (i32.const 0)) (i32.const 0))
|
||||
(assert_trap (invoke "32.load16u o1" (i32.const 0)) "misaligned memory access")
|
||||
(assert_trap (invoke "32.load32u o1" (i32.const 0)) "misaligned memory access")
|
||||
(assert_trap (invoke "32.load16u o1" (i32.const 0)) "unaligned atomic")
|
||||
(assert_trap (invoke "32.load32u o1" (i32.const 0)) "unaligned atomic")
|
||||
(assert_return (invoke "64.load8u o1" (i32.const 0)) (i64.const 0))
|
||||
(assert_trap (invoke "64.load16u o1" (i32.const 0)) "misaligned memory access")
|
||||
(assert_trap (invoke "64.load32u o1" (i32.const 0)) "misaligned memory access")
|
||||
(assert_trap (invoke "64.load64u o1" (i32.const 0)) "misaligned memory access")
|
||||
(assert_trap (invoke "64.load16u o1" (i32.const 0)) "unaligned atomic")
|
||||
(assert_trap (invoke "64.load32u o1" (i32.const 0)) "unaligned atomic")
|
||||
(assert_trap (invoke "64.load64u o1" (i32.const 0)) "unaligned atomic")
|
||||
|
||||
;; aligned stores
|
||||
(assert_return (invoke "32.store8" (i32.const 0)))
|
||||
@@ -111,16 +111,16 @@
|
||||
|
||||
;; misaligned stores
|
||||
(assert_return (invoke "32.store8" (i32.const 1)))
|
||||
(assert_trap (invoke "32.store16" (i32.const 1)) "misaligned memory access")
|
||||
(assert_trap (invoke "32.store32" (i32.const 1)) "misaligned memory access")
|
||||
(assert_trap (invoke "32.store16" (i32.const 1)) "unaligned atomic")
|
||||
(assert_trap (invoke "32.store32" (i32.const 1)) "unaligned atomic")
|
||||
(assert_return (invoke "64.store8" (i32.const 1)))
|
||||
(assert_trap (invoke "64.store16" (i32.const 1)) "misaligned memory access")
|
||||
(assert_trap (invoke "64.store32" (i32.const 1)) "misaligned memory access")
|
||||
(assert_trap (invoke "64.store64" (i32.const 1)) "misaligned memory access")
|
||||
(assert_trap (invoke "64.store16" (i32.const 1)) "unaligned atomic")
|
||||
(assert_trap (invoke "64.store32" (i32.const 1)) "unaligned atomic")
|
||||
(assert_trap (invoke "64.store64" (i32.const 1)) "unaligned atomic")
|
||||
(assert_return (invoke "32.store8 o1" (i32.const 0)))
|
||||
(assert_trap (invoke "32.store16 o1" (i32.const 0)) "misaligned memory access")
|
||||
(assert_trap (invoke "32.store32 o1" (i32.const 0)) "misaligned memory access")
|
||||
(assert_trap (invoke "32.store16 o1" (i32.const 0)) "unaligned atomic")
|
||||
(assert_trap (invoke "32.store32 o1" (i32.const 0)) "unaligned atomic")
|
||||
(assert_return (invoke "64.store8 o1" (i32.const 0)))
|
||||
(assert_trap (invoke "64.store16 o1" (i32.const 0)) "misaligned memory access")
|
||||
(assert_trap (invoke "64.store32 o1" (i32.const 0)) "misaligned memory access")
|
||||
(assert_trap (invoke "64.store64 o1" (i32.const 0)) "misaligned memory access")
|
||||
(assert_trap (invoke "64.store16 o1" (i32.const 0)) "unaligned atomic")
|
||||
(assert_trap (invoke "64.store32 o1" (i32.const 0)) "unaligned atomic")
|
||||
(assert_trap (invoke "64.store64 o1" (i32.const 0)) "unaligned atomic")
|
||||
|
||||
Reference in New Issue
Block a user