diff --git a/crates/oxide_vm/src/lib.rs b/crates/oxide_vm/src/lib.rs index 6fd5578c..a4aa640c 100644 --- a/crates/oxide_vm/src/lib.rs +++ b/crates/oxide_vm/src/lib.rs @@ -7,4 +7,5 @@ mod dispatch; pub mod native; pub mod vm; pub mod vm_pool; +mod vm_support; pub use oxide_types::value::JsValue; diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 1ec5a531..043ffafc 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -2,16 +2,14 @@ use std::sync::Arc; -use oxide_compiler::compiler::Constant; use oxide_compiler::module::CompiledModule; use oxide_compiler::opcode::{self, OpCode}; use smallvec::SmallVec; -use crate::bindings; pub use crate::bindings::init_kernel_builtins; use crate::coercion; use crate::native::{NativeFn, NativeResult}; -use oxide_kernel::kernel::{KernelConfig, OxideKernel}; +use oxide_kernel::kernel::OxideKernel; use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_kernel::{vm_debug, vm_trace}; use oxide_types::error::{JsError, JsErrorKind}; @@ -202,72 +200,6 @@ impl Vm { self.raise_error_kind("TypeError", msg) } - pub fn new() -> Self { - let kernel = Arc::new(OxideKernel::new(KernelConfig::minimal())); - bindings::init_kernel_builtins(&kernel); - let obj_proto = P::clone(&kernel.builtin_world().object_proto); - Self { - regs: [JsValue::undefined(); 256], - pc: 0, - bytecode: Vec::new(), - constants: Vec::new(), - frames: SmallVec::new(), - for_in_iters: Vec::new(), - kernel, - interned_strings: Vec::new(), - epoch: Epoch::new(), - object_prototype: obj_proto, - math_rng_state: 0, - sub_modules: Vec::new(), - sub_module_constants: Vec::new(), - saved_bytecode_stack: Vec::new(), - saved_constants_stack: Vec::new(), - try_stack: Vec::new(), - exception_value: None, - pending_exception: None, - pending_error_kind: None, - symbol_counter: 0, - symbol_descriptions: Vec::new(), - for_of_iters: Vec::new(), - root_reg_limit: 0, - active_reg_limit: 0, - native_call_depth: 0, - accessor_frame_target_reg: None, - } - } - - pub fn with_kernel(kernel: Arc) -> Self { - let obj_proto = P::clone(&kernel.builtin_world().object_proto); - Self { - regs: [JsValue::undefined(); 256], - pc: 0, - bytecode: Vec::new(), - constants: Vec::new(), - frames: SmallVec::new(), - for_in_iters: Vec::new(), - kernel, - interned_strings: Vec::new(), - epoch: Epoch::new(), - object_prototype: obj_proto, - math_rng_state: 0, - sub_modules: Vec::new(), - sub_module_constants: Vec::new(), - saved_bytecode_stack: Vec::new(), - saved_constants_stack: Vec::new(), - try_stack: Vec::new(), - exception_value: None, - pending_exception: None, - pending_error_kind: None, - symbol_counter: 0, - symbol_descriptions: Vec::new(), - for_of_iters: Vec::new(), - root_reg_limit: 0, - active_reg_limit: 0, - native_call_depth: 0, - accessor_frame_target_reg: None, - } - } - pub fn step_rng(&mut self) { if self.math_rng_state == 0 { self.math_rng_state = std::time::SystemTime::now() @@ -314,181 +246,6 @@ impl Vm { &self.epoch } - fn clear_execution_state(&mut self) { - // Reset contract: - // - Clears register file, pc, frame/iterator stacks, saved execution stacks, - // try handlers, pending exceptions, and native call depth. - // - Leaves kernel-owned shared state intact. - // - `reset()` additionally clears bytecode/constants and resets epoch ownership. - self.regs = [JsValue::undefined(); 256]; - self.pc = 0; - self.frames.clear(); - self.for_in_iters.clear(); - self.for_of_iters.clear(); - self.saved_bytecode_stack.clear(); - self.saved_constants_stack.clear(); - self.try_stack.clear(); - self.exception_value = None; - self.pending_exception = None; - self.pending_error_kind = None; - self.native_call_depth = 0; - } - - pub fn reset(&mut self) { - self.clear_execution_state(); - self.bytecode.clear(); - self.constants.clear(); - self.epoch.reset(); - 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.string_forge().intern(s); - self.interned_strings.push(idx); - JsValue::string(idx, hash) - } - - /// Create a function JsObject for a BytecodeFunc constant. - /// When `is_arrow` is true, captures the current `this` (regs[254]) - /// for lexical this binding at call time (D-01). - fn create_function_object( - &mut self, sub_idx: u32, is_arrow: bool, is_class_constructor: bool, is_derived_constructor: bool, - needs_home_object: bool, - ) -> JsValue { - let func_proto_ptr = self.kernel.builtin_world().function_proto.as_ptr() as *mut JsObject; - let proto_val = JsValue::from_js_object(func_proto_ptr); - let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto_val); - obj.set_function(true); - obj.set_sub_module_index(sub_idx); - obj.set_class_constructor(is_class_constructor); - obj.set_derived_constructor(is_derived_constructor); - let _ = needs_home_object; - if is_arrow { - obj.set_arrow(true); - // Capture lexical `this` from the enclosing scope (regs[254]). - obj.set_captured_this(self.regs[254]); - } - let obj_ptr = self.epoch.alloc(obj); - let func_val = JsValue::object(obj_ptr as *mut u8); - - if !is_arrow { - let object_proto_ptr = self.kernel.builtin_world().object_proto.as_ptr() as *mut JsObject; - let prototype_obj = self - .epoch - .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto_ptr))); - let prototype_val = JsValue::from_js_object(prototype_obj); - - let constructor_si = self.kernel.string_forge().intern("constructor").0; - let constructor_shape = self.kernel.shape_forge().make_shape(EMPTY_SHAPE_ID, constructor_si); - let prototype = unsafe { &mut *prototype_obj }; - prototype.set_shape_id(constructor_shape); - let constructor_pos = prototype.push_prop(func_val); - prototype.set_data_meta(constructor_pos, PropAttributes::new(true, false, true)); - prototype.bump_generation(); - - let prototype_si = self.kernel.string_forge().intern("prototype").0; - let func = unsafe { &mut *obj_ptr }; - let prototype_shape = self.kernel.shape_forge().make_shape(func.shape_id(), prototype_si); - func.set_shape_id(prototype_shape); - func.ensure_hash_props().push(prototype_val); - func.bump_generation(); - } - - func_val - } - - fn error_text(&self, val: JsValue) -> String { - if let Some(s) = self.lookup_str(val) { - return s; - } - if val.is_object() { - let obj = unsafe { &*val.as_js_object_ptr() }; - let name_si = self.kernel.string_forge().intern("name").0; - let message_si = self.kernel.string_forge().intern("message").0; - let name = self - .resolve_property(obj, name_si) - .and_then(|v| self.lookup_str(v)) - .unwrap_or_else(|| "Error".to_string()); - let message = self - .resolve_property(obj, message_si) - .and_then(|v| self.lookup_str(v)) - .unwrap_or_default(); - return if message.is_empty() { name } else { format!("{name}: {message}") }; - } - format!("{val}") - } - - fn convert_constant(&mut self, constant: &Constant) -> Result { - match constant { - Constant::Number(v) => Ok(JsValue::float(*v)), - Constant::Int(v) => Ok(JsValue::int(*v)), - Constant::String(s) => Ok(self.intern(s)), - Constant::Boolean(b) => Ok(JsValue::bool(*b)), - Constant::Null => Ok(JsValue::null()), - Constant::Undefined => Ok(JsValue::undefined()), - Constant::BytecodeFunc(idx) => { - let sub_idx = *idx as usize; - let (is_arrow, is_class_constructor, is_derived_constructor, needs_home_object) = - if sub_idx > 0 && sub_idx <= self.sub_modules.len() { - let sub_module = &self.sub_modules[sub_idx - 1]; - ( - sub_module.is_arrow, - sub_module.is_class_constructor, - sub_module.is_derived_constructor, - sub_module.needs_home_object, - ) - } else { - (false, false, false, false) - }; - Ok(self.create_function_object( - *idx, - is_arrow, - is_class_constructor, - is_derived_constructor, - needs_home_object, - )) - } - Constant::RegExp(pattern, flags) => { - let pat_si = self.kernel.string_forge().intern(pattern).0; - let flags_si = self.kernel.string_forge().intern(flags).0; - let pat_val = JsValue::string(pat_si, 0); - let flags_val = JsValue::string(flags_si, 0); - - let ctor_ptr = self.kernel.builtin_world().regexp_constructor.as_ptr() as *mut JsObject; - let ctor = unsafe { &*ctor_ptr }; - let Some(native_fn) = ctor.native_fn() else { - return Err(JsError::syntax_error("RegExp constructor unavailable")); - }; - - let saved_0 = self.regs[0]; - let saved_1 = self.regs[1]; - let saved_2 = self.regs[2]; - self.regs[0] = JsValue::undefined(); - self.regs[1] = pat_val; - self.regs[2] = flags_val; - // SAFETY: regexp constructor was registered via set_native_fn with a valid NativeFn pointer; - // native_fn_ptr_to_fn is the single coercion point for NativeFnPtr → NativeFn. - let func: crate::native::NativeFn = unsafe { native_fn_ptr_to_fn(native_fn) }; - let result = func(self, &[0, 1, 2]); - self.regs[0] = saved_0; - self.regs[1] = saved_1; - self.regs[2] = saved_2; - result.map_err(|err| JsError::syntax_error(self.error_text(err))) - } - } - } - - /// Convert a module constant pool into runtime values. - fn convert_constants(&mut self, constants: &[Constant]) -> Result, JsError> { - let mut values = Vec::with_capacity(constants.len()); - for constant in constants { - values.push(self.convert_constant(constant)?); - } - Ok(values) - } - pub fn lookup_str(&self, val: JsValue) -> Option { if !val.is_string() { return None; diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs new file mode 100644 index 00000000..dc977829 --- /dev/null +++ b/crates/oxide_vm/src/vm_support.rs @@ -0,0 +1,253 @@ +#![allow(clippy::arc_with_non_send_sync)] + +use std::sync::Arc; + +use oxide_compiler::compiler::Constant; + +use crate::bindings; +use crate::vm::{native_fn_ptr_to_fn, Vm}; +use oxide_kernel::kernel::{KernelConfig, OxideKernel}; +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::value::JsValue; + +impl Vm { + pub fn new() -> Self { + let kernel = Arc::new(OxideKernel::new(KernelConfig::minimal())); + bindings::init_kernel_builtins(&kernel); + let obj_proto = P::clone(&kernel.builtin_world().object_proto); + Self { + regs: [JsValue::undefined(); 256], + pc: 0, + bytecode: Vec::new(), + constants: Vec::new(), + frames: smallvec::SmallVec::new(), + for_in_iters: Vec::new(), + kernel, + interned_strings: Vec::new(), + epoch: Epoch::new(), + object_prototype: obj_proto, + math_rng_state: 0, + sub_modules: Vec::new(), + sub_module_constants: Vec::new(), + saved_bytecode_stack: Vec::new(), + saved_constants_stack: Vec::new(), + try_stack: Vec::new(), + exception_value: None, + pending_exception: None, + pending_error_kind: None, + symbol_counter: 0, + symbol_descriptions: Vec::new(), + for_of_iters: Vec::new(), + root_reg_limit: 0, + active_reg_limit: 0, + native_call_depth: 0, + accessor_frame_target_reg: None, + } + } + + pub fn with_kernel(kernel: Arc) -> Self { + let obj_proto = P::clone(&kernel.builtin_world().object_proto); + Self { + regs: [JsValue::undefined(); 256], + pc: 0, + bytecode: Vec::new(), + constants: Vec::new(), + frames: smallvec::SmallVec::new(), + for_in_iters: Vec::new(), + kernel, + interned_strings: Vec::new(), + epoch: Epoch::new(), + object_prototype: obj_proto, + math_rng_state: 0, + sub_modules: Vec::new(), + sub_module_constants: Vec::new(), + saved_bytecode_stack: Vec::new(), + saved_constants_stack: Vec::new(), + try_stack: Vec::new(), + exception_value: None, + pending_exception: None, + pending_error_kind: None, + symbol_counter: 0, + symbol_descriptions: Vec::new(), + for_of_iters: Vec::new(), + root_reg_limit: 0, + active_reg_limit: 0, + native_call_depth: 0, + accessor_frame_target_reg: None, + } + } + + pub(crate) fn clear_execution_state(&mut self) { + // Reset contract: + // - Clears register file, pc, frame/iterator stacks, saved execution stacks, + // try handlers, pending exceptions, and native call depth. + // - Leaves kernel-owned shared state intact. + // - `reset()` additionally clears bytecode/constants and resets epoch ownership. + self.regs = [JsValue::undefined(); 256]; + self.pc = 0; + self.frames.clear(); + self.for_in_iters.clear(); + self.for_of_iters.clear(); + self.saved_bytecode_stack.clear(); + self.saved_constants_stack.clear(); + self.try_stack.clear(); + self.exception_value = None; + self.pending_exception = None; + self.pending_error_kind = None; + self.native_call_depth = 0; + } + + pub fn reset(&mut self) { + self.clear_execution_state(); + self.bytecode.clear(); + self.constants.clear(); + self.epoch.reset(); + 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.string_forge().intern(s); + self.interned_strings.push(idx); + JsValue::string(idx, hash) + } + + /// Create a function JsObject for a BytecodeFunc constant. + /// When `is_arrow` is true, captures the current `this` (regs[254]) + /// for lexical this binding at call time (D-01). + fn create_function_object( + &mut self, sub_idx: u32, is_arrow: bool, is_class_constructor: bool, is_derived_constructor: bool, + needs_home_object: bool, + ) -> JsValue { + let func_proto_ptr = self.kernel.builtin_world().function_proto.as_ptr() as *mut JsObject; + let proto_val = JsValue::from_js_object(func_proto_ptr); + let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto_val); + obj.set_function(true); + obj.set_sub_module_index(sub_idx); + obj.set_class_constructor(is_class_constructor); + obj.set_derived_constructor(is_derived_constructor); + let _ = needs_home_object; + if is_arrow { + obj.set_arrow(true); + obj.set_captured_this(self.regs[254]); + } + let obj_ptr = self.epoch.alloc(obj); + let func_val = JsValue::object(obj_ptr as *mut u8); + + if !is_arrow { + let object_proto_ptr = self.kernel.builtin_world().object_proto.as_ptr() as *mut JsObject; + let prototype_obj = self + .epoch + .alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(object_proto_ptr))); + let prototype_val = JsValue::from_js_object(prototype_obj); + + let constructor_si = self.kernel.string_forge().intern("constructor").0; + let constructor_shape = self.kernel.shape_forge().make_shape(EMPTY_SHAPE_ID, constructor_si); + let prototype = unsafe { &mut *prototype_obj }; + prototype.set_shape_id(constructor_shape); + let constructor_pos = prototype.push_prop(func_val); + prototype.set_data_meta(constructor_pos, PropAttributes::new(true, false, true)); + prototype.bump_generation(); + + let prototype_si = self.kernel.string_forge().intern("prototype").0; + let func = unsafe { &mut *obj_ptr }; + let prototype_shape = self.kernel.shape_forge().make_shape(func.shape_id(), prototype_si); + func.set_shape_id(prototype_shape); + func.ensure_hash_props().push(prototype_val); + func.bump_generation(); + } + + func_val + } + + pub(crate) fn error_text(&self, val: JsValue) -> String { + if let Some(s) = self.lookup_str(val) { + return s; + } + if val.is_object() { + let obj = unsafe { &*val.as_js_object_ptr() }; + let name_si = self.kernel.string_forge().intern("name").0; + let message_si = self.kernel.string_forge().intern("message").0; + let name = self + .resolve_property(obj, name_si) + .and_then(|v| self.lookup_str(v)) + .unwrap_or_else(|| "Error".to_string()); + let message = self + .resolve_property(obj, message_si) + .and_then(|v| self.lookup_str(v)) + .unwrap_or_default(); + return if message.is_empty() { name } else { format!("{name}: {message}") }; + } + format!("{val}") + } + + fn convert_constant(&mut self, constant: &Constant) -> Result { + match constant { + Constant::Number(v) => Ok(JsValue::float(*v)), + Constant::Int(v) => Ok(JsValue::int(*v)), + Constant::String(s) => Ok(self.intern(s)), + Constant::Boolean(b) => Ok(JsValue::bool(*b)), + Constant::Null => Ok(JsValue::null()), + Constant::Undefined => Ok(JsValue::undefined()), + Constant::BytecodeFunc(idx) => { + let sub_idx = *idx as usize; + let (is_arrow, is_class_constructor, is_derived_constructor, needs_home_object) = + if sub_idx > 0 && sub_idx <= self.sub_modules.len() { + let sub_module = &self.sub_modules[sub_idx - 1]; + ( + sub_module.is_arrow, + sub_module.is_class_constructor, + sub_module.is_derived_constructor, + sub_module.needs_home_object, + ) + } else { + (false, false, false, false) + }; + Ok(self.create_function_object( + *idx, + is_arrow, + is_class_constructor, + is_derived_constructor, + needs_home_object, + )) + } + Constant::RegExp(pattern, flags) => { + let pat_si = self.kernel.string_forge().intern(pattern).0; + let flags_si = self.kernel.string_forge().intern(flags).0; + let pat_val = JsValue::string(pat_si, 0); + let flags_val = JsValue::string(flags_si, 0); + + let ctor_ptr = self.kernel.builtin_world().regexp_constructor.as_ptr() as *mut JsObject; + let ctor = unsafe { &*ctor_ptr }; + let Some(native_fn) = ctor.native_fn() else { + return Err(JsError::syntax_error("RegExp constructor unavailable")); + }; + + let saved_0 = self.regs[0]; + let saved_1 = self.regs[1]; + let saved_2 = self.regs[2]; + self.regs[0] = JsValue::undefined(); + self.regs[1] = pat_val; + self.regs[2] = flags_val; + let func = unsafe { native_fn_ptr_to_fn(native_fn) }; + let result = func(self, &[0, 1, 2]); + self.regs[0] = saved_0; + self.regs[1] = saved_1; + self.regs[2] = saved_2; + result.map_err(|err| JsError::syntax_error(self.error_text(err))) + } + } + } + + pub(crate) fn convert_constants(&mut self, constants: &[Constant]) -> Result, JsError> { + let mut values = Vec::with_capacity(constants.len()); + for constant in constants { + values.push(self.convert_constant(constant)?); + } + Ok(values) + } +}