refactor(compiler): remove statement jump count helpers
This commit is contained in:
@@ -331,6 +331,17 @@ impl CompileCtx {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn emit_try_finally_begin_labeled(&mut self, label: Label) {
|
||||||
|
let pc = self.bytecode.len();
|
||||||
|
self.emit(opcode::encode_try_finally_begin(0));
|
||||||
|
self.fixups.push(JumpFixup {
|
||||||
|
pc,
|
||||||
|
label,
|
||||||
|
opcode: OpCode::TRY_FINALLY_BEGIN,
|
||||||
|
rd: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn count_word(&mut self) {
|
pub(crate) fn count_word(&mut self) {
|
||||||
self.projected_pc += 1;
|
self.projected_pc += 1;
|
||||||
}
|
}
|
||||||
@@ -481,6 +492,7 @@ impl CompileCtx {
|
|||||||
OpCode::JMP_IF_TRUE => opcode::encode_jmp_if_true(fixup.rd, offset),
|
OpCode::JMP_IF_TRUE => opcode::encode_jmp_if_true(fixup.rd, offset),
|
||||||
OpCode::JMP_IF_NULLISH => opcode::encode_jmp_if_nullish(fixup.rd, offset),
|
OpCode::JMP_IF_NULLISH => opcode::encode_jmp_if_nullish(fixup.rd, offset),
|
||||||
OpCode::TRY_BEGIN => opcode::encode_try_begin(offset),
|
OpCode::TRY_BEGIN => opcode::encode_try_begin(offset),
|
||||||
|
OpCode::TRY_FINALLY_BEGIN => opcode::encode_try_finally_begin(offset),
|
||||||
_ => return Err(format!("Unsupported fixup opcode {:?}", fixup.opcode)),
|
_ => return Err(format!("Unsupported fixup opcode {:?}", fixup.opcode)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1275,17 +1287,11 @@ impl Compiler {
|
|||||||
|
|
||||||
pub(crate) fn count_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) {
|
pub(crate) fn count_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) {
|
||||||
match stmt {
|
match stmt {
|
||||||
Statement::BreakStatement(_) | Statement::ContinueStatement(_) | Statement::LabeledStatement(_) => {
|
|
||||||
self.count_basic(stmt, ctx)
|
|
||||||
}
|
|
||||||
Statement::IfStatement(_) => self.count_control_domain(stmt, ctx),
|
|
||||||
Statement::WhileStatement(_)
|
Statement::WhileStatement(_)
|
||||||
| Statement::DoWhileStatement(_)
|
| Statement::DoWhileStatement(_)
|
||||||
| Statement::ForStatement(_)
|
| Statement::ForStatement(_)
|
||||||
| Statement::ForInStatement(_)
|
| Statement::ForInStatement(_)
|
||||||
| Statement::ForOfStatement(_) => self.count_iteration_domain(stmt, ctx),
|
| Statement::ForOfStatement(_) => self.count_iteration_domain(stmt, ctx),
|
||||||
Statement::SwitchStatement(_) => self.count_switch_domain(stmt, ctx),
|
|
||||||
Statement::TryStatement(_) => self.count_exception_domain(stmt, ctx),
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
use crate::compiler::{CompileCtx, Compiler};
|
use crate::compiler::{CompileCtx, Compiler};
|
||||||
|
|
||||||
impl Compiler {
|
impl Compiler {
|
||||||
pub(super) fn count_break_statement(&self, ctx: &mut CompileCtx) {
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn count_continue_statement(&self, ctx: &mut CompileCtx) {
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
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<u8>, String> {
|
) -> Result<Option<u8>, String> {
|
||||||
|
|||||||
@@ -2,24 +2,6 @@ use crate::compiler::{CompileCtx, Compiler, Label};
|
|||||||
use oxide_parser::Statement;
|
use oxide_parser::Statement;
|
||||||
|
|
||||||
impl Compiler {
|
impl Compiler {
|
||||||
pub(super) fn count_labeled_statement(&self, stmt: &oxide_parser::LabeledStatement<'_>, ctx: &mut CompileCtx) {
|
|
||||||
let body_is_loop = matches!(
|
|
||||||
stmt.body,
|
|
||||||
Statement::WhileStatement(_)
|
|
||||||
| Statement::DoWhileStatement(_)
|
|
||||||
| Statement::ForStatement(_)
|
|
||||||
| Statement::ForInStatement(_)
|
|
||||||
| Statement::ForOfStatement(_)
|
|
||||||
);
|
|
||||||
if body_is_loop {
|
|
||||||
self.count_statement(&stmt.body, ctx);
|
|
||||||
} else {
|
|
||||||
let id = ctx.next_label_id();
|
|
||||||
self.count_statement(&stmt.body, ctx);
|
|
||||||
ctx.labels.label_map.insert(Label::LabeledEnd(id), ctx.projected_pc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_iteration_statement(stmt: &Statement) -> bool {
|
fn is_iteration_statement(stmt: &Statement) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
stmt,
|
stmt,
|
||||||
|
|||||||
@@ -15,13 +15,4 @@ impl Compiler {
|
|||||||
_ => Ok(None),
|
_ => Ok(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn count_basic(&self, stmt: &Statement, ctx: &mut CompileCtx) {
|
|
||||||
match stmt {
|
|
||||||
Statement::BreakStatement(_) => self.count_break_statement(ctx),
|
|
||||||
Statement::ContinueStatement(_) => self.count_continue_statement(ctx),
|
|
||||||
Statement::LabeledStatement(ls) => self.count_labeled_statement(ls, ctx),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,27 +4,6 @@ use oxide_bytecode::opcode::{self, OpCode};
|
|||||||
use oxide_parser::Statement;
|
use oxide_parser::Statement;
|
||||||
|
|
||||||
impl Compiler {
|
impl Compiler {
|
||||||
fn count_if_statement(&self, stmt: &oxide_parser::IfStatement<'_>, ctx: &mut CompileCtx) {
|
|
||||||
let id = ctx.next_label_id();
|
|
||||||
let else_label = Label::IfElse(id);
|
|
||||||
let end_label = Label::IfEnd(id);
|
|
||||||
|
|
||||||
self.count_expression(&stmt.test, ctx);
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
self.count_statement(&stmt.consequent, ctx);
|
|
||||||
ctx.alloc_reg();
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
if stmt.alternate.is_some() {
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
}
|
|
||||||
ctx.labels.label_map.insert(else_label, ctx.projected_pc);
|
|
||||||
if let Some(alt_stmt) = &stmt.alternate {
|
|
||||||
self.count_statement(alt_stmt, ctx);
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
}
|
|
||||||
ctx.labels.label_map.insert(end_label, ctx.projected_pc);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_if_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
fn emit_if_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||||
let Statement::IfStatement(ifs) = stmt else {
|
let Statement::IfStatement(ifs) = stmt else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@@ -66,12 +45,6 @@ impl Compiler {
|
|||||||
Ok(Some(result_reg))
|
Ok(Some(result_reg))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn count_control_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) {
|
|
||||||
if let Statement::IfStatement(ifs) = stmt {
|
|
||||||
self.count_if_statement(ifs, ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn emit_control_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
pub(crate) fn emit_control_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||||
match stmt {
|
match stmt {
|
||||||
Statement::IfStatement(_) => self.emit_if_statement(stmt, ctx),
|
Statement::IfStatement(_) => self.emit_if_statement(stmt, ctx),
|
||||||
|
|||||||
@@ -5,13 +5,6 @@ mod throw;
|
|||||||
mod try_catch;
|
mod try_catch;
|
||||||
|
|
||||||
impl Compiler {
|
impl Compiler {
|
||||||
pub(crate) fn count_exception_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) {
|
|
||||||
match stmt {
|
|
||||||
Statement::TryStatement(ts) => self.count_try_statement(ts, ctx),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn emit_exception_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
pub(crate) fn emit_exception_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||||
match stmt {
|
match stmt {
|
||||||
Statement::ThrowStatement(_) => self.emit_throw_statement(stmt, ctx),
|
Statement::ThrowStatement(_) => self.emit_throw_statement(stmt, ctx),
|
||||||
|
|||||||
@@ -3,71 +3,19 @@ use oxide_bytecode::opcode::{self, OpCode};
|
|||||||
use oxide_parser::{Statement, VariableDeclarationKind};
|
use oxide_parser::{Statement, VariableDeclarationKind};
|
||||||
|
|
||||||
impl Compiler {
|
impl Compiler {
|
||||||
pub(crate) fn count_try_statement(&self, stmt: &oxide_parser::TryStatement<'_>, ctx: &mut CompileCtx) {
|
|
||||||
let id = ctx.next_label_id();
|
|
||||||
let catch_label = Label::CatchBody(id);
|
|
||||||
let try_end_label = Label::TryEnd(id);
|
|
||||||
let has_catch = stmt.handler.is_some();
|
|
||||||
let has_finally = stmt.finalizer.is_some();
|
|
||||||
ctx.alloc_reg();
|
|
||||||
if has_finally {
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
}
|
|
||||||
if has_catch {
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
}
|
|
||||||
for s in &stmt.block.body {
|
|
||||||
self.count_statement(s, ctx);
|
|
||||||
}
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
if has_catch {
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
}
|
|
||||||
let jmp_needed = has_catch || has_finally;
|
|
||||||
if jmp_needed {
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
}
|
|
||||||
ctx.labels.label_map.insert(catch_label, ctx.projected_pc);
|
|
||||||
if let Some(catch) = &stmt.handler {
|
|
||||||
ctx.push_scope();
|
|
||||||
if let Some(param) = &catch.param {
|
|
||||||
ctx.alloc_reg();
|
|
||||||
if let oxide_parser::BindingPattern::BindingIdentifier(_) = ¶m.pattern {
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for cs in &catch.body.body {
|
|
||||||
self.count_statement(cs, ctx);
|
|
||||||
}
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
ctx.pop_scope();
|
|
||||||
}
|
|
||||||
if let Some(finally) = &stmt.finalizer {
|
|
||||||
let finally_label = Label::FinallyBody(id);
|
|
||||||
ctx.labels.label_map.insert(finally_label, ctx.projected_pc);
|
|
||||||
for fs in &finally.body {
|
|
||||||
self.count_statement(fs, ctx);
|
|
||||||
}
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
}
|
|
||||||
ctx.labels.label_map.insert(try_end_label, ctx.projected_pc);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn emit_try_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
pub(crate) fn emit_try_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||||
let Statement::TryStatement(ts) = stmt else {
|
let Statement::TryStatement(ts) = stmt else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
let id = ctx.next_label_id();
|
let id = ctx.next_label_id();
|
||||||
let catch_label = Label::CatchBody(id);
|
let catch_label = Label::CatchBody(id);
|
||||||
|
let finally_label = Label::FinallyBody(id);
|
||||||
let try_end_label = Label::TryEnd(id);
|
let try_end_label = Label::TryEnd(id);
|
||||||
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 result_reg = ctx.alloc_reg();
|
||||||
let mut try_finally_begin_pos: Option<usize> = None;
|
|
||||||
if has_finally {
|
if has_finally {
|
||||||
try_finally_begin_pos = Some(ctx.bytecode.len());
|
ctx.emit_try_finally_begin_labeled(finally_label);
|
||||||
ctx.emit(opcode::encode_try_finally_begin(0));
|
|
||||||
}
|
}
|
||||||
if has_catch {
|
if has_catch {
|
||||||
ctx.emit_try_begin_labeled(catch_label);
|
ctx.emit_try_begin_labeled(catch_label);
|
||||||
@@ -84,7 +32,7 @@ impl Compiler {
|
|||||||
}
|
}
|
||||||
let jmp_needed = has_catch || has_finally;
|
let jmp_needed = has_catch || has_finally;
|
||||||
if jmp_needed {
|
if jmp_needed {
|
||||||
let target = if has_finally { Label::FinallyBody(id) } else { try_end_label };
|
let target = if has_finally { finally_label } else { try_end_label };
|
||||||
ctx.emit_jmp_labeled(target);
|
ctx.emit_jmp_labeled(target);
|
||||||
}
|
}
|
||||||
let catch_label_pc = ctx.bytecode.len();
|
let catch_label_pc = ctx.bytecode.len();
|
||||||
@@ -108,14 +56,7 @@ impl Compiler {
|
|||||||
ctx.pop_scope();
|
ctx.pop_scope();
|
||||||
}
|
}
|
||||||
if has_finally {
|
if has_finally {
|
||||||
let finally_label = Label::FinallyBody(id);
|
ctx.labels.label_map.insert(finally_label, ctx.bytecode.len());
|
||||||
let finally_label_pc = ctx.bytecode.len();
|
|
||||||
ctx.labels.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);
|
|
||||||
let offset = ctx.checked_jump_offset(offset);
|
|
||||||
ctx.bytecode[fb_pos] = opcode::encode_try_finally_begin(offset);
|
|
||||||
}
|
|
||||||
let mut last_finally_result: Option<u8> = None;
|
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 {
|
||||||
if let Some(r) = self.emit_statement(s, ctx)? {
|
if let Some(r) = self.emit_statement(s, ctx)? {
|
||||||
|
|||||||
@@ -3,37 +3,6 @@ use oxide_bytecode::opcode::{self, OpCode};
|
|||||||
use oxide_parser::Statement;
|
use oxide_parser::Statement;
|
||||||
|
|
||||||
impl Compiler {
|
impl Compiler {
|
||||||
fn count_switch_statement(&self, stmt: &oxide_parser::SwitchStatement<'_>, ctx: &mut CompileCtx) {
|
|
||||||
let id = ctx.next_label_id();
|
|
||||||
let end_label = Label::SwitchEnd(id);
|
|
||||||
ctx.push_switch(end_label);
|
|
||||||
self.count_expression(&stmt.discriminant, ctx);
|
|
||||||
let compare_reg_checkpoint = ctx.reg_checkpoint();
|
|
||||||
let cases = &stmt.cases;
|
|
||||||
for case in cases.iter() {
|
|
||||||
if let Some(test) = &case.test {
|
|
||||||
self.count_expression(test, ctx);
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
ctx.alloc_reg();
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
ctx.restore_reg_checkpoint(compare_reg_checkpoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let has_default = cases.iter().any(|c| c.test.is_none());
|
|
||||||
if !has_default {
|
|
||||||
ctx.projected_pc += 1;
|
|
||||||
}
|
|
||||||
for (case_idx, case) in cases.iter().enumerate() {
|
|
||||||
let case_label = Label::SwitchCase(id, case_idx as u32);
|
|
||||||
ctx.labels.label_map.insert(case_label, ctx.projected_pc);
|
|
||||||
for s in &case.consequent {
|
|
||||||
self.count_statement(s, ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.labels.label_map.insert(end_label, ctx.projected_pc);
|
|
||||||
ctx.pop_switch();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn emit_switch_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
fn emit_switch_statement(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||||
let Statement::SwitchStatement(sw) = stmt else {
|
let Statement::SwitchStatement(sw) = stmt else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
@@ -70,12 +39,6 @@ impl Compiler {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn count_switch_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) {
|
|
||||||
if let Statement::SwitchStatement(sw) = stmt {
|
|
||||||
self.count_switch_statement(sw, ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn emit_switch_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
pub(crate) fn emit_switch_domain(&self, stmt: &Statement, ctx: &mut CompileCtx) -> Result<Option<u8>, String> {
|
||||||
match stmt {
|
match stmt {
|
||||||
Statement::SwitchStatement(_) => self.emit_switch_statement(stmt, ctx),
|
Statement::SwitchStatement(_) => self.emit_switch_statement(stmt, ctx),
|
||||||
|
|||||||
@@ -218,6 +218,18 @@ fn compile_switch_break() {
|
|||||||
assert!(!module.bytecode.is_empty());
|
assert!(!module.bytecode.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compile_try_finally_resolves_finally_offset() {
|
||||||
|
let module = compile_source("try { 1; } finally { 2; }");
|
||||||
|
let try_finally_begin = module
|
||||||
|
.bytecode
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.find(|&instr| opcode::opcode(instr) == OpCode::TRY_FINALLY_BEGIN)
|
||||||
|
.expect("try/finally should emit TRY_FINALLY_BEGIN");
|
||||||
|
assert_ne!(opcode::offset16(try_finally_begin), 0, "finally target offset should be resolved");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn compile_continue_in_switch_errors() {
|
fn compile_continue_in_switch_errors() {
|
||||||
let result = std::panic::catch_unwind(|| {
|
let result = std::panic::catch_unwind(|| {
|
||||||
|
|||||||
Reference in New Issue
Block a user