feat: arguments 对象绑定(CREATE_ARGUMENTS + 帧实参 spill 区)

This commit is contained in:
2026-08-08 00:01:22 +08:00
parent bfc2312f6c
commit bfcf132d73
12 changed files with 267 additions and 7 deletions
+5 -2
View File
@@ -164,8 +164,10 @@ define_opcodes! {
COMPOUND_MEMBER_MOD = 0x61 => "COMPOUND_MEMBER_MOD",
COMPOUND_MEMBER_EXP = 0x62 => "COMPOUND_MEMBER_EXP",
// ── Profiling — 占位符 (0x63-0x6F) ──
PROFILE_TYPE = 0x63 => "PROFILE_TYPE",
// ── 对象 (0x63) ──
CREATE_ARGUMENTS = 0x63 => "CREATE_ARGUMENTS",
// ── Profiling — 占位符 (0x64-0x6F) ──
PROFILE_SHAPE = 0x64 => "PROFILE_SHAPE",
PROFILE_BRANCH = 0x65 => "PROFILE_BRANCH",
PROFILE_CALL = 0x66 => "PROFILE_CALL",
@@ -326,6 +328,7 @@ mod tests {
assert_eq!(OpCode::MOV as u8, 0x0C);
assert_eq!(OpCode::SPILL as u8, 0x0D);
assert_eq!(OpCode::UNSPILL as u8, 0x0E);
assert_eq!(OpCode::CREATE_ARGUMENTS as u8, 0x63);
assert_eq!(OpCode::ADD.to_string(), "ADD");
assert_eq!(OpCode::COMPOUND_MEMBER_EXP.to_string(), "COMPOUND_MEMBER_EXP");
assert_eq!(OpCode::MOV.to_string(), "MOV");
+27
View File
@@ -803,6 +803,18 @@ impl Emitter {
}
}
ctx.own_bindings = self.collect_own_binding_names(&param_names, body_stmts);
// 自动声明 arguments 绑定(非箭头函数,且用户未显式声明同名标识符)。
// 先登记符号并纳入 own_bindings,使嵌套箭头引用 arguments 被识别为本函数
// 绑定(否则被当自由变量 → 子模块 upvalue 解析错位)。
let mut arguments_reg = None;
if !matches!(body_context, FunctionBodyContext::Arrow) && !ctx.own_bindings.contains("arguments") {
let reg = ctx.alloc_reg();
ctx.declare_initialized("arguments", reg, VariableDeclarationKind::Var, false)?;
ctx.own_bindings.insert("arguments".to_string());
arguments_reg = Some(reg);
}
ctx.captured_bindings = self.collect_captured_bindings(body_stmts, &param_defaults, &ctx.own_bindings);
// 自由变量分析:收集 upvalue 捕获(类方法也是普通函数,可捕获外层变量)。
if matches!(
@@ -818,6 +830,11 @@ impl Emitter {
);
}
// 创建 arguments 对象:指令须在默认参数求值前发出(默认参数可引用 arguments)。
if let Some(reg) = arguments_reg {
ctx.inst(Inst::create_arguments(Operand::Reg(reg)));
}
for spec in param_specs {
match spec {
ParamSpec::Pattern { synthetic_name, pattern, initializer } => {
@@ -856,6 +873,16 @@ impl Emitter {
}
}
// 被捕获的 arguments 绑定同样建 cell(与参数一致,须在默认值之后)。
if let (Some(reg), Some(&cell_idx)) = (arguments_reg, ctx.captured_bindings.get("arguments")) {
ctx.inst(Inst::new(
OpCode::MAKE_CELL,
Operand::Reg(reg),
Operand::Imm(cell_idx as u16),
Operand::None,
));
}
Ok(param_base)
}
+12 -1
View File
@@ -199,6 +199,7 @@ impl Inst {
| OpCode::VOID
| OpCode::NEW_OBJECT
| OpCode::NEW_ARRAY
| OpCode::CREATE_ARGUMENTS
| OpCode::NOP => {}
// 变量读写:LOAD_VAR/STORE_VAR/CELL_GET 读 a(None→0)
OpCode::LOAD_VAR | OpCode::STORE_VAR | OpCode::CELL_GET => {
@@ -256,7 +257,6 @@ impl Inst {
}
// 占位 opcode(emit 不产):按无 use 保守处理,不影响合法产物
OpCode::SWITCH_TABLE
| OpCode::PROFILE_TYPE
| OpCode::PROFILE_SHAPE
| OpCode::PROFILE_BRANCH
| OpCode::PROFILE_CALL
@@ -530,6 +530,17 @@ mod tests {
assert_eq!(inst.use_regs().as_slice(), &[1]);
}
#[test]
fn create_arguments_defs_rd_no_register_use_impure() {
// CREATE_ARGUMENTS 写 rd;实参从 spill 栈读取,无寄存器 use;
// 创建对象有观察副作用(分配的 arguments 可被外部观察到),不可删除。
let inst = Inst::create_arguments(Operand::Reg(6));
assert_eq!(inst.def_reg(), Some(6));
assert!(inst.use_regs().is_empty());
let f = IRFunction::new();
assert!(!inst.is_pure(&f));
}
#[test]
fn for_in_next_defs_rd_no_use() {
let inst = Inst::new(OpCode::FOR_IN_NEXT, Operand::Reg(0), Operand::None, Operand::None);
+13
View File
@@ -178,6 +178,12 @@ impl Inst {
Self::new(OpCode::CREATE_CLOSURE, dst, Operand::Imm(sub_idx), Operand::None)
}
/// 创建 arguments 对象:`dst = 当前帧的实参列表`。运行时从当前帧
/// (CallFrame 或 inline 同步调用)的实参区构建,无寄存器 use。
pub fn create_arguments(dst: Operand) -> Self {
Self::new(OpCode::CREATE_ARGUMENTS, dst, Operand::None, Operand::None)
}
// ── 跳转族:label 放 b 槽,offset 计算是 lowering 职责 ──
/// 无条件跳转。label 放 b 槽,offset 由 lowering 回填。
@@ -311,6 +317,13 @@ mod tests {
assert_eq!(cc.a, Operand::Imm(5));
assert_eq!(cc.b, Operand::None);
assert!(cc.ext.is_empty());
let ca = Inst::create_arguments(Operand::Reg(6));
assert_eq!(ca.op, OpCode::CREATE_ARGUMENTS);
assert_eq!(ca.rd, Operand::Reg(6));
assert_eq!(ca.a, Operand::None);
assert_eq!(ca.b, Operand::None);
assert!(ca.ext.is_empty());
}
#[test]
+11 -1
View File
@@ -238,7 +238,7 @@ impl Vm {
/// 当前执行函数的闭包对象:普通路径取 frames 栈顶帧,inline(sync 回调)路径
/// frames 被隔离为空,取 `inline_callee`。
fn current_callee(&self) -> Option<JsValue> {
pub(crate) fn current_callee(&self) -> Option<JsValue> {
self.frames.last().map(|f| f.callee).or(self.inline_callee)
}
@@ -465,6 +465,14 @@ impl Vm {
let saved_this = self.regs[254];
let saved_new_target = self.regs[255];
// 完整实参写入 spill 栈实参区(在帧的 spill 区之前),供 CREATE_ARGUMENTS 使用。
let args_base = self.spill_stack.len() as u32;
for i in 0..arg_count {
let src_reg = first_arg_reg.wrapping_add(i as u8) as usize;
self.spill_stack.push(self.regs[src_reg]);
}
let args_count = arg_count.min(u16::MAX as usize) as u16;
for i in 0..sub_n_args {
let src_reg = first_arg_reg.wrapping_add(i as u8) as usize;
self.regs[sub_param_base + i] = self.regs[src_reg];
@@ -487,6 +495,8 @@ impl Vm {
caller_reg_limit,
saved_reg_offset,
spill_offset: self.spill_stack.len() as u32,
arguments_base: args_base,
arguments_count: args_count,
saved_this,
saved_new_target,
callee: super_ctor,
+37 -1
View File
@@ -2,7 +2,7 @@ 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;
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::value::JsValue;
impl Vm {
@@ -132,6 +132,42 @@ impl Vm {
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 };
// 索引属性:按实参下标写入 shape 槽,属性描述符为默认(可写/可枚举/可配置)。
for i in 0..count as usize {
let si = self.kernel_core.perm_interner().intern(&i.to_string()).0;
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;
self.define_data_property(obj, length_si, JsValue::int(count as i32), PropAttributes::new(true, false, true))?;
// callee:当前执行函数,可写、不可枚举、可配置(严格模式应抛 TypeError,未支持)。
let callee_si = self.kernel_core.perm_interner().intern("callee").0;
let callee = self.current_callee().unwrap_or(JsValue::undefined());
self.define_data_property(obj, callee_si, callee, PropAttributes::new(true, false, true))?;
self.regs[rd] = JsValue::from_js_object(obj_ptr);
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_new_array(&mut self, rd: usize, instr: u32) {
let n = opcode::imm16(instr) as usize;
+2
View File
@@ -589,6 +589,8 @@ mod tests {
caller_reg_limit: 1,
saved_reg_offset: 0,
spill_offset: 0,
arguments_base: 0,
arguments_count: 0,
saved_this: JsValue::from_js_object(this_session),
saved_new_target: JsValue::from_js_object(child_session),
callee: JsValue::from_js_object(child_session),
+26 -2
View File
@@ -112,6 +112,10 @@ pub struct CallFrame {
pub saved_reg_offset: u32,
/// 记录本帧 spill 栈起始长度,作为 SPILL/UNSPILL 的帧边界基址(push 时快照)。
pub spill_offset: u32,
/// 本帧完整实参在 spill 栈的起始下标(帧恢复时随 spill 区一起截断丢弃)。
pub arguments_base: u32,
/// 实际传入的实参个数(可大于形参个数)。
pub arguments_count: u16,
pub saved_this: JsValue,
pub saved_new_target: JsValue,
pub callee: JsValue,
@@ -164,6 +168,8 @@ pub(crate) struct InlineSyncState {
pub(crate) spill_stack: Vec<JsValue>,
pub(crate) cell_stack: Vec<Vec<*mut Cell>>,
pub(crate) inline_callee: Option<JsValue>,
pub(crate) inline_args_base: u32,
pub(crate) inline_args_count: u16,
}
/// 基于寄存器的 JS 虚拟机:持有执行状态、寄存器文件、调用栈与 session 内存。
@@ -211,6 +217,10 @@ pub struct Vm {
pub(crate) root_reg_limit: u8,
pub(crate) active_reg_limit: u8,
pub(crate) native_call_depth: usize,
/// inline 同步调用(`call_bytecode_function_inline`,frames 为空)的实参区位置。
/// frames 非空时 CREATE_ARGUMENTS 优先读当前帧的实参区;此字段只服务内联路径。
pub(crate) inline_args_base: u32,
pub(crate) inline_args_count: u16,
/// `ordinary_get` 压入字节码 accessor 帧时设为 Some(target_reg)。
/// 调度循环检查该标志,跳过用调用结果写 `regs[target_reg]` —— 值改由 RETURN
/// 处理器交付。
@@ -822,12 +832,20 @@ impl Vm {
.map(|name| self.kernel_core.perm_interner().intern(name).0)
.unwrap_or(0);
// 完整实参写入 spill 栈实参区(在帧的 spill 区之前):CREATE_ARGUMENTS 据此
// 构建 arguments 对象,帧恢复时随 spill 区截断一起丢弃。
let args_base = self.spill_stack.len() as u32;
self.spill_stack.extend_from_slice(args);
let args_count = args.len().min(u16::MAX as usize) as u16;
self.frames.push(CallFrame {
return_addr: self.pc,
function_name,
caller_reg_limit,
saved_reg_offset,
spill_offset: self.spill_stack.len() as u32,
arguments_base: args_base,
arguments_count: args_count,
saved_this,
saved_new_target,
callee,
@@ -1144,6 +1162,10 @@ impl Vm {
self.dispatch_new_object(rd);
}
OpCode::CREATE_ARGUMENTS => {
self.dispatch_create_arguments(rd)?;
}
OpCode::NEW_ARRAY => {
self.dispatch_new_array(rd, instr);
}
@@ -1553,6 +1575,8 @@ mod tests {
caller_reg_limit: 2,
saved_reg_offset: 0,
spill_offset: 0,
arguments_base: 0,
arguments_count: 0,
saved_this: JsValue::undefined(),
saved_new_target: JsValue::undefined(),
callee: JsValue::undefined(),
@@ -1653,7 +1677,7 @@ mod tests {
fn unimplemented_profile_opcode_fails_explicitly() {
let module = CompiledModule {
bytecode: vec![
opcode::encode(opcode::OpCode::PROFILE_TYPE, 0, 0, 0),
opcode::encode(opcode::OpCode::PROFILE_SHAPE, 0, 0, 0),
opcode::encode(opcode::OpCode::HALT, 0, 0, 0),
],
n_registers: 1,
@@ -1661,7 +1685,7 @@ mod tests {
};
let mut vm = Vm::new();
let err = vm.run(&module).expect_err("unimplemented opcode should fail explicitly");
assert_eq!(err, "opcode PROFILE_TYPE not yet implemented");
assert_eq!(err, "opcode PROFILE_SHAPE not yet implemented");
}
#[test]
+10
View File
@@ -113,6 +113,14 @@ impl Vm {
let saved_this = self.regs[254];
let saved_new_target = self.regs[255];
// 完整实参写入 spill 栈实参区(在帧的 spill 区之前),供 CREATE_ARGUMENTS 使用。
let args_base = self.spill_stack.len() as u32;
for i in 0..arg_count {
let src_reg = first_arg_reg.wrapping_add(i as u8) as usize;
self.spill_stack.push(self.regs[src_reg]);
}
let args_count = arg_count.min(u16::MAX as usize) as u16;
for i in 0..sub_n_args {
let src_reg = first_arg_reg.wrapping_add(i as u8) as usize;
self.regs[sub_param_base + i] = self.regs[src_reg];
@@ -137,6 +145,8 @@ impl Vm {
caller_reg_limit,
saved_reg_offset,
spill_offset: self.spill_stack.len() as u32,
arguments_base: args_base,
arguments_count: args_count,
saved_this,
saved_new_target,
callee: constructor,
+8
View File
@@ -81,6 +81,8 @@ impl Vm {
spill_stack: std::mem::take(&mut self.spill_stack),
cell_stack: std::mem::take(&mut self.cell_stack),
inline_callee: self.inline_callee,
inline_args_base: self.inline_args_base,
inline_args_count: self.inline_args_count,
});
self.regs = [JsValue::undefined(); 256];
@@ -91,6 +93,10 @@ impl Vm {
self.root_reg_limit = self.active_reg_limit;
self.cell_stack.push(Vec::with_capacity(sub.cells_needed as usize));
self.inline_callee = Some(callee);
// inline 同步调用无 CallFrame:完整实参写入 spill 栈实参区,供 CREATE_ARGUMENTS 读取。
self.inline_args_base = self.spill_stack.len() as u32;
self.spill_stack.extend_from_slice(args);
self.inline_args_count = args.len().min(u16::MAX as usize) as u16;
for i in 0..sub.n_args as usize {
self.regs[sub.param_base as usize + i] = args.get(i).copied().unwrap_or(JsValue::undefined());
}
@@ -136,6 +142,8 @@ impl Vm {
self.spill_stack = saved.spill_stack;
self.cell_stack = saved.cell_stack;
self.inline_callee = saved.inline_callee;
self.inline_args_base = saved.inline_args_base;
self.inline_args_count = saved.inline_args_count;
result
}
+4
View File
@@ -46,6 +46,8 @@ impl Vm {
root_reg_limit: 0,
active_reg_limit: 0,
native_call_depth: 0,
inline_args_base: 0,
inline_args_count: 0,
accessor_frame_target_reg: None,
inline_callee: None,
gc_state: GcState {
@@ -109,6 +111,8 @@ impl Vm {
root_reg_limit: 0,
active_reg_limit: 0,
native_call_depth: 0,
inline_args_base: 0,
inline_args_count: 0,
accessor_frame_target_reg: None,
inline_callee: None,
gc_state: GcState {
+112
View File
@@ -0,0 +1,112 @@
//! arguments 对象执行语义测试:实参个数/索引读取、箭头函数词法继承、
//! 默认参数引用、方法调用、闭包捕获、显式声明屏蔽、嵌套函数调用帧边界。
use oxide_compiler::compiler::Compiler;
use oxide_types::value::JsValue;
use oxide_vm::vm::Vm;
fn eval(vm: &mut Vm, source: &str) -> Result<JsValue, String> {
let allocator = oxide_parser::Allocator::default();
let program = oxide_parser::parse(&allocator, source).map_err(|e| format!("Parse error: {:?}", e))?;
let module = Compiler::new().compile(&program).map_err(|e| format!("Compile error: {}", e))?;
vm.run(&module)
}
#[test]
fn arguments_length_counts_actual_args() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(){return arguments.length}; f(1,2,3)").unwrap();
assert_eq!(result.as_int(), 3);
}
#[test]
fn arguments_index_reads_first_arg() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(a){return arguments[0]}; f(42)").unwrap();
assert_eq!(result.as_int(), 42);
}
#[test]
fn arguments_index_reads_extra_arg() {
// 实参多于形参:多余实参仍可从 arguments 读取。
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(a){return arguments[1]}; f(1, 99)").unwrap();
assert_eq!(result.as_int(), 99);
}
#[test]
fn arrow_inherits_outer_arguments() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(){return (()=>arguments.length)()}; f(1,2)").unwrap();
assert_eq!(result.as_int(), 2);
}
#[test]
fn default_param_can_read_arguments() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(a=arguments.length){return a}; f()").unwrap();
assert_eq!(result.as_int(), 0);
}
#[test]
fn method_call_arguments() {
let mut vm = Vm::new();
let result = eval(&mut vm, "var o={m:function(){return arguments.length}}; o.m(5,6)").unwrap();
assert_eq!(result.as_int(), 2);
}
#[test]
fn closure_captures_arguments() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(){var g=()=>arguments[0]; return g()}; f('x')").unwrap();
assert_eq!(vm.lookup_str(result).as_deref(), Some("x"));
}
#[test]
fn arguments_not_created_when_param_named_arguments() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(arguments){return arguments}; f(7)").unwrap();
assert_eq!(result.as_int(), 7);
}
#[test]
fn arguments_not_created_when_var_shadowed() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function f(){var arguments=9; return arguments}; f(1)").unwrap();
assert_eq!(result.as_int(), 9);
}
#[test]
fn arguments_object_has_length_and_callee() {
let mut vm = Vm::new();
let result =
eval(&mut vm, "function f(){return arguments.callee === f && arguments.length === 2}; f(1,2)").unwrap();
assert_eq!(result, JsValue::bool(true));
}
#[test]
fn arguments_frame_boundary_across_nested_calls() {
// 子调用返回后父函数的 arguments 实参区仍完好(帧恢复截断不越界)。
let mut vm = Vm::new();
let result = eval(&mut vm, "function g(){return 1} function f(){return arguments.length + g()}; f(4,5,6)").unwrap();
assert!((result.as_double() - 4.0).abs() < 0.0001);
}
#[test]
fn nested_plain_function_has_own_arguments() {
// 非箭头嵌套函数创建自己的 arguments(空实参),不继承外层。
let mut vm = Vm::new();
let result = eval(
&mut vm,
"function f(){function g(){return arguments.length} return g() === 0 && arguments.length === 1}; f(8)",
)
.unwrap();
assert_eq!(result, JsValue::bool(true));
}
#[test]
fn new_expression_target_gets_arguments() {
let mut vm = Vm::new();
let result = eval(&mut vm, "function Ctor(){this.n=arguments.length} var c=new Ctor(1,2,3); c.n").unwrap();
assert_eq!(result.as_int(), 3);
}