fix: 子模块全局扁平化根治逃逸闭包上下文错位

This commit is contained in:
2026-08-07 10:49:15 +08:00
parent 98b07687f0
commit fd94ff6931
10 changed files with 137 additions and 62 deletions
+5
View File
@@ -64,6 +64,9 @@ pub struct CompiledModule {
pub needs_home_object: bool, pub needs_home_object: bool,
pub upvalue_captures: Vec<UpvalueCapture>, pub upvalue_captures: Vec<UpvalueCapture>,
pub cells_needed: u8, pub cells_needed: u8,
/// 全局扁平模块 id:编译末端 flatten 阶段分配(顶层 0,子模块 DFS 递增)。
/// `CREATE_CLOSURE` 的 imm16 在 flatten 后即此 id,运行时以它为平表下标。
pub flat_id: u32,
} }
impl CompiledModule { impl CompiledModule {
@@ -85,6 +88,7 @@ impl CompiledModule {
needs_home_object: false, needs_home_object: false,
upvalue_captures: Vec::new(), upvalue_captures: Vec::new(),
cells_needed: 0, cells_needed: 0,
flat_id: 0,
} }
} }
} }
@@ -113,6 +117,7 @@ impl Clone for CompiledModule {
needs_home_object: self.needs_home_object, needs_home_object: self.needs_home_object,
upvalue_captures: self.upvalue_captures.clone(), upvalue_captures: self.upvalue_captures.clone(),
cells_needed: self.cells_needed, cells_needed: self.cells_needed,
flat_id: self.flat_id,
} }
} }
} }
+3 -1
View File
@@ -58,7 +58,9 @@ impl Compiler {
oxide_regalloc::alloc(&mut ir, &live2)?; // 无可行染色 → RangeError 上抛 oxide_regalloc::alloc(&mut ir, &live2)?; // 无可行染色 → RangeError 上抛
} }
crate::compiler_debug!("compile: done, {} instructions", ir.insts.len()); crate::compiler_debug!("compile: done, {} instructions", ir.insts.len());
lower(&ir) let mut module = lower(&ir)?;
crate::flatten::flatten_submodules(&mut module);
Ok(module)
} }
} }
+69
View File
@@ -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>) -> 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);
}
}
+1
View File
@@ -6,4 +6,5 @@
pub mod compiler; pub mod compiler;
pub mod compiler_log; pub mod compiler_log;
pub mod flatten;
pub mod hash; pub mod hash;
+1
View File
@@ -90,6 +90,7 @@ pub fn lower(f: &IRFunction) -> Result<CompiledModule, String> {
needs_home_object: f.needs_home_object, needs_home_object: f.needs_home_object,
upvalue_captures: f.upvalue_captures.clone(), upvalue_captures: f.upvalue_captures.clone(),
cells_needed: f.cells_needed, cells_needed: f.cells_needed,
flat_id: 0,
}) })
} }
+4 -15
View File
@@ -1,5 +1,3 @@
use std::sync::OnceLock;
use crate::native::NativeFn; use crate::native::NativeFn;
use crate::vm::{native_fn_ptr_to_fn, CallFrame, FrameContinuation, Vm}; use crate::vm::{native_fn_ptr_to_fn, CallFrame, FrameContinuation, Vm};
use crate::{vm_debug, vm_trace}; 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> { pub(crate) fn dispatch_create_closure(&mut self, rd: usize, instr: u32) -> Result<(), String> {
let sub_idx = opcode::imm16(instr) as u32; let sub_idx = opcode::imm16(instr) as u32;
vm_trace!("CREATE_CLOSURE rd={} sub_idx={}", rd, sub_idx); 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_idx 相对定义模块,
// 超出当前 sub_modules 上下文——按运行时错误处理而非索引越界 panic。 // 超出当前 sub_modules 上下文——按运行时错误处理而非索引越界 panic。
return Err(format!( return Err(format!(
@@ -98,7 +96,7 @@ impl Vm {
self.sub_modules.len() 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_arrow = sub.is_arrow;
let is_class_constructor = sub.is_class_constructor; let is_class_constructor = sub.is_class_constructor;
let is_derived_constructor = sub.is_derived_constructor; let is_derived_constructor = sub.is_derived_constructor;
@@ -366,7 +364,7 @@ impl Vm {
} }
} }
} else if super_obj.sub_module_index() > 0 { } 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() { if sub_idx >= self.sub_modules.len() {
return Err(format!( return Err(format!(
"SUPER_CALL: sub_module_index {} out of bounds (max {})", "SUPER_CALL: sub_module_index {} out of bounds (max {})",
@@ -421,7 +419,7 @@ impl Vm {
self.bytecode = sub_bytecode; self.bytecode = sub_bytecode;
let subs = Arc::clone(&self.sub_modules); 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)); 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() { 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; 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.active_reg_limit = sub_n_registers.max(1);
self.pc = 0; self.pc = 0;
return Ok(true); return Ok(true);
+29 -16
View File
@@ -188,6 +188,8 @@ pub struct Vm {
pub epoch: Epoch, pub epoch: Epoch,
pub object_prototype: P<JsObject>, pub object_prototype: P<JsObject>,
pub math_rng_state: u64, pub math_rng_state: u64,
/// 全局扁平模块表:下标 = 模块 `flat_id`(顶层 0,子模块 flatten 后全局唯一)。
/// 闭包 `sub_module_index` 即 flat_id,逃逸闭包也能自足解析。
pub(crate) sub_modules: Arc<Vec<CompiledModule>>, pub(crate) sub_modules: Arc<Vec<CompiledModule>>,
pub(crate) saved_bytecode_stack: Vec<Vec<opcode::Instr>>, pub(crate) saved_bytecode_stack: Vec<Vec<opcode::Instr>>,
pub(crate) saved_immutables_stack: Vec<*const [JsValue]>, pub(crate) saved_immutables_stack: Vec<*const [JsValue]>,
@@ -225,9 +227,7 @@ pub struct Vm {
pub(crate) iters: IterState, pub(crate) iters: IterState,
/// 分组保存 inline cache 与指令计数器。 /// 分组保存 inline cache 与指令计数器。
pub(crate) profiling: ProfilingState, pub(crate) profiling: ProfilingState,
pub(crate) sub_module_stack: Vec<(Arc<Vec<CompiledModule>>, Option<Vec<OnceLock<Vec<JsValue>>>>)>,
pub(crate) cell_stack: Vec<Vec<*mut Cell>>, pub(crate) cell_stack: Vec<Vec<*mut Cell>>,
pub(crate) temp_immutables: Vec<Vec<JsValue>>,
/// 可复用字符串缓冲区,避免每次 `+` 拼接都分配。 /// 可复用字符串缓冲区,避免每次 `+` 拼接都分配。
pub(crate) string_buf: String, pub(crate) string_buf: String,
} }
@@ -608,6 +608,31 @@ impl Vm {
let s = unsafe { &(*val.as_string_ptr()).data }; let s = unsafe { &(*val.as_string_ptr()).data };
return self.kernel_core.perm_interner().intern(s).0; 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); let key = coercion::to_string(val);
self.kernel_core.perm_interner().intern(&key).0 self.kernel_core.perm_interner().intern(&key).0
} }
@@ -748,7 +773,7 @@ impl Vm {
if !obj.is_function() || obj.sub_module_index() == 0 { if !obj.is_function() || obj.sub_module_index() == 0 {
return Err(self.error_message_text("TypeError", "CALL target is not callable")); 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() { if sub_idx >= self.sub_modules.len() {
return Err(format!( return Err(format!(
"CALL: sub_module_index {} out of bounds (max {})", "CALL: sub_module_index {} out of bounds (max {})",
@@ -804,9 +829,7 @@ impl Vm {
self.pc = 0; self.pc = 0;
self.bytecode = sub_bytecode; self.bytecode = sub_bytecode;
let subs = Arc::clone(&self.sub_modules); let subs = Arc::clone(&self.sub_modules);
let converted = self.convert_immutables(&subs[sub_idx].constants); self.activate_immutables(sub_idx, &subs[sub_idx].constants);
self.temp_immutables.push(converted);
self.active_immutables = self.temp_immutables.last().unwrap().as_slice() as *const [JsValue];
self.cell_stack.push(Vec::with_capacity(subs[sub_idx].cells_needed as usize)); 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() { 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; 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.active_reg_limit = sub_n_registers.max(1);
self.pc = 0; self.pc = 0;
Ok(()) Ok(())
+3 -13
View File
@@ -1,4 +1,4 @@
use std::sync::{Arc, OnceLock}; use std::sync::Arc;
use crate::native::NativeFn; use crate::native::NativeFn;
use crate::vm::{native_fn_ptr_to_fn, CallFrame, ForInIter, FrameContinuation, Vm, MAX_PROTO_CHAIN_DEPTH}; 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 { } 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() { if sub_idx >= self.sub_modules.len() {
return Err(format!( return Err(format!(
"NEW_EXPRESSION: sub_module_index {} out of bounds (max {})", "NEW_EXPRESSION: sub_module_index {} out of bounds (max {})",
@@ -142,7 +142,7 @@ impl Vm {
self.bytecode = sub_bytecode; self.bytecode = sub_bytecode;
let subs = Arc::clone(&self.sub_modules); 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)); 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 { 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.active_reg_limit = sub_n_registers.max(1);
self.pc = 0; self.pc = 0;
Ok(true) Ok(true)
+22 -11
View File
@@ -7,6 +7,24 @@ use crate::{vm_debug, vm_info, vm_trace, vm_warn};
use oxide_types::object::JsObject; use oxide_types::object::JsObject;
use oxide_types::value::JsValue; use oxide_types::value::JsValue;
/// 按 `flat_id` 下标收集整棵子模块树为平表,供 `run()` 装载。
fn collect_flat_modules(module: &CompiledModule) -> Vec<CompiledModule> {
let mut out: Vec<Option<CompiledModule>> = 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<Option<CompiledModule>>) {
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 { impl Vm {
pub(crate) fn call_bytecode_function_inline( pub(crate) fn call_bytecode_function_inline(
&mut self, callee: JsValue, callee_obj: &JsObject, receiver: JsValue, args: &[JsValue], &mut self, callee: JsValue, callee_obj: &JsObject, receiver: JsValue, args: &[JsValue],
@@ -14,7 +32,7 @@ impl Vm {
if callee_obj.sub_module_index() == 0 { if callee_obj.sub_module_index() == 0 {
return Err(self.error_message_text("TypeError", "accessor is not callable")); 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!( vm_debug!(
"call_bytecode_function_inline: sub_idx={}, args={}, depth={}", "call_bytecode_function_inline: sub_idx={}, args={}, depth={}",
sub_idx, sub_idx,
@@ -68,7 +86,7 @@ impl Vm {
self.regs = [JsValue::undefined(); 256]; self.regs = [JsValue::undefined(); 256];
self.pc = 0; self.pc = 0;
self.bytecode = sub.bytecode.clone(); 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.active_reg_limit = sub.n_registers.max(1);
self.root_reg_limit = self.active_reg_limit; self.root_reg_limit = self.active_reg_limit;
self.cell_stack.push(Vec::with_capacity(sub.cells_needed as usize)); 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() { if let Some(saved_imm) = self.saved_immutables_stack.pop() {
self.active_immutables = saved_imm; 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 offset = frame.saved_reg_offset as usize;
let len = frame.caller_reg_limit as usize; let len = frame.caller_reg_limit as usize;
self.regs[..len].copy_from_slice(&self.save_stack[offset..offset + len]); self.regs[..len].copy_from_slice(&self.save_stack[offset..offset + len]);
@@ -171,8 +182,8 @@ impl Vm {
self.clear_execution_state(); self.clear_execution_state();
self.cell_stack.clear(); self.cell_stack.clear();
self.cell_stack.push(Vec::new()); self.cell_stack.push(Vec::new());
self.sub_modules = Arc::new(module.sub_modules.clone()); self.sub_modules = Arc::new(collect_flat_modules(module));
self.immutables_cache = (0..=self.sub_modules.len()).map(|_| OnceLock::new()).collect(); self.immutables_cache = (0..self.sub_modules.len()).map(|_| OnceLock::new()).collect();
self.bytecode = module.bytecode.clone(); self.bytecode = module.bytecode.clone();
self.activate_immutables(0, &module.constants); self.activate_immutables(0, &module.constants);
self.root_reg_limit = module.n_registers.max(1); self.root_reg_limit = module.n_registers.max(1);
-6
View File
@@ -73,9 +73,7 @@ impl Vm {
instruction_count: 0, instruction_count: 0,
}, },
string_buf: String::new(), string_buf: String::new(),
sub_module_stack: Vec::new(),
cell_stack: Vec::new(), cell_stack: Vec::new(),
temp_immutables: Vec::new(),
}; };
vm_info!("Vm created"); vm_info!("Vm created");
vm vm
@@ -138,9 +136,7 @@ impl Vm {
instruction_count: 0, instruction_count: 0,
}, },
string_buf: String::new(), string_buf: String::new(),
sub_module_stack: Vec::new(),
cell_stack: Vec::new(), cell_stack: Vec::new(),
temp_immutables: Vec::new(),
}; };
vm_info!("Vm created (pool)"); vm_info!("Vm created (pool)");
vm vm
@@ -213,14 +209,12 @@ impl Vm {
self.regs = [JsValue::undefined(); 256]; self.regs = [JsValue::undefined(); 256];
self.pc = 0; self.pc = 0;
self.frames.clear(); self.frames.clear();
self.sub_module_stack.clear();
self.iters.reset(); self.iters.reset();
self.saved_bytecode_stack.clear(); self.saved_bytecode_stack.clear();
self.saved_immutables_stack.clear(); self.saved_immutables_stack.clear();
self.save_stack.clear(); self.save_stack.clear();
self.spill_stack.clear(); self.spill_stack.clear();
self.cell_stack.clear(); self.cell_stack.clear();
self.temp_immutables.clear();
self.try_stack.clear(); self.try_stack.clear();
self.exception_value = None; self.exception_value = None;
self.pending_exception = None; self.pending_exception = None;