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
Generated
+21 -21
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]]
name = "oxide_builtins"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"chrono",
"fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]]
name = "oxide_bytecode"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"num-bigint",
"oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]]
name = "oxide_cfg"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"oxide_bytecode",
"oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]]
name = "oxide_cli"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"ansi_term",
"bumpalo",
@@ -1228,7 +1228,7 @@ dependencies = [
[[package]]
name = "oxide_code_cache"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"lru",
"oxide_bytecode",
@@ -1237,7 +1237,7 @@ dependencies = [
[[package]]
name = "oxide_compiler"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1258,7 +1258,7 @@ dependencies = [
[[package]]
name = "oxide_dce"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1272,7 +1272,7 @@ dependencies = [
[[package]]
name = "oxide_emit"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"num-bigint",
"oxc_allocator",
@@ -1288,7 +1288,7 @@ dependencies = [
[[package]]
name = "oxide_engine"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1300,7 +1300,7 @@ dependencies = [
[[package]]
name = "oxide_fold"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"num-bigint",
"oxide_bytecode",
@@ -1312,7 +1312,7 @@ dependencies = [
[[package]]
name = "oxide_ir"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"oxide_bytecode",
"oxide_log",
@@ -1321,7 +1321,7 @@ dependencies = [
[[package]]
name = "oxide_kernel"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"bumpalo",
"dashmap",
@@ -1335,7 +1335,7 @@ dependencies = [
[[package]]
name = "oxide_liveness"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1347,7 +1347,7 @@ dependencies = [
[[package]]
name = "oxide_log"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"tracing",
"tracing-appender",
@@ -1356,11 +1356,11 @@ dependencies = [
[[package]]
name = "oxide_net"
version = "0.19.32"
version = "0.19.33"
[[package]]
name = "oxide_parser"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"oxc_allocator",
"oxc_ast",
@@ -1375,7 +1375,7 @@ dependencies = [
[[package]]
name = "oxide_regalloc"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"oxide_bytecode",
"oxide_cfg",
@@ -1391,7 +1391,7 @@ dependencies = [
[[package]]
name = "oxide_runtime_api"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"num-bigint",
"num-traits",
@@ -1404,7 +1404,7 @@ dependencies = [
[[package]]
name = "oxide_test262"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"oxide_bytecode",
"oxide_compiler",
@@ -1424,7 +1424,7 @@ dependencies = [
[[package]]
name = "oxide_types"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"bumpalo",
"dashmap",
@@ -1437,7 +1437,7 @@ dependencies = [
[[package]]
name = "oxide_vm"
version = "0.19.32"
version = "0.19.33"
dependencies = [
"bumpalo",
"chrono",
+1 -1
View File
@@ -25,7 +25,7 @@ members = [
]
[workspace.package]
version = "0.19.32"
version = "0.19.33"
edition = "2021"
license = "MIT"
rust-version = "1.80"
+15 -1
View File
@@ -614,6 +614,19 @@ define_opcodes! {
def = None, uses = [SlotSpec::Slot(Slot::Rd), SlotSpec::Slot(Slot::A), SlotSpec::Slot(Slot::B), SlotSpec::ExtReg],
pure = false, jump = false, term = false, ic = false,
// 全局词法环境读:imm16 常量池 key(标识符名字符串)。先查 session 全局词法
// 环境镜像(顶层 let/const 真源),命中取值;未命中回退全局对象属性读,
// 缺失抛 ReferenceError(未声明名与 known builtin 名同形 LOAD_GLOBAL)。
LOAD_GLOBAL_LEX = 0xA0 => "LOAD_GLOBAL_LEX",
def = Some(SlotSpec::Slot(Slot::Rd)), uses = [],
pure = false, jump = false, term = false, ic = false,
// 全局词法环境写:ext 字 = 键常量池下标(u16)、a 槽 = 值寄存器。镜像对象
// 由 session 解析(不依赖 this)。顶层 let/const 声明与赋值双写镜像,
// 使跨脚本(间接 eval / 宿主 evalScript)读经镜像可见。
STORE_GLOBAL_LEX = 0xA1 => "STORE_GLOBAL_LEX",
def = None, uses = [SlotSpec::Slot(Slot::A)],
pure = false, jump = false, term = false, ic = false,
// ── 成员复合赋值:位/移位 (0x64-0x69) ──
COMPOUND_MEMBER_BIT_AND = 0x64 => "COMPOUND_MEMBER_BIT_AND",
def = Some(SlotSpec::Slot(Slot::A)), uses = [SlotSpec::Slot(Slot::Rd), SlotSpec::Slot(Slot::A), SlotSpec::Slot(Slot::B)],
@@ -824,7 +837,8 @@ pub fn ext_word_count(bytecode: &[Instr], pc: usize) -> usize {
| OpCode::DELETE_GLOBAL_PROP_C
| OpCode::DELETE_PROP_STATIC
| OpCode::REST_OBJECT
| OpCode::INIT_PRIVATE => 1,
| OpCode::INIT_PRIVATE
| OpCode::STORE_GLOBAL_LEX => 1,
OpCode::DEFINE_ACCESSOR_ATTRS
| OpCode::DEFINE_ACCESSOR_ATTRS_DYNAMIC
| OpCode::GET_PRIVATE
@@ -25,6 +25,11 @@ impl CompilerService for DefaultCompilerService {
fn compile_eval_script(&self, source: &str) -> Result<CompiledModule, String> {
oxide_parser::with_parse_allocator(|allocator| {
let program = parse_program(allocator, source)?;
// eval 代码是脚本:import/export 声明不得出现(规范 Script 产生式),
// 按模块解析命中即 SyntaxError(既有包装臂转成 JS 异常)。
if program.source_type.is_module() {
return Err("eval code may not contain import or export declarations".to_string());
}
// eval 脚本:顶层 var/function 声明落全局属性 configurable:true。
Compiler::new()
.with_eval_script(true)
+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
}
+7
View File
@@ -261,6 +261,13 @@ impl Inst {
)
}
/// 全局词法环境写:ext = [key_idx](键常量池下标),value 放 a 槽。镜像对象
/// 由 VM 运行期从 session 解析(不依赖 this 寄存器)。顶层 let/const 声明与
/// 赋值双写镜像,使跨脚本读经镜像可见。
pub fn store_global_lex(value: Operand, key_idx: u16) -> Self {
Self::with_ext(OpCode::STORE_GLOBAL_LEX, Operand::None, value, Operand::None, &[key_idx as u32])
}
/// 顶层函数声明的全局绑定声明检查(GDI step 9):target 为全局对象,key 为
/// 声明名寄存器。运行期按 CanDeclareGlobalFunction 判定——撞既有不可配置
/// 非可写数据属性(或不可扩展上的缺失名)抛 TypeError。
+25
View File
@@ -19,6 +19,10 @@ use super::{BuiltinDirtySet, BuiltinId, BuiltinSnapshot, KernelCore};
pub struct KernelSession {
pub builtin_world: Arc<BuiltinWorld>,
pub global_object: P<JsObject>,
/// 全局词法环境镜像:普通对象(Object.prototype),顶层 let/const 声明与赋值
/// 双写此对象,使跨脚本(间接 eval / 宿主 evalScript)读经镜像可见顶层词法
/// 绑定。会话级持久,full_reset 随 session 重建。
pub global_lexical: P<JsObject>,
pub builtin_snapshot: BuiltinSnapshot,
}
@@ -69,11 +73,15 @@ impl KernelSession {
let builtin_world = Arc::new(BuiltinWorld::new(&core.perm_interner, &core.shape_forge));
let object_proto_val = JsValue::from_js_object(builtin_world.object_proto.as_ptr() as *mut JsObject);
let global_object = Self::new_global_object(core, object_proto_val);
// 全局词法环境镜像:普通对象,[[Prototype]] = Object.prototype,与全局对象
// 同策略(session 永久区,属性区随 session 收尾释放)。
let global_lexical = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, object_proto_val));
let builtin_snapshot = BuiltinSnapshot::new(&builtin_world, &global_object);
Self {
builtin_world,
global_object,
global_lexical,
builtin_snapshot,
}
}
@@ -88,6 +96,20 @@ impl KernelSession {
&self.global_object
}
/// 只读访问当前 session 的全局词法环境镜像。
pub fn global_lexical(&self) -> &P<JsObject> {
&self.global_lexical
}
/// 清空全局词法环境镜像:释放旧镜像属性区并重建空对象。full_reset 随 session
/// 隔离重建调用——顶层词法绑定不跨 run 存活。
pub fn clear_global_lexical(&mut self) {
let old = unsafe { &mut *(self.global_lexical.as_ptr() as *mut JsObject) };
old.release_raw_heap();
let object_proto_val = JsValue::from_js_object(self.builtin_world.object_proto.as_ptr() as *mut JsObject);
self.global_lexical = P::new(JsObject::new_empty(EMPTY_SHAPE_ID, object_proto_val));
}
/// 重新采集内置对象世代快照,作为下一次脏检查的基准。
pub fn record_snapshot(&mut self) {
// 重建收尾的原型槽重指与重绑内部写会推进 wrapper 世代:先以当前世代
@@ -256,6 +278,9 @@ impl KernelSession {
// SAFETY: global 归本 session 所有,收尾时恰好释放其属性区一次。
let global = unsafe { &mut *(self.global_object.as_ptr() as *mut JsObject) };
global.release_raw_heap();
// 全局词法环境镜像同 global 归属本 session,属性区恰好释放一次。
let lexical = unsafe { &mut *(self.global_lexical.as_ptr() as *mut JsObject) };
lexical.release_raw_heap();
}
/// 选择性重置:只重建被污染的对象(global 或相应 builtin 家族),并返回脏集合。
+69
View File
@@ -77,6 +77,75 @@ impl Vm {
Ok(())
}
/// 全局词法环境读:先查 session 全局词法环境镜像(顶层 let/const 真源),
/// 命中取值;未命中回退全局对象属性读,缺失抛 ReferenceError。
///
/// # 步骤
/// 1. imm16 取常量池 key(标识符名字符串),解析为属性键 si。
/// 2. 查镜像对象属性,命中写 rd 返回。
/// 3. 未命中回退全局对象属性读,命中写 rd 返回;缺失 raise ReferenceError。
///
/// # 边界与前提
/// - key 常量必须是字符串(emit 侧保证);非字符串按错误返回。
/// - 镜像命中即返回(顶层词法绑定优先于全局对象属性,规范全局环境词法面)。
pub(crate) fn dispatch_load_global_lex(&mut self, rd: usize, instr: u32) -> Result<bool, String> {
let idx = (instr >> 16) as usize;
vm_trace!("LOAD_GLOBAL_LEX rd={} idx={}", rd, idx);
let key_val = self.immutables().get(idx).copied().unwrap_or(JsValue::undefined());
if !key_val.is_string() {
return Err(format!("LOAD_GLOBAL_LEX constant index {idx} is not a string key"));
}
let si = self.property_key_si(key_val)?;
// 镜像优先:顶层 let/const 真源,命中即返回。
let lexical_hit = {
let session = self.realm.session.borrow();
self.resolve_property(session.global_lexical(), si)
};
if let Some(val) = lexical_hit {
self.regs[rd] = val;
return Ok(false);
}
// 回退全局对象属性读(未声明名与 known builtin 名同形 LOAD_GLOBAL)。
let global_hit = {
let session = self.realm.session.borrow();
let global = session.global_object().clone();
self.resolve_property(&global, si)
};
if let Some(val) = global_hit {
self.regs[rd] = val;
Ok(false)
} else {
// SAFETY: key_val 已校验为字符串值;错误消息走 lossy 文本。
let name = unsafe { (*key_val.as_string_ptr()).as_lossy_str() };
self.raise_error_kind("ReferenceError", &format!("{name} is not defined"))?;
Ok(true)
}
}
/// 全局词法环境写:把 a 槽值写入镜像对象(顶层 let/const 声明与赋值双写)。
///
/// # 步骤
/// 1. ext[0] 取键常量池下标,解析为属性键 si。
/// 2. 镜像对象由 session 解析,`set_or_create_prop_value` 写值(缺失建属性)。
///
/// # 副作用
/// - 修改镜像对象 shape/属性表/generation。
pub(crate) fn dispatch_store_global_lex(&mut self, a: usize, key_idx: u16) -> Result<(), String> {
let idx = key_idx as usize;
vm_trace!("STORE_GLOBAL_LEX value={} idx={}", a, idx);
let key_val = self.immutables().get(idx).copied().unwrap_or(JsValue::undefined());
if !key_val.is_string() {
return Err(format!("STORE_GLOBAL_LEX constant index {idx} is not a string key"));
}
let si = self.property_key_si(key_val)?;
let value = self.regs[a];
let lexical_ptr = self.realm.session.borrow().global_lexical().as_ptr() as *mut JsObject;
// SAFETY: 镜像对象钉在 session 永久区,指针在 VM 生命周期内有效。
let obj = unsafe { &mut *lexical_ptr };
self.set_or_create_prop_value(obj, si, value);
Ok(())
}
#[inline(always)]
pub(crate) fn dispatch_typeof(&mut self, rd: usize, a: usize) {
vm_trace!("TYPEOF rd={} r{}={:?}", rd, a, self.regs[a]);
+8
View File
@@ -811,6 +811,14 @@ impl Vm {
return Err(format!("DELETE_GLOBAL_PROP_C constant index {idx} is not a string key"));
}
let si = self.property_key_si(key_val)?;
// 全局词法环境绑定(顶层 let/const)不可删除:镜像命中恒 false 且属性保留。
{
let session = self.realm.session.borrow();
if self.resolve_property(session.global_lexical(), si).is_some() {
self.regs[rd] = JsValue::bool(false);
return Ok(());
}
}
let global_ptr = self.realm.session.borrow().global_object().as_ptr() as *mut JsObject;
// SAFETY: 全局对象钉在 session 永久区,指针在 VM 生命周期内有效。
let obj = unsafe { &mut *global_ptr };
+12
View File
@@ -281,6 +281,12 @@ impl Vm {
Err(e) => return Err(e),
},
OpCode::LOAD_GLOBAL_LEX => match self.dispatch_load_global_lex(rd, instr) {
Ok(true) => continue,
Ok(false) => {}
Err(e) => return Err(e),
},
OpCode::LOAD_GLOBAL_TYPEOF => {
self.dispatch_load_global_typeof(rd, instr)?;
}
@@ -581,6 +587,12 @@ impl Vm {
self.dispatch_delete_global_prop_c(rd, a, key_idx)?;
}
OpCode::STORE_GLOBAL_LEX => {
let key_idx = self.bytecode[self.pc] as u16;
self.pc += 1;
self.dispatch_store_global_lex(a, key_idx)?;
}
OpCode::CAN_DECLARE_GLOBAL_FUNC => {
self.dispatch_can_declare_global_func(rd, b)?;
}
+3
View File
@@ -530,6 +530,9 @@ impl Vm {
fn clear_full_reset_state(&mut self) {
self.clear_execution_state();
// 全局词法环境镜像随 session 隔离重建:顶层词法绑定不跨 run 存活,
// 旧镜像属性区释放并重建空对象(防上一 run 残留值被新 run 读到)。
self.realm.session.borrow_mut().clear_global_lexical();
// 模板对象缓存中的 JsValue 指向将被重建的 session 对象:全量重置后悬垂,
// 必须随 session 一并清空。
self.template_objects.clear();