mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 异常与 return 穿越释放资源,dispose_count 门控
This commit is contained in:
Generated
+19
-19
@@ -1160,7 +1160,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_builtins"
|
name = "oxide_builtins"
|
||||||
version = "0.13.16"
|
version = "0.13.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"fast-float",
|
"fast-float",
|
||||||
@@ -1185,7 +1185,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_bytecode"
|
name = "oxide_bytecode"
|
||||||
version = "0.13.16"
|
version = "0.13.17"
|
||||||
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.16"
|
version = "0.13.17"
|
||||||
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.16"
|
version = "0.13.17"
|
||||||
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.16"
|
version = "0.13.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lru",
|
"lru",
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
@@ -1235,7 +1235,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_compiler"
|
name = "oxide_compiler"
|
||||||
version = "0.13.16"
|
version = "0.13.17"
|
||||||
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.16"
|
version = "0.13.17"
|
||||||
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.16"
|
version = "0.13.17"
|
||||||
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.16"
|
version = "0.13.17"
|
||||||
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.16"
|
version = "0.13.17"
|
||||||
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.16"
|
version = "0.13.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
@@ -1319,7 +1319,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_liveness"
|
name = "oxide_liveness"
|
||||||
version = "0.13.16"
|
version = "0.13.17"
|
||||||
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.16"
|
version = "0.13.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-appender",
|
"tracing-appender",
|
||||||
@@ -1340,7 +1340,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_parser"
|
name = "oxide_parser"
|
||||||
version = "0.13.16"
|
version = "0.13.17"
|
||||||
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.16"
|
version = "0.13.17"
|
||||||
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.16"
|
version = "0.13.17"
|
||||||
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.16"
|
version = "0.13.17"
|
||||||
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.16"
|
version = "0.13.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
@@ -1415,7 +1415,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_vm"
|
name = "oxide_vm"
|
||||||
version = "0.13.16"
|
version = "0.13.17"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|||||||
+1
-1
@@ -23,7 +23,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.13.16"
|
version = "0.13.17"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.80"
|
rust-version = "1.80"
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// 手工构造反汇编夹具:顶层 5 条指令(NEW_OBJECT 1 字键表、CALL 1 ext 字、
|
/// 手工构造反汇编夹具:顶层 5 条指令(NEW_OBJECT 1 字键表、CALL 1 ext 字、
|
||||||
/// IC_GET_PROP 8 ext 字),子模块 1 个(3 条指令,RETURN 带 1 个逃出计数 ext 字)。
|
/// IC_GET_PROP 8 ext 字),子模块 1 个(3 条指令,RETURN 带 2 个逃出计数 ext 字)。
|
||||||
fn fixture() -> CompiledModule {
|
fn fixture() -> CompiledModule {
|
||||||
let sub = CompiledModule {
|
let sub = CompiledModule {
|
||||||
function_name: Some("f".into()),
|
function_name: Some("f".into()),
|
||||||
@@ -357,7 +357,8 @@ mod tests {
|
|||||||
bytecode: Arc::from([
|
bytecode: Arc::from([
|
||||||
opcode::encode(OpCode::LOAD_CONST, 1, 0, 0),
|
opcode::encode(OpCode::LOAD_CONST, 1, 0, 0),
|
||||||
opcode::encode(OpCode::RETURN, 1, 0, 0),
|
opcode::encode(OpCode::RETURN, 1, 0, 0),
|
||||||
0, // RETURN 逃出计数 ext 字
|
0, // RETURN 迭代器打包 ext 字
|
||||||
|
0, // RETURN dispose_count ext 字
|
||||||
opcode::encode(OpCode::HALT, 0, 0, 0),
|
opcode::encode(OpCode::HALT, 0, 0, 0),
|
||||||
]),
|
]),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -404,7 +405,7 @@ mod tests {
|
|||||||
" 0014 HALT r0",
|
" 0014 HALT r0",
|
||||||
" 0000 LOAD_CONST r1, const[0]",
|
" 0000 LOAD_CONST r1, const[0]",
|
||||||
" 0001 RETURN r1",
|
" 0001 RETURN r1",
|
||||||
" 0003 HALT r0",
|
" 0004 HALT r0",
|
||||||
];
|
];
|
||||||
for line in &expected {
|
for line in &expected {
|
||||||
assert!(lines.contains(line), "missing line: {line}\n{text}");
|
assert!(lines.contains(line), "missing line: {line}\n{text}");
|
||||||
|
|||||||
@@ -824,10 +824,11 @@ pub fn ext_word_count(bytecode: &[Instr], pc: usize) -> usize {
|
|||||||
| OpCode::GET_PRIVATE
|
| OpCode::GET_PRIVATE
|
||||||
| OpCode::SET_PRIVATE
|
| OpCode::SET_PRIVATE
|
||||||
| OpCode::PRIVATE_BRAND_IN => 2,
|
| OpCode::PRIVATE_BRAND_IN => 2,
|
||||||
// 逃出计数 ext:BREAK/CONTINUE/RETURN 恒带 1 个 pack_escape_counts 字
|
// 逃出计数 ext:BREAK/CONTINUE/RETURN 恒带 2 个字——第一字
|
||||||
// (for-of/for-in 逃出层数打包);lower 对这三条无条件落 ext 字,
|
// pack_escape_counts(for-of/for-in 逃出层数打包),第二字 dispose_count
|
||||||
// dispatch 经 read_escape_counts 消费,扫描必须同步跳过以免错位。
|
// (逃出资源条数);lower 对这三条无条件落 ext 字,dispatch 经
|
||||||
OpCode::BREAK | OpCode::CONTINUE | OpCode::RETURN => 1,
|
// read_escape_counts 消费,扫描必须同步跳过以免错位。
|
||||||
|
OpCode::BREAK | OpCode::CONTINUE | OpCode::RETURN => 2,
|
||||||
OpCode::CALL_SPREAD | OpCode::NEW_EXPRESSION_SPREAD | OpCode::SUPER_CALL_SPREAD => {
|
OpCode::CALL_SPREAD | OpCode::NEW_EXPRESSION_SPREAD | OpCode::SUPER_CALL_SPREAD => {
|
||||||
let header = bytecode.get(pc + 1).copied().unwrap_or(0);
|
let header = bytecode.get(pc + 1).copied().unwrap_or(0);
|
||||||
1 + (header & 0xFF) as usize + ((header >> 8) & 0xFF) as usize
|
1 + (header & 0xFF) as usize + ((header >> 8) & 0xFF) as usize
|
||||||
@@ -972,11 +973,11 @@ mod tests {
|
|||||||
// IC 族固定 8 字。
|
// IC 族固定 8 字。
|
||||||
let ic = [encode(OpCode::IC_GET_PROP, 0, 0, 0); IC_EXT_WORDS + 1];
|
let ic = [encode(OpCode::IC_GET_PROP, 0, 0, 0); IC_EXT_WORDS + 1];
|
||||||
assert_eq!(ext_word_count(&ic, 0), IC_EXT_WORDS);
|
assert_eq!(ext_word_count(&ic, 0), IC_EXT_WORDS);
|
||||||
// 1 字族(含 BREAK/CONTINUE/RETURN 逃出计数)。
|
// 1 字族。
|
||||||
assert_eq!(ext_word_count(&[encode(OpCode::CALL, 0, 0, 0), 3], 0), 1);
|
assert_eq!(ext_word_count(&[encode(OpCode::CALL, 0, 0, 0), 3], 0), 1);
|
||||||
assert_eq!(ext_word_count(&[encode(OpCode::BREAK, 0, 0, 0), 1], 0), 1);
|
// 2 字族(含 BREAK/CONTINUE/RETURN 逃出计数:迭代器打包字 + dispose_count 字)。
|
||||||
assert_eq!(ext_word_count(&[encode(OpCode::RETURN, 0, 0, 0), 1], 0), 1);
|
assert_eq!(ext_word_count(&[encode(OpCode::BREAK, 0, 0, 0), 1, 2], 0), 2);
|
||||||
// 2 字族。
|
assert_eq!(ext_word_count(&[encode(OpCode::RETURN, 0, 0, 0), 1, 2], 0), 2);
|
||||||
assert_eq!(ext_word_count(&[encode(OpCode::GET_PRIVATE, 0, 0, 0), 1, 2], 0), 2);
|
assert_eq!(ext_word_count(&[encode(OpCode::GET_PRIVATE, 0, 0, 0), 1, 2], 0), 2);
|
||||||
// 变长族:spread 首字 nstatic|nspread、TEMPLATE_STR 段数、GET_TEMPLATE_OBJECT 2+2n、
|
// 变长族:spread 首字 nstatic|nspread、TEMPLATE_STR 段数、GET_TEMPLATE_OBJECT 2+2n、
|
||||||
// CONCAT_N=n、NEW_OBJECT=a 槽属性数。
|
// CONCAT_N=n、NEW_OBJECT=a 槽属性数。
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ impl Emitter {
|
|||||||
module.insts.extend(field_ctx.insts);
|
module.insts.extend(field_ctx.insts);
|
||||||
module.constants = field_ctx.constants;
|
module.constants = field_ctx.constants;
|
||||||
module.n_registers = field_ctx.max_regs.max(1);
|
module.n_registers = field_ctx.max_regs.max(1);
|
||||||
module.insts.push(Inst::ret(Operand::Reg(undef_reg), 0, 0));
|
module.insts.push(Inst::ret(Operand::Reg(undef_reg), 0, 0, 0));
|
||||||
}
|
}
|
||||||
module
|
module
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ pub(crate) struct LoopEntry {
|
|||||||
pub(crate) v_reg: u32,
|
pub(crate) v_reg: u32,
|
||||||
/// 循环打开时已打开的释放作用域数(break/continue/return 逃出时据此
|
/// 循环打开时已打开的释放作用域数(break/continue/return 逃出时据此
|
||||||
/// 计算需穿越释放的作用域层数)。
|
/// 计算需穿越释放的作用域层数)。
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) dispose_depth_at_open: usize,
|
pub(crate) dispose_depth_at_open: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +87,6 @@ pub struct LabelScope {
|
|||||||
/// 标签打开时已打开的 for-in 循环数。
|
/// 标签打开时已打开的 for-in 循环数。
|
||||||
pub(crate) for_in_depth_at_open: usize,
|
pub(crate) for_in_depth_at_open: usize,
|
||||||
/// 标签打开时已打开的释放作用域数(break 逃出时据此计算需穿越释放的层数)。
|
/// 标签打开时已打开的释放作用域数(break 逃出时据此计算需穿越释放的层数)。
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) dispose_depth_at_open: usize,
|
pub(crate) dispose_depth_at_open: usize,
|
||||||
/// 标签目标出口结果寄存器:迭代标签取所包裹循环的 `v_reg`;非迭代标签仅当
|
/// 标签目标出口结果寄存器:迭代标签取所包裹循环的 `v_reg`;非迭代标签仅当
|
||||||
/// 体含指向本标签的 break 时分配(体正常完成回写、break 携值写入)。
|
/// 体含指向本标签的 break 时分配(体正常完成回写、break 携值写入)。
|
||||||
@@ -104,7 +102,6 @@ pub(crate) struct SwitchEntry {
|
|||||||
pub(crate) for_of_depth_at_open: usize,
|
pub(crate) for_of_depth_at_open: usize,
|
||||||
pub(crate) for_in_depth_at_open: usize,
|
pub(crate) for_in_depth_at_open: usize,
|
||||||
/// switch 打开时已打开的释放作用域数(switch 内 break 逃出时据此计算穿越层数)。
|
/// switch 打开时已打开的释放作用域数(switch 内 break 逃出时据此计算穿越层数)。
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) dispose_depth_at_open: usize,
|
pub(crate) dispose_depth_at_open: usize,
|
||||||
pub(crate) result_reg: u32,
|
pub(crate) result_reg: u32,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -509,18 +509,18 @@ impl Emitter {
|
|||||||
}
|
}
|
||||||
if is_expression_body {
|
if is_expression_body {
|
||||||
if let Some(reg) = last_result_reg {
|
if let Some(reg) = last_result_reg {
|
||||||
ctx.inst(Inst::ret(Operand::Reg(reg), 0, 0));
|
ctx.inst(Inst::ret(Operand::Reg(reg), 0, 0, 0));
|
||||||
} else {
|
} else {
|
||||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||||
let undef_reg = ctx.alloc_reg();
|
let undef_reg = ctx.alloc_reg();
|
||||||
ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx));
|
ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx));
|
||||||
ctx.inst(Inst::ret(Operand::Reg(undef_reg), 0, 0));
|
ctx.inst(Inst::ret(Operand::Reg(undef_reg), 0, 0, 0));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||||
let undef_reg = ctx.alloc_reg();
|
let undef_reg = ctx.alloc_reg();
|
||||||
ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx));
|
ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx));
|
||||||
ctx.inst(Inst::ret(Operand::Reg(undef_reg), 0, 0));
|
ctx.inst(Inst::ret(Operand::Reg(undef_reg), 0, 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用契约参数段只含固定形参:rest 是函数体内普通变量,不在 VM 实参传递区。
|
// 调用契约参数段只含固定形参:rest 是函数体内普通变量,不在 VM 实参传递区。
|
||||||
|
|||||||
@@ -938,9 +938,9 @@ impl Emitter {
|
|||||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||||
let r = ctx.alloc_reg();
|
let r = ctx.alloc_reg();
|
||||||
ctx.inst(Inst::load_const(Operand::Reg(r), undef_idx));
|
ctx.inst(Inst::load_const(Operand::Reg(r), undef_idx));
|
||||||
ctx.inst(Inst::ret(Operand::Reg(r), 0, 0));
|
ctx.inst(Inst::ret(Operand::Reg(r), 0, 0, 0));
|
||||||
} else {
|
} else {
|
||||||
ctx.inst(Inst::ret(Operand::Reg(ns_reg), 0, 0));
|
ctx.inst(Inst::ret(Operand::Reg(ns_reg), 0, 0, 0));
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1003,7 +1003,7 @@ impl Emitter {
|
|||||||
let kind_reg = self.load_string_const(kind, &mut ctx);
|
let kind_reg = self.load_string_const(kind, &mut ctx);
|
||||||
let content_reg = self.load_string_const(content, &mut ctx);
|
let content_reg = self.load_string_const(content, &mut ctx);
|
||||||
let val_reg = self.emit_module_call(&mut ctx, "__moduleData", &[kind_reg, content_reg])?;
|
let val_reg = self.emit_module_call(&mut ctx, "__moduleData", &[kind_reg, content_reg])?;
|
||||||
ctx.inst(Inst::ret(Operand::Reg(val_reg), 0, 0));
|
ctx.inst(Inst::ret(Operand::Reg(val_reg), 0, 0, 0));
|
||||||
Ok(ctx.assemble_ir(ParamLayout { base: 0, count: 0 }, None))
|
Ok(ctx.assemble_ir(ParamLayout { base: 0, count: 0 }, None))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,46 +33,51 @@ impl Emitter {
|
|||||||
pub(crate) fn emit_break_statement(
|
pub(crate) fn emit_break_statement(
|
||||||
&self, stmt: &oxide_parser::BreakStatement, ctx: &mut CompileCtx,
|
&self, stmt: &oxide_parser::BreakStatement, ctx: &mut CompileCtx,
|
||||||
) -> Result<Option<u32>, String> {
|
) -> Result<Option<u32>, String> {
|
||||||
let (break_label, fd_at_open, fod_at_open, fid_at_open, target_v_reg) = if let Some(label) = &stmt.label {
|
let (break_label, fd_at_open, fod_at_open, fid_at_open, dd_at_open, target_v_reg) =
|
||||||
let name = label.name.as_str();
|
if let Some(label) = &stmt.label {
|
||||||
let scope = ctx
|
let name = label.name.as_str();
|
||||||
.find_label(name)
|
let scope = ctx
|
||||||
.ok_or_else(|| format!("SyntaxError: Undefined label '{name}'"))?;
|
.find_label(name)
|
||||||
(
|
.ok_or_else(|| format!("SyntaxError: Undefined label '{name}'"))?;
|
||||||
scope.break_label,
|
(
|
||||||
scope.finally_depth_at_open,
|
scope.break_label,
|
||||||
scope.for_of_depth_at_open,
|
scope.finally_depth_at_open,
|
||||||
scope.for_in_depth_at_open,
|
scope.for_of_depth_at_open,
|
||||||
scope.completion_reg,
|
scope.for_in_depth_at_open,
|
||||||
)
|
scope.dispose_depth_at_open,
|
||||||
} else if let Some(sw) = ctx.current_switch() {
|
scope.completion_reg,
|
||||||
// switch 内 break:目标为 switch 出口。逃出计数以 switch 打开时的
|
)
|
||||||
// 深度快照为基准——switch 之前已打开的循环不属于本次逃出,仅当
|
} else if let Some(sw) = ctx.current_switch() {
|
||||||
// case 内新打开了迭代循环(并逃出)时才需关闭。
|
// switch 内 break:目标为 switch 出口。逃出计数以 switch 打开时的
|
||||||
(
|
// 深度快照为基准——switch 之前已打开的循环不属于本次逃出,仅当
|
||||||
sw.break_label,
|
// case 内新打开了迭代循环(并逃出)时才需关闭。
|
||||||
sw.finally_depth_at_open,
|
(
|
||||||
sw.for_of_depth_at_open,
|
sw.break_label,
|
||||||
sw.for_in_depth_at_open,
|
sw.finally_depth_at_open,
|
||||||
Some(sw.result_reg),
|
sw.for_of_depth_at_open,
|
||||||
)
|
sw.for_in_depth_at_open,
|
||||||
} else {
|
sw.dispose_depth_at_open,
|
||||||
let entry = ctx.current_loop().ok_or("break outside switch or loop".to_string())?;
|
Some(sw.result_reg),
|
||||||
(
|
)
|
||||||
entry.break_label,
|
} else {
|
||||||
entry.finally_depth_at_open,
|
let entry = ctx.current_loop().ok_or("break outside switch or loop".to_string())?;
|
||||||
entry.for_of_depth_at_open,
|
(
|
||||||
entry.for_in_depth_at_open,
|
entry.break_label,
|
||||||
Some(entry.v_reg),
|
entry.finally_depth_at_open,
|
||||||
)
|
entry.for_of_depth_at_open,
|
||||||
};
|
entry.for_in_depth_at_open,
|
||||||
|
entry.dispose_depth_at_open,
|
||||||
|
Some(entry.v_reg),
|
||||||
|
)
|
||||||
|
};
|
||||||
if let Some(target_v_reg) = target_v_reg {
|
if let Some(target_v_reg) = target_v_reg {
|
||||||
self.emit_completion_carry(ctx, target_v_reg);
|
self.emit_completion_carry(ctx, target_v_reg);
|
||||||
}
|
}
|
||||||
let crossed = ctx.labels.finally_depth.saturating_sub(fd_at_open) as u16;
|
let crossed = ctx.labels.finally_depth.saturating_sub(fd_at_open) as u16;
|
||||||
let (for_of_count, for_in_count) = escape_iter_counts(ctx, fod_at_open, fid_at_open);
|
let (for_of_count, for_in_count) = escape_iter_counts(ctx, fod_at_open, fid_at_open);
|
||||||
if crossed > 0 || for_of_count > 0 || for_in_count > 0 {
|
let dispose_count = ctx.labels.dispose_depth.saturating_sub(dd_at_open);
|
||||||
ctx.inst(Inst::brk(break_label, crossed, for_of_count, for_in_count));
|
if crossed > 0 || for_of_count > 0 || for_in_count > 0 || dispose_count > 0 {
|
||||||
|
ctx.inst(Inst::brk(break_label, crossed, for_of_count, for_in_count, dispose_count));
|
||||||
} else {
|
} else {
|
||||||
ctx.inst(Inst::jmp(break_label));
|
ctx.inst(Inst::jmp(break_label));
|
||||||
}
|
}
|
||||||
@@ -82,37 +87,41 @@ impl Emitter {
|
|||||||
pub(crate) fn emit_continue_statement(
|
pub(crate) fn emit_continue_statement(
|
||||||
&self, stmt: &oxide_parser::ContinueStatement, ctx: &mut CompileCtx,
|
&self, stmt: &oxide_parser::ContinueStatement, ctx: &mut CompileCtx,
|
||||||
) -> Result<Option<u32>, String> {
|
) -> Result<Option<u32>, String> {
|
||||||
let (continue_label, fd_at_open, fod_at_open, fid_at_open, target_v_reg) = if let Some(label) = &stmt.label {
|
let (continue_label, fd_at_open, fod_at_open, fid_at_open, dd_at_open, target_v_reg) =
|
||||||
let name = label.name.as_str();
|
if let Some(label) = &stmt.label {
|
||||||
let scope = ctx
|
let name = label.name.as_str();
|
||||||
.find_label(name)
|
let scope = ctx
|
||||||
.ok_or_else(|| format!("SyntaxError: Undefined label '{name}'"))?;
|
.find_label(name)
|
||||||
(
|
.ok_or_else(|| format!("SyntaxError: Undefined label '{name}'"))?;
|
||||||
scope.continue_label.ok_or_else(|| {
|
(
|
||||||
format!("SyntaxError: Illegal continue statement: '{name}' does not denote an iteration statement")
|
scope.continue_label.ok_or_else(|| {
|
||||||
})?,
|
format!("SyntaxError: Illegal continue statement: '{name}' does not denote an iteration statement")
|
||||||
scope.finally_depth_at_open,
|
})?,
|
||||||
scope.for_of_depth_at_open,
|
scope.finally_depth_at_open,
|
||||||
scope.for_in_depth_at_open,
|
scope.for_of_depth_at_open,
|
||||||
scope.completion_reg,
|
scope.for_in_depth_at_open,
|
||||||
)
|
scope.dispose_depth_at_open,
|
||||||
} else {
|
scope.completion_reg,
|
||||||
let entry = ctx.current_loop().ok_or("continue outside loop".to_string())?;
|
)
|
||||||
(
|
} else {
|
||||||
entry.continue_label,
|
let entry = ctx.current_loop().ok_or("continue outside loop".to_string())?;
|
||||||
entry.finally_depth_at_open,
|
(
|
||||||
entry.for_of_depth_at_open,
|
entry.continue_label,
|
||||||
entry.for_in_depth_at_open,
|
entry.finally_depth_at_open,
|
||||||
Some(entry.v_reg),
|
entry.for_of_depth_at_open,
|
||||||
)
|
entry.for_in_depth_at_open,
|
||||||
};
|
entry.dispose_depth_at_open,
|
||||||
|
Some(entry.v_reg),
|
||||||
|
)
|
||||||
|
};
|
||||||
if let Some(target_v_reg) = target_v_reg {
|
if let Some(target_v_reg) = target_v_reg {
|
||||||
self.emit_completion_carry(ctx, target_v_reg);
|
self.emit_completion_carry(ctx, target_v_reg);
|
||||||
}
|
}
|
||||||
let crossed = ctx.labels.finally_depth.saturating_sub(fd_at_open) as u16;
|
let crossed = ctx.labels.finally_depth.saturating_sub(fd_at_open) as u16;
|
||||||
let (for_of_count, for_in_count) = escape_iter_counts(ctx, fod_at_open, fid_at_open);
|
let (for_of_count, for_in_count) = escape_iter_counts(ctx, fod_at_open, fid_at_open);
|
||||||
if crossed > 0 || for_of_count > 0 || for_in_count > 0 {
|
let dispose_count = ctx.labels.dispose_depth.saturating_sub(dd_at_open);
|
||||||
ctx.inst(Inst::cont(continue_label, crossed, for_of_count, for_in_count));
|
if crossed > 0 || for_of_count > 0 || for_in_count > 0 || dispose_count > 0 {
|
||||||
|
ctx.inst(Inst::cont(continue_label, crossed, for_of_count, for_in_count, dispose_count));
|
||||||
} else {
|
} else {
|
||||||
ctx.inst(Inst::jmp(continue_label));
|
ctx.inst(Inst::jmp(continue_label));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ impl Emitter {
|
|||||||
let Statement::ReturnStatement(ret) = stmt else {
|
let Statement::ReturnStatement(ret) = stmt else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
// return 逃出全部打开的迭代循环:for-of 需 IteratorClose,for-in 弹出迭代器。
|
// return 逃出全部打开的迭代循环与释放作用域:for-of 需 IteratorClose,
|
||||||
|
// for-in 弹出迭代器,释放栈顶部资源需 DisposeResources。
|
||||||
let for_of_count = ctx.labels.for_of_depth;
|
let for_of_count = ctx.labels.for_of_depth;
|
||||||
let for_in_count = ctx.labels.for_in_depth;
|
let for_in_count = ctx.labels.for_in_depth;
|
||||||
|
let dispose_count = ctx.labels.dispose_depth;
|
||||||
match &ret.argument {
|
match &ret.argument {
|
||||||
Some(expr) => {
|
Some(expr) => {
|
||||||
let r = self.emit_expression(expr, ctx)?;
|
let r = self.emit_expression(expr, ctx)?;
|
||||||
@@ -22,15 +24,15 @@ impl Emitter {
|
|||||||
// 弹出连续纯 catch handler(finally handler 走运行时完成穿越逐个
|
// 弹出连续纯 catch handler(finally handler 走运行时完成穿越逐个
|
||||||
// 执行)。否则 return 跳过 TRY_END 会让 handler 残留在 try_stack,
|
// 执行)。否则 return 跳过 TRY_END 会让 handler 残留在 try_stack,
|
||||||
// 后续异常 unwind 会跳回已返回函数的 catch 形成死循环。
|
// 后续异常 unwind 会跳回已返回函数的 catch 形成死循环。
|
||||||
// 逃出迭代器时(close_count>0)保留 handler:return() 抛错须经
|
// 逃出迭代器或资源时(close_count>0)保留 handler:return() 抛错
|
||||||
// unwind 被外围 catch 捕获(新错误替代完成值),弹出后无法展开。
|
// 须经 unwind 被外围 catch 捕获(新错误替代完成值),弹出后无法展开。
|
||||||
if for_of_count == 0 && for_in_count == 0 {
|
if for_of_count == 0 && for_in_count == 0 && dispose_count == 0 {
|
||||||
self.emit_return_try_cleanup(ctx);
|
self.emit_return_try_cleanup(ctx);
|
||||||
}
|
}
|
||||||
ctx.inst(Inst::ret(Operand::Reg(r), for_of_count, for_in_count));
|
ctx.inst(Inst::ret(Operand::Reg(r), for_of_count, for_in_count, dispose_count));
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
if for_of_count == 0 && for_in_count == 0 {
|
if for_of_count == 0 && for_in_count == 0 && dispose_count == 0 {
|
||||||
self.emit_return_try_cleanup(ctx);
|
self.emit_return_try_cleanup(ctx);
|
||||||
}
|
}
|
||||||
// 裸 return 须显式物化 undefined 为完成值:RETURN 从 rd 槽读值,
|
// 裸 return 须显式物化 undefined 为完成值:RETURN 从 rd 槽读值,
|
||||||
@@ -38,7 +40,7 @@ impl Emitter {
|
|||||||
let undef_idx = ctx.add_constant(Constant::Undefined);
|
let undef_idx = ctx.add_constant(Constant::Undefined);
|
||||||
let undef_reg = ctx.alloc_reg();
|
let undef_reg = ctx.alloc_reg();
|
||||||
ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx));
|
ctx.inst(Inst::load_const(Operand::Reg(undef_reg), undef_idx));
|
||||||
ctx.inst(Inst::ret(Operand::Reg(undef_reg), for_of_count, for_in_count));
|
ctx.inst(Inst::ret(Operand::Reg(undef_reg), for_of_count, for_in_count, dispose_count));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|||||||
+12
-10
@@ -447,39 +447,41 @@ impl Inst {
|
|||||||
|
|
||||||
/// break 完成:label 指向循环/switch 出口,`crossed` 为逃出的 finally 域数
|
/// break 完成:label 指向循环/switch 出口,`crossed` 为逃出的 finally 域数
|
||||||
/// (emit 词法计算,运行时据此逐个穿越 finally;0 表示未逃出,不产生完成语义)。
|
/// (emit 词法计算,运行时据此逐个穿越 finally;0 表示未逃出,不产生完成语义)。
|
||||||
/// `for_of_count`/`for_in_count` 为逃出的迭代器层数,编码进 ext 字——
|
/// `for_of_count`/`for_in_count` 为逃出的迭代器层数,`dispose_count` 为逃出的
|
||||||
/// BREAK/CONTINUE 的 a/b 槽被跳转 offset 占用,无空闲位。
|
/// 资源条数,编码进 ext 字——BREAK/CONTINUE 的 a/b 槽被跳转 offset 占用,无空闲位。
|
||||||
/// 编码:rd 槽放 crossed(≤255),label 放 b 槽,ext[0] 打包两个逃出计数。
|
/// 编码:rd 槽放 crossed(≤255),label 放 b 槽,ext[0] 打包两个迭代器计数,
|
||||||
pub fn brk(label: LabelId, crossed: u16, for_of_count: usize, for_in_count: usize) -> Self {
|
/// ext[1] 放 dispose_count。
|
||||||
|
pub fn brk(label: LabelId, crossed: u16, for_of_count: usize, for_in_count: usize, dispose_count: usize) -> Self {
|
||||||
Self::with_ext(
|
Self::with_ext(
|
||||||
OpCode::BREAK,
|
OpCode::BREAK,
|
||||||
Operand::Imm(crossed),
|
Operand::Imm(crossed),
|
||||||
Operand::None,
|
Operand::None,
|
||||||
Operand::Label(label),
|
Operand::Label(label),
|
||||||
&[pack_escape_counts(for_of_count, for_in_count)],
|
&[pack_escape_counts(for_of_count, for_in_count), dispose_count as u32],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// continue 完成:同 break,label 指向循环继续目标。
|
/// continue 完成:同 break,label 指向循环继续目标。
|
||||||
pub fn cont(label: LabelId, crossed: u16, for_of_count: usize, for_in_count: usize) -> Self {
|
pub fn cont(label: LabelId, crossed: u16, for_of_count: usize, for_in_count: usize, dispose_count: usize) -> Self {
|
||||||
Self::with_ext(
|
Self::with_ext(
|
||||||
OpCode::CONTINUE,
|
OpCode::CONTINUE,
|
||||||
Operand::Imm(crossed),
|
Operand::Imm(crossed),
|
||||||
Operand::None,
|
Operand::None,
|
||||||
Operand::Label(label),
|
Operand::Label(label),
|
||||||
&[pack_escape_counts(for_of_count, for_in_count)],
|
&[pack_escape_counts(for_of_count, for_in_count), dispose_count as u32],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// return 完成:`src` 为返回值寄存器(None = 隐式 undefined,reg 0)。
|
/// return 完成:`src` 为返回值寄存器(None = 隐式 undefined,reg 0)。
|
||||||
/// `for_of_count`/`for_in_count` 为逃出的迭代器层数,编码进 ext 字。
|
/// `for_of_count`/`for_in_count` 为逃出的迭代器层数,`dispose_count` 为逃出的
|
||||||
pub fn ret(src: Operand, for_of_count: usize, for_in_count: usize) -> Self {
|
/// 资源条数,编码进 ext 字。
|
||||||
|
pub fn ret(src: Operand, for_of_count: usize, for_in_count: usize, dispose_count: usize) -> Self {
|
||||||
Self::with_ext(
|
Self::with_ext(
|
||||||
OpCode::RETURN,
|
OpCode::RETURN,
|
||||||
src,
|
src,
|
||||||
Operand::None,
|
Operand::None,
|
||||||
Operand::None,
|
Operand::None,
|
||||||
&[pack_escape_counts(for_of_count, for_in_count)],
|
&[pack_escape_counts(for_of_count, for_in_count), dispose_count as u32],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -723,15 +723,17 @@ impl Vm {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|h| h.frame_depth == self.frames.len() && h.finally_pc.is_some())
|
.filter(|h| h.frame_depth == self.frames.len() && h.finally_pc.is_some())
|
||||||
.count();
|
.count();
|
||||||
// .return()/.throw() 注入时生成器挂起快照中打开的全部迭代器一并逃出
|
// .return()/.throw() 注入时生成器挂起快照中打开的全部迭代器与资源一并逃出
|
||||||
// (栈上迭代器属于本生成器),完成恢复处统一关闭。
|
// (栈上迭代器/资源属于本生成器),完成恢复处统一关闭/释放。
|
||||||
let for_of_count = self.iters.for_of_iters.len();
|
let for_of_count = self.iters.for_of_iters.len();
|
||||||
let for_in_count = self.iters.for_in_iters.len();
|
let for_in_count = self.iters.for_in_iters.len();
|
||||||
|
let dispose_count = self.dispose_stack.len();
|
||||||
let completion = crate::vm::Completion::Return {
|
let completion = crate::vm::Completion::Return {
|
||||||
value,
|
value,
|
||||||
remaining_finally: crossed,
|
remaining_finally: crossed,
|
||||||
for_of_count,
|
for_of_count,
|
||||||
for_in_count,
|
for_in_count,
|
||||||
|
dispose_count,
|
||||||
};
|
};
|
||||||
if let Some(finally_pc) = self.record_completion(completion) {
|
if let Some(finally_pc) = self.record_completion(completion) {
|
||||||
self.pc = finally_pc;
|
self.pc = finally_pc;
|
||||||
|
|||||||
@@ -285,16 +285,19 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn break_ret_ext_word_count_is_one() {
|
fn break_ret_ext_word_count_is_two() {
|
||||||
// BREAK/CONTINUE/RETURN 恒带 1 个逃出计数 ext 字(pack_escape_counts)。
|
// BREAK/CONTINUE/RETURN 恒带 2 个逃出计数 ext 字:第一字
|
||||||
|
// pack_escape_counts(迭代器层数打包),第二字 dispose_count(资源条数)。
|
||||||
let bytecode = vec![
|
let bytecode = vec![
|
||||||
opcode::encode(OpCode::BREAK, 0, 0, 0),
|
opcode::encode(OpCode::BREAK, 0, 0, 0),
|
||||||
0x0000_0040,
|
0x0000_0040,
|
||||||
|
0x0000_0002,
|
||||||
opcode::encode(OpCode::RETURN, 1, 0, 0),
|
opcode::encode(OpCode::RETURN, 1, 0, 0),
|
||||||
0x1234_5678,
|
0x1234_5678,
|
||||||
|
0x0000_0003,
|
||||||
];
|
];
|
||||||
assert_eq!(ext_word_count(&bytecode, 0), 1, "BREAK ext 字数 = 1");
|
assert_eq!(ext_word_count(&bytecode, 0), 2, "BREAK ext 字数 = 2");
|
||||||
assert_eq!(ext_word_count(&bytecode, 2), 1, "RETURN ext 字数 = 1");
|
assert_eq!(ext_word_count(&bytecode, 3), 2, "RETURN ext 字数 = 2");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -331,12 +334,14 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn clear_ic_caches_skips_break_ext_word() {
|
fn clear_ic_caches_skips_break_ext_word() {
|
||||||
// BREAK 的逃出计数 ext 字必须原样保留;其低 8 位 = for_of_count,取 0x40
|
// BREAK 的逃出计数 ext 字(2 个:迭代器打包字 + dispose_count 字)必须原样
|
||||||
// (CALL 指令号,带 ext)——未登记时扫描会把它当 CALL 指令解析,越过后续
|
// 保留;第一字低 8 位 = for_of_count,取 0x40(CALL 指令号,带 ext)——未
|
||||||
// IC 指令导致扩展字漏清,本用例可抓住该错位。
|
// 登记时扫描会把它当 CALL 指令解析,越过后续 IC 指令导致扩展字漏清,本用例
|
||||||
|
// 可抓住该错位。
|
||||||
let mut bytecode = vec![
|
let mut bytecode = vec![
|
||||||
opcode::encode(OpCode::BREAK, 0, 0, 0),
|
opcode::encode(OpCode::BREAK, 0, 0, 0),
|
||||||
0x0000_0040,
|
0x0000_0040,
|
||||||
|
0x0000_0002,
|
||||||
opcode::encode(OpCode::IC_GET_PROP, 1, 2, 3),
|
opcode::encode(OpCode::IC_GET_PROP, 1, 2, 3),
|
||||||
0xAAAA_AAAA,
|
0xAAAA_AAAA,
|
||||||
0xBBBB_BBBB,
|
0xBBBB_BBBB,
|
||||||
@@ -348,8 +353,9 @@ mod tests {
|
|||||||
0x2222_2222,
|
0x2222_2222,
|
||||||
];
|
];
|
||||||
clear_ic_caches(&mut bytecode);
|
clear_ic_caches(&mut bytecode);
|
||||||
assert_eq!(bytecode[1], 0x0000_0040, "BREAK ext 字保持原值");
|
assert_eq!(bytecode[1], 0x0000_0040, "BREAK 第一 ext 字保持原值");
|
||||||
assert_eq!(&bytecode[3..=10], &[0; 8], "后续 IC 扩展字被清零");
|
assert_eq!(bytecode[2], 0x0000_0002, "BREAK 第二 ext 字保持原值");
|
||||||
|
assert_eq!(&bytecode[4..=11], &[0; 8], "后续 IC 扩展字被清零");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -312,6 +312,7 @@ mod tests {
|
|||||||
remaining_finally: 1,
|
remaining_finally: 1,
|
||||||
for_of_count: 0,
|
for_of_count: 0,
|
||||||
for_in_count: 0,
|
for_in_count: 0,
|
||||||
|
dispose_count: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut frame = SuspendedFrame::new_empty();
|
let mut frame = SuspendedFrame::new_empty();
|
||||||
|
|||||||
@@ -109,14 +109,18 @@ pub struct TryHandler {
|
|||||||
pub frame_depth: usize,
|
pub frame_depth: usize,
|
||||||
/// try 入口时 for_of_iters 的长度,界定异常展开时哪些迭代器需要 IteratorClose。
|
/// try 入口时 for_of_iters 的长度,界定异常展开时哪些迭代器需要 IteratorClose。
|
||||||
pub for_of_depth: usize,
|
pub for_of_depth: usize,
|
||||||
|
/// try 入口时释放栈(dispose_stack)的长度,界定异常展开时哪些资源需要
|
||||||
|
/// DisposeResources(逆序释放并截断到本水位)。
|
||||||
|
pub dispose_depth: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 控制流完成:break/continue/return 逃出 finally 域时暂存的完成目标。
|
/// 控制流完成:break/continue/return 逃出 finally 域时暂存的完成目标。
|
||||||
///
|
///
|
||||||
/// 仿 `pending_exception` 的侧通道:finally 执行期间悬挂在此,由 TRY_FINALLY_END
|
/// 仿 `pending_exception` 的侧通道:finally 执行期间悬挂在此,由 TRY_FINALLY_END
|
||||||
/// 逐个恢复(`remaining_finally` 为仍需穿越的 finally 体数,进入一个递减一个)。
|
/// 逐个恢复(`remaining_finally` 为仍需穿越的 finally 体数,进入一个递减一个)。
|
||||||
/// `for_of_count`/`for_in_count` 为逃出时需关闭的迭代器层数:在全部 finally 穿越
|
/// `for_of_count`/`for_in_count` 为逃出时需关闭的迭代器层数,`dispose_count` 为
|
||||||
/// 之后、跳转/返回之前执行(规范 §13.7.5.4 的 IteratorClose 在完成值之后)。
|
/// 逃出时需释放的资源条数:在全部 finally 穿越之后、跳转/返回之前执行(规范序
|
||||||
|
/// IteratorClose 先、DisposeResources 后)。
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum Completion {
|
pub enum Completion {
|
||||||
Break {
|
Break {
|
||||||
@@ -124,18 +128,21 @@ pub enum Completion {
|
|||||||
remaining_finally: usize,
|
remaining_finally: usize,
|
||||||
for_of_count: usize,
|
for_of_count: usize,
|
||||||
for_in_count: usize,
|
for_in_count: usize,
|
||||||
|
dispose_count: usize,
|
||||||
},
|
},
|
||||||
Continue {
|
Continue {
|
||||||
target_pc: usize,
|
target_pc: usize,
|
||||||
remaining_finally: usize,
|
remaining_finally: usize,
|
||||||
for_of_count: usize,
|
for_of_count: usize,
|
||||||
for_in_count: usize,
|
for_in_count: usize,
|
||||||
|
dispose_count: usize,
|
||||||
},
|
},
|
||||||
Return {
|
Return {
|
||||||
value: JsValue,
|
value: JsValue,
|
||||||
remaining_finally: usize,
|
remaining_finally: usize,
|
||||||
for_of_count: usize,
|
for_of_count: usize,
|
||||||
for_in_count: usize,
|
for_in_count: usize,
|
||||||
|
dispose_count: usize,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +174,15 @@ impl Completion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 逃出时需释放的资源条数(释放栈顶部连续条数)。
|
||||||
|
pub fn dispose_count(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
Completion::Break { dispose_count, .. }
|
||||||
|
| Completion::Continue { dispose_count, .. }
|
||||||
|
| Completion::Return { dispose_count, .. } => dispose_count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 复制并改写剩余 finally 计数(进入一个 finally 后递减)。
|
/// 复制并改写剩余 finally 计数(进入一个 finally 后递减)。
|
||||||
pub fn with_remaining(&self, remaining: usize) -> Completion {
|
pub fn with_remaining(&self, remaining: usize) -> Completion {
|
||||||
match *self {
|
match *self {
|
||||||
@@ -174,34 +190,40 @@ impl Completion {
|
|||||||
target_pc,
|
target_pc,
|
||||||
for_of_count,
|
for_of_count,
|
||||||
for_in_count,
|
for_in_count,
|
||||||
|
dispose_count,
|
||||||
..
|
..
|
||||||
} => Completion::Break {
|
} => Completion::Break {
|
||||||
target_pc,
|
target_pc,
|
||||||
remaining_finally: remaining,
|
remaining_finally: remaining,
|
||||||
for_of_count,
|
for_of_count,
|
||||||
for_in_count,
|
for_in_count,
|
||||||
|
dispose_count,
|
||||||
},
|
},
|
||||||
Completion::Continue {
|
Completion::Continue {
|
||||||
target_pc,
|
target_pc,
|
||||||
for_of_count,
|
for_of_count,
|
||||||
for_in_count,
|
for_in_count,
|
||||||
|
dispose_count,
|
||||||
..
|
..
|
||||||
} => Completion::Continue {
|
} => Completion::Continue {
|
||||||
target_pc,
|
target_pc,
|
||||||
remaining_finally: remaining,
|
remaining_finally: remaining,
|
||||||
for_of_count,
|
for_of_count,
|
||||||
for_in_count,
|
for_in_count,
|
||||||
|
dispose_count,
|
||||||
},
|
},
|
||||||
Completion::Return {
|
Completion::Return {
|
||||||
value,
|
value,
|
||||||
for_of_count,
|
for_of_count,
|
||||||
for_in_count,
|
for_in_count,
|
||||||
|
dispose_count,
|
||||||
..
|
..
|
||||||
} => Completion::Return {
|
} => Completion::Return {
|
||||||
value,
|
value,
|
||||||
remaining_finally: remaining,
|
remaining_finally: remaining,
|
||||||
for_of_count,
|
for_of_count,
|
||||||
for_in_count,
|
for_in_count,
|
||||||
|
dispose_count,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -631,7 +631,7 @@ impl Vm {
|
|||||||
/// ext 字携带逃出的迭代器层数:无 finally 穿越时立即关闭后跳转,有 finally
|
/// ext 字携带逃出的迭代器层数:无 finally 穿越时立即关闭后跳转,有 finally
|
||||||
/// 时计数随 Completion 悬挂,由 TRY_FINALLY_END 在穿越完成后消费。
|
/// 时计数随 Completion 悬挂,由 TRY_FINALLY_END 在穿越完成后消费。
|
||||||
pub(crate) fn dispatch_break(&mut self, instr: u32) -> Result<(), String> {
|
pub(crate) fn dispatch_break(&mut self, instr: u32) -> Result<(), String> {
|
||||||
let (for_of_count, for_in_count) = self.read_escape_counts();
|
let (for_of_count, for_in_count, dispose_count) = self.read_escape_counts();
|
||||||
let offset = opcode::offset16(instr) as isize;
|
let offset = opcode::offset16(instr) as isize;
|
||||||
let target_pc = ((self.pc as isize) + offset - 1) as usize;
|
let target_pc = ((self.pc as isize) + offset - 1) as usize;
|
||||||
let crossed = opcode::rd(instr) as usize;
|
let crossed = opcode::rd(instr) as usize;
|
||||||
@@ -640,6 +640,7 @@ impl Vm {
|
|||||||
remaining_finally: crossed,
|
remaining_finally: crossed,
|
||||||
for_of_count,
|
for_of_count,
|
||||||
for_in_count,
|
for_in_count,
|
||||||
|
dispose_count,
|
||||||
};
|
};
|
||||||
if let Some(finally_pc) = self.record_completion(completion) {
|
if let Some(finally_pc) = self.record_completion(completion) {
|
||||||
self.pc = finally_pc;
|
self.pc = finally_pc;
|
||||||
@@ -658,7 +659,7 @@ impl Vm {
|
|||||||
|
|
||||||
/// continue 完成:同 break,目标为循环继续位置。
|
/// continue 完成:同 break,目标为循环继续位置。
|
||||||
pub(crate) fn dispatch_continue(&mut self, instr: u32) -> Result<(), String> {
|
pub(crate) fn dispatch_continue(&mut self, instr: u32) -> Result<(), String> {
|
||||||
let (for_of_count, for_in_count) = self.read_escape_counts();
|
let (for_of_count, for_in_count, dispose_count) = self.read_escape_counts();
|
||||||
let offset = opcode::offset16(instr) as isize;
|
let offset = opcode::offset16(instr) as isize;
|
||||||
let target_pc = ((self.pc as isize) + offset - 1) as usize;
|
let target_pc = ((self.pc as isize) + offset - 1) as usize;
|
||||||
let crossed = opcode::rd(instr) as usize;
|
let crossed = opcode::rd(instr) as usize;
|
||||||
@@ -667,6 +668,7 @@ impl Vm {
|
|||||||
remaining_finally: crossed,
|
remaining_finally: crossed,
|
||||||
for_of_count,
|
for_of_count,
|
||||||
for_in_count,
|
for_in_count,
|
||||||
|
dispose_count,
|
||||||
};
|
};
|
||||||
if let Some(finally_pc) = self.record_completion(completion) {
|
if let Some(finally_pc) = self.record_completion(completion) {
|
||||||
self.pc = finally_pc;
|
self.pc = finally_pc;
|
||||||
@@ -683,19 +685,21 @@ impl Vm {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 读取 BREAK/CONTINUE/RETURN 的 ext 字逃出计数(低 16 位 for-of,高 16 位
|
/// 读取 BREAK/CONTINUE/RETURN 的 ext 字逃出计数:第一字低 16 位 for-of、
|
||||||
/// for-in)。调用时机:主循环已把 pc 推进到 ext 字位置;两条跳转/返回路径
|
/// 高 16 位 for-in,第二字为 dispose_count(逃出资源条数)。调用时机:主循环
|
||||||
/// 都会覆盖 pc,此处无需再推进。
|
/// 已把 pc 推进到 ext 字位置;两条跳转/返回路径都会覆盖 pc,此处无需再推进。
|
||||||
///
|
///
|
||||||
/// # 边界与前提
|
/// # 边界与前提
|
||||||
/// - 手工构造的 IR 可能让 RETURN/BREAK/CONTINUE 落在末位且无 ext 字(pc == len),
|
/// - 手工构造的 IR 可能让 RETURN/BREAK/CONTINUE 落在末位且无 ext 字(pc == len),
|
||||||
/// 此时按 (0, 0) 处理:无迭代器逃出,语义正确。
|
/// 此时按 (0, 0, 0) 处理:无迭代器/资源逃出,语义正确。第二字缺失(pc+1 == len)
|
||||||
fn read_escape_counts(&self) -> (usize, usize) {
|
/// 同样按 0 兜底。
|
||||||
|
fn read_escape_counts(&self) -> (usize, usize, usize) {
|
||||||
if self.pc >= self.bytecode.len() {
|
if self.pc >= self.bytecode.len() {
|
||||||
return (0, 0);
|
return (0, 0, 0);
|
||||||
}
|
}
|
||||||
let packed = self.bytecode[self.pc];
|
let packed = self.bytecode[self.pc];
|
||||||
((packed & 0xFFFF) as usize, (packed >> 16) as usize)
|
let dispose = self.bytecode.get(self.pc + 1).copied().unwrap_or(0) as usize;
|
||||||
|
((packed & 0xFFFF) as usize, (packed >> 16) as usize, dispose)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 记录一次控制流完成:穿越 `remaining_finally` 个 finally 体后执行完成本身。
|
/// 记录一次控制流完成:穿越 `remaining_finally` 个 finally 体后执行完成本身。
|
||||||
@@ -752,7 +756,7 @@ impl Vm {
|
|||||||
pub(crate) fn dispatch_return(&mut self, instr: u32) -> Result<Option<JsValue>, String> {
|
pub(crate) fn dispatch_return(&mut self, instr: u32) -> Result<Option<JsValue>, String> {
|
||||||
let rd = opcode::rd(instr) as usize;
|
let rd = opcode::rd(instr) as usize;
|
||||||
let result = self.regs[rd];
|
let result = self.regs[rd];
|
||||||
let (for_of_count, for_in_count) = self.read_escape_counts();
|
let (for_of_count, for_in_count, dispose_count) = self.read_escape_counts();
|
||||||
crate::vm_debug!(
|
crate::vm_debug!(
|
||||||
"RETURN depth={} saved_pc={}",
|
"RETURN depth={} saved_pc={}",
|
||||||
self.frames.len(),
|
self.frames.len(),
|
||||||
@@ -772,6 +776,7 @@ impl Vm {
|
|||||||
remaining_finally: crossed,
|
remaining_finally: crossed,
|
||||||
for_of_count,
|
for_of_count,
|
||||||
for_in_count,
|
for_in_count,
|
||||||
|
dispose_count,
|
||||||
};
|
};
|
||||||
if let Some(finally_pc) = self.record_completion(completion) {
|
if let Some(finally_pc) = self.record_completion(completion) {
|
||||||
self.pc = finally_pc;
|
self.pc = finally_pc;
|
||||||
@@ -903,6 +908,7 @@ impl Vm {
|
|||||||
finally_active: false,
|
finally_active: false,
|
||||||
frame_depth: self.frames.len(),
|
frame_depth: self.frames.len(),
|
||||||
for_of_depth: self.iters.for_of_iters.len(),
|
for_of_depth: self.iters.for_of_iters.len(),
|
||||||
|
dispose_depth: self.dispose_stack.len(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -921,6 +927,7 @@ impl Vm {
|
|||||||
finally_active: false,
|
finally_active: false,
|
||||||
frame_depth: self.frames.len(),
|
frame_depth: self.frames.len(),
|
||||||
for_of_depth: self.iters.for_of_iters.len(),
|
for_of_depth: self.iters.for_of_iters.len(),
|
||||||
|
dispose_depth: self.dispose_stack.len(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1029,6 +1036,12 @@ impl Vm {
|
|||||||
return Ok(CloseEscapeOutcome::Handled);
|
return Ok(CloseEscapeOutcome::Handled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// 逃出资源释放(规范序:IteratorClose 先、DisposeResources 后):逆序释放
|
||||||
|
// 顶部 dispose_count 条。释放方法抛错时新错误替代完成值,展开到外围处理器。
|
||||||
|
if self.dispose_top_n(completion.dispose_count()) {
|
||||||
|
self.unwind()?;
|
||||||
|
return Ok(CloseEscapeOutcome::Handled);
|
||||||
|
}
|
||||||
// 异步条目逐层关闭:首个有 return 方法的登记挂起,其余入 remaining;无
|
// 异步条目逐层关闭:首个有 return 方法的登记挂起,其余入 remaining;无
|
||||||
// return 方法时继续下一层。
|
// return 方法时继续下一层。
|
||||||
let mut remaining = async_iters;
|
let mut remaining = async_iters;
|
||||||
|
|||||||
@@ -689,6 +689,57 @@ impl Vm {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 逆序释放 `depth` 以上资源并截断释放栈到 `depth`,同步弹出水位栈中
|
||||||
|
/// `>= depth` 的基线条目(保两栈一致,防异常路径基线泄漏)。
|
||||||
|
///
|
||||||
|
/// # 步骤
|
||||||
|
/// 1. 逐条弹出并释放(`dispose_value`)。
|
||||||
|
/// 2. 释放方法抛错时,新错误替代在途异常向外传播(`exception_value` 改写),
|
||||||
|
/// 剩余资源继续释放(其错误被抑制,多错合并归后续任务)。
|
||||||
|
/// 3. 截断释放栈后,弹出水位栈中 `>= depth` 的基线条目。
|
||||||
|
///
|
||||||
|
/// # 返回值
|
||||||
|
/// `true` = 有释放方法抛错(新错误已写入 `exception_value`);`false` = 全部正常。
|
||||||
|
///
|
||||||
|
/// # 副作用
|
||||||
|
/// - 释放栈截断到 `depth`、水位栈弹出 `>= depth` 的基线。
|
||||||
|
/// - 有释放方法抛错时改写 `exception_value`/`pending_error_kind`。
|
||||||
|
pub(crate) fn dispose_above(&mut self, depth: usize) -> bool {
|
||||||
|
let mut threw = false;
|
||||||
|
let truncated = self.dispose_stack.len() > depth;
|
||||||
|
while self.dispose_stack.len() > depth {
|
||||||
|
let (value, hint) = self.dispose_stack.pop().expect("水位以上必有条目");
|
||||||
|
if let Err(e) = self.dispose_value(value, hint) {
|
||||||
|
let exc = self
|
||||||
|
.last_uncaught_value
|
||||||
|
.take()
|
||||||
|
.unwrap_or_else(|| oxide_builtins::error::create_from_text(self, &e));
|
||||||
|
let kind = self.thrown_error_kind(exc);
|
||||||
|
self.exception_value = Some(exc);
|
||||||
|
self.pending_error_kind = Some(kind);
|
||||||
|
threw = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 仅在实际截断释放栈时同步弹基线,防 no-op 调用(如 dispose_top_n(0))
|
||||||
|
// 误弹外层作用域的水位。
|
||||||
|
if truncated {
|
||||||
|
while self.dispose_marks.last().is_some_and(|&m| m >= depth) {
|
||||||
|
self.dispose_marks.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
threw
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 释放栈顶部 `n` 条资源(逃出释放:return/break/continue 完成值逃出作用域时
|
||||||
|
/// 逆序释放被逃出的资源)。`n` 超过栈长时按全量释放。
|
||||||
|
///
|
||||||
|
/// # 返回值
|
||||||
|
/// 同 `dispose_above`:`true` = 有释放方法抛错。
|
||||||
|
pub(crate) fn dispose_top_n(&mut self, n: usize) -> bool {
|
||||||
|
let depth = self.dispose_stack.len().saturating_sub(n);
|
||||||
|
self.dispose_above(depth)
|
||||||
|
}
|
||||||
|
|
||||||
/// 单资源释放(规范 Dispose 语义):非对象跳过;按释放提示取方法(同步只取
|
/// 单资源释放(规范 Dispose 语义):非对象跳过;按释放提示取方法(同步只取
|
||||||
/// Symbol.dispose;异步先取 Symbol.asyncDispose,未定义回退 Symbol.dispose),
|
/// Symbol.dispose;异步先取 Symbol.asyncDispose,未定义回退 Symbol.dispose),
|
||||||
/// 不可调用跳过;以资源自身为 this 零参调用。
|
/// 不可调用跳过;以资源自身为 this 零参调用。
|
||||||
|
|||||||
@@ -870,6 +870,9 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
// 对该处理器作用域内被中断的 for-of 循环执行 IteratorClose。
|
// 对该处理器作用域内被中断的 for-of 循环执行 IteratorClose。
|
||||||
self.close_for_of_above(handler.for_of_depth);
|
self.close_for_of_above(handler.for_of_depth);
|
||||||
|
// 异常穿越释放:逆序释放该作用域内登记的资源(规范序 IteratorClose 先、
|
||||||
|
// DisposeResources 后)。释放方法抛错时新错误替代在途异常继续向外展开。
|
||||||
|
self.dispose_above(handler.dispose_depth);
|
||||||
if let Some(finally_pc) = handler.finally_pc {
|
if let Some(finally_pc) = handler.finally_pc {
|
||||||
if handler.finally_active {
|
if handler.finally_active {
|
||||||
// 新异常在 finally 体内抛出:覆盖在途异常与完成,继续向外展开,
|
// 新异常在 finally 体内抛出:覆盖在途异常与完成,继续向外展开,
|
||||||
@@ -899,6 +902,8 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.close_for_of_above(0);
|
self.close_for_of_above(0);
|
||||||
|
// 未捕获兜底:释放栈上全部资源(异常穿越全作用域)。
|
||||||
|
self.dispose_above(0);
|
||||||
while let Some(frame) = self.frames.pop() {
|
while let Some(frame) = self.frames.pop() {
|
||||||
self.cell_stack.pop();
|
self.cell_stack.pop();
|
||||||
self.restore_frame(frame);
|
self.restore_frame(frame);
|
||||||
@@ -1003,6 +1008,7 @@ mod tests {
|
|||||||
remaining_finally: 1,
|
remaining_finally: 1,
|
||||||
for_of_count: 0,
|
for_of_count: 0,
|
||||||
for_in_count: 0,
|
for_in_count: 0,
|
||||||
|
dispose_count: 0,
|
||||||
});
|
});
|
||||||
vm.iters.for_of_iters.push(crate::vm_state::ForOfEntry {
|
vm.iters.for_of_iters.push(crate::vm_state::ForOfEntry {
|
||||||
iterator: JsValue::float(8.5),
|
iterator: JsValue::float(8.5),
|
||||||
@@ -1146,19 +1152,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn using_declaration_registers_dispose_stack() {
|
fn using_declaration_registers_dispose_stack() {
|
||||||
// using 声明经 DISPOSE_REGISTER 把资源值压入释放栈。早 return 跳过函数
|
// using 声明经 DISPOSE_REGISTER 把资源值压入释放栈。早 return 经逃出释放
|
||||||
// 体 POP(穿越面,归后续任务),资源残留栈上;run 边界(下次 run 入口)
|
// (dispose_top_n)逆序释放本作用域资源,函数返回后栈空;run 边界清栈防
|
||||||
// 清栈,残留条目不得跨 run 可见。
|
// 跨 run 残留。
|
||||||
let mut vm = Vm::new();
|
let mut vm = Vm::new();
|
||||||
let module = Arc::new(compile("function f() { using x = { a: 1 }; return 42; } f();"));
|
let module = Arc::new(compile("function f() { using x = { a: 1 }; return 42; } f();"));
|
||||||
vm.run(&module).expect("run");
|
vm.run(&module).expect("run");
|
||||||
assert_eq!(vm.dispose_stack.len(), 1, "早 return 跳过 POP,资源残留栈上");
|
assert!(vm.dispose_stack.is_empty(), "早 return 经逃出释放,函数返回后栈空");
|
||||||
assert!(vm.dispose_stack[0].0.is_object(), "登记值应为资源对象");
|
|
||||||
assert_eq!(vm.dispose_stack[0].1, 0, "using 声明释放提示应为同步");
|
|
||||||
|
|
||||||
// 二次 run 入口清栈后重新登记:残留条目不跨 run 存活。
|
// 二次 run 入口清栈:残留条目不跨 run 存活。
|
||||||
vm.run(&module).expect("run2");
|
vm.run(&module).expect("run2");
|
||||||
assert_eq!(vm.dispose_stack.len(), 1, "二次 run 应恰好登记一条新条目");
|
assert!(vm.dispose_stack.is_empty(), "二次 run 后栈仍空");
|
||||||
|
|
||||||
// 执行状态清空同样清释放栈。
|
// 执行状态清空同样清释放栈。
|
||||||
vm.clear_execution_state();
|
vm.clear_execution_state();
|
||||||
|
|||||||
@@ -841,6 +841,7 @@ fn reset_clears_runtime_state_like_rerun() {
|
|||||||
finally_active: false,
|
finally_active: false,
|
||||||
frame_depth: 0,
|
frame_depth: 0,
|
||||||
for_of_depth: 0,
|
for_of_depth: 0,
|
||||||
|
dispose_depth: 0,
|
||||||
});
|
});
|
||||||
vm.exception_value = Some(JsValue::int(2));
|
vm.exception_value = Some(JsValue::int(2));
|
||||||
vm.pending_exception = Some(JsValue::int(3));
|
vm.pending_exception = Some(JsValue::int(3));
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ fn spill_unspill_roundtrip() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn frame_boundary_parent_spill_survives_call() {
|
fn frame_boundary_parent_spill_survives_call() {
|
||||||
let child = int_module(
|
let child = int_module(
|
||||||
vec![Inst::load_const(Operand::Reg(3), 0), Inst::ret(Operand::Reg(3), 0, 0)],
|
vec![Inst::load_const(Operand::Reg(3), 0), Inst::ret(Operand::Reg(3), 0, 0, 0)],
|
||||||
99,
|
99,
|
||||||
4,
|
4,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
@@ -89,7 +89,7 @@ fn nested_call_both_spill() {
|
|||||||
vec![
|
vec![
|
||||||
Inst::load_const(Operand::Reg(3), 0),
|
Inst::load_const(Operand::Reg(3), 0),
|
||||||
Inst::inst_spill(Operand::Reg(3), 0),
|
Inst::inst_spill(Operand::Reg(3), 0),
|
||||||
Inst::ret(Operand::Reg(3), 0, 0),
|
Inst::ret(Operand::Reg(3), 0, 0, 0),
|
||||||
],
|
],
|
||||||
99,
|
99,
|
||||||
4,
|
4,
|
||||||
|
|||||||
Reference in New Issue
Block a user