Skip to content

Commit

Permalink
feat: implement memory.atomic.notify,wait32,wait64 (bytecodealliance#…
Browse files Browse the repository at this point in the history
…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 <[email protected]>

* fix: change trap message for HeapMisaligned

The threads spec test wants "unaligned atomic"
instead of "misaligned memory access".

Signed-off-by: Harald Hoyer <[email protected]>

* tests: add test for atomic wait on non-shared memory

Signed-off-by: Harald Hoyer <[email protected]>

* 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 <[email protected]>

* tests: add atomics_notify.wast

checking that notify with 0 waiters returns 0 on shared and non-shared
memory.

Signed-off-by: Harald Hoyer <[email protected]>

* 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 <[email protected]>

* fixup! feat: implement memory.atomic.notify,wait32,wait64

Signed-off-by: Harald Hoyer <[email protected]>

* fixup! feat: implement memory.atomic.notify,wait32,wait64

Signed-off-by: Harald Hoyer <[email protected]>

Signed-off-by: Harald Hoyer <[email protected]>
  • Loading branch information
haraldh authored Nov 21, 2022
1 parent fe2bfdb commit c74706a
Show file tree
Hide file tree
Showing 21 changed files with 972 additions and 114 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ wasmtime-wast = { workspace = true, features = ['component-model'] }
wasmtime-component-util = { workspace = true }
component-macro-test = { path = "crates/misc/component-macro-test" }
component-test-util = { workspace = true }
bstr = "0.2.17"

[target.'cfg(windows)'.dev-dependencies]
windows-sys = { workspace = true, features = ["Win32_System_Memory"] }
Expand Down
10 changes: 5 additions & 5 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::process::Command;

fn main() -> anyhow::Result<()> {
println!("cargo:rerun-if-changed=build.rs");

let out_dir = PathBuf::from(
env::var_os("OUT_DIR").expect("The OUT_DIR environment variable must be set"),
);
Expand Down Expand Up @@ -43,6 +44,7 @@ fn main() -> anyhow::Result<()> {
"tests/spec_testsuite/proposals/multi-memory",
strategy,
)?;
test_directory_module(out, "tests/spec_testsuite/proposals/threads", strategy)?;
} else {
println!(
"cargo:warning=The spec testsuite is disabled. To enable, run `git submodule \
Expand All @@ -62,7 +64,6 @@ fn main() -> anyhow::Result<()> {
drop(Command::new("rustfmt").arg(&output).status());
Ok(())
}

fn test_directory_module(
out: &mut String,
path: impl AsRef<Path>,
Expand Down Expand Up @@ -91,7 +92,7 @@ fn test_directory(
return None;
}
// Ignore files starting with `.`, which could be editor temporary files
if p.file_stem()?.to_str()?.starts_with(".") {
if p.file_stem()?.to_str()?.starts_with('.') {
return None;
}
Some(p)
Expand All @@ -116,8 +117,7 @@ fn extract_name(path: impl AsRef<Path>) -> String {
.expect("filename should have a stem")
.to_str()
.expect("filename should be representable as a string")
.replace("-", "_")
.replace("/", "_")
.replace(['-', '/'], "_")
}

fn with_test_module<T>(
Expand Down Expand Up @@ -163,7 +163,7 @@ fn write_testsuite_tests(
" crate::wast::run_wast(r#\"{}\"#, crate::wast::Strategy::{}, {}).unwrap();",
path.display(),
strategy,
pooling
pooling,
)?;
writeln!(out, "}}")?;
writeln!(out)?;
Expand Down
8 changes: 7 additions & 1 deletion crates/environ/src/trap_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ pub enum Trap {
/// When wasm code is configured to consume fuel and it runs out of fuel
/// then this trap will be raised.
OutOfFuel,

/// Used to indicate that a trap was raised by atomic wait operations on non shared memory.
AtomicWaitNonSharedMemory,
// if adding a variant here be sure to update the `check!` macro below
}

impl fmt::Display for Trap {
Expand All @@ -93,7 +97,7 @@ impl fmt::Display for Trap {
let desc = match self {
StackOverflow => "call stack exhausted",
MemoryOutOfBounds => "out of bounds memory access",
HeapMisaligned => "misaligned memory access",
HeapMisaligned => "unaligned atomic",
TableOutOfBounds => "undefined element: out of bounds table access",
IndirectCallToNull => "uninitialized element",
BadSignature => "indirect call type mismatch",
Expand All @@ -104,6 +108,7 @@ impl fmt::Display for Trap {
Interrupt => "interrupt",
AlwaysTrapAdapter => "degenerate component adapter called",
OutOfFuel => "all fuel consumed by WebAssembly",
AtomicWaitNonSharedMemory => "atomic wait on non-shared memory",
};
write!(f, "wasm trap: {desc}")
}
Expand Down Expand Up @@ -217,6 +222,7 @@ pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<Trap> {
Interrupt
AlwaysTrapAdapter
OutOfFuel
AtomicWaitNonSharedMemory
}

if cfg!(debug_assertions) {
Expand Down
2 changes: 1 addition & 1 deletion crates/fuzzing/src/oracles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ pub fn spectest(mut fuzz_config: generators::Config, test: generators::SpecTest)
fuzz_config.set_spectest_compliant();
log::debug!("running {:?}", test.file);
let mut wast_context = WastContext::new(fuzz_config.to_store());
wast_context.register_spectest().unwrap();
wast_context.register_spectest(false).unwrap();
wast_context
.run_buffer(test.file, test.contents.as_bytes())
.unwrap();
Expand Down
3 changes: 3 additions & 0 deletions crates/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ features = [
"Win32_Security",
]

[dev-dependencies]
once_cell = { workspace = true }

[build-dependencies]
cc = "1.0"

Expand Down
11 changes: 11 additions & 0 deletions crates/runtime/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,17 @@ impl Instance {
}
}

/// Get a locally defined or imported memory.
pub(crate) fn get_runtime_memory(&mut self, index: MemoryIndex) -> &mut Memory {
if let Some(defined_index) = self.module().defined_memory_index(index) {
unsafe { &mut *self.get_defined_memory(defined_index) }
} else {
let import = self.imported_memory(index);
let ctx = unsafe { &mut *import.vmctx };
unsafe { &mut *ctx.instance_mut().get_defined_memory(import.index) }
}
}

/// Return the indexed `VMMemoryDefinition`.
fn memory(&self, index: DefinedMemoryIndex) -> VMMemoryDefinition {
unsafe { VMMemoryDefinition::load(self.memory_ptr(index)) }
Expand Down
1 change: 1 addition & 0 deletions crates/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ mod instance;
mod memory;
mod mmap;
mod mmap_vec;
mod parking_spot;
mod table;
mod traphandlers;
mod vmcontext;
Expand Down
118 changes: 59 additions & 59 deletions crates/runtime/src/libcalls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@
//! ```
use crate::externref::VMExternRef;
use crate::instance::Instance;
use crate::table::{Table, TableElementType};
use crate::vmcontext::{VMCallerCheckedAnyfunc, VMContext};
use crate::TrapReason;
use crate::{SharedMemory, TrapReason};
use anyhow::Result;
use std::mem;
use std::ptr::{self, NonNull};
use std::time::{Duration, Instant};
use wasmtime_environ::{
DataIndex, ElemIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex, Trap,
};
Expand Down Expand Up @@ -434,81 +434,81 @@ unsafe fn externref_global_set(vmctx: *mut VMContext, index: u32, externref: *mu
unsafe fn memory_atomic_notify(
vmctx: *mut VMContext,
memory_index: u32,
addr: u64,
_count: u32,
addr_index: u64,
count: u32,
) -> Result<u32, TrapReason> {
let memory = MemoryIndex::from_u32(memory_index);
let instance = (*vmctx).instance();
validate_atomic_addr(instance, memory, addr, 4, 4)?;
Err(
anyhow::anyhow!("unimplemented: wasm atomics (fn memory_atomic_notify) unsupported",)
.into(),
)
let instance = (*vmctx).instance_mut();
instance
.get_memory(memory)
.validate_addr(addr_index, 4, 4)?;

let shared_mem = instance.get_runtime_memory(memory).as_shared_memory();

if count == 0 {
return Ok(0);
}

let unparked_threads = shared_mem.map_or(0, |shared_mem| {
// SAFETY: checked `addr_index` above
unsafe { shared_mem.unchecked_atomic_notify(addr_index, count) }
});

Ok(unparked_threads)
}

// Implementation of `memory.atomic.wait32` for locally defined memories.
unsafe fn memory_atomic_wait32(
vmctx: *mut VMContext,
memory_index: u32,
addr: u64,
_expected: u32,
_timeout: u64,
addr_index: u64,
expected: u32,
timeout: u64,
) -> Result<u32, TrapReason> {
// convert timeout to Instant, before any wait happens on locking
let timeout = (timeout as i64 >= 0).then(|| Instant::now() + Duration::from_nanos(timeout));

let memory = MemoryIndex::from_u32(memory_index);
let instance = (*vmctx).instance();
validate_atomic_addr(instance, memory, addr, 4, 4)?;
Err(
anyhow::anyhow!("unimplemented: wasm atomics (fn memory_atomic_wait32) unsupported",)
.into(),
)
let instance = (*vmctx).instance_mut();
let addr = instance
.get_memory(memory)
.validate_addr(addr_index, 4, 4)?;

let shared_mem: SharedMemory = instance
.get_runtime_memory(memory)
.as_shared_memory()
.ok_or(Trap::AtomicWaitNonSharedMemory)?;

// SAFETY: checked `addr_index` above
let res = unsafe { shared_mem.unchecked_atomic_wait32(addr_index, addr, expected, timeout) };
Ok(res)
}

// Implementation of `memory.atomic.wait64` for locally defined memories.
unsafe fn memory_atomic_wait64(
vmctx: *mut VMContext,
memory_index: u32,
addr: u64,
_expected: u64,
_timeout: u64,
addr_index: u64,
expected: u64,
timeout: u64,
) -> Result<u32, TrapReason> {
let memory = MemoryIndex::from_u32(memory_index);
let instance = (*vmctx).instance();
validate_atomic_addr(instance, memory, addr, 8, 8)?;
Err(
anyhow::anyhow!("unimplemented: wasm atomics (fn memory_atomic_wait64) unsupported",)
.into(),
)
}

macro_rules! ensure {
($cond:expr, $trap:expr) => {
if !($cond) {
return Err($trap);
}
};
}
// convert timeout to Instant, before any wait happens on locking
let timeout = (timeout as i64 >= 0).then(|| Instant::now() + Duration::from_nanos(timeout));

/// In the configurations where bounds checks were elided in JIT code (because
/// we are using static memories with virtual memory guard pages) this manual
/// check is here so we don't segfault from Rust. For other configurations,
/// these checks are required anyways.
unsafe fn validate_atomic_addr(
instance: &Instance,
memory: MemoryIndex,
addr: u64,
access_size: u64,
access_alignment: u64,
) -> Result<(), Trap> {
debug_assert!(access_alignment.is_power_of_two());
ensure!(addr % access_alignment == 0, Trap::HeapMisaligned);

let length = u64::try_from(instance.get_memory(memory).current_length()).unwrap();
ensure!(
addr.saturating_add(access_size) < length,
Trap::MemoryOutOfBounds
);

Ok(())
let memory = MemoryIndex::from_u32(memory_index);
let instance = (*vmctx).instance_mut();
let addr = instance
.get_memory(memory)
.validate_addr(addr_index, 8, 8)?;

let shared_mem: SharedMemory = instance
.get_runtime_memory(memory)
.as_shared_memory()
.ok_or(Trap::AtomicWaitNonSharedMemory)?;

// SAFETY: checked `addr_index` above
let res = unsafe { shared_mem.unchecked_atomic_wait64(addr_index, addr, expected, timeout) };
Ok(res)
}

// Hook for when an instance runs out of fuel.
Expand Down
Loading

0 comments on commit c74706a

Please sign in to comment.