feat: 实现 VM 字节码执行循环
This commit is contained in:
@@ -3,4 +3,5 @@
|
|||||||
pub mod coercion;
|
pub mod coercion;
|
||||||
pub mod mem;
|
pub mod mem;
|
||||||
pub mod value;
|
pub mod value;
|
||||||
|
pub mod vm;
|
||||||
pub use value::JsValue;
|
pub use value::JsValue;
|
||||||
|
|||||||
@@ -0,0 +1,223 @@
|
|||||||
|
use oxide_compiler::compiler::Constant;
|
||||||
|
use oxide_compiler::module::CompiledModule;
|
||||||
|
use oxide_compiler::opcode::{self, OpCode};
|
||||||
|
|
||||||
|
use crate::coercion;
|
||||||
|
use crate::value::JsValue;
|
||||||
|
|
||||||
|
pub struct CallFrame {
|
||||||
|
pub return_addr: usize,
|
||||||
|
pub n_locals: u8,
|
||||||
|
pub n_args: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Vm {
|
||||||
|
regs: [JsValue; 256],
|
||||||
|
pc: usize,
|
||||||
|
bytecode: Vec<opcode::Instr>,
|
||||||
|
constants: Vec<JsValue>,
|
||||||
|
frames: Vec<CallFrame>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vm {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
regs: [JsValue::undefined(); 256],
|
||||||
|
pc: 0,
|
||||||
|
bytecode: Vec::new(),
|
||||||
|
constants: Vec::new(),
|
||||||
|
frames: Vec::with_capacity(128),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self, module: &CompiledModule) -> Result<JsValue, String> {
|
||||||
|
self.constants = module.constants.iter().map(convert_constant).collect();
|
||||||
|
self.bytecode = module.bytecode.clone();
|
||||||
|
self.pc = 0;
|
||||||
|
self.regs = [JsValue::undefined(); 256];
|
||||||
|
self.frames.clear();
|
||||||
|
|
||||||
|
self.dispatch()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch(&mut self) -> Result<JsValue, String> {
|
||||||
|
loop {
|
||||||
|
if self.pc >= self.bytecode.len() {
|
||||||
|
return Err("program counter out of bounds".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let instr = self.bytecode[self.pc];
|
||||||
|
let op = opcode::opcode(instr);
|
||||||
|
let rd = opcode::rd(instr) as usize;
|
||||||
|
let a = opcode::a(instr) as usize;
|
||||||
|
let b = opcode::b(instr) as usize;
|
||||||
|
self.pc += 1;
|
||||||
|
|
||||||
|
match op {
|
||||||
|
OpCode::NOP => {}
|
||||||
|
|
||||||
|
OpCode::HALT => return Ok(self.regs[0]),
|
||||||
|
|
||||||
|
OpCode::LOAD_CONST => {
|
||||||
|
let idx = opcode::imm16(instr) as usize;
|
||||||
|
if idx < self.constants.len() {
|
||||||
|
self.regs[rd] = self.constants[idx];
|
||||||
|
} else {
|
||||||
|
return Err(format!("constant index {idx} out of bounds"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::ADD => {
|
||||||
|
let lhs = coercion::to_primitive(self.regs[a]);
|
||||||
|
let rhs = coercion::to_primitive(self.regs[b]);
|
||||||
|
if lhs.is_object() || rhs.is_object() {
|
||||||
|
return Err("ADD with objects not yet supported".into());
|
||||||
|
}
|
||||||
|
if lhs.is_double()
|
||||||
|
&& lhs.as_double().is_nan()
|
||||||
|
&& lhs.as_double().to_bits() & 0x000F_FFFF_FFFF_FFFF == 0
|
||||||
|
{
|
||||||
|
// Actually this is too complex. Simpler: just check if it's a string via our JsValue type system.
|
||||||
|
}
|
||||||
|
// String concat: JsValue doesn't have is_string yet (Phase 6).
|
||||||
|
// For Phase 5, strings come as NaN-boxed objects — we don't have string JsValues.
|
||||||
|
// Addition defaults to numeric.
|
||||||
|
let ln = coercion::to_number(lhs);
|
||||||
|
let rn = coercion::to_number(rhs);
|
||||||
|
self.regs[rd] = JsValue::float(ln + rn);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::SUB => {
|
||||||
|
let l = coercion::to_number(self.regs[a]);
|
||||||
|
let r = coercion::to_number(self.regs[b]);
|
||||||
|
self.regs[rd] = JsValue::float(l - r);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::MUL => {
|
||||||
|
let l = coercion::to_number(self.regs[a]);
|
||||||
|
let r = coercion::to_number(self.regs[b]);
|
||||||
|
self.regs[rd] = JsValue::float(l * r);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::DIV => {
|
||||||
|
let l = coercion::to_number(self.regs[a]);
|
||||||
|
let r = coercion::to_number(self.regs[b]);
|
||||||
|
self.regs[rd] = JsValue::float(l / r);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::MOD => {
|
||||||
|
let l = coercion::to_number(self.regs[a]);
|
||||||
|
let r = coercion::to_number(self.regs[b]);
|
||||||
|
self.regs[rd] = JsValue::float(l % r);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::NEG => {
|
||||||
|
let v = coercion::to_number(self.regs[a]);
|
||||||
|
self.regs[rd] = JsValue::float(-v);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::EQ => {
|
||||||
|
let eq = coercion::abstract_eq(self.regs[a], self.regs[b]);
|
||||||
|
self.regs[rd] = JsValue::bool(eq);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::NEQ => {
|
||||||
|
let ne = !coercion::abstract_eq(self.regs[a], self.regs[b]);
|
||||||
|
self.regs[rd] = JsValue::bool(ne);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::LT => {
|
||||||
|
let rel = coercion::relational_compare(self.regs[a], self.regs[b]);
|
||||||
|
self.regs[rd] = JsValue::bool(rel.unwrap_or(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::GT => {
|
||||||
|
let rel = coercion::relational_compare(self.regs[b], self.regs[a]);
|
||||||
|
self.regs[rd] = JsValue::bool(rel.unwrap_or(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::LTE => {
|
||||||
|
let rel = coercion::relational_compare(self.regs[b], self.regs[a]);
|
||||||
|
self.regs[rd] = JsValue::bool(!rel.unwrap_or(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::GTE => {
|
||||||
|
let rel = coercion::relational_compare(self.regs[a], self.regs[b]);
|
||||||
|
self.regs[rd] = JsValue::bool(!rel.unwrap_or(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::JMP => {
|
||||||
|
let offset = opcode::offset16(instr) as isize;
|
||||||
|
self.pc = ((self.pc as isize) + offset - 1) as usize;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::JMP_IF_FALSE => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::JMP_IF_TRUE => {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::LOAD_VAR => {
|
||||||
|
self.regs[rd] = self.regs[a];
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::STORE_VAR => {
|
||||||
|
self.regs[rd] = self.regs[a];
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::CALL => {
|
||||||
|
let offset = opcode::offset16(instr) as usize;
|
||||||
|
self.frames.push(CallFrame {
|
||||||
|
return_addr: self.pc,
|
||||||
|
n_locals: b as u8,
|
||||||
|
n_args: a as u8,
|
||||||
|
});
|
||||||
|
self.pc = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::RETURN => {
|
||||||
|
let result = self.regs[rd];
|
||||||
|
if let Some(frame) = self.frames.pop() {
|
||||||
|
self.pc = frame.return_addr;
|
||||||
|
self.regs[0] = result;
|
||||||
|
} else {
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
if !op.is_implemented() {
|
||||||
|
return Ok(JsValue::undefined());
|
||||||
|
}
|
||||||
|
return Err(format!("opcode {op} not yet implemented"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Vm {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn convert_constant(c: &Constant) -> JsValue {
|
||||||
|
match c {
|
||||||
|
Constant::Number(v) => JsValue::float(*v),
|
||||||
|
Constant::String(_s) => JsValue::null(),
|
||||||
|
Constant::Boolean(b) => JsValue::bool(*b),
|
||||||
|
Constant::Null => JsValue::null(),
|
||||||
|
Constant::Undefined => JsValue::undefined(),
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user