From 6c4b49b1968ad0af438b164245cf63b0bb91e04d Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Thu, 17 Sep 2026 14:11:06 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=A1=A5=E9=BD=90=E8=99=9A=E6=8B=9F?= =?UTF-8?q?=E6=9C=BA=E8=BF=90=E8=A1=8C=E6=97=B6=E4=B8=8E=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/oxide_vm/src/bindings/mod.rs | 17 ++++ crates/oxide_vm/src/dispatch/call.rs | 107 ++++++++++++++++++++++++ crates/oxide_vm/src/vm_dispatch_ctrl.rs | 2 +- crates/oxide_vm/src/vm_runtime.rs | 57 +++++++++++++ 4 files changed, 182 insertions(+), 1 deletion(-) diff --git a/crates/oxide_vm/src/bindings/mod.rs b/crates/oxide_vm/src/bindings/mod.rs index 56f2efac..bc0a2d43 100644 --- a/crates/oxide_vm/src/bindings/mod.rs +++ b/crates/oxide_vm/src/bindings/mod.rs @@ -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, 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, bindings: &[(&'static str, *const (), u8)], ) { diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index 4c389970..d440bee0 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -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; diff --git a/crates/oxide_vm/src/vm_dispatch_ctrl.rs b/crates/oxide_vm/src/vm_dispatch_ctrl.rs index 1182a02b..c943572d 100644 --- a/crates/oxide_vm/src/vm_dispatch_ctrl.rs +++ b/crates/oxide_vm/src/vm_dispatch_ctrl.rs @@ -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(|_| ()); } diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index fb14c252..fa3de05b 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -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 { @@ -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() {