docs: 补齐虚拟机运行时与调用绑定注释

This commit is contained in:
2026-09-17 14:11:06 +00:00
parent 505cc4ff47
commit 6c4b49b196
4 changed files with 182 additions and 1 deletions
+17
View File
@@ -104,6 +104,14 @@ macro_rules! bind_constructor {
}};
}
/// 给既有构造器对象安装 native 函数项指针、形参个数与构造器类型标签。
///
/// 绑定模块构造器注册的公共步骤:只更新既有对象的函数元数据,不新建对象、不写属性表。
/// 函数项指针经 `NativeFnPtr::from_raw` 存入。
///
/// # 注意事项
/// - `native_fn` 须为调用方转成 `*const ()` 的合法 `NativeFn` 函数项指针。
/// - 不设置 `length` 属性:需要 Function.length 的调用方自行补写。
pub(crate) fn configure_native_constructor(ctor: &mut JsObject, native_fn: *const (), arg_count: u8) {
// SAFETY: native_fn 始终是调用方转成 *const () 的合法 NativeFn 函数项指针。
ctor.set_native_fn(Some(unsafe { NativeFnPtr::from_raw(native_fn) }));
@@ -117,6 +125,15 @@ fn configure_existing_ctor(ctor: &P<JsObject>, native_fn: *const (), arg_count:
configure_native_constructor(ctor, native_fn, arg_count);
}
/// 把 `(name, native_fn_ptr, nargs)` 绑定表逐条安装为目标对象的命名方法。
///
/// 每条经 `world.bind_method` 在 `target` 上建/复用方法 wrapper 并写命名属性;shape 与
/// 字符串 interner 由 `core` 借出,循环内共享。
///
/// # 注意事项
/// - 表中所有函数项指针须为合法的 `NativeFn`(转成 `*const ()`),此处经
/// `NativeFnPtr::from_raw` 转换。
/// - `bind_method` 的返回值被丢弃:单条绑定失败不中断后续条目。
pub(crate) fn apply_binding_table(
world: &BuiltinWorld, target: &mut JsObject, core: &Arc<KernelCore>, bindings: &[(&'static str, *const (), u8)],
) {
+107
View File
@@ -9,6 +9,15 @@ use oxide_types::object::{Cell, JsObject, PropAttributes};
use oxide_types::value::JsValue;
impl Vm {
/// 把连续实参寄存器段与 this 寄存器打包成 native 调用下标表。
///
/// 返回 `([u8; 257], len)`:`buf[0]` 为 this 寄存器下标,其后依次为 `first_arg_reg`
/// 起连续递增的实参寄存器下标。数组由调用方在栈上持有,供 native 函数按下标直读
/// 共享寄存器窗口。
///
/// # 边界与前提
/// - `arg_count` 截断为 256,超出部分不进入下标表(`len ≤ 257`)。
/// - 实参寄存器下标按 `u8` 回绕,调用方须保证窗口内连续可用。
#[inline(always)]
pub(crate) fn build_native_args(first_arg_reg: u8, arg_count: usize, this_reg: u8) -> ([u8; 257], usize) {
let mut args_buf = [0u8; 257];
@@ -20,6 +29,28 @@ impl Vm {
(args_buf, n + 1)
}
/// native 函数调用收口:校验调用深度、打包实参、快照 callee 槽后执行实现并按三态回写结果。
///
/// native 与调用方共享扁平寄存器文件,故实现执行前把当前 callee 放进 `regs[254]`
/// 供分发型 builtin(bind/call/apply)读取「当前 callee」,执行后恢复调用方 `this`。
///
/// # 步骤
/// 1. 调用深度达上限抛 RangeError。
/// 2. `build_native_args` 打包实参下标表。
/// 3. 经 `native_fn_ptr_to_fn` 取得函数项并执行;执行前后快照/恢复 `regs[254]`。
/// 4. 按 `NativeResult` 三态回写:`Ok` 写 `regs[0]`;`Err` 以原始值走 `unwind`;
/// `TailCall` 目标为 native 时同步调用,否则压字节码帧。
///
/// # 边界与前提
/// - 调用方须保证 `obj` 已是 native 函数:`native_fn()` 为空时此处 unwrap 失败。
/// - `Err` 保留异常原值:用户回调 throw 的原始值经 catch 原样收到,不包装成 Error。
///
/// # 副作用
/// - 修改 `native_call_depth`、`regs[0]`、`regs[254]`,可能压帧或触发异常展开。
///
/// # 注意事项
/// - `native_fn_ptr_to_fn` 是 NativeFnPtr → NativeFn 的唯一强制转换点,SAFETY 前提
/// 为 `native_fn` 由 `set_native_fn` 以合法函数项指针设置。
#[inline(always)]
pub(crate) fn dispatch_native_call(
&mut self, obj: &JsObject, callee: JsValue, this_reg: u8, first_arg_reg: u8, arg_count: usize,
@@ -197,6 +228,23 @@ impl Vm {
Ok(())
}
/// 初始化(或复用)当前帧调用环境的 cell:按 a 槽下标写入 `cell_stack`,b 槽决定初始化标志。
///
/// # 步骤
/// 1. 取 a 槽为 cell 下标,按需扩容 `cell_stack` 至可容纳该下标。
/// 2. b 槽为 0 表示已初始化,非 0 为 TDZ(读写抛 ReferenceError,用于 for-in/for-of
/// 词法头环境建模)。
/// 3. 槽位为空时新建 cell;已有 `CREATE_CLOSURE` 建的占位 cell 则原位更新值与标志,
/// 使闭包 upvalue 指向的同一 cell 跟随初始化。
///
/// # 边界与前提
/// - `cell_stack` 为空时 `last_mut()` unwrap 失败;调用前须已压入当前帧环境层。
///
/// # 副作用
/// - 写入/更新堆上 `Cell`(可能触发分配),改动闭包 upvalue 可见值。
///
/// # 注意事项
/// - 手写 IR 路径保留的指令,常规编译不发射,故标 `#[allow(dead_code)]`。
#[allow(dead_code)]
pub(crate) fn dispatch_make_cell(&mut self, rd: usize, instr: u32) -> Result<(), String> {
// a 槽为 cell 索引;b 槽承载未初始化标志(1 = 绑定永不被初始化,
@@ -240,6 +288,18 @@ impl Vm {
Ok(())
}
/// 读取当前帧 cell:空槽惰性建 cell,未初始化(TDZ)抛 ReferenceError,否则写 `regs[rd]`。
///
/// # 步骤
/// 1. 取 b 槽为 cell 下标,按需扩容 `cell_stack`。
/// 2. 槽位为空时以 `regs[a]` 为初值新建已初始化 cell(`CREATE_CLOSURE` 占位缺失路径)。
/// 3. cell 未初始化抛 ReferenceError;否则值写入 `regs[rd]`。
///
/// # 边界与前提
/// - cell 下标越界按需扩容,不报错。
///
/// # 注意事项
/// - 手写 IR 保留指令(同 `dispatch_make_cell`)。
#[allow(dead_code)]
pub(crate) fn dispatch_cell_get(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
let cell_idx = b;
@@ -259,6 +319,22 @@ impl Vm {
Ok(())
}
/// 写入当前帧 cell:越界或空槽静默 no-op,TDZ 写检查后写值并置已初始化。
///
/// # 步骤
/// 1. b 槽为 cell 下标;下标越界直接返回(不建槽)。
/// 2. 槽位为空(尚无 `CREATE_CLOSURE`/`MAKE_CELL`)静默返回,不改状态。
/// 3. cell 未初始化(声明点前)抛 ReferenceError。
/// 4. 写入 `regs[a]` 并置 initialized。
///
/// # 边界与前提
/// - 空槽/越界均为 no-op,与读路径的惰性建 cell 不对称:写方无可用源值建槽。
///
/// # 副作用
/// - 修改堆上 `Cell` 值与初始化标志。
///
/// # 注意事项
/// - 手写 IR 保留指令(同 `dispatch_make_cell`)。
#[allow(dead_code)]
pub(crate) fn dispatch_cell_set(&mut self, a: usize, b: usize) -> Result<(), String> {
let cell_idx = b;
@@ -289,6 +365,19 @@ impl Vm {
self.frames.last().map(|f| f.callee).or(self.inline_callee)
}
/// 读取当前闭包的 upvalue:命中 cell 判初始化后取值,未命中委托惰性建 cell。
///
/// # 步骤
/// 1. 取 imm16 为 upvalue 下标,从当前 callee 的 `upvalues` 命中 cell。
/// 2. 命中且非空:未初始化(TDZ)抛 ReferenceError,否则值写入 `regs[rd]`。
/// 3. 未命中/空槽(`CREATE_CLOSURE` 早于 `MAKE_CELL`):委托
/// `lazy_create_upvalue_cell` 建 cell。
///
/// # 边界与前提
/// - `uv_idx` 越界或当前无 callee 时同样走惰性路径,最终回退 undefined。
///
/// # 注意事项
/// - 手写 IR 保留指令(同 `dispatch_make_cell`)。
#[allow(dead_code)]
pub(crate) fn dispatch_load_upvalue(&mut self, rd: usize, instr: u32) -> Result<(), String> {
let uv_idx = opcode::imm16(instr) as usize;
@@ -315,6 +404,11 @@ impl Vm {
self.lazy_create_upvalue_cell(rd, uv_idx)
}
/// 为当前闭包惰性创建 upvalue cell:初值优先取调用方 cell 表(`cell_stack` 倒数
/// 第二层)同下标 cell 的值,缺失时取 `regs[rd]`。
///
/// 服务于 `CREATE_CLOSURE` 早于 `MAKE_CELL` 的 hoisting 顺序。cell 建好后写回闭包
/// `upvalues[uv_idx]` 并把值读回 `regs[rd]`;无 callee 或下标越界时 `regs[rd]` 置 undefined。
fn lazy_create_upvalue_cell(&mut self, rd: usize, uv_idx: usize) -> Result<(), String> {
if let Some(callee) = self.current_callee() {
if callee.is_object() {
@@ -342,6 +436,19 @@ impl Vm {
Ok(())
}
/// 写入当前闭包的 upvalue:空槽惰性建 cell,TDZ/const 检查后写值置已初始化。
///
/// # 步骤
/// 1. b 槽为 upvalue 下标,源值为 `regs[a]`;`rd` 复用为 const 标志。
/// 2. 命中闭包 `upvalues` 且非空:未初始化(TDZ)抛 ReferenceError,const 标志非 0
/// 抛 TypeError,否则写值置 initialized。
/// 3. 槽位为空(尚未建 cell)以源值新建已初始化 cell;下标越界或无 callee 静默返回。
///
/// # 边界与前提
/// - const 检查在 TDZ 检查之后,两者均先于写值。
///
/// # 注意事项
/// - 手写 IR 保留指令(同 `dispatch_make_cell`)。
#[allow(dead_code)]
pub(crate) fn dispatch_store_upvalue(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> {
let const_flag = rd;
+1 -1
View File
@@ -123,7 +123,7 @@ impl Vm {
let obj = unsafe { &*obj_ptr };
// 目标为普通 JS 函数(如用户覆盖了 builtin 名):回退到通用调用路径,
// 复用 dispatch_call 的 JS 函数调用逻辑而非强制走 native。
// pc 此刻指向 ext 字(与 CALL 指令一致),dispatch_call 直接读取。
// pc 此刻指向指令的扩展操作数字(ext 字,与 CALL 指令一致),dispatch_call 直接读取。
if obj.is_function() && obj.native_fn().is_none() {
return self.dispatch_call(rd, a, b).map(|_| ());
}
+57
View File
@@ -370,6 +370,29 @@ impl Vm {
)
}
/// 内联同步调用字节码函数:不起新 CallFrame,直接在调用方寄存器窗口内执行被调模块。
///
/// 平表按被调对象创建期 `table_gen` 解析(跨 run 调用仍命中定义模块所在原表);
/// 生成器/异步/异步生成器函数不执行函数体,分别返回对应迭代器或 promise 对象。
/// 普通函数经 `save_inline_state` 保存窗口后在调用方寄存器文件上执行子模块,结束
/// 时 `restore_inline_state` 回拷窗口。
///
/// # 步骤
/// 1. `sub_module_index == 0` 为 native 哨兵(accessor 不可调用),直接返回 TypeError。
/// 2. 按创建期代际取平表并校验下标;调用深度达上限抛 RangeError。
/// 3. 生成器/异步/异步生成器分流到对应对象构造,不进入 dispatch。
/// 4. 保存窗口与 pc,零填 callee 读区,装载字节码/常量/平表 id,实参写入 spill 栈实参区。
/// 5. 绑定 `regs[254]`:箭头函数用创建期捕获的 `this`,sloppy 普通函数在 nullish
/// receiver 上替换全局对象,严格模式与显式 receiver 原样保留;`regs[255]` 置 undefined。
/// 6. 执行 dispatch 后恢复调用方状态并返回结果。
///
/// # 边界与前提
/// - 表代际未注册或 `sub_module_index` 越界返回 `Err`,不 panic。
/// - 实参超出 `n_args` 的部分忽略;缺位参数填 undefined。
///
/// # 副作用
/// - 改写全局寄存器文件、pc、字节码/常量/平表 id、spill 栈与 `cell_stack`;
/// 结束前经 `restore_inline_state` 全部还原。
pub(crate) fn call_bytecode_function_inline(
&mut self, callee: JsValue, callee_obj: &JsObject, receiver: JsValue, args: &[JsValue],
) -> Result<JsValue, String> {
@@ -480,6 +503,18 @@ impl Vm {
result
}
/// 从调用帧恢复调用方执行状态:弹回字节码/平表 id/表代际/常量四组保存栈,
/// 回拷调用方寄存器窗口并按 `return_addr` 回写 pc。
///
/// 帧恢复只负责执行核心状态;被调函数的返回值传递由调用路径另行处理。
///
/// # 边界与前提
/// - 保存栈为空时对应字段保持原值(不 panic);`saved_reg_offset` 与
/// `caller_reg_limit` 由压帧时按调用方活动上界写入,须与其匹配。
///
/// # 副作用
/// - 改写 pc、字节码、活动平表 id/代际、活动常量、寄存器窗口与 `regs[254]/[255]`、
/// save_stack/spill_stack、`active_reg_limit`,并重载调用方 builtin 镜像槽。
pub(crate) fn restore_frame(&mut self, frame: CallFrame) {
vm_trace!(
"restore_frame: return_addr={} caller_reg_limit={}",
@@ -628,6 +663,28 @@ impl Vm {
result
}
/// 异常展开:沿 `try_stack` 查找最近可接管的 catch/finally,无则逐帧回退并返回未捕获错误。
///
/// 与 `TRY_FINALLY_END` 的完成穿越逻辑对应:finally 体在途异常经 `pending_exception`
/// 悬挂,`finally_active` 标记其已进入,防止同一 finally 重复执行;处理器作用域内被
/// 中断的 for-of 循环先经 `close_for_of_above` 执行 IteratorClose。
///
/// # 步骤
/// 1. 逐 handler 展开:所属帧已返回(`frame_depth` 超出当前帧数)的残留 handler 跳过。
/// 2. 弹帧至 handler 深度,逐帧弹 `cell_stack` 并 `restore_frame` 回写调用方状态。
/// 3. 关闭该作用域内被中断的 for-of 迭代器。
/// 4. 有 finally:未进入则挂起在途异常并转入 finally 体;已进入(finally 体内新异常)
/// 则丢弃在途异常继续向外展开,不重入本 finally。
/// 5. 有 catch:在途异常写入 `regs[0]`,pc 转向 catch。
/// 6. 无可用处理器:关闭全部迭代器、回退所有帧,记 `last_uncaught_value` 并返回 `Err`。
///
/// # 边界与前提
/// - `exception_value` 为空时按 undefined 处理;`try_stack` 为空直接走未捕获路径。
///
/// # 副作用
/// - 弹出 `try_stack`/`frames`/`cell_stack`,改写 pc 与寄存器窗口;未捕获时把逃逸
/// 异常值存入 `last_uncaught_value`(for-of 的 next() 抛出经此重抛原值而非展平
/// 字符串),并消费 `pending_error_kind`。
pub(crate) fn unwind(&mut self) -> Result<(), String> {
vm_debug!("unwind: {} try handlers on stack", self.try_stack.len());
while let Some(mut handler) = self.try_stack.pop() {