diff --git a/crates/oxide_cli/src/main.rs b/crates/oxide_cli/src/main.rs index 384ed882..af1a2dc6 100644 --- a/crates/oxide_cli/src/main.rs +++ b/crates/oxide_cli/src/main.rs @@ -10,7 +10,7 @@ use clap::{Parser, Subcommand}; use oxide_compiler::compiler::{compiled_module_hash, Compiler}; use oxide_kernel::kernel::{KernelConfig, KernelCore}; use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; -use oxide_kernel::string_forge::StringForge; +use oxide_kernel::string_forge::PermInterner; use oxide_parser::Allocator; use oxide_types::object::JsObject; use oxide_vm::vm_pool::VmPool; @@ -140,7 +140,7 @@ fn eval(code: &str, kernel: &Arc, pool: &Arc) -> ExitCode { let mut guard = pool.spawn(); match guard.vm_mut().run(&module) { Ok(result) => { - format_result(kernel.string_forge().as_ref(), kernel.shape_forge().as_ref(), result); + format_result(kernel.perm_interner().as_ref(), kernel.shape_forge().as_ref(), result); ExitCode::SUCCESS } Err(err) => { @@ -150,17 +150,15 @@ fn eval(code: &str, kernel: &Arc, pool: &Arc) -> ExitCode { } } -fn format_result(string_forge: &StringForge, shape_forge: &ShapeForge, val: JsValue) { +fn format_result(string_forge: &PermInterner, shape_forge: &ShapeForge, val: JsValue) { println!("{}", format_js_value(string_forge, shape_forge, val)); } -fn format_js_value(string_forge: &StringForge, shape_forge: &ShapeForge, val: JsValue) -> String { +fn format_js_value(string_forge: &PermInterner, shape_forge: &ShapeForge, val: JsValue) -> String { if val.is_string() { - if let Some(s) = string_forge.lookup(val.as_string_index()) { - format!("\"{s}\"") - } else { - format!("{val}") - } + // SAFETY: val is a string value. + let s = unsafe { (*val.as_string_ptr()).data.clone() }; + format!("\"{s}\"") } else if val.is_object() { let obj = unsafe { &*val.as_js_object_ptr() }; if obj.is_function() { @@ -177,7 +175,7 @@ fn format_js_value(string_forge: &StringForge, shape_forge: &ShapeForge, val: Js } } -fn format_object(string_forge: &StringForge, shape_forge: &ShapeForge, obj: &JsObject) -> String { +fn format_object(string_forge: &PermInterner, shape_forge: &ShapeForge, obj: &JsObject) -> String { let mut entries = Vec::new(); let shape_id = obj.shape_id(); let mut shape_ids = Vec::new(); @@ -214,7 +212,7 @@ fn format_object(string_forge: &StringForge, shape_forge: &ShapeForge, obj: &JsO format!("{{{}}}", entries.join(", ")) } -fn format_array(string_forge: &StringForge, shape_forge: &ShapeForge, obj: &JsObject) -> String { +fn format_array(string_forge: &PermInterner, shape_forge: &ShapeForge, obj: &JsObject) -> String { let len = obj.prop_vec_len(); let mut items = Vec::new(); for i in 0..len { diff --git a/crates/oxide_kernel/src/builtin.rs b/crates/oxide_kernel/src/builtin.rs index 6233c014..712f5b3d 100644 --- a/crates/oxide_kernel/src/builtin.rs +++ b/crates/oxide_kernel/src/builtin.rs @@ -4,7 +4,7 @@ use oxide_types::value::JsValue; use crate::kernel::BuiltinDirtySet; use crate::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; -use crate::string_forge::StringForge; +use crate::string_forge::PermInterner; #[macro_export] macro_rules! bind_method { @@ -219,12 +219,12 @@ pub struct BuiltinWorld { pub stub_objects: Vec>, } -fn intern_label(string_forge: &StringForge, label: &str) -> u32 { +fn intern_label(string_forge: &PermInterner, label: &str) -> u32 { string_forge.intern(label).0 } fn make_pair( - string_forge: &StringForge, shape_forge: &ShapeForge, name: &str, si_prototype: u32, si_constructor: u32, + string_forge: &PermInterner, shape_forge: &ShapeForge, name: &str, si_prototype: u32, si_constructor: u32, si_name: u32, ) -> (P, P) { intern_label(string_forge, name); @@ -258,7 +258,7 @@ struct BuiltinLabels { name: u32, } -fn builtin_labels(string_forge: &StringForge) -> BuiltinLabels { +fn builtin_labels(string_forge: &PermInterner) -> BuiltinLabels { let labels = BuiltinLabels { prototype: intern_label(string_forge, "prototype"), constructor: intern_label(string_forge, "constructor"), @@ -271,7 +271,7 @@ fn builtin_labels(string_forge: &StringForge) -> BuiltinLabels { } fn make_named_pair( - string_forge: &StringForge, shape_forge: &ShapeForge, labels: BuiltinLabels, name: &str, + string_forge: &PermInterner, shape_forge: &ShapeForge, labels: BuiltinLabels, name: &str, ) -> (P, P) { make_pair(string_forge, shape_forge, name, labels.prototype, labels.constructor, labels.name) } @@ -324,7 +324,7 @@ struct TypedArrayFamily { } fn make_typed_array_family( - string_forge: &StringForge, shape_forge: &ShapeForge, labels: BuiltinLabels, object_proto: &P, + string_forge: &PermInterner, shape_forge: &ShapeForge, labels: BuiltinLabels, object_proto: &P, ) -> TypedArrayFamily { let obj_proto_val = JsValue::from_js_object(object_proto.as_ptr() as *mut JsObject); let typed_array_proto = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, obj_proto_val)); @@ -472,7 +472,7 @@ impl BuiltinWorld { JsValue::from_js_object(self.function_proto.as_ptr() as *mut JsObject) } - pub fn new(string_forge: &StringForge, shape_forge: &ShapeForge) -> Self { + pub fn new(string_forge: &PermInterner, shape_forge: &ShapeForge) -> Self { let labels = builtin_labels(string_forge); let (object_proto, object_constructor) = make_named_pair(string_forge, shape_forge, labels, "Object"); @@ -578,7 +578,7 @@ impl BuiltinWorld { } pub fn rebuild_with_dirty( - current: &BuiltinWorld, string_forge: &StringForge, shape_forge: &ShapeForge, dirty: &BuiltinDirtySet, + current: &BuiltinWorld, string_forge: &PermInterner, shape_forge: &ShapeForge, dirty: &BuiltinDirtySet, ) -> BuiltinWorld { let labels = builtin_labels(string_forge); @@ -796,7 +796,7 @@ impl BuiltinWorld { world } - pub fn bind_object_methods(&self, methods: &ObjectMethods, string_forge: &StringForge, shape_forge: &ShapeForge) { + pub fn bind_object_methods(&self, methods: &ObjectMethods, string_forge: &PermInterner, shape_forge: &ShapeForge) { let ctor_ptr = P::as_ptr(&self.object_constructor) as *mut JsObject; let ctor = unsafe { &mut *ctor_ptr }; bind_methods!( @@ -843,7 +843,7 @@ impl BuiltinWorld { } } - pub fn bind_array_methods(&self, methods: &ArrayMethods, string_forge: &StringForge, shape_forge: &ShapeForge) { + pub fn bind_array_methods(&self, methods: &ArrayMethods, string_forge: &PermInterner, shape_forge: &ShapeForge) { let ctor_ptr = P::as_ptr(&self.array_constructor) as *mut JsObject; let ctor = unsafe { &mut *ctor_ptr }; bind_methods!(self, ctor, string_forge, shape_forge, ("isArray", methods.is_array, 1),); @@ -895,7 +895,7 @@ impl BuiltinWorld { debug_assert!(shape_forge.lookup_position(proto.shape_id(), si).is_some()); } - pub fn bind_error_methods(&self, methods: &ErrorMethods, string_forge: &StringForge, shape_forge: &ShapeForge) { + pub fn bind_error_methods(&self, methods: &ErrorMethods, string_forge: &PermInterner, shape_forge: &ShapeForge) { let ctor_ptr = P::as_ptr(&self.error_constructor) as *mut JsObject; let ctor = unsafe { &mut *ctor_ptr }; bind_methods!( @@ -924,7 +924,7 @@ impl BuiltinWorld { ); } - pub fn bind_string_methods(&self, methods: &StringMethods, string_forge: &StringForge, shape_forge: &ShapeForge) { + pub fn bind_string_methods(&self, methods: &StringMethods, string_forge: &PermInterner, shape_forge: &ShapeForge) { let ctor_ptr = P::as_ptr(&self.string_constructor) as *mut JsObject; let ctor = unsafe { &mut *ctor_ptr }; bind_methods!(self, ctor, string_forge, shape_forge, ("fromCharCode", methods.from_char_code, 1),); @@ -966,7 +966,7 @@ impl BuiltinWorld { } pub fn bind_function_methods( - &self, methods: &FunctionMethods, string_forge: &StringForge, shape_forge: &ShapeForge, + &self, methods: &FunctionMethods, string_forge: &PermInterner, shape_forge: &ShapeForge, ) { let proto_ptr = P::as_ptr(&self.function_proto) as *mut JsObject; let proto = unsafe { &mut *proto_ptr }; @@ -984,7 +984,7 @@ impl BuiltinWorld { } pub fn bind_method( - &self, proto: &mut JsObject, shape_forge: &ShapeForge, string_forge: &StringForge, method_name: &str, + &self, proto: &mut JsObject, shape_forge: &ShapeForge, string_forge: &PermInterner, method_name: &str, native_fn_ptr: NativeFnPtr, arg_count: u8, ) -> Result<(), String> { Self::bind_method_static( @@ -999,7 +999,7 @@ impl BuiltinWorld { } pub fn bind_method_static( - proto: &mut JsObject, shape_forge: &ShapeForge, string_forge: &StringForge, method_name: &str, + proto: &mut JsObject, shape_forge: &ShapeForge, string_forge: &PermInterner, method_name: &str, native_fn_ptr: NativeFnPtr, arg_count: u8, wrapper_proto: JsValue, ) -> Result<(), String> { let si = string_forge.intern(method_name).0; @@ -1022,7 +1022,7 @@ impl BuiltinWorld { wrapper.set_shape_id(name_shape); wrapper .ensure_hash_props() - .push(JsValue::string(string_forge.intern(method_name).0, 0)); + .push(JsValue::perm_string(string_forge.string_ptr(si))); let wrapper_val = JsValue::from_js_object(Box::into_raw(wrapper)); let new_shape = shape_forge.make_shape(proto.shape_id(), si); proto.set_shape_id(new_shape); @@ -1036,10 +1036,10 @@ impl BuiltinWorld { mod tests { use super::*; use crate::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; - use crate::string_forge::StringForge; + use crate::string_forge::PermInterner; fn make_world() -> BuiltinWorld { - let sf = StringForge::new(); + let sf = PermInterner::new(); let sh = ShapeForge::new(); BuiltinWorld::new(&sf, &sh) } @@ -1115,7 +1115,7 @@ mod tests { #[test] fn builtin_rebuild_with_dirty_reuses_clean_fields() { - let sf = StringForge::new(); + let sf = PermInterner::new(); let sh = ShapeForge::new(); let w = BuiltinWorld::new(&sf, &sh); let rebuilt = BuiltinWorld::rebuild_with_dirty(&w, &sf, &sh, &crate::kernel::BuiltinDirtySet::default()); @@ -1127,7 +1127,7 @@ mod tests { #[test] fn builtin_rebuild_with_dirty_replaces_only_dirty_group() { - let sf = StringForge::new(); + let sf = PermInterner::new(); let sh = ShapeForge::new(); let w = BuiltinWorld::new(&sf, &sh); let dirty = crate::kernel::BuiltinDirtySet { @@ -1144,7 +1144,7 @@ mod tests { #[test] fn builtin_rebuild_with_dirty_repairs_ctor_proto_links() { - let sf = StringForge::new(); + let sf = PermInterner::new(); let sh = ShapeForge::new(); let w = BuiltinWorld::new(&sf, &sh); let dirty = crate::kernel::BuiltinDirtySet { diff --git a/crates/oxide_kernel/src/kernel.rs b/crates/oxide_kernel/src/kernel.rs index feb30351..c324361b 100644 --- a/crates/oxide_kernel/src/kernel.rs +++ b/crates/oxide_kernel/src/kernel.rs @@ -12,7 +12,7 @@ use crate::code_forge::CodeForge; use crate::logging::{init_logging, LogLevel, SUBSYSTEM_COUNT}; use crate::prop_forge::PropForge; use crate::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; -use crate::string_forge::StringForge; +use crate::string_forge::PermInterner; #[derive(Clone)] pub struct KernelConfig { @@ -105,7 +105,7 @@ impl Default for KernelConfig { /// Never rebuilt after construction — forge tables are append-only. pub struct KernelCore { pub config: KernelConfig, - pub string_forge: Arc, + pub perm_interner: Arc, pub shape_forge: Arc, pub code_forge: Arc, pub prop_forge: Arc, @@ -114,7 +114,7 @@ pub struct KernelCore { impl KernelCore { pub fn new(config: KernelConfig) -> Arc { init_logging(&config.log_levels); - let string_forge = Arc::new(StringForge::new()); + let perm_interner = Arc::new(PermInterner::new()); let shape_forge = Arc::new(ShapeForge::new()); let code_forge = Arc::new(CodeForge::new( NonZeroUsize::new(config.max_cached_modules).expect("max_cached_modules must be greater than zero"), @@ -122,15 +122,15 @@ impl KernelCore { let prop_forge = Arc::new(PropForge::new()); Arc::new(Self { config, - string_forge, + perm_interner, shape_forge, code_forge, prop_forge, }) } - pub fn string_forge(&self) -> &Arc { - &self.string_forge + pub fn perm_interner(&self) -> &Arc { + &self.perm_interner } pub fn shape_forge(&self) -> &Arc { @@ -166,7 +166,8 @@ impl KernelCore { } pub fn sweep_runner_forges(&self) { - self.string_forge.maybe_sweep(self.config.max_dead_strings.or(Some(1))); + // The key interner is append-only (no per-run sweep); only the transient + // shape/prop tables need bounding at the test262 per-test boundary. // test262 creates a fresh VM/session per test. At this boundary no JS object // from prior tests may retain transient shapes/templates. if self.shape_forge.len() > 50_000 { @@ -394,9 +395,9 @@ impl KernelSession { fn new_global_object(core: &KernelCore) -> P { let mut global_obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null()); - let si_nan = core.string_forge.intern("NaN").0; - let si_undef = core.string_forge.intern("undefined").0; - let si_infinity = core.string_forge.intern("Infinity").0; + let si_nan = core.perm_interner.intern("NaN").0; + let si_undef = core.perm_interner.intern("undefined").0; + let si_infinity = core.perm_interner.intern("Infinity").0; let nan_shape = core.shape_forge.make_shape(EMPTY_SHAPE_ID, si_nan); global_obj.set_shape_id(nan_shape); @@ -416,7 +417,7 @@ impl KernelSession { /// Build a fresh session from a KernelCore. All string/shape intern calls hit /// cache on second and subsequent calls — net cost is < 0.5 ms. pub fn new(core: &KernelCore) -> Self { - let builtin_world = Arc::new(BuiltinWorld::new(&core.string_forge, &core.shape_forge)); + let builtin_world = Arc::new(BuiltinWorld::new(&core.perm_interner, &core.shape_forge)); let global_object = Self::new_global_object(core); let builtin_snapshot = BuiltinSnapshot::new(&builtin_world, &global_object); @@ -536,7 +537,7 @@ impl KernelSession { if dirty.any_builtin_dirty() { self.builtin_world = Arc::new(BuiltinWorld::rebuild_with_dirty( &self.builtin_world, - core.string_forge.as_ref(), + core.perm_interner.as_ref(), core.shape_forge.as_ref(), &dirty, )); @@ -553,8 +554,8 @@ mod tests { #[test] fn test_kernel_new() { let core = KernelCore::new(KernelConfig::minimal()); - let (i1, _) = core.string_forge().intern("test"); - let (i2, _) = core.string_forge().intern("test"); + let (i1, _) = core.perm_interner().intern("test"); + let (i2, _) = core.perm_interner().intern("test"); assert_eq!(i1, i2); } @@ -575,8 +576,8 @@ mod tests { #[test] fn test_kernel_string_forge() { let core = KernelCore::new(KernelConfig::minimal()); - let (i1, _) = core.string_forge().intern("hello"); - let (i2, _) = core.string_forge().intern("hello"); + let (i1, _) = core.perm_interner().intern("hello"); + let (i2, _) = core.perm_interner().intern("hello"); assert_eq!(i1, i2); } @@ -596,9 +597,9 @@ mod tests { #[test] fn test_session_rebuild_shares_forges() { let core = KernelCore::new(KernelConfig::minimal()); - let (i1, _) = core.string_forge().intern("hello"); + let (i1, _) = core.perm_interner().intern("hello"); let _s2 = KernelSession::new(&core); - let (i2, _) = core.string_forge().intern("hello"); + let (i2, _) = core.perm_interner().intern("hello"); assert_eq!(i1, i2); } diff --git a/crates/oxide_kernel/src/string_forge.rs b/crates/oxide_kernel/src/string_forge.rs index 8ab5bef9..dd45c4a0 100644 --- a/crates/oxide_kernel/src/string_forge.rs +++ b/crates/oxide_kernel/src/string_forge.rs @@ -1,142 +1,137 @@ use std::hash::{Hash, Hasher}; -use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::RwLock; -use hashbrown::hash_map::DefaultHashBuilder as FxBuildHasher; -use hashbrown::HashMap; +use dashmap::DashMap; +use oxide_types::object::JsString; +use rustc_hash::FxHasher; use crate::{kernel_debug, kernel_trace}; -pub fn hash16(s: &str) -> u16 { - let mut h = rustc_hash::FxHasher::default(); +/// Full 64-bit content hash. Replaces the old 16-bit `hash16` so the interner's +/// hash→candidate map has negligible collision risk. +fn hash64(s: &str) -> u64 { + let mut h = FxHasher::default(); s.hash(&mut h); - (h.finish() >> 48) as u16 + h.finish() } -pub struct StringEntry { - pub(crate) data: String, - pub(crate) ref_count: AtomicU32, - pub hash: u16, +/// One interned key. `data` is a leaked `&'static str` — permanent keys are never +/// freed (append-only by design), so the leak is the storage model, not a bug. +#[derive(Clone, Copy)] +struct PermEntry { + data: &'static str, + hash: u64, } -struct StringForgeInner { - map: HashMap, - entries: Vec, -} - -/// Global string interning pool shared by all VMs. +/// Append-only, never-move, lock-free-read key interner shared by all VMs. /// -/// Concurrency model: -/// `StringForge` keeps a single `RwLock` around the interner state. -/// - `lookup()` takes a shared read lock and scales across readers. -/// - `intern()` uses a read-check then write-check pattern to keep hot-path hits cheap. -/// - New string insertion is serialized, which matches the workload here: most strings -/// are interned early and later accesses are dominated by reads. -pub struct StringForge { - inner: RwLock, +/// Replaces the old ref-counted `StringForge` (and its buggy `maybe_sweep` +/// renumber path). Keys (property names, method names) are interned once and +/// addressed by a stable `u32` id that the shape/IC system keys on. Runtime +/// string *values* are no longer interned here — they are heap `JsString` +/// pointers (see `oxide_vm::Vm::new_string`). +/// +/// Concurrency: +/// - `hash_map` (`DashMap`) gives sharded, lock-free reads of the hash→candidates +/// mapping on the hot intern path. +/// - `entries` sits behind a short `RwLock`; reads copy out a `&'static str` +/// (Copy) so the borrow outlives the guard. +pub struct PermInterner { + hash_map: DashMap>, + entries: RwLock>, + /// Lazily materialized permanent `JsString` values, indexed by entry id. + /// Used when a permanent key must also exist as a JS string *value* + /// (e.g. method names exposed as property values at builtin init). + permanent_strings: RwLock>>>, } -impl StringForge { +impl PermInterner { pub fn new() -> Self { Self { - inner: RwLock::new(StringForgeInner { - map: HashMap::with_hasher(FxBuildHasher::default()), - entries: Vec::new(), - }), + hash_map: DashMap::new(), + entries: RwLock::new(Vec::new()), + permanent_strings: RwLock::new(Vec::new()), } } - pub fn intern(&self, s: &str) -> (u32, u16) { - { - let inner = self.inner.read().unwrap(); - if let Some(&idx) = inner.map.get(s) { - let entry = &inner.entries[idx as usize]; - entry.ref_count.fetch_add(1, Ordering::Release); - kernel_trace!("StringForge intern hit idx={}", idx); - return (idx, entry.hash); + /// Intern a key. Returns its stable id and full 64-bit hash. Each unique + /// string is stored exactly once (single allocation, no double `to_string`). + pub fn intern(&self, s: &str) -> (u32, u64) { + let hash = hash64(s); + + // Fast path: lock-free candidate read, short entries read-lock. + if let Some(candidates) = self.hash_map.get(&hash) { + let entries = self.entries.read().unwrap(); + for &id in candidates.iter() { + if entries[id as usize].data == s { + kernel_trace!("PermInterner intern hit id={}", id); + return (id, hash); + } } } - let mut inner = self.inner.write().unwrap(); - if let Some(&idx) = inner.map.get(s) { - let entry = &inner.entries[idx as usize]; - entry.ref_count.fetch_add(1, Ordering::Release); - kernel_trace!("StringForge intern hit idx={}", idx); - return (idx, entry.hash); - } - - let h = hash16(s); - let idx = inner.entries.len() as u32; - inner.map.insert(s.to_string(), idx); - inner.entries.push(StringEntry { - data: s.to_string(), - ref_count: AtomicU32::new(1), - hash: h, - }); - kernel_debug!("StringForge intern new idx={} len={}", idx, s.len()); - (idx, h) - } - - pub fn lookup(&self, idx: u32) -> Option { - let inner = self.inner.read().unwrap(); - inner.entries.get(idx as usize).map(|e| e.data.clone()) - } - - pub fn get_hash(&self, idx: u32) -> Option { - let inner = self.inner.read().unwrap(); - inner.entries.get(idx as usize).map(|e| e.hash) - } - - pub fn decref(&self, idx: u32) { - let inner = self.inner.read().unwrap(); - if let Some(entry) = inner.entries.get(idx as usize) { - entry.ref_count.fetch_sub(1, Ordering::Release); - } - } - - pub fn maybe_sweep(&self, max_dead: Option) { - let threshold = match max_dead { - Some(t) => t, - None => return, - }; - - let mut inner = self.inner.write().unwrap(); - let dead_count = inner - .entries - .iter() - .filter(|e| e.ref_count.load(Ordering::Acquire) == 0) - .count(); - - if dead_count < threshold { - return; - } - - let mut new_map: HashMap = HashMap::with_hasher(FxBuildHasher::default()); - let mut new_entries = Vec::with_capacity(inner.entries.len() - dead_count); - - for entry in inner.entries.drain(..) { - let rc = entry.ref_count.load(Ordering::Acquire); - if rc > 0 { - let new_idx = new_entries.len() as u32; - new_map.insert(entry.data.clone(), new_idx); - new_entries.push(entry); + // Slow path: append under write lock, re-checking for a racing insert. + let mut entries = self.entries.write().unwrap(); + if let Some(candidates) = self.hash_map.get(&hash) { + for &id in candidates.iter() { + if entries[id as usize].data == s { + return (id, hash); + } } } - - inner.map = new_map; - inner.entries = new_entries; + let id = entries.len() as u32; + let leaked: &'static str = Box::leak(s.to_string().into_boxed_str()); + entries.push(PermEntry { data: leaked, hash }); + drop(entries); + self.hash_map.entry(hash).or_default().push(id); + kernel_debug!("PermInterner intern new id={} len={}", id, s.len()); + (id, hash) } - pub fn len(&self) -> usize { - self.inner.read().unwrap().entries.len() + /// Resolve a key id to its text with zero clone. The returned `&'static str` + /// is valid for the program lifetime (keys are never freed). + pub fn lookup(&self, id: u32) -> Option<&'static str> { + let entries = self.entries.read().unwrap(); + entries.get(id as usize).map(|e| e.data) + } + + /// Full 64-bit hash for a key id. + pub fn get_hash(&self, id: u32) -> Option { + let entries = self.entries.read().unwrap(); + entries.get(id as usize).map(|e| e.hash) + } + + /// Total number of unique keys interned. + pub fn entry_count(&self) -> u32 { + self.entries.read().unwrap().len() as u32 } pub fn is_empty(&self) -> bool { - self.len() == 0 + self.entry_count() == 0 + } + + /// Materialize (once) and return a stable pointer to a permanent `JsString` + /// for the given key id. The `JsString` lives for the program lifetime. + pub fn string_ptr(&self, id: u32) -> *const JsString { + { + let perm = self.permanent_strings.read().unwrap(); + if let Some(Some(boxed)) = perm.get(id as usize) { + return boxed.as_ref() as *const JsString; + } + } + let text = self.lookup(id).unwrap_or(""); + let mut perm = self.permanent_strings.write().unwrap(); + if perm.len() <= id as usize { + perm.resize_with(id as usize + 1, || None); + } + if perm[id as usize].is_none() { + perm[id as usize] = Some(Box::new(JsString::new(text.to_string()))); + } + perm[id as usize].as_ref().unwrap().as_ref() as *const JsString } } -impl Default for StringForge { +impl Default for PermInterner { fn default() -> Self { Self::new() } @@ -147,67 +142,63 @@ mod tests { use super::*; #[test] - fn test_hash16_same() { - assert_eq!(hash16("hello"), hash16("hello")); - } - - #[test] - fn test_hash16_different() { - assert_ne!(hash16("hello"), hash16("world")); - } - - #[test] - fn test_intern_dedup() { - let string_forge = StringForge::new(); - let (i1, h1) = string_forge.intern("abc"); - let (i2, h2) = string_forge.intern("abc"); + fn intern_dedup() { + let interner = PermInterner::new(); + let (i1, h1) = interner.intern("abc"); + let (i2, h2) = interner.intern("abc"); assert_eq!(i1, i2); assert_eq!(h1, h2); } #[test] - fn test_intern_different() { - let string_forge = StringForge::new(); - let (i1, _) = string_forge.intern("x"); - let (i2, _) = string_forge.intern("y"); + fn intern_different() { + let interner = PermInterner::new(); + let (i1, _) = interner.intern("x"); + let (i2, _) = interner.intern("y"); assert_ne!(i1, i2); } #[test] - fn test_lookup_found() { - let string_forge = StringForge::new(); - let (idx, _) = string_forge.intern("hello"); - assert_eq!(string_forge.lookup(idx), Some("hello".to_string())); + fn lookup_zero_clone() { + let interner = PermInterner::new(); + let (id, _) = interner.intern("hello"); + assert_eq!(interner.lookup(id), Some("hello")); } #[test] - fn test_lookup_not_found() { - let string_forge = StringForge::new(); - assert_eq!(string_forge.lookup(99999), None); + fn lookup_not_found() { + let interner = PermInterner::new(); + assert_eq!(interner.lookup(99999), None); } #[test] - fn test_ref_count_and_decref() { - let string_forge = StringForge::new(); - let (idx, _) = string_forge.intern("s"); - assert_eq!(string_forge.lookup(idx), Some("s".to_string())); - string_forge.decref(idx); - assert_eq!(string_forge.lookup(idx), Some("s".to_string())); + fn entry_count_monotonic() { + let interner = PermInterner::new(); + assert_eq!(interner.entry_count(), 0); + interner.intern("a"); + interner.intern("b"); + interner.intern("a"); + assert_eq!(interner.entry_count(), 2); } #[test] - fn test_maybe_sweep_noop_when_none() { - let string_forge = StringForge::new(); - string_forge.intern("live"); - string_forge.maybe_sweep(None); - assert!(string_forge.lookup(0).is_some()); + fn many_unique_no_collision() { + let interner = PermInterner::new(); + for i in 0..10_000 { + let s = format!("key{i}"); + let (id, _) = interner.intern(&s); + assert_eq!(interner.lookup(id), Some(&*Box::leak(s.into_boxed_str()))); + } + assert_eq!(interner.entry_count(), 10_000); } #[test] - fn test_maybe_sweep_skip_below_threshold() { - let string_forge = StringForge::new(); - string_forge.intern("live"); - string_forge.maybe_sweep(Some(100)); - assert_eq!(string_forge.lookup(0), Some("live".to_string())); + fn string_ptr_roundtrip() { + let interner = PermInterner::new(); + let (id, _) = interner.intern("perm"); + let ptr = interner.string_ptr(id); + assert_eq!(unsafe { (*ptr).as_str() }, "perm"); + // Second call returns the same stable pointer (materialized once). + assert_eq!(interner.string_ptr(id), ptr); } } diff --git a/crates/oxide_types/src/object.rs b/crates/oxide_types/src/object.rs index 15572316..72208698 100644 --- a/crates/oxide_types/src/object.rs +++ b/crates/oxide_types/src/object.rs @@ -1,5 +1,42 @@ +use std::hash::{Hash, Hasher}; + +use rustc_hash::FxHasher; + use crate::value::JsValue; +/// Heap-allocated JS string value. +/// +/// String *values* are NaN-boxed as 48-bit pointers to a `JsString` (see +/// `JsValue::string`), replacing the old interner `(index, hash16)` pair. `hash` +/// is a full 64-bit FxHash computed once at construction and used as a fast +/// inequality reject before content comparison. +#[derive(Debug)] +pub struct JsString { + pub data: String, + pub hash: u64, +} + +impl JsString { + pub fn new(data: String) -> Self { + let mut h = FxHasher::default(); + data.hash(&mut h); + let hash = h.finish(); + Self { data, hash } + } + + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + pub fn as_str(&self) -> &str { + self.data.as_str() + } +} + /// Type-safe opaque wrapper around a native function pointer. /// /// Stored as `*const ()` rather than a concrete `fn` type so that `oxide_types` does not diff --git a/crates/oxide_types/src/value.rs b/crates/oxide_types/src/value.rs index 7135835a..0410de73 100644 --- a/crates/oxide_types/src/value.rs +++ b/crates/oxide_types/src/value.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::object::JsObject; +use crate::object::{JsObject, JsString}; /// Quiet NaN prefix — bits 63-51 = sign(1) + exponent(0x7FF) + quiet_bit(1) const QNAN_PREFIX: u64 = 0xFFF8_0000_0000_0000; @@ -28,11 +28,6 @@ const PTR_MASK: u64 = 0x0000_FFFF_FFFF_FFFF; /// 32-bit integer payload mask const INT_MASK: u64 = 0x0000_0000_FFFF_FFFF; -/// String payload: [hash_prefix:16bit][table_index:32bit] -const STRING_HASH_SHIFT: u64 = 32; -const STRING_HASH_MASK: u64 = 0xFFFF_0000_0000; -const STRING_INDEX_MASK: u64 = 0x0000_FFFF_FFFF; - fn make_tag(tag: u64) -> u64 { QNAN_PREFIX | (tag << TAG_SHIFT) } @@ -71,12 +66,7 @@ impl PartialEq for JsValue { return self.as_ptr() == other.as_ptr(); } if self.is_string() && other.is_string() { - let ha = self.as_string_hash(); - let hb = other.as_string_hash(); - if ha != hb { - return false; - } - return self.as_string_index() == other.as_string_index(); + return self.as_string_ptr() == other.as_string_ptr(); } if self.is_symbol() && other.is_symbol() { return self.as_symbol_index() == other.as_symbol_index(); @@ -122,10 +112,17 @@ impl JsValue { Self(make_tag(TAG_OBJECT) | addr) } - pub fn string(index: u32, hash: u16) -> Self { - let payload = ((hash as u64) << STRING_HASH_SHIFT) | (index as u64); - debug_assert!(payload & !(STRING_HASH_MASK | STRING_INDEX_MASK) == 0, "string payload overflow"); - Self(make_tag(TAG_STRING) | payload) + pub fn string(ptr: *const JsString) -> Self { + let addr = ptr as u64; + debug_assert!(addr <= PTR_MASK, "string pointer must fit in 48 bits"); + Self(make_tag(TAG_STRING) | addr) + } + + /// Semantic alias for permanent (kernel-owned, never-collected) strings. + /// Encoding is identical to `string`; the perm-vs-session distinction is by + /// ownership (membership in the GC root set), not by the NaN-box bits. + pub fn perm_string(ptr: *const JsString) -> Self { + Self::string(ptr) } pub fn is_double(&self) -> bool { @@ -214,14 +211,14 @@ impl JsValue { Self(make_tag(TAG_OBJECT) | addr) } - pub fn as_string_index(&self) -> u32 { + pub fn as_string_ptr(&self) -> *const JsString { debug_assert!(self.is_string(), "JsValue is not a string"); - (self.0 & STRING_INDEX_MASK) as u32 + (self.0 & PTR_MASK) as *const JsString } - pub fn as_string_hash(&self) -> u16 { + pub fn as_string_ptr_mut(&self) -> *mut JsString { debug_assert!(self.is_string(), "JsValue is not a string"); - ((self.0 & STRING_HASH_MASK) >> STRING_HASH_SHIFT) as u16 + (self.0 & PTR_MASK) as *mut JsString } pub fn symbol(index: u32) -> Self { @@ -234,7 +231,7 @@ impl JsValue { pub fn as_symbol_index(&self) -> u32 { debug_assert!(self.is_symbol()); - (self.0 & STRING_INDEX_MASK) as u32 + (self.0 & INT_MASK) as u32 } } @@ -301,7 +298,7 @@ impl fmt::Debug for JsValue { } else if self.is_object() { write!(f, "JsValue(Object({:p}))", self.as_ptr()) } else if self.is_string() { - write!(f, "JsValue(String(idx={}, hash={:#06x}))", self.as_string_index(), self.as_string_hash()) + write!(f, "JsValue(String({:p}))", self.as_string_ptr()) } else if self.is_symbol() { write!(f, "JsValue(Symbol(idx={}))", self.as_symbol_index()) } else { @@ -411,4 +408,29 @@ mod tests { }) .unwrap(); } + + #[test] + fn string_ptr_roundtrip() { + use crate::object::JsString; + let s = Box::new(JsString::new("test".to_string())); + let ptr: *const JsString = &*s; + let val = JsValue::string(ptr); + assert!(val.is_string()); + assert_eq!(val.as_string_ptr(), ptr); + assert_eq!(unsafe { (*val.as_string_ptr()).as_str() }, "test"); + } + + #[test] + fn string_pointer_equality() { + use crate::object::JsString; + let a = Box::new(JsString::new("x".to_string())); + let b = Box::new(JsString::new("x".to_string())); + let va = JsValue::string(&*a); + let vb = JsValue::string(&*b); + // Distinct allocations with identical content are NOT == (pointer identity). + // Semantic content equality is handled in coercion, not PartialEq. + assert_ne!(va, vb); + assert_eq!(va, JsValue::string(&*a)); + assert_eq!(unsafe { (*va.as_string_ptr()).as_str() }, unsafe { (*vb.as_string_ptr()).as_str() }); + } } diff --git a/crates/oxide_vm/src/bindings/bind_array.rs b/crates/oxide_vm/src/bindings/bind_array.rs index 76b15465..403a6cbc 100644 --- a/crates/oxide_vm/src/bindings/bind_array.rs +++ b/crates/oxide_vm/src/bindings/bind_array.rs @@ -42,7 +42,7 @@ pub fn bind_array(core: &Arc, session: &KernelSession, global: &mut session.builtin_world().bind_array_methods( &_array_methods, - core.string_forge().as_ref(), + core.perm_interner().as_ref(), core.shape_forge().as_ref(), ); diff --git a/crates/oxide_vm/src/bindings/bind_error.rs b/crates/oxide_vm/src/bindings/bind_error.rs index 3d7e071c..e6d1e512 100644 --- a/crates/oxide_vm/src/bindings/bind_error.rs +++ b/crates/oxide_vm/src/bindings/bind_error.rs @@ -10,7 +10,7 @@ fn bind_error_subtype_constructor( core: &Arc, session: &KernelSession, global: &mut JsObject, name: &str, proto_ptr: *mut JsObject, ctor_fn: *const (), ) { - let sf = core.string_forge().as_ref(); + let sf = core.perm_interner().as_ref(); let sh = core.shape_forge().as_ref(); let function_proto_ptr = session.builtin_world().function_proto.as_ptr() as *mut JsObject; @@ -28,7 +28,7 @@ fn bind_error_subtype_constructor( let ctor_shape2 = sh.make_shape(ctor_shape1, si_name); ctor.set_shape_id(ctor_shape2); ctor.ensure_hash_props().push(JsValue::from_js_object(proto_ptr)); - ctor.ensure_hash_props().push(JsValue::string(name_si, 0)); + ctor.ensure_hash_props().push(JsValue::perm_string(sf.string_ptr(name_si))); let ctor_ptr = Box::into_raw(ctor); @@ -57,11 +57,11 @@ pub fn bind_error(core: &Arc, session: &KernelSession, global: &mut }; session.builtin_world().bind_error_methods( &error_methods, - core.string_forge().as_ref(), + core.perm_interner().as_ref(), core.shape_forge().as_ref(), ); - let si_err = core.string_forge().intern("Error").0; + let si_err = core.perm_interner().intern("Error").0; let err_shape = core.shape_forge().make_shape(global.shape_id(), si_err); let err_val = JsValue::from_js_object(session.builtin_world().error_constructor.as_ptr() as *mut JsObject); global.set_shape_id(err_shape); diff --git a/crates/oxide_vm/src/bindings/bind_function.rs b/crates/oxide_vm/src/bindings/bind_function.rs index f5ec29fc..30867e3b 100644 --- a/crates/oxide_vm/src/bindings/bind_function.rs +++ b/crates/oxide_vm/src/bindings/bind_function.rs @@ -16,7 +16,7 @@ pub fn bind_function(core: &Arc, session: &KernelSession, global: &m }; session.builtin_world().bind_function_methods( &function_methods, - core.string_forge().as_ref(), + core.perm_interner().as_ref(), core.shape_forge().as_ref(), ); diff --git a/crates/oxide_vm/src/bindings/bind_math.rs b/crates/oxide_vm/src/bindings/bind_math.rs index 7a6c1a57..6e00bcd6 100644 --- a/crates/oxide_vm/src/bindings/bind_math.rs +++ b/crates/oxide_vm/src/bindings/bind_math.rs @@ -10,7 +10,7 @@ pub fn bind_math(core: &Arc, session: &KernelSession, global: &mut J let math = unsafe { &mut *math_ptr }; let bw = session.builtin_world(); - let sf = core.string_forge().as_ref(); + let sf = core.perm_interner().as_ref(); let sh = core.shape_forge().as_ref(); bind_methods!( @@ -65,13 +65,13 @@ pub fn bind_math(core: &Arc, session: &KernelSession, global: &mut J ("SQRT1_2", std::f64::consts::FRAC_1_SQRT_2), ("SQRT2", std::f64::consts::SQRT_2), ] { - let si = core.string_forge().as_ref().intern(name).0; + let si = core.perm_interner().as_ref().intern(name).0; let sh_c = core.shape_forge().as_ref().make_shape(math.shape_id(), si); math.set_shape_id(sh_c); math.ensure_hash_props().push(JsValue::float(val)); } - let si_m = core.string_forge().intern("Math").0; + let si_m = core.perm_interner().intern("Math").0; let m_shape = core.shape_forge().make_shape(global.shape_id(), si_m); let m_val = JsValue::from_js_object(session.builtin_world().math_object.as_ptr() as *mut JsObject); global.set_shape_id(m_shape); diff --git a/crates/oxide_vm/src/bindings/bind_number.rs b/crates/oxide_vm/src/bindings/bind_number.rs index 988a4c83..df3b3909 100644 --- a/crates/oxide_vm/src/bindings/bind_number.rs +++ b/crates/oxide_vm/src/bindings/bind_number.rs @@ -50,7 +50,7 @@ pub fn bind_number(core: &Arc, session: &KernelSession, global: &mut ("POSITIVE_INFINITY", JsValue::float(f64::INFINITY)), ] { ctor.ensure_hash_props().push(value); - let prop_si = core.string_forge().intern(name).0; + let prop_si = core.perm_interner().intern(name).0; let next_shape = core.shape_forge().make_shape(ctor.shape_id(), prop_si); ctor.set_shape_id(next_shape); } diff --git a/crates/oxide_vm/src/bindings/bind_object.rs b/crates/oxide_vm/src/bindings/bind_object.rs index 76183695..c976e176 100644 --- a/crates/oxide_vm/src/bindings/bind_object.rs +++ b/crates/oxide_vm/src/bindings/bind_object.rs @@ -32,7 +32,7 @@ pub fn bind_object(core: &Arc, session: &KernelSession, global: &mut }; session .builtin_world() - .bind_object_methods(&methods, core.string_forge().as_ref(), core.shape_forge().as_ref()); + .bind_object_methods(&methods, core.perm_interner().as_ref(), core.shape_forge().as_ref()); let ctor_ptr = session.builtin_world().object_constructor.as_ptr() as *mut JsObject; bind_constructor!(core, global, "Object", ctor_ptr, crate::builtins::object::object_constructor, 1); diff --git a/crates/oxide_vm/src/bindings/bind_string.rs b/crates/oxide_vm/src/bindings/bind_string.rs index cbd70b1c..01dd6a25 100644 --- a/crates/oxide_vm/src/bindings/bind_string.rs +++ b/crates/oxide_vm/src/bindings/bind_string.rs @@ -38,7 +38,7 @@ pub fn bind_string(core: &Arc, session: &KernelSession, global: &mut }; session.builtin_world().bind_string_methods( &string_methods, - core.string_forge().as_ref(), + core.perm_interner().as_ref(), core.shape_forge().as_ref(), ); @@ -55,7 +55,7 @@ pub fn bind_string(core: &Arc, session: &KernelSession, global: &mut &[("toString", crate::builtins::string::string_to_string as *const (), 0)], ); - let si_str = core.string_forge().intern("String").0; + let si_str = core.perm_interner().intern("String").0; let str_shape = core.shape_forge().make_shape(global.shape_id(), si_str); let str_val = JsValue::from_js_object(session.builtin_world().string_constructor.as_ptr() as *mut JsObject); global.set_shape_id(str_shape); diff --git a/crates/oxide_vm/src/bindings/bind_symbol.rs b/crates/oxide_vm/src/bindings/bind_symbol.rs index 3625666a..fd6180da 100644 --- a/crates/oxide_vm/src/bindings/bind_symbol.rs +++ b/crates/oxide_vm/src/bindings/bind_symbol.rs @@ -61,7 +61,7 @@ pub fn bind_symbol(core: &Arc, session: &KernelSession, global: &mut } fn bind_well_known_symbol(core: &Arc, ctor: &mut JsObject, name: &str, val: JsValue) { - let si = core.string_forge().intern(name).0; + let si = core.perm_interner().intern(name).0; let shape_id = core.shape_forge().make_shape(ctor.shape_id(), si); ctor.set_shape_id(shape_id); ctor.ensure_hash_props().push(val); diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 596f37d0..d399da90 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -35,7 +35,7 @@ macro_rules! bind_constructor { bind_constructor!($core, $global, $name, $ctor_ptr, $ctor_fn, $nargs, hash: false) }}; ($core:expr, $global:expr, $name:literal, $ctor_ptr:expr, $ctor_fn:path, $nargs:literal, hash: $hash:literal) => {{ - let si = $core.string_forge().intern($name).0; + let si = $core.perm_interner().intern($name).0; let shape = $core.shape_forge().make_shape($global.shape_id(), si); let val = $crate::JsValue::from_js_object($ctor_ptr); $global.set_shape_id(shape); @@ -67,7 +67,7 @@ pub(crate) fn apply_binding_table( world: &BuiltinWorld, target: &mut JsObject, core: &Arc, bindings: &[(&'static str, *const (), u8)], ) { let shape_forge = core.shape_forge().as_ref(); - let string_forge = core.string_forge().as_ref(); + let string_forge = core.perm_interner().as_ref(); for (name, func, nargs) in bindings { // SAFETY: all entries in the binding table are NativeFn fn-item pointers cast to *const (). let fn_ptr = unsafe { oxide_types::object::NativeFnPtr::from_raw(*func) }; @@ -76,7 +76,7 @@ pub(crate) fn apply_binding_table( } pub(crate) fn bind_global_value(core: &Arc, global: &mut JsObject, name: &str, value: JsValue) { - let si = core.string_forge().intern(name).0; + let si = core.perm_interner().intern(name).0; let shape = core.shape_forge().make_shape(global.shape_id(), si); global.set_shape_id(shape); global.ensure_hash_props().push(value); @@ -91,7 +91,7 @@ fn bind_error_subtype_global( core: &Arc, session: &KernelSession, global: &mut JsObject, name: &str, proto: &P, ctor_fn: *const (), ) { - let constructor_si = core.string_forge().intern("constructor").0; + let constructor_si = core.perm_interner().intern("constructor").0; if let Some(pos) = core.shape_forge().lookup_position(proto.shape_id(), constructor_si) { let existing_ctor = proto.get_prop_at(pos); if existing_ctor.is_object() { @@ -105,7 +105,7 @@ fn bind_error_subtype_global( ctor.set_function(true); configure_native_constructor(&mut ctor, ctor_fn, 1); - let sf = core.string_forge().as_ref(); + let sf = core.perm_interner().as_ref(); let sh = core.shape_forge().as_ref(); let si_prototype = sf.intern("prototype").0; let si_name = sf.intern("name").0; @@ -115,7 +115,7 @@ fn bind_error_subtype_global( ctor.set_shape_id(ctor_shape2); ctor.ensure_hash_props() .push(JsValue::from_js_object(proto.as_ptr() as *mut JsObject)); - ctor.ensure_hash_props().push(JsValue::string(name_si, 0)); + ctor.ensure_hash_props().push(JsValue::perm_string(sf.string_ptr(name_si))); let ctor_ptr = Box::into_raw(ctor); bind_existing_global(core, global, name, JsValue::from_js_object(ctor_ptr)); diff --git a/crates/oxide_vm/src/builtins/array.rs b/crates/oxide_vm/src/builtins/array.rs index 96717476..b10051cb 100644 --- a/crates/oxide_vm/src/builtins/array.rs +++ b/crates/oxide_vm/src/builtins/array.rs @@ -64,7 +64,7 @@ fn create_new_array(vm: &mut Vm, n: usize) -> *mut JsObject { } fn array_length_arg(vm: &mut Vm, value: JsValue) -> Result { - let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref()); + let n = coercion::to_number(value); if !n.is_finite() || n < 0.0 || n.fract() != 0.0 || n > MAX_DENSE_PROPS as f64 { return Err(crate::builtins::error::create_range_error(vm, "Invalid array length")); } @@ -186,12 +186,12 @@ pub fn array_slice(vm: &mut Vm, args: &[u8]) -> NativeResult { let arr = unsafe { &*arr_ptr }; let n = arr.prop_count() as usize; let start = if args.len() > 1 { - (coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32) as usize + (coercion::to_number(vm.reg(args[1])) as i32) as usize } else { 0 }; let end = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as i32) as usize + (coercion::to_number(vm.reg(args[2])) as i32) as usize } else { n }; @@ -215,7 +215,7 @@ pub fn array_splice(vm: &mut Vm, args: &[u8]) -> NativeResult { let n = arr.prop_count() as usize; let start = if args.len() > 1 { - let v = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()); + let v = coercion::to_number(vm.reg(args[1])); let s = v as i32; if s < 0 { (n as i32 + s).max(0) as usize @@ -227,7 +227,7 @@ pub fn array_splice(vm: &mut Vm, args: &[u8]) -> NativeResult { }; let delete_count = if args.len() > 2 { - let v = coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()); + let v = coercion::to_number(vm.reg(args[2])); (v as usize).min(n - start) } else { n - start @@ -311,23 +311,22 @@ pub fn array_join(vm: &mut Vm, args: &[u8]) -> NativeResult { let arr = unsafe { &*arr_ptr }; let n = arr.prop_count() as usize; let sep = if args.len() > 1 { - coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1])) + coercion::to_string(vm.reg(args[1])) } else { ",".to_string() }; - let sf = vm.kernel_core().string_forge().as_ref(); let parts: Vec = (0..n) .map(|i| { let v = arr.get_prop_at(i); if v.is_undefined() || v.is_null() { String::new() } else { - coercion::to_string(sf, v) + coercion::to_string(v) } }) .collect(); let joined = parts.join(&sep); - let result_val = vm.intern(&joined); + let result_val = vm.new_string(&joined); NativeResult::Ok(result_val) } @@ -392,7 +391,7 @@ pub fn array_flat(vm: &mut Vm, args: &[u8]) -> NativeResult { let arr = unsafe { &*arr_ptr }; let n = arr.prop_count() as usize; let depth = if args.len() > 1 { - let n = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()); + let n = coercion::to_number(vm.reg(args[1])); if !n.is_finite() { vm.kernel_core.config.max_call_depth } else { @@ -506,8 +505,7 @@ pub fn array_filter(vm: &mut Vm, args: &[u8]) -> NativeResult { let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) { NativeResult::Ok(result_val) => { - let sf = vm.kernel_core().string_forge().as_ref(); - if coercion::to_boolean(result_val, sf) { + if coercion::to_boolean(result_val) { kept.push(elem); } } @@ -578,8 +576,7 @@ pub fn array_find(vm: &mut Vm, args: &[u8]) -> NativeResult { let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) { NativeResult::Ok(result_val) => { - let sf = vm.kernel_core().string_forge().as_ref(); - if coercion::to_boolean(result_val, sf) { + if coercion::to_boolean(result_val) { return NativeResult::Ok(elem); } } @@ -604,8 +601,7 @@ pub fn array_some(vm: &mut Vm, args: &[u8]) -> NativeResult { let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) { NativeResult::Ok(result_val) => { - let sf = vm.kernel_core().string_forge().as_ref(); - if coercion::to_boolean(result_val, sf) { + if coercion::to_boolean(result_val) { return NativeResult::Ok(JsValue::bool(true)); } } @@ -630,8 +626,7 @@ pub fn array_every(vm: &mut Vm, args: &[u8]) -> NativeResult { let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) { NativeResult::Ok(result_val) => { - let sf = vm.kernel_core().string_forge().as_ref(); - if !coercion::to_boolean(result_val, sf) { + if !coercion::to_boolean(result_val) { return NativeResult::Ok(JsValue::bool(false)); } } @@ -725,12 +720,12 @@ pub fn array_fill(vm: &mut Vm, args: &[u8]) -> NativeResult { let len = arr.prop_count() as usize; let value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; let start = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as i32).max(0) as usize + (coercion::to_number(vm.reg(args[2])) as i32).max(0) as usize } else { 0 }; let end = if args.len() > 3 { - let e = coercion::to_number(vm.reg(args[3]), vm.kernel_core().string_forge().as_ref()) as i32; + let e = coercion::to_number(vm.reg(args[3])) as i32; (e as usize).min(len) } else { len @@ -749,17 +744,17 @@ pub fn array_copy_within(vm: &mut Vm, args: &[u8]) -> NativeResult { return NativeResult::Ok(vm.reg(args[0])); } let target = if args.len() > 1 { - (coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32) as usize + (coercion::to_number(vm.reg(args[1])) as i32) as usize } else { 0 }; let start = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as i32) as usize + (coercion::to_number(vm.reg(args[2])) as i32) as usize } else { 0 }; let end = if args.len() > 3 { - (coercion::to_number(vm.reg(args[3]), vm.kernel_core().string_forge().as_ref()) as i32 as usize).min(len) + (coercion::to_number(vm.reg(args[3])) as i32 as usize).min(len) } else { len }; @@ -779,11 +774,7 @@ pub fn array_at(vm: &mut Vm, args: &[u8]) -> NativeResult { let arr_ptr = array_ptr!(vm, args); let arr = unsafe { &*arr_ptr }; let len = arr.prop_count() as i32; - let mut index = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32 - } else { - 0 - }; + let mut index = if args.len() > 1 { coercion::to_number(vm.reg(args[1])) as i32 } else { 0 }; if index < 0 { index += len; } @@ -820,7 +811,7 @@ pub fn array_find_index(vm: &mut Vm, args: &[u8]) -> NativeResult { let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i as i32), vm.reg(args[0])]) { NativeResult::Ok(r) => { - if coercion::to_boolean(r, vm.kernel_core().string_forge().as_ref()) { + if coercion::to_boolean(r) { return NativeResult::Ok(JsValue::int(i as i32)); } } @@ -848,7 +839,7 @@ pub fn array_find_last(vm: &mut Vm, args: &[u8]) -> NativeResult { let elem = unsafe { (*arr_ptr).get_prop_at(i) }; match invoke_native_callback(vm, callback_val, this_val, &[elem, JsValue::int(i), vm.reg(args[0])]) { NativeResult::Ok(r) => { - if coercion::to_boolean(r, vm.kernel_core().string_forge().as_ref()) { + if coercion::to_boolean(r) { return NativeResult::Ok(elem); } } @@ -917,7 +908,7 @@ pub fn array_sort(vm: &mut Vm, args: &[u8]) -> NativeResult { if let Some(callback) = comparator { match invoke_native_callback(vm, callback, JsValue::undefined(), &[*a, *b]) { NativeResult::Ok(result) => { - let n = coercion::to_number(result, vm.kernel_core().string_forge().as_ref()); + let n = coercion::to_number(result); if n.is_nan() || n == 0.0 { std::cmp::Ordering::Equal } else if n < 0.0 { @@ -937,8 +928,8 @@ pub fn array_sort(vm: &mut Vm, args: &[u8]) -> NativeResult { } } } else { - let sa = coercion::to_string(vm.kernel_core().string_forge().as_ref(), *a); - let sb = coercion::to_string(vm.kernel_core().string_forge().as_ref(), *b); + let sa = coercion::to_string(*a); + let sb = coercion::to_string(*b); sa.cmp(&sb) } }); diff --git a/crates/oxide_vm/src/builtins/array_buffer.rs b/crates/oxide_vm/src/builtins/array_buffer.rs index 93e71027..001dde2f 100644 --- a/crates/oxide_vm/src/builtins/array_buffer.rs +++ b/crates/oxide_vm/src/builtins/array_buffer.rs @@ -22,7 +22,7 @@ fn type_error(vm: &mut Vm, msg: &str) -> NativeResult { } fn to_index(vm: &mut Vm, value: JsValue) -> Result { - let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref()); + let n = coercion::to_number(value); if n.is_nan() { return Ok(0); } @@ -32,8 +32,8 @@ fn to_index(vm: &mut Vm, value: JsValue) -> Result { Ok(n.trunc() as usize) } -fn normalize_index(vm: &mut Vm, value: JsValue, len: usize) -> usize { - let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref()); +fn normalize_index(_vm: &mut Vm, value: JsValue, len: usize) -> usize { + let n = coercion::to_number(value); if n.is_nan() { return 0; } @@ -46,7 +46,7 @@ fn normalize_index(vm: &mut Vm, value: JsValue, len: usize) -> usize { } fn set_named_prop(vm: &mut Vm, obj: &mut JsObject, name: &str, value: JsValue, attributes: PropAttributes) { - let si = vm.kernel_core().string_forge().intern(name).0; + let si = vm.kernel_core().perm_interner().intern(name).0; let _ = vm.define_data_property(obj, si, value, attributes); } @@ -177,5 +177,5 @@ pub fn array_buffer_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { if array_buffer_data_ptr(vm, this_val).is_err() { return type_error(vm, "ArrayBuffer.prototype.toString called on incompatible receiver"); } - NativeResult::Ok(vm.intern("[object ArrayBuffer]")) + NativeResult::Ok(vm.new_string("[object ArrayBuffer]")) } diff --git a/crates/oxide_vm/src/builtins/boolean.rs b/crates/oxide_vm/src/builtins/boolean.rs index 56f41645..07adbf3f 100644 --- a/crates/oxide_vm/src/builtins/boolean.rs +++ b/crates/oxide_vm/src/builtins/boolean.rs @@ -13,12 +13,8 @@ pub fn boolean_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { } else if arg.is_bool() { arg.as_bool() } else if arg.is_string() { - let s = vm - .kernel_core() - .string_forge() - .lookup(arg.as_string_index()) - .unwrap_or_default(); - !s.is_empty() + // SAFETY: arg is a string value. + !unsafe { (*arg.as_string_ptr()).is_empty() } } else if arg.is_object() { true } else if arg.is_int() { @@ -81,9 +77,9 @@ pub fn boolean_prototype_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); if this_val.is_bool() { return if this_val.as_bool() { - NativeResult::Ok(vm.intern("true")) + NativeResult::Ok(vm.new_string("true")) } else { - NativeResult::Ok(vm.intern("false")) + NativeResult::Ok(vm.new_string("false")) }; } if !this_val.is_object() { @@ -106,8 +102,8 @@ pub fn boolean_prototype_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { !val.is_null() && !val.is_undefined() }; if is_true { - NativeResult::Ok(vm.intern("true")) + NativeResult::Ok(vm.new_string("true")) } else { - NativeResult::Ok(vm.intern("false")) + NativeResult::Ok(vm.new_string("false")) } } diff --git a/crates/oxide_vm/src/builtins/data_view.rs b/crates/oxide_vm/src/builtins/data_view.rs index c642a502..b431c9f1 100644 --- a/crates/oxide_vm/src/builtins/data_view.rs +++ b/crates/oxide_vm/src/builtins/data_view.rs @@ -24,7 +24,7 @@ macro_rules! native_try { } fn to_index(vm: &mut Vm, value: JsValue, msg: &str) -> Result { - let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref()); + let n = coercion::to_number(value); if n.is_nan() { return Ok(0); } @@ -35,19 +35,15 @@ fn to_index(vm: &mut Vm, value: JsValue, msg: &str) -> Result { } fn is_little_endian(vm: &mut Vm, args: &[u8], idx: usize) -> bool { - args.get(idx) - .map(|reg| coercion::to_boolean(vm.reg(*reg), vm.kernel_core().string_forge().as_ref())) - .unwrap_or(false) + args.get(idx).map(|reg| coercion::to_boolean(vm.reg(*reg))).unwrap_or(false) } fn numeric_arg(vm: &mut Vm, args: &[u8], idx: usize) -> f64 { - args.get(idx) - .map(|reg| coercion::to_number(vm.reg(*reg), vm.kernel_core().string_forge().as_ref())) - .unwrap_or(0.0) + args.get(idx).map(|reg| coercion::to_number(vm.reg(*reg))).unwrap_or(0.0) } fn set_named_prop(vm: &mut Vm, obj: &mut JsObject, name: &str, value: JsValue, attributes: PropAttributes) { - let si = vm.kernel_core().string_forge().intern(name).0; + let si = vm.kernel_core().perm_interner().intern(name).0; let _ = vm.define_data_property(obj, si, value, attributes); } @@ -438,5 +434,5 @@ pub fn data_view_set_big_uint64(vm: &mut Vm, args: &[u8]) -> NativeResult { pub fn data_view_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); native_try!(get_data_view_data(vm, this_val)); - NativeResult::Ok(vm.intern("[object DataView]")) + NativeResult::Ok(vm.new_string("[object DataView]")) } diff --git a/crates/oxide_vm/src/builtins/date.rs b/crates/oxide_vm/src/builtins/date.rs index 55f5ced2..1436ff2d 100644 --- a/crates/oxide_vm/src/builtins/date.rs +++ b/crates/oxide_vm/src/builtins/date.rs @@ -104,33 +104,13 @@ pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { let timestamp = if args.len() < 2 { Utc::now().timestamp_millis() as f64 } else if args.len() > 2 { - let y = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32; - let m = coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as u32; - let d = if args.len() > 3 { - coercion::to_number(vm.reg(args[3]), vm.kernel_core().string_forge().as_ref()) as u32 - } else { - 1 - }; - let h = if args.len() > 4 { - coercion::to_number(vm.reg(args[4]), vm.kernel_core().string_forge().as_ref()) as u32 - } else { - 0 - }; - let min = if args.len() > 5 { - coercion::to_number(vm.reg(args[5]), vm.kernel_core().string_forge().as_ref()) as u32 - } else { - 0 - }; - let sec = if args.len() > 6 { - coercion::to_number(vm.reg(args[6]), vm.kernel_core().string_forge().as_ref()) as u32 - } else { - 0 - }; - let ms = if args.len() > 7 { - coercion::to_number(vm.reg(args[7]), vm.kernel_core().string_forge().as_ref()) as u32 - } else { - 0 - }; + let y = coercion::to_number(vm.reg(args[1])) as i32; + let m = coercion::to_number(vm.reg(args[2])) as u32; + let d = if args.len() > 3 { coercion::to_number(vm.reg(args[3])) as u32 } else { 1 }; + let h = if args.len() > 4 { coercion::to_number(vm.reg(args[4])) as u32 } else { 0 }; + let min = if args.len() > 5 { coercion::to_number(vm.reg(args[5])) as u32 } else { 0 }; + let sec = if args.len() > 6 { coercion::to_number(vm.reg(args[6])) as u32 } else { 0 }; + let ms = if args.len() > 7 { coercion::to_number(vm.reg(args[7])) as u32 } else { 0 }; NaiveDate::from_ymd_opt(y, m + 1, d) .and_then(|nd| { nd.and_hms_milli_opt(h, min, sec, ms) @@ -141,11 +121,8 @@ pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { } else { let val = vm.reg(args[1]); if val.is_string() { - let s = vm - .kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default(); + // SAFETY: val is a string value. + let s = unsafe { (*val.as_string_ptr()).data.clone() }; let formats = ["%Y-%m-%dT%H:%M:%S%.fZ", "%Y-%m-%dT%H:%M:%S%.f"]; let mut ts = f64::NAN; for fmt in &formats { @@ -191,7 +168,7 @@ pub fn date_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { } else { "Invalid Date".to_string() }; - return NativeResult::Ok(vm.intern(&s)); + return NativeResult::Ok(vm.new_string(&s)); } let mut obj = JsObject::new_empty( @@ -217,11 +194,8 @@ pub fn date_parse(vm: &mut Vm, args: &[u8]) -> NativeResult { if !val.is_string() { return NativeResult::Ok(JsValue::float(f64::NAN)); } - let s = vm - .kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default(); + // SAFETY: val is a string value. + let s = unsafe { (*val.as_string_ptr()).data.clone() }; let formats = ["%Y-%m-%dT%H:%M:%S%.fZ", "%Y-%m-%dT%H:%M:%S%.f"]; let mut ts = f64::NAN; for fmt in &formats { @@ -315,7 +289,7 @@ fn local_offset_minutes() -> i32 { fn get_opt_arg(vm: &Vm, args: &[u8], idx: usize, default: u32) -> u32 { if args.len() > idx { - coercion::to_number(vm.reg(args[idx]), vm.kernel_core().string_forge().as_ref()) as u32 + coercion::to_number(vm.reg(args[idx])) as u32 } else { default } @@ -323,18 +297,14 @@ fn get_opt_arg(vm: &Vm, args: &[u8], idx: usize, default: u32) -> u32 { pub fn date_set_time(vm: &mut Vm, args: &[u8]) -> NativeResult { let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; - let val = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) - } else { - f64::NAN - }; + let val = if args.len() > 1 { coercion::to_number(vm.reg(args[1])) } else { f64::NAN }; set_timestamp(obj, val); NativeResult::Ok(JsValue::float(val)) } pub fn date_set_full_year(vm: &mut Vm, args: &[u8]) -> NativeResult { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let v = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let v = coercion::to_number(val); let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -357,7 +327,7 @@ pub fn date_set_full_year(vm: &mut Vm, args: &[u8]) -> NativeResult { } pub fn date_set_month(vm: &mut Vm, args: &[u8]) -> NativeResult { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let v = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let v = coercion::to_number(val); let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -375,7 +345,7 @@ pub fn date_set_month(vm: &mut Vm, args: &[u8]) -> NativeResult { } pub fn date_set_date(vm: &mut Vm, args: &[u8]) -> NativeResult { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let v = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let v = coercion::to_number(val); let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -392,7 +362,7 @@ pub fn date_set_date(vm: &mut Vm, args: &[u8]) -> NativeResult { } pub fn date_set_hours(vm: &mut Vm, args: &[u8]) -> NativeResult { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let v = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let v = coercion::to_number(val); let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -417,7 +387,7 @@ pub fn date_set_hours(vm: &mut Vm, args: &[u8]) -> NativeResult { } pub fn date_set_minutes(vm: &mut Vm, args: &[u8]) -> NativeResult { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let v = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let v = coercion::to_number(val); let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -440,7 +410,7 @@ pub fn date_set_minutes(vm: &mut Vm, args: &[u8]) -> NativeResult { } pub fn date_set_seconds(vm: &mut Vm, args: &[u8]) -> NativeResult { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let v = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let v = coercion::to_number(val); let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -461,7 +431,7 @@ pub fn date_set_seconds(vm: &mut Vm, args: &[u8]) -> NativeResult { } pub fn date_set_milliseconds(vm: &mut Vm, args: &[u8]) -> NativeResult { let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; - let v = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let v = coercion::to_number(val); let obj = unsafe { &mut *native_try!(date_this_mut(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { @@ -481,13 +451,13 @@ fn date_to_string_inner(vm: &mut Vm, args: &[u8], format_str: &str, invalid: &st let obj = unsafe { &*native_try!(date_this(vm, args)) }; let ms = get_timestamp(obj); if !ms.is_finite() { - return NativeResult::Ok(vm.intern(invalid)); + return NativeResult::Ok(vm.new_string(invalid)); } let s = match dt_from_ms(ms) { Some(dt) => dt.format(format_str).to_string(), None => invalid.to_string(), }; - NativeResult::Ok(vm.intern(&s)) + NativeResult::Ok(vm.new_string(&s)) } pub fn date_to_iso_string(vm: &mut Vm, args: &[u8]) -> NativeResult { @@ -504,7 +474,7 @@ pub fn date_to_json(vm: &mut Vm, args: &[u8]) -> NativeResult { Some(dt) => dt.format("%Y-%m-%dT%H:%M:%S%.3fZ").to_string(), None => return NativeResult::Ok(JsValue::null()), }; - NativeResult::Ok(vm.intern(&s)) + NativeResult::Ok(vm.new_string(&s)) } pub fn date_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { diff --git a/crates/oxide_vm/src/builtins/error.rs b/crates/oxide_vm/src/builtins/error.rs index b704b9c6..77f5fac2 100644 --- a/crates/oxide_vm/src/builtins/error.rs +++ b/crates/oxide_vm/src/builtins/error.rs @@ -14,12 +14,12 @@ fn create_error_object(vm: &mut Vm, error_proto_ptr: *mut JsObject, name: &str, .epoch() .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(error_proto_ptr))); - let sf = Arc::clone(vm.kernel_core().string_forge()); + let sf = Arc::clone(vm.kernel_core().perm_interner()); let sh = Arc::clone(vm.kernel_core().shape_forge()); let si_message = sf.intern("message").0; let sh_message = sh.make_shape(EMPTY_SHAPE_ID, si_message); - let msg_val = vm.intern(message); + let msg_val = vm.new_string(message); unsafe { (*obj).set_shape_id(sh_message); (*obj).push_prop(msg_val); @@ -27,7 +27,7 @@ fn create_error_object(vm: &mut Vm, error_proto_ptr: *mut JsObject, name: &str, let si_name = sf.intern("name").0; let sh_name = sh.make_shape(sh_message, si_name); - let name_val = vm.intern(name); + let name_val = vm.new_string(name); unsafe { (*obj).set_shape_id(sh_name); (*obj).push_prop(name_val); @@ -75,7 +75,7 @@ pub fn create_uri_error(vm: &mut Vm, msg: &str) -> JsValue { fn get_msg(vm: &mut Vm, args: &[u8]) -> String { if args.len() > 1 { - coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1])) + coercion::to_string(vm.reg(args[1])) } else { String::new() } @@ -145,8 +145,7 @@ pub fn error_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { ("Error".to_string(), String::new()) }; let result = crate::vm::format_error_message(&name, &msg); - let si = vm.kernel_core().string_forge().intern(&result).0; - NativeResult::Ok(JsValue::string(si, 0)) + NativeResult::Ok(vm.new_string(&result)) } pub fn error_stack_getter(vm: &mut Vm, _args: &[u8]) -> NativeResult { @@ -154,15 +153,14 @@ pub fn error_stack_getter(vm: &mut Vm, _args: &[u8]) -> NativeResult { for frame in vm.frames.iter().rev() { let func_name = vm .kernel_core() - .string_forge() + .perm_interner() .lookup(frame.function_name) - .unwrap_or_else(|| "".to_string()); + .unwrap_or(""); lines.push(format!(" at {} (native)", func_name)); } lines.push(" at (native)".to_string()); let result = lines.join("\n"); - let si = vm.kernel_core().string_forge().intern(&result).0; - NativeResult::Ok(JsValue::string(si, 0)) + NativeResult::Ok(vm.new_string(&result)) } #[cfg(test)] @@ -174,7 +172,7 @@ mod tests { #[test] fn error_stack_uses_call_frame_function_name() { let mut vm = Vm::new(); - let name_si = vm.kernel_core().string_forge().intern("foo").0; + let name_si = vm.kernel_core().perm_interner().intern("foo").0; vm.frames.push(CallFrame { return_addr: 0, function_name: name_si, @@ -190,11 +188,7 @@ mod tests { }); let stack = error_stack_getter(&mut vm, &[0]).unwrap(); - let stack_str = vm - .kernel_core() - .string_forge() - .lookup(stack.as_string_index()) - .unwrap_or_default(); + let stack_str = vm.lookup_str(stack).unwrap_or_default(); assert!(stack_str.contains("foo"), "expected function name in stack: {stack_str}"); } } diff --git a/crates/oxide_vm/src/builtins/function.rs b/crates/oxide_vm/src/builtins/function.rs index dd1470bb..a69579d5 100644 --- a/crates/oxide_vm/src/builtins/function.rs +++ b/crates/oxide_vm/src/builtins/function.rs @@ -121,16 +121,10 @@ pub fn function_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { )); } - let name_si = vm.kernel_core().string_forge().intern("name").0; + let name_si = vm.kernel_core().perm_interner().intern("name").0; let name = vm .resolve_property(func, name_si) - .and_then(|v| { - if v.is_string() { - vm.kernel_core().string_forge().lookup(v.as_string_index()) - } else { - None - } - }) + .and_then(|v| vm.lookup_str(v)) .unwrap_or_else(|| { let sub_idx = func.sub_module_index(); if sub_idx > 0 && (sub_idx as usize) <= vm.sub_modules.len() { @@ -146,6 +140,5 @@ pub fn function_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { } else { format!("function {name}() {{ {body} }}") }; - let si = vm.kernel_core().string_forge().intern(&result).0; - NativeResult::Ok(JsValue::string(si, 0)) + NativeResult::Ok(vm.new_string(&result)) } diff --git a/crates/oxide_vm/src/builtins/global.rs b/crates/oxide_vm/src/builtins/global.rs index c7c0acee..16051375 100644 --- a/crates/oxide_vm/src/builtins/global.rs +++ b/crates/oxide_vm/src/builtins/global.rs @@ -9,7 +9,7 @@ pub use uri::{decode_uri, decode_uri_component, encode_uri, encode_uri_component fn string_arg(vm: &mut Vm, args: &[u8]) -> String { if args.len() > 1 { - coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1])) + coercion::to_string(vm.reg(args[1])) } else { "undefined".to_string() } diff --git a/crates/oxide_vm/src/builtins/global/annex_b.rs b/crates/oxide_vm/src/builtins/global/annex_b.rs index 4aea2a7b..7e4950ab 100644 --- a/crates/oxide_vm/src/builtins/global/annex_b.rs +++ b/crates/oxide_vm/src/builtins/global/annex_b.rs @@ -87,12 +87,12 @@ fn unescape_string(input: &str) -> String { pub fn js_escape(vm: &mut Vm, args: &[u8]) -> NativeResult { let input = super::string_arg(vm, args); - NativeResult::Ok(vm.intern(&escape_string(&input))) + NativeResult::Ok(vm.new_string(&escape_string(&input))) } pub fn js_unescape(vm: &mut Vm, args: &[u8]) -> NativeResult { let input = super::string_arg(vm, args); - NativeResult::Ok(vm.intern(&unescape_string(&input))) + NativeResult::Ok(vm.new_string(&unescape_string(&input))) } #[cfg(test)] diff --git a/crates/oxide_vm/src/builtins/global/uri.rs b/crates/oxide_vm/src/builtins/global/uri.rs index d2291da8..a37b7838 100644 --- a/crates/oxide_vm/src/builtins/global/uri.rs +++ b/crates/oxide_vm/src/builtins/global/uri.rs @@ -88,18 +88,18 @@ fn uri_error(vm: &mut Vm) -> NativeResult { pub fn encode_uri(vm: &mut Vm, args: &[u8]) -> NativeResult { let input = super::string_arg(vm, args); - NativeResult::Ok(vm.intern(&encode_uri_string(&input, URI_SAFE))) + NativeResult::Ok(vm.new_string(&encode_uri_string(&input, URI_SAFE))) } pub fn encode_uri_component(vm: &mut Vm, args: &[u8]) -> NativeResult { let input = super::string_arg(vm, args); - NativeResult::Ok(vm.intern(&encode_uri_string(&input, URI_UNESCAPED))) + NativeResult::Ok(vm.new_string(&encode_uri_string(&input, URI_UNESCAPED))) } pub fn decode_uri(vm: &mut Vm, args: &[u8]) -> NativeResult { let input = super::string_arg(vm, args); match decode_uri_string(&input, true) { - Ok(decoded) => NativeResult::Ok(vm.intern(&decoded)), + Ok(decoded) => NativeResult::Ok(vm.new_string(&decoded)), Err(()) => uri_error(vm), } } @@ -107,7 +107,7 @@ pub fn decode_uri(vm: &mut Vm, args: &[u8]) -> NativeResult { pub fn decode_uri_component(vm: &mut Vm, args: &[u8]) -> NativeResult { let input = super::string_arg(vm, args); match decode_uri_string(&input, false) { - Ok(decoded) => NativeResult::Ok(vm.intern(&decoded)), + Ok(decoded) => NativeResult::Ok(vm.new_string(&decoded)), Err(()) => uri_error(vm), } } diff --git a/crates/oxide_vm/src/builtins/iterator.rs b/crates/oxide_vm/src/builtins/iterator.rs index 81b50396..4e561ef7 100644 --- a/crates/oxide_vm/src/builtins/iterator.rs +++ b/crates/oxide_vm/src/builtins/iterator.rs @@ -28,9 +28,9 @@ pub(crate) fn make_iterator_for_value(vm: &mut Vm, value: JsValue) -> Result NativeResult { } let wrapper = unsafe { &mut *this_val.as_js_object_ptr() }; - let inner_si = vm.kernel_core().string_forge().intern(INNER_PROP).0; - let index_si = vm.kernel_core().string_forge().intern(INDEX_PROP).0; + let inner_si = vm.kernel_core().perm_interner().intern(INNER_PROP).0; + let index_si = vm.kernel_core().perm_interner().intern(INDEX_PROP).0; let inner = match vm.ordinary_get(wrapper, inner_si, this_val) { Ok(inner) if !inner.is_undefined() => inner, _ => { @@ -69,7 +69,7 @@ pub fn iterator_wrapper_next(vm: &mut Vm, args: &[u8]) -> NativeResult { if inner.is_object() { let inner_obj = unsafe { &*inner.as_js_object_ptr() }; - let next_si = vm.kernel_core().string_forge().intern("next").0; + let next_si = vm.kernel_core().perm_interner().intern("next").0; let next = match vm.ordinary_get(inner_obj, next_si, inner) { Ok(next) => next, Err(err) => return NativeResult::Err(crate::builtins::error::create_type_error(vm, &err)), @@ -90,7 +90,7 @@ fn get_iterator(vm: &mut Vm, value: JsValue) -> Result { if value.is_object() { let obj = unsafe { &*value.as_js_object_ptr() }; - let next_si = vm.kernel_core().string_forge().intern("next").0; + let next_si = vm.kernel_core().perm_interner().intern("next").0; if let Ok(next) = vm.ordinary_get(obj, next_si, value) { if is_callable(next) { return Ok(value); @@ -115,11 +115,11 @@ fn next_array_like(vm: &mut Vm, wrapper: &mut JsObject, inner: JsValue, index_si if inner.is_string() { let index = current_index(vm, wrapper, index_si); - let source = coercion::to_string(vm.kernel_core().string_forge().as_ref(), inner); + let source = coercion::to_string(inner); let mut chars = source.chars(); if let Some(ch) = chars.nth(index) { vm.set_or_create_prop_value(wrapper, index_si, JsValue::int((index + 1) as i32)); - let value = vm.intern(&ch.to_string()); + let value = vm.new_string(&ch.to_string()); return Some(make_iter_result(vm, value, false)); } return Some(make_iter_result(vm, JsValue::undefined(), true)); @@ -141,8 +141,8 @@ fn make_iter_result(vm: &mut Vm, value: JsValue, done: bool) -> JsValue { let obj = vm .epoch() .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto))); - let value_si = vm.kernel_core().string_forge().intern("value").0; - let done_si = vm.kernel_core().string_forge().intern("done").0; + let value_si = vm.kernel_core().perm_interner().intern("value").0; + let done_si = vm.kernel_core().perm_interner().intern("done").0; let obj_ref = unsafe { &mut *obj }; vm.set_or_create_prop_value(obj_ref, value_si, value); vm.set_or_create_prop_value(obj_ref, done_si, JsValue::bool(done)); @@ -157,8 +157,8 @@ fn make_native_function(vm: &mut Vm, name: &str, native_fn: *const (), arg_count func.set_native_fn(Some(unsafe { oxide_types::object::NativeFnPtr::from_raw(native_fn) })); func.set_native_arg_count(arg_count); let func = vm.alloc_object(func); - let name_si = vm.kernel_core().string_forge().intern("name").0; - let value = vm.intern(name); + let name_si = vm.kernel_core().perm_interner().intern("name").0; + let value = vm.new_string(name); let func_ref = unsafe { &mut *func }; vm.set_or_create_prop_value(func_ref, name_si, value); JsValue::from_js_object(func) diff --git a/crates/oxide_vm/src/builtins/json.rs b/crates/oxide_vm/src/builtins/json.rs index 515d2dcb..a0485c6e 100644 --- a/crates/oxide_vm/src/builtins/json.rs +++ b/crates/oxide_vm/src/builtins/json.rs @@ -21,8 +21,8 @@ pub fn json_parse(vm: &mut Vm, args: &[u8]) -> NativeResult { )); } let text = { - let si = val.as_string_index(); - vm.kernel_core().string_forge().lookup(si).unwrap_or_default() + // SAFETY: val is a string value. + unsafe { (*val.as_string_ptr()).data.clone() } }; let parsed: serde_json::Value = match serde_json::from_str(&text) { @@ -44,7 +44,7 @@ fn value_to_jsvalue(vm: &mut Vm, val: &serde_json::Value) -> JsValue { JsValue::float(0.0) } } - serde_json::Value::String(s) => vm.intern(s), + serde_json::Value::String(s) => vm.new_string(s), serde_json::Value::Array(arr) => { let array_proto = vm.session().builtin_world().array_proto.as_ptr() as *mut JsObject; let n = arr.len(); @@ -66,7 +66,7 @@ fn value_to_jsvalue(vm: &mut Vm, val: &serde_json::Value) -> JsValue { let object_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto)); for (key, val) in map { - let si = vm.kernel_core().string_forge().intern(key).0; + let si = vm.kernel_core().perm_interner().intern(key).0; let jsv = value_to_jsvalue(vm, val); let new_shape = vm.kernel_core().shape_forge().make_shape(obj.shape_id(), si); obj.set_shape_id(new_shape); @@ -91,7 +91,7 @@ pub fn json_stringify(vm: &mut Vm, args: &[u8]) -> NativeResult { if jsvalue_to_json(vm, value, &mut visited, &mut output).is_err() { return NativeResult::Err(crate::builtins::error::create_type_error(vm, "cyclic object value")); }; - NativeResult::Ok(vm.intern(&output)) + NativeResult::Ok(vm.new_string(&output)) } fn jsvalue_to_json(vm: &Vm, val: JsValue, visited: &mut HashSet<*const u8>, out: &mut String) -> Result<(), ()> { @@ -113,8 +113,8 @@ fn jsvalue_to_json(vm: &Vm, val: JsValue, visited: &mut HashSet<*const u8>, out: out.push_str(buf.format(n)); } } else if val.is_string() { - let si = val.as_string_index(); - let s = vm.kernel_core().string_forge().lookup(si).unwrap_or_default(); + // SAFETY: val is a string value. + let s = unsafe { (*val.as_string_ptr()).data.clone() }; stringify_string(&s, out); } else if val.is_object() { let obj_ptr = val.as_js_object_ptr(); @@ -184,8 +184,8 @@ fn stringify_object(vm: &Vm, obj: &JsObject, visited: &mut HashSet<*const u8>, o out.push(','); } first = false; - let name = vm.kernel_core().string_forge().lookup(si).unwrap_or_default(); - stringify_string(&name, out); + let name = vm.kernel_core().perm_interner().lookup(si).unwrap_or_default(); + stringify_string(name, out); out.push(':'); jsvalue_to_json(vm, val, visited, out)?; } diff --git a/crates/oxide_vm/src/builtins/math.rs b/crates/oxide_vm/src/builtins/math.rs index acd3840a..d1e3949b 100644 --- a/crates/oxide_vm/src/builtins/math.rs +++ b/crates/oxide_vm/src/builtins/math.rs @@ -5,7 +5,7 @@ use crate::native::NativeResult; use crate::vm::Vm; fn num(vm: &Vm, reg: u8) -> f64 { - coercion::to_number(vm.reg(reg), vm.kernel_core().string_forge().as_ref()) + coercion::to_number(vm.reg(reg)) } fn arg1(vm: &Vm, args: &[u8]) -> f64 { diff --git a/crates/oxide_vm/src/builtins/number.rs b/crates/oxide_vm/src/builtins/number.rs index e96fe66d..c6aa6760 100644 --- a/crates/oxide_vm/src/builtins/number.rs +++ b/crates/oxide_vm/src/builtins/number.rs @@ -5,11 +5,7 @@ use crate::native::NativeResult; use crate::vm::Vm; pub fn number_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { - let n = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) - } else { - 0.0 - }; + let n = if args.len() > 1 { coercion::to_number(vm.reg(args[1])) } else { 0.0 }; let number_proto = vm.session().builtin_world().number_proto.as_ptr() as *mut oxide_types::object::JsObject; let is_ctor = if let Some(this_reg) = args.first().copied() { let this_val = vm.reg(this_reg); @@ -71,7 +67,7 @@ pub fn number_is_finite(vm: &mut Vm, args: &[u8]) -> NativeResult { if val.is_double() { return NativeResult::Ok(JsValue::bool(val.as_double().is_finite())); } - let n = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let n = coercion::to_number(val); NativeResult::Ok(JsValue::bool(n.is_finite())) } @@ -79,8 +75,7 @@ pub fn number_parse_int(vm: &mut Vm, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::float(f64::NAN)); } - let sf = vm.kernel_core().string_forge().as_ref(); - let s = coercion::to_string(sf, vm.reg(args[1])); + let s = coercion::to_string(vm.reg(args[1])); let s = s.trim(); if s.is_empty() { @@ -88,7 +83,7 @@ pub fn number_parse_int(vm: &mut Vm, args: &[u8]) -> NativeResult { } let radix = if args.len() > 2 { - let r = coercion::to_number(vm.reg(args[2]), sf) as i32; + let r = coercion::to_number(vm.reg(args[2])) as i32; if r == 0 { 10 } else { @@ -121,8 +116,7 @@ pub fn number_parse_float(vm: &mut Vm, args: &[u8]) -> NativeResult { if args.len() < 2 { return NativeResult::Ok(JsValue::float(f64::NAN)); } - let sf = vm.kernel_core().string_forge().as_ref(); - let s = coercion::to_string(sf, vm.reg(args[1])); + let s = coercion::to_string(vm.reg(args[1])); let s = s.trim(); if s.is_empty() { @@ -136,9 +130,9 @@ pub fn number_parse_float(vm: &mut Vm, args: &[u8]) -> NativeResult { } pub fn number_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { - let n = coercion::to_number(vm.reg(args[0]), vm.kernel_core().string_forge().as_ref()); + let n = coercion::to_number(vm.reg(args[0])); let radix = if args.len() > 1 { - let r = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as u32; + let r = coercion::to_number(vm.reg(args[1])) as u32; r.clamp(2, 36) } else { 10u32 @@ -146,22 +140,22 @@ pub fn number_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { if radix == 10 { if n.is_nan() { - return NativeResult::Ok(vm.intern("NaN")); + return NativeResult::Ok(vm.new_string("NaN")); } if n.is_infinite() { if n.is_sign_positive() { - return NativeResult::Ok(vm.intern("Infinity")); + return NativeResult::Ok(vm.new_string("Infinity")); } - return NativeResult::Ok(vm.intern("-Infinity")); + return NativeResult::Ok(vm.new_string("-Infinity")); } if n.fract() == 0.0 && n >= i32::MIN as f64 && n <= i32::MAX as f64 { - return NativeResult::Ok(vm.intern(&(n as i64).to_string())); + return NativeResult::Ok(vm.new_string(&(n as i64).to_string())); } let mut buf = ryu::Buffer::new(); - NativeResult::Ok(vm.intern(buf.format(n))) + NativeResult::Ok(vm.new_string(buf.format(n))) } else { if n.is_nan() { - return NativeResult::Ok(vm.intern("NaN")); + return NativeResult::Ok(vm.new_string("NaN")); } let nn = n as i64; let mut result = String::new(); @@ -182,70 +176,70 @@ pub fn number_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { if nn < 0 { result.insert(0, '-'); } - NativeResult::Ok(vm.intern(&result)) + NativeResult::Ok(vm.new_string(&result)) } } pub fn number_to_fixed(vm: &mut Vm, args: &[u8]) -> NativeResult { - let n = coercion::to_number(vm.reg(args[0]), vm.kernel_core().string_forge().as_ref()); + let n = coercion::to_number(vm.reg(args[0])); let digits = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as usize + coercion::to_number(vm.reg(args[1])) as usize } else { 0usize } .min(20); let formatted = format!("{:.digits$}", n); - NativeResult::Ok(vm.intern(&formatted)) + NativeResult::Ok(vm.new_string(&formatted)) } pub fn number_is_integer(vm: &mut Vm, args: &[u8]) -> NativeResult { let val = vm.reg(if args.len() > 1 { args[1] } else { args[0] }); - let n = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let n = coercion::to_number(val); NativeResult::Ok(JsValue::bool(n.trunc() == n && n.is_finite())) } pub fn number_is_safe_integer(vm: &mut Vm, args: &[u8]) -> NativeResult { let val = vm.reg(if args.len() > 1 { args[1] } else { args[0] }); - let n = coercion::to_number(val, vm.kernel_core().string_forge().as_ref()); + let n = coercion::to_number(val); let safe = n.trunc() == n && n.is_finite() && n >= -9007199254740991i64 as f64 && n <= 9007199254740991i64 as f64; NativeResult::Ok(JsValue::bool(safe)) } pub fn number_to_exponential(vm: &mut Vm, args: &[u8]) -> NativeResult { - let n = coercion::to_number(vm.reg(args[0]), vm.kernel_core().string_forge().as_ref()); + let n = coercion::to_number(vm.reg(args[0])); let digits = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as usize + coercion::to_number(vm.reg(args[1])) as usize } else { 0usize } .min(100); if n.is_nan() { - return NativeResult::Ok(vm.intern("NaN")); + return NativeResult::Ok(vm.new_string("NaN")); } if n.is_infinite() { - return NativeResult::Ok(vm.intern(if n.is_sign_positive() { "Infinity" } else { "-Infinity" })); + return NativeResult::Ok(vm.new_string(if n.is_sign_positive() { "Infinity" } else { "-Infinity" })); } let formatted = format!("{:.digits$e}", n); - NativeResult::Ok(vm.intern(&formatted)) + NativeResult::Ok(vm.new_string(&formatted)) } pub fn number_to_precision(vm: &mut Vm, args: &[u8]) -> NativeResult { - let n = coercion::to_number(vm.reg(args[0]), vm.kernel_core().string_forge().as_ref()); + let n = coercion::to_number(vm.reg(args[0])); let precision = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as usize + coercion::to_number(vm.reg(args[1])) as usize } else { 0usize } .min(21); if n.is_nan() { - return NativeResult::Ok(vm.intern("NaN")); + return NativeResult::Ok(vm.new_string("NaN")); } if n.is_infinite() { - return NativeResult::Ok(vm.intern(if n.is_sign_positive() { "Infinity" } else { "-Infinity" })); + return NativeResult::Ok(vm.new_string(if n.is_sign_positive() { "Infinity" } else { "-Infinity" })); } let formatted = format!("{:.precision$}", n); - NativeResult::Ok(vm.intern(&formatted)) + NativeResult::Ok(vm.new_string(&formatted)) } pub fn number_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult { diff --git a/crates/oxide_vm/src/builtins/object.rs b/crates/oxide_vm/src/builtins/object.rs index a2445d8e..a2c5f2d0 100644 --- a/crates/oxide_vm/src/builtins/object.rs +++ b/crates/oxide_vm/src/builtins/object.rs @@ -1,5 +1,5 @@ use oxide_kernel::shape_forge::{ShapeForge, EMPTY_SHAPE_ID}; -use oxide_kernel::string_forge::StringForge; +use oxide_kernel::string_forge::PermInterner; use oxide_types::object::{JsObject, PropAttributes}; use oxide_types::private_key::is_private_name_key; use oxide_types::value::JsValue; @@ -62,7 +62,7 @@ pub fn object_keys(vm: &mut Vm, args: &[u8]) -> NativeResult { let keys = walk_own_keys(vm, obj); key_names = keys .iter() - .map(|(si, _offset)| vm.kernel_core().string_forge().lookup(*si).unwrap_or_default()) + .map(|(si, _offset)| vm.kernel_core().perm_interner().lookup(*si).unwrap_or("").to_string()) .collect(); } @@ -75,7 +75,7 @@ pub fn object_keys(vm: &mut Vm, args: &[u8]) -> NativeResult { vm.epoch().bump(), )); for (i, k) in key_names.iter().enumerate() { - let str_val = vm.intern(k); + let str_val = vm.new_string(k); unsafe { (*arr).set_prop_at(i, str_val); } @@ -159,27 +159,23 @@ pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult { if desc_ptr.is_null() { return NativeResult::Err(JsValue::undefined()); } - let prop_name_str = coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[2])); - let si = vm.kernel_core().string_forge().intern(&prop_name_str).0; + let prop_name_str = coercion::to_string(vm.reg(args[2])); + let si = vm.kernel_core().perm_interner().intern(&prop_name_str).0; - let value_si = vm.kernel_core().string_forge().intern("value").0; - let get_si = vm.kernel_core().string_forge().intern("get").0; - let set_si = vm.kernel_core().string_forge().intern("set").0; - let writable_si = vm.kernel_core().string_forge().intern("writable").0; - let enumerable_si = vm.kernel_core().string_forge().intern("enumerable").0; - let configurable_si = vm.kernel_core().string_forge().intern("configurable").0; + let value_si = vm.kernel_core().perm_interner().intern("value").0; + let get_si = vm.kernel_core().perm_interner().intern("get").0; + let set_si = vm.kernel_core().perm_interner().intern("set").0; + let writable_si = vm.kernel_core().perm_interner().intern("writable").0; + let enumerable_si = vm.kernel_core().perm_interner().intern("enumerable").0; + let configurable_si = vm.kernel_core().perm_interner().intern("configurable").0; let desc = unsafe { &*desc_ptr }; let value_field = own_field(vm, desc, value_si); let get_field = own_field(vm, desc, get_si); let set_field = own_field(vm, desc, set_si); let writable_field = own_field(vm, desc, writable_si); - let enumerable = own_field(vm, desc, enumerable_si) - .map(|v| coercion::to_boolean(v, vm.kernel_core().string_forge().as_ref())) - .unwrap_or(false); - let configurable = own_field(vm, desc, configurable_si) - .map(|v| coercion::to_boolean(v, vm.kernel_core().string_forge().as_ref())) - .unwrap_or(false); + let enumerable = own_field(vm, desc, enumerable_si).map(coercion::to_boolean).unwrap_or(false); + let configurable = own_field(vm, desc, configurable_si).map(coercion::to_boolean).unwrap_or(false); let has_data = value_field.is_some() || writable_field.is_some(); let has_accessor = get_field.is_some() || set_field.is_some(); @@ -208,9 +204,7 @@ pub fn object_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult { } } else { let value = value_field.unwrap_or(JsValue::undefined()); - let writable = writable_field - .map(|v| coercion::to_boolean(v, vm.kernel_core().string_forge().as_ref())) - .unwrap_or(false); + let writable = writable_field.map(coercion::to_boolean).unwrap_or(false); if vm .define_data_property(obj, si, value, PropAttributes::new(writable, enumerable, configurable)) .is_err() @@ -233,8 +227,8 @@ pub fn object_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeRes if obj_ptr.is_null() { return NativeResult::Ok(JsValue::undefined()); } - let prop_name_str = coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[2])); - let si = vm.kernel_core().string_forge().intern(&prop_name_str).0; + let prop_name_str = coercion::to_string(vm.reg(args[2])); + let si = vm.kernel_core().perm_interner().intern(&prop_name_str).0; let (found_value, found_meta, found) = { let obj = unsafe { &*obj_ptr }; @@ -257,7 +251,7 @@ pub fn object_get_own_property_descriptor(vm: &mut Vm, args: &[u8]) -> NativeRes return NativeResult::Ok(JsValue::undefined()); } - let sf_ptr = vm.kernel_core().string_forge().as_ref() as *const StringForge; + let sf_ptr = vm.kernel_core().perm_interner().as_ref() as *const PermInterner; let sh_ptr = vm.kernel_core().shape_forge().as_ref() as *const ShapeForge; let desc_proto = vm.session().builtin_world().object_proto.as_ptr() as *mut JsObject; let desc = vm @@ -406,7 +400,7 @@ pub fn object_get_own_property_names(vm: &mut Vm, args: &[u8]) -> NativeResult { let obj = unsafe { &*obj_ptr }; let keys = walk_own_keys(vm, obj); keys.iter() - .map(|(si, _)| vm.kernel_core().string_forge().lookup(*si).unwrap_or_default()) + .map(|(si, _)| vm.kernel_core().perm_interner().lookup(*si).unwrap_or("").to_string()) .collect() }; @@ -419,7 +413,7 @@ pub fn object_get_own_property_names(vm: &mut Vm, args: &[u8]) -> NativeResult { vm.epoch().bump(), )); for (i, k) in key_names.iter().enumerate() { - let str_val = vm.intern(k); + let str_val = vm.new_string(k); unsafe { (*arr).set_prop_at(i, str_val); } @@ -524,8 +518,8 @@ pub fn object_entries(vm: &mut Vm, args: &[u8]) -> NativeResult { vm.epoch().bump(), )); for (i, (si, offset)) in keys.iter().enumerate() { - let key_str = vm.kernel_core().string_forge().lookup(*si).unwrap_or_default(); - let key_val = vm.intern(&key_str); + let key_str = vm.kernel_core().perm_interner().lookup(*si).unwrap_or_default(); + let key_val = vm.new_string(key_str); let val = obj.get_prop_at(*offset); let pair = vm.alloc_object(JsObject::new_array( EMPTY_SHAPE_ID, diff --git a/crates/oxide_vm/src/builtins/reflect.rs b/crates/oxide_vm/src/builtins/reflect.rs index 9d8ada1d..10b123b7 100644 --- a/crates/oxide_vm/src/builtins/reflect.rs +++ b/crates/oxide_vm/src/builtins/reflect.rs @@ -39,23 +39,19 @@ pub fn reflect_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult { let key_si = vm.property_key_si(arg(vm, args, 2)); let desc = unsafe { &*desc_ptr }; - let value_si = vm.kernel_core().string_forge().intern("value").0; - let get_si = vm.kernel_core().string_forge().intern("get").0; - let set_si = vm.kernel_core().string_forge().intern("set").0; - let writable_si = vm.kernel_core().string_forge().intern("writable").0; - let enumerable_si = vm.kernel_core().string_forge().intern("enumerable").0; - let configurable_si = vm.kernel_core().string_forge().intern("configurable").0; + let value_si = vm.kernel_core().perm_interner().intern("value").0; + let get_si = vm.kernel_core().perm_interner().intern("get").0; + let set_si = vm.kernel_core().perm_interner().intern("set").0; + let writable_si = vm.kernel_core().perm_interner().intern("writable").0; + let enumerable_si = vm.kernel_core().perm_interner().intern("enumerable").0; + let configurable_si = vm.kernel_core().perm_interner().intern("configurable").0; let value_field = own_field(vm, desc, value_si); let get_field = own_field(vm, desc, get_si); let set_field = own_field(vm, desc, set_si); let writable_field = own_field(vm, desc, writable_si); - let enumerable = own_field(vm, desc, enumerable_si) - .map(|v| coercion::to_boolean(v, vm.kernel_core().string_forge().as_ref())) - .unwrap_or(false); - let configurable = own_field(vm, desc, configurable_si) - .map(|v| coercion::to_boolean(v, vm.kernel_core().string_forge().as_ref())) - .unwrap_or(false); + let enumerable = own_field(vm, desc, enumerable_si).map(coercion::to_boolean).unwrap_or(false); + let configurable = own_field(vm, desc, configurable_si).map(coercion::to_boolean).unwrap_or(false); let has_data = value_field.is_some() || writable_field.is_some(); let has_accessor = get_field.is_some() || set_field.is_some(); @@ -73,9 +69,7 @@ pub fn reflect_define_property(vm: &mut Vm, args: &[u8]) -> NativeResult { vm.define_accessor_property(target, key_si, get, set, PropAttributes::new(false, enumerable, configurable)) } else { let value = value_field.unwrap_or(JsValue::undefined()); - let writable = writable_field - .map(|v| coercion::to_boolean(v, vm.kernel_core().string_forge().as_ref())) - .unwrap_or(false); + let writable = writable_field.map(coercion::to_boolean).unwrap_or(false); vm.define_data_property(target, key_si, value, PropAttributes::new(writable, enumerable, configurable)) }; NativeResult::Ok(JsValue::bool(result.is_ok())) @@ -145,7 +139,7 @@ pub fn reflect_own_keys(vm: &mut Vm, args: &[u8]) -> NativeResult { let target = unsafe { &*target_ptr }; let key_names: Vec = walk_own_keys(vm, target) .into_iter() - .map(|(si, _)| vm.kernel_core().string_forge().lookup(si).unwrap_or_default()) + .map(|(si, _)| vm.kernel_core().perm_interner().lookup(si).unwrap_or("").to_string()) .collect(); NativeResult::Ok(make_string_array(vm, &key_names)) } @@ -228,7 +222,7 @@ fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue { vm.epoch().bump(), )); for (idx, part) in parts.iter().enumerate() { - let value = vm.intern(part); + let value = vm.new_string(part); unsafe { &mut *arr }.set_prop_at(idx, value); } unsafe { &mut *arr }.set_prop_count(parts.len()); diff --git a/crates/oxide_vm/src/builtins/regexp.rs b/crates/oxide_vm/src/builtins/regexp.rs index 1c32d52c..f5293441 100644 --- a/crates/oxide_vm/src/builtins/regexp.rs +++ b/crates/oxide_vm/src/builtins/regexp.rs @@ -43,7 +43,7 @@ fn parse_flags(flags: &str) -> (bool, bool, bool) { } fn set_prop(obj: &mut JsObject, name: &str, val: JsValue, vm: &Vm) { - let si = vm.kernel_core().string_forge().intern(name).0; + let si = vm.kernel_core().perm_interner().intern(name).0; let shape_id = vm.kernel_core().shape_forge().make_shape(obj.shape_id(), si); obj.set_shape_id(shape_id); obj.ensure_hash_props().push(val); @@ -71,11 +71,11 @@ pub fn regexp_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { let (pattern, flags) = if args.len() < 2 { (String::new(), String::new()) } else if args.len() < 3 { - let pat = coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1])); + let pat = coercion::to_string(vm.reg(args[1])); (pat, String::new()) } else { - let pat = coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1])); - let fl = coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[2])); + let pat = coercion::to_string(vm.reg(args[1])); + let fl = coercion::to_string(vm.reg(args[2])); (pat, fl) }; @@ -109,8 +109,8 @@ pub fn regexp_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { } set_prop(&mut obj, "lastIndex", JsValue::int(0), vm); - set_prop(&mut obj, "source", vm.intern(&pattern), vm); - set_prop(&mut obj, "flags", vm.intern(&flags), vm); + set_prop(&mut obj, "source", vm.new_string(&pattern), vm); + set_prop(&mut obj, "flags", vm.new_string(&flags), vm); set_prop(&mut obj, "global", JsValue::bool(global), vm); set_prop(&mut obj, "ignoreCase", JsValue::bool(ignore_case), vm); set_prop(&mut obj, "multiline", JsValue::bool(multi_line), vm); @@ -155,11 +155,8 @@ pub fn regexp_test(vm: &mut Vm, args: &[u8]) -> NativeResult { // SAFETY: fn_ptr holds a Box pointer stored by regexp_constructor. let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) }; - let haystack = coercion::to_string( - vm.kernel_core().string_forge().as_ref(), - vm.reg(if args.len() > 1 { args[1] } else { args[0] }), - ); - let last_index = coercion::to_number(get_prop(re, 0), vm.kernel_core().string_forge().as_ref()) as usize; + let haystack = coercion::to_string(vm.reg(if args.len() > 1 { args[1] } else { args[0] })); + let last_index = coercion::to_number(get_prop(re, 0)) as usize; let is_global = get_prop(re, 3).as_bool(); if is_global { @@ -188,14 +185,11 @@ pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult { // SAFETY: fn_ptr holds a Box pointer stored by regexp_constructor. let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) }; - let haystack = coercion::to_string( - vm.kernel_core().string_forge().as_ref(), - vm.reg(if args.len() > 1 { args[1] } else { args[0] }), - ); + let haystack = coercion::to_string(vm.reg(if args.len() > 1 { args[1] } else { args[0] })); let (last_index, is_global) = { let re = unsafe { &*re_ptr }; - let li = coercion::to_number(get_prop(re, 0), vm.kernel_core().string_forge().as_ref()) as usize; + let li = coercion::to_number(get_prop(re, 0)) as usize; let g = get_prop(re, 3).as_bool(); (li, g) }; @@ -216,20 +210,20 @@ pub fn regexp_exec(vm: &mut Vm, args: &[u8]) -> NativeResult { ); let full_match = &haystack[m.start()..m.end()]; - let full_si = vm.kernel_core().string_forge().intern(full_match).0; - match_obj.ensure_hash_props().push(JsValue::string(full_si, 0)); + let full_val = vm.new_string(full_match); + match_obj.ensure_hash_props().push(full_val); let captures = regex.captures(&haystack[m.start()..m.end()]); if let Some(caps) = &captures { for i in 1..caps.len() { let cap_str = caps.get(i).map(|cm| cm.as_str()).unwrap_or(""); - let si = vm.kernel_core().string_forge().intern(cap_str).0; - match_obj.ensure_hash_props().push(JsValue::string(si, 0)); + let cap_val = vm.new_string(cap_str); + match_obj.ensure_hash_props().push(cap_val); } } set_prop(&mut match_obj, "index", JsValue::int(m.start() as i32), vm); - let haystack_val = vm.intern(&haystack); + let haystack_val = vm.new_string(&haystack); set_prop(&mut match_obj, "input", haystack_val, vm); set_prop(&mut match_obj, "groups", JsValue::undefined(), vm); @@ -262,6 +256,5 @@ pub fn regexp_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { vm.lookup_str(val).unwrap_or_default() }; let result = format!("/{}/{}", source, flags); - let si = vm.kernel_core().string_forge().intern(&result).0; - NativeResult::Ok(JsValue::string(si, 0)) + NativeResult::Ok(vm.new_string(&result)) } diff --git a/crates/oxide_vm/src/builtins/string.rs b/crates/oxide_vm/src/builtins/string.rs index 862b3840..dbc7fdfc 100644 --- a/crates/oxide_vm/src/builtins/string.rs +++ b/crates/oxide_vm/src/builtins/string.rs @@ -8,20 +8,20 @@ use crate::vm::Vm; use memchr::memchr; fn this_string(vm: &Vm, args: &[u8]) -> String { - coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[0])) + coercion::to_string(vm.reg(args[0])) } pub fn string_from_char_code(vm: &mut Vm, args: &[u8]) -> NativeResult { let mut out = String::new(); for &arg_reg in args.iter().skip(1) { - let code = coercion::to_uint32(vm.reg(arg_reg), vm.kernel_core().string_forge().as_ref()) & 0xFFFF; + let code = coercion::to_uint32(vm.reg(arg_reg)) & 0xFFFF; if let Some(ch) = char::from_u32(code) { out.push(ch); } else { out.push('\u{FFFD}'); } } - NativeResult::Ok(vm.intern(&out)) + NativeResult::Ok(vm.new_string(&out)) } pub fn string_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult { @@ -46,11 +46,11 @@ pub fn string_value_of(vm: &mut Vm, args: &[u8]) -> NativeResult { pub fn string_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = if args.len() > 1 { - coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1])) + coercion::to_string(vm.reg(args[1])) } else { String::new() }; - let str_val = vm.intern(&s); + let str_val = vm.new_string(&s); let string_proto = vm.session().builtin_world().string_proto.as_ptr() as *mut JsObject; let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); @@ -104,7 +104,7 @@ fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue { .alloc(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto), n, vm.epoch().bump())); unsafe { for (i, s) in parts.iter().enumerate() { - let sv = vm.intern(s); + let sv = vm.new_string(s); (*arr).set_prop_at(i, sv); } (*arr).set_prop_count(n); @@ -112,8 +112,8 @@ fn make_string_array(vm: &mut Vm, parts: &[String]) -> JsValue { JsValue::from_js_object(arr) } -fn as_string(vm: &Vm, val: JsValue) -> String { - coercion::to_string(vm.kernel_core().string_forge().as_ref(), val) +fn as_string(_vm: &Vm, val: JsValue) -> String { + coercion::to_string(val) } fn char_len(s: &str) -> usize { @@ -166,7 +166,7 @@ pub fn string_index_of(vm: &mut Vm, args: &[u8]) -> NativeResult { } let search = as_string(vm, vm.reg(args[1])); let pos = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as usize).min(n) + (coercion::to_number(vm.reg(args[2])) as usize).min(n) } else { 0 }; @@ -197,7 +197,7 @@ pub fn string_includes(vm: &mut Vm, args: &[u8]) -> NativeResult { } let search = as_string(vm, vm.reg(args[1])); let pos = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as usize).min(n) + (coercion::to_number(vm.reg(args[2])) as usize).min(n) } else { 0 }; @@ -218,16 +218,16 @@ pub fn string_char_at(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = this_string(vm, args); if args.len() < 2 { if s.is_empty() { - return NativeResult::Ok(vm.intern("")); + return NativeResult::Ok(vm.new_string("")); } - return NativeResult::Ok(vm.intern(&take_chars(&s, 1))); + return NativeResult::Ok(vm.new_string(&take_chars(&s, 1))); } - let idx = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32; + let idx = coercion::to_number(vm.reg(args[1])) as i32; if idx < 0 || idx as usize >= char_len(&s) { - return NativeResult::Ok(vm.intern("")); + return NativeResult::Ok(vm.new_string("")); } let ch = s.chars().nth(idx as usize).map(|c| c.to_string()).unwrap_or_default(); - NativeResult::Ok(vm.intern(&ch)) + NativeResult::Ok(vm.new_string(&ch)) } pub fn string_char_code_at(vm: &mut Vm, args: &[u8]) -> NativeResult { @@ -238,7 +238,7 @@ pub fn string_char_code_at(vm: &mut Vm, args: &[u8]) -> NativeResult { } return NativeResult::Ok(JsValue::int(s.chars().next().unwrap() as i32)); } - let idx = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32; + let idx = coercion::to_number(vm.reg(args[1])) as i32; if idx < 0 || idx as usize >= char_len(&s) { return NativeResult::Ok(JsValue::float(f64::NAN)); } @@ -247,18 +247,17 @@ pub fn string_char_code_at(vm: &mut Vm, args: &[u8]) -> NativeResult { pub fn string_concat(vm: &mut Vm, args: &[u8]) -> NativeResult { let mut result = this_string(vm, args); - let sf = vm.kernel_core().string_forge().as_ref(); for &arg_reg in args.iter().skip(1) { - result.push_str(&coercion::to_string(sf, vm.reg(arg_reg))); + result.push_str(&coercion::to_string(vm.reg(arg_reg))); } - NativeResult::Ok(vm.intern(&result)) + NativeResult::Ok(vm.new_string(&result)) } pub fn string_slice(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = this_string(vm, args); let n = char_len(&s) as i32; let start = if args.len() > 1 { - let v = coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32; + let v = coercion::to_number(vm.reg(args[1])) as i32; if v < 0 { (n + v).max(0) } else { @@ -268,7 +267,7 @@ pub fn string_slice(vm: &mut Vm, args: &[u8]) -> NativeResult { 0 }; let end = if args.len() > 2 { - let v = coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as i32; + let v = coercion::to_number(vm.reg(args[2])) as i32; if v < 0 { (n + v).max(0) } else { @@ -280,23 +279,19 @@ pub fn string_slice(vm: &mut Vm, args: &[u8]) -> NativeResult { let start = start as usize; let end = end as usize; let result = if start < end { char_slice(&s, start, end) } else { "" }; - NativeResult::Ok(vm.intern(result)) + NativeResult::Ok(vm.new_string(result)) } pub fn string_substring(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = this_string(vm, args); let n = char_len(&s) as i32; let mut start = if args.len() > 1 { - (coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as i32) - .max(0) - .min(n) + (coercion::to_number(vm.reg(args[1])) as i32).max(0).min(n) } else { 0 }; let mut end = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as i32) - .max(0) - .min(n) + (coercion::to_number(vm.reg(args[2])) as i32).max(0).min(n) } else { n }; @@ -304,39 +299,39 @@ pub fn string_substring(vm: &mut Vm, args: &[u8]) -> NativeResult { std::mem::swap(&mut start, &mut end); } let result = char_slice(&s, start as usize, end as usize); - NativeResult::Ok(vm.intern(result)) + NativeResult::Ok(vm.new_string(result)) } pub fn string_to_upper_case(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = this_string(vm, args); - NativeResult::Ok(vm.intern(&s.to_uppercase())) + NativeResult::Ok(vm.new_string(&s.to_uppercase())) } pub fn string_to_lower_case(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = this_string(vm, args); - NativeResult::Ok(vm.intern(&s.to_lowercase())) + NativeResult::Ok(vm.new_string(&s.to_lowercase())) } pub fn string_trim(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = this_string(vm, args); - NativeResult::Ok(vm.intern(s.trim())) + NativeResult::Ok(vm.new_string(s.trim())) } pub fn string_repeat(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = this_string(vm, args); let n = if args.len() > 1 { - (coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as usize).min(10000) + (coercion::to_number(vm.reg(args[1])) as usize).min(10000) } else { 1 }; - NativeResult::Ok(vm.intern(&s.repeat(n))) + NativeResult::Ok(vm.new_string(&s.repeat(n))) } pub fn string_pad_start(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = this_string(vm, args); let s_len = char_len(&s); let target = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as usize + coercion::to_number(vm.reg(args[1])) as usize } else { s_len }; @@ -345,21 +340,21 @@ pub fn string_pad_start(vm: &mut Vm, args: &[u8]) -> NativeResult { } let pad = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { " ".to_string() }; if s_len >= target || pad.is_empty() { - return NativeResult::Ok(vm.intern(&s)); + return NativeResult::Ok(vm.new_string(&s)); } let needed = target - s_len; let pad_len = char_len(&pad).max(1); let reps = needed.div_ceil(pad_len); let mut out = take_chars(&pad.repeat(reps), needed); out.push_str(&s); - NativeResult::Ok(vm.intern(&out)) + NativeResult::Ok(vm.new_string(&out)) } pub fn string_pad_end(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = this_string(vm, args); let s_len = char_len(&s); let target = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as usize + coercion::to_number(vm.reg(args[1])) as usize } else { s_len }; @@ -368,14 +363,14 @@ pub fn string_pad_end(vm: &mut Vm, args: &[u8]) -> NativeResult { } let pad = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { " ".to_string() }; if s_len >= target || pad.is_empty() { - return NativeResult::Ok(vm.intern(&s)); + return NativeResult::Ok(vm.new_string(&s)); } let needed = target - s_len; let pad_len = char_len(&pad).max(1); let reps = needed.div_ceil(pad_len); let mut out = s; out.push_str(&take_chars(&pad.repeat(reps), needed)); - NativeResult::Ok(vm.intern(&out)) + NativeResult::Ok(vm.new_string(&out)) } pub fn string_starts_with(vm: &mut Vm, args: &[u8]) -> NativeResult { @@ -386,7 +381,7 @@ pub fn string_starts_with(vm: &mut Vm, args: &[u8]) -> NativeResult { } let search = as_string(vm, vm.reg(args[1])); let pos = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as usize).min(n) + (coercion::to_number(vm.reg(args[2])) as usize).min(n) } else { 0 }; @@ -401,7 +396,7 @@ pub fn string_ends_with(vm: &mut Vm, args: &[u8]) -> NativeResult { } let search = as_string(vm, vm.reg(args[1])); let end_pos = if args.len() > 2 { - (coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as usize).min(n) + (coercion::to_number(vm.reg(args[2])) as usize).min(n) } else { n }; @@ -416,7 +411,7 @@ pub fn string_split(vm: &mut Vm, args: &[u8]) -> NativeResult { } let sep_val = vm.reg(args[1]); let limit = if args.len() > 2 { - coercion::to_number(vm.reg(args[2]), vm.kernel_core().string_forge().as_ref()) as usize + coercion::to_number(vm.reg(args[2])) as usize } else { usize::MAX }; @@ -450,7 +445,7 @@ pub fn string_split(vm: &mut Vm, args: &[u8]) -> NativeResult { pub fn string_replace(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = this_string(vm, args); if args.len() < 2 { - return NativeResult::Ok(vm.intern(&s)); + return NativeResult::Ok(vm.new_string(&s)); } let pattern_val = vm.reg(args[1]); let replacement = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { String::new() }; @@ -460,17 +455,17 @@ pub fn string_replace(vm: &mut Vm, args: &[u8]) -> NativeResult { let re = unsafe { &*re_ptr }; let fn_ptr = match re.native_fn() { Some(p) => p, - None => return NativeResult::Ok(vm.intern(&s)), + None => return NativeResult::Ok(vm.new_string(&s)), }; // SAFETY: fn_ptr holds a Box pointer stored by regexp_constructor. let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) }; let result = regex.replace_all(&s, replacement.as_str()).to_string(); - return NativeResult::Ok(vm.intern(&result)); + return NativeResult::Ok(vm.new_string(&result)); } let pattern = as_string(vm, pattern_val); let result = s.replacen(&pattern, &replacement, 1); - NativeResult::Ok(vm.intern(&result)) + NativeResult::Ok(vm.new_string(&result)) } pub fn string_match_fn(vm: &mut Vm, args: &[u8]) -> NativeResult { @@ -531,21 +526,17 @@ pub fn string_search(vm: &mut Vm, args: &[u8]) -> NativeResult { pub fn string_trim_start(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = this_string(vm, args); - NativeResult::Ok(vm.intern(s.trim_start())) + NativeResult::Ok(vm.new_string(s.trim_start())) } pub fn string_trim_end(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = this_string(vm, args); - NativeResult::Ok(vm.intern(s.trim_end())) + NativeResult::Ok(vm.new_string(s.trim_end())) } pub fn string_code_point_at(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = this_string(vm, args); - let pos = if args.len() > 1 { - coercion::to_number(vm.reg(args[1]), vm.kernel_core().string_forge().as_ref()) as usize - } else { - 0 - }; + let pos = if args.len() > 1 { coercion::to_number(vm.reg(args[1])) as usize } else { 0 }; let chars: Vec = s.chars().collect(); if pos >= chars.len() { return NativeResult::Ok(JsValue::undefined()); @@ -571,7 +562,7 @@ pub fn string_normalize(vm: &mut Vm, args: &[u8]) -> NativeResult { "NFKD" => s.nfkd().collect(), _ => s.nfc().collect(), }; - NativeResult::Ok(vm.intern(&result)) + NativeResult::Ok(vm.new_string(&result)) } pub fn string_match_all(vm: &mut Vm, args: &[u8]) -> NativeResult { @@ -598,7 +589,7 @@ pub fn string_match_all(vm: &mut Vm, args: &[u8]) -> NativeResult { pub fn string_replace_all(vm: &mut Vm, args: &[u8]) -> NativeResult { let s = this_string(vm, args); if args.len() < 2 { - return NativeResult::Ok(vm.intern(&s)); + return NativeResult::Ok(vm.new_string(&s)); } let pattern_val = vm.reg(args[1]); let replacement = if args.len() > 2 { as_string(vm, vm.reg(args[2])) } else { String::new() }; @@ -607,14 +598,14 @@ pub fn string_replace_all(vm: &mut Vm, args: &[u8]) -> NativeResult { let re = unsafe { &*re_ptr }; let fn_ptr = match re.native_fn() { Some(p) => p, - None => return NativeResult::Ok(vm.intern(&s)), + None => return NativeResult::Ok(vm.new_string(&s)), }; // SAFETY: fn_ptr holds a Box pointer stored by regexp_constructor. let regex = unsafe { &*(fn_ptr.as_ptr() as *const regex::Regex) }; let result = regex.replace_all(&s, replacement.as_str()).to_string(); - return NativeResult::Ok(vm.intern(&result)); + return NativeResult::Ok(vm.new_string(&result)); } let pattern = as_string(vm, pattern_val); let result = s.replace(&pattern, &replacement); - NativeResult::Ok(vm.intern(&result)) + NativeResult::Ok(vm.new_string(&result)) } diff --git a/crates/oxide_vm/src/builtins/symbol.rs b/crates/oxide_vm/src/builtins/symbol.rs index 501a3570..aedf0e67 100644 --- a/crates/oxide_vm/src/builtins/symbol.rs +++ b/crates/oxide_vm/src/builtins/symbol.rs @@ -28,7 +28,7 @@ pub fn symbol_constructor(vm: &mut Vm, args: &[u8]) -> NativeResult { } let description = if args.len() > 1 { - coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1])) + coercion::to_string(vm.reg(args[1])) } else { String::new() }; @@ -51,13 +51,12 @@ pub fn symbol_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { let idx = this_val.as_symbol_index(); let desc = vm.symbol_descriptions.get(idx as usize).cloned().unwrap_or_default(); let result = format!("Symbol({})", desc); - let si = vm.kernel_core().string_forge().intern(&result).0; - NativeResult::Ok(JsValue::string(si, 0)) + NativeResult::Ok(vm.new_string(&result)) } pub fn symbol_for(vm: &mut Vm, args: &[u8]) -> NativeResult { let key = if args.len() > 1 { - coercion::to_string(vm.kernel_core().string_forge().as_ref(), vm.reg(args[1])) + coercion::to_string(vm.reg(args[1])) } else { "undefined".to_string() }; @@ -79,12 +78,13 @@ pub fn symbol_key_for(vm: &mut Vm, args: &[u8]) -> NativeResult { } let idx = sym.as_symbol_index(); - for (key, ®istered_idx) in &vm.symbol_registry { - if registered_idx == idx { - let si = vm.kernel_core().string_forge().intern(key).0; - return NativeResult::Ok(JsValue::string(si, 0)); - } + let found = vm + .symbol_registry + .iter() + .find(|(_, ®istered_idx)| registered_idx == idx) + .map(|(key, _)| key.clone()); + match found { + Some(key) => NativeResult::Ok(vm.new_string(&key)), + None => NativeResult::Ok(JsValue::undefined()), } - - NativeResult::Ok(JsValue::undefined()) } diff --git a/crates/oxide_vm/src/builtins/typed_array.rs b/crates/oxide_vm/src/builtins/typed_array.rs index b1b9b0ea..04b6f159 100644 --- a/crates/oxide_vm/src/builtins/typed_array.rs +++ b/crates/oxide_vm/src/builtins/typed_array.rs @@ -33,7 +33,7 @@ fn range_error(vm: &mut Vm, msg: &str) -> JsValue { } fn to_index(vm: &mut Vm, value: JsValue, msg: &str) -> Result { - let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref()); + let n = coercion::to_number(value); if n.is_nan() { return Ok(0); } @@ -43,8 +43,8 @@ fn to_index(vm: &mut Vm, value: JsValue, msg: &str) -> Result { Ok(n.trunc() as usize) } -fn normalize_index(vm: &mut Vm, value: JsValue, len: usize) -> usize { - let n = coercion::to_number(value, vm.kernel_core().string_forge().as_ref()); +fn normalize_index(_vm: &mut Vm, value: JsValue, len: usize) -> usize { + let n = coercion::to_number(value); if n.is_nan() { return 0; } @@ -57,7 +57,7 @@ fn normalize_index(vm: &mut Vm, value: JsValue, len: usize) -> usize { } fn set_named_prop(vm: &mut Vm, obj: &mut JsObject, name: &str, value: JsValue, attributes: PropAttributes) { - let si = vm.kernel_core().string_forge().intern(name).0; + let si = vm.kernel_core().perm_interner().intern(name).0; let _ = vm.define_data_property(obj, si, value, attributes); } @@ -236,8 +236,8 @@ fn read_element(kind: TypedArrayKind, bytes: &[u8], offset: usize) -> JsValue { } } -fn numeric_value(vm: &mut Vm, value: JsValue) -> f64 { - coercion::to_number(value, vm.kernel_core().string_forge().as_ref()) +fn numeric_value(_vm: &mut Vm, value: JsValue) -> f64 { + coercion::to_number(value) } fn write_element(kind: TypedArrayKind, bytes: &mut [u8], offset: usize, value: f64) { @@ -477,5 +477,5 @@ pub fn typed_array_set(vm: &mut Vm, args: &[u8]) -> NativeResult { pub fn typed_array_to_string(vm: &mut Vm, args: &[u8]) -> NativeResult { let this_val = vm.reg(if args.is_empty() { 0 } else { args[0] }); native_try!(get_typed_array_data(vm, this_val)); - NativeResult::Ok(vm.intern("[object TypedArray]")) + NativeResult::Ok(vm.new_string("[object TypedArray]")) } diff --git a/crates/oxide_vm/src/coercion.rs b/crates/oxide_vm/src/coercion.rs index d4c27c85..5c267162 100644 --- a/crates/oxide_vm/src/coercion.rs +++ b/crates/oxide_vm/src/coercion.rs @@ -1,10 +1,32 @@ use crate::vm::Vm; -use oxide_kernel::string_forge::StringForge; use oxide_types::object::JsObject; use oxide_types::shape::EMPTY_SHAPE_ID; use oxide_types::value::JsValue; -pub fn to_number(val: JsValue, string_forge: &StringForge) -> f64 { +/// Borrow a string value's content. Caller must hold a valid string `JsValue`. +/// +/// # Safety +/// `val` must be a string `JsValue` whose `JsString` pointer is alive (session or +/// permanent string) — the same contract as object-pointer derefs in the VM. +#[inline] +unsafe fn string_data(val: JsValue) -> &'static str { + (*val.as_string_ptr()).as_str() +} + +/// Content equality of two string values: pointer identity → hash+len reject → +/// byte compare. Replaces the old interner-index compare. +#[inline] +fn string_value_eq(a: JsValue, b: JsValue) -> bool { + if a.as_string_ptr() == b.as_string_ptr() { + return true; + } + // SAFETY: both are string JsValues; their JsString pointers are valid. + let sa = unsafe { &*a.as_string_ptr() }; + let sb = unsafe { &*b.as_string_ptr() }; + sa.hash == sb.hash && sa.data == sb.data +} + +pub fn to_number(val: JsValue) -> f64 { if val.is_int() { return val.as_int() as f64; } @@ -21,7 +43,8 @@ pub fn to_number(val: JsValue, string_forge: &StringForge) -> f64 { return f64::NAN; } if val.is_string() { - let s = string_forge.lookup(val.as_string_index()).unwrap_or_default(); + // SAFETY: val is a string value. + let s = unsafe { string_data(val) }; return s.parse::().unwrap_or(f64::NAN); } if val.is_object() { @@ -30,16 +53,16 @@ pub fn to_number(val: JsValue, string_forge: &StringForge) -> f64 { f64::NAN } -pub fn to_uint32(val: JsValue, string_forge: &StringForge) -> u32 { - let n = to_number(val, string_forge); +pub fn to_uint32(val: JsValue) -> u32 { + let n = to_number(val); if n == 0.0 || !n.is_finite() { return 0; } n.trunc().rem_euclid(4_294_967_296.0) as u32 } -pub fn to_int32(val: JsValue, string_forge: &StringForge) -> i32 { - let int = to_uint32(val, string_forge); +pub fn to_int32(val: JsValue) -> i32 { + let int = to_uint32(val); if int > i32::MAX as u32 { (int as i64 - 4_294_967_296i64) as i32 } else { @@ -47,7 +70,7 @@ pub fn to_int32(val: JsValue, string_forge: &StringForge) -> i32 { } } -pub fn to_string(string_forge: &StringForge, val: JsValue) -> String { +pub fn to_string(val: JsValue) -> String { if val.is_int() { return val.as_int().to_string(); } @@ -79,7 +102,8 @@ pub fn to_string(string_forge: &StringForge, val: JsValue) -> String { return "undefined".to_string(); } if val.is_string() { - return string_forge.lookup(val.as_string_index()).unwrap_or_default(); + // SAFETY: val is a string value. + return unsafe { string_data(val) }.to_string(); } if val.is_object() { return "[object]".to_string(); @@ -87,7 +111,7 @@ pub fn to_string(string_forge: &StringForge, val: JsValue) -> String { String::new() } -pub fn to_boolean(val: JsValue, string_forge: &StringForge) -> bool { +pub fn to_boolean(val: JsValue) -> bool { if val.is_undefined() || val.is_null() { return false; } @@ -102,8 +126,8 @@ pub fn to_boolean(val: JsValue, string_forge: &StringForge) -> bool { return !(d == 0.0 || d == -0.0 || d.is_nan()); } if val.is_string() { - let s = string_forge.lookup(val.as_string_index()).unwrap_or_default(); - return !s.is_empty(); + // SAFETY: val is a string value. + return !unsafe { (*val.as_string_ptr()).is_empty() }; } if val.is_object() { return true; @@ -114,7 +138,7 @@ pub fn to_boolean(val: JsValue, string_forge: &StringForge) -> bool { false } -pub fn abstract_eq(lhs: JsValue, rhs: JsValue, string_forge: &StringForge) -> bool { +pub fn abstract_eq(lhs: JsValue, rhs: JsValue) -> bool { if lhs.is_null() && rhs.is_undefined() { return true; } @@ -122,7 +146,7 @@ pub fn abstract_eq(lhs: JsValue, rhs: JsValue, string_forge: &StringForge) -> bo return true; } if lhs.is_string() && rhs.is_string() { - return lhs == rhs; + return string_value_eq(lhs, rhs); } if lhs.is_int() && rhs.is_int() { return lhs.as_int() == rhs.as_int(); @@ -140,16 +164,16 @@ pub fn abstract_eq(lhs: JsValue, rhs: JsValue, string_forge: &StringForge) -> bo return true; } if (lhs.is_int() || lhs.is_double()) && (rhs.is_int() || rhs.is_double()) { - return strict_double_eq(to_number(lhs, string_forge), to_number(rhs, string_forge)); + return strict_double_eq(to_number(lhs), to_number(rhs)); } if (lhs.is_int() || lhs.is_double()) && rhs.is_bool() { - return to_number(lhs, string_forge) == to_number(rhs, string_forge); + return to_number(lhs) == to_number(rhs); } if lhs.is_bool() && (rhs.is_int() || rhs.is_double()) { - return to_number(lhs, string_forge) == to_number(rhs, string_forge); + return to_number(lhs) == to_number(rhs); } if lhs.is_bool() || rhs.is_bool() { - return to_number(lhs, string_forge) == to_number(rhs, string_forge); + return to_number(lhs) == to_number(rhs); } if lhs.is_null() || rhs.is_null() { return false; @@ -177,7 +201,7 @@ pub fn strict_eq(lhs: JsValue, rhs: JsValue) -> bool { return true; } if lhs.is_string() && rhs.is_string() { - return lhs == rhs; + return string_value_eq(lhs, rhs); } if lhs.is_object() && rhs.is_object() { return lhs.as_ptr() == rhs.as_ptr(); @@ -195,14 +219,15 @@ fn strict_double_eq(a: f64, b: f64) -> bool { a == b } -pub fn relational_compare(string_forge: &StringForge, lhs: JsValue, rhs: JsValue) -> Option { +pub fn relational_compare(lhs: JsValue, rhs: JsValue) -> Option { if lhs.is_string() && rhs.is_string() { - let ls = string_forge.lookup(lhs.as_string_index()).unwrap_or_default(); - let rs = string_forge.lookup(rhs.as_string_index()).unwrap_or_default(); + // SAFETY: both are string values. + let ls = unsafe { string_data(lhs) }; + let rs = unsafe { string_data(rhs) }; return Some(ls < rs); } - let l = to_number(lhs, string_forge); - let r = to_number(rhs, string_forge); + let l = to_number(lhs); + let r = to_number(rhs); if l.is_nan() || r.is_nan() { return None; } @@ -264,7 +289,7 @@ pub fn same_value(lhs: JsValue, rhs: JsValue) -> bool { return true; } if lhs.is_string() && rhs.is_string() { - return lhs == rhs; + return string_value_eq(lhs, rhs); } if lhs.is_object() && rhs.is_object() { return lhs.as_ptr() == rhs.as_ptr(); diff --git a/crates/oxide_vm/src/dispatch/arithmetic.rs b/crates/oxide_vm/src/dispatch/arithmetic.rs index 3f8ec75d..9ae4ed09 100644 --- a/crates/oxide_vm/src/dispatch/arithmetic.rs +++ b/crates/oxide_vm/src/dispatch/arithmetic.rs @@ -8,13 +8,13 @@ impl Vm { let lhs = self.coerce_primitive_bounded(self.regs[a], false)?; let rhs = self.coerce_primitive_bounded(self.regs[b], false)?; if lhs.is_string() || rhs.is_string() { - let ls = coercion::to_string(self.kernel_core.string_forge().as_ref(), lhs); - let rs = coercion::to_string(self.kernel_core.string_forge().as_ref(), rhs); + let ls = coercion::to_string(lhs); + let rs = coercion::to_string(rhs); let concat = format!("{ls}{rs}"); - self.regs[rd] = self.intern(&concat); + self.regs[rd] = self.new_string(&concat); } else { - let ln = coercion::to_number(lhs, self.kernel_core.string_forge().as_ref()); - let rn = coercion::to_number(rhs, self.kernel_core.string_forge().as_ref()); + let ln = coercion::to_number(lhs); + let rn = coercion::to_number(rhs); self.regs[rd] = JsValue::float(ln + rn); } Ok(()) diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index 0a6437b1..b2729c01 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -45,10 +45,8 @@ impl Vm { (err_val, self.thrown_error_kind(err_val)) } else { let msg = if err_val.is_string() { - self.kernel_core - .string_forge() - .lookup(err_val.as_string_index()) - .unwrap_or_else(|| format!("{err_val}")) + // SAFETY: err_val is a string value. + unsafe { (*err_val.as_string_ptr()).data.clone() } } else { format!("{err_val}") }; diff --git a/crates/oxide_vm/src/dispatch/comparison.rs b/crates/oxide_vm/src/dispatch/comparison.rs index c9587393..7180e096 100644 --- a/crates/oxide_vm/src/dispatch/comparison.rs +++ b/crates/oxide_vm/src/dispatch/comparison.rs @@ -5,37 +5,37 @@ use oxide_types::value::JsValue; impl Vm { #[inline(always)] pub(crate) fn dispatch_eq(&mut self, rd: usize, a: usize, b: usize) { - let eq = coercion::abstract_eq(self.regs[a], self.regs[b], self.kernel_core.string_forge().as_ref()); + let eq = coercion::abstract_eq(self.regs[a], self.regs[b]); self.regs[rd] = JsValue::bool(eq); } #[inline(always)] pub(crate) fn dispatch_neq(&mut self, rd: usize, a: usize, b: usize) { - let ne = !coercion::abstract_eq(self.regs[a], self.regs[b], self.kernel_core.string_forge().as_ref()); + let ne = !coercion::abstract_eq(self.regs[a], self.regs[b]); self.regs[rd] = JsValue::bool(ne); } #[inline(always)] pub(crate) fn dispatch_lt(&mut self, rd: usize, a: usize, b: usize) { - let rel = coercion::relational_compare(self.kernel_core.string_forge().as_ref(), self.regs[a], self.regs[b]); + let rel = coercion::relational_compare(self.regs[a], self.regs[b]); self.regs[rd] = JsValue::bool(rel.unwrap_or(false)); } #[inline(always)] pub(crate) fn dispatch_gt(&mut self, rd: usize, a: usize, b: usize) { - let rel = coercion::relational_compare(self.kernel_core.string_forge().as_ref(), self.regs[b], self.regs[a]); + let rel = coercion::relational_compare(self.regs[b], self.regs[a]); self.regs[rd] = JsValue::bool(rel.unwrap_or(false)); } #[inline(always)] pub(crate) fn dispatch_lte(&mut self, rd: usize, a: usize, b: usize) { - let rel = coercion::relational_compare(self.kernel_core.string_forge().as_ref(), self.regs[b], self.regs[a]); + let rel = coercion::relational_compare(self.regs[b], self.regs[a]); self.regs[rd] = JsValue::bool(!rel.unwrap_or(true)); } #[inline(always)] pub(crate) fn dispatch_gte(&mut self, rd: usize, a: usize, b: usize) { - let rel = coercion::relational_compare(self.kernel_core.string_forge().as_ref(), self.regs[a], self.regs[b]); + let rel = coercion::relational_compare(self.regs[a], self.regs[b]); self.regs[rd] = JsValue::bool(!rel.unwrap_or(true)); } diff --git a/crates/oxide_vm/src/dispatch/control_flow.rs b/crates/oxide_vm/src/dispatch/control_flow.rs index 69331faa..e42df1c6 100644 --- a/crates/oxide_vm/src/dispatch/control_flow.rs +++ b/crates/oxide_vm/src/dispatch/control_flow.rs @@ -6,7 +6,7 @@ impl Vm { #[inline(always)] pub(crate) fn dispatch_and(&mut self, rd: usize, a: usize, b: usize) { let val = self.regs[a]; - if coercion::to_boolean(val, self.kernel_core.string_forge().as_ref()) { + if coercion::to_boolean(val) { self.regs[rd] = self.regs[b]; } else { self.regs[rd] = val; @@ -16,7 +16,7 @@ impl Vm { #[inline(always)] pub(crate) fn dispatch_or(&mut self, rd: usize, a: usize, b: usize) { let val = self.regs[a]; - if coercion::to_boolean(val, self.kernel_core.string_forge().as_ref()) { + if coercion::to_boolean(val) { self.regs[rd] = val; } else { self.regs[rd] = self.regs[b]; @@ -31,7 +31,7 @@ impl Vm { #[inline(always)] pub(crate) fn dispatch_not(&mut self, rd: usize, a: usize) { - let b = !coercion::to_boolean(self.regs[a], self.kernel_core.string_forge().as_ref()); + let b = !coercion::to_boolean(self.regs[a]); self.regs[rd] = JsValue::bool(b); } } diff --git a/crates/oxide_vm/src/dispatch/member.rs b/crates/oxide_vm/src/dispatch/member.rs index 986add06..06931fbb 100644 --- a/crates/oxide_vm/src/dispatch/member.rs +++ b/crates/oxide_vm/src/dispatch/member.rs @@ -110,12 +110,12 @@ impl Vm { let prop_val = self.coerce_primitive_bounded(prop_raw, false)?; let rhs = self.coerce_primitive_bounded(self.regs[a], false)?; let new_val = if prop_val.is_string() || rhs.is_string() { - let ls = coercion::to_string(self.kernel_core.string_forge().as_ref(), prop_val); - let rs = coercion::to_string(self.kernel_core.string_forge().as_ref(), rhs); - self.intern(&format!("{ls}{rs}")) + let ls = coercion::to_string(prop_val); + let rs = coercion::to_string(rhs); + self.new_string(&format!("{ls}{rs}")) } else { - let ln = coercion::to_number(prop_val, self.kernel_core.string_forge().as_ref()); - let rn = coercion::to_number(rhs, self.kernel_core.string_forge().as_ref()); + let ln = coercion::to_number(prop_val); + let rn = coercion::to_number(rhs); JsValue::float(ln + rn) }; let obj = unsafe { &mut *obj_ptr }; diff --git a/crates/oxide_vm/src/dispatch/misc.rs b/crates/oxide_vm/src/dispatch/misc.rs index d8921393..d790ddac 100644 --- a/crates/oxide_vm/src/dispatch/misc.rs +++ b/crates/oxide_vm/src/dispatch/misc.rs @@ -37,6 +37,6 @@ impl Vm { } else { "undefined" }; - self.regs[rd] = self.intern(result); + self.regs[rd] = self.new_string(result); } } diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index b9fbf9cd..348ba942 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -61,15 +61,10 @@ impl Vm { fn primitive_property_get(&mut self, val: JsValue, prop_name_si: u32) -> Result, String> { if val.is_string() { - let length_si = self.kernel_core.string_forge().intern("length").0; + let length_si = self.kernel_core.perm_interner().intern("length").0; if prop_name_si == length_si { - let len = self - .kernel_core - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() - .encode_utf16() - .count(); + // SAFETY: val is a string value. + let len = unsafe { (*val.as_string_ptr()).data.encode_utf16().count() }; return Ok(Some(JsValue::int(len as i32))); } let proto_ptr = self.session.builtin_world().string_proto.as_ptr() as *mut JsObject; @@ -245,7 +240,7 @@ impl Vm { }; let prop_name_si = self.property_key_si(self.regs[b]); - if self.kernel_core.string_forge().lookup(prop_name_si).as_deref() == Some("__proto__") { + if self.kernel_core.perm_interner().lookup(prop_name_si) == Some("__proto__") { let proto_value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]); if self.is_object_prototype(obj_ptr) && !proto_value.is_null() { self.raise_type_error("Object.prototype.__proto__ is immutable")?; @@ -328,7 +323,7 @@ impl Vm { return Ok(()); }; let prop_name_si = self.property_key_si(self.regs[b]); - if self.kernel_core.string_forge().lookup(prop_name_si).as_deref() == Some("__proto__") { + if self.kernel_core.perm_interner().lookup(prop_name_si) == Some("__proto__") { let proto_value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[a]); if self.is_object_prototype(obj_ptr) && !proto_value.is_null() { self.raise_type_error("Object.prototype.__proto__ is immutable")?; @@ -366,7 +361,7 @@ impl Vm { return Ok(()); }; let prop_name_si = self.property_key_si(self.regs[a]); - if self.kernel_core.string_forge().lookup(prop_name_si).as_deref() == Some("__proto__") { + if self.kernel_core.perm_interner().lookup(prop_name_si) == Some("__proto__") { let proto_value = self.promote_if_needed_for_write_ptr(obj_ptr, self.regs[b]); if self.is_object_prototype(obj_ptr) && !proto_value.is_null() { self.raise_type_error("Object.prototype.__proto__ is immutable")?; diff --git a/crates/oxide_vm/src/session_gc.rs b/crates/oxide_vm/src/session_gc.rs index 12c6be19..160b1ad9 100644 --- a/crates/oxide_vm/src/session_gc.rs +++ b/crates/oxide_vm/src/session_gc.rs @@ -609,7 +609,7 @@ mod tests { (*obj).set_prop_at(0, JsValue::int(42)); } let old_ptr = vm.promote_object(obj); - let key = vm.kernel_core.string_forge().intern("gcRoot").0; + let key = vm.kernel_core.perm_interner().intern("gcRoot").0; let global_ptr = vm.session.global_object().as_ptr() as *mut JsObject; unsafe { let global = &mut *global_ptr; diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index aa79907b..6ee8f9f1 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -15,7 +15,7 @@ use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::error::{JsError, JsErrorKind}; use oxide_types::mem::{Epoch, P}; -use oxide_types::object::{JsObject, NativeFnPtr}; +use oxide_types::object::{JsObject, JsString, NativeFnPtr}; use oxide_types::value::JsValue; pub(crate) const MAX_PROTO_CHAIN_DEPTH: usize = 1024; @@ -151,7 +151,7 @@ pub struct Vm { pub for_in_iters: Vec<*mut ForInIter<'static>>, pub(crate) kernel_core: Arc, pub(crate) session: KernelSession, - pub(crate) interned_strings: Vec, + pub(crate) session_string_ptrs: Vec<*mut JsString>, pub epoch: Epoch, pub(crate) session_epoch: bumpalo::Bump, pub(crate) session_gc: SessionGc, @@ -207,7 +207,7 @@ impl Vm { let method_names = if prefer_string { ["toString", "valueOf"] } else { ["valueOf", "toString"] }; for method_name in method_names { - let method_si = self.kernel_core.string_forge().intern(method_name).0; + let method_si = self.kernel_core.perm_interner().intern(method_name).0; let method = { let obj = unsafe { &*obj_ptr }; self.ordinary_get(obj, method_si, value)? @@ -234,7 +234,7 @@ impl Vm { pub(crate) fn coerce_number_bounded(&mut self, value: JsValue) -> Result { let primitive = self.coerce_primitive_bounded(value, false)?; - Ok(coercion::to_number(primitive, self.kernel_core.string_forge().as_ref())) + Ok(coercion::to_number(primitive)) } pub(crate) fn coerce_int32_bounded(&mut self, value: JsValue) -> Result { @@ -471,14 +471,15 @@ impl Vm { if !val.is_string() { return None; } - self.kernel_core.string_forge().lookup(val.as_string_index()) + // SAFETY: val is a string value; its JsString pointer is alive for its lifetime. + Some(unsafe { (*val.as_string_ptr()).data.clone() }) } pub(crate) fn thrown_error_kind(&self, val: JsValue) -> &'static str { if !val.is_object() { return "Error"; } - let name_si = self.kernel_core.string_forge().intern("name").0; + let name_si = self.kernel_core.perm_interner().intern("name").0; let obj = unsafe { &*val.as_js_object_ptr() }; let Some(name_val) = self.resolve_property(obj, name_si) else { return "Error"; @@ -500,14 +501,16 @@ impl Vm { pub(crate) fn property_key_si(&mut self, val: JsValue) -> u32 { if val.is_string() { - return val.as_string_index(); + // SAFETY: val is a string value; bridge its content to a permanent key id. + let s = unsafe { &(*val.as_string_ptr()).data }; + return self.kernel_core.perm_interner().intern(s).0; } - let key = coercion::to_string(self.kernel_core.string_forge().as_ref(), val); - self.kernel_core.string_forge().intern(&key).0 + let key = coercion::to_string(val); + self.kernel_core.perm_interner().intern(&key).0 } pub(crate) fn array_index_from_property_key(&self, prop_name_si: u32) -> Option { - let key = self.kernel_core.string_forge().lookup(prop_name_si)?; + let key = self.kernel_core.perm_interner().lookup(prop_name_si)?; if key.is_empty() || (key.len() > 1 && key.starts_with('0')) { return None; } @@ -515,7 +518,7 @@ impl Vm { } pub(crate) fn resolve_property(&self, obj: &JsObject, prop_name_si: u32) -> Option { - let length_si = self.kernel_core.string_forge().intern("length").0; + let length_si = self.kernel_core.perm_interner().intern("length").0; if obj.is_array() && prop_name_si == length_si { return Some(JsValue::int(obj.prop_count() as i32)); } @@ -553,7 +556,7 @@ impl Vm { } pub(crate) fn get_own_property_slot(&self, obj: &JsObject, prop_name_si: u32) -> Option { - let length_si = self.kernel_core.string_forge().intern("length").0; + let length_si = self.kernel_core.perm_interner().intern("length").0; if obj.is_array() && prop_name_si == length_si { return None; } @@ -664,7 +667,7 @@ impl Vm { let function_name = self.sub_modules[sub_idx] .function_name .as_deref() - .map(|name| self.kernel_core.string_forge().intern(name).0) + .map(|name| self.kernel_core.perm_interner().intern(name).0) .unwrap_or(0); self.frames.push(CallFrame { @@ -684,7 +687,7 @@ impl Vm { self.bytecode = sub_bytecode; self.constants = converted_sub_constants; for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map.clone() { - let si = self.kernel_core.string_forge().intern(name.as_str()).0; + let si = self.kernel_core.perm_interner().intern(name.as_str()).0; let global = self.session.global_object(); if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) { self.regs[*reg as usize] = global.get_prop_at(pos); @@ -826,7 +829,7 @@ impl Vm { } OpCode::JMP_IF_FALSE => { - let cond = coercion::to_boolean(self.regs[rd], self.kernel_core.string_forge().as_ref()); + let cond = coercion::to_boolean(self.regs[rd]); if !cond { let offset = opcode::offset16(instr) as isize; self.pc = ((self.pc as isize) + offset - 1) as usize; @@ -834,7 +837,7 @@ impl Vm { } OpCode::JMP_IF_TRUE => { - let cond = coercion::to_boolean(self.regs[rd], self.kernel_core.string_forge().as_ref()); + let cond = coercion::to_boolean(self.regs[rd]); if cond { let offset = opcode::offset16(instr) as isize; self.pc = ((self.pc as isize) + offset - 1) as usize; @@ -987,7 +990,7 @@ impl Vm { function_name: self.sub_modules[sub_idx] .function_name .as_deref() - .map(|name| self.kernel_core.string_forge().intern(name).0) + .map(|name| self.kernel_core.perm_interner().intern(name).0) .unwrap_or(0), caller_reg_limit, saved_regs, @@ -1003,7 +1006,7 @@ impl Vm { self.bytecode = sub_bytecode; self.constants = converted_sub_constants; for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map { - let si = self.kernel_core.string_forge().intern(name.as_str()).0; + let si = self.kernel_core.perm_interner().intern(name.as_str()).0; let global = self.session.global_object(); if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) { self.regs[*reg as usize] = global.get_prop_at(pos); @@ -1107,13 +1110,13 @@ impl Vm { let lhs = self.coerce_primitive_bounded(self.regs[rd], false)?; let rhs = self.coerce_primitive_bounded(self.regs[a], false)?; if lhs.is_string() || rhs.is_string() { - let ls = coercion::to_string(self.kernel_core.string_forge().as_ref(), lhs); - let rs = coercion::to_string(self.kernel_core.string_forge().as_ref(), rhs); + let ls = coercion::to_string(lhs); + let rs = coercion::to_string(rhs); let concat = format!("{ls}{rs}"); - self.regs[rd] = self.intern(&concat); + self.regs[rd] = self.new_string(&concat); } else { - let ln = coercion::to_number(lhs, self.kernel_core.string_forge().as_ref()); - let rn = coercion::to_number(rhs, self.kernel_core.string_forge().as_ref()); + let ln = coercion::to_number(lhs); + let rn = coercion::to_number(rhs); self.regs[rd] = JsValue::float(ln + rn); } } @@ -1357,7 +1360,7 @@ mod tests { if !this_val.is_object() { return NativeResult::Ok(JsValue::undefined()); } - let marker_si = vm.kernel_core.string_forge().intern("marker").0; + let marker_si = vm.kernel_core.perm_interner().intern("marker").0; let obj = unsafe { &*this_val.as_js_object_ptr() }; NativeResult::Ok(vm.resolve_property(obj, marker_si).unwrap_or(JsValue::undefined())) } @@ -1368,7 +1371,7 @@ mod tests { if !this_val.is_object() { return NativeResult::Ok(JsValue::undefined()); } - let marker_si = vm.kernel_core.string_forge().intern("marker").0; + let marker_si = vm.kernel_core.perm_interner().intern("marker").0; let obj = unsafe { &mut *this_val.as_js_object_ptr() }; vm.set_or_create_prop_value(obj, marker_si, value); NativeResult::Ok(JsValue::undefined()) @@ -1399,7 +1402,7 @@ mod tests { } fn add_accessor(vm: &mut Vm, obj_val: JsValue, name: &str, get: JsValue, set: JsValue) { - let si = vm.kernel_core.string_forge().intern(name).0; + let si = vm.kernel_core.perm_interner().intern(name).0; let obj = unsafe { &mut *obj_val.as_js_object_ptr() }; let shape_id = vm.kernel_core.shape_forge().make_shape(obj.shape_id(), si); obj.set_shape_id(shape_id); @@ -1409,7 +1412,7 @@ mod tests { } fn set_data(vm: &mut Vm, obj_val: JsValue, name: &str, val: JsValue) { - let si = vm.kernel_core.string_forge().intern(name).0; + let si = vm.kernel_core.perm_interner().intern(name).0; let obj = unsafe { &mut *obj_val.as_js_object_ptr() }; vm.set_or_create_prop_value(obj, si, val); } @@ -1568,7 +1571,7 @@ mod tests { let getter = native_function(&mut vm, native_return_7); add_accessor(&mut vm, obj_val, "x", getter, JsValue::undefined()); - let x_si = vm.kernel_core.string_forge().intern("x").0; + let x_si = vm.kernel_core.perm_interner().intern("x").0; let obj = unsafe { &*obj_val.as_js_object_ptr() }; let value = vm.ordinary_get(obj, x_si, obj_val).expect("getter"); assert_eq!(value, JsValue::int(7)); @@ -1581,11 +1584,11 @@ mod tests { let setter = native_function(&mut vm, native_set_marker); add_accessor(&mut vm, obj_val, "x", JsValue::undefined(), setter); - let x_si = vm.kernel_core.string_forge().intern("x").0; + let x_si = vm.kernel_core.perm_interner().intern("x").0; let obj = unsafe { &mut *obj_val.as_js_object_ptr() }; vm.ordinary_set(obj, x_si, JsValue::int(9), obj_val).expect("setter"); - let marker_si = vm.kernel_core.string_forge().intern("marker").0; + let marker_si = vm.kernel_core.perm_interner().intern("marker").0; let obj = unsafe { &*obj_val.as_js_object_ptr() }; assert_eq!(vm.resolve_property(obj, marker_si), Some(JsValue::int(9))); } @@ -1602,7 +1605,7 @@ mod tests { (*child_val.as_js_object_ptr()).set_proto(proto_val).expect("proto"); } - let x_si = vm.kernel_core.string_forge().intern("x").0; + let x_si = vm.kernel_core.perm_interner().intern("x").0; let child = unsafe { &*child_val.as_js_object_ptr() }; let value = vm.ordinary_get(child, x_si, child_val).expect("getter"); assert_eq!(value, JsValue::int(42)); @@ -1619,11 +1622,11 @@ mod tests { (*child_val.as_js_object_ptr()).set_proto(proto_val).expect("proto"); } - let x_si = vm.kernel_core.string_forge().intern("x").0; + let x_si = vm.kernel_core.perm_interner().intern("x").0; let child = unsafe { &mut *child_val.as_js_object_ptr() }; vm.ordinary_set(child, x_si, JsValue::int(12), child_val).expect("setter"); - let marker_si = vm.kernel_core.string_forge().intern("marker").0; + let marker_si = vm.kernel_core.perm_interner().intern("marker").0; let child = unsafe { &*child_val.as_js_object_ptr() }; let proto = unsafe { &*proto_val.as_js_object_ptr() }; assert_eq!(vm.resolve_property(child, marker_si), Some(JsValue::int(12))); @@ -1643,11 +1646,11 @@ mod tests { (*child_val.as_js_object_ptr()).set_proto(proto_val).expect("proto"); } - let x_si = vm.kernel_core.string_forge().intern("x").0; + let x_si = vm.kernel_core.perm_interner().intern("x").0; let child = unsafe { &mut *child_val.as_js_object_ptr() }; vm.ordinary_set(child, x_si, JsValue::int(15), child_val).expect("setter"); - let marker_si = vm.kernel_core.string_forge().intern("marker").0; + let marker_si = vm.kernel_core.perm_interner().intern("marker").0; let child = unsafe { &*child_val.as_js_object_ptr() }; assert_eq!(vm.resolve_property(child, marker_si), Some(JsValue::int(15))); } @@ -1658,7 +1661,7 @@ mod tests { let obj_val = plain_object(&mut vm); set_data(&mut vm, obj_val, "x", JsValue::int(1)); - let x_si = vm.kernel_core.string_forge().intern("x").0; + let x_si = vm.kernel_core.perm_interner().intern("x").0; let obj = unsafe { &mut *obj_val.as_js_object_ptr() }; assert!(!obj.has_prop_meta()); assert_eq!(vm.ordinary_get(obj, x_si, obj_val).expect("get"), JsValue::int(1)); diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index 5c72005e..1e1c9c48 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -42,7 +42,7 @@ impl Vm { JsValue::from_js_object(proto_ptr), )); - let proto_si = self.kernel_core.string_forge().intern("prototype").0; + let proto_si = self.kernel_core.perm_interner().intern("prototype").0; if let Some(proto_val) = self.resolve_property(ctor_obj, proto_si) { if proto_val.is_object() { let new_obj_mut = unsafe { &mut *new_obj }; @@ -123,7 +123,7 @@ impl Vm { function_name: self.sub_modules[sub_idx] .function_name .as_deref() - .map(|name| self.kernel_core.string_forge().intern(name).0) + .map(|name| self.kernel_core.perm_interner().intern(name).0) .unwrap_or(0), caller_reg_limit, saved_regs, @@ -140,7 +140,7 @@ impl Vm { self.constants = converted_sub_constants; for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map { - let si = self.kernel_core.string_forge().intern(name.as_str()).0; + let si = self.kernel_core.perm_interner().intern(name.as_str()).0; let global = self.session.global_object(); if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) { self.regs[*reg as usize] = global.get_prop_at(pos); @@ -173,10 +173,8 @@ impl Vm { let reg = (seg & 0x7F) as u8; let val = self.regs[reg as usize]; let s = if val.is_string() { - self.kernel_core - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + // SAFETY: val is a string value. + unsafe { (*val.as_string_ptr()).data.clone() } } else { format!("{}", val) }; @@ -186,18 +184,14 @@ impl Vm { if const_idx < self.constants.len() { let val = self.constants[const_idx]; if val.is_string() { - let s = self - .kernel_core - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default(); + // SAFETY: val is a string value. + let s = unsafe { (*val.as_string_ptr()).data.clone() }; result.push_str(&s); } } } } - let si = self.kernel_core.string_forge().intern(&result).0; - self.regs[rd] = JsValue::string(si, 0); + self.regs[rd] = self.new_string(&result); } pub(crate) fn dispatch_instanceof(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { @@ -213,7 +207,7 @@ impl Vm { } let rhs_obj = unsafe { &*rhs_val.as_js_object_ptr() }; - let proto_si = self.kernel_core.string_forge().intern("prototype").0; + let proto_si = self.kernel_core.perm_interner().intern("prototype").0; let ctor_proto = self.resolve_property(rhs_obj, proto_si); let ctor_proto_ptr = match ctor_proto { @@ -297,8 +291,9 @@ impl Vm { .map(|meta| meta.attributes.enumerable()) .unwrap_or(PropAttributes::DEFAULT_DATA.enumerable()); if enumerable { - let hash = self.kernel_core.string_forge().get_hash(shape.property_name).unwrap_or(0); - keys_vec.push(JsValue::string(shape.property_name, hash)); + keys_vec.push(JsValue::perm_string( + self.kernel_core.perm_interner().string_ptr(shape.property_name), + )); } } cursor = shape.parent; @@ -368,7 +363,7 @@ impl Vm { } let iter_obj = unsafe { &*iterator.as_js_object_ptr() }; - let next_si = self.kernel_core.string_forge().intern("next").0; + let next_si = self.kernel_core.perm_interner().intern("next").0; let next_fn = self.ordinary_get(iter_obj, next_si, iterator)?; let result = self.call_function_sync(next_fn, iterator, &[])?; if !result.is_object() { @@ -376,9 +371,9 @@ impl Vm { } let result_obj = unsafe { &*result.as_js_object_ptr() }; - let done_si = self.kernel_core.string_forge().intern("done").0; + let done_si = self.kernel_core.perm_interner().intern("done").0; let done_val = self.ordinary_get(result_obj, done_si, result)?; - let done = crate::coercion::to_boolean(done_val, self.kernel_core.string_forge().as_ref()); + let done = crate::coercion::to_boolean(done_val); self.last_for_of_result = result; self.regs[rd] = JsValue::bool(!done); Ok(()) @@ -391,7 +386,7 @@ impl Vm { return Ok(()); } let result_obj = unsafe { &*result.as_js_object_ptr() }; - let value_si = self.kernel_core.string_forge().intern("value").0; + let value_si = self.kernel_core.perm_interner().intern("value").0; self.regs[rd] = self.ordinary_get(result_obj, value_si, result)?; Ok(()) } @@ -404,7 +399,7 @@ impl Vm { return Ok(()); } let iter_obj = unsafe { &*iterator.as_js_object_ptr() }; - let return_si = self.kernel_core.string_forge().intern("return").0; + let return_si = self.kernel_core.perm_interner().intern("return").0; let return_fn = self.ordinary_get(iter_obj, return_si, iterator)?; if return_fn.is_object() { let return_obj = unsafe { &*return_fn.as_js_object_ptr() }; @@ -427,7 +422,8 @@ impl Vm { .get(excluded_idx) .and_then(|v| { if v.is_string() { - self.kernel_core.string_forge().lookup(v.as_string_index()) + // SAFETY: v is a string constant value. + Some(unsafe { (*v.as_string_ptr()).data.clone() }) } else { None } @@ -450,8 +446,8 @@ impl Vm { break; }; if shape.property_name != u32::MAX && !is_private_name_key(shape.property_name) { - if let Some(name) = self.kernel_core.string_forge().lookup(shape.property_name) { - if !excluded.contains(name.as_str()) { + if let Some(name) = self.kernel_core.perm_interner().lookup(shape.property_name) { + if !excluded.contains(name) { if let Some(pos) = self .kernel_core .shape_forge() diff --git a/crates/oxide_vm/src/vm_pool.rs b/crates/oxide_vm/src/vm_pool.rs index daf04366..7675e686 100644 --- a/crates/oxide_vm/src/vm_pool.rs +++ b/crates/oxide_vm/src/vm_pool.rs @@ -22,7 +22,6 @@ pub struct VmGuard { vm: Option, pool: Arc, dirty: bool, - interned_strings: Vec, } impl VmPool { @@ -55,7 +54,6 @@ impl VmPool { vm: Some(vm), pool: Arc::clone(self), dirty: false, - interned_strings: Vec::new(), }; } @@ -72,7 +70,6 @@ impl VmPool { vm: Some(vm), pool: Arc::clone(self), dirty: false, - interned_strings: Vec::new(), }; } @@ -86,7 +83,6 @@ impl VmPool { vm: Some(vm), pool: Arc::clone(self), dirty: false, - interned_strings: Vec::new(), }; } } @@ -115,10 +111,6 @@ impl Drop for VmGuard { let new_vm = self.pool.replace_vm(); inner.available.push(new_vm); } else { - for &idx in &self.interned_strings { - self.pool.kernel_core.string_forge().decref(idx); - } - vm.full_reset(); inner.available.push(vm); } diff --git a/crates/oxide_vm/src/vm_props.rs b/crates/oxide_vm/src/vm_props.rs index 93e9caa5..567fc5d0 100644 --- a/crates/oxide_vm/src/vm_props.rs +++ b/crates/oxide_vm/src/vm_props.rs @@ -19,7 +19,7 @@ impl Vm { fn ordinary_get_inner( &mut self, obj: &JsObject, prop_name_si: u32, receiver: JsValue, target_reg: Option, ) -> Result { - let length_si = self.kernel_core.string_forge().intern("length").0; + let length_si = self.kernel_core.perm_interner().intern("length").0; let mut current = Some(obj); let mut depth = 0usize; while let Some(obj) = current { diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index e0d8a592..e8cf273f 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -64,7 +64,7 @@ impl Vm { self.regs[254] = if sub.is_arrow { callee_obj.captured_this() } else { receiver }; self.regs[255] = JsValue::undefined(); for (name, reg) in &sub.builtin_reg_map { - let si = self.kernel_core.string_forge().intern(name.as_str()).0; + let si = self.kernel_core.perm_interner().intern(name.as_str()).0; let global = self.session.global_object(); if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) { self.regs[*reg as usize] = global.get_prop_at(pos); @@ -144,7 +144,7 @@ impl Vm { self.active_reg_limit = self.root_reg_limit; for (name, reg) in &module.builtin_reg_map { - let si = self.kernel_core.string_forge().intern(name.as_str()).0; + let si = self.kernel_core.perm_interner().intern(name.as_str()).0; let global = self.session.global_object(); if let Some(pos) = self.kernel_core.shape_forge().lookup_position(global.shape_id(), si) { self.regs[*reg as usize] = global.get_prop_at(pos); diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index ad575273..c79efd31 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -10,7 +10,7 @@ use oxide_kernel::kernel::{KernelConfig, KernelCore, KernelSession}; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_types::error::JsError; use oxide_types::mem::{Epoch, P}; -use oxide_types::object::{JsObject, PropAttributes}; +use oxide_types::object::{JsObject, JsString, PropAttributes}; use oxide_types::value::JsValue; impl Vm { @@ -28,7 +28,7 @@ impl Vm { for_in_iters: Vec::new(), kernel_core: core, session, - interned_strings: Vec::new(), + session_string_ptrs: Vec::new(), epoch: Epoch::new(), session_epoch: bumpalo::Bump::new(), session_gc: crate::session_gc::SessionGc::new(), @@ -74,7 +74,7 @@ impl Vm { for_in_iters: Vec::new(), kernel_core: core, session, - interned_strings: Vec::new(), + session_string_ptrs: Vec::new(), epoch: Epoch::new(), session_epoch: bumpalo::Bump::new(), session_gc: crate::session_gc::SessionGc::new(), @@ -141,7 +141,7 @@ impl Vm { self.session_object_ptrs.clear(); self.session_bytes_allocated = 0; self.session_gc = crate::session_gc::SessionGc::new(); - self.interned_strings.clear(); + self.free_session_string_heap_data(); self.symbol_counter = 0; self.symbol_descriptions.clear(); self.symbol_registry.clear(); @@ -189,15 +189,44 @@ impl Vm { self.free_epoch_object_heap_data(); self.epoch.reset(); self.epoch_object_ptrs.clear(); - self.interned_strings.clear(); self.root_reg_limit = 0; self.active_reg_limit = 0; } - pub fn intern(&mut self, s: &str) -> JsValue { - let (idx, hash) = self.kernel_core.string_forge().intern(s); - self.interned_strings.push(idx); - JsValue::string(idx, hash) + pub fn new_string(&mut self, s: &str) -> JsValue { + let ptr = Box::into_raw(Box::new(JsString::new(s.to_string()))); + self.session_string_ptrs.push(ptr); + self.session_bytes_allocated += std::mem::size_of::() + s.len(); + JsValue::string(ptr) + } + + pub fn intern_key(&self, s: &str) -> u32 { + self.kernel_core.perm_interner().intern(s).0 + } + + /// Intern a compile-time string literal as a permanent, process-lifetime + /// `JsString` value shared across sessions. Source literals and RegExp + /// source/flags recur (templated/repetitive code) and are immutable, so + /// sharing them via `PermInterner` restores cross-session string reuse — + /// without interning transient computed values, which stay session-heap + /// (`new_string`) so they remain collectable. + pub fn perm_string(&self, s: &str) -> JsValue { + let id = self.kernel_core.perm_interner().intern(s).0; + JsValue::perm_string(self.kernel_core.perm_interner().string_ptr(id)) + } + + /// Drop all session-heap `JsString` values. Called only on full isolation reset + /// (`full_reset` / `clear_full_reset_state`), where no surviving session object + /// can reference them. The lighter `reset()` deliberately keeps them alive, + /// mirroring session-object survival across evals. + fn free_session_string_heap_data(&mut self) { + for ptr in self.session_string_ptrs.drain(..) { + // SAFETY: each ptr came from Box::into_raw(Box::new(JsString)) in new_string + // and is dropped exactly once here. + unsafe { + drop(Box::from_raw(ptr)); + } + } } /// Create a function JsObject for a BytecodeFunc constant. @@ -230,7 +259,7 @@ impl Vm { self.epoch_object_ptrs.push(prototype_obj); let prototype_val = JsValue::from_js_object(prototype_obj); - let constructor_si = self.kernel_core.string_forge().intern("constructor").0; + let constructor_si = self.kernel_core.perm_interner().intern("constructor").0; let constructor_shape = self.kernel_core.shape_forge().make_shape(EMPTY_SHAPE_ID, constructor_si); let prototype = unsafe { &mut *prototype_obj }; prototype.set_shape_id(constructor_shape); @@ -238,7 +267,7 @@ impl Vm { prototype.set_data_meta(constructor_pos, PropAttributes::new(true, false, true)); prototype.bump_generation(); - let prototype_si = self.kernel_core.string_forge().intern("prototype").0; + let prototype_si = self.kernel_core.perm_interner().intern("prototype").0; let func = unsafe { &mut *obj_ptr }; let prototype_shape = self.kernel_core.shape_forge().make_shape(func.shape_id(), prototype_si); func.set_shape_id(prototype_shape); @@ -255,8 +284,8 @@ impl Vm { } if val.is_object() { let obj = unsafe { &*val.as_js_object_ptr() }; - let name_si = self.kernel_core.string_forge().intern("name").0; - let message_si = self.kernel_core.string_forge().intern("message").0; + let name_si = self.kernel_core.perm_interner().intern("name").0; + let message_si = self.kernel_core.perm_interner().intern("message").0; let name = self .resolve_property(obj, name_si) .and_then(|v| self.lookup_str(v)) @@ -274,7 +303,7 @@ impl Vm { match constant { Constant::Number(v) => Ok(JsValue::float(*v)), Constant::Int(v) => Ok(JsValue::int(*v)), - Constant::String(s) => Ok(self.intern(s)), + Constant::String(s) => Ok(self.perm_string(s)), Constant::Boolean(b) => Ok(JsValue::bool(*b)), Constant::Null => Ok(JsValue::null()), Constant::Undefined => Ok(JsValue::undefined()), @@ -301,10 +330,8 @@ impl Vm { )) } Constant::RegExp(pattern, flags) => { - let pat_si = self.kernel_core.string_forge().intern(pattern).0; - let flags_si = self.kernel_core.string_forge().intern(flags).0; - let pat_val = JsValue::string(pat_si, 0); - let flags_val = JsValue::string(flags_si, 0); + let pat_val = self.perm_string(pattern); + let flags_val = self.perm_string(flags); let ctor_ptr = self.session.builtin_world().regexp_constructor.as_ptr() as *mut JsObject; let ctor = unsafe { &*ctor_ptr }; @@ -347,7 +374,7 @@ mod tests { fn global_prop_opt(vm: &Vm, name: &str) -> Option { let global = vm.session.global_object(); - let si = vm.kernel_core.string_forge().intern(name).0; + let si = vm.kernel_core.perm_interner().intern(name).0; vm.kernel_core .shape_forge() .lookup_position(global.shape_id(), si) @@ -418,7 +445,7 @@ mod tests { global_prop(&vm, "Array").as_js_object_ptr(), vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject )); - let constructor_si = vm.kernel_core.string_forge().intern("constructor").0; + let constructor_si = vm.kernel_core.perm_interner().intern("constructor").0; let array_proto = &*vm.session.builtin_world().array_proto; let constructor = vm .resolve_property(array_proto, constructor_si) diff --git a/crates/oxide_vm/tests/arrow_tests.rs b/crates/oxide_vm/tests/arrow_tests.rs index f7a0c834..c68d9673 100644 --- a/crates/oxide_vm/tests/arrow_tests.rs +++ b/crates/oxide_vm/tests/arrow_tests.rs @@ -37,10 +37,7 @@ fn eval_val(source: &str) -> (Vm, Result) { fn to_str(vm: &Vm, val: JsValue) -> String { if val.is_string() { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } else { format!("{}", val) } diff --git a/crates/oxide_vm/tests/builtins_array_buffer_tests.rs b/crates/oxide_vm/tests/builtins_array_buffer_tests.rs index 70360a4d..97464e2b 100644 --- a/crates/oxide_vm/tests/builtins_array_buffer_tests.rs +++ b/crates/oxide_vm/tests/builtins_array_buffer_tests.rs @@ -10,10 +10,7 @@ fn eval(vm: &mut Vm, source: &str) -> Result { } fn to_str(vm: &Vm, val: JsValue) -> String { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } #[test] diff --git a/crates/oxide_vm/tests/builtins_array_tests.rs b/crates/oxide_vm/tests/builtins_array_tests.rs index df36ee6f..78822112 100644 --- a/crates/oxide_vm/tests/builtins_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_array_tests.rs @@ -13,10 +13,7 @@ fn eval(source: &str) -> Result<(Vm, JsValue), String> { } fn to_str(vm: &Vm, val: JsValue) -> String { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } fn assert_num_eq(val: JsValue, expected: f64) { diff --git a/crates/oxide_vm/tests/builtins_boolean_tests.rs b/crates/oxide_vm/tests/builtins_boolean_tests.rs index c5b6e8b2..b11684f9 100644 --- a/crates/oxide_vm/tests/builtins_boolean_tests.rs +++ b/crates/oxide_vm/tests/builtins_boolean_tests.rs @@ -14,10 +14,7 @@ fn eval(vm: &mut Vm, source: &str) -> Result { } fn str_val(vm: &Vm, val: JsValue) -> String { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } // -- direct native fn tests -- @@ -47,7 +44,7 @@ fn tboolean_call_true_false() { #[test] fn tboolean_call_empty_string() { let mut vm = Vm::new(); - let empty = vm.intern(""); + let empty = vm.new_string(""); assert!(!bool_ctor_call(&mut vm, empty).as_bool()); } diff --git a/crates/oxide_vm/tests/builtins_data_view_tests.rs b/crates/oxide_vm/tests/builtins_data_view_tests.rs index 0dc90999..ebe923f6 100644 --- a/crates/oxide_vm/tests/builtins_data_view_tests.rs +++ b/crates/oxide_vm/tests/builtins_data_view_tests.rs @@ -10,10 +10,7 @@ fn eval(vm: &mut Vm, source: &str) -> Result { } fn to_str(vm: &Vm, val: JsValue) -> String { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } #[test] diff --git a/crates/oxide_vm/tests/builtins_date_tests.rs b/crates/oxide_vm/tests/builtins_date_tests.rs index 55f3d741..6be7816b 100644 --- a/crates/oxide_vm/tests/builtins_date_tests.rs +++ b/crates/oxide_vm/tests/builtins_date_tests.rs @@ -9,10 +9,7 @@ fn eval(vm: &mut Vm, source: &str) -> Result String { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } // -- static methods -- diff --git a/crates/oxide_vm/tests/builtins_error_tests.rs b/crates/oxide_vm/tests/builtins_error_tests.rs index 3ddab65f..47a4eb12 100644 --- a/crates/oxide_vm/tests/builtins_error_tests.rs +++ b/crates/oxide_vm/tests/builtins_error_tests.rs @@ -25,7 +25,7 @@ fn error_constructor_creates_object() { #[test] fn error_has_message() { let mut vm = make_vm(); - let msg = vm.intern("test message"); + let msg = vm.new_string("test message"); vm.set_reg(1, msg); let result = error::error_constructor(&mut vm, &[0u8, 1u8]).unwrap(); assert!(result.is_object()); diff --git a/crates/oxide_vm/tests/builtins_function_tests.rs b/crates/oxide_vm/tests/builtins_function_tests.rs index 0dfcb381..ea1bb17f 100644 --- a/crates/oxide_vm/tests/builtins_function_tests.rs +++ b/crates/oxide_vm/tests/builtins_function_tests.rs @@ -34,11 +34,7 @@ fn function_bind_creates_wrapper() { fn function_constructor_is_global() { let mut vm = Vm::new(); let result = eval(&mut vm, "typeof Function").unwrap(); - let rendered = vm - .kernel_core() - .string_forge() - .lookup(result.as_string_index()) - .unwrap_or_default(); + let rendered = vm.lookup_str(result).unwrap_or_default(); assert_eq!(rendered, "function"); } @@ -86,11 +82,7 @@ fn function_to_string_includes_function() { let mut vm = Vm::new(); let result = eval(&mut vm, "Math.max.toString()").unwrap(); assert!(result.is_string()); - let rendered = vm - .kernel_core() - .string_forge() - .lookup(result.as_string_index()) - .unwrap_or_default(); + let rendered = vm.lookup_str(result).unwrap_or_default(); assert_eq!(rendered, "function max() { [native code] }"); } diff --git a/crates/oxide_vm/tests/builtins_globals_tests.rs b/crates/oxide_vm/tests/builtins_globals_tests.rs index 4f3aadc3..5271d624 100644 --- a/crates/oxide_vm/tests/builtins_globals_tests.rs +++ b/crates/oxide_vm/tests/builtins_globals_tests.rs @@ -21,10 +21,7 @@ fn eval_with_vm(source: &str) -> (Vm, Result) { } fn str_val(vm: &Vm, val: JsValue) -> String { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } #[test] diff --git a/crates/oxide_vm/tests/builtins_iterator_tests.rs b/crates/oxide_vm/tests/builtins_iterator_tests.rs index 597d5d1a..9563c21a 100644 --- a/crates/oxide_vm/tests/builtins_iterator_tests.rs +++ b/crates/oxide_vm/tests/builtins_iterator_tests.rs @@ -10,10 +10,7 @@ fn eval(vm: &mut Vm, source: &str) -> Result { } fn to_str(vm: &Vm, val: JsValue) -> String { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } #[test] diff --git a/crates/oxide_vm/tests/builtins_map_tests.rs b/crates/oxide_vm/tests/builtins_map_tests.rs index 972a2e0d..ad3ee11e 100644 --- a/crates/oxide_vm/tests/builtins_map_tests.rs +++ b/crates/oxide_vm/tests/builtins_map_tests.rs @@ -11,10 +11,7 @@ fn eval(vm: &mut Vm, source: &str) -> Result { } fn str_val(vm: &Vm, val: JsValue) -> String { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } // -- direct native fn tests -- diff --git a/crates/oxide_vm/tests/builtins_number_tests.rs b/crates/oxide_vm/tests/builtins_number_tests.rs index 824bec26..846c5aac 100644 --- a/crates/oxide_vm/tests/builtins_number_tests.rs +++ b/crates/oxide_vm/tests/builtins_number_tests.rs @@ -76,11 +76,7 @@ fn number_constructor() { fn to_string_of_number() { let mut vm = Vm::new(); let result = eval(&mut vm, "var n = 42; n.toString()").unwrap(); - let s = vm - .kernel_core() - .string_forge() - .lookup(result.as_string_index()) - .unwrap_or_default(); + let s = vm.lookup_str(result).unwrap_or_default(); assert_eq!(s, "42"); } @@ -88,11 +84,7 @@ fn to_string_of_number() { fn to_fixed_of_number() { let mut vm = Vm::new(); let result = eval(&mut vm, "var n = 3.14159; n.toFixed(2)").unwrap(); - let s = vm - .kernel_core() - .string_forge() - .lookup(result.as_string_index()) - .unwrap_or_default(); + let s = vm.lookup_str(result).unwrap_or_default(); assert_eq!(s, "3.14"); } @@ -121,11 +113,7 @@ fn number_epsilon_is_positive() { fn number_to_exponential() { let mut vm = Vm::new(); let result = eval(&mut vm, "var n = 123.456; n.toExponential(2)").unwrap(); - let s = vm - .kernel_core() - .string_forge() - .lookup(result.as_string_index()) - .unwrap_or_default(); + let s = vm.lookup_str(result).unwrap_or_default(); assert_eq!(s, "1.23e2"); } @@ -155,7 +143,7 @@ fn boxed_number_valueof_preserves_int_and_float() { fn boxed_number_is_object_and_call_stays_primitive() { let mut vm = Vm::new(); let ty = eval(&mut vm, "typeof new Number(5)").unwrap(); - assert_eq!(vm.kernel_core().string_forge().lookup(ty.as_string_index()).unwrap(), "object"); + assert_eq!(vm.lookup_str(ty).unwrap(), "object"); let n = eval(&mut vm, "Number('7')").unwrap(); assert_eq!(n.as_int(), 7); diff --git a/crates/oxide_vm/tests/builtins_object_tests.rs b/crates/oxide_vm/tests/builtins_object_tests.rs index a38c1041..4d39a7cd 100644 --- a/crates/oxide_vm/tests/builtins_object_tests.rs +++ b/crates/oxide_vm/tests/builtins_object_tests.rs @@ -63,11 +63,7 @@ fn object_assign_copies() { fn object_assign_boxes_primitive_target() { let (_vm, result) = eval("var result = Object.assign('a'); result.valueOf()").unwrap(); assert!(result.is_string()); - let rendered = _vm - .kernel_core() - .string_forge() - .lookup(result.as_string_index()) - .unwrap_or_default(); + let rendered = _vm.lookup_str(result).unwrap_or_default(); assert_eq!(rendered, "a"); } diff --git a/crates/oxide_vm/tests/builtins_reflect_tests.rs b/crates/oxide_vm/tests/builtins_reflect_tests.rs index 3f1fceaa..70b1544d 100644 --- a/crates/oxide_vm/tests/builtins_reflect_tests.rs +++ b/crates/oxide_vm/tests/builtins_reflect_tests.rs @@ -10,10 +10,7 @@ fn eval(vm: &mut Vm, source: &str) -> Result { } fn to_str(vm: &Vm, val: JsValue) -> String { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } #[test] diff --git a/crates/oxide_vm/tests/builtins_regexp_tests.rs b/crates/oxide_vm/tests/builtins_regexp_tests.rs index df67bd62..bd13b857 100644 --- a/crates/oxide_vm/tests/builtins_regexp_tests.rs +++ b/crates/oxide_vm/tests/builtins_regexp_tests.rs @@ -10,10 +10,7 @@ fn eval(vm: &mut Vm, source: &str) -> Result { } fn to_str(vm: &Vm, val: JsValue) -> String { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } // --- RegExp constructor --- diff --git a/crates/oxide_vm/tests/builtins_string_tests.rs b/crates/oxide_vm/tests/builtins_string_tests.rs index bd1be7cd..feb1e90b 100644 --- a/crates/oxide_vm/tests/builtins_string_tests.rs +++ b/crates/oxide_vm/tests/builtins_string_tests.rs @@ -10,10 +10,7 @@ fn eval(vm: &mut Vm, source: &str) -> Result { } fn to_str(vm: &Vm, val: JsValue) -> String { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } fn assert_num_eq(val: JsValue, expected: f64) { diff --git a/crates/oxide_vm/tests/builtins_symbol_tests.rs b/crates/oxide_vm/tests/builtins_symbol_tests.rs index 1e7c7c47..3f70562d 100644 --- a/crates/oxide_vm/tests/builtins_symbol_tests.rs +++ b/crates/oxide_vm/tests/builtins_symbol_tests.rs @@ -10,10 +10,7 @@ fn eval(vm: &mut Vm, source: &str) -> Result { } fn to_str(vm: &Vm, val: JsValue) -> String { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } // --- Symbol constructor --- diff --git a/crates/oxide_vm/tests/builtins_typed_array_tests.rs b/crates/oxide_vm/tests/builtins_typed_array_tests.rs index 36a81d7f..4c6ab571 100644 --- a/crates/oxide_vm/tests/builtins_typed_array_tests.rs +++ b/crates/oxide_vm/tests/builtins_typed_array_tests.rs @@ -10,10 +10,7 @@ fn eval(vm: &mut Vm, source: &str) -> Result { } fn to_str(vm: &Vm, val: JsValue) -> String { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } #[test] diff --git a/crates/oxide_vm/tests/coercion_tests.rs b/crates/oxide_vm/tests/coercion_tests.rs index 757d3a03..28a24bbf 100644 --- a/crates/oxide_vm/tests/coercion_tests.rs +++ b/crates/oxide_vm/tests/coercion_tests.rs @@ -85,25 +85,18 @@ fn test_strict_equality_null_undefined() { #[test] fn test_to_int32_and_to_uint32() { - let vm = Vm::new(); - let sf = vm.kernel_core().string_forge().as_ref(); - let string_three = { - let (idx, hash) = vm.kernel_core().string_forge().intern("3"); - JsValue::string(idx, hash) - }; - let string_bad = { - let (idx, hash) = vm.kernel_core().string_forge().intern("x"); - JsValue::string(idx, hash) - }; + let mut vm = Vm::new(); + let string_three = vm.new_string("3"); + let string_bad = vm.new_string("x"); - assert_eq!(coercion::to_int32(string_three, sf), 3); - assert_eq!(coercion::to_int32(string_bad, sf), 0); - assert_eq!(coercion::to_int32(JsValue::undefined(), sf), 0); - assert_eq!(coercion::to_int32(JsValue::float(1.9), sf), 1); - assert_eq!(coercion::to_int32(JsValue::float(-1.9), sf), -1); - assert_eq!(coercion::to_int32(JsValue::float(f64::NAN), sf), 0); - assert_eq!(coercion::to_int32(JsValue::float(f64::INFINITY), sf), 0); - assert_eq!(coercion::to_uint32(JsValue::int(-1), sf), 4_294_967_295); + assert_eq!(coercion::to_int32(string_three), 3); + assert_eq!(coercion::to_int32(string_bad), 0); + assert_eq!(coercion::to_int32(JsValue::undefined()), 0); + assert_eq!(coercion::to_int32(JsValue::float(1.9)), 1); + assert_eq!(coercion::to_int32(JsValue::float(-1.9)), -1); + assert_eq!(coercion::to_int32(JsValue::float(f64::NAN)), 0); + assert_eq!(coercion::to_int32(JsValue::float(f64::INFINITY)), 0); + assert_eq!(coercion::to_uint32(JsValue::int(-1)), 4_294_967_295); } #[test] diff --git a/crates/oxide_vm/tests/function_tests.rs b/crates/oxide_vm/tests/function_tests.rs index 96d8cb18..5731120c 100644 --- a/crates/oxide_vm/tests/function_tests.rs +++ b/crates/oxide_vm/tests/function_tests.rs @@ -204,10 +204,6 @@ fn function_default_prototype_accepts_member_assignment() { "function Test262Error(message) { this.message = message || ''; } Test262Error.prototype.toString = function () { return this.message; }; new Test262Error('ok').toString()", ) .unwrap(); - let rendered = vm - .kernel_core() - .string_forge() - .lookup(result.as_string_index()) - .unwrap_or_default(); + let rendered = vm.lookup_str(result).unwrap_or_default(); assert_eq!(rendered, "ok"); } diff --git a/crates/oxide_vm/tests/json_tests.rs b/crates/oxide_vm/tests/json_tests.rs index 6a2a7ae6..ddbf6444 100644 --- a/crates/oxide_vm/tests/json_tests.rs +++ b/crates/oxide_vm/tests/json_tests.rs @@ -10,10 +10,7 @@ fn eval(vm: &mut Vm, source: &str) -> Result { } fn string_value(vm: &Vm, val: JsValue) -> String { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } // -- JSON.parse -- diff --git a/crates/oxide_vm/tests/session_arena_tests.rs b/crates/oxide_vm/tests/session_arena_tests.rs index d4b77fd8..0bd440ef 100644 --- a/crates/oxide_vm/tests/session_arena_tests.rs +++ b/crates/oxide_vm/tests/session_arena_tests.rs @@ -11,7 +11,7 @@ fn run_source(vm: &mut Vm, source: &str) -> JsValue { } fn global_prop(vm: &Vm, name: &str) -> JsValue { - let si = vm.kernel_core().string_forge().intern(name).0; + let si = vm.kernel_core().perm_interner().intern(name).0; let global = vm.session().global_object(); let pos = vm .kernel_core() diff --git a/crates/oxide_vm/tests/template_tests.rs b/crates/oxide_vm/tests/template_tests.rs index 6f2d888b..28474be3 100644 --- a/crates/oxide_vm/tests/template_tests.rs +++ b/crates/oxide_vm/tests/template_tests.rs @@ -37,10 +37,7 @@ fn eval_val(source: &str) -> (Vm, Result) { fn to_str(vm: &Vm, val: JsValue) -> String { if val.is_string() { - vm.kernel_core() - .string_forge() - .lookup(val.as_string_index()) - .unwrap_or_default() + vm.lookup_str(val).unwrap_or_default() } else { format!("{}", val) }