feat: 七类作用域出口正常释放 using 资源,for-of 每迭代

This commit is contained in:
2026-10-09 18:40:24 +00:00
parent b5ff2c132a
commit 48ffb54c4f
19 changed files with 473 additions and 46 deletions
+6
View File
@@ -289,6 +289,12 @@ impl Vm {
OpCode::DISPOSE_REGISTER => {
self.dispatch_dispose_register(rd, a);
}
OpCode::DISPOSE_MARK => {
self.dispatch_dispose_mark();
}
OpCode::DISPOSE_POP => {
self.dispatch_dispose_pop()?;
}
OpCode::CELL_GET => {
self.dispatch_cell_get(rd, a, b)?;
}
+2 -2
View File
@@ -84,8 +84,8 @@ impl Vm {
f(RootGroup::SpillStack, v);
}
// 释放栈持 using 声明的资源值:漏根 → sweep 释放 → 释放点解引用悬垂。
for &v in &self.dispose_stack {
f(RootGroup::DisposeStack, v);
for (v, _) in &self.dispose_stack {
f(RootGroup::DisposeStack, *v);
}
for cell_vec in &self.cell_stack {
for &cell_ptr in cell_vec {
+6 -3
View File
@@ -230,10 +230,13 @@ pub struct Vm {
/// VM 级 spill 栈。`CallFrame.spill_offset` 定位本帧区:调用子函数时从边界后分配,
/// 帧恢复时截断到边界,子函数 spill 数据随帧丢弃。
pub(crate) spill_stack: Vec<JsValue>,
/// using/await using 声明的资源释放栈:DISPOSE_REGISTER 把资源值压入,
/// 作用域出口点逆序释放(释放点由后续指令族承担)。栈内 JsValue 是 GC 根
/// using/await using 声明的资源释放栈:DISPOSE_REGISTER 把(资源值,释放提示)
/// 压入,DISPOSE_POP 按水位逆序释放并截断。栈内 JsValue 是 GC 根
/// (`for_each_value` 遍历);run 边界清空,防跨 run 残留条目在下一 run 被误释放。
pub(crate) dispose_stack: Vec<JsValue>,
pub(crate) dispose_stack: Vec<(JsValue, u16)>,
/// 作用域水位栈:DISPOSE_MARK 压入当前释放栈深度,DISPOSE_POP 弹出并只释放
/// 水位以上条目。与释放栈同生共死(run 边界一并清空)。
pub(crate) dispose_marks: Vec<usize>,
/// 本次 native 调用的 spill 溢出实参区:`spill_stack[base..base+count)`。
/// 实参数超过寄存器窗口(253)时,窗口外的实参转存 spill 栈(GC 根),
/// native 侧经 `VmHost::native_arg_count`/`native_arg_at` 读取。仅在一次
+66 -4
View File
@@ -4,7 +4,7 @@ use oxide_runtime_api::{push_units_to, to_boolean, to_units_full, VmHost};
use oxide_types::object::{JsObject, PropAttributes};
use oxide_types::private_key::{
encode_symbol_key, int_key_value, is_int_key, is_private_name_key, is_symbol_key, make_int_key, INT_KEY_COUNT,
WELL_KNOWN_SYMBOL_HAS_INSTANCE,
WELL_KNOWN_SYMBOL_ASYNC_DISPOSE, WELL_KNOWN_SYMBOL_DISPOSE, WELL_KNOWN_SYMBOL_HAS_INSTANCE,
};
use oxide_types::value::JsValue;
@@ -649,16 +649,78 @@ impl Vm {
self.iters.pop_for_in();
}
/// using/await using 声明登记:把资源值(rd 槽)压入释放栈。
/// using/await using 声明登记:把资源值(rd 槽)与释放提示(a 槽)压入释放栈。
///
/// a 槽是释放提示(0 = 同步、1 = 异步),登记期原样保留供释放点分派;
/// 释放提示 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]);
self.dispose_stack.push((self.regs[rd], a as u16));
}
/// 作用域入口水位:把当前释放栈深度压入水位栈(DISPOSE_POP 的配对入口)。
///
/// # 副作用
/// - 水位栈增长。
pub(crate) fn dispatch_dispose_mark(&mut self) {
vm_trace!("DISPOSE_MARK depth={}", self.dispose_stack.len());
self.dispose_marks.push(self.dispose_stack.len());
}
/// 作用域出口:弹出水位,逆序释放水位以上资源并截断释放栈(规范
/// DisposeResources)。释放方法自身抛错按第一错误向外传播。
///
/// # 副作用
/// - 释放栈截断到水位、水位栈弹出;释放方法是任意 JS 调用(可再入,
/// 嵌套 using 声明会在其内再压释放栈)。
pub(crate) fn dispatch_dispose_pop(&mut self) -> Result<(), String> {
let mark = match self.dispose_marks.pop() {
Some(m) => m,
None => return Ok(()),
};
vm_trace!("DISPOSE_POP mark={} depth={}", mark, self.dispose_stack.len());
while self.dispose_stack.len() > mark {
let (value, hint) = self.dispose_stack.pop().expect("水位以上必有条目");
self.dispose_value(value, hint)?;
}
Ok(())
}
/// 单资源释放(规范 Dispose 语义):非对象跳过;按释放提示取方法(同步只取
/// Symbol.dispose;异步先取 Symbol.asyncDispose,未定义回退 Symbol.dispose),
/// 不可调用跳过;以资源自身为 this 零参调用。
///
/// # 边界与前提
/// - 异步方法结果为对象时须 Await 挂起,归后续任务;本期同步调用,结果丢弃。
fn dispose_value(&mut self, value: JsValue, hint: u16) -> Result<(), String> {
if !value.is_object() {
return Ok(());
}
let val_obj = unsafe { &*value.as_js_object_ptr() };
let dispose_key = encode_symbol_key(self.realm_id(), WELL_KNOWN_SYMBOL_DISPOSE);
let method = if hint == 1 {
let async_key = encode_symbol_key(self.realm_id(), WELL_KNOWN_SYMBOL_ASYNC_DISPOSE);
let async_method = self.ordinary_get(val_obj, async_key, value)?;
if async_method.is_undefined() {
self.ordinary_get(val_obj, dispose_key, value)?
} else {
async_method
}
} else {
self.ordinary_get(val_obj, dispose_key, value)?
};
if !method.is_object() {
return Ok(());
}
let method_obj = unsafe { &*method.as_js_object_ptr() };
if !method_obj.is_function() {
return Ok(());
}
let _ = self.call_function_sync(method, value, &[])?;
Ok(())
}
pub(crate) fn dispatch_for_of_init(&mut self, a: usize) -> Result<(), String> {
+110 -5
View File
@@ -1146,13 +1146,15 @@ mod tests {
#[test]
fn using_declaration_registers_dispose_stack() {
// using 声明经 DISPOSE_REGISTER 把资源值压入释放栈;run 边界(下次 run
// 入口)清栈,上一 run 的残留条目不得跨 run 可见。
// using 声明经 DISPOSE_REGISTER 把资源值压入释放栈。早 return 跳过函数
// 体 POP(穿越面,归后续任务),资源残留栈上;run 边界(下次 run 入口)
// 清栈,残留条目不得跨 run 可见。
let mut vm = Vm::new();
let module = Arc::new(compile("function f() { using x = { a: 1 }; } f();"));
let module = Arc::new(compile("function f() { using x = { a: 1 }; return 42; } f();"));
vm.run(&module).expect("run");
assert_eq!(vm.dispose_stack.len(), 1, "using 声明应登记一个资源值");
assert!(vm.dispose_stack[0].is_object(), "登记值应为资源对象");
assert_eq!(vm.dispose_stack.len(), 1, "早 return 跳过 POP,资源残留栈上");
assert!(vm.dispose_stack[0].0.is_object(), "登记值应为资源对象");
assert_eq!(vm.dispose_stack[0].1, 0, "using 声明释放提示应为同步");
// 二次 run 入口清栈后重新登记:残留条目不跨 run 存活。
vm.run(&module).expect("run2");
@@ -1162,4 +1164,107 @@ mod tests {
vm.clear_execution_state();
assert!(vm.dispose_stack.is_empty(), "执行状态清空应清空释放栈");
}
#[test]
fn using_disposed_at_end_of_block() {
// using 声明在块尾正常释放:Symbol.dispose 以资源自身为 this 调用一次。
let mut vm = Vm::new();
let module = Arc::new(compile(
"var count = 0, thisOk = false; \
function make() { return { [Symbol.dispose]: function () { count++; thisOk = (this === r); } }; } \
var r = make(); \
{ using x = r; }",
));
vm.run(&module).expect("run");
assert_eq!(global_value(&mut vm, "count"), JsValue::int(1), "块尾应释放一次");
assert_eq!(global_value(&mut vm, "thisOk"), JsValue::bool(true), "释放方法 this 应为资源自身");
}
#[test]
fn using_disposed_in_reverse_order() {
// 多资源按声明逆序释放(后声明者先释放):释放序拼成数字 321。
let mut vm = Vm::new();
let module = Arc::new(compile(
"var seq = 0; \
function m(a) { return { [Symbol.dispose]: function () { seq = seq * 10 + a; } }; } \
{ using a = m(1); using b = m(2); using c = m(3); }",
));
vm.run(&module).expect("run");
assert_eq!(as_number(&global_value(&mut vm, "seq")), 321.0, "逆序释放拼成 321");
}
#[test]
fn using_disposed_at_end_of_function_body() {
// 函数体尾释放:每次调用各释放一次,早 return 不释放(归穿越面)。
let mut vm = Vm::new();
let module = Arc::new(compile(
"var count = 0; \
function make() { return { [Symbol.dispose]: function () { count++; } }; } \
function f() { using x = make(); } \
f(); f();",
));
vm.run(&module).expect("run");
assert_eq!(global_value(&mut vm, "count"), JsValue::int(2), "两次调用各释放一次");
}
#[test]
fn using_disposed_at_end_of_for_statement() {
// C 风格 for 是整语句单一环境:每迭代的块尾释放,循环尾不重复释放。
let mut vm = Vm::new();
let module = Arc::new(compile(
"var count = 0; \
function make() { return { [Symbol.dispose]: function () { count++; } }; } \
for (var i = 0; i < 3; i++) { using x = make(); }",
));
vm.run(&module).expect("run");
assert_eq!(global_value(&mut vm, "count"), JsValue::int(3), "每迭代块尾各释放一次");
}
#[test]
fn using_disposed_each_iteration_of_for_of() {
// for-of 每迭代独立环境:每迭代 mark/pop,迭代器关闭与释放互不干扰。
let mut vm = Vm::new();
let module = Arc::new(compile(
"var count = 0; \
function make() { return { [Symbol.dispose]: function () { count++; } }; } \
for (using x of [make(), make(), make()]) { }",
));
vm.run(&module).expect("run");
assert_eq!(global_value(&mut vm, "count"), JsValue::int(3), "每迭代各释放一次");
}
#[test]
fn using_disposed_at_end_of_switch_and_try() {
// switch CaseBlock 单一环境、try/catch/finally 各体独立环境:各体出口
// 释放本体检证(catch 体未触发不释放)。
let mut vm = Vm::new();
let module = Arc::new(compile(
"var count = 0; \
function make() { return { [Symbol.dispose]: function () { count++; } }; } \
switch (1) { case 1: { using a = make(); break; } } \
try { using b = make(); } catch (e) { using c = make(); } finally { using d = make(); }",
));
vm.run(&module).expect("run");
assert_eq!(
global_value(&mut vm, "count"),
JsValue::int(3),
"switch 体、try 体、finally 体各释放一次(catch 未触发)"
);
}
#[test]
fn await_using_prefers_async_dispose_falls_back_to_dispose() {
// await using 释放:有 Symbol.asyncDispose 用之,未定义回退 Symbol.dispose。
let mut vm = Vm::new();
let module = Arc::new(compile(
"var asyncUsed = 0, syncUsed = 0; \
var withAsync = { [Symbol.asyncDispose]: function () { asyncUsed++; } }; \
var withSync = { [Symbol.dispose]: function () { syncUsed++; } }; \
{ await using a = withAsync; } \
{ await using b = withSync; }",
));
vm.run(&module).expect("run");
assert_eq!(global_value(&mut vm, "asyncUsed"), JsValue::int(1), "asyncDispose 优先");
assert_eq!(global_value(&mut vm, "syncUsed"), JsValue::int(1), "asyncDispose 未定义回退 dispose");
}
}
+4 -1
View File
@@ -131,6 +131,7 @@ impl Vm {
save_stack: Vec::new(),
spill_stack: Vec::new(),
dispose_stack: Vec::new(),
dispose_marks: Vec::new(),
native_overflow_base: 0,
native_overflow_count: 0,
try_stack: Vec::new(),
@@ -293,6 +294,7 @@ impl Vm {
save_stack: Vec::new(),
spill_stack: Vec::new(),
dispose_stack: Vec::new(),
dispose_marks: Vec::new(),
native_overflow_base: 0,
native_overflow_count: 0,
try_stack: Vec::new(),
@@ -518,8 +520,9 @@ impl Vm {
self.save_stack.clear();
self.spill_stack.clear();
// 释放栈条目持资源值(session 对象指针):跨 run/reset 残留会在下一 run
// 被误释放或悬垂,随执行状态一并清空。
// 被误释放或悬垂,随执行状态一并清空;水位栈同清,防陈旧水位误截断。
self.dispose_stack.clear();
self.dispose_marks.clear();
self.native_overflow_base = 0;
self.native_overflow_count = 0;
// native pack 实参区上界与溢出描述符同构:仅一次 native 调用期间有效,