From 3d503068535fafd8bbd48eaed36b60c5500bea43 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Wed, 23 Sep 2026 12:44:05 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20SharedArrayBuffer=20=E6=9C=80=E5=B0=8F?= =?UTF-8?q?=E6=9E=84=E9=80=A0=E5=99=A8=E4=B8=8E=E8=BD=BD=E8=8D=B7=E7=94=9F?= =?UTF-8?q?=E5=91=BD=E5=91=A8=E6=9C=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_builtins/src/array_buffer.rs | 82 +++++++++++++++++++ crates/oxide_builtins/src/stubs.rs | 4 - crates/oxide_types/src/object.rs | 10 +++ crates/oxide_vm/src/bindings/bind_stubs.rs | 2 +- crates/oxide_vm/src/bindings/mod.rs | 2 +- crates/oxide_vm/src/session_arena.rs | 3 + crates/oxide_vm/src/session_gc.rs | 4 + .../tests/builtins_arraybuffer_state_tests.rs | 21 ++++- 8 files changed, 121 insertions(+), 7 deletions(-) diff --git a/crates/oxide_builtins/src/array_buffer.rs b/crates/oxide_builtins/src/array_buffer.rs index 2338fa55..84bec7c6 100644 --- a/crates/oxide_builtins/src/array_buffer.rs +++ b/crates/oxide_builtins/src/array_buffer.rs @@ -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(vm: &mut H, data: Vec) -> *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::() + 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(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( diff --git a/crates/oxide_builtins/src/stubs.rs b/crates/oxide_builtins/src/stubs.rs index 4de8493c..2e7bbfc3 100644 --- a/crates/oxide_builtins/src/stubs.rs +++ b/crates/oxide_builtins/src/stubs.rs @@ -25,10 +25,6 @@ pub fn weakref_stub(vm: &mut H, _args: &[u8]) -> NativeResult { pub fn finalization_registry_stub(vm: &mut H, _args: &[u8]) -> NativeResult { stub_error(vm, "FinalizationRegistry") } -/// `SharedArrayBuffer` 全局占位:始终抛 TypeError。按设计延后实现。 -pub fn shared_array_buffer_stub(vm: &mut H, _args: &[u8]) -> NativeResult { - stub_error(vm, "SharedArrayBuffer") -} /// `Atomics` 全局占位:始终抛 TypeError。按设计延后实现。 pub fn atomics_stub(vm: &mut H, _args: &[u8]) -> NativeResult { stub_error(vm, "Atomics") diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index d5903ae8..de6488d3 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -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 { diff --git a/crates/oxide_vm/src/bindings/bind_stubs.rs b/crates/oxide_vm/src/bindings/bind_stubs.rs index ef683bb5..848130e6 100644 --- a/crates/oxide_vm/src/bindings/bind_stubs.rs +++ b/crates/oxide_vm/src/bindings/bind_stubs.rs @@ -20,7 +20,7 @@ const STUBS: [(&str, *const (), u8); 7] = [ ), ( "SharedArrayBuffer", - oxide_builtins::stubs::shared_array_buffer_stub:: as *const (), + oxide_builtins::array_buffer::shared_array_buffer_constructor:: as *const (), 1, ), ("Atomics", oxide_builtins::stubs::atomics_stub:: as *const (), 0), diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 8baa8220..7b58ef57 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -882,7 +882,7 @@ fn bind_stub_globals(core: &Arc, session: &KernelSession, global: &m ), ( "SharedArrayBuffer", - oxide_builtins::stubs::shared_array_buffer_stub:: as *const (), + oxide_builtins::array_buffer::shared_array_buffer_constructor:: as *const (), 1, ), ("Atomics", oxide_builtins::stubs::atomics_stub:: as *const (), 0), diff --git a/crates/oxide_vm/src/session_arena.rs b/crates/oxide_vm/src/session_arena.rs index d0b55329..5e19e4de 100644 --- a/crates/oxide_vm/src/session_arena.rs +++ b/crates/oxide_vm/src/session_arena.rs @@ -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| { diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index f8aa5ce3..f1f5de15 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -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() { diff --git a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs index 49f217bf..e167643e 100644 --- a/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs +++ b/crates/oxide_vm/tests/builtins_arraybuffer_state_tests.rs @@ -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());