feat: 搭建字节码函数调用基础设施
This commit is contained in:
@@ -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(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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<Constant>,
|
||||
pub n_registers: u8,
|
||||
pub builtin_reg_map: Vec<(String, u8)>,
|
||||
pub sub_modules: Vec<CompiledModule>,
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
@@ -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<u8> 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",
|
||||
|
||||
@@ -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<JsObject>,
|
||||
pub math_rng_state: u64,
|
||||
sub_modules: Vec<CompiledModule>,
|
||||
sub_module_constants: Vec<Vec<JsValue>>,
|
||||
}
|
||||
|
||||
fn bind_object(kernel: &Arc<OxideKernel>, 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<JsValue, String> {
|
||||
Err("bytecode function calls not yet supported".into())
|
||||
}
|
||||
|
||||
fn dispatch(&mut self) -> Result<JsValue, String> {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user