Files
OxideJS-New/crates/oxide_vm/src/dispatch/misc.rs
T

253 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use crate::vm::Vm;
use crate::{vm_error, vm_trace};
use oxide_bytecode::opcode;
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::private_key::make_int_key;
use oxide_types::value::JsValue;
impl Vm {
#[inline(always)]
pub(crate) fn dispatch_load_const(&mut self, rd: usize, instr: u32) -> Result<(), String> {
let idx = (instr >> 16) as usize;
vm_trace!("LOAD_CONST rd={} idx={}", rd, idx);
let imm = self.immutables();
if idx < imm.len() {
self.regs[rd] = imm[idx];
Ok(())
} else {
vm_error!("LOAD_CONST index {} out of bounds (len={})", idx, imm.len());
Err(format!("constant index {idx} out of bounds"))
}
}
#[inline(always)]
pub(crate) fn dispatch_typeof(&mut self, rd: usize, a: usize) {
vm_trace!("TYPEOF rd={} r{}={:?}", rd, a, self.regs[a]);
let val = self.regs[a];
let result = if val.is_undefined() {
"undefined"
} else if val.is_null() {
"object"
} else if val.is_bool() {
"boolean"
} else if val.is_int() || val.is_double() {
"number"
} else if val.is_string() {
"string"
} else if val.is_symbol() {
"symbol"
} else if val.is_bigint() {
"bigint"
} else if val.is_object() {
let obj = unsafe { &*val.as_js_object_ptr() };
if obj.is_function() {
"function"
} else {
"object"
}
} else {
"undefined"
};
self.regs[rd] = self.new_string(result);
}
/// ToObject:rd 原地转换。对象直接返回;null/undefined 抛 TypeError;
/// 其余原始值包装为对应包装对象(with 对象环境需要)。
#[inline(always)]
pub(crate) fn dispatch_to_object(&mut self, rd: usize) -> Result<(), String> {
vm_trace!("TO_OBJECT rd={} r{}={:?}", rd, rd, self.regs[rd]);
let val = self.regs[rd];
if val.is_object() {
return Ok(());
}
if val.is_null() || val.is_undefined() {
// 抛 JS 异常而非返回 Err:外围 try/catch 须能捕获(对象解构空 pattern)。
self.raise_error_kind("TypeError", "Cannot convert null or undefined to object")?;
return Ok(());
}
let obj_val = oxide_runtime_api::to_object(val, self)?;
self.regs[rd] = obj_val;
Ok(())
}
pub(crate) fn dispatch_load_var(&mut self, rd: usize, a: usize) -> Result<bool, String> {
vm_trace!("LOAD_VAR rd={} r{}={:?}", rd, a, self.regs[a]);
if a == 254
&& self.frames.last().map(|frame| frame.is_derived_constructor).unwrap_or(false)
&& self.regs[a].is_undefined()
{
self.raise_error_kind("ReferenceError", "must call super constructor before using 'this'")?;
return Ok(true);
}
self.regs[rd] = self.regs[a];
Ok(false)
}
pub(crate) fn dispatch_store_var(&mut self, rd: usize, a: usize, b: usize) -> Result<bool, String> {
vm_trace!("STORE_VAR r{}={:?} const={}", rd, self.regs[a], b);
if b != 0 {
// const guard:检查目标槽是否已初始化。
if !self.regs[rd].is_undefined() {
self.raise_error_kind("TypeError", "Assignment to constant variable")?;
return Ok(true);
}
}
self.regs[rd] = self.regs[a];
Ok(false)
}
/// SPILL:`regs[rd]` → `spill_stack[帧基址 + slot]`。
///
/// ext 字为 slot(u16);帧基址 = 当前 CallFrame.spill_offset(顶层模块无帧为 0)。
/// # 边界与前提
/// - slot 超 u16 范围返回错误(RegAlloc 保证发射在界内)。
/// # 副作用
/// - 写 spill 栈,可能触发 resize 扩容。
#[inline(always)]
pub(crate) fn dispatch_spill(&mut self, rd: usize) -> Result<(), String> {
let slot = self.bytecode[self.pc] as usize;
self.pc += 1;
if slot > u16::MAX as usize {
return Err(format!("SPILL slot {slot} out of range (max 65535)"));
}
let base = self.frames.last().map(|f| f.spill_offset as usize).unwrap_or(0);
let idx = base + slot;
if idx >= self.spill_stack.len() {
self.spill_stack.resize(idx + 1, JsValue::undefined());
}
vm_trace!("SPILL r{} -> slot[{}] base={}", rd, slot, base);
self.spill_stack[idx] = self.regs[rd];
Ok(())
}
/// UNSPILL:`spill_stack[帧基址 + slot]` → `regs[rd]`。
///
/// # 边界与前提
/// - slot 超 u16 范围返回错误。
/// - 越界读返回 undefined(防御:slot 由 RegAlloc 保证在界内)。
#[inline(always)]
pub(crate) fn dispatch_unspill(&mut self, rd: usize) -> Result<(), String> {
let slot = self.bytecode[self.pc] as usize;
self.pc += 1;
if slot > u16::MAX as usize {
return Err(format!("UNSPILL slot {slot} out of range (max 65535)"));
}
let base = self.frames.last().map(|f| f.spill_offset as usize).unwrap_or(0);
let idx = base + slot;
vm_trace!("UNSPILL r{} <- slot[{}] base={}", rd, slot, base);
self.regs[rd] = if idx < self.spill_stack.len() {
self.spill_stack[idx]
} else {
JsValue::undefined()
};
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_new_object(&mut self, rd: usize) {
vm_trace!("NEW_OBJECT rd={}", rd);
let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject;
let obj = self
.epoch
.alloc(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr)));
self.regs[rd] = JsValue::object(obj as *mut u8);
}
/// 创建 arguments 对象:索引属性取当前帧(或 inline 同步调用)的完整实参,
/// 附 length / callee 属性。第一版为 unmapped(非严格)语义,索引与形参不同步。
///
/// # 边界与前提
/// - 实参区由 `push_bytecode_frame` / `dispatch_super_call` / `dispatch_new_expression`
/// 在推帧时写入 spill 栈;frames 为空(inline 同步调用)时读 `inline_args_*`。
/// - 索引属性用 shape 槽存储(普通对象),length/callee 为不可枚举数据属性。
pub(crate) fn dispatch_create_arguments(&mut self, rd: usize) -> Result<(), String> {
let (base, count) = match self.frames.last() {
Some(frame) => (frame.arguments_base, frame.arguments_count),
None => (self.inline_args_base, self.inline_args_count),
};
let proto_ptr = &*self.object_prototype as *const JsObject as *mut JsObject;
let obj_ptr = self.alloc_object(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr)));
let obj = unsafe { &mut *obj_ptr };
obj.type_tag = JsObject::OBJ_TYPE_ARGUMENTS;
// 索引属性:按实参下标写入 shape 槽,属性描述符为默认(可写/可枚举/可配置)。
// 下标走整数键,保证 `arguments[0]`(property_key_si(int 0))键等价命中。
for i in 0..count as usize {
let si = make_int_key(i as u32);
let val = self.spill_stack.get(base as usize + i).copied().unwrap_or(JsValue::undefined());
self.set_or_create_prop_value(obj, si, val);
}
// length:实参个数,可写、不可枚举、可配置。
let length_si = self.kernel_core.perm_interner().intern("length").0;
if let Err(msg) = self.define_data_property(
obj,
length_si,
JsValue::int(count as i32),
PropAttributes::new(true, false, true),
) {
return self.raise_error_kind("TypeError", &msg);
}
// callee:当前执行函数,可写、不可枚举、可配置(严格模式应抛 TypeError,未支持)。
let callee_si = self.kernel_core.perm_interner().intern("callee").0;
let callee = self.current_callee().unwrap_or(JsValue::undefined());
if let Err(msg) = self.define_data_property(obj, callee_si, callee, PropAttributes::new(true, false, true)) {
return self.raise_error_kind("TypeError", &msg);
}
self.regs[rd] = JsValue::from_js_object(obj_ptr);
Ok(())
}
/// 创建 rest 参数数组:实参区下标 ≥ `fixed_count` 的实参收集为数组存入 `rd`。
///
/// # 步骤
/// 1. 读当前帧(或 inline 同步调用)的实参区基址与个数。
/// 2. 实参个数超出固定形参数的部分作为数组元素写入。
///
/// # 边界与前提
/// - 实参来源与 CREATE_ARGUMENTS 相同(push_bytecode_frame / inline 路径写入 spill 栈)。
/// - 实参不足 `fixed_count` 时数组为空。
pub(crate) fn dispatch_create_rest_array(&mut self, rd: usize, fixed_count: usize) -> Result<(), String> {
let (base, count) = match self.frames.last() {
Some(frame) => (frame.arguments_base, frame.arguments_count),
None => (self.inline_args_base, self.inline_args_count),
};
let n = count as usize;
let rest_len = n.saturating_sub(fixed_count);
let proto_ptr = self.session.builtin_world().array_proto.as_ptr() as *mut JsObject;
let bump = self.epoch.bump();
let obj_ptr =
self.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr), rest_len, bump));
let obj = unsafe { &mut *obj_ptr };
for i in 0..rest_len {
let val = self
.spill_stack
.get(base as usize + fixed_count + i)
.copied()
.unwrap_or(JsValue::undefined());
obj.set_prop_at(i as u32, val);
}
self.regs[rd] = JsValue::object(obj_ptr as *mut u8);
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_new_array(&mut self, rd: usize, instr: u32) {
let n = opcode::imm16(instr) as usize;
vm_trace!("NEW_ARRAY rd={} n={}", rd, n);
let proto_ptr = self.session.builtin_world().array_proto.as_ptr() as *mut JsObject;
let bump = self.epoch.bump();
let obj = self.alloc_object(JsObject::new_array(EMPTY_SHAPE_ID, JsValue::from_js_object(proto_ptr), n, bump));
self.regs[rd] = JsValue::object(obj as *mut u8);
}
#[inline(always)]
pub(crate) fn dispatch_void(&mut self, rd: usize) {
vm_trace!("VOID rd={}", rd);
self.regs[rd] = JsValue::undefined();
}
}