feat: 间接 eval 读全局词法环境镜像并补齐写路径

This commit is contained in:
2026-10-16 15:03:55 +00:00
parent 84900dc4a1
commit cf0b59d892
18 changed files with 250 additions and 31 deletions
+3
View File
@@ -426,6 +426,9 @@ impl Emitter {
Operand::Reg(ctor_reg),
Operand::None,
));
// 顶层词法双写镜像:类声明值同步写镜像,使跨脚本(间接 eval)读经
// 镜像见当前值。
self.emit_global_lex_mirror_write(name, ctor_reg, ctx);
if let Some(cell_idx) = class_self_cell {
// 以构造器初始化类名 cell 并解除 TDZ(占位 cell 已由类元素闭包在 hoist 期创建)。
ctx.inst(Inst::new(
+9
View File
@@ -887,6 +887,15 @@ impl CompileCtx {
matches!(self.scopes.symbols.lookup_any_binding(name), Some((binding, 0)) if !binding.lexical)
}
/// 名字是否解析到顶层词法绑定(全局词法环境):scope 0 的 let/const/class/
/// 导入绑定。顶层词法绑定的值存储是全局词法环境记录(镜像),所有写路径
/// (声明/赋值/复合/逻辑/update/解构)须同步镜像,否则跨脚本(间接 eval)
/// 读经镜像见陈旧值。块级词法(scope >0)与顶层 var/隐式全局(lexical 不置位)
/// 不属全局词法环境,不写镜像。
pub(crate) fn is_global_lexical_binding(&self, name: &str) -> bool {
matches!(self.scopes.symbols.lookup_any_binding(name), Some((binding, 0)) if binding.lexical)
}
/// 写目标是否为只读全局内置(undefined/NaN/Infinity 的全局绑定——全局对象上
/// 数据属性 {writable:false, configurable:false},put 永不成功)。名字在只读
/// 名单内且解析到全局内置槽;声明臂与赋值臂共用本谓词。
+8
View File
@@ -91,6 +91,7 @@ impl Emitter {
Operand::Reg(val_reg),
Operand::Imm(uv as u16),
));
self.emit_global_lex_mirror_write(name, val_reg, ctx);
self.emit_module_write_through(name, val_reg, ctx)?;
Ok(val_reg)
} else if let Some((cell_idx, binding_reg)) = ctx.visible_cell(name) {
@@ -108,6 +109,7 @@ impl Emitter {
Operand::Reg(val_reg),
Operand::Imm(cell_idx as u16),
));
self.emit_global_lex_mirror_write(name, val_reg, ctx);
self.emit_module_write_through(name, val_reg, ctx)?;
Ok(val_reg)
} else {
@@ -162,6 +164,9 @@ impl Emitter {
// 可写内置名:RMW 新值同步落全局对象属性。
self.emit_global_put_write(name, var_reg, ctx);
}
// 顶层词法双写镜像:RMW 新值同步写镜像,使跨脚本(间接 eval)读经镜像
// 见当前值。
self.emit_global_lex_mirror_write(name, var_reg, ctx);
// live 模块顶层复合赋值同样写穿命名空间条目。
self.emit_module_write_through(name, var_reg, ctx)?;
Ok(var_reg)
@@ -609,6 +614,9 @@ impl Emitter {
}
}
}
// 顶层词法双写镜像:短路通过后新值同步写镜像,使跨脚本(间接
// eval)读经镜像见当前值。
self.emit_global_lex_mirror_write(name, val_reg, ctx);
// 逻辑赋值仅在短路通过后到达此处:写穿同步命名空间条目。
self.emit_module_write_through(name, val_reg, ctx)?;
ctx.inst(Inst::new(
+24 -7
View File
@@ -79,8 +79,9 @@ impl Emitter {
|| ctx.is_implicit_global_reg(var_reg)
|| (ctx.is_builtin(name) && !ctx.is_local_shadowing_builtin(name))
{
// 全局词法环境读:先查镜像(顶层 let/const 真源),未命中回退全局对象属性。
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
ctx.inst(Inst::new(OpCode::LOAD_GLOBAL, Operand::Reg(r), Operand::Const(key_idx), Operand::None));
ctx.inst(Inst::new(OpCode::LOAD_GLOBAL_LEX, Operand::Reg(r), Operand::Const(key_idx), Operand::None));
} else {
ctx.inst(Inst::new(OpCode::LOAD_VAR, Operand::Reg(r), Operand::Reg(var_reg), Operand::None));
}
@@ -139,22 +140,22 @@ impl Emitter {
Operand::Imm(cell_idx as u16),
));
} else if self.is_global_tier_name(ctx, name) {
// 顶层已声明 var:with 对象无该属性时回退读全局对象属性(顶层 var 的唯一
// 存储,引擎侧不保留镜像副本)。
// 顶层已声明 var:with 对象无该属性时回退读全局词法环境(镜像优先,
// 未命中回退全局对象属性)。
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
ctx.inst(Inst::new(
OpCode::LOAD_GLOBAL,
OpCode::LOAD_GLOBAL_LEX,
Operand::Reg(result_reg),
Operand::Const(key_idx),
Operand::None,
));
} else if let Some(reg) = ctx.scopes.symbols.lookup_any(name) {
// 已知 builtin 名(非局部遮蔽)读 A 侧全局对象属性,与静态读臂同形;
// 局部遮蔽绑定保持 LOAD_VAR 读局部槽。
// 已知 builtin 名(非局部遮蔽)读全局词法环境(镜像优先,未命中回退
// 全局对象属性),与静态读臂同形;局部遮蔽绑定保持 LOAD_VAR 读局部槽。
if ctx.is_builtin(name) && !ctx.is_local_shadowing_builtin(name) {
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
ctx.inst(Inst::new(
OpCode::LOAD_GLOBAL,
OpCode::LOAD_GLOBAL_LEX,
Operand::Reg(result_reg),
Operand::Const(key_idx),
Operand::None,
@@ -171,6 +172,17 @@ impl Emitter {
Ok(result_reg)
}
/// 顶层词法绑定镜像写:写目标是顶层词法绑定(全局词法环境)时把新值同步写
/// 镜像,使跨脚本(间接 eval)读经镜像见当前值。eval 脚本顶层词法落自身声明
/// 环境,不写镜像;非顶层词法(块级)与顶层 var/隐式全局不属全局词法环境,
/// 不写镜像。
pub(crate) fn emit_global_lex_mirror_write(&self, name: &str, val_reg: u32, ctx: &mut CompileCtx) {
if ctx.is_global_scope && !ctx.is_eval_script && ctx.is_global_lexical_binding(name) {
let key_idx = ctx.add_constant(Constant::String(name.to_string()));
ctx.inst(Inst::store_global_lex(Operand::Reg(val_reg), key_idx));
}
}
/// 静态标识符写入:upvalue / 被捕获 cell / 普通槽(含 const guard)。
///
/// # 边界与前提
@@ -229,6 +241,7 @@ impl Emitter {
Operand::Reg(val_reg),
Operand::Imm(uv_idx as u16),
));
self.emit_global_lex_mirror_write(name, val_reg, ctx);
self.emit_module_write_through(name, val_reg, ctx)?;
return Ok(());
}
@@ -243,6 +256,7 @@ impl Emitter {
Operand::Reg(val_reg),
Operand::Imm(cell_idx as u16),
));
self.emit_global_lex_mirror_write(name, val_reg, ctx);
self.emit_module_write_through(name, val_reg, ctx)?;
return Ok(());
}
@@ -282,6 +296,9 @@ impl Emitter {
// enumerable/configurable 位;否则内置名镜像槽与 globalThis 反射读到不同值。
self.emit_global_put_write(name, var_reg, ctx);
}
// 顶层词法双写镜像:宿主脚本(主脚本 + 宿主 evalScript)顶层 let/const 赋值
// 同步写镜像,使跨脚本(间接 eval)读经镜像可见当前值。
self.emit_global_lex_mirror_write(name, val_reg, ctx);
// 模块顶层对导出源绑定的写入同步命名空间条目;非 live 模块空表直接返回。
self.emit_module_write_through(name, val_reg, ctx)?;
Ok(())
@@ -915,6 +915,7 @@ impl Emitter {
Operand::Reg(new_reg),
Operand::Imm(uv as u16),
));
self.emit_global_lex_mirror_write(name, new_reg, ctx);
self.emit_module_write_through(name, new_reg, ctx)?;
Ok(if update.prefix { new_reg } else { old_reg })
} else if let Some((cell_idx, binding_reg)) = ctx.visible_cell(name) {
@@ -943,6 +944,7 @@ impl Emitter {
Operand::Reg(new_reg),
Operand::Imm(cell_idx as u16),
));
self.emit_global_lex_mirror_write(name, new_reg, ctx);
self.emit_module_write_through(name, new_reg, ctx)?;
Ok(if update.prefix { new_reg } else { old_reg })
} else {
@@ -1048,6 +1050,9 @@ impl Emitter {
// 可写内置名:RMW 新值同步落全局对象属性。
self.emit_global_put_write(name, var_reg, ctx);
}
// 顶层词法双写镜像:更新后新值同步写镜像,使跨脚本(间接 eval)读经
// 镜像见当前值。
self.emit_global_lex_mirror_write(name, var_reg, ctx);
// 槽位寄存器承载更新后的新值:写穿必须取 var_reg,
// 否则命名空间条目被写回旧值。
self.emit_module_write_through(name, var_reg, ctx)?;
+13
View File
@@ -3,6 +3,7 @@
use crate::symbol_table::ScopeKind;
use crate::{CompileCtx, Emitter};
use oxide_bytecode::module::Constant;
use oxide_bytecode::opcode::OpCode;
use oxide_ir::inst::Inst;
use oxide_ir::operand::Operand;
@@ -394,6 +395,18 @@ impl Emitter {
VariableDeclarationKind::Let
},
);
// 顶层词法双写镜像:宿主脚本(主脚本 + 宿主 evalScript)顶层 let/const
// 落全局词法环境,预声明期先写 undefined 占位(无初始化器声明即终值;
// 带初始化器声明由声明点写真实值覆盖)。eval 脚本顶层词法落自身声明
// 环境,不写镜像;块级词法(scope >0)不属全局词法环境,不写镜像。
if ctx.is_global_scope
&& !ctx.is_eval_script
&& ctx.is_global_lexical_binding(bi.name.as_str())
{
let key_idx = ctx.add_constant(Constant::String(bi.name.to_string()));
let undef_reg = self.emit_undefined(ctx);
ctx.inst(Inst::store_global_lex(Operand::Reg(undef_reg), key_idx));
}
// 块级遮蔽绑定:名字在捕获集且最内层为块作用域时,追加新 cell
// 索引并发未初始化 MAKE_CELL(TDZ 占位)。声明点与闭包捕获据
// 绑定字段走新索引,不原位覆写函数级同名绑定的名字排序索引;
+17
View File
@@ -129,6 +129,11 @@ impl Emitter {
Operand::Imm(0),
));
}
// 顶层词法双写镜像:宿主脚本(主脚本 + 宿主 evalScript)顶层 let/const 落
// 全局词法环境,声明点把值写镜像,使跨脚本(间接 eval)读经镜像可见。
// eval 脚本顶层词法落自身声明环境,不写镜像;块级词法(scope >0)与顶层
// var 不属全局词法环境,不写镜像。
self.emit_global_lex_mirror_write(name, src_reg, ctx);
ctx.init_var(name);
// 脚本顶层 var:同步写全局对象,使 globalThis.x 反射声明值。
if ctx.is_global_scope && matches!(kind, VariableDeclarationKind::Var) {
@@ -168,6 +173,7 @@ impl Emitter {
Operand::Reg(src_reg),
Operand::Imm(uv_idx as u16),
));
self.emit_global_lex_mirror_write(name, src_reg, ctx);
self.emit_module_write_through(name, src_reg, ctx)?;
return Ok(());
}
@@ -181,6 +187,7 @@ impl Emitter {
Operand::Reg(src_reg),
Operand::Imm(cell_idx as u16),
));
self.emit_global_lex_mirror_write(name, src_reg, ctx);
self.emit_module_write_through(name, src_reg, ctx)?;
return Ok(());
}
@@ -217,6 +224,8 @@ impl Emitter {
// 可写内置名:值同步落全局对象属性。
self.emit_global_put_write(name, var_reg, ctx);
}
// 顶层词法双写镜像:值同步写镜像,使跨脚本(间接 eval)读经镜像见当前值。
self.emit_global_lex_mirror_write(name, src_reg, ctx);
// live 模块顶层解构赋值目标同样写穿命名空间条目。
self.emit_module_write_through(name, src_reg, ctx)?;
Ok(())
@@ -397,6 +406,7 @@ impl Emitter {
Operand::Reg(src_reg),
Operand::Imm(uv_idx as u16),
));
self.emit_global_lex_mirror_write(name, src_reg, ctx);
self.emit_module_write_through(name, src_reg, ctx)?;
return Ok(());
}
@@ -410,6 +420,7 @@ impl Emitter {
Operand::Reg(src_reg),
Operand::Imm(cell_idx as u16),
));
self.emit_global_lex_mirror_write(name, src_reg, ctx);
self.emit_module_write_through(name, src_reg, ctx)?;
return Ok(());
}
@@ -446,6 +457,8 @@ impl Emitter {
// 可写内置名:值同步落全局对象属性。
self.emit_global_put_write(name, var_reg, ctx);
}
// 顶层词法双写镜像:值同步写镜像,使跨脚本(间接 eval)读经镜像见当前值。
self.emit_global_lex_mirror_write(name, src_reg, ctx);
// live 模块顶层解构赋值目标同样写穿命名空间条目。
self.emit_module_write_through(name, src_reg, ctx)?;
Ok(())
@@ -516,6 +529,7 @@ impl Emitter {
Operand::Reg(prop_reg),
Operand::Imm(uv_idx as u16),
));
self.emit_global_lex_mirror_write(name, prop_reg, ctx);
self.emit_module_write_through(name, prop_reg, ctx)?;
continue;
}
@@ -529,6 +543,7 @@ impl Emitter {
Operand::Reg(prop_reg),
Operand::Imm(cell_idx as u16),
));
self.emit_global_lex_mirror_write(name, prop_reg, ctx);
self.emit_module_write_through(name, prop_reg, ctx)?;
continue;
}
@@ -565,6 +580,8 @@ impl Emitter {
// 可写内置名:值同步落全局对象属性(其余属性继续赋值)。
self.emit_global_put_write(name, var_reg, ctx);
}
// 顶层词法双写镜像:值同步写镜像,使跨脚本(间接 eval)读经镜像见当前值。
self.emit_global_lex_mirror_write(name, prop_reg, ctx);
// live 模块顶层解构赋值目标同样写穿命名空间条目。
self.emit_module_write_through(name, prop_reg, ctx)?;
}
+5 -1
View File
@@ -132,8 +132,12 @@ impl Emitter {
let mut static_regs = words;
let first_arg_reg = if static_regs.is_empty() { 0u32 } else { pack_arg_regs(&mut static_regs, ctx) };
let op = match &call.callee {
// 可选链调用(eval?.)是间接求值:走通用 CALL 而非 CALL_NATIVE,
// 不传直接求值上下文(规范 ChainEvaluation 的 eval 特判)。
Expression::Identifier(ident)
if ctx.is_builtin(ident.name.as_str()) && !ctx.is_local_shadowing_builtin(ident.name.as_str()) =>
if ctx.is_builtin(ident.name.as_str())
&& !ctx.is_local_shadowing_builtin(ident.name.as_str())
&& !call.optional =>
{
OpCode::CALL_NATIVE
}