feat: SharedArrayBuffer 最小构造器与载荷生命周期

This commit is contained in:
2026-09-23 12:44:05 +00:00
parent 7457740c36
commit 3d50306853
8 changed files with 121 additions and 7 deletions
+82
View File
@@ -159,6 +159,88 @@ pub fn drop_array_buffer_native(obj: &mut JsObject) -> u64 {
bytes
}
/// 分配定长 SharedArrayBuffer 对象(零填充字节缓冲,`max_byte_length` 存 0)。
pub(crate) fn new_shared_array_buffer<H: VmHost>(vm: &mut H, data: Vec<u8>) -> *mut JsObject {
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null());
obj.type_tag = JsObject::OBJ_TYPE_SHARED_ARRAY_BUFFER;
let payload = ArrayBufferPayload {
data: Some(data),
max_byte_length: 0,
immutable: false,
};
let payload_ptr = Box::into_raw(Box::new(payload));
// SAFETY: SharedArrayBuffer 对象不可调用,native_fn 槽复用为不透明载荷盒
// 指针,与 ArrayBuffer 存储模式一致。
obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) }));
vm.alloc_object(obj)
}
pub(crate) fn shared_array_buffer_payload_ptr(obj: &JsObject) -> Option<*mut ArrayBufferPayload> {
if !obj.is_shared_array_buffer_obj() {
return None;
}
obj.native_fn().map(|ptr| ptr.as_ptr() as *mut ArrayBufferPayload)
}
/// SharedArrayBuffer 载荷盒字节数(`native_fn` 槽);非 SAB 或已释放 → 0。
pub fn shared_array_buffer_native_size(obj: &JsObject) -> u64 {
let payload_ptr = match shared_array_buffer_payload_ptr(obj) {
Some(p) => p,
None => return 0,
};
if payload_ptr.is_null() {
return 0;
}
// SAFETY: payload_ptr 非空且指向存活载荷盒。
let payload = unsafe { &*payload_ptr };
(std::mem::size_of::<ArrayBufferPayload>() + payload.data.as_ref().map_or(0, |d| d.capacity())) as u64
}
/// 释放 SharedArrayBuffer 载荷盒(native_fn 槽),返回字节数;槽置空后重复调用零释放。
pub fn drop_shared_array_buffer_native(obj: &mut JsObject) -> u64 {
let bytes = shared_array_buffer_native_size(obj);
if bytes == 0 {
return 0;
}
let Some(payload_ptr) = shared_array_buffer_payload_ptr(obj) else {
return 0;
};
// SAFETY: payload_ptr 非空(size 已验证),Box::from_raw 恰好释放一次。
let payload = unsafe { Box::from_raw(payload_ptr) };
drop(payload);
obj.set_native_fn(None);
bytes
}
/// 克隆 SharedArrayBuffer 载荷(字节缓冲)到新对象(跨 epoch 克隆流程用)。
pub fn clone_shared_array_buffer_native(old_obj: &JsObject, new_obj: &mut JsObject) {
let Some(ptr) = shared_array_buffer_payload_ptr(old_obj) else {
return;
};
if ptr.is_null() {
return;
}
// SAFETY: ptr 非空且指向存活载荷盒(克隆/晋升臂由 GC 持有源对象)。
let cloned = unsafe { &*ptr }.clone();
let cloned_ptr = Box::into_raw(Box::new(cloned));
new_obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(cloned_ptr as *const ()) }));
}
/// `SharedArrayBuffer(length)`:构造语义仅 `new`;length 缺省 0,经 ToIndex
/// 传播式(强转副作用原值上抛),超引擎上界 → RangeError。最小实现:字节
/// 缓冲零填充定长,无 resizable 选项面与 `[[ArrayBufferMaxByteLength]]`。
pub fn shared_array_buffer_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
if !vm.constructing_native() {
return NativeResult::Err(crate::error::create_type_error(vm, "SharedArrayBuffer must be called with new"));
}
let length = if args.len() > 1 { native_try!(to_index(vm, vm.reg(args[1]))) } else { 0 };
if length > MAX_ARRAY_BUFFER_LENGTH {
return NativeResult::Err(crate::error::create_range_error(vm, "invalid SharedArrayBuffer length"));
}
let obj_ptr = new_shared_array_buffer(vm, vec![0; length]);
NativeResult::Ok(JsValue::from_js_object(obj_ptr))
}
/// 读入口:校验 receiver 为 ArrayBuffer 并取载荷指针。detach(载荷 `data` 为
/// `None`)不在此分叉,由消费方现读 `data` 时按既有错误形态处理。
pub(crate) fn array_buffer_payload<H: VmHost>(
-4
View File
@@ -25,10 +25,6 @@ pub fn weakref_stub<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
pub fn finalization_registry_stub<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
stub_error(vm, "FinalizationRegistry")
}
/// `SharedArrayBuffer` 全局占位:始终抛 TypeError。按设计延后实现。
pub fn shared_array_buffer_stub<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
stub_error(vm, "SharedArrayBuffer")
}
/// `Atomics` 全局占位:始终抛 TypeError。按设计延后实现。
pub fn atomics_stub<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
stub_error(vm, "Atomics")
+10
View File
@@ -208,6 +208,11 @@ impl JsObject {
/// 与宽松相等按 undefined 处理,Type 与其余强制转换按普通对象处理;无额外
/// 内部载荷。
pub const OBJ_TYPE_HTML_DDA: u8 = 29;
/// SharedArrayBuffer 对象:与 ArrayBuffer 同构的字节载荷盒(`native_fn`
/// 槽存 `ArrayBufferPayload`),但无 `[[ArrayBufferMaxByteLength]]`/定长
/// 语义面;接收者品牌守卫按本 tag 拒绝,ArrayBuffer 方法族对其抛
/// TypeError。释放与深拷贝守卫同 ArrayBuffer。
pub const OBJ_TYPE_SHARED_ARRAY_BUFFER: u8 = 30;
/// `is_session_epoch` 字段中的 session 标记位。
pub const SESSION_EPOCH_BIT: u8 = 0x01;
/// `is_session_epoch` 字段中的 GC 标记位。
@@ -268,6 +273,11 @@ impl JsObject {
pub fn is_array_buffer_obj(&self) -> bool {
self.type_tag == Self::OBJ_TYPE_ARRAY_BUFFER
}
/// 是否 SharedArrayBuffer 对象。
#[inline]
pub fn is_shared_array_buffer_obj(&self) -> bool {
self.type_tag == Self::OBJ_TYPE_SHARED_ARRAY_BUFFER
}
/// 是否 DataView 对象。
#[inline]
pub fn is_data_view_obj(&self) -> bool {
+1 -1
View File
@@ -20,7 +20,7 @@ const STUBS: [(&str, *const (), u8); 7] = [
),
(
"SharedArrayBuffer",
oxide_builtins::stubs::shared_array_buffer_stub::<crate::vm::Vm> as *const (),
oxide_builtins::array_buffer::shared_array_buffer_constructor::<crate::vm::Vm> as *const (),
1,
),
("Atomics", oxide_builtins::stubs::atomics_stub::<crate::vm::Vm> as *const (), 0),
+1 -1
View File
@@ -882,7 +882,7 @@ fn bind_stub_globals(core: &Arc<KernelCore>, session: &KernelSession, global: &m
),
(
"SharedArrayBuffer",
oxide_builtins::stubs::shared_array_buffer_stub::<crate::vm::Vm> as *const (),
oxide_builtins::array_buffer::shared_array_buffer_constructor::<crate::vm::Vm> as *const (),
1,
),
("Atomics", oxide_builtins::stubs::atomics_stub::<crate::vm::Vm> as *const (), 0),
+3
View File
@@ -67,6 +67,9 @@ impl Vm {
} else if src_ref.is_array_buffer_obj() {
// 字节缓冲深拷贝到新对象:源 Vec 随 epoch 释放,互不共享。
array_buffer::clone_array_buffer_native(src_ref, dst_ref);
} else if src_ref.is_shared_array_buffer_obj() {
// 字节缓冲深拷贝到新对象:源 Vec 随 epoch 释放,互不共享。
array_buffer::clone_shared_array_buffer_native(src_ref, dst_ref);
} else if src_ref.is_generator_obj() {
// 生成器状态盒深拷贝到新对象:源盒随 epoch 释放,互不共享。
crate::generator::clone_generator_native_with_rewrite(src_ref, dst_ref, |value| {
+4
View File
@@ -116,6 +116,7 @@ impl SessionGc {
bytes += module::module_ns_native_size(obj);
bytes += disposable_stack::disposable_stack_native_size(obj);
bytes += array_buffer::array_buffer_native_size(obj);
bytes += array_buffer::shared_array_buffer_native_size(obj);
bytes += regexp::regexp_native_size(obj);
bytes += typed_array::typed_array_native_size(obj);
bytes += data_view::data_view_native_size(obj);
@@ -537,6 +538,7 @@ impl SessionGc {
freed_bytes += module::drop_module_ns_native(obj);
freed_bytes += disposable_stack::drop_dispose_native(obj);
freed_bytes += array_buffer::drop_array_buffer_native(obj);
freed_bytes += array_buffer::drop_shared_array_buffer_native(obj);
freed_bytes += regexp::drop_regexp_native(obj);
freed_bytes += typed_array::drop_typed_array_native(obj);
freed_bytes += data_view::drop_data_view_native(obj);
@@ -625,6 +627,8 @@ impl SessionGc {
disposable_stack::clone_dispose_native_with_rewrite(old_ref, new_ref, |value| value);
} else if old_ref.is_array_buffer_obj() {
array_buffer::clone_array_buffer_native(old_ref, new_ref);
} else if old_ref.is_shared_array_buffer_obj() {
array_buffer::clone_shared_array_buffer_native(old_ref, new_ref);
} else if old_ref.is_typed_array_obj() {
typed_array::clone_typed_array_native_with_rewrite(old_ref, new_ref, |value| value);
} else if old_ref.is_data_view_obj() {
@@ -1134,7 +1134,26 @@ fn ab_sti_brand_family() {
var t2 = false, t3 = false; \
var nab = new ArrayBuffer(8); \
try { new nab.sliceToImmutable(); t3 = true; } catch (e) { t2 = e instanceof TypeError; t3 = false; } \
return ok && calls.length === 0 && t2 && calls2.length === 0 && !t3; })()",
return ok && calls.length === 0 && t2 && calls2.length === 0 && !t3; })()",
)
.unwrap();
assert!(result.as_bool());
}
/// SharedArrayBuffer 最小构造钉:`new SharedArrayBuffer(n)` 建 n 字节缓冲,
/// 非 ArrayBuffer 品牌(ArrayBuffer 方法族对其先读参后抛 TypeError)。
#[test]
fn sab_constructor_and_brand_rejection() {
let mut vm = Vm::new();
let result = eval(
&mut vm,
"(function () { var sab = new SharedArrayBuffer(4); \
var calls = []; \
var start = { valueOf: function () { calls.push('s'); return 0; } }; \
var t = false; \
try { ArrayBuffer.prototype.sliceToImmutable.call(sab, start); t = true; } \
catch (e) { t = e instanceof TypeError; } \
return t && calls.length === 0; })()",
)
.unwrap();
assert!(result.as_bool());