feat: 内核与内置层增 Atomics 对象槽位与十方法

This commit is contained in:
2026-09-23 18:29:51 +00:00
parent 2adac5547c
commit c5589c98c2
7 changed files with 320 additions and 9 deletions
+260
View File
@@ -0,0 +1,260 @@
//! Atomics 全局纯对象的 10 个原子方法(load/store/exchange/add/sub/and/or/
//! xor/compareExchange/isLockFree)。单线程引擎无锁层级,全部退化为
//! 读-改-写;宽度截断与元素写路径(`write_element`)结构同构。
use num_bigint::BigInt;
use oxide_types::object::TypedArrayKind;
use oxide_types::value::JsValue;
use crate::array_buffer::{buffer_payload_ptr, ArrayBufferPayload};
use crate::typed_array::{
get_typed_array_data, read_element, ta_element_value, ta_to_integer_or_infinity, ta_validate, write_element,
TypedArrayData,
};
use oxide_runtime_api::{NativeResult, VmHost};
macro_rules! native_try {
($expr:expr) => {
match $expr {
Ok(value) => value,
Err(err) => return NativeResult::Err(err),
}
};
}
fn type_error<H: VmHost>(vm: &mut H, msg: &str) -> JsValue {
crate::error::create_type_error(vm, msg)
}
fn range_error<H: VmHost>(vm: &mut H, msg: &str) -> JsValue {
crate::error::create_range_error(vm, msg)
}
/// 按寄存器下标取实参;越界(实参缺失)回落 undefined(JS 缺省参语义)。
fn arg_at<H: VmHost>(vm: &mut H, args: &[u8], idx: usize) -> JsValue {
if args.len() > idx {
vm.reg(args[idx])
} else {
JsValue::undefined()
}
}
/// 原子操作可接受的整数宽度:八整数种类(含 BigInt64/BigUint64);
/// 浮点(Float32/Float64)与 Uint8Clamped 视图非整数宽度,抛 TypeError。
fn is_integer_kind(kind: TypedArrayKind) -> bool {
matches!(
kind,
TypedArrayKind::Int8
| TypedArrayKind::Uint8
| TypedArrayKind::Int16
| TypedArrayKind::Uint16
| TypedArrayKind::Int32
| TypedArrayKind::Uint32
| TypedArrayKind::BigInt64
| TypedArrayKind::BigUint64
)
}
fn is_bigint_kind(kind: TypedArrayKind) -> bool {
matches!(kind, TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64)
}
/// 原子访问共享核:品牌 → 整数宽度守卫 → 索引 ToIntegerOrInfinity →
/// 相对字节偏移越界 RangeError →(写族)ValidateTypedArray 写守卫。
///
/// # 步骤
/// 1. `get_typed_array_data` 品牌校验(非 TA 对象抛 TypeError)。
/// 2. 整数宽度守卫(浮点/clamped 视图抛 TypeError)。
/// 3. 索引经 ToIntegerOrInfinity(valueOf/toString 抛错原值上抛,先于越界判)。
/// 4. 相对字节偏移 = view.byte_offset + index·bpe(f64 运算);越界
/// (`< 0` 或 `>= buffer 字节长`)抛 RangeError,detach 视同越界。
/// 5. 写族加查写守卫(immutable/detach 抛 TypeError)。
///
/// # 边界与前提
/// - 返回的载荷指针、视图与偏移在 session 独占期内有效,调用方同一语句内消费。
/// - 偏移已验证落在 `[0, buffer 字节长)`,`as usize` 安全。
///
/// # 副作用
/// 无(纯读;写守卫只读 immutable 位)。
fn resolve_atomic_access<H: VmHost>(
vm: &mut H, this_val: JsValue, writable: bool, index_val: JsValue,
) -> Result<(*mut ArrayBufferPayload, TypedArrayData, usize), JsValue> {
let view = get_typed_array_data(vm, this_val)?;
if !is_integer_kind(view.kind) {
return Err(type_error(vm, "Atomics operations require an integer TypedArray type"));
}
let index = ta_to_integer_or_infinity(vm, index_val)?;
// 缓冲字节长:AB/SAB 双认(buffer_payload_ptr),载荷缺失视同越界。
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 payload_ptr = match buffer_payload_ptr(unsafe { &*buffer_ptr }) {
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 offset = view.byte_offset as f64 + index * (view.kind.bytes_per_element() as f64);
if offset < 0.0 || offset >= buffer_len as f64 {
return Err(range_error(vm, "invalid indexed access to Atomics"));
}
if writable {
ta_validate(vm, view, true)?;
}
Ok((payload_ptr, view, offset as usize))
}
/// 读指定偏移元素并转为 JS 值;detach(`data` 为 `None`)返回 `None`。
fn atomic_read<H: VmHost>(
vm: &mut H, payload_ptr: *mut ArrayBufferPayload, kind: TypedArrayKind, offset: usize,
) -> Option<JsValue> {
// SAFETY: payload_ptr 经 resolve_atomic_access 校验为存活载荷盒,只读字节切片。
let bytes = unsafe { &*payload_ptr }.data.as_deref()?;
Some(read_element(vm, kind, bytes, offset))
}
/// 把已转换值按位模式写入指定偏移;detach 静默 no-op(越界语义已由偏移判收口)。
fn atomic_write<H: VmHost>(
vm: &mut H, payload_ptr: *mut ArrayBufferPayload, kind: TypedArrayKind, offset: usize, value: JsValue,
) {
// SAFETY: payload_ptr 经 resolve_atomic_access 校验为存活载荷盒,只写字节切片。
let Some(bytes) = unsafe { &mut *payload_ptr }.data.as_deref_mut() else {
return;
};
write_element(vm, kind, bytes, offset, value);
}
/// 读-改-写运算结果:整数宽度按算术/位运算在 i32 域(语料值 < 2^31,f64 精确),
/// BigInt 宽度在 BigInt 域;宽度截断由 [`atomic_write`] 收口,返回待写值。
fn arithmetic_write_value<H: VmHost>(
vm: &mut H, kind: TypedArrayKind, op: &str, old: JsValue, value: JsValue,
) -> JsValue {
if is_bigint_kind(kind) {
let old_bi = vm.bigint_value(old).clone();
let val_bi = vm.bigint_value(value).clone();
let r: BigInt = match op {
"add" => &old_bi + &val_bi,
"sub" => &old_bi - &val_bi,
"and" => &old_bi & &val_bi,
"or" => &old_bi | &val_bi,
"xor" => &old_bi ^ &val_bi,
_ => unreachable!("未知原子运算"),
};
return vm.new_bigint(r);
}
let old_i = oxide_runtime_api::to_number(old) as i32;
let val_i = oxide_runtime_api::to_number(value) as i32;
let r = match op {
"add" => old_i.wrapping_add(val_i),
"sub" => old_i.wrapping_sub(val_i),
"and" => old_i & val_i,
"or" => old_i | val_i,
"xor" => old_i ^ val_i,
_ => unreachable!("未知原子运算"),
};
JsValue::int(r)
}
/// `Atomics.load(typedArray, index)`:读指定索引元素。
pub fn atomics_load<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = arg_at(vm, args, 0);
let index_val = arg_at(vm, args, 1);
let (payload_ptr, view, offset) = native_try!(resolve_atomic_access(vm, this_val, false, index_val));
NativeResult::Ok(atomic_read(vm, payload_ptr, view.kind, offset).unwrap_or(JsValue::undefined()))
}
/// `Atomics.store(typedArray, index, value)`:写值后读回返回(宽度归一,-0 → +0)。
pub fn atomics_store<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = arg_at(vm, args, 0);
let index_val = arg_at(vm, args, 1);
let value_val = arg_at(vm, args, 2);
let (payload_ptr, view, offset) = native_try!(resolve_atomic_access(vm, this_val, true, index_val));
let value = native_try!(ta_element_value(vm, view.kind, value_val));
atomic_write(vm, payload_ptr, view.kind, offset, value);
NativeResult::Ok(atomic_read(vm, payload_ptr, view.kind, offset).unwrap_or(value))
}
/// `Atomics.exchange(typedArray, index, value)`:读旧值、写新值、返回旧值。
pub fn atomics_exchange<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = arg_at(vm, args, 0);
let index_val = arg_at(vm, args, 1);
let value_val = arg_at(vm, args, 2);
let (payload_ptr, view, offset) = native_try!(resolve_atomic_access(vm, this_val, true, index_val));
let old = atomic_read(vm, payload_ptr, view.kind, offset).unwrap_or(JsValue::undefined());
let value = native_try!(ta_element_value(vm, view.kind, value_val));
atomic_write(vm, payload_ptr, view.kind, offset, value);
NativeResult::Ok(old)
}
/// `Atomics.add(typedArray, index, value)`:读旧值、加 value 写回、返回旧值。
pub fn atomics_add<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
atomic_arithmetic(vm, args, "add")
}
/// `Atomics.sub(typedArray, index, value)`:读旧值、减 value 写回、返回旧值。
pub fn atomics_sub<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
atomic_arithmetic(vm, args, "sub")
}
/// `Atomics.and(typedArray, index, value)`:读旧值、与 value 按位与写回、返回旧值。
pub fn atomics_and<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
atomic_arithmetic(vm, args, "and")
}
/// `Atomics.or(typedArray, index, value)`:读旧值、与 value 按位或写回、返回旧值。
pub fn atomics_or<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
atomic_arithmetic(vm, args, "or")
}
/// `Atomics.xor(typedArray, index, value)`:读旧值、与 value 按位异或写回、返回旧值。
pub fn atomics_xor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
atomic_arithmetic(vm, args, "xor")
}
/// add/sub/and/or/xor 共享核:读旧值 → 运算值转换 → 读-改-写 → 返回旧值。
fn atomic_arithmetic<H: VmHost>(vm: &mut H, args: &[u8], op: &str) -> NativeResult {
let this_val = arg_at(vm, args, 0);
let index_val = arg_at(vm, args, 1);
let value_val = arg_at(vm, args, 2);
let (payload_ptr, view, offset) = native_try!(resolve_atomic_access(vm, this_val, true, index_val));
let old = atomic_read(vm, payload_ptr, view.kind, offset).unwrap_or(JsValue::undefined());
let value = native_try!(ta_element_value(vm, view.kind, value_val));
let result = arithmetic_write_value(vm, view.kind, op, old, value);
atomic_write(vm, payload_ptr, view.kind, offset, result);
NativeResult::Ok(old)
}
/// `Atomics.compareExchange(typedArray, index, expected, replacement)`:读旧值;
/// 相等(数值宽度 `==`、BigInt 宽度 BigInt 相等)则写 replacement;返回旧值。
pub fn atomics_compare_exchange<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let this_val = arg_at(vm, args, 0);
let index_val = arg_at(vm, args, 1);
let expected_val = arg_at(vm, args, 2);
let replacement_val = arg_at(vm, args, 3);
let (payload_ptr, view, offset) = native_try!(resolve_atomic_access(vm, this_val, true, index_val));
let old = atomic_read(vm, payload_ptr, view.kind, offset).unwrap_or(JsValue::undefined());
let expected = native_try!(ta_element_value(vm, view.kind, expected_val));
let matched = if is_bigint_kind(view.kind) {
let old_bi = vm.bigint_value(old).clone();
let expected_bi = vm.bigint_value(expected).clone();
old_bi.cmp(&expected_bi) == std::cmp::Ordering::Equal
} else {
oxide_runtime_api::to_number(old) == oxide_runtime_api::to_number(expected)
};
if matched {
let replacement = native_try!(ta_element_value(vm, view.kind, replacement_val));
atomic_write(vm, payload_ptr, view.kind, offset, replacement);
}
NativeResult::Ok(old)
}
/// `Atomics.isLockFree(size)`:单线程引擎无锁层级,`size ∈ {1,2,4,8}` 恒真,余假。
pub fn atomics_is_lock_free<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let size_val = arg_at(vm, args, 1);
let n = native_try!(ta_to_integer_or_infinity(vm, size_val));
NativeResult::Ok(JsValue::bool(matches!(n, 1.0 | 2.0 | 4.0 | 8.0)))
}
+4 -2
View File
@@ -5,13 +5,15 @@
//! ArrayBuffer、DataView、Reflect、Function、Error 与全局函数(URI/escape 等)。
//! 每个内置方法以 `fn xxx<H: VmHost>(vm, args: &[u8]) -> NativeResult` 形式暴露,
//! 由上层(oxide_vm / oxide_api)注册为 JS 全局对象上的 native function。
//! 架构性延后的特性(Proxy、WeakMap、WeakSet、WeakRef、FinalizationRegistry、
//! Atomics)在 `stubs` 中仅提供占位实现,调用时抛 TypeError。
//! 架构性延后的特性(Proxy、WeakMap、WeakSet、WeakRef、FinalizationRegistry)
//! 在 `stubs` 中仅提供占位实现,调用时抛 TypeError。
/// Array 内置对象实现(目录模块:common 共享助手、from 构造器与静态方法、element 元素变更、iterate 高阶迭代、sort_iterator 排序与迭代协议、immutable ES2023 不可变方法族)。
pub mod array;
/// ArrayBuffer 内置对象实现(字节缓冲区与 byteLength/slice/isView)。
pub mod array_buffer;
/// Atomics 全局纯对象实现(10 个原子方法,单线程退化读-改-写)。
pub mod atomics;
/// BigInt 内置对象实现(constructor 与 prototype 的 toString)。
pub mod bigint;
/// Boolean 内置对象实现(constructor 与 prototype 的 valueOf/toString)。
+5 -1
View File
@@ -360,7 +360,7 @@ pub(crate) fn wire_builtin_world_links(world: &BuiltinWorld) {
}
let obj_proto_val = JsValue::from_js_object(world.object_proto.as_ptr() as *mut JsObject);
let non_object_protos: [&P<JsObject>; 26] = [
let non_object_protos: [&P<JsObject>; 27] = [
&world.array_proto,
&world.function_proto,
&world.string_proto,
@@ -374,6 +374,7 @@ pub(crate) fn wire_builtin_world_links(world: &BuiltinWorld) {
&world.regexp_proto,
&world.array_buffer_proto,
&world.shared_array_buffer_proto,
&world.atomics_object,
&world.data_view_proto,
&world.typed_array_proto,
&world.instant_proto,
@@ -484,6 +485,8 @@ impl BuiltinWorld {
let json_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let atomics_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let console_object = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()));
let (date_proto, date_constructor) = make_named_pair(string_forge, shape_forge, labels, "Date");
@@ -583,6 +586,7 @@ impl BuiltinWorld {
array_buffer_proto,
shared_array_buffer_proto,
shared_array_buffer_constructor,
atomics_object,
data_view_constructor,
data_view_proto,
typed_array_proto: typed_arrays.typed_array_proto,
@@ -314,6 +314,11 @@ impl BuiltinWorld {
} else {
(current.shared_array_buffer_proto.clone(), current.shared_array_buffer_constructor.clone())
};
let atomics_object = if dirty.atomics {
P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()))
} else {
current.atomics_object.clone()
};
let (data_view_proto, data_view_constructor) = if dirty.data_view {
make_named_pair(string_forge, shape_forge, labels, "DataView")
} else {
@@ -518,6 +523,7 @@ impl BuiltinWorld {
array_buffer_proto,
shared_array_buffer_proto,
shared_array_buffer_constructor,
atomics_object,
data_view_constructor,
data_view_proto,
typed_array_proto: typed_arrays.typed_array_proto,
+33 -1
View File
@@ -52,6 +52,9 @@ pub struct BuiltinWorld {
/// `make_named_pair` 成对构造,方法/访问器由绑定层安装。
pub shared_array_buffer_proto: P<JsObject>,
pub shared_array_buffer_constructor: P<JsObject>,
/// `Atomics` 全局纯对象(非函数):10 个原子方法由绑定层安装,
/// `@@toStringTag` 与原型链在 `wire_builtin_world_links` 落到 Object.prototype。
pub atomics_object: P<JsObject>,
pub data_view_constructor: P<JsObject>,
pub data_view_proto: P<JsObject>,
pub typed_array_proto: P<JsObject>,
@@ -347,7 +350,7 @@ impl BuiltinWorld {
/// session 收尾(`teardown_heap_data`)与选择性重建收尾(`retire_replaced`)
/// 的 P 字段枚举唯一入口:`BuiltinWorld` 新增 P 字段须在此同步补一行,否则
/// 收尾时该字段属性区无法释放、重建原型槽改写/释放漏掉该字段。
pub(crate) fn all_p_fields(&self) -> [&P<JsObject>; 106] {
pub(crate) fn all_p_fields(&self) -> [&P<JsObject>; 107] {
[
&self.object_proto,
&self.array_proto,
@@ -386,6 +389,7 @@ impl BuiltinWorld {
&self.array_buffer_proto,
&self.shared_array_buffer_proto,
&self.shared_array_buffer_constructor,
&self.atomics_object,
&self.data_view_constructor,
&self.data_view_proto,
&self.typed_array_proto,
@@ -540,6 +544,7 @@ impl BuiltinWorld {
BuiltinId::ArrayBufferProto => &self.array_buffer_proto,
BuiltinId::SharedArrayBufferProto => &self.shared_array_buffer_proto,
BuiltinId::SharedArrayBufferConstructor => &self.shared_array_buffer_constructor,
BuiltinId::AtomicsObject => &self.atomics_object,
BuiltinId::DataViewConstructor => &self.data_view_constructor,
BuiltinId::DataViewProto => &self.data_view_proto,
BuiltinId::TypedArrayProto => &self.typed_array_proto,
@@ -699,4 +704,31 @@ mod tests {
));
assert!(std::ptr::eq(ab_proto, session.builtin_world.array_buffer_proto.as_ptr() as *mut JsObject));
}
/// Atomics 家族脏线冒烟:仅 Atomics 单例世代漂移 → 选择性重置只判 Atomics
/// 家族脏,ArrayBuffer 家族指针原样保留(钉 dirty_since_snapshot 家族线接线)。
#[test]
fn atomics_family_selective_rebuild() {
use crate::kernel::{KernelConfig, KernelCore, KernelSession};
let core = KernelCore::new(KernelConfig::minimal());
let mut session = KernelSession::new(&core);
let old_world = std::sync::Arc::clone(&session.builtin_world);
let atomics = old_world.atomics_object.as_ptr() as *mut JsObject;
let ab_proto = old_world.array_buffer_proto.as_ptr() as *mut JsObject;
unsafe {
(&mut *atomics).bump_generation();
}
let dirty = session.selective_reset(&core);
assert!(dirty.atomics);
assert!(!dirty.array_buffer);
assert!(!dirty.shared_array_buffer);
// 脏家族换新对象,未脏家族指针原样保留。
assert!(!std::ptr::eq(
atomics,
session.builtin_world.atomics_object.as_ptr() as *mut JsObject
));
assert!(std::ptr::eq(ab_proto, session.builtin_world.array_buffer_proto.as_ptr() as *mut JsObject));
}
}
+11 -5
View File
@@ -11,7 +11,7 @@ use crate::builtin::BuiltinWorld;
/// 维护注意:每个新增的 `BuiltinWorld` 对象字段都必须加到这里以及
/// `KernelSession::dirty_since_snapshot()`,以便选择性重置重建正确的
/// builtin 家族。
pub const NUM_BUILTINS: usize = 96;
pub const NUM_BUILTINS: usize = 97;
/// 内置对象枚举 id,与 `BuiltinWorld` 中的存储槽一一对应。
///
@@ -117,6 +117,7 @@ pub enum BuiltinId {
PlainYearMonthProto = 93,
SharedArrayBufferProto = 94,
SharedArrayBufferConstructor = 95,
AtomicsObject = 96,
}
impl BuiltinId {
@@ -220,6 +221,7 @@ impl BuiltinId {
BuiltinId::PlainYearMonthProto,
BuiltinId::SharedArrayBufferProto,
BuiltinId::SharedArrayBufferConstructor,
BuiltinId::AtomicsObject,
];
}
@@ -288,6 +290,7 @@ pub struct BuiltinDirtySet {
pub regexp: bool,
pub array_buffer: bool,
pub shared_array_buffer: bool,
pub atomics: bool,
pub data_view: bool,
pub typed_array_family: bool,
pub temporal: bool,
@@ -320,6 +323,7 @@ impl BuiltinDirtySet {
regexp: true,
array_buffer: true,
shared_array_buffer: true,
atomics: true,
data_view: true,
typed_array_family: true,
temporal: true,
@@ -347,6 +351,7 @@ impl BuiltinDirtySet {
|| self.regexp
|| self.array_buffer
|| self.shared_array_buffer
|| self.atomics
|| self.data_view
|| self.typed_array_family
|| self.temporal
@@ -364,18 +369,19 @@ impl BuiltinDirtySet {
mod tests {
use super::*;
/// 槽对齐面:表尾追加 SAB 家族后总槽数 96,既有 94 个判别值零位移,
/// 槽对齐面:表尾追加 Atomics 单例后总槽数 97,既有 96 个判别值零位移,
/// 快照数组随 NUM_BUILTINS 自动扩维、逐槽对齐。
#[test]
fn builtin_snapshot_all_slots_aligned() {
assert_eq!(NUM_BUILTINS, 96);
assert_eq!(NUM_BUILTINS, 97);
assert_eq!(BuiltinId::ALL.len(), NUM_BUILTINS);
// 前 94 项判别值 0-93 逐项不变(尾追加零位移)。
for i in 0..94usize {
// 前 96 项判别值 0-95 逐项不变(尾追加零位移)。
for i in 0..96usize {
assert_eq!(BuiltinId::ALL[i] as usize, i);
}
assert_eq!(BuiltinId::ALL[94], BuiltinId::SharedArrayBufferProto);
assert_eq!(BuiltinId::ALL[95], BuiltinId::SharedArrayBufferConstructor);
assert_eq!(BuiltinId::ALL[96], BuiltinId::AtomicsObject);
// 快照经 session 全量构造路径采集,generations 数组维度 = 槽数。
use crate::kernel::{KernelConfig, KernelCore, KernelSession};
@@ -159,6 +159,7 @@ impl KernelSession {
|| gen(BuiltinId::ArrayBufferProto) != snap(BuiltinId::ArrayBufferProto),
shared_array_buffer: gen(BuiltinId::SharedArrayBufferProto) != snap(BuiltinId::SharedArrayBufferProto)
|| gen(BuiltinId::SharedArrayBufferConstructor) != snap(BuiltinId::SharedArrayBufferConstructor),
atomics: gen(BuiltinId::AtomicsObject) != snap(BuiltinId::AtomicsObject),
data_view: gen(BuiltinId::DataViewConstructor) != snap(BuiltinId::DataViewConstructor)
|| gen(BuiltinId::DataViewProto) != snap(BuiltinId::DataViewProto),
typed_array_family: gen(BuiltinId::TypedArrayProto) != snap(BuiltinId::TypedArrayProto)