feat: 支持 try-catch 语法字节码编译及缓存

This commit is contained in:
爱特每羊队
2026-06-07 21:31:05 +08:00
parent 3f41fe9d71
commit bd70fecea5
6 changed files with 210 additions and 6 deletions
+3
View File
@@ -43,6 +43,9 @@ pub(crate) enum Label {
ForInEnd(u32),
SwitchEnd(u32),
SwitchCase(u32),
CatchBody(u32),
FinallyBody(u32),
TryEnd(u32),
}
pub(crate) struct CompileCtx {
+55 -2
View File
@@ -195,8 +195,61 @@ impl Compiler {
// FD emits LOAD_CONST(BytecodeFunc) + STORE_VAR
ctx.projected_pc += 2;
}
Statement::ThrowStatement(_) => {
// throw not yet implemented; emit pass will error
Statement::ThrowStatement(ts) => {
self.count_expression(&ts.argument, ctx);
ctx.projected_pc += 1; // THROW
}
Statement::TryStatement(ts) => {
let id = ctx.next_label_id();
let catch_label = Label::CatchBody(id);
let try_end_label = Label::TryEnd(id);
let has_catch = ts.handler.is_some();
let has_finally = ts.finalizer.is_some();
if has_finally {
ctx.projected_pc += 1; // TRY_FINALLY_BEGIN (before try body)
}
if has_catch {
ctx.projected_pc += 1; // TRY_BEGIN (before try body)
}
for s in &ts.block.body {
self.count_statement(s, ctx);
}
if has_catch {
ctx.projected_pc += 1; // TRY_END
}
let jmp_needed = has_catch || has_finally;
if jmp_needed {
ctx.projected_pc += 1; // JMP
}
ctx.label_map.insert(catch_label, ctx.projected_pc);
if let Some(catch) = &ts.handler {
ctx.push_scope();
if let Some(_param) = &catch.param {
ctx.alloc_reg();
ctx.projected_pc += 1; // STORE_VAR
}
for cs in &catch.body.body {
self.count_statement(cs, ctx);
}
ctx.pop_scope();
}
if let Some(finally) = &ts.finalizer {
let finally_label = Label::FinallyBody(id);
ctx.label_map.insert(finally_label, ctx.projected_pc);
for fs in &finally.body {
self.count_statement(fs, ctx);
}
ctx.projected_pc += 1; // TRY_FINALLY_END
}
ctx.label_map.insert(try_end_label, ctx.projected_pc);
}
_ => {}
}
+96 -1
View File
@@ -371,7 +371,102 @@ impl Compiler {
Ok(None)
}
Statement::ThrowStatement(_) => Err("throw statement not supported".into()),
Statement::ThrowStatement(ts) => {
let exc_reg = self.emit_expression(&ts.argument, ctx)?;
ctx.emit(opcode::encode(OpCode::THROW, exc_reg, 0, 0));
Ok(None)
}
Statement::TryStatement(ts) => {
let id = ctx.next_label_id();
let catch_label = Label::CatchBody(id);
let try_end_label = Label::TryEnd(id);
let has_catch = ts.handler.is_some();
let has_finally = ts.finalizer.is_some();
let mut try_finally_begin_pos: Option<usize> = None;
let mut try_begin_pos: Option<usize> = None;
if has_finally {
try_finally_begin_pos = Some(ctx.bytecode.len());
ctx.emit(opcode::encode_try_finally_begin(0)); // placeholder
}
if has_catch {
try_begin_pos = Some(ctx.bytecode.len());
ctx.emit(opcode::encode_try_begin(0)); // placeholder
}
for s in &ts.block.body {
self.emit_statement(s, ctx)?;
}
if has_catch {
ctx.emit(opcode::encode(OpCode::TRY_END, 0, 0, 0));
}
let jmp_needed = has_catch || has_finally;
let jmp_skip_pos = if jmp_needed {
let pos = ctx.bytecode.len();
ctx.emit(opcode::encode_jmp(0)); // placeholder
Some(pos)
} else {
None
};
let catch_label_pc = ctx.bytecode.len();
ctx.label_map.insert(catch_label, catch_label_pc);
if let Some(try_begin_pc) = try_begin_pos {
let offset = catch_label_pc as isize - (try_begin_pc as isize + 1);
ctx.bytecode[try_begin_pc] = opcode::encode_try_begin(offset as i16);
}
if let Some(catch) = &ts.handler {
ctx.push_scope();
if let Some(param) = &catch.param {
let catch_reg = ctx.alloc_reg();
if let oxide_parser::BindingPattern::BindingIdentifier(bi) =
&param.pattern
{
ctx.declare_initialized(bi.name.as_str(), catch_reg)?;
ctx.emit(opcode::encode(OpCode::STORE_VAR, catch_reg, 0, 0));
}
}
for s in &catch.body.body {
self.emit_statement(s, ctx)?;
}
ctx.pop_scope();
}
if has_finally {
let finally_label = Label::FinallyBody(id);
let finally_label_pc = ctx.bytecode.len();
ctx.label_map.insert(finally_label, finally_label_pc);
if let Some(fb_pos) = try_finally_begin_pos {
let offset = finally_label_pc as isize - (fb_pos as isize + 1);
ctx.bytecode[fb_pos] = opcode::encode_try_finally_begin(offset as i16);
}
if let Some(jmp_pos) = jmp_skip_pos {
let offset = finally_label_pc as isize - (jmp_pos as isize + 1);
ctx.bytecode[jmp_pos] = opcode::encode_jmp(offset as i16);
}
for s in &ts.finalizer.as_ref().unwrap().body {
self.emit_statement(s, ctx)?;
}
ctx.emit(opcode::encode(OpCode::TRY_FINALLY_END, 0, 0, 0));
} else if let Some(jmp_pos) = jmp_skip_pos {
let offset = ctx.bytecode.len() as isize - (jmp_pos as isize + 1);
ctx.bytecode[jmp_pos] = opcode::encode_jmp(offset as i16);
}
let try_end_pc = ctx.bytecode.len();
ctx.label_map.insert(try_end_label, try_end_pc);
Ok(None)
}
_ => Ok(None),
}
}
+25 -1
View File
@@ -116,8 +116,32 @@ fn hash_statement(stmt: &Statement, h: &mut rustc_hash::FxHasher) {
}
}
}
Statement::ThrowStatement(_ts) => {
Statement::ThrowStatement(ts) => {
14u8.hash(h);
hash_expression(&ts.argument, h);
}
Statement::TryStatement(ts) => {
34u8.hash(h);
for s in &ts.block.body {
hash_statement(s, h);
}
if let Some(catch) = &ts.handler {
if let Some(param) = &catch.param {
if let oxide_parser::BindingPattern::BindingIdentifier(bi) =
&param.pattern
{
bi.name.as_str().hash(h);
}
}
for s in &catch.body.body {
hash_statement(s, h);
}
}
if let Some(finally) = &ts.finalizer {
for s in &finally.body {
hash_statement(s, h);
}
}
}
_ => {
std::mem::discriminant(stmt).hash(h);
+30 -1
View File
@@ -55,7 +55,14 @@ pub enum OpCode {
SWITCH_TABLE = 0x2C,
FOR_IN_CLEANUP = 0x2D,
// ── Variable (0x30-0x3F) ──
// -- Exception (0x2E-0x2F, 0x33-0x35) --
THROW = 0x2E,
TRY_BEGIN = 0x2F,
TRY_END = 0x33,
TRY_FINALLY_BEGIN = 0x34,
TRY_FINALLY_END = 0x35,
// -- Variable (0x30-0x32) --
LOAD_VAR = 0x30,
STORE_VAR = 0x31,
LOAD_CONST = 0x32,
@@ -178,9 +185,14 @@ impl TryFrom<u8> for OpCode {
0x2B => Ok(OpCode::FOR_IN_DONE),
0x2C => Ok(OpCode::SWITCH_TABLE),
0x2D => Ok(OpCode::FOR_IN_CLEANUP),
0x2E => Ok(OpCode::THROW),
0x2F => Ok(OpCode::TRY_BEGIN),
0x30 => Ok(OpCode::LOAD_VAR),
0x31 => Ok(OpCode::STORE_VAR),
0x32 => Ok(OpCode::LOAD_CONST),
0x33 => Ok(OpCode::TRY_END),
0x34 => Ok(OpCode::TRY_FINALLY_BEGIN),
0x35 => Ok(OpCode::TRY_FINALLY_END),
0x40 => Ok(OpCode::CALL),
0x41 => Ok(OpCode::RETURN),
0x42 => Ok(OpCode::CALL_NATIVE),
@@ -259,6 +271,11 @@ impl fmt::Display for OpCode {
OpCode::FOR_IN_DONE => "FOR_IN_DONE",
OpCode::SWITCH_TABLE => "SWITCH_TABLE",
OpCode::FOR_IN_CLEANUP => "FOR_IN_CLEANUP",
OpCode::THROW => "THROW",
OpCode::TRY_BEGIN => "TRY_BEGIN",
OpCode::TRY_END => "TRY_END",
OpCode::TRY_FINALLY_BEGIN => "TRY_FINALLY_BEGIN",
OpCode::TRY_FINALLY_END => "TRY_FINALLY_END",
OpCode::LOAD_VAR => "LOAD_VAR",
OpCode::STORE_VAR => "STORE_VAR",
OpCode::LOAD_CONST => "LOAD_CONST",
@@ -353,3 +370,15 @@ pub fn encode_jmp_if_true(rd: u8, offset: i16) -> Instr {
let hi = ((offset as u16 >> 8) & 0xFF) as u8;
encode(OpCode::JMP_IF_TRUE, rd, lo, hi)
}
pub fn encode_try_begin(offset: i16) -> Instr {
let lo = (offset as u16 & 0xFF) as u8;
let hi = ((offset as u16 >> 8) & 0xFF) as u8;
encode(OpCode::TRY_BEGIN, 0, lo, hi)
}
pub fn encode_try_finally_begin(offset: i16) -> Instr {
let lo = (offset as u16 & 0xFF) as u8;
let hi = ((offset as u16 >> 8) & 0xFF) as u8;
encode(OpCode::TRY_FINALLY_BEGIN, 0, lo, hi)
}
+1 -1
View File
@@ -61,7 +61,7 @@ fn regression_recursion_depth_limit() {
fn regression_throw_statement_errors() {
assert_eq!(
eval("throw 'error'"),
"compile error: throw statement not supported"
"vm error: opcode THROW not yet implemented"
);
}