feat: using 声明登记释放栈指令与 GC 根

This commit is contained in:
2026-10-09 17:55:08 +00:00
parent 0df3f9d5fd
commit b5ff2c132a
10 changed files with 88 additions and 23 deletions
Generated
+19 -19
View File
@@ -1160,7 +1160,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_builtins" name = "oxide_builtins"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"chrono", "chrono",
"fast-float", "fast-float",
@@ -1185,7 +1185,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_bytecode" name = "oxide_bytecode"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxide_log", "oxide_log",
@@ -1193,7 +1193,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cfg" name = "oxide_cfg"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_emit", "oxide_emit",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_cli" name = "oxide_cli"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"ansi_term", "ansi_term",
"bumpalo", "bumpalo",
@@ -1226,7 +1226,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_code_cache" name = "oxide_code_cache"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"lru", "lru",
"oxide_bytecode", "oxide_bytecode",
@@ -1235,7 +1235,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_compiler" name = "oxide_compiler"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1255,7 +1255,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_dce" name = "oxide_dce"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1269,7 +1269,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_emit" name = "oxide_emit"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"oxc_allocator", "oxc_allocator",
@@ -1284,7 +1284,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_engine" name = "oxide_engine"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1296,7 +1296,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_ir" name = "oxide_ir"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_log", "oxide_log",
@@ -1305,7 +1305,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_kernel" name = "oxide_kernel"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1319,7 +1319,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_liveness" name = "oxide_liveness"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1331,7 +1331,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_log" name = "oxide_log"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"tracing", "tracing",
"tracing-appender", "tracing-appender",
@@ -1340,7 +1340,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_parser" name = "oxide_parser"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"oxc_allocator", "oxc_allocator",
"oxc_ast", "oxc_ast",
@@ -1355,7 +1355,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_regalloc" name = "oxide_regalloc"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_cfg", "oxide_cfg",
@@ -1371,7 +1371,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_runtime_api" name = "oxide_runtime_api"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"num-bigint", "num-bigint",
"num-traits", "num-traits",
@@ -1384,7 +1384,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_test262" name = "oxide_test262"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"oxide_bytecode", "oxide_bytecode",
"oxide_compiler", "oxide_compiler",
@@ -1402,7 +1402,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_types" name = "oxide_types"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"dashmap", "dashmap",
@@ -1415,7 +1415,7 @@ dependencies = [
[[package]] [[package]]
name = "oxide_vm" name = "oxide_vm"
version = "0.13.14" version = "0.13.15"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"chrono", "chrono",
+1 -1
View File
@@ -23,7 +23,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.13.14" version = "0.13.15"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
rust-version = "1.80" rust-version = "1.80"
+6
View File
@@ -297,6 +297,12 @@ define_opcodes! {
FOR_IN_DONE = 0x2B => "FOR_IN_DONE", FOR_IN_DONE = 0x2B => "FOR_IN_DONE",
def = Some(SlotSpec::Slot(Slot::Rd)), uses = [], def = Some(SlotSpec::Slot(Slot::Rd)), uses = [],
pure = false, jump = false, term = false, ic = false, pure = false, jump = false, term = false, ic = false,
// using/await using 声明登记:rd = 资源值寄存器,a = 释放提示(0 = 同步、1 = 异步)。
// 无寄存器 def(值压入 VM 释放栈,释放时机由作用域出口点决定);
// pure = false 防 DCE 删除压栈副作用。
DISPOSE_REGISTER = 0x2C => "DISPOSE_REGISTER",
def = None, uses = [SlotSpec::Slot(Slot::Rd)],
pure = false, jump = false, term = false, ic = false,
FOR_IN_CLEANUP = 0x2D => "FOR_IN_CLEANUP", FOR_IN_CLEANUP = 0x2D => "FOR_IN_CLEANUP",
def = None, uses = [], def = None, uses = [],
pure = false, jump = false, term = false, ic = false, pure = false, jump = false, term = false, ic = false,
@@ -43,6 +43,17 @@ impl Emitter {
} }
} }
} }
// using/await using 声明登记:把资源值压入 VM 释放栈(释放时机由
// 作用域出口点决定,本指令只登记)。a 槽为释放提示:0 = 同步、1 = 异步。
if matches!(decl.kind, VariableDeclarationKind::Using | VariableDeclarationKind::AwaitUsing) {
let hint = u16::from(matches!(decl.kind, VariableDeclarationKind::AwaitUsing));
ctx.inst(Inst::new(
OpCode::DISPOSE_REGISTER,
Operand::Reg(val_reg),
Operand::Imm(hint),
Operand::None,
));
}
} else { } else {
let BindingPattern::BindingIdentifier(bi) = &d.id else { let BindingPattern::BindingIdentifier(bi) = &d.id else {
return Err("destructuring declaration requires an initializer".into()); return Err("destructuring declaration requires an initializer".into());
+3
View File
@@ -286,6 +286,9 @@ impl Vm {
OpCode::MAKE_CELL_FRESH => { OpCode::MAKE_CELL_FRESH => {
self.dispatch_make_cell_fresh(rd, instr)?; self.dispatch_make_cell_fresh(rd, instr)?;
} }
OpCode::DISPOSE_REGISTER => {
self.dispatch_dispose_register(rd, a);
}
OpCode::CELL_GET => { OpCode::CELL_GET => {
self.dispatch_cell_get(rd, a, b)?; self.dispatch_cell_get(rd, a, b)?;
} }
+8 -3
View File
@@ -8,7 +8,7 @@ use super::frames::Completion;
use super::Vm; use super::Vm;
use crate::session_gc::SessionGc; use crate::session_gc::SessionGc;
/// GC 根组来源:`for_each_value` 枚举的 25 个根组,变体顺序与根清单 /// GC 根组来源:`for_each_value` 枚举的 26 个根组,变体顺序与根清单
/// 的枚举顺序一致。 /// 的枚举顺序一致。
/// ///
/// 变体下标是逐组计数数组(`SessionGc::root_counts`)的下标;`COUNT` 与 /// 变体下标是逐组计数数组(`SessionGc::root_counts`)的下标;`COUNT` 与
@@ -40,11 +40,12 @@ pub(crate) enum RootGroup {
ForInIters, ForInIters,
Global, Global,
WorkerObjects, WorkerObjects,
DisposeStack,
} }
impl RootGroup { impl RootGroup {
/// 根组总数(与变体数同源,绑定逐组计数数组的长度)。 /// 根组总数(与变体数同源,绑定逐组计数数组的长度)。
pub const COUNT: usize = 25; pub const COUNT: usize = 26;
} }
impl Vm { impl Vm {
@@ -52,7 +53,7 @@ impl Vm {
/// 覆盖执行核心的全部 JsValue 持有点:regs/帧/各栈段/cell/在途异常与完成/ /// 覆盖执行核心的全部 JsValue 持有点:regs/帧/各栈段/cell/在途异常与完成/
/// 挂起信号/迭代器/微任务/global/Worker 对象注册表。 /// 挂起信号/迭代器/微任务/global/Worker 对象注册表。
/// ///
/// 闭包接收根组来源(`RootGroup`)与根值:组是枚举顺序中的 25 根组之一, /// 闭包接收根组来源(`RootGroup`)与根值:组是枚举顺序中的 26 根组之一,
/// 调用方按组计数(如 `SessionGc` 的逐组计数)。 /// 调用方按组计数(如 `SessionGc` 的逐组计数)。
pub(crate) fn for_each_value(&self, mut f: impl FnMut(RootGroup, JsValue)) { pub(crate) fn for_each_value(&self, mut f: impl FnMut(RootGroup, JsValue)) {
for value in &self.regs { for value in &self.regs {
@@ -82,6 +83,10 @@ impl Vm {
for &v in &self.spill_stack { for &v in &self.spill_stack {
f(RootGroup::SpillStack, v); f(RootGroup::SpillStack, v);
} }
// 释放栈持 using 声明的资源值:漏根 → sweep 释放 → 释放点解引用悬垂。
for &v in &self.dispose_stack {
f(RootGroup::DisposeStack, v);
}
for cell_vec in &self.cell_stack { for cell_vec in &self.cell_stack {
for &cell_ptr in cell_vec { for &cell_ptr in cell_vec {
if cell_ptr.is_null() { if cell_ptr.is_null() {
+4
View File
@@ -230,6 +230,10 @@ pub struct Vm {
/// VM 级 spill 栈。`CallFrame.spill_offset` 定位本帧区:调用子函数时从边界后分配, /// VM 级 spill 栈。`CallFrame.spill_offset` 定位本帧区:调用子函数时从边界后分配,
/// 帧恢复时截断到边界,子函数 spill 数据随帧丢弃。 /// 帧恢复时截断到边界,子函数 spill 数据随帧丢弃。
pub(crate) spill_stack: Vec<JsValue>, pub(crate) spill_stack: Vec<JsValue>,
/// using/await using 声明的资源释放栈:DISPOSE_REGISTER 把资源值压入,
/// 作用域出口点逆序释放(释放点由后续指令族承担)。栈内 JsValue 是 GC 根
/// (`for_each_value` 遍历);run 边界清空,防跨 run 残留条目在下一 run 被误释放。
pub(crate) dispose_stack: Vec<JsValue>,
/// 本次 native 调用的 spill 溢出实参区:`spill_stack[base..base+count)`。 /// 本次 native 调用的 spill 溢出实参区:`spill_stack[base..base+count)`。
/// 实参数超过寄存器窗口(253)时,窗口外的实参转存 spill 栈(GC 根), /// 实参数超过寄存器窗口(253)时,窗口外的实参转存 spill 栈(GC 根),
/// native 侧经 `VmHost::native_arg_count`/`native_arg_at` 读取。仅在一次 /// native 侧经 `VmHost::native_arg_count`/`native_arg_at` 读取。仅在一次
+12
View File
@@ -649,6 +649,18 @@ impl Vm {
self.iters.pop_for_in(); self.iters.pop_for_in();
} }
/// using/await using 声明登记:把资源值(rd 槽)压入释放栈。
///
/// a 槽是释放提示(0 = 同步、1 = 异步),登记期原样保留供释放点分派;
/// 本指令只登记,释放时机由作用域出口点决定。
///
/// # 副作用
/// - 释放栈增长(栈内 JsValue 是 GC 根,run 边界清空)。
pub(crate) fn dispatch_dispose_register(&mut self, rd: usize, a: usize) {
vm_trace!("DISPOSE_REGISTER rd={} hint={}", rd, a);
self.dispose_stack.push(self.regs[rd]);
}
pub(crate) fn dispatch_for_of_init(&mut self, a: usize) -> Result<(), String> { pub(crate) fn dispatch_for_of_init(&mut self, a: usize) -> Result<(), String> {
vm_trace!("FOR_OF_INIT r{}={:?}", a, self.regs[a]); vm_trace!("FOR_OF_INIT r{}={:?}", a, self.regs[a]);
let iterable = self.regs[a]; let iterable = self.regs[a];
+19
View File
@@ -1143,4 +1143,23 @@ mod tests {
vm.drain_microtasks(); vm.drain_microtasks();
assert!(vm.job_queue.is_empty()); assert!(vm.job_queue.is_empty());
} }
#[test]
fn using_declaration_registers_dispose_stack() {
// using 声明经 DISPOSE_REGISTER 把资源值压入释放栈;run 边界(下次 run
// 入口)清栈,上一 run 的残留条目不得跨 run 可见。
let mut vm = Vm::new();
let module = Arc::new(compile("function f() { using x = { a: 1 }; } f();"));
vm.run(&module).expect("run");
assert_eq!(vm.dispose_stack.len(), 1, "using 声明应登记一个资源值");
assert!(vm.dispose_stack[0].is_object(), "登记值应为资源对象");
// 二次 run 入口清栈后重新登记:残留条目不跨 run 存活。
vm.run(&module).expect("run2");
assert_eq!(vm.dispose_stack.len(), 1, "二次 run 应恰好登记一条新条目");
// 执行状态清空同样清释放栈。
vm.clear_execution_state();
assert!(vm.dispose_stack.is_empty(), "执行状态清空应清空释放栈");
}
} }
+5
View File
@@ -130,6 +130,7 @@ impl Vm {
saved_immutables_stack: Vec::new(), saved_immutables_stack: Vec::new(),
save_stack: Vec::new(), save_stack: Vec::new(),
spill_stack: Vec::new(), spill_stack: Vec::new(),
dispose_stack: Vec::new(),
native_overflow_base: 0, native_overflow_base: 0,
native_overflow_count: 0, native_overflow_count: 0,
try_stack: Vec::new(), try_stack: Vec::new(),
@@ -291,6 +292,7 @@ impl Vm {
saved_immutables_stack: Vec::new(), saved_immutables_stack: Vec::new(),
save_stack: Vec::new(), save_stack: Vec::new(),
spill_stack: Vec::new(), spill_stack: Vec::new(),
dispose_stack: Vec::new(),
native_overflow_base: 0, native_overflow_base: 0,
native_overflow_count: 0, native_overflow_count: 0,
try_stack: Vec::new(), try_stack: Vec::new(),
@@ -515,6 +517,9 @@ impl Vm {
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();
// 释放栈条目持资源值(session 对象指针):跨 run/reset 残留会在下一 run
// 被误释放或悬垂,随执行状态一并清空。
self.dispose_stack.clear();
self.native_overflow_base = 0; self.native_overflow_base = 0;
self.native_overflow_count = 0; self.native_overflow_count = 0;
// native pack 实参区上界与溢出描述符同构:仅一次 native 调用期间有效, // native pack 实参区上界与溢出描述符同构:仅一次 native 调用期间有效,