mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-27 04:01:11 +08:00
feat: 支持 try-catch 语法字节码执行
This commit is contained in:
@@ -206,6 +206,8 @@ impl Compiler {
|
|||||||
let has_catch = ts.handler.is_some();
|
let has_catch = ts.handler.is_some();
|
||||||
let has_finally = ts.finalizer.is_some();
|
let has_finally = ts.finalizer.is_some();
|
||||||
|
|
||||||
|
ctx.alloc_reg(); // result_reg
|
||||||
|
|
||||||
if has_finally {
|
if has_finally {
|
||||||
ctx.projected_pc += 1; // TRY_FINALLY_BEGIN (before try body)
|
ctx.projected_pc += 1; // TRY_FINALLY_BEGIN (before try body)
|
||||||
}
|
}
|
||||||
@@ -217,6 +219,7 @@ impl Compiler {
|
|||||||
for s in &ts.block.body {
|
for s in &ts.block.body {
|
||||||
self.count_statement(s, ctx);
|
self.count_statement(s, ctx);
|
||||||
}
|
}
|
||||||
|
ctx.projected_pc += 1; // LOAD_VAR result_reg (if try body has result)
|
||||||
|
|
||||||
if has_catch {
|
if has_catch {
|
||||||
ctx.projected_pc += 1; // TRY_END
|
ctx.projected_pc += 1; // TRY_END
|
||||||
@@ -237,6 +240,7 @@ impl Compiler {
|
|||||||
for cs in &catch.body.body {
|
for cs in &catch.body.body {
|
||||||
self.count_statement(cs, ctx);
|
self.count_statement(cs, ctx);
|
||||||
}
|
}
|
||||||
|
ctx.projected_pc += 1; // LOAD_VAR result_reg (if catch body has result)
|
||||||
ctx.pop_scope();
|
ctx.pop_scope();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,6 +250,7 @@ impl Compiler {
|
|||||||
for fs in &finally.body {
|
for fs in &finally.body {
|
||||||
self.count_statement(fs, ctx);
|
self.count_statement(fs, ctx);
|
||||||
}
|
}
|
||||||
|
ctx.projected_pc += 1; // LOAD_VAR result_reg (if finally has result)
|
||||||
ctx.projected_pc += 1; // TRY_FINALLY_END
|
ctx.projected_pc += 1; // TRY_FINALLY_END
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -383,6 +383,7 @@ impl Compiler {
|
|||||||
let has_catch = ts.handler.is_some();
|
let has_catch = ts.handler.is_some();
|
||||||
let has_finally = ts.finalizer.is_some();
|
let has_finally = ts.finalizer.is_some();
|
||||||
|
|
||||||
|
let result_reg = ctx.alloc_reg();
|
||||||
let mut try_finally_begin_pos: Option<usize> = None;
|
let mut try_finally_begin_pos: Option<usize> = None;
|
||||||
let mut try_begin_pos: Option<usize> = None;
|
let mut try_begin_pos: Option<usize> = None;
|
||||||
|
|
||||||
@@ -396,9 +397,18 @@ impl Compiler {
|
|||||||
ctx.emit(opcode::encode_try_begin(0)); // placeholder
|
ctx.emit(opcode::encode_try_begin(0)); // placeholder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut last_try_result: Option<u8> = None;
|
||||||
for s in &ts.block.body {
|
for s in &ts.block.body {
|
||||||
self.emit_statement(s, ctx)?;
|
if let Some(r) = self.emit_statement(s, ctx)? {
|
||||||
|
last_try_result = Some(r);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
ctx.emit(opcode::encode(
|
||||||
|
OpCode::LOAD_VAR,
|
||||||
|
result_reg,
|
||||||
|
last_try_result.unwrap_or(result_reg),
|
||||||
|
0,
|
||||||
|
));
|
||||||
|
|
||||||
if has_catch {
|
if has_catch {
|
||||||
ctx.emit(opcode::encode(OpCode::TRY_END, 0, 0, 0));
|
ctx.emit(opcode::encode(OpCode::TRY_END, 0, 0, 0));
|
||||||
@@ -417,7 +427,7 @@ impl Compiler {
|
|||||||
ctx.label_map.insert(catch_label, catch_label_pc);
|
ctx.label_map.insert(catch_label, catch_label_pc);
|
||||||
|
|
||||||
if let Some(try_begin_pc) = try_begin_pos {
|
if let Some(try_begin_pc) = try_begin_pos {
|
||||||
let offset = catch_label_pc as isize - (try_begin_pc as isize + 1);
|
let offset = catch_label_pc as isize - (try_begin_pc as isize);
|
||||||
ctx.bytecode[try_begin_pc] = opcode::encode_try_begin(offset as i16);
|
ctx.bytecode[try_begin_pc] = opcode::encode_try_begin(offset as i16);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,16 +435,24 @@ impl Compiler {
|
|||||||
ctx.push_scope();
|
ctx.push_scope();
|
||||||
if let Some(param) = &catch.param {
|
if let Some(param) = &catch.param {
|
||||||
let catch_reg = ctx.alloc_reg();
|
let catch_reg = ctx.alloc_reg();
|
||||||
if let oxide_parser::BindingPattern::BindingIdentifier(bi) =
|
if let oxide_parser::BindingPattern::BindingIdentifier(bi) = ¶m.pattern
|
||||||
¶m.pattern
|
|
||||||
{
|
{
|
||||||
ctx.declare_initialized(bi.name.as_str(), catch_reg)?;
|
ctx.declare_initialized(bi.name.as_str(), catch_reg)?;
|
||||||
ctx.emit(opcode::encode(OpCode::STORE_VAR, catch_reg, 0, 0));
|
ctx.emit(opcode::encode(OpCode::STORE_VAR, catch_reg, 0, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut last_catch_result: Option<u8> = None;
|
||||||
for s in &catch.body.body {
|
for s in &catch.body.body {
|
||||||
self.emit_statement(s, ctx)?;
|
if let Some(r) = self.emit_statement(s, ctx)? {
|
||||||
|
last_catch_result = Some(r);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
ctx.emit(opcode::encode(
|
||||||
|
OpCode::LOAD_VAR,
|
||||||
|
result_reg,
|
||||||
|
last_catch_result.unwrap_or(result_reg),
|
||||||
|
0,
|
||||||
|
));
|
||||||
ctx.pop_scope();
|
ctx.pop_scope();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,30 +460,39 @@ impl Compiler {
|
|||||||
let finally_label = Label::FinallyBody(id);
|
let finally_label = Label::FinallyBody(id);
|
||||||
let finally_label_pc = ctx.bytecode.len();
|
let finally_label_pc = ctx.bytecode.len();
|
||||||
ctx.label_map.insert(finally_label, finally_label_pc);
|
ctx.label_map.insert(finally_label, finally_label_pc);
|
||||||
|
|
||||||
if let Some(fb_pos) = try_finally_begin_pos {
|
if let Some(fb_pos) = try_finally_begin_pos {
|
||||||
let offset = finally_label_pc as isize - (fb_pos as isize + 1);
|
let offset = finally_label_pc as isize - (fb_pos as isize);
|
||||||
ctx.bytecode[fb_pos] = opcode::encode_try_finally_begin(offset as i16);
|
ctx.bytecode[fb_pos] =
|
||||||
|
opcode::encode_try_finally_begin(offset as i16);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(jmp_pos) = jmp_skip_pos {
|
if let Some(jmp_pos) = jmp_skip_pos {
|
||||||
let offset = finally_label_pc as isize - (jmp_pos as isize + 1);
|
let offset = finally_label_pc as isize - (jmp_pos as isize);
|
||||||
ctx.bytecode[jmp_pos] = opcode::encode_jmp(offset as i16);
|
ctx.bytecode[jmp_pos] = opcode::encode_jmp(offset as i16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut last_finally_result: Option<u8> = None;
|
||||||
for s in &ts.finalizer.as_ref().unwrap().body {
|
for s in &ts.finalizer.as_ref().unwrap().body {
|
||||||
self.emit_statement(s, ctx)?;
|
if let Some(r) = self.emit_statement(s, ctx)? {
|
||||||
|
last_finally_result = Some(r);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
ctx.emit(opcode::encode(
|
||||||
|
OpCode::LOAD_VAR,
|
||||||
|
result_reg,
|
||||||
|
last_finally_result.unwrap_or(result_reg),
|
||||||
|
0,
|
||||||
|
));
|
||||||
ctx.emit(opcode::encode(OpCode::TRY_FINALLY_END, 0, 0, 0));
|
ctx.emit(opcode::encode(OpCode::TRY_FINALLY_END, 0, 0, 0));
|
||||||
} else if let Some(jmp_pos) = jmp_skip_pos {
|
} else if let Some(jmp_pos) = jmp_skip_pos {
|
||||||
let offset = ctx.bytecode.len() as isize - (jmp_pos as isize + 1);
|
let offset = ctx.bytecode.len() as isize - (jmp_pos as isize);
|
||||||
ctx.bytecode[jmp_pos] = opcode::encode_jmp(offset as i16);
|
ctx.bytecode[jmp_pos] = opcode::encode_jmp(offset as i16);
|
||||||
}
|
}
|
||||||
|
|
||||||
let try_end_pc = ctx.bytecode.len();
|
let try_end_pc = ctx.bytecode.len();
|
||||||
ctx.label_map.insert(try_end_label, try_end_pc);
|
ctx.label_map.insert(try_end_label, try_end_pc);
|
||||||
|
|
||||||
Ok(None)
|
Ok(Some(result_reg))
|
||||||
}
|
}
|
||||||
_ => Ok(None),
|
_ => Ok(None),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,9 +127,7 @@ fn hash_statement(stmt: &Statement, h: &mut rustc_hash::FxHasher) {
|
|||||||
}
|
}
|
||||||
if let Some(catch) = &ts.handler {
|
if let Some(catch) = &ts.handler {
|
||||||
if let Some(param) = &catch.param {
|
if let Some(param) = &catch.param {
|
||||||
if let oxide_parser::BindingPattern::BindingIdentifier(bi) =
|
if let oxide_parser::BindingPattern::BindingIdentifier(bi) = ¶m.pattern {
|
||||||
¶m.pattern
|
|
||||||
{
|
|
||||||
bi.name.as_str().hash(h);
|
bi.name.as_str().hash(h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+101
-1
@@ -49,6 +49,12 @@ pub struct ForInIter<'bump> {
|
|||||||
pub index: usize,
|
pub index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct TryHandler {
|
||||||
|
pub catch_pc: Option<usize>,
|
||||||
|
pub finally_pc: Option<usize>,
|
||||||
|
pub frame_depth: usize,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Vm {
|
pub struct Vm {
|
||||||
regs: [JsValue; 256],
|
regs: [JsValue; 256],
|
||||||
pc: usize,
|
pc: usize,
|
||||||
@@ -67,6 +73,10 @@ pub struct Vm {
|
|||||||
saved_bytecode_stack: Vec<Vec<opcode::Instr>>,
|
saved_bytecode_stack: Vec<Vec<opcode::Instr>>,
|
||||||
/// Stack of saved constants for nested bytecode calls
|
/// Stack of saved constants for nested bytecode calls
|
||||||
saved_constants_stack: Vec<Vec<JsValue>>,
|
saved_constants_stack: Vec<Vec<JsValue>>,
|
||||||
|
try_stack: Vec<TryHandler>,
|
||||||
|
exception_value: Option<JsValue>,
|
||||||
|
pending_exception: Option<JsValue>,
|
||||||
|
pending_error_kind: Option<&'static str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bind_object(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
fn bind_object(kernel: &Arc<OxideKernel>, global: &mut JsObject) {
|
||||||
@@ -660,6 +670,10 @@ impl Vm {
|
|||||||
sub_module_constants: Vec::new(),
|
sub_module_constants: Vec::new(),
|
||||||
saved_bytecode_stack: Vec::new(),
|
saved_bytecode_stack: Vec::new(),
|
||||||
saved_constants_stack: Vec::new(),
|
saved_constants_stack: Vec::new(),
|
||||||
|
try_stack: Vec::new(),
|
||||||
|
exception_value: None,
|
||||||
|
pending_exception: None,
|
||||||
|
pending_error_kind: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,6 +694,10 @@ impl Vm {
|
|||||||
sub_module_constants: Vec::new(),
|
sub_module_constants: Vec::new(),
|
||||||
saved_bytecode_stack: Vec::new(),
|
saved_bytecode_stack: Vec::new(),
|
||||||
saved_constants_stack: Vec::new(),
|
saved_constants_stack: Vec::new(),
|
||||||
|
try_stack: Vec::new(),
|
||||||
|
exception_value: None,
|
||||||
|
pending_exception: None,
|
||||||
|
pending_error_kind: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -864,6 +882,10 @@ impl Vm {
|
|||||||
self.regs = [JsValue::undefined(); 256];
|
self.regs = [JsValue::undefined(); 256];
|
||||||
self.frames.clear();
|
self.frames.clear();
|
||||||
self.for_in_iters.clear();
|
self.for_in_iters.clear();
|
||||||
|
self.try_stack.clear();
|
||||||
|
self.exception_value = None;
|
||||||
|
self.pending_exception = None;
|
||||||
|
self.pending_error_kind = None;
|
||||||
self.clear_ic_caches();
|
self.clear_ic_caches();
|
||||||
self.dispatch()
|
self.dispatch()
|
||||||
}
|
}
|
||||||
@@ -895,6 +917,10 @@ impl Vm {
|
|||||||
self.frames.clear();
|
self.frames.clear();
|
||||||
self.saved_bytecode_stack.clear();
|
self.saved_bytecode_stack.clear();
|
||||||
self.saved_constants_stack.clear();
|
self.saved_constants_stack.clear();
|
||||||
|
self.try_stack.clear();
|
||||||
|
self.exception_value = None;
|
||||||
|
self.pending_exception = None;
|
||||||
|
self.pending_error_kind = None;
|
||||||
|
|
||||||
for (name, reg) in &module.builtin_reg_map {
|
for (name, reg) in &module.builtin_reg_map {
|
||||||
let si = self.kernel.string_forge().intern(name.as_str()).0;
|
let si = self.kernel.string_forge().intern(name.as_str()).0;
|
||||||
@@ -922,6 +948,31 @@ impl Vm {
|
|||||||
Err("bytecode function calls not yet supported".into())
|
Err("bytecode function calls not yet supported".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unwind(&mut self) -> Result<(), String> {
|
||||||
|
while let Some(handler) = self.try_stack.pop() {
|
||||||
|
if let Some(finally_pc) = handler.finally_pc {
|
||||||
|
if self.pending_exception.is_none() {
|
||||||
|
self.pending_exception = self.exception_value.take();
|
||||||
|
}
|
||||||
|
self.try_stack.push(handler);
|
||||||
|
self.pc = finally_pc;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
if let Some(catch_pc) = handler.catch_pc {
|
||||||
|
let exc = self.exception_value.take().unwrap_or(JsValue::undefined());
|
||||||
|
self.regs[0] = exc;
|
||||||
|
self.pc = catch_pc;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let msg = self
|
||||||
|
.exception_value
|
||||||
|
.take()
|
||||||
|
.map(|v| format!("uncaught {v}"))
|
||||||
|
.unwrap_or_else(|| "uncaught exception".to_string());
|
||||||
|
Err(msg)
|
||||||
|
}
|
||||||
|
|
||||||
fn dispatch(&mut self) -> Result<JsValue, String> {
|
fn dispatch(&mut self) -> Result<JsValue, String> {
|
||||||
let mut steps: u64 = 0;
|
let mut steps: u64 = 0;
|
||||||
const MAX_STEPS: u64 = 100_000_000;
|
const MAX_STEPS: u64 = 100_000_000;
|
||||||
@@ -1264,7 +1315,7 @@ impl Vm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OpCode::NEW_EXPRESSION => {
|
OpCode::NEW_EXPRESSION => {
|
||||||
let constructor_reg = a as usize;
|
let constructor_reg = a;
|
||||||
let first_arg_reg = b as u8;
|
let first_arg_reg = b as u8;
|
||||||
|
|
||||||
let constructor = self.regs[constructor_reg];
|
let constructor = self.regs[constructor_reg];
|
||||||
@@ -2412,6 +2463,55 @@ impl Vm {
|
|||||||
self.for_in_iters.pop();
|
self.for_in_iters.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpCode::THROW => {
|
||||||
|
let exc_value = self.regs[rd];
|
||||||
|
self.exception_value = Some(exc_value);
|
||||||
|
match self.unwind() {
|
||||||
|
Ok(()) => continue,
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::TRY_BEGIN => {
|
||||||
|
let offset = opcode::offset16(instr) as isize;
|
||||||
|
let catch_pc = if offset == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(((self.pc as isize) + offset - 1) as usize)
|
||||||
|
};
|
||||||
|
self.try_stack.push(TryHandler {
|
||||||
|
catch_pc,
|
||||||
|
finally_pc: None,
|
||||||
|
frame_depth: self.frames.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::TRY_END => {
|
||||||
|
self.try_stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::TRY_FINALLY_BEGIN => {
|
||||||
|
let offset = opcode::offset16(instr) as isize;
|
||||||
|
let finally_pc = ((self.pc as isize) + offset - 1) as usize;
|
||||||
|
self.try_stack.push(TryHandler {
|
||||||
|
catch_pc: None,
|
||||||
|
finally_pc: Some(finally_pc),
|
||||||
|
frame_depth: self.frames.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
OpCode::TRY_FINALLY_END => {
|
||||||
|
self.try_stack.pop();
|
||||||
|
if self.pending_exception.is_some() && self.exception_value.is_none() {
|
||||||
|
self.exception_value = self.pending_exception.take();
|
||||||
|
match self.unwind() {
|
||||||
|
Ok(()) => continue,
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.pending_exception = None;
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
if !op.is_implemented() {
|
if !op.is_implemented() {
|
||||||
return Ok(JsValue::undefined());
|
return Ok(JsValue::undefined());
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
use oxide_compiler::compiler::Compiler;
|
||||||
|
use oxide_parser::Allocator;
|
||||||
|
use oxide_vm::vm::Vm;
|
||||||
|
|
||||||
|
fn eval(source: &str) -> String {
|
||||||
|
let allocator = Allocator::default();
|
||||||
|
let program = match oxide_parser::parse(&allocator, source) {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(e) => return format!("parse error: {}", e[0].message),
|
||||||
|
};
|
||||||
|
let module = match Compiler::new().compile(&program) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(e) => return format!("compile error: {e}"),
|
||||||
|
};
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
match vm.run(&module) {
|
||||||
|
Ok(result) => format!("{result}"),
|
||||||
|
Err(e) => format!("vm error: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_throw_caught_by_catch() {
|
||||||
|
assert_eq!(eval("try { throw 42; } catch(e) { e; }"), "42");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_throw_without_catch() {
|
||||||
|
let result = eval("throw 'err';");
|
||||||
|
assert!(
|
||||||
|
result.contains("uncaught"),
|
||||||
|
"expected uncaught, got: {result}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_try_finally_normal_path() {
|
||||||
|
assert_eq!(eval("var x = 1; try { x = 2; } finally { x = 3; } x;"), "3");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_try_finally_exception_path() {
|
||||||
|
let result = eval("var x = 0; try { throw 1; } finally { x = 2; }");
|
||||||
|
assert!(
|
||||||
|
result.contains("uncaught"),
|
||||||
|
"expected uncaught, got: {result}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nested_try_inner_catches() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("try { try { throw 1; } catch(e) { e; } } catch(e) { 2; }"),
|
||||||
|
"1"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nested_try_outer_catches() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("try { try { throw 1; } catch(e) { throw 2; } } catch(e) { e; }"),
|
||||||
|
"2"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_try_catch_finally() {
|
||||||
|
assert_eq!(
|
||||||
|
eval("var x = 0; try { x = 1; } catch(e) { x = 2; } finally { x = 3; } x;"),
|
||||||
|
"3"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_catch_param_binding() {
|
||||||
|
assert_eq!(eval("try { throw 99; } catch(myErr) { myErr; }"), "99");
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user