diff --git a/crates/oxide_builtins/src/module.rs b/crates/oxide_builtins/src/module.rs index a26dd002..2897f155 100644 --- a/crates/oxide_builtins/src/module.rs +++ b/crates/oxide_builtins/src/module.rs @@ -1,9 +1,10 @@ //! 模块命名空间与求值辅助(import 实现内部用,非 JS 可见标准 API)。 //! //! 命名约定 `__module*`:由编译器模块 prelude 发出(`lookup_or_builtin` 解析为全局槽, -//! VM 在 frame push 时从 global 对象取回 native 函数)。当前为快照式链接: -//! 依赖模块先整体求值并返回命名空间对象,导入方从中读取导出值; -//! live binding / source-phase / defer 语义留待后续轮次。 +//! VM 在 frame push 时从 global 对象取回 native 函数)。依赖模块先整体求值并返回 +//! 命名空间对象,导入方从中读取导出值;依赖含可重赋导出时命名/默认导入经 +//! `__moduleGet` 活读,被捕获导出由 `__moduleSetCell` 挂共享 cell。 +//! source-phase / defer 语义留待后续轮次。 use oxide_kernel::shape_forge::EMPTY_SHAPE_ID; use oxide_runtime_api::{NativeResult, VmHost}; @@ -512,6 +513,60 @@ pub fn module_set(vm: &mut H, args: &[u8]) -> NativeResult { NativeResult::Ok(JsValue::undefined()) } +/// `__moduleSetCell(ns, name, cell_idx)`:把导出条目挂到模块顶层帧的共享 cell。 +/// +/// # 步骤 +/// 1. 校验 ns 为 module namespace 且已有条目表与同名条目(预注册保证存在)。 +/// 2. 取当前字节码帧第 `cell_idx` 个 cell(`VmHost::module_frame_cell`)。 +/// 3. 条目状态置 `Cell`,并把真实槽值同步为 cell 当前值一次。 +/// +/// # 边界与前提 +/// - 条目必须已预注册;无表、条目未命中或 cell 缺失均抛 TypeError,不静默退化。 +/// - `cell_idx` 来自 emit 期捕获索引,同一模块顶层帧内稳定。 +/// +/// # 副作用 +/// - 修改条目表状态与 ns 真实槽值;后续活值由共享 cell 承载。 +pub fn module_set_cell(vm: &mut H, args: &[u8]) -> NativeResult { + if args.len() < 4 { + return type_error(vm, "__moduleSetCell: 3 arguments required"); + } + let ns_val = vm.reg(args[1]); + let ns_ptr = match vm.checked_object_ptr(ns_val, "__moduleSetCell: target is not an object") { + Ok(Some(p)) => p, + Ok(None) => return type_error(vm, "__moduleSetCell: target is not an object"), + Err(e) => return NativeResult::Err(crate::error::create_error(vm, &e)), + }; + let name_val = vm.reg(args[2]); + let name_si = vm.property_key_si(name_val); + let cell_idx = oxide_runtime_api::to_int32(vm.reg(args[3])) as u32; + let obj = unsafe { &mut *ns_ptr }; + if !obj.is_module_namespace() { + return type_error(vm, "__moduleSetCell: target is not a module namespace"); + } + let table = ns_table_ptr(obj); + if table.is_null() { + return type_error(vm, "__moduleSetCell: export is not registered"); + } + // SAFETY: table 归本 ns 对象持有,生命周期见 `module_ns_export`。 + let Some(idx) = (unsafe { entry_index(&*table, name_si) }) else { + return type_error(vm, "__moduleSetCell: export is not registered"); + }; + let Some(cell) = vm.module_frame_cell(cell_idx) else { + return type_error(vm, "__moduleSetCell: cell is not available"); + }; + // SAFETY: cell 由 alloc_cell 分配并登记,至 full_reset 才释放。 + let value = unsafe { (*cell).value }; + // SAFETY: idx 来自刚完成的表内查找,仍在界内。 + unsafe { + (&mut (*table).entries)[idx].state = ModuleNsState::Cell(cell); + } + // 真实槽同步一次 cell 当前值:条目表是活值权威,槽仅保留存在性与占位。 + if let Some(pos) = vm.get_own_property_slot(obj, name_si) { + obj.set_prop_at(pos, value); + } + NativeResult::Ok(JsValue::undefined()) +} + /// `__moduleSetReexport(ns, name, value, dep_path, imported)`:注册显式再导出 /// (`export { x } from` / `export * as ns from` / 导入绑定再导出),记录跨模块 /// 来源身份 `(dep_path, imported)`,使同一绑定经不同路径转发时来源 token 相等。 diff --git a/crates/oxide_emit/src/capture/captured.rs b/crates/oxide_emit/src/capture/captured.rs index d406ecd1..a6d2b3ea 100644 --- a/crates/oxide_emit/src/capture/captured.rs +++ b/crates/oxide_emit/src/capture/captured.rs @@ -4,7 +4,7 @@ use std::collections::{BTreeMap, HashSet}; -use oxide_parser::{Expression, Statement}; +use oxide_parser::{Declaration, ExportDefaultDeclarationKind, Expression, Statement}; use super::collect_fn_param_names; use super::names::collect_own_binding_names; @@ -103,6 +103,60 @@ pub(crate) fn collect_captured_stmt(stmt: &Statement, own: &HashSet, out } collect_class_capture_names(&cd.body, own, &class_shadow, out); } + // export 包裹的变量/函数/类声明:捕获分析须下探声明体,否则被导出函数体内 + // 引用模块绑定(含自引用)不会被识别为捕获,闭包内读写退化为隐式全局。 + Statement::ExportNamedDeclaration(exp) => { + if let Some(decl) = &exp.declaration { + match decl { + Declaration::VariableDeclaration(vd) => { + for d in &vd.declarations { + collect_captured_binding_keys(&d.id, own, out); + if let Some(init) = &d.init { + collect_captured_expr(init, own, out); + } + } + } + Declaration::FunctionDeclaration(fd) => { + let body: &[Statement] = fd.body.as_ref().map(|b| &b.statements[..]).unwrap_or(&[]); + collect_fn_default_captured(&fd.params, own, out); + let mut fn_shadow = HashSet::new(); + fn_shadow.extend(collect_fn_param_names(&fd.params)); + fn_shadow.extend(collect_own_binding_names(&[], body)); + collect_capture_names_shadowed(body, own, &fn_shadow, out); + } + Declaration::ClassDeclaration(cd) => { + let mut class_shadow = HashSet::new(); + if let Some(id) = &cd.id { + class_shadow.insert(id.name.as_str().to_string()); + } + collect_class_capture_names(&cd.body, own, &class_shadow, out); + } + _ => {} + } + } + } + Statement::ExportDefaultDeclaration(exp) => match &exp.declaration { + ExportDefaultDeclarationKind::FunctionDeclaration(fd) => { + let body: &[Statement] = fd.body.as_ref().map(|b| &b.statements[..]).unwrap_or(&[]); + collect_fn_default_captured(&fd.params, own, out); + let mut fn_shadow = HashSet::new(); + fn_shadow.extend(collect_fn_param_names(&fd.params)); + fn_shadow.extend(collect_own_binding_names(&[], body)); + collect_capture_names_shadowed(body, own, &fn_shadow, out); + } + ExportDefaultDeclarationKind::ClassDeclaration(cd) => { + let mut class_shadow = HashSet::new(); + if let Some(id) = &cd.id { + class_shadow.insert(id.name.as_str().to_string()); + } + collect_class_capture_names(&cd.body, own, &class_shadow, out); + } + other => { + if let Some(e) = other.as_expression() { + collect_captured_expr(e, own, out); + } + } + }, Statement::ExpressionStatement(es) => collect_captured_expr(&es.expression, own, out), Statement::ReturnStatement(rs) => { if let Some(a) = &rs.argument { diff --git a/crates/oxide_emit/src/compile_ctx.rs b/crates/oxide_emit/src/compile_ctx.rs index bf1f511b..8c47a0ef 100644 --- a/crates/oxide_emit/src/compile_ctx.rs +++ b/crates/oxide_emit/src/compile_ctx.rs @@ -149,6 +149,19 @@ pub struct CompileCtx { /// 非自导入的导入局部名 → (依赖模块规范路径, 导入名)。命名空间导入的导入名为 /// `oxide_kernel::MODULE_NAMESPACE_BINDING`;再导出据此重分类为间接导出。 pub(crate) module_import_origins: HashMap, + /// 依赖 source 字符串 → 依赖模块是否含可重赋导出(`compile_js_dep` 回传; + /// Json/Text 数据模块恒 false)。导入方据此把命名/默认导入登记为活读。 + pub(crate) module_dep_reassignable: HashMap, + /// 导入本地名 → (依赖命名空间对象寄存器, 导出名):可重赋依赖的命名/默认导入 + /// 活读映射。顶层读点据此改发 `__moduleGet` 活读;仅模块顶层 ctx 填充,嵌套 + /// 函数 ctx 不继承,闭包内读退化为链接期快照。 + pub(crate) module_live_imports: HashMap, + /// 本模块是否含可重赋导出。仅在依赖模块(`top_level == false`)且捕获/写穿 + /// 消费者需要时置真;入口模块恒假,保证纯导出入口 IR 零变化。 + pub(crate) module_live_dep: bool, + /// live 命名空间激活:自导入 ns 或本模块含可重赋导出时为真。写穿反演与 + /// 导出预注册据此发射;非 live 模块编译产物逐字节不变。 + pub(crate) module_live_ns_active: bool, /// 自导入(import from 自身)的 source 字符串集合:绑定走别名语义,不能链接期快照。 pub(crate) module_self_import_specs: HashSet, /// 自导入别名:导出名 → 本地绑定槽寄存器(export 语句执行时回写绑定值)。 @@ -240,6 +253,10 @@ impl CompileCtx { module_dep_ns_regs: HashMap::new(), module_dep_paths: HashMap::new(), module_import_origins: HashMap::new(), + module_dep_reassignable: HashMap::new(), + module_live_imports: HashMap::new(), + module_live_dep: false, + module_live_ns_active: false, module_self_import_specs: HashSet::new(), module_self_aliases: HashMap::new(), module_alias_pairs: Vec::new(), diff --git a/crates/oxide_emit/src/expr/identifier.rs b/crates/oxide_emit/src/expr/identifier.rs index cac60dae..a474bce6 100644 --- a/crates/oxide_emit/src/expr/identifier.rs +++ b/crates/oxide_emit/src/expr/identifier.rs @@ -10,8 +10,18 @@ use oxide_ir::inst::Inst; use oxide_ir::operand::Operand; impl Emitter { - /// 静态标识符读取:upvalue / 被捕获 cell / 全局槽。 + /// 静态标识符读取:可重赋依赖导入活读 / upvalue / 被捕获 cell / 全局槽。 + /// + /// # 注意事项 + /// - 活读映射仅模块顶层 ctx 填充:嵌套函数内读 import 名仍走链接期快照 + /// (`current_upvalue_captures` 不在此列,闭包内不继承映射)。 fn emit_static_identifier_read(&self, name: &str, ctx: &mut CompileCtx) -> Result { + // 可重赋依赖的命名/默认导入:读点直接查依赖命名空间当前值,跟随源模块重赋。 + if let Some((dep_ns, exported)) = ctx.module_live_imports.get(name).cloned() { + let name_reg = self.load_string_const(&exported, ctx); + return self.emit_module_call(ctx, "__moduleGet", &[dep_ns, name_reg]); + } + for (uv_idx, up) in ctx.current_upvalue_captures.iter().enumerate() { if up.name == name { let r = ctx.alloc_reg(); diff --git a/crates/oxide_emit/src/module.rs b/crates/oxide_emit/src/module.rs index e6f046fe..5e402159 100644 --- a/crates/oxide_emit/src/module.rs +++ b/crates/oxide_emit/src/module.rs @@ -1,10 +1,11 @@ //! 模块编译:import/export 解析、依赖加载(递归)、快照式链接与命名空间。 //! -//! 普通依赖的导入绑定在 prelude 一次性链接为快照;自导入(`import ... from -//! 自身`)的局部名解析为本模块源绑定的活别名,TDZ/提升/活值/不可变四语义全部 -//! 委托源绑定。`export` 语句就地注册导出值。 -//! 未支持:跨模块 live binding(导入绑定不跟随依赖模块值变化)、defer(延迟 -//! 求值)、source-phase;star 转发的自导入名退化为链接期快照;循环导入在编译期跳过。 +//! 普通依赖的导入绑定在 prelude 链接:依赖含可重赋导出时顶层读点经 +//! `__moduleGet` 活读,其余退化为快照;被闭包捕获的导出挂共享 cell,闭包内写 +//! 随活读可见。自导入(`import ... from 自身`)的局部名解析为本模块源绑定的活 +//! 别名,TDZ/提升/活值/不可变四语义全部委托源绑定。`export` 语句就地注册导出值。 +//! 未支持:嵌套闭包内读 import 名(仍为链接期快照)、defer(延迟求值)、 +//! source-phase;star 转发与经再导出链的活值仍为快照;循环导入在编译期跳过。 use std::collections::{HashMap, HashSet}; use std::path::Path; @@ -248,6 +249,62 @@ fn module_var_scoped_export_names(body: &[Statement]) -> HashSet { names } +/// 判断模块是否含可重赋导出:导出源绑定既非顶层 const 也非导入本地名时为真。 +/// +/// # 边界与前提 +/// - 依据 `module_export_name_map`(导出名 → 源绑定名)与顶层 const 绑定名集合: +/// var/let/function/class 与 default(含合成 `*default*`)均判真,const-only 判假。 +/// - 经导入本地名再导出(`export { x }` 底层为 import 绑定)不判真:源绑定不可重赋, +/// 其可变性归来源模块。star 转发名不在映射表内,天然不参与判定。 +/// +/// # 注意事项 +/// - 保守超集:`export default 42` 之类不可重赋值也判真,仅使该依赖多一次预注册, +/// 导入方读值等价。 +fn module_has_reassignable_export(body: &[Statement], export_name_map: &HashMap) -> bool { + let mut const_names = HashSet::new(); + let mut import_names = HashSet::new(); + for stmt in body { + match stmt { + Statement::VariableDeclaration(vd) => { + if matches!(vd.kind, VariableDeclarationKind::Const) { + for d in &vd.declarations { + if let BindingPattern::BindingIdentifier(bi) = &d.id { + const_names.insert(bi.name.to_string()); + } + } + } + } + Statement::ExportNamedDeclaration(exp) => { + if let Some(Declaration::VariableDeclaration(vd)) = &exp.declaration { + if matches!(vd.kind, VariableDeclarationKind::Const) { + for d in &vd.declarations { + if let BindingPattern::BindingIdentifier(bi) = &d.id { + const_names.insert(bi.name.to_string()); + } + } + } + } + } + Statement::ImportDeclaration(imp) => { + if let Some(specifiers) = &imp.specifiers { + for sp in specifiers { + let local = match sp { + ImportDeclarationSpecifier::ImportSpecifier(s) => &s.local.name, + ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => &s.local.name, + ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => &s.local.name, + }; + import_names.insert(local.to_string()); + } + } + } + _ => {} + } + } + export_name_map + .values() + .any(|source| !const_names.contains(source) && !import_names.contains(source)) +} + /// 判断语句是否为提升的函数声明(含 export 包装的函数声明)。 fn is_hoisted_function_decl(stmt: &Statement) -> bool { match stmt { @@ -436,14 +493,15 @@ impl Emitter { return Err(format!("circular module import not supported: {}", resolved.path)); } path_stack.push(resolved.path.clone()); - let dep_ir = match resolved.kind { + let (dep_ir, dep_reassignable) = match resolved.kind { ModuleKind::Js => self.compile_js_dep(&resolved, loader, path_stack)?, - ModuleKind::Json => self.compile_data_dep("json", &resolved.source)?, - ModuleKind::Text => self.compile_data_dep("text", &resolved.source)?, + ModuleKind::Json => (self.compile_data_dep("json", &resolved.source)?, false), + ModuleKind::Text => (self.compile_data_dep("text", &resolved.source)?, false), ModuleKind::Bytes => return Err("bytes module not supported".into()), }; path_stack.pop(); ctx.nested.push(dep_ir); + ctx.module_dep_reassignable.insert(spec.clone(), dep_reassignable); let fn_reg = ctx.alloc_reg(); ctx.inst(Inst::create_closure(Operand::Reg(fn_reg), ctx.nested.len() as u16)); self.emit_module_call(ctx, "__moduleEval", &[fn_reg])? @@ -468,6 +526,9 @@ impl Emitter { // 导出名 → 本模块源绑定名:self-import 的局部名别名到该源绑定。star 转发的 // 自导入名不在表内(退化为下方非别名占位路径)。 let export_name_map = module_export_name_map(body, &ctx.module_self_import_specs); + // 依赖模块的可重赋导出标志:仅依赖模块(非入口)参与判定,入口模块恒假, + // 使纯导出入口(含 `export let x`)的 IR 逐字节不变。 + ctx.module_live_dep = !top_level && module_has_reassignable_export(body, &export_name_map); // live 命名空间门控:仅自导入 `import * as ns from './self'` 的模块需要预注册 // 真实导出槽,其余模块(含普通外部命名空间导入)编译产物逐字节不变。 let mut has_self_ns_import = false; @@ -517,6 +578,12 @@ impl Emitter { false, ctx, )?; + // 依赖含可重赋导出:登记活读映射,顶层读点改走 + // `__moduleGet`,快照绑定仅服务嵌套闭包读。 + if ctx.module_dep_reassignable.get(&dep_spec).copied().unwrap_or(false) { + ctx.module_live_imports + .insert(s.local.name.to_string(), (dep_ns_reg, imported.clone())); + } } } ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => { @@ -563,6 +630,13 @@ impl Emitter { false, ctx, )?; + // 默认导入同命名导入:可重赋依赖的 default 导出活读。 + if ctx.module_dep_reassignable.get(&dep_spec).copied().unwrap_or(false) { + ctx.module_live_imports.insert( + s.local.name.to_string(), + (dep_ns_reg, DEFAULT_EXPORT_NAME.to_string()), + ); + } } } ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => { @@ -591,11 +665,16 @@ impl Emitter { } } - // —— 写穿反演:源绑定槽寄存器 → 导出名集合。仅 live 模块(自导入 ns)填充: + // live 命名空间激活:自导入 ns(本模块自身观测 ns)或本模块含可重赋导出 + // (外部导入方需活读)。非 live 模块写穿与预注册均不发射,IR 逐字节不变。 + let live_active = has_self_ns_import || ctx.module_live_dep; + ctx.module_live_ns_active = live_active; + + // —— 写穿反演:源绑定槽寄存器 → 导出名集合。仅 live 模块填充: // 非 live 模块不激活写穿,编译产物逐字节零 IR 变化。以槽位为键使块级/catch // 同名遮蔽解析到内层槽时不命中;按名排序保证多导出名映射到同一源绑定的 // 发射顺序确定。—— - if has_self_ns_import { + if live_active { let mut pairs: Vec<(&String, &String)> = export_name_map.iter().map(|(exported, source)| (source, exported)).collect(); pairs.sort(); @@ -611,7 +690,7 @@ impl Emitter { // 按名序预注册全部本地导出名,使自导入 ns 在读点先观察到未初始化状态。 // var/函数类导出(VarScopedDeclarations)在实例化期即初始化为 undefined, // lexical/class 保持未初始化(TDZ)。 —— - if has_self_ns_import { + if live_active { let var_scoped = module_var_scoped_export_names(body); let mut export_names: Vec = own_export_names.into_iter().collect(); export_names.sort(); @@ -709,10 +788,17 @@ impl Emitter { Ok(()) } - /// 编译 JS 依赖模块(递归)。 + /// 编译 JS 依赖模块(递归),回传 IR 与「是否含可重赋导出」标志。 + /// + /// # 边界与前提 + /// - 标志取自依赖 ctx 的 `module_live_dep`;入口模块不参与,依赖模块恒按 + /// `!top_level` 判定,故 const-only 依赖回传 false。 + /// + /// # 注意事项 + /// - 导入方据标志决定是否登记活读映射;标志不改变本模块 IR。 fn compile_js_dep( &self, resolved: &ResolvedModule, loader: &mut dyn ModuleSourceLoader, path_stack: &mut Vec, - ) -> Result { + ) -> Result<(IRFunction, bool), String> { let alloc = oxide_parser::Allocator::default(); let program = oxide_parser::parse_module(&alloc, &resolved.source).map_err(|errs| { format!( @@ -725,7 +811,8 @@ impl Emitter { // 依赖模块经 parse_module 解析,顶层恒严格模式。 ctx.is_strict = true; self.emit_module_into_ctx(&program, &resolved.path, loader, path_stack, &mut ctx, false)?; - Ok(ctx.assemble_ir(ParamLayout { base: 0, count: 0 }, None)) + let reassignable = ctx.module_live_dep; + Ok((ctx.assemble_ir(ParamLayout { base: 0, count: 0 }, None), reassignable)) } /// 编译数据模块(json/text):body 仅为 __moduleData 调用 + RETURN。 @@ -759,7 +846,7 @@ impl Emitter { Ok(result_reg) } - fn load_string_const(&self, s: &str, ctx: &mut CompileCtx) -> u32 { + pub(crate) fn load_string_const(&self, s: &str, ctx: &mut CompileCtx) -> u32 { let idx = ctx.add_constant(Constant::String(s.to_string())); let reg = ctx.alloc_reg(); ctx.inst(Inst::load_const(Operand::Reg(reg), idx)); @@ -803,6 +890,53 @@ impl Emitter { Ok(()) } + /// 发 `__moduleSetCell`:把导出条目挂到源绑定的共享 cell(cell_idx 常量传参)。 + /// + /// # 边界与前提 + /// - `cell_idx` 来自 `ctx.captured_bindings`,在同一模块顶层帧的 `cell_stack` + /// 内由声明点的 MAKE_CELL 建好;导出注册点晚于声明点,故 cell 必已存在。 + /// + /// # 副作用 + /// - 依赖运行期 `module_frame_cell(cell_idx)` 取当前字节码帧 cell,改命名空间 + /// 条目状态为共享 cell。 + fn emit_module_set_cell( + &self, ctx: &mut CompileCtx, ns_reg: u32, export_name: &str, cell_idx: u8, + ) -> Result<(), String> { + let name_reg = self.load_string_const(export_name, ctx); + let idx = ctx.add_constant(Constant::Int(cell_idx as i32)); + let cell_reg = ctx.alloc_reg(); + ctx.inst(Inst::load_const(Operand::Reg(cell_reg), idx)); + self.emit_module_call(ctx, "__moduleSetCell", &[ns_reg, name_reg, cell_reg])?; + Ok(()) + } + + /// 导出注册:被捕获的源绑定在 live 模块挂共享 cell(闭包写随活读可见), + /// 其余回退 `__moduleSet` 快照/写穿路径。 + /// + /// # 边界与前提 + /// - 仅 `module_live_ns_active` 模块激活;非 live 模块恒回退 `__moduleSet`, + /// 编译产物与引入 Cell 前逐字节一致。 + /// - `source_name` 命中 `module_live_imports`(导入快照)时回退:不得把导入 + /// 本地槽误注册为活条目。 + /// - 源绑定须在 `captured_bindings` 内且当前作用域可解析(`visible_binding_reg` + /// 有值)——块退出后残留的同名 cell 不可见,此时回退快照注册。 + /// + /// # 副作用 + /// - 命中时发 `__moduleSetCell`(条目状态改 Cell);否则发 `__moduleSet`。 + fn emit_export_registration( + &self, ctx: &mut CompileCtx, ns_reg: u32, exported_name: &str, source_name: &str, value_reg: u32, + ) -> Result<(), String> { + if ctx.module_live_ns_active + && !ctx.module_live_imports.contains_key(source_name) + && ctx.visible_binding_reg(source_name).is_some() + { + if let Some(&cell_idx) = ctx.captured_bindings.get(source_name) { + return self.emit_module_set_cell(ctx, ns_reg, exported_name, cell_idx); + } + } + self.emit_module_set(ctx, ns_reg, exported_name, value_reg) + } + /// 发 `__moduleSetReexport`:注册带 `(dep_path, imported)` 来源身份的再导出。 fn emit_module_set_reexport( &self, ctx: &mut CompileCtx, ns_reg: u32, export_name: &str, value_reg: u32, dep_path: &str, imported: &str, @@ -919,7 +1053,7 @@ impl Emitter { if let BindingPattern::BindingIdentifier(bi) = &d.id { let name = bi.name.as_str(); let val_reg = self.load_var_reg(name, ctx)?; - self.emit_module_set(ctx, ns_reg, name, val_reg)?; + self.emit_export_registration(ctx, ns_reg, name, name, val_reg)?; self.emit_self_alias_write(ctx, name, val_reg); } } @@ -929,7 +1063,7 @@ impl Emitter { if let Some(id) = &fd.id { let name = id.name.as_str(); let val_reg = self.load_var_reg(name, ctx)?; - self.emit_module_set(ctx, ns_reg, name, val_reg)?; + self.emit_export_registration(ctx, ns_reg, name, name, val_reg)?; self.emit_self_alias_write(ctx, name, val_reg); } } @@ -938,7 +1072,7 @@ impl Emitter { if let Some(id) = &cl.id { let name = id.name.as_str(); let val_reg = self.load_var_reg(name, ctx)?; - self.emit_module_set(ctx, ns_reg, name, val_reg)?; + self.emit_export_registration(ctx, ns_reg, name, name, val_reg)?; self.emit_self_alias_write(ctx, name, val_reg); } } @@ -982,12 +1116,12 @@ impl Emitter { } // 本地导出:来源恒为 Local。 let val_reg = self.load_var_reg(&local_name, ctx)?; - self.emit_module_set(ctx, ns_reg, &exported_name, val_reg)?; + self.emit_export_registration(ctx, ns_reg, &exported_name, &local_name, val_reg)?; } } } Statement::ExportDefaultDeclaration(exp) => { - let val_reg = match &exp.declaration { + let (val_reg, source_name) = match &exp.declaration { ExportDefaultDeclarationKind::FunctionDeclaration(fd) => { let reg = self.emit_function_expression(fd, ctx)?; // 具名 default 函数绑定自身 id 名;匿名 default 函数绑定合成 @@ -1000,13 +1134,14 @@ impl Emitter { .lookup_any(SYNTHETIC_DEFAULT_BINDING) .map(|_| SYNTHETIC_DEFAULT_BINDING.to_string()) }); - if let Some(name) = binding { - self.emit_bind_target(&name, reg, VariableDeclarationKind::Var, false, false, ctx)?; + if let Some(name) = &binding { + self.emit_bind_target(name, reg, VariableDeclarationKind::Var, false, false, ctx)?; } if fd.id.is_none() { set_implicit_name_of_last_nested(ctx, DEFAULT_EXPORT_NAME); } - reg + // 具名 default 的注册源是该 id 绑定(可重赋),匿名退化为合成名。 + (reg, binding.unwrap_or_else(|| SYNTHETIC_DEFAULT_BINDING.to_string())) } ExportDefaultDeclarationKind::ClassDeclaration(cl) => { let reg = self.emit_class_with_binding( @@ -1024,19 +1159,22 @@ impl Emitter { false, ctx, )?; - } else if ctx.scopes.symbols.lookup_any(SYNTHETIC_DEFAULT_BINDING).is_some() { - // 匿名 default 类按规范在求值点初始化合成 `*default*` - // 绑定(仅在存在 self-import default 时惰性建立)。 - self.emit_bind_target( - SYNTHETIC_DEFAULT_BINDING, - reg, - VariableDeclarationKind::Let, - false, - false, - ctx, - )?; + (reg, id.name.to_string()) + } else { + if ctx.scopes.symbols.lookup_any(SYNTHETIC_DEFAULT_BINDING).is_some() { + // 匿名 default 类按规范在求值点初始化合成 `*default*` + // 绑定(仅在存在 self-import default 时惰性建立)。 + self.emit_bind_target( + SYNTHETIC_DEFAULT_BINDING, + reg, + VariableDeclarationKind::Let, + false, + false, + ctx, + )?; + } + (reg, SYNTHETIC_DEFAULT_BINDING.to_string()) } - reg } other => { let expr = other @@ -1072,10 +1210,10 @@ impl Emitter { ctx, )?; } - reg + (reg, SYNTHETIC_DEFAULT_BINDING.to_string()) } }; - self.emit_module_set(ctx, ns_reg, DEFAULT_EXPORT_NAME, val_reg)?; + self.emit_export_registration(ctx, ns_reg, DEFAULT_EXPORT_NAME, &source_name, val_reg)?; self.emit_self_alias_write(ctx, DEFAULT_EXPORT_NAME, val_reg); } Statement::ExportAllDeclaration(exp) => { diff --git a/crates/oxide_emit/tests/module_link_tests.rs b/crates/oxide_emit/tests/module_link_tests.rs index 5f01e552..08c314a6 100644 --- a/crates/oxide_emit/tests/module_link_tests.rs +++ b/crates/oxide_emit/tests/module_link_tests.rs @@ -326,3 +326,43 @@ fn write_through_only_for_live_modules() { let (ir, _) = emit_module("export let a = 1; a = 2;", "./entry.js", &[]).expect("非 live 模块应编译成功"); assert_eq!(native_calls_to(&ir, "__moduleSet"), 1, "非 live 模块重赋不得写穿"); } + +/// 面 12:可重赋依赖激活活读与 Cell——依赖预注册并挂共享 cell,导入方顶层读走 +/// `__moduleGet`;const-only 依赖零变化,导入方仍走链接期快照、依赖不预注册。 +#[test] +fn reassignable_dep_activates_live_read_and_cell() { + let (ir, _) = emit_module( + "import { x } from './dep.js'; x;", + "./entry.js", + &[("./dep.js", ModuleKind::Js, "export const x = 1;")], + ) + .expect("const-only 依赖应编译成功"); + assert_eq!(native_calls_to(&ir, "__moduleGet"), 0, "const-only 依赖导入方不得活读"); + assert!(native_calls_to(&ir, "__moduleLinkGet") >= 1, "导入绑定仍走链接期快照"); + let dep = &ir.nested[0]; + assert_eq!(native_calls_to(dep, "__modulePreRegister"), 0, "const-only 依赖不得预注册"); + assert_eq!(native_calls_to(dep, "__moduleSetCell"), 0, "const-only 依赖不得挂 cell"); + + let (ir, _) = emit_module( + "import { x } from './dep.js'; x;", + "./entry.js", + &[( + "./dep.js", + ModuleKind::Js, + "var x = 1; export { x }; globalThis.f = function(){ x = 2; };", + )], + ) + .expect("可重赋依赖应编译成功"); + let dep = &ir.nested[0]; + assert!(native_calls_to(dep, "__modulePreRegister") >= 1, "可重赋依赖应预注册导出"); + assert!(native_calls_to(dep, "__moduleSetCell") >= 1, "被捕获导出应挂共享 cell"); + assert!(native_calls_to(&ir, "__moduleGet") >= 1, "导入方顶层读应活读"); +} + +/// 面 13:入口模块的可重赋导出不激活预注册(`module_live_dep` 仅依赖模块成立)。 +#[test] +fn entry_module_reassignable_export_not_pre_registered() { + let (ir, _) = emit_module("export let x = 1; x = 2;", "./entry.js", &[]).expect("入口模块应编译成功"); + assert_eq!(native_calls_to(&ir, "__modulePreRegister"), 0, "入口模块不得预注册"); + assert_eq!(native_calls_to(&ir, "__moduleSetCell"), 0, "入口模块不得挂 cell"); +} diff --git a/crates/oxide_runtime_api/src/host.rs b/crates/oxide_runtime_api/src/host.rs index 2d5878a8..8961d3d6 100644 --- a/crates/oxide_runtime_api/src/host.rs +++ b/crates/oxide_runtime_api/src/host.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::mem::Epoch; -use oxide_types::object::{JsObject, PropAttributes}; +use oxide_types::object::{Cell, JsObject, PropAttributes}; use oxide_types::value::JsValue; /// builtins 依赖的 `Vm` 能力集合。 @@ -157,6 +157,12 @@ pub trait VmHost { fn step_rng(&mut self); fn math_rng_value(&self) -> f64; fn sub_module_function_name(&self, gen: u32, sub_idx: u16) -> String; + /// 取当前字节码帧 `cell_stack.last()[cell_idx]` 的共享 cell 指针。 + /// + /// # 边界与前提 + /// - 仅在同一 VM 世代的字节码帧执行期读取:native 调用不压 `cell_stack`,故 + /// builtin 内读到的是调用方模块帧;`cell_idx` 越界或槽为空返回 `None`。 + fn module_frame_cell(&self, cell_idx: u32) -> Option<*mut Cell>; /// 动态编译一个函数体(`Function` 构造器用):把参数列表与函数体编译为可调用 /// 函数对象。编译或解析失败返回 `Err`,由调用方转为 `SyntaxError`。 fn create_dynamic_function(&mut self, params: &[String], body: &str) -> Result; diff --git a/crates/oxide_vm/src/bindings/bind_global.rs b/crates/oxide_vm/src/bindings/bind_global.rs index 0ad335ad..81010654 100644 --- a/crates/oxide_vm/src/bindings/bind_global.rs +++ b/crates/oxide_vm/src/bindings/bind_global.rs @@ -34,6 +34,11 @@ pub fn bind_global(core: &Arc, session: &KernelSession, global: &mut 3, ), ("__moduleSet", oxide_builtins::module::module_set:: as *const (), 3), + ( + "__moduleSetCell", + oxide_builtins::module::module_set_cell:: as *const (), + 3, + ), ( "__moduleSetReexport", oxide_builtins::module::module_set_reexport:: as *const (), diff --git a/crates/oxide_vm/src/vm/vmhost.rs b/crates/oxide_vm/src/vm/vmhost.rs index fd8265b2..0a8ba4bf 100644 --- a/crates/oxide_vm/src/vm/vmhost.rs +++ b/crates/oxide_vm/src/vm/vmhost.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use oxide_kernel::kernel::{KernelCore, KernelSession}; use oxide_types::mem::Epoch; -use oxide_types::object::{JsObject, PropAttributes}; +use oxide_types::object::{Cell, JsObject, PropAttributes}; use oxide_types::value::JsValue; use super::Vm; @@ -161,6 +161,12 @@ impl oxide_runtime_api::VmHost for Vm { .and_then(|m| m.function_name.clone()) .unwrap_or_default() } + fn module_frame_cell(&self, cell_idx: u32) -> Option<*mut Cell> { + self.cell_stack + .last() + .and_then(|cells| cells.get(cell_idx as usize).copied()) + .filter(|p| !p.is_null()) + } fn create_dynamic_function(&mut self, params: &[String], body: &str) -> Result { self.create_dynamic_function(params, body) } diff --git a/crates/oxide_vm/tests/module_namespace_tests.rs b/crates/oxide_vm/tests/module_namespace_tests.rs index d32f4052..00b53ad5 100644 --- a/crates/oxide_vm/tests/module_namespace_tests.rs +++ b/crates/oxide_vm/tests/module_namespace_tests.rs @@ -338,3 +338,75 @@ fn external_namespace_enumeration_stays_unaffected() { let result = run_namespace_module(&dir); assert_eq!(result, "z|1|1", "非 live 外部命名空间枚举行为不应变化"); } + +/// 跨模块可重赋导出活读:依赖模块经闭包重赋源绑定后,导入方命名绑定读到新值。 +#[test] +fn cross_module_named_import_reads_live_value() { + let cwd = std::env::current_dir().expect("cwd"); + let dir = cwd.join("__module_cross_live__"); + std::fs::create_dir_all(&dir).expect("mkdir"); + std::fs::write( + dir.join("dep.mjs"), + "var x = 1;\nexport { x };\nglobalThis.__bump = function () { x = 2; };", + ) + .expect("write dep"); + std::fs::write( + dir.join("main.mjs"), + "import { x } from './dep.mjs';\n \ + const before = x;\n \ + globalThis.__bump();\n \ + const after = x;\n \ + globalThis.__ns = [before, after].join('|');", + ) + .expect("write main"); + let _cleanup = Cleanup(dir.clone()); + + let result = run_namespace_module(&dir); + assert_eq!(result, "1|2", "命名导入未跟随依赖源绑定重赋"); +} + +/// 一源两别名:同一导出的两个导入局部名经活读同步更新,且互不分裂。 +#[test] +fn cross_module_aliased_imports_read_live_value() { + let cwd = std::env::current_dir().expect("cwd"); + let dir = cwd.join("__module_cross_live_alias__"); + std::fs::create_dir_all(&dir).expect("mkdir"); + std::fs::write( + dir.join("dep.mjs"), + "var x = 1;\nexport { x };\nglobalThis.__bump = function () { x = 2; };", + ) + .expect("write dep"); + std::fs::write( + dir.join("main.mjs"), + "import { x as y, x as z } from './dep.mjs';\n \ + const before = [y, z];\n \ + globalThis.__bump();\n \ + globalThis.__ns = [before[0], before[1], y, z].join('|');", + ) + .expect("write main"); + let _cleanup = Cleanup(dir.clone()); + + let result = run_namespace_module(&dir); + assert_eq!(result, "1|1|2|2", "一源两别名未同步读到重赋后的活值"); +} + +/// 默认导出具名函数体的自引用重赋:函数体内 `fn = 2` 写模块级绑定, +/// 导入方再次读 default 得新值(含捕获分析下探 export 声明与共享 cell)。 +#[test] +fn cross_module_default_export_reads_live_value() { + let cwd = std::env::current_dir().expect("cwd"); + let dir = cwd.join("__module_cross_live_default__"); + std::fs::create_dir_all(&dir).expect("mkdir"); + std::fs::write(dir.join("dep.mjs"), "export default function fn() { fn = 2; return 1; }").expect("write dep"); + std::fs::write( + dir.join("main.mjs"), + "import val from './dep.mjs';\n \ + const ret = val();\n \ + globalThis.__ns = [ret, val].join('|');", + ) + .expect("write main"); + let _cleanup = Cleanup(dir.clone()); + + let result = run_namespace_module(&dir); + assert_eq!(result, "1|2", "default 导出的具名函数自引用重赋未活读"); +}