diff --git a/crates/oxide_bytecode/src/module.rs b/crates/oxide_bytecode/src/module.rs index cd31cf9f..b8de0524 100644 --- a/crates/oxide_bytecode/src/module.rs +++ b/crates/oxide_bytecode/src/module.rs @@ -64,6 +64,9 @@ pub struct CompiledModule { pub needs_home_object: bool, pub upvalue_captures: Vec, pub cells_needed: u8, + /// 全局扁平模块 id:编译末端 flatten 阶段分配(顶层 0,子模块 DFS 递增)。 + /// `CREATE_CLOSURE` 的 imm16 在 flatten 后即此 id,运行时以它为平表下标。 + pub flat_id: u32, } impl CompiledModule { @@ -85,6 +88,7 @@ impl CompiledModule { needs_home_object: false, upvalue_captures: Vec::new(), cells_needed: 0, + flat_id: 0, } } } @@ -113,6 +117,7 @@ impl Clone for CompiledModule { needs_home_object: self.needs_home_object, upvalue_captures: self.upvalue_captures.clone(), cells_needed: self.cells_needed, + flat_id: self.flat_id, } } } diff --git a/crates/oxide_compiler/src/compiler.rs b/crates/oxide_compiler/src/compiler.rs index 61e52554..5029c0d1 100644 --- a/crates/oxide_compiler/src/compiler.rs +++ b/crates/oxide_compiler/src/compiler.rs @@ -58,7 +58,9 @@ impl Compiler { oxide_regalloc::alloc(&mut ir, &live2)?; // 无可行染色 → RangeError 上抛 } crate::compiler_debug!("compile: done, {} instructions", ir.insts.len()); - lower(&ir) + let mut module = lower(&ir)?; + crate::flatten::flatten_submodules(&mut module); + Ok(module) } } diff --git a/crates/oxide_compiler/src/flatten.rs b/crates/oxide_compiler/src/flatten.rs new file mode 100644 index 00000000..fb385c08 --- /dev/null +++ b/crates/oxide_compiler/src/flatten.rs @@ -0,0 +1,69 @@ +//! 子模块扁平化 pass:把嵌套的 `sub_modules` 树展开为全局唯一 id。 +//! +//! `CREATE_CLOSURE` 在 emit 期用相对索引(当前模块 `nested` 的 1-based 位置)。 +//! 逃逸闭包(函数对象在定义模块之外被调用)时相对索引不再可解。此 pass 在编译 +//! 末端给整棵树分配 DFS 前序 `flat_id`(顶层 0),并把每条 `CREATE_CLOSURE` 的 +//! imm16 重写为对应子模块的 `flat_id`。运行时以 `flat_id` 为平表下标,闭包自足, +//! 不再依赖调用上下文。 + +use oxide_bytecode::module::CompiledModule; +use oxide_bytecode::opcode::{self, OpCode}; + +/// 给整棵子模块树分配扁平 id 并重写 `CREATE_CLOSURE` 操作数。 +pub fn flatten_submodules(module: &mut CompiledModule) { + let mut next_id = 1u32; + assign_ids(module, &mut next_id); +} + +fn assign_ids(module: &mut CompiledModule, next_id: &mut u32) { + for sub in &mut module.sub_modules { + sub.flat_id = *next_id; + *next_id += 1; + } + for instr in &mut module.bytecode { + if opcode::opcode(*instr) == OpCode::CREATE_CLOSURE { + let rel = opcode::imm16(*instr) as usize; + let flat = module.sub_modules.get(rel - 1).map(|s| s.flat_id).unwrap_or(0); + let rd = opcode::rd(*instr); + *instr = opcode::encode( + OpCode::CREATE_CLOSURE, + rd, + (flat & 0xFF) as u8, + ((flat >> 8) & 0xFF) as u8, + ); + } + } + for sub in &mut module.sub_modules { + assign_ids(sub, next_id); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn module_with_closure(nested: Vec) -> CompiledModule { + let mut m = CompiledModule::new(); + m.sub_modules = nested; + m + } + + #[test] + fn flatten_assigns_global_ids_and_rewrites_closures() { + let mut leaf = CompiledModule::new(); + leaf.bytecode = vec![opcode::encode(OpCode::CREATE_CLOSURE, 1, 1, 0)]; + let mut mid = CompiledModule::new(); + mid.sub_modules = vec![leaf]; + mid.bytecode = vec![opcode::encode(OpCode::CREATE_CLOSURE, 2, 1, 0)]; + let mut top = module_with_closure(vec![mid]); + + flatten_submodules(&mut top); + + // 顶层 0,mid = 1,leaf = 2 + assert_eq!(top.flat_id, 0); + assert_eq!(top.sub_modules[0].flat_id, 1); + assert_eq!(top.sub_modules[0].sub_modules[0].flat_id, 2); + // mid 的 CREATE_CLOSURE 相对 1 → flat 2 + assert_eq!(opcode::imm16(top.sub_modules[0].bytecode[0]), 2); + } +} diff --git a/crates/oxide_compiler/src/lib.rs b/crates/oxide_compiler/src/lib.rs index 2ff885db..1f9c8b5f 100644 --- a/crates/oxide_compiler/src/lib.rs +++ b/crates/oxide_compiler/src/lib.rs @@ -6,4 +6,5 @@ pub mod compiler; pub mod compiler_log; +pub mod flatten; pub mod hash; diff --git a/crates/oxide_ir/src/lower.rs b/crates/oxide_ir/src/lower.rs index 3ad984f2..c37d6231 100644 --- a/crates/oxide_ir/src/lower.rs +++ b/crates/oxide_ir/src/lower.rs @@ -90,6 +90,7 @@ pub fn lower(f: &IRFunction) -> Result { needs_home_object: f.needs_home_object, upvalue_captures: f.upvalue_captures.clone(), cells_needed: f.cells_needed, + flat_id: 0, }) } diff --git a/crates/oxide_vm/src/dispatch/call.rs b/crates/oxide_vm/src/dispatch/call.rs index bcc0e863..c4794874 100644 --- a/crates/oxide_vm/src/dispatch/call.rs +++ b/crates/oxide_vm/src/dispatch/call.rs @@ -1,5 +1,3 @@ -use std::sync::OnceLock; - use crate::native::NativeFn; use crate::vm::{native_fn_ptr_to_fn, CallFrame, FrameContinuation, Vm}; use crate::{vm_debug, vm_trace}; @@ -89,7 +87,7 @@ impl Vm { pub(crate) fn dispatch_create_closure(&mut self, rd: usize, instr: u32) -> Result<(), String> { let sub_idx = opcode::imm16(instr) as u32; vm_trace!("CREATE_CLOSURE rd={} sub_idx={}", rd, sub_idx); - if sub_idx == 0 || (sub_idx as usize) > self.sub_modules.len() { + if sub_idx == 0 || (sub_idx as usize) >= self.sub_modules.len() { // 逃逸闭包(函数对象在定义模块之外被创建)时 sub_idx 相对定义模块, // 超出当前 sub_modules 上下文——按运行时错误处理而非索引越界 panic。 return Err(format!( @@ -98,7 +96,7 @@ impl Vm { self.sub_modules.len() )); } - let sub = &self.sub_modules[sub_idx as usize - 1]; + let sub = &self.sub_modules[sub_idx as usize]; let is_arrow = sub.is_arrow; let is_class_constructor = sub.is_class_constructor; let is_derived_constructor = sub.is_derived_constructor; @@ -366,7 +364,7 @@ impl Vm { } } } else if super_obj.sub_module_index() > 0 { - let sub_idx = super_obj.sub_module_index() as usize - 1; + let sub_idx = super_obj.sub_module_index() as usize; if sub_idx >= self.sub_modules.len() { return Err(format!( "SUPER_CALL: sub_module_index {} out of bounds (max {})", @@ -421,7 +419,7 @@ impl Vm { self.bytecode = sub_bytecode; let subs = Arc::clone(&self.sub_modules); - self.activate_immutables(sub_idx + 1, &subs[sub_idx].constants); + self.activate_immutables(sub_idx, &subs[sub_idx].constants); self.cell_stack.push(Vec::with_capacity(subs[sub_idx].cells_needed as usize)); for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map.clone() { let si = self.kernel_core.perm_interner().intern(name.as_str()).0; @@ -431,15 +429,6 @@ impl Vm { } } - let callee_subs = &subs[sub_idx].sub_modules; - if callee_subs.is_empty() { - self.sub_module_stack.push((Arc::clone(&self.sub_modules), None)); - } else { - self.sub_module_stack - .push((Arc::clone(&self.sub_modules), Some(std::mem::take(&mut self.immutables_cache)))); - self.sub_modules = Arc::new(callee_subs.clone()); - self.immutables_cache = (0..=callee_subs.len()).map(|_| OnceLock::new()).collect(); - } self.active_reg_limit = sub_n_registers.max(1); self.pc = 0; return Ok(true); diff --git a/crates/oxide_vm/src/vm.rs b/crates/oxide_vm/src/vm.rs index 30f2ac3c..34ca1167 100644 --- a/crates/oxide_vm/src/vm.rs +++ b/crates/oxide_vm/src/vm.rs @@ -188,6 +188,8 @@ pub struct Vm { pub epoch: Epoch, pub object_prototype: P, pub math_rng_state: u64, + /// 全局扁平模块表:下标 = 模块 `flat_id`(顶层 0,子模块 flatten 后全局唯一)。 + /// 闭包 `sub_module_index` 即 flat_id,逃逸闭包也能自足解析。 pub(crate) sub_modules: Arc>, pub(crate) saved_bytecode_stack: Vec>, pub(crate) saved_immutables_stack: Vec<*const [JsValue]>, @@ -225,9 +227,7 @@ pub struct Vm { pub(crate) iters: IterState, /// 分组保存 inline cache 与指令计数器。 pub(crate) profiling: ProfilingState, - pub(crate) sub_module_stack: Vec<(Arc>, Option>>>)>, pub(crate) cell_stack: Vec>, - pub(crate) temp_immutables: Vec>, /// 可复用字符串缓冲区,避免每次 `+` 拼接都分配。 pub(crate) string_buf: String, } @@ -608,6 +608,31 @@ impl Vm { let s = unsafe { &(*val.as_string_ptr()).data }; return self.kernel_core.perm_interner().intern(s).0; } + // well-known symbol 是空对象:按指针比对映射为各自专属键,避免全部塌缩。 + if val.is_object() { + let world = self.session.builtin_world(); + let ptr = val.as_js_object_ptr(); + let key = if std::ptr::eq(ptr, world.sym_iterator.as_ptr()) { + "@@iterator" + } else if std::ptr::eq(ptr, world.sym_match.as_ptr()) { + "@@match" + } else if std::ptr::eq(ptr, world.sym_replace.as_ptr()) { + "@@replace" + } else if std::ptr::eq(ptr, world.sym_search.as_ptr()) { + "@@search" + } else if std::ptr::eq(ptr, world.sym_split.as_ptr()) { + "@@split" + } else if std::ptr::eq(ptr, world.sym_to_primitive.as_ptr()) { + "@@toPrimitive" + } else if std::ptr::eq(ptr, world.sym_has_instance.as_ptr()) { + "@@hasInstance" + } else { + "" + }; + if !key.is_empty() { + return self.kernel_core.perm_interner().intern(key).0; + } + } let key = coercion::to_string(val); self.kernel_core.perm_interner().intern(&key).0 } @@ -748,7 +773,7 @@ impl Vm { if !obj.is_function() || obj.sub_module_index() == 0 { return Err(self.error_message_text("TypeError", "CALL target is not callable")); } - let sub_idx = obj.sub_module_index() as usize - 1; + let sub_idx = obj.sub_module_index() as usize; if sub_idx >= self.sub_modules.len() { return Err(format!( "CALL: sub_module_index {} out of bounds (max {})", @@ -804,9 +829,7 @@ impl Vm { self.pc = 0; self.bytecode = sub_bytecode; let subs = Arc::clone(&self.sub_modules); - let converted = self.convert_immutables(&subs[sub_idx].constants); - self.temp_immutables.push(converted); - self.active_immutables = self.temp_immutables.last().unwrap().as_slice() as *const [JsValue]; + self.activate_immutables(sub_idx, &subs[sub_idx].constants); self.cell_stack.push(Vec::with_capacity(subs[sub_idx].cells_needed as usize)); for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map.clone() { let si = self.kernel_core.perm_interner().intern(name.as_str()).0; @@ -816,16 +839,6 @@ impl Vm { } } - let callee_subs = &subs[sub_idx].sub_modules; - if callee_subs.is_empty() { - self.sub_module_stack.push((Arc::clone(&self.sub_modules), None)); - } else { - self.sub_module_stack - .push((Arc::clone(&self.sub_modules), Some(std::mem::take(&mut self.immutables_cache)))); - self.sub_modules = Arc::new(callee_subs.clone()); - self.immutables_cache = (0..=callee_subs.len()).map(|_| OnceLock::new()).collect(); - } - self.active_reg_limit = sub_n_registers.max(1); self.pc = 0; Ok(()) diff --git a/crates/oxide_vm/src/vm_dispatch_misc.rs b/crates/oxide_vm/src/vm_dispatch_misc.rs index abccbe82..84e6f34b 100644 --- a/crates/oxide_vm/src/vm_dispatch_misc.rs +++ b/crates/oxide_vm/src/vm_dispatch_misc.rs @@ -1,4 +1,4 @@ -use std::sync::{Arc, OnceLock}; +use std::sync::Arc; use crate::native::NativeFn; use crate::vm::{native_fn_ptr_to_fn, CallFrame, ForInIter, FrameContinuation, Vm, MAX_PROTO_CHAIN_DEPTH}; @@ -83,7 +83,7 @@ impl Vm { } } } else if ctor_obj.sub_module_index() > 0 { - let sub_idx = ctor_obj.sub_module_index() as usize - 1; + let sub_idx = ctor_obj.sub_module_index() as usize; if sub_idx >= self.sub_modules.len() { return Err(format!( "NEW_EXPRESSION: sub_module_index {} out of bounds (max {})", @@ -142,7 +142,7 @@ impl Vm { self.bytecode = sub_bytecode; let subs = Arc::clone(&self.sub_modules); - self.activate_immutables(sub_idx + 1, &subs[sub_idx].constants); + self.activate_immutables(sub_idx, &subs[sub_idx].constants); self.cell_stack.push(Vec::with_capacity(subs[sub_idx].cells_needed as usize)); for (name, reg) in &self.sub_modules[sub_idx].builtin_reg_map { @@ -153,16 +153,6 @@ impl Vm { } } - let callee_subs = &subs[sub_idx].sub_modules; - if callee_subs.is_empty() { - self.sub_module_stack.push((Arc::clone(&self.sub_modules), None)); - } else { - self.sub_module_stack - .push((Arc::clone(&self.sub_modules), Some(std::mem::take(&mut self.immutables_cache)))); - self.sub_modules = Arc::new(callee_subs.clone()); - self.immutables_cache = (0..=callee_subs.len()).map(|_| OnceLock::new()).collect(); - } - self.active_reg_limit = sub_n_registers.max(1); self.pc = 0; Ok(true) diff --git a/crates/oxide_vm/src/vm_runtime.rs b/crates/oxide_vm/src/vm_runtime.rs index 2a8f0e40..73a589a6 100644 --- a/crates/oxide_vm/src/vm_runtime.rs +++ b/crates/oxide_vm/src/vm_runtime.rs @@ -7,6 +7,24 @@ use crate::{vm_debug, vm_info, vm_trace, vm_warn}; use oxide_types::object::JsObject; use oxide_types::value::JsValue; +/// 按 `flat_id` 下标收集整棵子模块树为平表,供 `run()` 装载。 +fn collect_flat_modules(module: &CompiledModule) -> Vec { + let mut out: Vec> = Vec::new(); + place_flat(module, &mut out); + out.into_iter().map(|m| m.expect("flat_id slot must be filled")).collect() +} + +fn place_flat(module: &CompiledModule, out: &mut Vec>) { + let idx = module.flat_id as usize; + if idx >= out.len() { + out.resize(idx + 1, None); + } + out[idx] = Some(module.clone()); + for sub in &module.sub_modules { + place_flat(sub, out); + } +} + impl Vm { pub(crate) fn call_bytecode_function_inline( &mut self, callee: JsValue, callee_obj: &JsObject, receiver: JsValue, args: &[JsValue], @@ -14,7 +32,7 @@ impl Vm { if callee_obj.sub_module_index() == 0 { return Err(self.error_message_text("TypeError", "accessor is not callable")); } - let sub_idx = callee_obj.sub_module_index() as usize - 1; + let sub_idx = callee_obj.sub_module_index() as usize; vm_debug!( "call_bytecode_function_inline: sub_idx={}, args={}, depth={}", sub_idx, @@ -68,7 +86,7 @@ impl Vm { self.regs = [JsValue::undefined(); 256]; self.pc = 0; self.bytecode = sub.bytecode.clone(); - self.activate_immutables(sub_idx + 1, &sub.constants); + self.activate_immutables(sub_idx, &sub.constants); self.active_reg_limit = sub.n_registers.max(1); self.root_reg_limit = self.active_reg_limit; self.cell_stack.push(Vec::with_capacity(sub.cells_needed as usize)); @@ -134,13 +152,6 @@ impl Vm { if let Some(saved_imm) = self.saved_immutables_stack.pop() { self.active_immutables = saved_imm; } - if let Some((saved_subs, saved_cache)) = self.sub_module_stack.pop() { - self.sub_modules = saved_subs; - if let Some(saved_cache) = saved_cache { - self.immutables_cache = saved_cache; - } - } - self.temp_immutables.pop(); let offset = frame.saved_reg_offset as usize; let len = frame.caller_reg_limit as usize; self.regs[..len].copy_from_slice(&self.save_stack[offset..offset + len]); @@ -171,8 +182,8 @@ impl Vm { self.clear_execution_state(); self.cell_stack.clear(); self.cell_stack.push(Vec::new()); - self.sub_modules = Arc::new(module.sub_modules.clone()); - self.immutables_cache = (0..=self.sub_modules.len()).map(|_| OnceLock::new()).collect(); + self.sub_modules = Arc::new(collect_flat_modules(module)); + self.immutables_cache = (0..self.sub_modules.len()).map(|_| OnceLock::new()).collect(); self.bytecode = module.bytecode.clone(); self.activate_immutables(0, &module.constants); self.root_reg_limit = module.n_registers.max(1); diff --git a/crates/oxide_vm/src/vm_support.rs b/crates/oxide_vm/src/vm_support.rs index a2bb6058..f90c1e05 100644 --- a/crates/oxide_vm/src/vm_support.rs +++ b/crates/oxide_vm/src/vm_support.rs @@ -73,9 +73,7 @@ impl Vm { instruction_count: 0, }, string_buf: String::new(), - sub_module_stack: Vec::new(), cell_stack: Vec::new(), - temp_immutables: Vec::new(), }; vm_info!("Vm created"); vm @@ -138,9 +136,7 @@ impl Vm { instruction_count: 0, }, string_buf: String::new(), - sub_module_stack: Vec::new(), cell_stack: Vec::new(), - temp_immutables: Vec::new(), }; vm_info!("Vm created (pool)"); vm @@ -213,14 +209,12 @@ impl Vm { self.regs = [JsValue::undefined(); 256]; self.pc = 0; self.frames.clear(); - self.sub_module_stack.clear(); self.iters.reset(); self.saved_bytecode_stack.clear(); self.saved_immutables_stack.clear(); self.save_stack.clear(); self.spill_stack.clear(); self.cell_stack.clear(); - self.temp_immutables.clear(); self.try_stack.clear(); self.exception_value = None; self.pending_exception = None;