diff --git a/Cargo.lock b/Cargo.lock index 10a04fcb..61e9fba3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 8353cac3..f20be145 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.19.32" +version = "0.19.33" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_bytecode/src/opcode.rs b/crates/oxide_bytecode/src/opcode.rs index 789dea7e..2b8b84df 100644 --- a/crates/oxide_bytecode/src/opcode.rs +++ b/crates/oxide_bytecode/src/opcode.rs @@ -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 diff --git a/crates/oxide_compiler/src/compiler_service.rs b/crates/oxide_compiler/src/compiler_service.rs index 1e61e10b..a9ccd4ae 100644 --- a/crates/oxide_compiler/src/compiler_service.rs +++ b/crates/oxide_compiler/src/compiler_service.rs @@ -25,6 +25,11 @@ impl CompilerService for DefaultCompilerService { fn compile_eval_script(&self, source: &str) -> Result { 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) diff --git a/crates/oxide_emit/src/class/emit.rs b/crates/oxide_emit/src/class/emit.rs index ba0d8801..a93b41d2 100644 --- a/crates/oxide_emit/src/class/emit.rs +++ b/crates/oxide_emit/src/class/emit.rs @@ -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( diff --git a/crates/oxide_emit/src/compile_ctx.rs b/crates/oxide_emit/src/compile_ctx.rs index 52cd7425..6e79483e 100644 --- a/crates/oxide_emit/src/compile_ctx.rs +++ b/crates/oxide_emit/src/compile_ctx.rs @@ -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 永不成功)。名字在只读 /// 名单内且解析到全局内置槽;声明臂与赋值臂共用本谓词。 diff --git a/crates/oxide_emit/src/expr/assignment.rs b/crates/oxide_emit/src/expr/assignment.rs index c6a8d1a1..2a2ef077 100644 --- a/crates/oxide_emit/src/expr/assignment.rs +++ b/crates/oxide_emit/src/expr/assignment.rs @@ -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( diff --git a/crates/oxide_emit/src/expr/identifier.rs b/crates/oxide_emit/src/expr/identifier.rs index f7ac9385..b0b77882 100644 --- a/crates/oxide_emit/src/expr/identifier.rs +++ b/crates/oxide_emit/src/expr/identifier.rs @@ -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(()) diff --git a/crates/oxide_emit/src/expr/operator/mod.rs b/crates/oxide_emit/src/expr/operator/mod.rs index 4aba1348..56495918 100644 --- a/crates/oxide_emit/src/expr/operator/mod.rs +++ b/crates/oxide_emit/src/expr/operator/mod.rs @@ -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)?; diff --git a/crates/oxide_emit/src/prepass/declare.rs b/crates/oxide_emit/src/prepass/declare.rs index 2c2e8d89..4c686a95 100644 --- a/crates/oxide_emit/src/prepass/declare.rs +++ b/crates/oxide_emit/src/prepass/declare.rs @@ -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 占位)。声明点与闭包捕获据 // 绑定字段走新索引,不原位覆写函数级同名绑定的名字排序索引; diff --git a/crates/oxide_emit/src/shared/binding.rs b/crates/oxide_emit/src/shared/binding.rs index f9e20470..de0c8187 100644 --- a/crates/oxide_emit/src/shared/binding.rs +++ b/crates/oxide_emit/src/shared/binding.rs @@ -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)?; } diff --git a/crates/oxide_emit/src/shared/chain.rs b/crates/oxide_emit/src/shared/chain.rs index d5107ada..eded1750 100644 --- a/crates/oxide_emit/src/shared/chain.rs +++ b/crates/oxide_emit/src/shared/chain.rs @@ -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 } diff --git a/crates/oxide_ir/src/inst.rs b/crates/oxide_ir/src/inst.rs index 712bea78..047d5b05 100644 --- a/crates/oxide_ir/src/inst.rs +++ b/crates/oxide_ir/src/inst.rs @@ -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。 diff --git a/crates/oxide_kernel/src/kernel/session.rs b/crates/oxide_kernel/src/kernel/session.rs index 5def4d2d..9d43f36c 100644 --- a/crates/oxide_kernel/src/kernel/session.rs +++ b/crates/oxide_kernel/src/kernel/session.rs @@ -19,6 +19,10 @@ use super::{BuiltinDirtySet, BuiltinId, BuiltinSnapshot, KernelCore}; pub struct KernelSession { pub builtin_world: Arc, pub global_object: P, + /// 全局词法环境镜像:普通对象(Object.prototype),顶层 let/const 声明与赋值 + /// 双写此对象,使跨脚本(间接 eval / 宿主 evalScript)读经镜像可见顶层词法 + /// 绑定。会话级持久,full_reset 随 session 重建。 + pub global_lexical: P, 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 { + &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 家族),并返回脏集合。 diff --git a/crates/oxide_vm/src/dispatch/misc.rs b/crates/oxide_vm/src/dispatch/misc.rs index 3ab222d7..48f9243c 100644 --- a/crates/oxide_vm/src/dispatch/misc.rs +++ b/crates/oxide_vm/src/dispatch/misc.rs @@ -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 { + 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]); diff --git a/crates/oxide_vm/src/dispatch/property.rs b/crates/oxide_vm/src/dispatch/property.rs index 0118a609..ace0cf61 100644 --- a/crates/oxide_vm/src/dispatch/property.rs +++ b/crates/oxide_vm/src/dispatch/property.rs @@ -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 }; diff --git a/crates/oxide_vm/src/vm/dispatch_loop.rs b/crates/oxide_vm/src/vm/dispatch_loop.rs index 7ed44252..f390dc36 100644 --- a/crates/oxide_vm/src/vm/dispatch_loop.rs +++ b/crates/oxide_vm/src/vm/dispatch_loop.rs @@ -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)?; } diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index cafac2ee..1d9b2e08 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -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();