From 3f269950408bff6f9202bbece8aea4897a3ced86 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 8 Oct 2026 09:12:44 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20SharedBuffer=20?= =?UTF-8?q?=E5=85=B1=E4=BA=AB=E5=AD=97=E8=8A=82=E7=BC=93=E5=86=B2=E4=B8=8E?= =?UTF-8?q?=E6=A0=B8=E8=B4=A6=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 38 ++-- Cargo.toml | 2 +- crates/oxide_kernel/src/kernel/core.rs | 19 ++ crates/oxide_kernel/src/lib.rs | 4 + crates/oxide_kernel/src/shared_buffer.rs | 247 +++++++++++++++++++++++ 5 files changed, 290 insertions(+), 20 deletions(-) create mode 100644 crates/oxide_kernel/src/shared_buffer.rs diff --git a/Cargo.lock b/Cargo.lock index e92342c1..fd141caf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.79" +version = "0.8.80" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.79" +version = "0.8.80" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.79" +version = "0.8.80" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.79" +version = "0.8.80" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.79" +version = "0.8.80" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.79" +version = "0.8.80" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.79" +version = "0.8.80" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.79" +version = "0.8.80" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.8.79" +version = "0.8.80" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.8.79" +version = "0.8.80" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.79" +version = "0.8.80" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.79" +version = "0.8.80" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.79" +version = "0.8.80" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.79" +version = "0.8.80" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.79" +version = "0.8.80" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.79" +version = "0.8.80" dependencies = [ "num-bigint", "num-traits", @@ -1383,7 +1383,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.79" +version = "0.8.80" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1401,7 +1401,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.79" +version = "0.8.80" dependencies = [ "bumpalo", "dashmap", @@ -1414,7 +1414,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.79" +version = "0.8.80" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 54c607ff..9703144a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.8.79" +version = "0.8.80" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_kernel/src/kernel/core.rs b/crates/oxide_kernel/src/kernel/core.rs index e685bd1d..0d3b1c4b 100644 --- a/crates/oxide_kernel/src/kernel/core.rs +++ b/crates/oxide_kernel/src/kernel/core.rs @@ -36,6 +36,9 @@ pub struct KernelCore { /// 内创建满 512 个 Vm 前重建 kernel(重建即计数器归零);test262 runner /// 经 kernel_batch 缺省值(不超 512)满足该约束。 realm_counter: AtomicU32, + /// 共享字节缓冲账目:存活 `SharedBuffer` 的预分配真实上限之和。 + /// `SharedBuffer` 构造加、`Drop` 减(同值),供宿主读取共享层内存占用。 + shared_buffer_bytes: AtomicUsize, } impl KernelCore { @@ -66,6 +69,7 @@ impl KernelCore { prop_forge, active_vms: AtomicUsize::new(0), realm_counter: AtomicU32::new(0), + shared_buffer_bytes: AtomicUsize::new(0), }); kernel_info!("KernelCore initialized: max_cached_modules={}, min_pool={}", max_cached, min_pool); core @@ -196,6 +200,21 @@ impl KernelCore { self.active_vms.load(Ordering::Relaxed) } + /// 读取存活共享字节缓冲的预分配真实上限之和(字节)。 + pub fn shared_buffer_bytes(&self) -> usize { + self.shared_buffer_bytes.load(Ordering::Relaxed) + } + + /// 共享字节缓冲账目加账(`SharedBuffer` 构造调用;单位是预分配真实上限)。 + pub(crate) fn shared_buffer_bytes_add(&self, bytes: usize) { + self.shared_buffer_bytes.fetch_add(bytes, Ordering::Relaxed); + } + + /// 共享字节缓冲账目减账(`SharedBuffer` 析构调用;单位是预分配真实上限)。 + pub(crate) fn shared_buffer_bytes_sub(&self, bytes: usize) { + self.shared_buffer_bytes.fetch_sub(bytes, Ordering::Relaxed); + } + /// 分配一个 realm 编号:取计数器当前值后自增,进程内单调递增。 /// /// 首个 realm 编号为 0(与旧符号编码一致),供 `Realm` 构造时固化。 diff --git a/crates/oxide_kernel/src/lib.rs b/crates/oxide_kernel/src/lib.rs index 3a2a274a..7e17449a 100644 --- a/crates/oxide_kernel/src/lib.rs +++ b/crates/oxide_kernel/src/lib.rs @@ -17,6 +17,8 @@ pub mod kernel_log; pub mod prop_forge; /// hidden class(shape)共享存储:把对象结构映射为整数 id。 pub mod shape_forge; +/// 共享字节缓冲:SharedArrayBuffer 的跨线程共享存储(预分配至真实上限,活长原子推进)。 +pub mod shared_buffer; /// kernel 对外的三个核心类型:配置、共享核心、会话。 pub use kernel::{KernelConfig, KernelCore, KernelSession}; @@ -24,3 +26,5 @@ pub use kernel::{KernelConfig, KernelCore, KernelSession}; pub use oxide_types::string_forge; /// 模块命名空间再导出的绑定身份哨兵(本体在 `oxide_types`,此处再导出)。 pub use oxide_types::MODULE_NAMESPACE_BINDING; +/// 跨线程共享字节缓冲(SharedArrayBuffer 的共享存储)。 +pub use shared_buffer::SharedBuffer; diff --git a/crates/oxide_kernel/src/shared_buffer.rs b/crates/oxide_kernel/src/shared_buffer.rs new file mode 100644 index 00000000..789747ec --- /dev/null +++ b/crates/oxide_kernel/src/shared_buffer.rs @@ -0,0 +1,247 @@ +//! 共享字节缓冲:SharedArrayBuffer 的跨线程共享存储。 +//! +//! 关键约定: +//! - 字节区一次性预分配至真实上限并零填充,此后不再重分配;`grow` 只推进活长,O(1)。 +//! - `write_range` 经 `&self` 裸写(字节区归本结构独占,类型不暴露 `&mut [u8]`),多线程并发写安全。 +//! - 账目单位是预分配真实上限:构造时计入 `KernelCore::shared_buffer_bytes`,`Drop` 减同值。 + +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +use crate::KernelCore; + +/// 共享字节缓冲操作的越界错误。 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SharedBufferError { + /// 写范围超出活长(offset + data 长度 > len)。 + OutOfRange, +} + +/// 跨线程共享字节缓冲:预分配至真实上限,活长经原子推进。 +/// +/// 全部字段 `Send + Sync`,可经 `Arc` 跨线程共享;账目入口是结构持有的 +/// `Arc`,`Drop` 时从核的 `shared_buffer_bytes` 减预分配真实上限。 +pub struct SharedBuffer { + /// 共享字节区,预分配至真实上限并零填充。 + bytes: Arc>, + /// 活字节长(growable 经原子推进)。 + length: AtomicUsize, + /// 预分配真实上限。 + max_length: usize, + /// 账目入口。 + core: Arc, +} + +impl SharedBuffer { + /// 定长构造:字节序列即活内容,上限即字节序列长度。 + /// + /// # 副作用 + /// - 核的 `shared_buffer_bytes` 加字节序列长度(真实上限)。 + pub fn new(core: Arc, bytes: Vec) -> Self { + let bytes = Arc::new(bytes); + let len = bytes.len(); + core.shared_buffer_bytes_add(len); + Self { + bytes, + length: AtomicUsize::new(len), + max_length: len, + core, + } + } + + /// 可增长构造:预分配至 `max_len` 并零填充,初始活长 `initial_len`。 + /// + /// # 边界与前提 + /// - `initial_len` 不得超过 `max_len`(debug 构建 fail-fast)。 + /// + /// # 副作用 + /// - 核的 `shared_buffer_bytes` 加 `max_len`(真实上限),而非 `initial_len`。 + pub fn new_growable(core: Arc, initial_len: usize, max_len: usize) -> Self { + debug_assert!(initial_len <= max_len, "initial_len must not exceed max_len"); + let bytes = Arc::new(vec![0u8; max_len]); + core.shared_buffer_bytes_add(max_len); + Self { + bytes, + length: AtomicUsize::new(initial_len), + max_length: max_len, + core, + } + } + + /// 读取当前活字节长。 + pub fn len(&self) -> usize { + self.length.load(Ordering::SeqCst) + } + + /// 判断活区是否为空。 + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// 读取预分配真实上限。 + pub fn max_len(&self) -> usize { + self.max_length + } + + /// 活长区只读切片。 + pub fn as_slice(&self) -> &[u8] { + &self.bytes[..self.len()] + } + + /// 把 `data` 写入活长区 `offset` 起(经 `&self` 裸写)。 + /// + /// # 边界与前提 + /// - `offset + data.len()` 超过活长时返回 `OutOfRange`,不做任何写入; + /// - 写入不改变活长。 + /// + /// # 注意事项 + /// - 字节区归本结构独占且类型不暴露 `&mut [u8]`,经 `&self` 裸写不构成别名; + /// 不得新增 `bytes_mut` 一类方法,否则该前提失效。 + pub fn write_range(&self, offset: usize, data: &[u8]) -> Result<(), SharedBufferError> { + let end = offset.checked_add(data.len()).ok_or(SharedBufferError::OutOfRange)?; + if end > self.len() { + return Err(SharedBufferError::OutOfRange); + } + unsafe { + // 字节区归本结构独占,裸写目标即其内部可变区。 + let dest = self.bytes.as_ptr().add(offset) as *mut u8; + std::ptr::copy_nonoverlapping(data.as_ptr(), dest, data.len()); + } + Ok(()) + } + + /// 把活长推进到 `new_len`(只增不缩),返回操作后活长。 + /// + /// # 边界与前提 + /// - 缩长(`new_len` 不大于当前活长)与超上限(`new_len` 大于 `max_len`)均被拒绝, + /// 返回当前活长; + /// - 字节区已预分配,推进只改原子计数,O(1)。 + /// + /// # 副作用 + /// - 经 CAS 循环推进 `length`;并发调用各自至多推进一次。 + pub fn grow(&self, new_len: usize) -> usize { + let mut cur = self.length.load(Ordering::SeqCst); + loop { + // 缩长与超上限统一拒绝,返回当前活长。 + if new_len <= cur || new_len > self.max_length { + return cur; + } + match self + .length + .compare_exchange_weak(cur, new_len, Ordering::SeqCst, Ordering::SeqCst) + { + Ok(_) => return new_len, + Err(next) => cur = next, + } + } + } +} + +impl Drop for SharedBuffer { + fn drop(&mut self) { + // 账目单位与构造时加账一致:预分配真实上限。 + self.core.shared_buffer_bytes_sub(self.bytes.len()); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::KernelConfig; + + fn new_core() -> Arc { + KernelCore::new(KernelConfig::minimal()) + } + + #[test] + fn shared_buffer_is_send_sync() { + fn assert_send_sync() {} + assert_send_sync::(); + } + + #[test] + fn new_fixed_size_len_and_accounting() { + let core = new_core(); + let buf = SharedBuffer::new(core.clone(), vec![1, 2, 3, 4]); + assert_eq!(buf.len(), 4); + assert_eq!(buf.max_len(), 4); + assert_eq!(buf.as_slice(), &[1, 2, 3, 4]); + assert_eq!(core.shared_buffer_bytes(), 4); + } + + #[test] + fn new_growable_preallocates_to_max() { + let core = new_core(); + let buf = SharedBuffer::new_growable(core.clone(), 2, 8); + assert_eq!(buf.len(), 2); + assert_eq!(buf.max_len(), 8); + assert_eq!(buf.as_slice(), &[0, 0]); + // 账目单位是预分配真实上限,而非初始活长。 + assert_eq!(core.shared_buffer_bytes(), 8); + } + + #[test] + fn write_range_updates_live_region() { + let core = new_core(); + let buf = SharedBuffer::new(core, vec![0u8; 8]); + buf.write_range(2, &[9, 8, 7]).unwrap(); + assert_eq!(buf.as_slice(), &[0, 0, 9, 8, 7, 0, 0, 0]); + assert_eq!(buf.len(), 8); + } + + #[test] + fn write_range_rejects_out_of_range() { + let core = new_core(); + let buf = SharedBuffer::new(core, vec![0u8; 4]); + assert_eq!(buf.write_range(3, &[1, 2]), Err(SharedBufferError::OutOfRange)); + // offset 溢出加法同样按越界处理。 + assert_eq!(buf.write_range(usize::MAX, &[1]), Err(SharedBufferError::OutOfRange)); + // 拒绝时活长不变。 + assert_eq!(buf.len(), 4); + } + + #[test] + fn grow_only_increases_and_caps_at_max() { + let core = new_core(); + let buf = SharedBuffer::new_growable(core, 2, 8); + assert_eq!(buf.grow(5), 5); + assert_eq!(buf.as_slice(), &[0, 0, 0, 0, 0]); + // 缩长被拒绝,活长不变。 + assert_eq!(buf.grow(3), 5); + // 超上限被拒绝,活长不变。 + assert_eq!(buf.grow(9), 5); + // 恰好到上限成功。 + assert_eq!(buf.grow(8), 8); + } + + #[test] + fn drop_returns_accounting_to_zero() { + let core = new_core(); + { + let fixed = SharedBuffer::new(core.clone(), vec![0u8; 4]); + // 作用域末隐式析构,验证减账归零。 + let _growable = SharedBuffer::new_growable(core.clone(), 0, 16); + assert_eq!(core.shared_buffer_bytes(), 20); + drop(fixed); + assert_eq!(core.shared_buffer_bytes(), 16); + } + assert_eq!(core.shared_buffer_bytes(), 0); + } + + #[test] + fn arc_shared_across_threads() { + let core = new_core(); + let buf = Arc::new(SharedBuffer::new_growable(core, 0, 16)); + let child = { + let buf = Arc::clone(&buf); + std::thread::spawn(move || { + buf.grow(8); + buf.write_range(4, &[0xAB, 0xCD]).unwrap(); + buf.len() + }) + }; + let len = child.join().unwrap(); + assert_eq!(len, 8); + assert_eq!(buf.as_slice(), &[0, 0, 0, 0, 0xAB, 0xCD, 0, 0]); + } +}