From b36b07a04cc8815fc350c61246707fe1e57a842c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=88=B1=E7=89=B9=E6=AF=8F=E7=BE=8A=E9=98=9F?= Date: Fri, 5 Jun 2026 02:26:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20vm=20=E5=86=85?= =?UTF-8?q?=E5=AD=98=E6=A8=A1=E5=9E=8B=E3=80=81=E5=BC=95=E7=94=A8=E8=AE=A1?= =?UTF-8?q?=E6=95=B0=E4=B8=8E=E6=8C=81=E4=B9=85=E5=8C=96=E5=A0=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/lib.rs | 1 + crates/oxide_vm/src/mem.rs | 120 +++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 crates/oxide_vm/src/mem.rs diff --git a/crates/oxide_vm/src/lib.rs b/crates/oxide_vm/src/lib.rs index 523dc41..5cc2637 100644 --- a/crates/oxide_vm/src/lib.rs +++ b/crates/oxide_vm/src/lib.rs @@ -1,4 +1,5 @@ #![doc = "OxideJS - Register-based VM with epoch arena memory"] +pub mod mem; pub mod value; pub use value::JsValue; diff --git a/crates/oxide_vm/src/mem.rs b/crates/oxide_vm/src/mem.rs new file mode 100644 index 0000000..9d97a00 --- /dev/null +++ b/crates/oxide_vm/src/mem.rs @@ -0,0 +1,120 @@ +use std::fmt; +use std::ops::Deref; +use std::sync::Arc; + +/// Reference-counted persistent pointer. +/// +/// Wraps `Arc` for cross-epoch object storage. `Clone` increments the +/// reference count; `Drop` decrements it. Objects wrapped in `P` survive +/// `Epoch::reset()` — they live on the global heap. +#[repr(transparent)] +pub struct P(Arc); + +impl P { + pub fn new(value: T) -> Self { + Self(Arc::new(value)) + } +} + +impl Clone for P { + fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) + } +} + +impl Deref for P { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl fmt::Debug for P { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "P({:?})", self.0) + } +} + +impl fmt::Display for P { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Persistent heap for cross-epoch object storage. +/// +/// Phase 3 provides a minimal API: `promote(value)` moves `value` to the +/// global heap under `Arc` and returns `P`. Phase 7 (OxideKernel) expands +/// with typed storage for shapes, code, IC templates, and strings. +pub struct PersistentHeap; + +impl PersistentHeap { + pub fn new() -> Self { + Self + } + + /// Move a value to the persistent heap and return a reference-counted pointer. + /// The returned `P` survives epoch resets — it's stored on the global heap + /// under `Arc`. + pub fn promote(&self, value: T) -> P { + P::new(value) + } +} + +impl Default for PersistentHeap { + fn default() -> Self { + Self::new() + } +} +/// +/// Wraps `bumpalo::Bump` with an epoch ID counter for dangling pointer detection. +/// All Agent-call objects are allocated here; `reset()` clears them in O(1) +/// at the end of each call. +pub struct Epoch { + bump: bumpalo::Bump, + epoch_id: u64, +} + +impl Epoch { + pub fn new() -> Self { + Self { + bump: bumpalo::Bump::new(), + epoch_id: 0, + } + } + + /// Bump-allocate a value and return a raw pointer to it. + /// The pointer is valid until `reset()` is called. + pub fn alloc(&self, value: T) -> *mut T { + self.bump.alloc(value) + } + + /// Bump-allocate a value using a closure for initialization. + /// Better for compiler optimization — construct directly on arena. + pub fn alloc_with(&self, f: F) -> *mut T + where + F: FnOnce() -> T, + { + self.bump.alloc_with(f) + } + + /// O(1) mass deallocation. All previous allocations become invalid. + /// Increments epoch ID to invalidate stale pointers (debug_assert guard). + pub fn reset(&mut self) { + self.bump.reset(); + self.epoch_id += 1; + } + + /// Current epoch ID. Arena-allocated objects store this value; + /// dereference checks they match the current ID (debug_assert only). + pub fn current_id(&self) -> u64 { + self.epoch_id + } +} + +impl Default for Epoch { + fn default() -> Self { + Self::new() + } +}