diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index ff169a7a..bb957a94 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -167,6 +167,10 @@ impl CompileCtx { self.switch_stack.last() } + pub(crate) fn is_builtin(&self, name: &str) -> bool { + self.builtin_reg_map.iter().any(|(n, _)| n == name) + } + pub(crate) fn pre_register_builtins(&mut self) { let builtins = [ "NaN", @@ -235,6 +239,7 @@ impl Compiler { constants: ctx.constants, n_registers: ctx.max_regs, builtin_reg_map: ctx.builtin_reg_map, + sub_modules: Vec::new(), }) } } diff --git a/crates/oxide_compiler/src/emitter.rs b/crates/oxide_compiler/src/emitter.rs index 5723c7ef..80cfe359 100644 --- a/crates/oxide_compiler/src/emitter.rs +++ b/crates/oxide_compiler/src/emitter.rs @@ -791,12 +791,13 @@ impl Compiler { } else { arg_regs[0] }; - ctx.emit(opcode::encode( - OpCode::CALL, - callee_reg, - this_reg, - first_arg_reg, - )); + let op = match &call.callee { + Expression::Identifier(ident) if ctx.is_builtin(ident.name.as_str()) => { + OpCode::CALL_NATIVE + } + _ => OpCode::CALL, + }; + ctx.emit(opcode::encode(op, callee_reg, this_reg, first_arg_reg)); ctx.emit(arg_regs.len() as u32); Ok(0u8) } diff --git a/crates/oxide_compiler/src/module.rs b/crates/oxide_compiler/src/module.rs index da21c599..c6d80b24 100644 --- a/crates/oxide_compiler/src/module.rs +++ b/crates/oxide_compiler/src/module.rs @@ -10,6 +10,7 @@ pub enum Constant { Boolean(bool), Null, Undefined, + BytecodeFunc(u32), } pub struct CompiledModule { @@ -17,6 +18,19 @@ pub struct CompiledModule { pub constants: Vec, pub n_registers: u8, pub builtin_reg_map: Vec<(String, u8)>, + pub sub_modules: Vec, +} + +impl Clone for CompiledModule { + fn clone(&self) -> Self { + Self { + bytecode: self.bytecode.clone(), + constants: self.constants.clone(), + n_registers: self.n_registers, + builtin_reg_map: self.builtin_reg_map.clone(), + sub_modules: self.sub_modules.clone(), + } + } } impl fmt::Display for CompiledModule { @@ -24,7 +38,12 @@ impl fmt::Display for CompiledModule { writeln!(f, "; n_registers = {}", self.n_registers)?; writeln!(f, "; constants:")?; for (i, c) in self.constants.iter().enumerate() { - writeln!(f, "; [{i}] = {:?}", c)?; + match c { + Constant::BytecodeFunc(idx) => { + writeln!(f, "; [{i}] = BytecodeFunc(sub_module[{idx}])")? + } + other => writeln!(f, "; [{i}] = {other:?}")?, + } } writeln!(f)?; for (offset, &instr) in self.bytecode.iter().enumerate() { diff --git a/crates/oxide_compiler/src/opcode.rs b/crates/oxide_compiler/src/opcode.rs index 3c20d221..ef368245 100644 --- a/crates/oxide_compiler/src/opcode.rs +++ b/crates/oxide_compiler/src/opcode.rs @@ -60,9 +60,11 @@ pub enum OpCode { STORE_VAR = 0x31, LOAD_CONST = 0x32, - // ── Call (0x40-0x4F) ── + // -- Call (0x40-0x4F) -- CALL = 0x40, RETURN = 0x41, + CALL_NATIVE = 0x42, + NEW_EXPRESSION = 0x43, // ── Object Property (0x50-0x5F) ── IC_GET_PROP = 0x50, @@ -165,6 +167,8 @@ impl TryFrom for OpCode { 0x32 => Ok(OpCode::LOAD_CONST), 0x40 => Ok(OpCode::CALL), 0x41 => Ok(OpCode::RETURN), + 0x42 => Ok(OpCode::CALL_NATIVE), + 0x43 => Ok(OpCode::NEW_EXPRESSION), 0x50 => Ok(OpCode::IC_GET_PROP), 0x51 => Ok(OpCode::IC_SET_PROP), 0x52 => Ok(OpCode::GET_PROP), @@ -244,6 +248,8 @@ impl fmt::Display for OpCode { OpCode::LOAD_CONST => "LOAD_CONST", OpCode::CALL => "CALL", OpCode::RETURN => "RETURN", + OpCode::CALL_NATIVE => "CALL_NATIVE", + OpCode::NEW_EXPRESSION => "NEW_EXPRESSION", OpCode::IC_GET_PROP => "IC_GET_PROP", OpCode::IC_SET_PROP => "IC_SET_PROP", OpCode::GET_PROP => "GET_PROP", diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index f503c7ee..f8a45370 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -40,6 +40,8 @@ pub struct CallFrame { pub return_addr: usize, pub n_locals: u8, pub n_args: u8, + pub function_obj_reg: u8, + pub frame_base: u8, } pub struct ForInIter<'bump> { @@ -59,6 +61,8 @@ pub struct Vm { pub epoch: Epoch, pub object_prototype: P, pub math_rng_state: u64, + sub_modules: Vec, + sub_module_constants: Vec>, } fn bind_object(kernel: &Arc, global: &mut JsObject) { @@ -630,6 +634,8 @@ impl Vm { epoch: Epoch::new(), object_prototype: P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())), math_rng_state: 0, + sub_modules: Vec::new(), + sub_module_constants: Vec::new(), } } @@ -646,6 +652,8 @@ impl Vm { epoch: Epoch::new(), object_prototype: P::clone(&kernel.builtin_world().object_proto), math_rng_state: 0, + sub_modules: Vec::new(), + sub_module_constants: Vec::new(), } } @@ -816,8 +824,11 @@ impl Vm { Constant::Boolean(b) => JsValue::bool(*b), Constant::Null => JsValue::null(), Constant::Undefined => JsValue::undefined(), + Constant::BytecodeFunc(_idx) => JsValue::undefined(), }) .collect(); + self.sub_modules = module.sub_modules.clone(); + self.sub_module_constants = vec![Vec::new(); self.sub_modules.len()]; self.bytecode = module.bytecode.clone(); self.pc = 0; self.regs = [JsValue::undefined(); 256]; @@ -838,6 +849,17 @@ impl Vm { self.dispatch() } + /// Call a bytecode function from native code (D-09). + /// Stub: sub_module storage not yet wired (plan 12.1-03). + #[allow(dead_code)] + pub fn call_bytecode_func( + &mut self, + _callback_obj: &JsObject, + _args_regs: &[u8], + ) -> Result { + Err("bytecode function calls not yet supported".into()) + } + fn dispatch(&mut self) -> Result { let mut steps: u64 = 0; const MAX_STEPS: u64 = 100_000_000; @@ -1049,15 +1071,137 @@ impl Vm { } continue; } + // bytecode path skeleton - wired in plan 12.1-03 + return Err("bytecode function calls not yet supported".into()); } } return Err("CALL target is not a native function".into()); } + OpCode::CALL_NATIVE => { + let callee_reg = rd; + let this_reg = a as u8; + let first_arg_reg = b as u8; + + let callee = self.regs[callee_reg]; + + if !callee.is_object() { + return Err("CALL_NATIVE target is not an object".into()); + } + let obj_ptr = callee.as_js_object_ptr(); + if obj_ptr.is_null() { + return Err("CALL_NATIVE target is null".into()); + } + let obj = unsafe { &*obj_ptr }; + if !obj.is_function() || obj.native_fn().is_none() { + return Err("CALL_NATIVE target is not a native function".into()); + } + + let ext = self.bytecode[self.pc]; + self.pc += 1; + let arg_count = (ext & 0xFF) as usize; + + let mut args_buf = [0u8; 257]; + args_buf[0] = this_reg; + for i in 0..arg_count.min(256) { + args_buf[i + 1] = first_arg_reg.wrapping_add(i as u8); + } + let args_slice = &args_buf[..arg_count + 1]; + + let func: NativeFn = unsafe { std::mem::transmute(obj.native_fn().unwrap()) }; + self.regs[254] = callee; + match func(self, args_slice) { + Ok(val) => self.regs[0] = val, + Err(err_val) => { + return Err(format!("Native error: {:?}", err_val)); + } + } + } + + OpCode::NEW_EXPRESSION => { + let constructor_reg = a as usize; + let first_arg_reg = b as u8; + + let constructor = self.regs[constructor_reg]; + if !constructor.is_object() { + return Err("NEW_EXPRESSION: constructor is not an object".into()); + } + let ctor_ptr = constructor.as_js_object_ptr(); + if ctor_ptr.is_null() { + return Err("NEW_EXPRESSION: constructor is null".into()); + } + let ctor_obj = unsafe { &*ctor_ptr }; + if !ctor_obj.is_function() { + return Err("NEW_EXPRESSION: constructor is not a function".into()); + } + + // Read extension word for arg_count + let ext = self.bytecode[self.pc]; + self.pc += 1; + let arg_count = (ext & 0xFF) as usize; + + // Create new empty object + let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject; + let new_obj = self.epoch.alloc(JsObject::new_empty( + EMPTY_SHAPE_ID, + JsValue::from_js_object(proto_ptr), + )); + + // Look up constructor.prototype and set as proto of new object + let proto_si = self.kernel.string_forge().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 }; + let proto_obj_ptr = proto_val.as_js_object_ptr(); + let _ = new_obj_mut.set_proto(JsValue::from_js_object(proto_obj_ptr)); + } + } + + // If constructor has native_fn, call it with this=new_obj + if ctor_obj.native_fn().is_some() { + let new_obj_val = JsValue::object(new_obj as *mut u8); + // this_reg = register holding new_obj; we use regs[255] as scratch + self.regs[255] = new_obj_val; + + let mut args_buf = [0u8; 257]; + args_buf[0] = 255u8; // this = new_obj + for i in 0..arg_count.min(256) { + args_buf[i + 1] = first_arg_reg.wrapping_add(i as u8); + } + let args_slice = &args_buf[..arg_count + 1]; + + let func: NativeFn = + unsafe { std::mem::transmute(ctor_obj.native_fn().unwrap()) }; + self.regs[254] = constructor; + match func(self, args_slice) { + Ok(val) => { + // If constructor returns an object, use that; else use new_obj + if val.is_object() { + self.regs[0] = val; + } else { + self.regs[0] = new_obj_val; + } + } + Err(err_val) => { + return Err(format!("Native constructor error: {:?}", err_val)); + } + } + } else { + // Bytecode constructor - wired in plan 12.1-03 + return Err( + "NEW_EXPRESSION: bytecode constructors not yet supported".into() + ); + } + } + OpCode::RETURN => { let result = self.regs[rd]; if let Some(frame) = self.frames.pop() { + // Restore caller's pc and merge result into caller's regs[0]. + // frame.frame_base marks the first register of the caller frame; + // when bytecode calls are wired in 12.1-03, registers above + // frame_base will be cleared on RETURN. self.pc = frame.return_addr; self.regs[0] = result; } else {