diff --git a/Cargo.lock b/Cargo.lock index eacbac76..1578153f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1095,7 +1095,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.8.7" +version = "0.8.8" dependencies = [ "chrono", "fast-float", @@ -1120,7 +1120,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.8.7" +version = "0.8.8" dependencies = [ "num-bigint", "oxide_log", @@ -1128,7 +1128,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.8.7" +version = "0.8.8" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1139,7 +1139,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.8.7" +version = "0.8.8" dependencies = [ "ansi_term", "bumpalo", @@ -1159,7 +1159,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.8.7" +version = "0.8.8" dependencies = [ "lru", "oxide_bytecode", @@ -1168,7 +1168,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.8.7" +version = "0.8.8" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1187,7 +1187,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.8.7" +version = "0.8.8" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1201,7 +1201,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.8.7" +version = "0.8.8" dependencies = [ "num-bigint", "oxc_allocator", @@ -1216,7 +1216,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.8.7" +version = "0.8.8" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1225,7 +1225,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.8.7" +version = "0.8.8" dependencies = [ "bumpalo", "dashmap", @@ -1240,7 +1240,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.8.7" +version = "0.8.8" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1252,7 +1252,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.8.7" +version = "0.8.8" dependencies = [ "tracing", "tracing-appender", @@ -1261,7 +1261,7 @@ dependencies = [ [[package]] name = "oxide_parser" -version = "0.8.7" +version = "0.8.8" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1276,7 +1276,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.8.7" +version = "0.8.8" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1292,7 +1292,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.8.7" +version = "0.8.8" dependencies = [ "num-bigint", "num-traits", @@ -1304,7 +1304,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.8.7" +version = "0.8.8" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.8.7" +version = "0.8.8" dependencies = [ "bumpalo", "hashbrown 0.14.5", @@ -1333,7 +1333,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.8.7" +version = "0.8.8" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 68887e34..4005df02 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ members = [ ] [workspace.package] -version = "0.8.7" +version = "0.8.8" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_emit/src/capture/mod.rs b/crates/oxide_emit/src/capture/mod.rs index 8538fd36..944e3f81 100644 --- a/crates/oxide_emit/src/capture/mod.rs +++ b/crates/oxide_emit/src/capture/mod.rs @@ -16,6 +16,7 @@ mod upvalues; use std::collections::HashSet; pub(crate) use captured::collect_captured_bindings; +pub(crate) use names::collect_block_level_decl_names; pub(crate) use names::collect_direct_lexical_names; pub(crate) use names::collect_own_binding_names; pub(crate) use names::collect_top_level_function_names; diff --git a/crates/oxide_emit/src/capture/names.rs b/crates/oxide_emit/src/capture/names.rs index fc51cd9d..353ab0b8 100644 --- a/crates/oxide_emit/src/capture/names.rs +++ b/crates/oxide_emit/src/capture/names.rs @@ -181,6 +181,116 @@ pub(crate) fn collect_direct_lexical_names(stmts: &[Statement]) -> HashSet HashSet { + let mut names = HashSet::new(); + collect_block_level_names_stmt(stmts, &mut names, true); + names +} + +/// 递归遍历语句树收集块级声明名。`top` 标记当前层是否为顶层(顶层直接子级 +/// let/const/class 是顶层词法绑定,非块级)。 +fn collect_block_level_names_stmt(stmts: &[Statement], out: &mut HashSet, top: bool) { + for stmt in stmts { + match stmt { + Statement::VariableDeclaration(vd) => { + if matches!(vd.kind, VariableDeclarationKind::Var) || top { + continue; + } + for d in &vd.declarations { + collect_binding_pattern_names(&d.id, out); + } + } + Statement::ClassDeclaration(cd) => { + if !top { + if let Some(id) = &cd.id { + out.insert(id.name.to_string()); + } + } + } + Statement::BlockStatement(b) => collect_block_level_names_stmt(&b.body, out, false), + Statement::IfStatement(is) => { + collect_block_level_names_stmt(std::slice::from_ref(&is.consequent), out, false); + if let Some(alt) = &is.alternate { + collect_block_level_names_stmt(std::slice::from_ref(alt), out, false); + } + } + Statement::WhileStatement(w) => collect_block_level_names_stmt(std::slice::from_ref(&w.body), out, false), + Statement::DoWhileStatement(d) => collect_block_level_names_stmt(std::slice::from_ref(&d.body), out, false), + Statement::ForStatement(f) => { + if let Some(oxide_parser::ForStatementInit::VariableDeclaration(vd)) = &f.init { + if !matches!(vd.kind, VariableDeclarationKind::Var) { + for d in &vd.declarations { + collect_binding_pattern_names(&d.id, out); + } + } + } + collect_block_level_names_stmt(std::slice::from_ref(&f.body), out, false); + } + Statement::ForInStatement(fi) => { + if let oxide_parser::ForStatementLeft::VariableDeclaration(vd) = &fi.left { + if !matches!(vd.kind, VariableDeclarationKind::Var) { + collect_for_left_decl_names(&fi.left, out); + } + } + collect_block_level_names_stmt(std::slice::from_ref(&fi.body), out, false); + } + Statement::ForOfStatement(fo) => { + if let oxide_parser::ForStatementLeft::VariableDeclaration(vd) = &fo.left { + if !matches!(vd.kind, VariableDeclarationKind::Var) { + collect_for_left_decl_names(&fo.left, out); + } + } + collect_block_level_names_stmt(std::slice::from_ref(&fo.body), out, false); + } + Statement::SwitchStatement(sw) => { + for case in &sw.cases { + collect_block_level_names_stmt(&case.consequent, out, false); + } + } + Statement::TryStatement(ts) => { + collect_block_level_names_stmt(&ts.block.body, out, false); + if let Some(h) = &ts.handler { + if let Some(param) = &h.param { + collect_binding_pattern_names(¶m.pattern, out); + } + collect_block_level_names_stmt(&h.body.body, out, false); + } + if let Some(f) = &ts.finalizer { + collect_block_level_names_stmt(&f.body, out, false); + } + } + Statement::LabeledStatement(ls) => { + collect_block_level_names_stmt(std::slice::from_ref(&ls.body), out, false) + } + Statement::WithStatement(ws) => collect_block_level_names_stmt(std::slice::from_ref(&ws.body), out, false), + Statement::ExportNamedDeclaration(exp) => { + if let Some(decl) = &exp.declaration { + match decl { + oxide_parser::Declaration::VariableDeclaration(vd) => { + if !matches!(vd.kind, VariableDeclarationKind::Var) && !top { + for d in &vd.declarations { + collect_binding_pattern_names(&d.id, out); + } + } + } + oxide_parser::Declaration::ClassDeclaration(cd) if !top => { + if let Some(id) = &cd.id { + out.insert(id.name.to_string()); + } + } + _ => {} + } + } + } + _ => {} + } + } +} + /// 收集当前函数作用域声明的绑定名(参数 + 变量/函数声明,含嵌套 block,不含嵌套函数体)。 pub(crate) fn collect_own_binding_names(param_names: &[&str], stmts: &[Statement]) -> HashSet { let mut names = HashSet::new(); diff --git a/crates/oxide_emit/src/compile_ctx.rs b/crates/oxide_emit/src/compile_ctx.rs index f470ccee..c61c7282 100644 --- a/crates/oxide_emit/src/compile_ctx.rs +++ b/crates/oxide_emit/src/compile_ctx.rs @@ -95,9 +95,13 @@ pub struct CompileCtx { /// 消除符号表时序依赖与 cell 索引错位。 pub(crate) captured_bindings: BTreeMap, /// 下一可用 cell 索引(计数器):捕获分析后初始化为名字集大小(函数级索引 - /// 0..n-1 占满),块级遮蔽绑定预声明追加与合成 cell 经 `alloc_cell_idx` - /// 递增取号,保证与函数级名字排序索引及历次追加互不碰撞。 + /// 0..n-1 占满),块级遮蔽绑定预声明追加、catch 参数就地声明与合成 cell 经 + /// `alloc_cell_idx` 递增取号,保证与函数级名字排序索引及历次追加互不碰撞。 pub(crate) next_cell_idx: u8, + /// 词法循环头(C-for / for-in / for-of)名集:头名索引由 ForHeadEnv 覆盖 + /// 捕获映射分配(TDZ 占位与体区 fresh cell),声明点据此走映射回退而非 + /// 就地追加新索引。begin 时并入、restore 时移除,嵌套循环成对。 + pub(crate) for_head_env_names: HashSet, /// 正在发射的 C 风格 for 头 let/const 声明名(含解构叶)。头名不像块级 /// let/const 那样在块入口预声明,init 表达式内创建的嵌套函数引用同头尚未 /// declare 的前向名时,父捕获可见性过滤据此放行;init 发射完毕后移除, @@ -253,6 +257,7 @@ impl CompileCtx { block_fn_entry_mats: Vec::new(), captured_bindings: BTreeMap::new(), next_cell_idx: 0, + for_head_env_names: HashSet::new(), pending_for_head_names: HashSet::new(), global_tier_names: HashSet::new(), upvalue_const_flags: HashSet::new(), @@ -426,13 +431,27 @@ impl CompileCtx { /// 登记)时返回 (cell_idx, binding_reg)。索引优先取绑定自身字段(函数级 /// 回填与块级追加的真源),绑定未分配索引时回退捕获映射(for 头覆盖 / /// catch 参数等容器面,后续子任务逐面收编)。块级遮蔽绑定在块退出后不可 - /// 解析,返回 None(落全局解析),不得按名误读已失效 cell。 + /// 解析,返回 None(落全局解析),不得按名误读已失效 cell。tier 绑定 + /// (scope 0 顶层 var/函数)的全局对象属性是唯一存储,不建 cell;映射中 + /// 被同名块级绑定保留的 entry 不属该 tier 绑定,回退不得命中——for 头 + /// 前向名除外(头绑定未 declare,映射值是 ForHeadEnv 的 TDZ 索引)。 pub(crate) fn visible_cell(&self, name: &str) -> Option<(u8, u32)> { - let (binding, _) = self.scopes.symbols.lookup_any_binding(name)?; + let (binding, scope_idx) = self.scopes.symbols.lookup_any_binding(name)?; if self.is_implicit_global_reg(binding.reg) { return None; } - let idx = binding.cell_idx.or_else(|| self.captured_bindings.get(name).copied())?; + // for 头前向名(C 风格 for 头 pending / for-in/for-of env)读 TDZ cell: + // 头绑定未 declare,按名回查命中外层同名 tier 绑定,映射回退须放行以命中 + // ForHeadEnv 覆盖的 TDZ 索引。 + let is_for_head = self.pending_for_head_names.contains(name) || self.for_head_env_names.contains(name); + let is_tier_binding = scope_idx == 0 && self.global_tier_names.contains(name); + let idx = binding.cell_idx.or_else(|| { + if is_tier_binding && !is_for_head { + None + } else { + self.captured_bindings.get(name).copied() + } + })?; Some((idx, binding.reg)) } @@ -443,15 +462,54 @@ impl CompileCtx { self.captured_bindings = map; } - /// 分配一个新 cell 索引:取当前计数器值并递增。块级遮蔽绑定预声明追加与 - /// 合成 cell 共用,分配序由发射序决定,跨 run 稳定。 - #[allow(dead_code)] // 块级遮蔽绑定预声明追加(后续子任务)消费,本件仅立计数器 + /// 分配一个新 cell 索引:取当前计数器值并递增。块级遮蔽绑定预声明追加、 + /// catch 参数就地声明与合成 cell 共用,分配序由发射序决定,跨 run 稳定。 pub(crate) fn alloc_cell_idx(&mut self) -> u8 { let idx = self.next_cell_idx; self.next_cell_idx = self.next_cell_idx.saturating_add(1); idx } + /// 声明点 cell 索引解析:索引按绑定实例取(绑定自身字段是真源)。绑定未分配 + /// 索引时,词法循环头名(ForHeadEnv 覆盖捕获映射)走映射回退;最内层为块 + /// 作用域的就地声明(catch 参数)现场追加新索引并写回绑定,与函数级同名 + /// 绑定的名字排序索引区分。名字不可见(隐式全局登记)或不在捕获集时返回 + /// None,调用方落 with/全局臂。顶层 tier 名(顶层 var/函数)的全局对象属性 + /// 是唯一存储,不建 cell,返回 None。 + /// + /// # 副作用 + /// - 就地追加臂写回绑定 cell 索引并递增计数器;指令流不改动。 + pub(crate) fn resolve_bind_cell_idx(&mut self, name: &str) -> Option { + let (existing, is_implicit, block_scope, for_head, is_tier) = { + let symbols = &self.scopes.symbols; + let (binding, scope_idx) = symbols.lookup_any_binding(name)?; + ( + binding.cell_idx, + self.is_implicit_global_reg(binding.reg), + symbols.scopes.last().is_some_and(|s| s.kind == ScopeKind::BlockScope), + self.for_head_env_names.contains(name), + self.is_global_scope && self.global_tier_names.contains(name) && scope_idx == 0, + ) + }; + if is_implicit { + return None; + } + if let Some(idx) = existing { + return Some(idx); + } + if is_tier { + return None; + } + let base = *self.captured_bindings.get(name)?; + if block_scope && !for_head { + let fresh = self.alloc_cell_idx(); + self.set_binding_cell_idx(name, fresh); + Some(fresh) + } else { + Some(base) + } + } + /// 给最内层同名可见绑定写入 cell 索引(委托符号表)。 pub(crate) fn set_binding_cell_idx(&mut self, name: &str, idx: u8) { self.scopes.symbols.set_binding_cell_idx(name, idx); @@ -459,10 +517,15 @@ impl CompileCtx { /// 函数级绑定 cell 索引回填:捕获集(名字排序分配)的索引写入对应绑定 /// 实例,使解析点按绑定身份取索引。预声明完成后调用,函数级绑定(参数 / - /// var / 直接子级词法 / 块级函数外层 var / arguments)此时已齐。 + /// var / 直接子级词法 / 块级函数外层 var / arguments)此时已齐。顶层 tier + /// 名(顶层 var/函数)的全局对象属性是唯一存储,不回填 cell 索引。 pub(crate) fn backfill_captured_cell_idxs(&mut self) { - let entries: Vec<(String, u8)> = - self.captured_bindings.iter().map(|(name, &idx)| (name.clone(), idx)).collect(); + let entries: Vec<(String, u8)> = self + .captured_bindings + .iter() + .filter(|(name, _)| !(self.is_global_scope && self.global_tier_names.contains(*name))) + .map(|(name, &idx)| (name.clone(), idx)) + .collect(); for (name, idx) in entries { self.set_binding_cell_idx(&name, idx); } diff --git a/crates/oxide_emit/src/function_body.rs b/crates/oxide_emit/src/function_body.rs index e018254f..65e13745 100644 --- a/crates/oxide_emit/src/function_body.rs +++ b/crates/oxide_emit/src/function_body.rs @@ -655,14 +655,56 @@ impl Emitter { // upvalue 代表的祖父绑定在父函数体内恒可见,不参与过滤。 // C 风格 for 头 let/const 名在 init 内尚未 declare,但同头声明会为其 // 建 cell,前向捕获须按父捕获集保留(见 `pending_for_head_names`)。 + // 索引按父可见绑定实例取(块级遮蔽绑定持追加索引);词法循环头名 + // (for-in/for-of 头绑定在右值区编译时未进符号表,按名回查会误命中 + // 外层同名绑定)与 for 头前向名无条件取捕获映射值(ForHeadEnv 覆盖 + // 索引或名字排序索引);绑定未分配索引的其余名(命名空间导入等) + // 回退映射值。顶层 tier 名(顶层 var/函数)已在捕获集剔除阶段出集, + // 不在此列。 let visible_parent_captured: BTreeMap = parent_ctx .captured_bindings .iter() .filter(|(n, _)| { + // for 头前向名(C 风格 for 头 pending / for-in/for-of env): + // 头绑定未 declare,按名回查命中外层同名 tier 绑定,映射值是 + // ForHeadEnv 的 TDZ 索引,须保留为 upvalue。 + let for_head = parent_ctx.pending_for_head_names.contains(n.as_str()) + || parent_ctx.for_head_env_names.contains(n.as_str()); + // tier 绑定(scope 0 顶层 var/函数):全局对象属性是唯一存储, + // 不建 cell;嵌套函数直读全局,不入 upvalue。映射中为同名块级 + // 绑定保留的 entry 不属该 tier 绑定,不得按名捕获。 + let tier = + !for_head + && parent_ctx.scopes.symbols.lookup_any_binding(n.as_str()).is_some_and( + |(_, scope_idx)| scope_idx == 0 && parent_ctx.global_tier_names.contains(n.as_str()), + ); + if tier { + return false; + } parent_ctx.visible_binding_reg(n.as_str()).is_some() || parent_ctx.pending_for_head_names.contains(n.as_str()) }) - .map(|(n, &i)| (n.clone(), i)) + .map(|(n, &i)| { + let idx = if parent_ctx.for_head_env_names.contains(n.as_str()) + || parent_ctx.pending_for_head_names.contains(n.as_str()) + { + // 词法循环头名:头绑定在右值区编译时未进符号表(按名回查 + // 会误命中外层同名绑定),索引由 ForHeadEnv 覆盖映射分配, + // 无条件取映射值。 + i + } else { + // 其余名按绑定实例取索引(块级遮蔽绑定持追加索引); + // 绑定未分配索引(命名空间导入等绑定点走映射值不写绑定 + // 字段)回退映射值,与基线按名取索引同口径。 + parent_ctx + .scopes + .symbols + .lookup_any_binding(n.as_str()) + .and_then(|(b, _)| b.cell_idx) + .unwrap_or(i) + }; + (n.clone(), idx) + }) .collect(); ctx.current_upvalue_captures = collect_upvalue_names( body_stmts, diff --git a/crates/oxide_emit/src/prepass/declare.rs b/crates/oxide_emit/src/prepass/declare.rs index 7a58d668..11e389ec 100644 --- a/crates/oxide_emit/src/prepass/declare.rs +++ b/crates/oxide_emit/src/prepass/declare.rs @@ -1,7 +1,11 @@ //! emit 前置 pass,声明预登记:函数/var 声明提升预声明、块级函数声明按 ES2015 //! 块绑定预声明、let/const/class 建 TDZ 占位。 +use crate::symbol_table::ScopeKind; use crate::{CompileCtx, Emitter}; +use oxide_bytecode::opcode::OpCode; +use oxide_ir::inst::Inst; +use oxide_ir::operand::Operand; use oxide_parser::{BindingPattern, Declaration, ExportDefaultDeclarationKind, Statement, VariableDeclarationKind}; impl Emitter { @@ -395,6 +399,29 @@ impl Emitter { }, is_const, ); + // 块级遮蔽绑定:名字在捕获集且最内层为块作用域时,追加新 cell + // 索引并发未初始化 MAKE_CELL(TDZ 占位)。声明点与闭包捕获据 + // 绑定字段走新索引,不原位覆写函数级同名绑定的名字排序索引; + // 函数作用域调用点(函数体/顶层直接子级)名字排序索引归该 + // 绑定所有,不追加。 + if ctx.captured_bindings.contains_key(bi.name.as_str()) + && ctx + .scopes + .symbols + .scopes + .last() + .is_some_and(|s| s.kind == ScopeKind::BlockScope) + { + let idx = ctx.alloc_cell_idx(); + ctx.set_binding_cell_idx(bi.name.as_str(), idx); + let undef_reg = self.emit_undefined(ctx); + ctx.inst(Inst::new( + OpCode::MAKE_CELL, + Operand::Reg(undef_reg), + Operand::Imm(idx as u16 | 0x0100), + Operand::None, + )); + } } BindingPattern::ArrayPattern(ap) => { for e in ap.elements.iter().flatten() { diff --git a/crates/oxide_emit/src/program.rs b/crates/oxide_emit/src/program.rs index 2bf4dfbc..60935236 100644 --- a/crates/oxide_emit/src/program.rs +++ b/crates/oxide_emit/src/program.rs @@ -17,7 +17,7 @@ use oxide_ir::IRFunction; use oxide_parser::Statement; use crate::capture::{ - collect_captured_bindings, collect_direct_lexical_names, collect_own_binding_names, + collect_block_level_decl_names, collect_captured_bindings, collect_direct_lexical_names, collect_own_binding_names, collect_top_level_function_names, collect_top_level_function_names_ordered, collect_var_binding_names, }; use crate::compile_ctx::CompileCtx; @@ -350,7 +350,12 @@ impl Emitter { } ctx.global_tier_names = tier_names; ctx.set_captured_bindings(collect_captured_bindings(&program.body, &[], &ctx.own_bindings)); - ctx.captured_bindings.retain(|n, _| !ctx.global_tier_names.contains(n)); + // 顶层 tier 名(顶层 var/函数)的全局对象属性是唯一存储,从捕获集剔除; + // 被同名块级绑定(catch 参数 / 块级 let/const)遮蔽时保留,使块级绑定能 + // 追加新索引供闭包捕获(顶层 var 本身不建 cell,回填与解析点均跳过)。 + let block_level_names = collect_block_level_decl_names(&program.body); + ctx.captured_bindings + .retain(|n, _| !ctx.global_tier_names.contains(n) || block_level_names.contains(n)); // 顶层函数级绑定 cell 索引回填:预声明已完成,绑定已齐。 ctx.backfill_captured_cell_idxs(); @@ -358,9 +363,10 @@ impl Emitter { // 声明语句执行前创建的闭包读取到 undefined(脚本 GlobalDeclarationInstantiation // 语义),而非占位 cell 的 TDZ 误报。声明语句的 MAKE_CELL 覆盖此初值。 // 名集保持 var-only:函数声明名从不入捕获集(tier 剔除),无需入口 cell。 + // tier 名(全局对象属性为唯一存储)不建入口 cell。 let mut var_names: Vec = var_names .into_iter() - .filter(|n| ctx.captured_bindings.contains_key(n)) + .filter(|n| ctx.captured_bindings.contains_key(n) && !ctx.global_tier_names.contains(n)) .collect(); // HashSet 迭代序带随机种子,排序后入口 MAKE_CELL 发射序跨进程稳定。 var_names.sort(); diff --git a/crates/oxide_emit/src/shared/binding.rs b/crates/oxide_emit/src/shared/binding.rs index 4ef121da..a4f004e0 100644 --- a/crates/oxide_emit/src/shared/binding.rs +++ b/crates/oxide_emit/src/shared/binding.rs @@ -86,9 +86,10 @@ impl Emitter { } return Ok(()); } - // 索引按绑定实例取(`visible_cell` 已含可见性判定):函数级绑定走回填 - // 索引,绑定未分配索引时回退捕获映射(for 头覆盖等容器面)。 - if let Some((cell_idx, _)) = ctx.visible_cell(name) { + // 索引按绑定实例取(`resolve_bind_cell_idx` 已含可见性判定):函数级 + // 绑定走回填索引,块级遮蔽绑定走预声明追加索引,catch 参数等块作用域 + // 就地声明现场追加;词法循环头名走捕获映射回退。 + if let Some(cell_idx) = ctx.resolve_bind_cell_idx(name) { let op = if fresh_cell { OpCode::MAKE_CELL_FRESH } else { OpCode::MAKE_CELL }; ctx.inst(Inst::new(op, Operand::Reg(src_reg), Operand::Imm(cell_idx as u16), Operand::None)); // C 风格 for 头绑定:cell 之外补写绑定寄存器——循环体/test/update 读寄存器, diff --git a/crates/oxide_emit/src/stmt/declaration/var.rs b/crates/oxide_emit/src/stmt/declaration/var.rs index 18a38179..974287d5 100644 --- a/crates/oxide_emit/src/stmt/declaration/var.rs +++ b/crates/oxide_emit/src/stmt/declaration/var.rs @@ -70,7 +70,7 @@ impl Emitter { }; (target, false) }; - if let Some(&cell_idx) = ctx.captured_bindings.get(bi.name.as_str()) { + if let Some(cell_idx) = ctx.resolve_bind_cell_idx(bi.name.as_str()) { // 被捕获绑定的 cell 在函数入口已实例化(undefined);声明语句 // 不赋值,仅首次声明刷新初值,已绑定的 cell 保留现值。 if !already_bound { diff --git a/crates/oxide_emit/src/stmt/iteration/for_in.rs b/crates/oxide_emit/src/stmt/iteration/for_in.rs index 449d7af6..dd90946c 100644 --- a/crates/oxide_emit/src/stmt/iteration/for_in.rs +++ b/crates/oxide_emit/src/stmt/iteration/for_in.rs @@ -71,8 +71,9 @@ impl Emitter { /// 3. 发射未初始化 MAKE_CELL 并覆盖映射。 /// /// # 副作用 - /// - 捕获映射被改动;须经 `switch_for_head_env_to_body` 与 - /// `restore_for_head_env` 成对收尾,防头名泄漏进兄弟语句的捕获映射。 + /// - 捕获映射与头名集(`for_head_env_names`)被改动;须经 + /// `switch_for_head_env_to_body` 与 `restore_for_head_env` 成对收尾, + /// 防头名泄漏进兄弟语句的捕获判定。 pub(crate) fn begin_for_head_env(&self, names: Vec, ctx: &mut CompileCtx) -> Result { let mut entries = Vec::with_capacity(names.len()); let mut next = ctx.captured_bindings.values().copied().max().map_or(0, |m| m.saturating_add(1)); @@ -81,6 +82,8 @@ impl Emitter { let tdz_idx = next; next = next.saturating_add(1); ctx.captured_bindings.insert(name.clone(), tdz_idx); + // 头名登记:声明点索引解析据此走映射回退,不就地追加新索引。 + ctx.for_head_env_names.insert(name.clone()); // MAKE_CELL 的 16 位立即数:低字节是 cell 索引,高字节 bit 0 是 // 未初始化标记。标志折入立即数高字节(0x0100),dispatch 侧按字节 // 拆回两字段。 @@ -93,6 +96,7 @@ impl Emitter { )); entries.push((name, saved, tdz_idx, 0)); } + ctx.next_cell_idx = next; Ok(ForHeadEnv { entries }) } @@ -128,20 +132,22 @@ impl Emitter { Operand::None, )); } + ctx.next_cell_idx = next; } /// 语句收尾恢复捕获映射:旧条目放回(无旧条目则移除),头名不进入后续 - /// 兄弟语句的捕获判定。 + /// 兄弟语句的捕获判定;头名集同步移除(嵌套循环成对)。 pub(crate) fn restore_for_head_env(&self, env: ForHeadEnv, ctx: &mut CompileCtx) { for (name, saved, _, _) in env.entries { match saved { Some(old) => { - ctx.captured_bindings.insert(name, old); + ctx.captured_bindings.insert(name.clone(), old); } None => { ctx.captured_bindings.remove(&name); } } + ctx.for_head_env_names.remove(&name); } } diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index 19c35634..51d3b04b 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -250,14 +250,15 @@ impl Vm { /// 1. 取 a 槽为 cell 下标,按需扩容 `cell_stack` 至可容纳该下标。 /// 2. b 槽为 0 表示已初始化,非 0 为 TDZ(读写抛 ReferenceError,用于 for-in/for-of /// 词法头环境建模)。 - /// 3. 槽位为空时新建 cell;已有 `CREATE_CLOSURE` 建的占位 cell 则原位更新值与标志, - /// 使闭包 upvalue 指向的同一 cell 跟随初始化。 + /// 3. 槽位为空时新建 cell;TDZ 占位遇已初始化 cell 恒新建替换(循环体每迭代 + /// 重执行不得把上一迭代已被闭包捕获的 cell 原位翻回未初始化);其余情形 + /// 原位更新值与标志,使闭包 upvalue 指向的同一 cell 跟随初始化。 /// /// # 边界与前提 /// - `cell_stack` 为空时 `last_mut()` unwrap 失败;调用前须已压入当前帧环境层。 /// /// # 副作用 - /// - 写入/更新堆上 `Cell`(可能触发分配),改动闭包 upvalue 可见值。 + /// - 写入/更新/替换堆上 `Cell`(可能触发分配),改动闭包 upvalue 可见值。 /// /// # 注意事项 /// - 手写 IR 路径保留的指令,常规编译不发射,故标 `#[allow(dead_code)]`。 @@ -276,12 +277,20 @@ impl Vm { // 无占位 cell:新建。 current[cell_idx] = self.gc_state.alloc_cell(value, initialized); } else { - // 更新占位 cell(CREATE_CLOSURE 已建),使闭包 upvalue 指向的 - // cell 值跟随初始化;初始化标志随指令 b 槽。 - unsafe { - let cell = &mut *current[cell_idx]; - cell.value = value; - cell.set_initialized(initialized); + let existing_initialized = unsafe { &*current[cell_idx] }.is_initialized(); + if !initialized && existing_initialized { + // TDZ 占位而槽内是已初始化 cell:新建替换。循环体每迭代重执行 + // 的 TDZ 占位不得原位翻新迭代 cell(已被本迭代前闭包捕获); + // 已初始化 cell 保持身份,闭包 upvalue 保值。 + current[cell_idx] = self.gc_state.alloc_cell(value, initialized); + } else { + // 更新占位 cell(CREATE_CLOSURE 已建),使闭包 upvalue 指向的 + // cell 值跟随初始化;初始化标志随指令 b 槽。 + unsafe { + let cell = &mut *current[cell_idx]; + cell.value = value; + cell.set_initialized(initialized); + } } } Ok(())