From 10628928bc8e795e8d190cd81d244a3857d2ce36 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Fri, 9 Oct 2026 03:34:56 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=AB=AF=E5=8F=A3=E8=BD=BD=E8=8D=B7?= =?UTF-8?q?=E7=9B=92=E6=8E=A5=E5=85=A5=20GC=20=E5=AE=B6=E6=97=8F=E8=A1=A8?= =?UTF-8?q?=E5=B9=B6=E7=A7=BB=E6=B6=88=E6=81=AF=E9=98=9F=E5=88=97=E8=87=B3?= =?UTF-8?q?=E5=86=85=E6=A0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 38 ++-- Cargo.toml | 2 +- crates/oxide_builtins/src/lib.rs | 2 + crates/oxide_builtins/src/message_channel.rs | 166 ++++++++++++++++++ crates/oxide_kernel/src/lib.rs | 2 + .../src/message_queue.rs | 0 crates/oxide_types/src/object.rs | 8 + crates/oxide_vm/src/lib.rs | 5 +- crates/oxide_vm/src/native_box_dispatch.rs | 19 +- crates/oxide_vm/src/session_gc.rs | 6 +- 10 files changed, 220 insertions(+), 28 deletions(-) create mode 100644 crates/oxide_builtins/src/message_channel.rs rename crates/{oxide_vm => oxide_kernel}/src/message_queue.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 57862617..fd11b67c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.84" +version = "0.8.85" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.84" +version = "0.8.85" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.84" +version = "0.8.85" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.84" +version = "0.8.85" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.84" +version = "0.8.85" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.84" +version = "0.8.85" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.84" +version = "0.8.85" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.84" +version = "0.8.85" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.8.84" +version = "0.8.85" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.8.84" +version = "0.8.85" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.84" +version = "0.8.85" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.84" +version = "0.8.85" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.84" +version = "0.8.85" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.84" +version = "0.8.85" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.84" +version = "0.8.85" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.84" +version = "0.8.85" dependencies = [ "num-bigint", "num-traits", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.84" +version = "0.8.85" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.84" +version = "0.8.85" dependencies = [ "bumpalo", "dashmap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.84" +version = "0.8.85" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index dd106fbf..28e63c0b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.8.84" +version = "0.8.85" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/lib.rs b/crates/oxide_builtins/src/lib.rs index 7829b9f1..26c70c15 100644 --- a/crates/oxide_builtins/src/lib.rs +++ b/crates/oxide_builtins/src/lib.rs @@ -44,6 +44,8 @@ pub mod json; pub mod map; /// Math 内置对象实现(数学函数)。 pub mod math; +/// MessagePort 载荷盒(mpsc 双端与对端端口边,GC 三自由函数族)。 +pub mod message_channel; /// MessageValue 跨 realm 值传递抽象(`detach_message` / `rehydrate_message` 与 /// `Send` 中间表示,供 worker 调度跨线程消息复用)。 pub mod message_value; diff --git a/crates/oxide_builtins/src/message_channel.rs b/crates/oxide_builtins/src/message_channel.rs new file mode 100644 index 00000000..eb7d87a1 --- /dev/null +++ b/crates/oxide_builtins/src/message_channel.rs @@ -0,0 +1,166 @@ +//! MessagePort 载荷盒:mpsc 双端与对端端口边存于对象 `native_fn` 槽, +//! 向 GC 家族表暴露 edges/size/drop 三自由函数。 + +use std::mem; + +use oxide_kernel::message_queue; +use oxide_types::object::JsObject; +use oxide_types::value::JsValue; + +use crate::message_value::MessageValue; + +/// MessagePort 载荷盒:存于对象 `native_fn` 槽(`Box::into_raw`,realm 局部 +/// 单线程)。`peer` 是发往对端的 mpsc 发送端(无 GC 边),`rx` 是收自对端的 +/// mpsc 接收端(无 GC 边),`peer_port` 是对端端口对象的裸指针(双向 GC 对象 +/// 边,保对端存活),`closed` 是关闭标志。 +/// +/// `peer` / `rx` / `closed` 当前仅单测读取,生产读取方是端口 send/recv 方法 +/// (后续任务落地);字段被生产路径读取后须移除本标注。 +#[allow(dead_code)] +struct MessagePortInner { + peer: message_queue::Sender, + rx: message_queue::Receiver, + peer_port: *mut JsObject, + closed: bool, +} + +/// 取载荷盒指针:仅 MessagePort 标签对象返回 `Some`,其余标签返回 `None`。 +fn message_port_payload_ptr(obj: &JsObject) -> Option<*mut MessagePortInner> { + if !obj.is_message_port() { + return None; + } + obj.native_fn().map(|ptr| ptr.as_ptr() as *mut MessagePortInner) +} + +/// 收集 MessagePort 的对端端口边(GC 根边)。 +/// +/// `peer_port` 空窗口(构造期回填之前)返回空边集;边函数自守,语义自含。 +pub fn message_port_native_edges(obj: &JsObject) -> Vec { + let Some(ptr) = message_port_payload_ptr(obj) else { + return Vec::new(); + }; + if ptr.is_null() { + return Vec::new(); + } + // SAFETY: ptr 指向有效载荷盒(槽位仅构造期写入、释放时置空), + // 只读单个裸指针字段,不搬移载荷。 + let peer_port = unsafe { (*ptr).peer_port }; + if peer_port.is_null() { + return Vec::new(); + } + vec![JsValue::from_js_object(peer_port)] +} + +/// 只读核算 MessagePort 载荷盒字节(不释放)。 +/// +/// mpsc 内部缓冲在 Arc 通道内、盒不可寻址,不计入(有界轻微少计, +/// 只推迟 GC 触发,不影响触发正确性)。 +pub fn message_port_native_size(obj: &JsObject) -> u64 { + let Some(ptr) = message_port_payload_ptr(obj) else { + return 0; + }; + if ptr.is_null() { + return 0; + } + mem::size_of::() as u64 +} + +/// 释放 MessagePort 载荷盒(`Box`),返回释放字节数。 +/// +/// 盒 drop 时发送端与接收端各减一次 mpsc 引用计数;`peer_port` 裸指针 +/// 无 drop。槽置空保证重复调用零释放(幂等)。 +pub fn drop_message_port_native(obj: &mut JsObject) -> u64 { + let bytes = message_port_native_size(obj); + if bytes == 0 { + return 0; + } + let Some(ptr) = message_port_payload_ptr(obj) else { + return 0; + }; + // SAFETY: ptr 非空(message_port_native_size 已验证),Box::from_raw 恰好释放一次。 + unsafe { drop(Box::from_raw(ptr)) }; + obj.set_native_fn(None); + bytes +} + +#[cfg(test)] +mod tests { + use super::*; + use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; + use oxide_types::object::NativeFnPtr; + + /// 构造真实通道并把双端移入载荷盒。 + fn port_payload() -> MessagePortInner { + let (peer, rx) = message_queue::channel::(); + MessagePortInner { + peer, + rx, + peer_port: std::ptr::null_mut(), + closed: false, + } + } + + /// 手工构造带载荷盒的 MessagePort 对象(不经 Vm,只验 GC 三自由函数)。 + fn port_object_with_payload(inner: MessagePortInner) -> JsObject { + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()); + obj.type_tag = JsObject::OBJ_TYPE_MESSAGE_PORT; + let payload_ptr = Box::into_raw(Box::new(inner)); + // SAFETY: 载荷盒形态与 native_fn 槽存储约定一致,测试结束前恰好释放一次。 + obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); + obj + } + + #[test] + fn size_and_drop_idempotent() { + let mut obj = port_object_with_payload(port_payload()); + let struct_bytes = mem::size_of::() as u64; + assert_eq!(message_port_native_size(&obj), struct_bytes); + // 首次 drop 释放结构体;槽置空后二次 drop 零释放。 + assert_eq!(drop_message_port_native(&mut obj), struct_bytes); + assert_eq!(drop_message_port_native(&mut obj), 0); + } + + #[test] + fn edges_peer_port_null_and_present() { + let mut obj = port_object_with_payload(port_payload()); + // peer_port 空窗口(构造期回填之前):边函数返回零条边。 + assert!(message_port_native_edges(&obj).is_empty()); + + // 回填对端端口边:一条对象边。 + let peer = Box::leak(Box::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()))); + peer.type_tag = JsObject::OBJ_TYPE_MESSAGE_PORT; + let ptr = message_port_payload_ptr(&obj).unwrap(); + // SAFETY: peer 是测试局部对象,测试期间存活,边只读不搬移。 + unsafe { (*ptr).peer_port = peer as *mut JsObject }; + let edges = message_port_native_edges(&obj); + assert_eq!(edges.len(), 1); + assert!(edges[0].is_object()); + // 对端对象是测试局部泄漏,不随端口 drop 释放(裸指针无 drop)。 + drop_message_port_native(&mut obj); + } + + #[test] + fn channel_roundtrip_through_box() { + let mut obj = port_object_with_payload(port_payload()); + let ptr = message_port_payload_ptr(&obj).unwrap(); + // SAFETY: 载荷盒测试期间存活。 + unsafe { + assert!(!(*ptr).closed); + (*ptr).peer.send(MessageValue::Number(1.0)).unwrap(); + assert!(matches!((*ptr).rx.try_recv().unwrap(), MessageValue::Number(n) if n == 1.0)); + } + drop_message_port_native(&mut obj); + } + + #[test] + fn payload_ptr_brand_guard() { + // 非 MessagePort 类型标签(PLAIN)对象取载荷指针恒 None。 + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::undefined()); + let payload_ptr = Box::into_raw(Box::new(port_payload())); + // SAFETY: 同 port_object_with_payload 的载荷盒形态。 + obj.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(payload_ptr as *const ()) })); + assert!(message_port_payload_ptr(&obj).is_none()); + // SAFETY: 测试构造的盒,恰好释放一次。 + unsafe { drop(Box::from_raw(payload_ptr)) }; + } +} diff --git a/crates/oxide_kernel/src/lib.rs b/crates/oxide_kernel/src/lib.rs index 7e17449a..3c557343 100644 --- a/crates/oxide_kernel/src/lib.rs +++ b/crates/oxide_kernel/src/lib.rs @@ -13,6 +13,8 @@ pub mod code_forge; pub mod kernel; /// kernel 层日志宏。 pub mod kernel_log; +/// 通用消息队列:`std::sync::mpsc` 的薄封装,提供命名发送端/接收端类型与统一入口。 +pub mod message_queue; /// 属性模板缓存:按 shape 缓存属性槽位布局,加速属性查找。 pub mod prop_forge; /// hidden class(shape)共享存储:把对象结构映射为整数 id。 diff --git a/crates/oxide_vm/src/message_queue.rs b/crates/oxide_kernel/src/message_queue.rs similarity index 100% rename from crates/oxide_vm/src/message_queue.rs rename to crates/oxide_kernel/src/message_queue.rs diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index 06eafcff..44808caa 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -220,6 +220,9 @@ impl JsObject { /// Raw JSON 对象(`JSON.rawJSON` 产物):null 原型、frozen,唯一自身属性 /// `rawJSON` 存原始 JSON 文本(不可写、不可配置);无原生载荷盒。 pub const OBJ_TYPE_RAW_JSON: u8 = 32; + /// MessagePort 对象:mpsc 双端(发往对端 / 收自对端)与对端端口裸指针 + /// 存于 `native_fn` 槽的载荷盒(`MessagePortInner`),对端端口为双向 GC 边。 + pub const OBJ_TYPE_MESSAGE_PORT: u8 = 33; /// `is_session_epoch` 字段中的 session 标记位。 pub const SESSION_EPOCH_BIT: u8 = 0x01; /// `is_session_epoch` 字段中的 GC 标记位。 @@ -297,6 +300,11 @@ impl JsObject { pub fn is_raw_json_obj(&self) -> bool { self.type_tag == Self::OBJ_TYPE_RAW_JSON } + /// 是否 MessagePort 对象(`native_fn` 槽存 mpsc 双端与对端端口边)。 + #[inline] + pub fn is_message_port(&self) -> bool { + self.type_tag == Self::OBJ_TYPE_MESSAGE_PORT + } /// 是否 DataView 对象。 #[inline] pub fn is_data_view_obj(&self) -> bool { diff --git a/crates/oxide_vm/src/lib.rs b/crates/oxide_vm/src/lib.rs index c7998733..14618913 100644 --- a/crates/oxide_vm/src/lib.rs +++ b/crates/oxide_vm/src/lib.rs @@ -17,8 +17,9 @@ pub mod bindings; mod dispatch; mod generator; mod ic_helper; -/// 通用消息队列:`std::sync::mpsc` 的薄封装,提供命名发送端/接收端类型与统一入口。 -pub mod message_queue; +/// 通用消息队列:`std::sync::mpsc` 的薄封装,提供命名发送端/接收端类型与统一入口 +/// (本体在 `oxide_kernel`,此处再导出保持既有路径有效)。 +pub use oxide_kernel::message_queue; /// native 函数签名类型([`native::NativeFn`]),builtin 绑定与 VM 调用约定依赖它。 pub mod native; /// native 载荷家族单点分类与每家族边函数引用(家族表,GC 各链注册面)。 diff --git a/crates/oxide_vm/src/native_box_dispatch.rs b/crates/oxide_vm/src/native_box_dispatch.rs index f7f5a81b..6a9f0a59 100644 --- a/crates/oxide_vm/src/native_box_dispatch.rs +++ b/crates/oxide_vm/src/native_box_dispatch.rs @@ -1,7 +1,7 @@ //! native 载荷家族的单点分类与每家族边函数引用(家族表)。 //! //! 分类是复合谓词:header 位(map/set/module_ns)优先于 `type_tag`, -//! 两维互斥、一个对象至多归入一个家族。tag 维显式枚举全部 0..=32, +//! 两维互斥、一个对象至多归入一个家族。tag 维显式枚举全部 0..=33, //! 未登记 tag panic。 //! //! 关键约定: @@ -10,7 +10,7 @@ //! - 注册面集中在 `ops_for` 的家族表,mark/size/drop/clone/rewrite 各链 //! 由同一张表驱动,新增家族只在此登记。 -use oxide_builtins::{data_view, disposable_stack, map, module, set, typed_array, weak_map}; +use oxide_builtins::{data_view, disposable_stack, map, message_channel, module, set, typed_array, weak_map}; use oxide_types::object::{Cell, JsObject, JsString}; use oxide_types::value::JsValue; @@ -49,6 +49,8 @@ pub(crate) enum NativeBoxFamily { AsyncGenerator, /// RegExp 与 matchAll 载体(tag 2/28,`native_fn` 槽持编译正则)。 RegExp, + /// MessagePort 端口对象(tag 33,mpsc 双端与对端端口边)。 + MessagePort, } /// 一个家族的边操作集(每家族函数引用,单一注册面)。 @@ -81,7 +83,7 @@ pub(crate) fn classify(obj: &JsObject) -> NativeBoxFamily { /// 按类型 tag 分类 native 载荷家族。 /// /// # 边界与前提 -/// - 0..=32 全 tag 显式枚举;未登记 tag(33 及以上)panic。 +/// - 0..=33 全 tag 显式枚举;未登记 tag(34 及以上)panic。 pub(crate) fn classify_by_tag(tag: u8) -> NativeBoxFamily { match tag { JsObject::OBJ_TYPE_ARRAY_BUFFER => NativeBoxFamily::ArrayBuffer, @@ -118,6 +120,7 @@ pub(crate) fn classify_by_tag(tag: u8) -> NativeBoxFamily { JsObject::OBJ_TYPE_PLAIN_YEAR_MONTH => NativeBoxFamily::None, JsObject::OBJ_TYPE_HTML_DDA => NativeBoxFamily::None, JsObject::OBJ_TYPE_RAW_JSON => NativeBoxFamily::None, + JsObject::OBJ_TYPE_MESSAGE_PORT => NativeBoxFamily::MessagePort, _ => panic!("unregistered type tag: {tag}"), } } @@ -203,6 +206,11 @@ pub(crate) fn ops_for(family: NativeBoxFamily) -> NativeBoxOps { string_edges: None, cell_edges: None, }, + NativeBoxFamily::MessagePort => NativeBoxOps { + object_edges: Some(message_channel::message_port_native_edges), + string_edges: None, + cell_edges: None, + }, } } @@ -232,13 +240,14 @@ mod tests { JsObject::OBJ_TYPE_WEAK_MAP => NativeBoxFamily::WeakMap, JsObject::OBJ_TYPE_SHARED_ARRAY_BUFFER => NativeBoxFamily::SharedArrayBuffer, JsObject::OBJ_TYPE_REGEXP | JsObject::OBJ_TYPE_REGEX_STUB => NativeBoxFamily::RegExp, + JsObject::OBJ_TYPE_MESSAGE_PORT => NativeBoxFamily::MessagePort, _ => NativeBoxFamily::None, } } #[test] fn tag_dimension_every_tag_hits_exactly_one_family() { - for tag in 0..=32u8 { + for tag in 0..=33u8 { let obj = obj_with_tag(tag); let family = classify(&obj); assert_eq!(family, expected_family_for_tag(tag), "tag {tag} 家族不一致"); @@ -272,7 +281,7 @@ mod tests { #[test] #[should_panic(expected = "unregistered type tag")] fn unregistered_tag_panics() { - let obj = obj_with_tag(33); + let obj = obj_with_tag(34); classify(&obj); } } diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index 6690edad..899021b7 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -10,7 +10,9 @@ use rustc_hash::FxBuildHasher; use crate::native_box_dispatch; use crate::vm::RootGroup; use crate::vm::Vm; -use oxide_builtins::{array_buffer, data_view, disposable_stack, map, module, regexp, set, typed_array, weak_map}; +use oxide_builtins::{ + array_buffer, data_view, disposable_stack, map, message_channel, module, regexp, set, typed_array, weak_map, +}; /// session 级 mark-sweep GC 的状态与统计。 /// @@ -127,6 +129,7 @@ impl SessionGc { bytes += crate::async_func::async_native_size(obj); bytes += crate::async_generator::async_generator_native_size(obj); bytes += weak_map::weak_map_native_size(obj); + bytes += message_channel::message_port_native_size(obj); bytes } @@ -566,6 +569,7 @@ impl SessionGc { freed_bytes += crate::async_func::drop_async_native(obj); freed_bytes += crate::async_generator::drop_async_generator_native(obj); freed_bytes += weak_map::drop_weak_map_native(obj); + freed_bytes += message_channel::drop_message_port_native(obj); freed_bytes }