diff --git a/crates/oxide_builtins/src/atomics.rs b/crates/oxide_builtins/src/atomics.rs index 42e40c8e..5728af0a 100644 --- a/crates/oxide_builtins/src/atomics.rs +++ b/crates/oxide_builtins/src/atomics.rs @@ -1,6 +1,7 @@ -//! Atomics 全局纯对象的 10 个原子方法(load/store/exchange/add/sub/and/or/ -//! xor/compareExchange/isLockFree)。单线程引擎无锁层级,全部退化为 -//! 读-改-写;宽度截断与元素写路径(`write_element`)结构同构。 +//! Atomics 全局纯对象的 12 个原子方法(load/store/exchange/add/sub/and/or/ +//! xor/compareExchange/isLockFree/wait/notify)。单线程引擎无锁层级:算术族 +//! 退化为读-改-写;wait/notify 退化为"调用时即定"的同步比较/恒 0(无真 +//! 阻塞、无 waiter 面);宽度截断与元素写路径(`write_element`)结构同构。 use num_bigint::BigInt; use oxide_types::object::TypedArrayKind; @@ -161,6 +162,102 @@ fn arithmetic_write_value( JsValue::int(r) } +/// wait/notify 共享验证核:品牌 → {Int32, BigInt64} 宽度守卫 → 越界/detach +/// TypeError → 缓冲字节长读取(先于 index 强转,"长度读取"语料钉)→ +/// index ToIntegerOrInfinity → 相对字节偏移越界 RangeError。 +/// +/// # 步骤 +/// 1. `get_typed_array_data` 品牌校验(非 TA 对象抛 TypeError)。 +/// 2. 宽度守卫(仅 Int32/BigInt64;其余整数/浮点/clamped 视图抛 TypeError, +/// 与算术族的八整数宽度集不同)。 +/// 3. 越界/detach 守卫(只读面,不查 immutable——notify 对不可变缓冲返 0 不抛)。 +/// 4. `require_sab` 臂:非 SAB 抛 TypeError(先于 index 强转)。 +/// 5. 缓冲字节长与载荷指针在 index 强转前读取:index 的 valueOf 内若发生 +/// 缓冲 grow/resize,越界判定仍用强转前读到的长度(语料钉)。 +/// 6. 索引越界(负值,或元素字节区间超缓冲)抛 RangeError。 +/// +/// # 边界与前提 +/// - 返回的载荷指针在 session 独占期内有效,调用方同一语句内消费。 +/// - `require_sab` 臂(wait)在 index 强转前即抛非 SAB TypeError(毒序钉: +/// 毒 index 不得被评估);`is_sab` 标志供 notify 臂在 count 强转后选早返 0。 +/// +/// # 副作用 +/// 无(纯读)。 +fn resolve_atomic_wait_access( + vm: &mut H, this_val: JsValue, index_val: JsValue, require_sab: bool, +) -> Result<(*mut ArrayBufferPayload, TypedArrayData, usize, bool), JsValue> { + let view = get_typed_array_data(vm, this_val)?; + if !matches!(view.kind, TypedArrayKind::Int32 | TypedArrayKind::BigInt64) { + return Err(type_error(vm, "not an int32 or BigInt64 typed array")); + } + ta_validate(vm, view, false)?; + + let buffer_ptr = view.buffer.as_js_object_ptr(); + if buffer_ptr.is_null() { + return Err(type_error(vm, "TypedArray buffer internal state invalid")); + } + // SAFETY: buffer 对象与视图同生命周期,此处只读标签与载荷指针。 + let buffer = unsafe { &*buffer_ptr }; + let is_sab = buffer.is_shared_array_buffer_obj(); + if require_sab && !is_sab { + return Err(type_error(vm, "Atomics.wait cannot be used on a non-shared buffer")); + } + let payload_ptr = match buffer_payload_ptr(buffer) { + Some(p) if !p.is_null() => p, + _ => return Err(range_error(vm, "invalid indexed access to Atomics")), + }; + // SAFETY: payload_ptr 经 buffer_payload_ptr 校验为合法载荷盒,只读字节长。 + let buffer_len = unsafe { (*payload_ptr).data.as_ref().map_or(0, |d| d.len()) }; + + let index = ta_to_integer_or_infinity(vm, index_val)?; + let esize = view.kind.bytes_per_element() as f64; + let offset = view.byte_offset as f64 + index * esize; + if offset < 0.0 || offset + esize > buffer_len as f64 { + return Err(range_error(vm, "invalid indexed access to Atomics")); + } + Ok((payload_ptr, view, offset as usize, is_sab)) +} + +/// `Atomics.wait(typedArray, index, value, timeout)` 单线程退化面:值/超时 +/// 强转(毒传播)后读载荷比较——等值返 "timed-out"、不等返 "not-equal"; +/// 无真阻塞(单线程无他 agent 可改值,结果调用时即定)。 +pub fn atomics_wait(vm: &mut H, args: &[u8]) -> NativeResult { + let ta_val = arg_at(vm, args, 1); + let index_val = arg_at(vm, args, 2); + let value_val = arg_at(vm, args, 3); + let timeout_val = arg_at(vm, args, 4); + let (payload_ptr, view, offset, _) = native_try!(resolve_atomic_wait_access(vm, ta_val, index_val, true)); + let value = native_try!(ta_element_value(vm, view.kind, value_val)); + // 超时 ToNumber 归一(NaN → +∞、负值 → 0):单线程下无观察差,只承载毒传播。 + native_try!(oxide_runtime_api::to_number_full(timeout_val, vm).map_err(|e| crate::iterator::engine_error(vm, &e))); + let old = atomic_read(vm, payload_ptr, view.kind, offset).unwrap_or(JsValue::undefined()); + let equal = if is_bigint_kind(view.kind) { + let old_bi = vm.bigint_value(old).clone(); + let value_bi = vm.bigint_value(value).clone(); + old_bi.cmp(&value_bi) == std::cmp::Ordering::Equal + } else { + oxide_runtime_api::to_number(old) == oxide_runtime_api::to_number(value) + }; + NativeResult::Ok(vm.new_string(if equal { "timed-out" } else { "not-equal" })) +} + +/// `Atomics.notify(typedArray, index, count)`:值验证后 count 经 +/// ToIntegerOrInfinity(毒传播,NaN → +∞、有限负值 → 0);非 SAB 提前返 0 +/// (先于本早返的越界/类型异常照常抛出);单线程无 waiter 面,恒返 0。 +pub fn atomics_notify(vm: &mut H, args: &[u8]) -> NativeResult { + let ta_val = arg_at(vm, args, 1); + let index_val = arg_at(vm, args, 2); + let count_val = arg_at(vm, args, 3); + let (_payload_ptr, _view, _offset, is_sab) = native_try!(resolve_atomic_wait_access(vm, ta_val, index_val, false)); + // count 归一:单线程下无观察差,只承载毒传播(语料钉:非 SAB 亦先评估 + // count 再返 0)。 + native_try!(ta_to_integer_or_infinity(vm, count_val)); + if !is_sab { + return NativeResult::Ok(JsValue::int(0)); + } + NativeResult::Ok(JsValue::int(0)) +} + /// `Atomics.load(typedArray, index)`:读指定索引元素。 pub fn atomics_load(vm: &mut H, args: &[u8]) -> NativeResult { let ta_val = arg_at(vm, args, 1); diff --git a/crates/oxide_test262/src/discovery.rs b/crates/oxide_test262/src/discovery.rs index 7f181e32..3b757bbd 100644 --- a/crates/oxide_test262/src/discovery.rs +++ b/crates/oxide_test262/src/discovery.rs @@ -24,6 +24,12 @@ pub(crate) fn is_skipped(meta: &TestMeta) -> Option { if flag.as_str() == "raw" { return Some("raw tests excluded".into()); } + // CanBlockIsFalse 语料期望主线程 wait 抛 TypeError;引擎单线程退化面 + // 取返回串形(等值 timed-out / 不等 not-equal),与语料互斥,默认 + // 模式跳过该族;--no-skip 下计红(口径外,见规划文档)。 + if flag.as_str() == "CanBlockIsFalse" { + return Some("CanBlockIsFalse: wait 返回串形".into()); + } // noStrict 测试放行——很多在严格模式下仍可通过;运行时跳过逻辑会捕获失败。 } diff --git a/crates/oxide_vm/src/bindings/bind_atomics.rs b/crates/oxide_vm/src/bindings/bind_atomics.rs index 2be737ff..df9f7fb4 100644 --- a/crates/oxide_vm/src/bindings/bind_atomics.rs +++ b/crates/oxide_vm/src/bindings/bind_atomics.rs @@ -34,6 +34,8 @@ pub fn bind_atomics(core: &Arc, session: &KernelSession, global: &mu ("xor", oxide_builtins::atomics::atomics_xor::, 3), ("compareExchange", oxide_builtins::atomics::atomics_compare_exchange::, 4), ("isLockFree", oxide_builtins::atomics::atomics_is_lock_free::, 1), + ("wait", oxide_builtins::atomics::atomics_wait::, 4), + ("notify", oxide_builtins::atomics::atomics_notify::, 3), ); let a_val = JsValue::from_js_object(atomics_ptr); diff --git a/crates/oxide_vm/tests/builtins_atomics_wait_tests.rs b/crates/oxide_vm/tests/builtins_atomics_wait_tests.rs new file mode 100644 index 00000000..60743a65 --- /dev/null +++ b/crates/oxide_vm/tests/builtins_atomics_wait_tests.rs @@ -0,0 +1,255 @@ +use std::sync::Arc; + +use oxide_compiler::compiler::Compiler; +use oxide_types::value::JsValue; +use oxide_vm::vm::Vm; + +fn eval(vm: &mut Vm, source: &str) -> Result { + let allocator = oxide_parser::Allocator::default(); + let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?; + let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?; + vm.run(&Arc::new(module)) +} + +fn truthy(vm: &mut Vm, source: &str) { + let result = eval(vm, source).unwrap(); + assert!(result.as_bool(), "expected truthy for: {}", source); +} + +/// 抛指定错误类型判定器:返回捕获实例是否为该类型。 +fn throws(src: &str, kind: &str) -> String { + format!( + "(function () {{ var threw = false; \ + try {{ {src}; }} catch (e) {{ threw = e instanceof {kind}; }} \ + return threw; }})()" + ) +} + +/// wait:SAB 等值 → "timed-out"(单线程退化,无真阻塞)。 +#[test] +fn eval_wait_timed_out() { + let mut vm = Vm::new(); + truthy( + &mut vm, + "(function () { \ + var ta = new Int32Array(new SharedArrayBuffer(8)); \ + ta[0] = 42; \ + return Atomics.wait(ta, 0, 42, 10) === 'timed-out' \ + && Atomics.wait(ta, 0, 42) === 'timed-out'; })()", + ); +} + +/// wait:不等值 → "not-equal"。 +#[test] +fn eval_wait_not_equal() { + let mut vm = Vm::new(); + truthy( + &mut vm, + "(function () { \ + var ta = new Int32Array(new SharedArrayBuffer(8)); \ + ta[0] = 7; \ + return Atomics.wait(ta, 0, 8, 0) === 'not-equal' \ + && Atomics.wait(ta, 0, 7.0, 5) === 'timed-out'; })()", + ); +} + +/// wait:非 SAB 视图 → TypeError(且先于 index 强转:毒 index 不评估)。 +#[test] +fn eval_wait_non_sab_type_error() { + let mut vm = Vm::new(); + truthy( + &mut vm, + &format!( + "(function () {{ \ + var ta = new Int32Array(new ArrayBuffer(8)); \ + var calls = 0; var poison = {{ valueOf: function () {{ calls = 1; throw new TypeError('p'); }} }}; \ + return {} && {} && calls === 0; }})()", + throws("Atomics.wait(ta, 0, 0, 1)", "TypeError"), + throws("Atomics.wait(ta, poison, 0, 1)", "TypeError"), + ), + ); +} + +/// wait:负 index → RangeError(非 "not-equal")。 +#[test] +fn eval_wait_negative_index_range_error() { + let mut vm = Vm::new(); + truthy( + &mut vm, + &format!( + "(function () {{ \ + var ta = new Int32Array(new SharedArrayBuffer(8)); \ + return {}; }})()", + throws("Atomics.wait(ta, -1, 0, 1)", "RangeError"), + ), + ); +} + +/// wait:index == length(元素字节区间超缓冲)→ RangeError。 +#[test] +fn eval_wait_out_of_range_range_error() { + let mut vm = Vm::new(); + truthy( + &mut vm, + &format!( + "(function () {{ \ + var ta = new Int32Array(new SharedArrayBuffer(16)); \ + return {} && {}; }})()", + throws("Atomics.wait(ta, 4, 0, 1)", "RangeError"), + throws("Atomics.wait(ta, 5, 0, 1)", "RangeError"), + ), + ); +} + +/// wait:BigInt64 视图合法(0n 等值 → "timed-out");BigUint64 视图 → TypeError。 +#[test] +fn eval_wait_bigint64_arm() { + let mut vm = Vm::new(); + truthy( + &mut vm, + &format!( + "(function () {{ \ + var ta = new BigInt64Array(new SharedArrayBuffer(8)); \ + ta[0] = 0n; \ + return Atomics.wait(ta, 0, 0n, 1) === 'timed-out' && {}; }})()", + throws("Atomics.wait(new BigUint64Array(new SharedArrayBuffer(8)), 0, 0n, 1)", "TypeError"), + ), + ); +} + +/// wait:浮点视图 → TypeError。 +#[test] +fn eval_wait_float_ta_type_error() { + let mut vm = Vm::new(); + truthy( + &mut vm, + &format!( + "(function () {{ \ + return {} && {}; }})()", + throws("Atomics.wait(new Float32Array(new SharedArrayBuffer(4)), 0, 0, 1)", "TypeError"), + throws("Atomics.wait(new Float64Array(new SharedArrayBuffer(8)), 0, 0, 1)", "TypeError"), + ), + ); +} + +/// wait:timeout 强转毒传播 + 归一(false/负值 → 0,等值仍 "timed-out"; +/// 对象 valueOf 抛错原样上抛)。 +#[test] +fn eval_wait_timeout_coercion() { + let mut vm = Vm::new(); + truthy( + &mut vm, + &format!( + "(function () {{ \ + var ta = new Int32Array(new SharedArrayBuffer(8)); \ + ta[0] = 1; \ + return Atomics.wait(ta, 0, 1, false) === 'timed-out' \ + && Atomics.wait(ta, 0, 1, -3) === 'timed-out' \ + && Atomics.wait(ta, 0, 1, NaN) === 'timed-out' \ + && {}; }})()", + throws( + "Atomics.wait(ta, 0, 1, {valueOf: function () { throw new TypeError('p'); }})", + "TypeError", + ), + ), + ); +} + +/// wait:值强转毒传播(BigInt64 视图配数值/非整串语义;Int32 视图配 BigInt)。 +#[test] +fn eval_wait_value_coercion() { + let mut vm = Vm::new(); + truthy( + &mut vm, + &format!( + "(function () {{ \ + var ta = new BigInt64Array(new SharedArrayBuffer(8)); \ + ta[0] = 5n; \ + var ab = new Int32Array(new SharedArrayBuffer(4)); \ + ab[0] = 3; \ + return Atomics.wait(ta, 0, '5', 1) === 'timed-out' \ + && Atomics.wait(ta, 0, true, 1) === 'not-equal' \ + && {} && {} && {}; }})()", + throws("Atomics.wait(ta, 0, 0.5, 1)", "TypeError"), + throws("Atomics.wait(ta, 0, null, 1)", "TypeError"), + throws("Atomics.wait(ab, 0, 1n, 1)", "TypeError"), + ), + ); +} + +/// notify:SAB 无 waiter 面 → 恒 0。 +#[test] +fn eval_notify_zero_no_waiters() { + let mut vm = Vm::new(); + truthy( + &mut vm, + "(function () { \ + var ta = new Int32Array(new SharedArrayBuffer(8)); \ + return Atomics.notify(ta, 0) === 0 && Atomics.notify(ta, 0, 3) === 0; })()", + ); +} + +/// notify:非 SAB 视图 → 返 0 不抛(immutable 同形)。 +#[test] +fn eval_notify_non_sab_returns_0() { + let mut vm = Vm::new(); + truthy( + &mut vm, + "(function () { \ + var ta = new Int32Array(new ArrayBuffer(8)); \ + return Atomics.notify(ta, 0) === 0 && Atomics.notify(ta, 0, 100) === 0; })()", + ); +} + +/// notify:负/越界 index → RangeError(先于非 SAB 早返 0)。 +#[test] +fn eval_notify_negative_index_range_error() { + let mut vm = Vm::new(); + truthy( + &mut vm, + &format!( + "(function () {{ \ + var sab = new Int32Array(new SharedArrayBuffer(8)); \ + var ab = new Int32Array(new ArrayBuffer(8)); \ + return {} && {} && {}; }})()", + throws("Atomics.notify(sab, -1)", "RangeError"), + throws("Atomics.notify(sab, 9, 1)", "RangeError"), + throws("Atomics.notify(ab, 9, 1)", "RangeError"), + ), + ); +} + +/// notify:count 各形(−3/+Inf/undefined/字符串/对象 → 0)+ 毒 count 先于 +/// 非 SAB 早返 0 抛错。 +#[test] +fn eval_notify_count_forms() { + let mut vm = Vm::new(); + truthy( + &mut vm, + &format!( + "(function () {{ \ + var sab = new Int32Array(new SharedArrayBuffer(8)); \ + var ab = new Int32Array(new ArrayBuffer(8)); \ + var poison = {{ valueOf: function () {{ throw new TypeError('p'); }} }}; \ + return Atomics.notify(sab, 0, -3) === 0 \ + && Atomics.notify(sab, 0, Infinity) === 0 \ + && Atomics.notify(sab, 0) === 0 \ + && Atomics.notify(sab, 0, '33') === 0 \ + && Atomics.notify(sab, 0, {{ valueOf: function () {{ return 8; }} }}) === 0 \ + && Atomics.notify(ab, 0, -1) === 0 \ + && {}; }})()", + throws("Atomics.notify(ab, 0, poison)", "TypeError"), + ), + ); +} + +/// 方法元数据钉:name/length(语料 descriptor 口径)。 +#[test] +fn atomics_wait_notify_name_and_length() { + let mut vm = Vm::new(); + truthy( + &mut vm, + "Atomics.wait.name === 'wait' && Atomics.wait.length === 4 \ + && Atomics.notify.name === 'notify' && Atomics.notify.length === 3", + ); +}