mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: Atomics.wait/notify 单线程退化面(等值 timed-out / 非 SAB 抛错返 0)
This commit is contained in:
@@ -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<H: VmHost>(
|
||||
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<H: VmHost>(
|
||||
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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ta_val = arg_at(vm, args, 1);
|
||||
|
||||
@@ -24,6 +24,12 @@ pub(crate) fn is_skipped(meta: &TestMeta) -> Option<String> {
|
||||
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 测试放行——很多在严格模式下仍可通过;运行时跳过逻辑会捕获失败。
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ pub fn bind_atomics(core: &Arc<KernelCore>, session: &KernelSession, global: &mu
|
||||
("xor", oxide_builtins::atomics::atomics_xor::<crate::vm::Vm>, 3),
|
||||
("compareExchange", oxide_builtins::atomics::atomics_compare_exchange::<crate::vm::Vm>, 4),
|
||||
("isLockFree", oxide_builtins::atomics::atomics_is_lock_free::<crate::vm::Vm>, 1),
|
||||
("wait", oxide_builtins::atomics::atomics_wait::<crate::vm::Vm>, 4),
|
||||
("notify", oxide_builtins::atomics::atomics_notify::<crate::vm::Vm>, 3),
|
||||
);
|
||||
|
||||
let a_val = JsValue::from_js_object(atomics_ptr);
|
||||
|
||||
@@ -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<JsValue, String> {
|
||||
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",
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user