mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-27 04:01:11 +08:00
fix: 块级函数声明支臂不再块入口物化,声明点按入口物化状态区分写回
This commit is contained in:
@@ -223,6 +223,13 @@ pub struct CompileCtx {
|
|||||||
/// 块级函数名 web-compat 外层绑定抑制集(形参 ∪ 函数作用域树内词法声明名):
|
/// 块级函数名 web-compat 外层绑定抑制集(形参 ∪ 函数作用域树内词法声明名):
|
||||||
/// 抑制集内名字退化为纯块作用域。预声明期定稿,实例化与声明点写回两侧同查。
|
/// 抑制集内名字退化为纯块作用域。预声明期定稿,实例化与声明点写回两侧同查。
|
||||||
pub(crate) block_fn_suppressed: HashSet<String>,
|
pub(crate) block_fn_suppressed: HashSet<String>,
|
||||||
|
/// 块入口已物化声明登记表(按块层级压/弹):块直接子与标签直接子体
|
||||||
|
/// 的函数声明在块入口物化闭包后,按 名 → 声明节点 arena 指针 登记。
|
||||||
|
/// 声明点据此三分:同节点命中(本声明已入口物化)→ 复用块槽保闭包
|
||||||
|
/// 同一;仅同名命中(支臂声明与直接子同名共享块槽)→ 闭包落 fresh
|
||||||
|
/// 寄存器只写回外层 var、块槽不动;均未命中 → 执行期自物化入块槽。
|
||||||
|
/// 节点指针解析产物 arena 地址,同一编译单元内稳定。
|
||||||
|
pub(crate) block_fn_entry_mats: Vec<HashMap<String, *const ()>>,
|
||||||
/// 本函数被嵌套函数捕获的绑定名 → cell_idx(名字排序分配,稳定跨 run)。
|
/// 本函数被嵌套函数捕获的绑定名 → cell_idx(名字排序分配,稳定跨 run)。
|
||||||
/// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射,
|
/// 捕获判断(MAKE_CELL / CELL_GET / CELL_SET)与子函数 upvalue cell_idx 统一查此映射,
|
||||||
/// 消除符号表时序依赖与 cell 索引错位。
|
/// 消除符号表时序依赖与 cell 索引错位。
|
||||||
@@ -371,6 +378,7 @@ impl CompileCtx {
|
|||||||
own_bindings: HashSet::new(),
|
own_bindings: HashSet::new(),
|
||||||
param_names: HashSet::new(),
|
param_names: HashSet::new(),
|
||||||
block_fn_suppressed: HashSet::new(),
|
block_fn_suppressed: HashSet::new(),
|
||||||
|
block_fn_entry_mats: Vec::new(),
|
||||||
captured_bindings: BTreeMap::new(),
|
captured_bindings: BTreeMap::new(),
|
||||||
global_tier_names: HashSet::new(),
|
global_tier_names: HashSet::new(),
|
||||||
upvalue_const_flags: HashSet::new(),
|
upvalue_const_flags: HashSet::new(),
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
//! 块语句 emit:`emit_block_statement` 压/弹作用域并顺序编译子语句(含域分发)。
|
//! 块语句 emit:`emit_block_statement` 压/弹作用域并顺序编译子语句(含域分发)。
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{CompileCtx, Emitter};
|
use crate::{CompileCtx, Emitter};
|
||||||
use oxide_parser::Statement;
|
use oxide_parser::Statement;
|
||||||
|
|
||||||
@@ -14,8 +16,10 @@ impl Emitter {
|
|||||||
self.predeclare_block_function_declarations(&block.body, ctx);
|
self.predeclare_block_function_declarations(&block.body, ctx);
|
||||||
// 块 lexical 声明是局部绑定:不做受限全局名检查(重复声明错在 emit 期报)。
|
// 块 lexical 声明是局部绑定:不做受限全局名检查(重复声明错在 emit 期报)。
|
||||||
let _ = self.predeclare_lexical_declarations(&block.body, ctx, false);
|
let _ = self.predeclare_lexical_declarations(&block.body, ctx, false);
|
||||||
// 块级函数声明的块入口初始化:闭包物化入预声明块槽,声明语句前读命中
|
// 块级函数声明的块入口初始化:直接子(含标签直接子体)闭包物化入预
|
||||||
// 函数对象(sloppy 与 strict 同形);声明点退化为其守语义。
|
// 声明块槽,声明语句前读命中函数对象(sloppy 与 strict 同形);声明
|
||||||
|
// 点不再重编闭包,只处理外层 var 写回(见 emit_function_declaration)。
|
||||||
|
ctx.block_fn_entry_mats.push(HashMap::new());
|
||||||
for s in &block.body {
|
for s in &block.body {
|
||||||
self.emit_block_fn_entry_init_stmt(s, ctx)?;
|
self.emit_block_fn_entry_init_stmt(s, ctx)?;
|
||||||
}
|
}
|
||||||
@@ -25,18 +29,25 @@ impl Emitter {
|
|||||||
r = Some(rr);
|
r = Some(rr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ctx.block_fn_entry_mats.pop();
|
||||||
ctx.pop_scope();
|
ctx.pop_scope();
|
||||||
Ok(r)
|
Ok(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 块入口初始化的单语句走法:函数声明就地物化(同名重复声明共享块绑定,
|
/// 块入口初始化的单语句走法:函数声明就地物化并登记(同名重复声明共享
|
||||||
/// 源序物化使末次声明天然成为入口值);if/labeled/while/do-while/with 体
|
/// 块绑定,源序物化使末次声明天然成为入口值);标签不改变执行流(V8 对
|
||||||
/// 不推作用域、随本块绑定递归;嵌套块/循环/try 各自压作用域、由其自身块
|
/// 标签直接子的函数声明同样块入口可见),随标签体递归一层;if/while/
|
||||||
/// 入口处理,不进入。
|
/// do-while/with 体不进入——支臂声明的执行期才生效(仅被求值支物化,
|
||||||
|
/// while/do-while/with 体直挂声明双侧均为语法错误不可达);嵌套块/循环/
|
||||||
|
/// try 各自压作用域、由其自身块入口处理,不进入。
|
||||||
///
|
///
|
||||||
/// # 前提
|
/// # 前提
|
||||||
/// - 预声明已为各声明名建立块 Let 绑定;`materialize_function_declaration`
|
/// - 预声明已为各声明名建立块 Let 绑定;`materialize_function_declaration`
|
||||||
/// 经 `lookup` 解析槽位。
|
/// 经 `lookup` 解析槽位。
|
||||||
|
///
|
||||||
|
/// # 副作用
|
||||||
|
/// - 当前指令流在块入口发 CREATE_CLOSURE(及 MAKE_CELL / STORE_VAR);
|
||||||
|
/// 物化登记写入 `block_fn_entry_mats` 栈顶帧。
|
||||||
fn emit_block_fn_entry_init_stmt(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<(), String> {
|
fn emit_block_fn_entry_init_stmt(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<(), String> {
|
||||||
match stmt {
|
match stmt {
|
||||||
Statement::FunctionDeclaration(fd) => {
|
Statement::FunctionDeclaration(fd) => {
|
||||||
@@ -45,19 +56,15 @@ impl Emitter {
|
|||||||
};
|
};
|
||||||
let name = id.name.to_string();
|
let name = id.name.to_string();
|
||||||
self.materialize_function_declaration(fd, &name, ctx)?;
|
self.materialize_function_declaration(fd, &name, ctx)?;
|
||||||
|
// 登记声明节点 arena 地址(Box 解引到 arena 本体):声明点
|
||||||
|
// 以同一节点地址判同一,区分本声明入口物化与同名支臂。
|
||||||
|
ctx.block_fn_entry_mats
|
||||||
|
.last_mut()
|
||||||
|
.unwrap()
|
||||||
|
.insert(name, &**fd as *const _ as *const ());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Statement::IfStatement(is) => {
|
|
||||||
self.emit_block_fn_entry_init_stmt(&is.consequent, ctx)?;
|
|
||||||
if let Some(alt) = &is.alternate {
|
|
||||||
self.emit_block_fn_entry_init_stmt(alt, ctx)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Statement::WhileStatement(wh) => self.emit_block_fn_entry_init_stmt(&wh.body, ctx),
|
|
||||||
Statement::DoWhileStatement(dw) => self.emit_block_fn_entry_init_stmt(&dw.body, ctx),
|
|
||||||
Statement::LabeledStatement(ls) => self.emit_block_fn_entry_init_stmt(&ls.body, ctx),
|
Statement::LabeledStatement(ls) => self.emit_block_fn_entry_init_stmt(&ls.body, ctx),
|
||||||
Statement::WithStatement(ws) => self.emit_block_fn_entry_init_stmt(&ws.body, ctx),
|
|
||||||
_ => Ok(()),
|
_ => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,16 +26,26 @@ impl Emitter {
|
|||||||
} else {
|
} else {
|
||||||
return Err("FunctionDeclaration without name".into());
|
return Err("FunctionDeclaration without name".into());
|
||||||
};
|
};
|
||||||
// 块级函数声明:闭包已在块入口物化写入预声明块槽(块绑定块入口即持有
|
// 块级函数声明按入口物化状态三分(块槽预声明 Let 绑定):
|
||||||
// 函数对象,声明语句前读命中它),声明点不重编闭包,只处理下方外层
|
// - 本声明已入口物化(块直接子/标签直接子体):块槽持有本声明闭包,
|
||||||
// var 写回。
|
// 复用槽位保闭包同一,不重编;
|
||||||
|
// - 同名直接子已入口物化(支臂与直接子共享块槽):支臂声明只更新外层
|
||||||
|
// var 绑定、块绑定不动(V8 双绑定面,引擎以块槽 + 外层 var 两槽模拟),
|
||||||
|
// 闭包落 fresh 寄存器写回外层 var;
|
||||||
|
// - 其余(支臂首次求值):执行期自物化闭包写入块槽。
|
||||||
|
// 非块路径(顶层/函数作用域声明点)恒自物化。
|
||||||
let binding_scope_kind = ctx
|
let binding_scope_kind = ctx
|
||||||
.scopes
|
.scopes
|
||||||
.symbols
|
.symbols
|
||||||
.lookup_any_binding(&name)
|
.lookup_any_binding(&name)
|
||||||
.map(|(_, scope_idx)| ctx.scopes.symbols.scopes[scope_idx].kind);
|
.map(|(_, scope_idx)| ctx.scopes.symbols.scopes[scope_idx].kind);
|
||||||
|
let node = fd as *const _ as *const ();
|
||||||
let var_reg = if binding_scope_kind == Some(ScopeKind::BlockScope) {
|
let var_reg = if binding_scope_kind == Some(ScopeKind::BlockScope) {
|
||||||
ctx.lookup(&name)?
|
match ctx.block_fn_entry_mats.last().and_then(|m| m.get(&name)) {
|
||||||
|
Some(&m) if m == node => ctx.lookup(&name)?,
|
||||||
|
Some(_) => self.materialize_function_declaration_var_only(fd, &name, ctx)?,
|
||||||
|
None => self.materialize_function_declaration(fd, &name, ctx)?,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
self.materialize_function_declaration(fd, &name, ctx)?
|
self.materialize_function_declaration(fd, &name, ctx)?
|
||||||
};
|
};
|
||||||
@@ -94,6 +104,26 @@ impl Emitter {
|
|||||||
pub(crate) fn materialize_function_declaration(
|
pub(crate) fn materialize_function_declaration(
|
||||||
&self, fd: &oxide_parser::Function, name: &str, ctx: &mut CompileCtx,
|
&self, fd: &oxide_parser::Function, name: &str, ctx: &mut CompileCtx,
|
||||||
) -> Result<u32, String> {
|
) -> Result<u32, String> {
|
||||||
|
let var_reg = ctx.lookup(name)?;
|
||||||
|
ctx.reserve_reg(var_reg);
|
||||||
|
self.materialize_function_closure(fd, name, var_reg, ctx, true)?;
|
||||||
|
Ok(var_reg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// G 形支臂物化变体:闭包落 fresh 寄存器,不写块槽、不建 cell(同名
|
||||||
|
/// 直接子占用块槽,嵌套捕获读槽 cell 与 V8 块绑定一致)。调用点经返回
|
||||||
|
/// 寄存器把闭包写回外层 var 绑定。
|
||||||
|
pub(crate) fn materialize_function_declaration_var_only(
|
||||||
|
&self, fd: &oxide_parser::Function, name: &str, ctx: &mut CompileCtx,
|
||||||
|
) -> Result<u32, String> {
|
||||||
|
let reg = ctx.alloc_reg();
|
||||||
|
self.materialize_function_closure(fd, name, reg, ctx, false)?;
|
||||||
|
Ok(reg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn materialize_function_closure(
|
||||||
|
&self, fd: &oxide_parser::Function, name: &str, reg: u32, ctx: &mut CompileCtx, write_slot: bool,
|
||||||
|
) -> Result<(), String> {
|
||||||
let mut param_names = Vec::new();
|
let mut param_names = Vec::new();
|
||||||
for (idx, param) in fd.params.items.iter().enumerate() {
|
for (idx, param) in fd.params.items.iter().enumerate() {
|
||||||
match ¶m.pattern {
|
match ¶m.pattern {
|
||||||
@@ -124,19 +154,19 @@ impl Emitter {
|
|||||||
};
|
};
|
||||||
sub_module.function_name = Some(name.to_string());
|
sub_module.function_name = Some(name.to_string());
|
||||||
ctx.nested.push(sub_module);
|
ctx.nested.push(sub_module);
|
||||||
let var_reg = ctx.lookup(name)?;
|
ctx.inst(Inst::create_closure(Operand::Reg(reg), ctx.nested.len() as u16));
|
||||||
ctx.reserve_reg(var_reg);
|
if write_slot {
|
||||||
ctx.inst(Inst::create_closure(Operand::Reg(var_reg), ctx.nested.len() as u16));
|
|
||||||
if let Some(&cell_idx) = ctx.captured_bindings.get(name) {
|
if let Some(&cell_idx) = ctx.captured_bindings.get(name) {
|
||||||
ctx.inst(Inst::new(
|
ctx.inst(Inst::new(
|
||||||
OpCode::MAKE_CELL,
|
OpCode::MAKE_CELL,
|
||||||
Operand::Reg(var_reg),
|
Operand::Reg(reg),
|
||||||
Operand::Imm(cell_idx as u16),
|
Operand::Imm(cell_idx as u16),
|
||||||
Operand::None,
|
Operand::None,
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(var_reg), Operand::Reg(var_reg), Operand::None));
|
ctx.inst(Inst::new(OpCode::STORE_VAR, Operand::Reg(reg), Operand::Reg(reg), Operand::None));
|
||||||
}
|
}
|
||||||
Ok(var_reg)
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
//! 块内函数声明:块级绑定预声明后,声明点前后引用均可编译并执行。
|
//! 块内函数声明:块级绑定预声明后,声明点前后引用均可编译并执行。
|
||||||
//!
|
//!
|
||||||
//! 覆盖:普通块内函数、块内后续调用、提前调用(阶段 1 现状)、与 let 同名冲突、
|
//! 覆盖:普通块内函数、块内后续调用、提前调用(阶段 1 现状)、与 let 同名冲突、
|
||||||
//! 捕获路径、嵌套块作用域隔离、重复声明(Annex B 最后生效)、单语句块体。
|
//! 捕获路径、嵌套块作用域隔离、重复声明(Annex B 最后生效)、单语句块体、
|
||||||
|
//! if 支臂声明(块入口不物化、仅被求值支执行期生效、同名直接子的双绑定面)、
|
||||||
|
//! 标签直接子(块入口可见、外层泄漏)。
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -83,3 +85,88 @@ fn block_function_decl_nested_outer_var_capture() {
|
|||||||
let result = eval("var r; { { function g(){return 4;} r = g(); } } r").unwrap();
|
let result = eval("var r; { { function g(){return 4;} r = g(); } } r").unwrap();
|
||||||
assert_eq!(result.as_int(), 4);
|
assert_eq!(result.as_int(), 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_fn_if_arm_decl_no_entry_init() {
|
||||||
|
// 支臂声明不做块入口初始化:声明点前读命中未初始化的块槽,typeof 为
|
||||||
|
// undefined 而非 function(与直接子声明的块入口可见性区分)。
|
||||||
|
let result = eval("{ var t = typeof g; if (true) function g(){return 1}; t }").unwrap();
|
||||||
|
assert!(
|
||||||
|
result.is_string() && unsafe { &*result.as_string_ptr() }.as_str() == "undefined",
|
||||||
|
"支臂声明前读应为 undefined,得 {result:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_fn_if_arm_decl_runs_on_taken_branch() {
|
||||||
|
// 仅被求值支的声明生效:true 支物化 return 1 的闭包,else 支不求值。
|
||||||
|
let result = eval("{ if (true) function g(){return 1} else function g(){return 2}; g() }").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_fn_if_arm_decl_leaks_outer_from_taken_branch() {
|
||||||
|
// 被求值支的声明经外层 var 写回泄漏块外:块后读见该支的闭包。
|
||||||
|
let result = eval("{ if (true) function g(){return 1} else function g(){return 2} } g()").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_fn_if_arm_call_before_decl_throws() {
|
||||||
|
// 声明点前调用:块槽未物化,调用未初始化值抛错(入口不预物化支臂)。
|
||||||
|
let err = eval("{ g(); if (true) function g(){return 1} }").unwrap_err();
|
||||||
|
assert!(err.contains("not callable"), "got: {err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_fn_arm_same_name_as_direct_child_keeps_block_binding() {
|
||||||
|
// 直接子 + 支臂同名:支臂声明只更新外层 var 绑定、块绑定不动——块内读
|
||||||
|
// 恒见直接子的闭包(V8 双绑定面在引擎块槽侧的投影)。
|
||||||
|
let result = eval("{ function g(){return 1}; if (true) function g(){return 2}; g() }").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_fn_arm_same_name_as_direct_child_updates_outer_var() {
|
||||||
|
// 同名形块外读:支臂声明的外层 var 写回生效,块后读见支臂闭包。
|
||||||
|
let result = eval("{ function g(){return 1}; if (true) function g(){return 2} } g()").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_fn_read_between_direct_child_and_arm() {
|
||||||
|
// 直接子声明与 if 之间的读:支臂尚未求值,读命中直接子的块入口闭包。
|
||||||
|
let result = eval("{ function g(){return 1}; var r = g(); if (true) function g(){return 2}; r }").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_fn_if_arm_decl_readable_after_decl() {
|
||||||
|
// 块内声明点后读:被求值支的闭包已物化入块槽,可正常调用。
|
||||||
|
let result = eval("{ if (true) function g(){return 1}; g() }").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_fn_else_arm_decl_evaluated() {
|
||||||
|
// else 支被求值:生效的是 else 支的闭包(执行期物化,与源序末位无关)。
|
||||||
|
let result = eval("{ if (false) function g(){return 1} else function g(){return 2}; g() }").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_fn_labeled_direct_body_entry_visible() {
|
||||||
|
// 标签直接子体声明保持块入口可见:声明点前读命中入口物化的闭包。
|
||||||
|
let result = eval("{ var t = typeof g; l: function g(){return 1}; t }").unwrap();
|
||||||
|
assert!(
|
||||||
|
result.is_string() && unsafe { &*result.as_string_ptr() }.as_str() == "function",
|
||||||
|
"标签声明前读应为 function,得 {result:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_fn_labeled_direct_body_leaks_outer() {
|
||||||
|
// 标签直接子体声明经外层 var 写回泄漏块外:块后读可调用。
|
||||||
|
let result = eval("{ l: function g(){return 1} } g()").unwrap();
|
||||||
|
assert_eq!(result.as_int(), 1);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user