diff --git a/Cargo.lock b/Cargo.lock index bf6674d3..34eadae1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.13.19" +version = "0.13.20" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.13.19" +version = "0.13.20" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.13.19" +version = "0.13.20" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.13.19" +version = "0.13.20" dependencies = [ "ansi_term", "bumpalo", @@ -1226,7 +1226,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.13.19" +version = "0.13.20" dependencies = [ "lru", "oxide_bytecode", @@ -1235,7 +1235,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.13.19" +version = "0.13.20" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1255,7 +1255,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.13.19" +version = "0.13.20" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1269,7 +1269,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.13.19" +version = "0.13.20" dependencies = [ "num-bigint", "oxc_allocator", @@ -1284,7 +1284,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.13.19" +version = "0.13.20" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1296,7 +1296,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.13.19" +version = "0.13.20" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1305,7 +1305,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.13.19" +version = "0.13.20" dependencies = [ "bumpalo", "dashmap", @@ -1319,7 +1319,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.13.19" +version = "0.13.20" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1331,7 +1331,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.13.19" +version = "0.13.20" dependencies = [ "tracing", "tracing-appender", @@ -1340,7 +1340,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.13.19" +version = "0.13.20" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1355,7 +1355,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.13.19" +version = "0.13.20" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1371,7 +1371,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.13.19" +version = "0.13.20" dependencies = [ "num-bigint", "num-traits", @@ -1384,7 +1384,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.13.19" +version = "0.13.20" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1402,7 +1402,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.13.19" +version = "0.13.20" dependencies = [ "bumpalo", "dashmap", @@ -1415,7 +1415,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.13.19" +version = "0.13.20" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index f555163d..340605d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ members = [ ] [workspace.package] -version = "0.13.19" +version = "0.13.20" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_bytecode/src/module.rs b/crates/oxide_bytecode/src/module.rs index f554e094..57157f16 100644 --- a/crates/oxide_bytecode/src/module.rs +++ b/crates/oxide_bytecode/src/module.rs @@ -66,6 +66,9 @@ pub struct CompiledModule { pub captured_this_const_idx: u16, /// 由赋值上下文推断的函数名,在变量声明 / 对象属性赋值点设置。 pub function_name: Option, + /// 函数名不可写绑定的寄存器(帧槽写入目标):压帧时把 callee 函数对象写入该槽。 + /// `None` 表示无名绑定(匿名函数 / 形参或 var 同名抑制 / arguments 特例)。 + pub function_name_reg: Option, /// 函数 `length` 属性值:第一个带默认值/解构默认的形参之前的形参数(rest 不计)。 pub function_length: u32, /// 形参列表是否 simple(全部无初始值标识符且无 rest):arguments 对象 @@ -110,6 +113,7 @@ impl CompiledModule { is_strict: false, captured_this_const_idx: 0, function_name: None, + function_name_reg: None, function_length: 0, has_simple_params: false, is_class_constructor: false, @@ -146,6 +150,7 @@ impl Clone for CompiledModule { is_strict: self.is_strict, captured_this_const_idx: self.captured_this_const_idx, function_name: self.function_name.clone(), + function_name_reg: self.function_name_reg, function_length: self.function_length, has_simple_params: self.has_simple_params, is_class_constructor: self.is_class_constructor, diff --git a/crates/oxide_emit/src/class/emit.rs b/crates/oxide_emit/src/class/emit.rs index 369b6fc2..9d983176 100644 --- a/crates/oxide_emit/src/class/emit.rs +++ b/crates/oxide_emit/src/class/emit.rs @@ -271,6 +271,7 @@ impl Emitter { &field_value_exprs, &extra_upvalue_names, true, + None, )? } else { let mut module = self.compile_function_body_with_field_hooks( @@ -285,6 +286,7 @@ impl Emitter { &field_value_exprs, &extra_upvalue_names, true, + None, )?; if is_derived { module.insts.clear(); diff --git a/crates/oxide_emit/src/class/method.rs b/crates/oxide_emit/src/class/method.rs index d7a9e2b0..991db92a 100644 --- a/crates/oxide_emit/src/class/method.rs +++ b/crates/oxide_emit/src/class/method.rs @@ -138,6 +138,7 @@ impl Emitter { method_value.generator, method_value.r#async, true, + None, )?; ctx.in_instance_method = saved_instance; ctx.in_static_method = saved_static; diff --git a/crates/oxide_emit/src/compile_ctx.rs b/crates/oxide_emit/src/compile_ctx.rs index 4770b173..446d52bb 100644 --- a/crates/oxide_emit/src/compile_ctx.rs +++ b/crates/oxide_emit/src/compile_ctx.rs @@ -208,6 +208,14 @@ pub struct CompileCtx { /// 运行时与模块 flat_id 组成模板对象缓存键,保证同一编译树同 site 恒返回 /// 同一对象、不同编译树(eval 每次编译)互不共享。 pub(crate) next_template_site: u32, + /// 本函数登记的函数名(非抑制面):`assemble_ir` 回写到 IR 供调试与 + /// 帧槽写入判定。抑制面(形参/var 同名)不登记名绑定,此字段仍记名 + /// 供 `instantiate_var_bindings` 排除入口 undefined 写。 + pub(crate) function_name: Option, + /// 函数名不可写绑定的寄存器(帧槽写入目标):压帧时把 callee 函数对象 + /// 写入该槽。非抑制面在名登记时分配;抑制面(var 同名)在 + /// `instantiate_var_bindings` 回填为 var 槽寄存器。 + pub(crate) function_name_reg: Option, } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -304,6 +312,8 @@ impl CompileCtx { module_self_aliases: HashMap::new(), module_alias_pairs: Vec::new(), next_template_site: 0, + function_name: None, + function_name_reg: None, } } @@ -1004,6 +1014,7 @@ impl CompileCtx { is_strict: self.is_strict, captured_this_const_idx: 0, function_name: None, + function_name_reg: self.function_name_reg, function_length: self.function_length, has_simple_params: self.has_simple_params, is_top_level: parent_ctx.is_none(), diff --git a/crates/oxide_emit/src/expr/function/mod.rs b/crates/oxide_emit/src/expr/function/mod.rs index 4d2db5c7..2b412aca 100644 --- a/crates/oxide_emit/src/expr/function/mod.rs +++ b/crates/oxide_emit/src/expr/function/mod.rs @@ -47,6 +47,7 @@ impl Emitter { is_expr_body, true, arrow.has_use_strict_directive(), + None, )? } else { self.compile_function_body( @@ -56,6 +57,7 @@ impl Emitter { is_expr_body, true, arrow.has_use_strict_directive(), + None, )? }; sub_module.is_arrow = true; @@ -98,14 +100,15 @@ impl Emitter { let body_stmts: &[Statement] = if let Some(body) = &fe.body { &body.statements } else { &[] }; let own_strict = fe.has_use_strict_directive(); + let fn_name = fe.id.as_ref().map(|id| id.name.as_str()); let mut sub_module = if fe.generator && fe.r#async { - self.compile_async_generator_body(¶m_names, body_stmts, ctx, own_strict)? + self.compile_async_generator_body(¶m_names, body_stmts, ctx, own_strict, fn_name)? } else if fe.generator { - self.compile_generator_body(¶m_names, body_stmts, ctx, own_strict)? + self.compile_generator_body(¶m_names, body_stmts, ctx, own_strict, fn_name)? } else if fe.r#async { - self.compile_async_body(¶m_names, body_stmts, ctx, false, false, own_strict)? + self.compile_async_body(¶m_names, body_stmts, ctx, false, false, own_strict, fn_name)? } else { - self.compile_function_body(¶m_names, body_stmts, ctx, false, false, own_strict)? + self.compile_function_body(¶m_names, body_stmts, ctx, false, false, own_strict, fn_name)? }; if let Some(id) = &fe.id { sub_module.function_name = Some(id.name.to_string()); diff --git a/crates/oxide_emit/src/function_body.rs b/crates/oxide_emit/src/function_body.rs index 21cf816f..43c1ca9b 100644 --- a/crates/oxide_emit/src/function_body.rs +++ b/crates/oxide_emit/src/function_body.rs @@ -141,10 +141,12 @@ impl Emitter { /// /// `is_expression_body` 为 true(箭头表达式体)时返回最后一个表达式的值, /// 否则返回 undefined。`is_arrow` 控制 super 相关标志的继承:箭头函数词法 - /// 继承外层 super,普通函数重置 super 作用域。 + /// 继承外层 super,普通函数重置 super 作用域。`name` 为函数名(具名函数 + /// 声明/表达式传名,箭头与匿名传 `None`),用于函数名不可写绑定登记。 + #[allow(clippy::too_many_arguments)] pub(crate) fn compile_function_body<'a>( &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, - is_expression_body: bool, is_arrow: bool, own_strict: bool, + is_expression_body: bool, is_arrow: bool, own_strict: bool, name: Option<&str>, ) -> Result { self.compile_function_body_with_flags( param_specs, @@ -155,12 +157,14 @@ impl Emitter { false, false, own_strict, + name, ) } /// 编译函数体并显式指定生成器标志(`function*` 走此入口)。 pub(crate) fn compile_generator_body<'a>( - &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, own_strict: bool, + &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, + own_strict: bool, name: Option<&str>, ) -> Result { self.compile_function_body_with_flags( param_specs, @@ -171,6 +175,7 @@ impl Emitter { true, false, own_strict, + name, ) } @@ -178,16 +183,20 @@ impl Emitter { /// 同时标记 `is_generator` 与 `is_async`,VM 调用时按异步生成器协议执行 /// (next 返回 Promise,yield 挂起与 await 挂起共存)。 pub(crate) fn compile_async_generator_body<'a>( - &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, own_strict: bool, + &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, + own_strict: bool, name: Option<&str>, ) -> Result { - self.compile_function_body_with_flags(param_specs, body_stmts, parent_ctx, false, false, true, true, own_strict) + self.compile_function_body_with_flags( + param_specs, body_stmts, parent_ctx, false, false, true, true, own_strict, name, + ) } /// 编译异步函数体(`async function` / async 箭头走此入口):`is_async` 使 /// `assemble_ir` 标记模块,VM 调用时按异步函数协议执行。 + #[allow(clippy::too_many_arguments)] pub(crate) fn compile_async_body<'a>( &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, - is_expression_body: bool, is_arrow: bool, own_strict: bool, + is_expression_body: bool, is_arrow: bool, own_strict: bool, name: Option<&str>, ) -> Result { self.compile_function_body_with_flags( param_specs, @@ -198,6 +207,7 @@ impl Emitter { false, true, own_strict, + name, ) } @@ -205,6 +215,7 @@ impl Emitter { fn compile_function_body_with_flags<'a>( &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, is_expression_body: bool, is_arrow: bool, is_generator: bool, is_async: bool, own_strict: bool, + name: Option<&str>, ) -> Result { let body_context = if is_arrow { FunctionBodyContext::Arrow @@ -221,6 +232,7 @@ impl Emitter { is_generator, is_async, own_strict, + name, ) } @@ -228,7 +240,7 @@ impl Emitter { pub(crate) fn compile_function_body_with_bindings_gen<'a>( &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, is_expression_body: bool, extra_bindings: &[(&str, u32)], body_context: FunctionBodyContext, - is_generator: bool, is_async: bool, own_strict: bool, + is_generator: bool, is_async: bool, own_strict: bool, name: Option<&str>, ) -> Result { self.compile_function_body_with_field_hooks_gen( param_specs, @@ -244,6 +256,7 @@ impl Emitter { is_generator, is_async, own_strict, + name, ) } @@ -259,7 +272,7 @@ impl Emitter { &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, is_expression_body: bool, extra_bindings: &[(&str, u32)], body_context: FunctionBodyContext, emit_fields: Option, fields_after_super: bool, extra_capture_exprs: &[&'a Expression<'a>], - extra_upvalue_names: &[(&str, u8)], own_strict: bool, + extra_upvalue_names: &[(&str, u8)], own_strict: bool, name: Option<&str>, ) -> Result where E: FnMut(&Emitter, &mut CompileCtx) -> Result<(), String>, @@ -278,6 +291,7 @@ impl Emitter { false, false, own_strict, + name, ) } @@ -287,6 +301,7 @@ impl Emitter { is_expression_body: bool, extra_bindings: &[(&str, u32)], body_context: FunctionBodyContext, mut emit_fields: Option, fields_after_super: bool, extra_capture_exprs: &[&'a Expression<'a>], extra_upvalue_names: &[(&str, u8)], is_generator: bool, is_async: bool, own_strict: bool, + name: Option<&str>, ) -> Result where E: FnMut(&Emitter, &mut CompileCtx) -> Result<(), String>, @@ -368,6 +383,7 @@ impl Emitter { // 父层 cell 索引在子帧无效:子帧经 upvalue 路径访问父捕获 // 绑定,不复制索引(复制会让解析点误发子帧 cell 表读)。 cell_idx: None, + non_writable: binding.non_writable, }, ); inherited_reg_start = inherited_reg_start.max(binding.reg.saturating_add(1)); @@ -394,6 +410,7 @@ impl Emitter { lexical: false, predeclared: false, cell_idx: None, + non_writable: false, }, ); inherited_reg_start = inherited_reg_start.max(reg.saturating_add(1)); @@ -411,6 +428,7 @@ impl Emitter { body_context, extra_capture_exprs, extra_upvalue_names, + name, )?; // 实例字段 computed key 数组所在 upvalue 下标,供字段初始化 emit 定位。 ctx.field_keys_uv = ctx @@ -458,6 +476,22 @@ impl Emitter { // MAKE_CELL(undefined) 实例化,此处只补未捕获名。 self.instantiate_var_bindings(body_stmts, &mut ctx); + // 被捕获的函数名绑定同样建 cell(MAKE_CELL):值源是帧槽写入已置位的 + // 名槽寄存器(函数对象),非 undefined。名槽寄存器此时已知(非抑制面 + // 在名登记时分配、抑制面在 instantiate_var_bindings 回填)。 + if let Some(fn_name) = ctx.function_name.as_deref() { + if let Some(&cell_idx) = ctx.captured_bindings.get(fn_name) { + if let Some(name_reg) = ctx.function_name_reg { + ctx.inst(Inst::new( + OpCode::MAKE_CELL, + Operand::Reg(name_reg), + Operand::Imm(cell_idx as u16), + Operand::None, + )); + } + } + } + // 函数体是释放作用域:仅当体含 using 声明时发 mark/pop(无 using 的体 // 不发,防早退 return 跳过 POP 留未配对水位错乱外层对齐)。 let has_using = Self::scope_contains_using(body_stmts); @@ -596,12 +630,18 @@ impl Emitter { true } - /// 参数 prologue:函数作用域 + 参数声明/解构 + 闭包捕获与 upvalue 分析。返回 param_base。 + /// 参数 prologue:函数作用域 + 参数声明/解构 + 函数名不可写绑定登记 + + /// 闭包捕获与 upvalue 分析。返回 param_base。 + /// + /// `name` 为函数名(具名函数声明/表达式传名,箭头与匿名传 `None`): + /// 非抑制面在函数作用域登记不可写绑定并分配名槽寄存器(帧槽写入目标); + /// 抑制面(形参/var 同名、arguments 特例)不建名绑定,var 同名时名槽 + /// 回填到 var 槽寄存器(`instantiate_var_bindings` 处)。 #[allow(clippy::too_many_arguments)] fn emit_params_prologue<'a>( &self, param_specs: &[ParamSpec<'a>], body_stmts: &[Statement<'a>], parent_ctx: &CompileCtx, ctx: &mut CompileCtx, body_context: FunctionBodyContext, extra_capture_exprs: &[&'a Expression<'a>], - extra_upvalue_names: &[(&str, u8)], + extra_upvalue_names: &[(&str, u8)], name: Option<&str>, ) -> Result { ctx.push_scope_with_kind(ScopeKind::FunctionScope); let param_base = ctx.next_reg; @@ -655,6 +695,48 @@ impl Emitter { arguments_reg = Some(reg); } + // 函数名不可写绑定(15.2.10.1 步 m):函数名在函数作用域登记为不可写 + // 绑定,帧槽写入把它初始化为函数对象。抑制面(形参同名 / 体内容器 var + // 同名 / arguments 对象占位)不建名绑定:前两者名解析到参数/var 绑定, + // 后者 arguments 绑定占位(var 同名时由帧槽写入初始化为函数对象)。 + // 名登记须先于 set_captured_bindings:嵌套函数捕获名分析依赖 own_bindings + // 完整(名被捕获时须识别为本函数绑定并建 cell)。 + if let Some(fn_name) = name { + let var_names = collect_var_binding_names(body_stmts); + let suppressed = ctx.param_names.contains(fn_name) + || var_names.contains(fn_name) + || (fn_name == "arguments" && arguments_reg.is_some()); + if !suppressed { + let reg = ctx.alloc_reg(); + ctx.scopes + .symbols + .scopes + .last_mut() + .expect("函数作用域已压入") + .bindings + .insert( + fn_name.to_string(), + Binding { + reg, + initialized: true, + is_const: false, + lexical: false, + predeclared: false, + cell_idx: None, + non_writable: true, + }, + ); + ctx.own_bindings.insert(fn_name.to_string()); + ctx.function_name = Some(fn_name.to_string()); + ctx.function_name_reg = Some(reg); + } else if var_names.contains(fn_name) { + // 抑制面:var 绑定持名槽,帧槽写入把函数对象写入该 var 槽。 + // var 绑定此时未声明(predeclare_var_declarations 在后),名槽 + // 寄存器在 instantiate_var_bindings 回填。 + ctx.function_name = Some(fn_name.to_string()); + } + } + // 字段初始化表达式(值表达式)与参数默认值一并纳入捕获分析。 let mut capture_exprs: Vec<&oxide_parser::Expression> = param_defaults.clone(); capture_exprs.extend_from_slice(extra_capture_exprs); @@ -844,6 +926,9 @@ impl Emitter { .into_iter() .filter(|n| !param_names.contains(&n.as_str()) && n != "arguments") .filter(|n| ctx.captured_bindings.contains_key(n)) + // 函数名(抑制面 var 同名)由专属 MAKE_CELL 处理(值源是帧槽写入的 + // 名槽寄存器,非 undefined),此处排除以免误发 undefined 源。 + .filter(|n| Some(n.as_str()) != ctx.function_name.as_deref()) .collect(); // HashSet 迭代序带随机种子,排序后入口 MAKE_CELL 发射序跨进程稳定。 var_names.sort(); @@ -926,6 +1011,17 @@ impl Emitter { } } + // 抑制面:函数名是 var 绑定,帧槽写入把它初始化为函数对象,入口 undefined + // 写须排除该名(否则覆盖帧槽写入的函数对象),并回填名槽寄存器供帧槽写入。 + // 非抑制面名绑定不是 var(不在 name_set 内),remove 返回 false 不回填。 + if let Some(fn_name) = ctx.function_name.as_deref() { + if name_set.remove(fn_name) { + if let Some(reg) = ctx.scopes.symbols.lookup_any(fn_name) { + ctx.function_name_reg = Some(reg); + } + } + } + let mut names: Vec = name_set .into_iter() .filter(|n| !ctx.param_names.contains(n) && n != "arguments") diff --git a/crates/oxide_emit/src/stmt/declaration/function_decl.rs b/crates/oxide_emit/src/stmt/declaration/function_decl.rs index beb94783..2a860c27 100644 --- a/crates/oxide_emit/src/stmt/declaration/function_decl.rs +++ b/crates/oxide_emit/src/stmt/declaration/function_decl.rs @@ -152,13 +152,13 @@ impl Emitter { let body_stmts: &[Statement] = if let Some(body) = &fd.body { &body.statements } else { &[] }; let own_strict = fd.has_use_strict_directive(); let mut sub_module = if fd.generator && fd.r#async { - self.compile_async_generator_body(¶m_names, body_stmts, ctx, own_strict)? + self.compile_async_generator_body(¶m_names, body_stmts, ctx, own_strict, Some(name))? } else if fd.generator { - self.compile_generator_body(¶m_names, body_stmts, ctx, own_strict)? + self.compile_generator_body(¶m_names, body_stmts, ctx, own_strict, Some(name))? } else if fd.r#async { - self.compile_async_body(¶m_names, body_stmts, ctx, false, false, own_strict)? + self.compile_async_body(¶m_names, body_stmts, ctx, false, false, own_strict, Some(name))? } else { - self.compile_function_body(¶m_names, body_stmts, ctx, false, false, own_strict)? + self.compile_function_body(¶m_names, body_stmts, ctx, false, false, own_strict, Some(name))? }; sub_module.function_name = Some(name.to_string()); ctx.nested.push(sub_module); diff --git a/crates/oxide_emit/src/symbol_table.rs b/crates/oxide_emit/src/symbol_table.rs index 7b88c61e..41f61109 100644 --- a/crates/oxide_emit/src/symbol_table.rs +++ b/crates/oxide_emit/src/symbol_table.rs @@ -34,6 +34,9 @@ pub(crate) struct Binding { /// 函数级绑定由捕获分析的名字排序分配回填;块级遮蔽绑定在预声明点 /// 追加新索引。解析点按绑定实例取索引,同名多绑定各持各的 cell。 pub(crate) cell_idx: Option, + /// 函数名不可写绑定标志(与 `is_const` 正交):函数名绑定在 sloppy 模式 + /// 写静默失败、strict 抛 TypeError,模式相关故独立于 const 的无条件抛错。 + pub(crate) non_writable: bool, } /// 作用域符号表:名字 → 寄存器号/初始化状态/const 标志。 @@ -120,6 +123,7 @@ impl SymbolTable { lexical: !matches!(kind, VariableDeclarationKind::Var), predeclared: false, cell_idx: None, + non_writable: false, }, ); Ok(()) @@ -196,6 +200,7 @@ impl SymbolTable { lexical: false, predeclared: false, cell_idx: None, + non_writable: false, }, ); reg_for_new @@ -214,6 +219,19 @@ impl SymbolTable { false } + /// 返回已初始化绑定是否为函数名不可写绑定(用于写路径守卫);未初始化/未找到返回 false。 + pub fn lookup_non_writable(&self, name: &str) -> bool { + for scope in self.scopes.iter().rev() { + if let Some(b) = scope.bindings.get(name) { + if b.initialized { + return b.non_writable; + } + return false; + } + } + false + } + /// 声明并直接标记为已初始化;绑定已存在时仅补初始化标志(供预声明路径)。 pub fn declare_initialized( &mut self, name: &str, reg: u32, kind: VariableDeclarationKind, is_const: bool, @@ -238,6 +256,7 @@ impl SymbolTable { lexical: !matches!(kind, VariableDeclarationKind::Var), predeclared: false, cell_idx: None, + non_writable: false, }, ); Ok(()) @@ -252,6 +271,7 @@ impl SymbolTable { lexical: false, predeclared: false, cell_idx: None, + non_writable: false, }); } @@ -278,6 +298,7 @@ impl SymbolTable { lexical: !matches!(kind, VariableDeclarationKind::Var), predeclared: true, cell_idx: None, + non_writable: false, }, ); Ok(()) @@ -339,6 +360,7 @@ impl SymbolTable { lexical: true, predeclared: false, cell_idx: None, + non_writable: false, }, ); self.aliases.insert(local.to_string(), base); diff --git a/crates/oxide_ir/src/lib.rs b/crates/oxide_ir/src/lib.rs index 17b44b1b..07339a5a 100644 --- a/crates/oxide_ir/src/lib.rs +++ b/crates/oxide_ir/src/lib.rs @@ -62,6 +62,9 @@ pub struct IRFunction { pub is_strict: bool, pub captured_this_const_idx: u16, pub function_name: Option, + /// 函数名不可写绑定的寄存器(帧槽写入目标):压帧时把 callee 函数对象写入该槽。 + /// `None` 表示无名绑定(匿名函数 / 形参或 var 同名抑制 / arguments 特例)。 + pub function_name_reg: Option, /// 函数 `length` 属性值:第一个带默认值/解构默认的形参之前的形参数(rest 不计)。 pub function_length: u32, /// 形参列表是否 simple(全部无初始值标识符且无 rest):arguments 对象 @@ -99,6 +102,7 @@ impl IRFunction { is_strict: false, captured_this_const_idx: 0, function_name: None, + function_name_reg: None, function_length: 0, has_simple_params: false, is_top_level: false, diff --git a/crates/oxide_ir/src/lower.rs b/crates/oxide_ir/src/lower.rs index 2213b447..3d6d5707 100644 --- a/crates/oxide_ir/src/lower.rs +++ b/crates/oxide_ir/src/lower.rs @@ -89,6 +89,7 @@ pub fn lower(f: &IRFunction) -> Result { is_strict: f.is_strict, captured_this_const_idx: f.captured_this_const_idx, function_name: f.function_name.clone(), + function_name_reg: f.function_name_reg, function_length: f.function_length, has_simple_params: f.has_simple_params, is_class_constructor: f.is_class_constructor, diff --git a/crates/oxide_regalloc/src/finish.rs b/crates/oxide_regalloc/src/finish.rs index 7789285f..79fc9ca4 100644 --- a/crates/oxide_regalloc/src/finish.rs +++ b/crates/oxide_regalloc/src/finish.rs @@ -58,6 +58,15 @@ pub(super) fn run(f: &mut IRFunction, map: &AllocMap) { // 直接槽引用(class field 计算键等)的 escaped vreg 已由预着色恒等保留。 // 注:cell 捕获变量的父 vreg 可被 RegAlloc 移动(MAKE_CELL 后值入 cell,与寄存器无关), // 此处不做恒等断言。 + + // 函数名不可写绑定寄存器回写:vreg → 物理槽(与 builtin_reg_map 同口径)。 + // 映射缺失(名槽无字节码引用,帧槽写入为死写)时置 None,VM 侧跳过写入。 + if let Some(vreg) = f.function_name_reg { + f.function_name_reg = match map.map.get(&vreg) { + Some(Alloc::Phys(p)) => Some(*p), + _ => None, + }; + } } #[cfg(test)] diff --git a/crates/oxide_vm/src/vm/call.rs b/crates/oxide_vm/src/vm/call.rs index ec2e25c7..cd5293f4 100644 --- a/crates/oxide_vm/src/vm/call.rs +++ b/crates/oxide_vm/src/vm/call.rs @@ -293,6 +293,13 @@ impl Vm { }; self.regs[sub_param_base + i] = v; } + // 函数名不可写绑定帧槽写入:把 callee 函数对象写入名槽寄存器(压帧时置位, + // 函数体入口的 MAKE_CELL / 读取据此取函数对象)。无名绑定(None)不写。 + // 须在 this 绑定之前:名槽寄存器与 this 槽(254)互不重叠,但集中在此 + // 保持帧初始化顺序清晰。 + if let Some(name_reg) = sub.function_name_reg { + self.regs[name_reg as usize] = callee; + } // this 绑定:箭头函数恒用词法捕获;sloppy 普通函数 this 为 null/undefined 时 // 替换为全局对象,其余原始值经 ToObject 盒装(ECMA-262 10.4.3);严格模式 // this 原样保留。 diff --git a/crates/oxide_vm/tests/function_tests.rs b/crates/oxide_vm/tests/function_tests.rs index 3ef96797..cbca148c 100644 --- a/crates/oxide_vm/tests/function_tests.rs +++ b/crates/oxide_vm/tests/function_tests.rs @@ -17,6 +17,15 @@ fn assert_num(result: JsValue, expected: f64) { assert!((actual - expected).abs() < 0.0001, "expected {expected}, got {actual}"); } +/// 判断值是否为函数对象:经对象指针解出 header 函数标志,非对象返回假。 +fn is_function_value(v: JsValue) -> bool { + let ptr = v.as_js_object_ptr(); + if ptr.is_null() { + return false; + } + unsafe { (*ptr).is_function() } +} + // --- Function Declaration Basics --- #[test] @@ -218,3 +227,61 @@ fn function_default_prototype_accepts_member_assignment() { let rendered = vm.lookup_str(result).unwrap_or_default(); assert_eq!(rendered, "ok"); } + +// ── 函数名不可写局部绑定(函数作用域内读回函数对象)── + +#[test] +fn fn_name_binding_readable() { + let mut vm = Vm::new(); + // 函数名登记进函数作用域,体内裸读 f 解析到名绑定,读回函数对象本身。 + let result = eval(&mut vm, "function f() { return f; } f()").unwrap(); + assert!(is_function_value(result), "expected the function itself, got: {:?}", result); +} + +#[test] +fn fn_name_binding_shadows_outer_var() { + let mut vm = Vm::new(); + // 外层 var f 持 'x';函数表达式名绑定 f 在函数作用域内优先,体内读回函数对象而非 'x'。 + let result = eval(&mut vm, "var f = 'x'; var g = function f() { return typeof f; }; g()").unwrap(); + assert_eq!(vm.lookup_str(result).unwrap_or_default(), "function"); +} + +#[test] +fn fn_name_var_suppression() { + let mut vm = Vm::new(); + // 体内容器 var f 同名:不建不可写绑定,var 绑定由帧槽写入初始化为函数对象。 + let result = eval(&mut vm, "function f() { var f; return f; } f()").unwrap(); + assert!(is_function_value(result), "expected the function, got: {:?}", result); +} + +#[test] +fn fn_name_param_priority() { + let mut vm = Vm::new(); + // 形参同名:形参绑定持有实参,名绑定被抑制,读回实参值。 + let result = eval(&mut vm, "function f(f) { return f; } f(1)").unwrap(); + assert_eq!(result.as_int(), 1); +} + +#[test] +fn fn_name_capture_side() { + let mut vm = Vm::new(); + // 嵌套函数捕获外层名绑定(upvalue),内层读回外层函数对象。 + let result = eval(&mut vm, "function f() { return function() { return f; }; } f()()").unwrap(); + assert!(is_function_value(result), "expected the outer function, got: {:?}", result); +} + +#[test] +fn fn_name_arguments_special_case() { + let mut vm = Vm::new(); + // 名等于 arguments 且 arguments 对象已建:抑制名绑定,arguments 解析到 arguments 对象。 + let result = eval(&mut vm, "function arguments() { return arguments; } arguments()").unwrap(); + assert!(result.is_object(), "expected the arguments object, got: {:?}", result); +} + +#[test] +fn fn_name_recursive_call() { + let mut vm = Vm::new(); + // 递归调用依赖名绑定读回函数对象再调用,覆盖寄存器写回回归面。 + let result = eval(&mut vm, "function fact(n) { return n <= 1 ? 1 : n * fact(n - 1); } fact(5)").unwrap(); + assert_num(result, 120.0); +} diff --git a/crates/oxide_vm/tests/module_namespace_tests.rs b/crates/oxide_vm/tests/module_namespace_tests.rs index 00b53ad5..40431a58 100644 --- a/crates/oxide_vm/tests/module_namespace_tests.rs +++ b/crates/oxide_vm/tests/module_namespace_tests.rs @@ -390,8 +390,9 @@ fn cross_module_aliased_imports_read_live_value() { assert_eq!(result, "1|1|2|2", "一源两别名未同步读到重赋后的活值"); } -/// 默认导出具名函数体的自引用重赋:函数体内 `fn = 2` 写模块级绑定, -/// 导入方再次读 default 得新值(含捕获分析下探 export 声明与共享 cell)。 +/// 默认导出具名函数体的自引用重赋:函数名 `fn` 在函数作用域登记为不可写 +/// 绑定(15.2.10.1 步 m),遮蔽模块级同名绑定;体内容器 `fn = 2` 落在函数 +/// 作用域名绑定,模块级绑定不被改写,导入方读 default 仍得函数。 #[test] fn cross_module_default_export_reads_live_value() { let cwd = std::env::current_dir().expect("cwd"); @@ -402,11 +403,11 @@ fn cross_module_default_export_reads_live_value() { dir.join("main.mjs"), "import val from './dep.mjs';\n \ const ret = val();\n \ - globalThis.__ns = [ret, val].join('|');", + globalThis.__ns = [ret, typeof val].join('|');", ) .expect("write main"); let _cleanup = Cleanup(dir.clone()); let result = run_namespace_module(&dir); - assert_eq!(result, "1|2", "default 导出的具名函数自引用重赋未活读"); + assert_eq!(result, "1|function", "函数名不可写绑定遮蔽模块级绑定,自引用重赋静默失败"); }