Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 53 additions & 5 deletions crates/perry-runtime/src/atomics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,17 @@ impl AtomicView {
bigint_integer_kind(self.kind())
}

fn has_shared_backing(&self) -> bool {
match self {
AtomicView::TypedArray { ptr, .. } => {
crate::typedarray::typed_array_has_shared_backing(*ptr)
}
AtomicView::Uint8ArrayBuffer(ptr) => {
crate::buffer::is_shared_array_buffer(*ptr as usize)
}
}
}

fn length(&self) -> i32 {
match self {
AtomicView::TypedArray { ptr, .. } => js_typed_array_length(*ptr),
Expand Down Expand Up @@ -161,6 +172,24 @@ fn atomics_int32_view_arg(value: f64) -> AtomicView {
throw_type_error(b"Atomics wait/notify requires an Int32Array");
}

fn atomics_wait_notify_view_arg(value: f64) -> AtomicView {
let js = JSValue::from_bits(value.to_bits());
if !js.is_pointer() {
throw_type_error(b"Atomics wait/notify requires an Int32Array or BigInt64Array");
}
let raw = clean_ta_ptr(js.as_pointer::<TypedArrayHeader>()) as usize;
if raw == 0 {
throw_type_error(b"Atomics wait/notify requires an Int32Array or BigInt64Array");
}
match lookup_typed_array_kind(raw) {
Some(kind @ (KIND_INT32 | KIND_BIGINT64)) => AtomicView::TypedArray {
ptr: raw as *mut TypedArrayHeader,
kind,
},
_ => throw_type_error(b"Atomics wait/notify requires an Int32Array or BigInt64Array"),
}
}

fn atomics_to_index(index: f64, length: i32) -> i32 {
let mut n = JSValue::from_bits(index.to_bits()).to_number();
if n.is_nan() {
Expand Down Expand Up @@ -375,6 +404,12 @@ fn int32_slot(view: f64, index: f64) -> (AtomicView, i32) {
(view, idx)
}

fn wait_notify_slot(view: f64, index: f64) -> (AtomicView, i32) {
let view = atomics_wait_notify_view_arg(view);
let idx = atomics_to_index(index, view.length());
(view, idx)
}

fn wait_async_result(async_value: bool, value: f64) -> f64 {
let scope = crate::gc::RuntimeHandleScope::new();
let obj = crate::object::js_object_alloc(0, 2);
Expand Down Expand Up @@ -581,8 +616,11 @@ pub extern "C" fn js_atomics_notify(
index: f64,
count: f64,
) -> f64 {
let (_view, _idx) = int32_slot(view, index);
let (view, _idx) = wait_notify_slot(view, index);
let _ = numeric_arg(count);
if !view.has_shared_backing() {
return 0.0;
}
0.0
}

Expand All @@ -594,10 +632,20 @@ pub extern "C" fn js_atomics_wait(
expected: f64,
timeout: f64,
) -> f64 {
let (view, idx) = int32_slot(view, index);
let expected = coerce_for_kind(KIND_INT32, expected);
if view.get_numeric(idx) != expected {
return string_value(b"not-equal");
let (view, idx) = wait_notify_slot(view, index);
if !view.has_shared_backing() {
throw_type_error(b"Atomics.wait requires a shared typed array");
}
if view.kind() == KIND_BIGINT64 {
let expected = bigint_bits(expected);
if view.get_bigint_bits(idx) != expected {
return string_value(b"not-equal");
}
} else {
let expected = coerce_for_kind(KIND_INT32, expected);
if view.get_numeric(idx) != expected {
return string_value(b"not-equal");
}
}

let _ = number_arg(timeout);
Expand Down
19 changes: 19 additions & 0 deletions crates/perry-runtime/src/typedarray/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ thread_local! {
/// (~1.0% leaf samples on perf-comprehensive).
static TYPED_ARRAY_REGISTRY: RefCell<crate::fast_hash::PtrHashMap<usize, u8>> =
RefCell::new(crate::fast_hash::new_ptr_hash_map());
/// Perry currently materializes typed-array views over ArrayBuffer storage
/// as owning TypedArrayHeader values. Track which views came from
/// SharedArrayBuffer so Atomics.wait can apply Node's shared-buffer guard.
static TYPED_ARRAY_SHARED_BACKING: RefCell<crate::fast_hash::PtrHashSet<usize>> =
RefCell::new(crate::fast_hash::new_ptr_hash_set());
}

pub fn register_typed_array(ptr: *const TypedArrayHeader, kind: u8) {
Expand All @@ -155,6 +160,9 @@ pub fn unregister_typed_array(ptr: *const TypedArrayHeader) {
TYPED_ARRAY_REGISTRY.with(|r| {
r.borrow_mut().remove(&owner);
});
TYPED_ARRAY_SHARED_BACKING.with(|r| {
r.borrow_mut().remove(&owner);
});
crate::typedarray_props::typed_array_clear_own_props(owner);
}

Expand All @@ -164,6 +172,17 @@ pub fn lookup_typed_array_kind(addr: usize) -> Option<u8> {
TYPED_ARRAY_REGISTRY.with(|r| r.borrow().get(&addr).copied())
}

pub(crate) fn mark_typed_array_shared_backing(ptr: *const TypedArrayHeader) {
TYPED_ARRAY_SHARED_BACKING.with(|r| {
r.borrow_mut().insert(ptr as usize);
});
}

pub(crate) fn typed_array_has_shared_backing(ptr: *const TypedArrayHeader) -> bool {
let ptr = clean_ta_ptr(ptr);
TYPED_ARRAY_SHARED_BACKING.with(|r| r.borrow().contains(&(ptr as usize)))
}

#[inline]
fn strip_nanbox(p: u64) -> usize {
let top16 = p >> 48;
Expand Down
3 changes: 3 additions & 0 deletions crates/perry-runtime/src/typedarray_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ pub extern "C" fn js_typed_array_view(
// so copy the backing bytes at `offset` directly to preserve element values.
let count = elem_count.max(0) as u32;
let ta = typed_array_alloc(kind, count);
if crate::buffer::is_shared_array_buffer(addr) {
crate::typedarray::mark_typed_array_shared_backing(ta);
}
if count > 0 {
unsafe {
let src_data = crate::buffer::buffer_data(src).add(offset as usize);
Expand Down
37 changes: 37 additions & 0 deletions test-parity/node-suite/globals/atomics-wait-notify-receivers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// @ts-nocheck
function show(label, value) {
console.log(label + ":" + String(value));
}

function showErr(label, fn) {
try {
show(label, fn());
} catch (err) {
console.log(label + ":" + err.name);
}
}

const sab = new SharedArrayBuffer(8);
const ab = new ArrayBuffer(8);

const sharedI32 = new Int32Array(sab);
const localI32 = new Int32Array(ab);
const sharedBigI64 = new BigInt64Array(sab);
const localBigI64 = new BigInt64Array(ab);
const sharedBigU64 = new BigUint64Array(sab);

show("wait i32 shared mismatch", Atomics.wait(sharedI32, 0, 1, 0));
showErr("wait i32 nonshared", () => Atomics.wait(localI32, 0, 0, 0));
show("notify i32 nonshared", Atomics.notify(localI32, 0));
showErr("notify i32 nonshared oob", () => Atomics.notify(localI32, 9));

show("wait b64 shared mismatch", Atomics.wait(sharedBigI64, 0, 1n, 0));
show("wait b64 shared timeout", Atomics.wait(sharedBigI64, 0, 0n, 0));
showErr("wait b64 number expected", () => Atomics.wait(sharedBigI64, 0, 0, 0));
showErr("wait b64 nonshared", () => Atomics.wait(localBigI64, 0, 0n, 0));
show("notify b64 shared", Atomics.notify(sharedBigI64, 0));
show("notify b64 nonshared", Atomics.notify(localBigI64, 0));
showErr("notify b64 nonshared oob", () => Atomics.notify(localBigI64, 9));

showErr("wait bu64 shared", () => Atomics.wait(sharedBigU64, 0, 0n, 0));
showErr("notify bu64 shared", () => Atomics.notify(sharedBigU64, 0));