Skip to content

Commit 1e277e7

Browse files
committed
feat: implement memory.atomic.notify,wait32,wait64
Use parking_lot_core, as this established crate does exactly what is needed here. Signed-off-by: Harald Hoyer <harald@profian.com>
1 parent 95ca72a commit 1e277e7

2 files changed

Lines changed: 104 additions & 23 deletions

File tree

crates/runtime/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ anyhow = { workspace = true }
2626
memfd = "0.6.1"
2727
paste = "1.0.3"
2828
encoding_rs = { version = "0.8.31", optional = true }
29+
parking_lot_core = "0.9.4"
2930

3031
[target.'cfg(target_os = "macos")'.dependencies]
3132
mach = "0.3.2"

crates/runtime/src/libcalls.rs

Lines changed: 103 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,13 @@ use crate::table::{Table, TableElementType};
6060
use crate::vmcontext::{VMCallerCheckedAnyfunc, VMContext};
6161
use crate::TrapReason;
6262
use anyhow::Result;
63+
use parking_lot_core::{
64+
park, unpark_all, unpark_one, ParkResult, DEFAULT_PARK_TOKEN, DEFAULT_UNPARK_TOKEN,
65+
};
6366
use std::mem;
6467
use std::ptr::{self, NonNull};
68+
use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
69+
use std::time::{Duration, Instant};
6570
use wasmtime_environ::{
6671
DataIndex, ElemIndex, FuncIndex, GlobalIndex, MemoryIndex, TableIndex, TrapCode,
6772
};
@@ -435,49 +440,122 @@ unsafe fn memory_atomic_notify(
435440
vmctx: *mut VMContext,
436441
memory_index: u32,
437442
addr: u64,
438-
_count: u32,
443+
count: u32,
439444
) -> Result<u32, TrapReason> {
440445
let memory = MemoryIndex::from_u32(memory_index);
441446
let instance = (*vmctx).instance();
442-
validate_atomic_addr(instance, memory, addr, 4, 4)?;
443-
Err(
444-
anyhow::anyhow!("unimplemented: wasm atomics (fn memory_atomic_notify) unsupported",)
445-
.into(),
446-
)
447+
let addr = validate_atomic_addr(instance, memory, addr, 4, 4)?;
448+
if count == 0 {
449+
return Ok(0);
450+
}
451+
452+
let unparked_threads = if count == u32::MAX {
453+
// SAFETY: `addr` is a valid pointer into the given memory and unique to parking_lot_core.
454+
unsafe { unpark_all(addr as _, DEFAULT_UNPARK_TOKEN) }
455+
} else {
456+
let mut num = 0;
457+
for _ in 0..count {
458+
// SAFETY: `addr` is a valid pointer into the given memory and unique to parking_lot_core.
459+
let num_t = unsafe { unpark_one(addr as _, |_| DEFAULT_UNPARK_TOKEN).unparked_threads };
460+
461+
if num_t == 0 {
462+
break;
463+
}
464+
465+
num += num_t;
466+
}
467+
num
468+
};
469+
470+
u32::try_from(unparked_threads).map_err(|e| TrapReason::user_with_backtrace(e.into()))
447471
}
448472

449473
// Implementation of `memory.atomic.wait32` for locally defined memories.
450474
unsafe fn memory_atomic_wait32(
451475
vmctx: *mut VMContext,
452476
memory_index: u32,
453477
addr: u64,
454-
_expected: u32,
455-
_timeout: u64,
478+
expected: u32,
479+
timeout: u64,
456480
) -> Result<u32, TrapReason> {
481+
// convert to absolute timestamp as soon as possible
482+
let wait_until = (timeout > 0)
483+
.then(|| {
484+
Instant::now()
485+
.checked_add(Duration::from_nanos(timeout))
486+
.ok_or_else(|| {
487+
TrapReason::user_with_backtrace(anyhow::anyhow!(
488+
"overflow when adding timeout to current time"
489+
))
490+
})
491+
})
492+
.transpose()?;
493+
457494
let memory = MemoryIndex::from_u32(memory_index);
458495
let instance = (*vmctx).instance();
459-
validate_atomic_addr(instance, memory, addr, 4, 4)?;
460-
Err(
461-
anyhow::anyhow!("unimplemented: wasm atomics (fn memory_atomic_wait32) unsupported",)
462-
.into(),
463-
)
496+
let addr = validate_atomic_addr(instance, memory, addr, 4, 4)?;
497+
498+
// SAFETY: `addr` was validated by `validate_atomic_addr` above.
499+
let atomic = unsafe { &*(addr as *const AtomicU32) };
500+
// SAFETY: `addr` is a valid pointer into the given memory and unique to parking_lot_core.
501+
match unsafe {
502+
park(
503+
addr as usize,
504+
|| atomic.load(Ordering::SeqCst) == expected,
505+
|| {},
506+
|_, _| {},
507+
DEFAULT_PARK_TOKEN,
508+
wait_until,
509+
)
510+
} {
511+
ParkResult::Unparked(_) => return Ok(0),
512+
ParkResult::Invalid => return Ok(1),
513+
ParkResult::TimedOut => return Ok(2),
514+
}
464515
}
465516

466517
// Implementation of `memory.atomic.wait64` for locally defined memories.
467518
unsafe fn memory_atomic_wait64(
468519
vmctx: *mut VMContext,
469520
memory_index: u32,
470521
addr: u64,
471-
_expected: u64,
472-
_timeout: u64,
522+
expected: u64,
523+
timeout: u64,
473524
) -> Result<u32, TrapReason> {
525+
// convert to absolute timestamp as soon as possible
526+
let wait_until = (timeout > 0)
527+
.then(|| {
528+
Instant::now()
529+
.checked_add(Duration::from_nanos(timeout))
530+
.ok_or_else(|| {
531+
TrapReason::user_with_backtrace(anyhow::anyhow!(
532+
"overflow when adding timeout to current time"
533+
))
534+
})
535+
})
536+
.transpose()?;
537+
474538
let memory = MemoryIndex::from_u32(memory_index);
475539
let instance = (*vmctx).instance();
476-
validate_atomic_addr(instance, memory, addr, 8, 8)?;
477-
Err(
478-
anyhow::anyhow!("unimplemented: wasm atomics (fn memory_atomic_wait64) unsupported",)
479-
.into(),
480-
)
540+
let addr = validate_atomic_addr(instance, memory, addr, 8, 8)?;
541+
542+
// SAFETY: `addr` was validated by `validate_atomic_addr` above.
543+
let atomic = unsafe { &*(addr as *const AtomicU64) };
544+
// SAFETY: `addr` is a valid pointer into the given memory and unique to parking_lot_core.
545+
match unsafe {
546+
park(
547+
addr as usize,
548+
|| atomic.load(Ordering::SeqCst) == expected,
549+
|| {},
550+
|_, _| {},
551+
DEFAULT_PARK_TOKEN,
552+
wait_until,
553+
)
554+
} {
555+
ParkResult::Unparked(_) => return Ok(0),
556+
ParkResult::Invalid => return Ok(1),
557+
ParkResult::TimedOut => return Ok(2),
558+
}
481559
}
482560

483561
macro_rules! ensure {
@@ -498,17 +576,19 @@ unsafe fn validate_atomic_addr(
498576
addr: u64,
499577
access_size: u64,
500578
access_alignment: u64,
501-
) -> Result<(), TrapCode> {
579+
) -> Result<*mut u8, TrapCode> {
502580
debug_assert!(access_alignment.is_power_of_two());
503581
ensure!(addr % access_alignment == 0, TrapCode::HeapMisaligned);
504582

505-
let length = u64::try_from(instance.get_memory(memory).current_length()).unwrap();
583+
let mem = instance.get_memory(memory);
584+
585+
let length = u64::try_from(mem.current_length()).unwrap();
506586
ensure!(
507587
addr.saturating_add(access_size) < length,
508588
TrapCode::HeapOutOfBounds
509589
);
510590

511-
Ok(())
591+
Ok(mem.base.add(addr as usize))
512592
}
513593

514594
// Hook for when an instance runs out of fuel.

0 commit comments

Comments
 (0)