Skip to content

Commit

Permalink
Merge pull request #88 from y21/try-finally
Browse files Browse the repository at this point in the history
Implement try-finally blocks
  • Loading branch information
y21 authored May 11, 2024
2 parents c175698 + e09086b commit 2bb12d5
Show file tree
Hide file tree
Showing 23 changed files with 420 additions and 86 deletions.
5 changes: 4 additions & 1 deletion crates/dash_compiler/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use dash_middle::compiler::instruction::Instruction;
use crate::jump_container::JumpContainer;
use crate::{jump_container, FunctionCompiler};

#[derive(PartialOrd, Ord, Hash, Eq, PartialEq, Debug, Clone)]
#[derive(PartialOrd, Ord, Hash, Eq, PartialEq, Debug, Clone, Copy)]
pub enum Label {
IfEnd,
/// A branch of an if statement
Expand All @@ -33,6 +33,9 @@ pub enum Label {
switch_id: usize,
},
Catch,
Finally {
finally_id: usize,
},
TryEnd,
InitParamWithDefaultValue,
FinishParamDefaultValueInit,
Expand Down
22 changes: 19 additions & 3 deletions crates/dash_compiler/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,25 @@ impl<'cx, 'interner> InstructionBuilder<'cx, 'interner> {
Ok(())
}

pub fn build_try_block(&mut self) {
fn write_bool(&mut self, b: bool) {
self.write(b.into());
}

pub fn build_try_block(&mut self, has_catch: bool, finally_id: Option<usize>) {
self.write_instr(Instruction::Try);
self.write_all(&[0, 0]);
self.write_bool(has_catch);
if has_catch {
self.write_all(&[0, 0]);
}
// NOTE: even though we won't *really* perform a jump (we skip over the following `jump` instruction emitted by this call in the vm dispatcher)
// we use the local jump resolving mechanism for updating the catch offset
self.add_local_jump(Label::Catch);
self.write_bool(finally_id.is_some());
if let Some(finally_id) = finally_id {
self.write_all(&[0, 0]);
self.current_function_mut()
.add_global_jump(Label::Finally { finally_id });
}
}

pub fn build_local_load(&mut self, index: u16, is_extern: bool) {
Expand All @@ -99,7 +114,8 @@ impl<'cx, 'interner> InstructionBuilder<'cx, 'interner> {

pub fn build_global_load(&mut self, ident: Symbol) -> Result<(), LimitExceededError> {
let id = self.current_function_mut().cp.add(Constant::Identifier(ident))?;
self.write_wide_instr(Instruction::LdGlobal, Instruction::LdGlobalW, id);
self.write_instr(Instruction::LdGlobal);
self.writew(id);
Ok(())
}

Expand Down
121 changes: 94 additions & 27 deletions crates/dash_compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use dash_middle::parser::statement::{
VariableDeclaration, VariableDeclarationKind, VariableDeclarationName, VariableDeclarations, WhileLoop,
};
use dash_middle::sourcemap::Span;
use dash_middle::util::Counter;
use dash_middle::visitor::Visitor;
use dash_optimizer::consteval::ConstFunctionEvalCtx;
use dash_optimizer::type_infer::TypeInferCtx;
Expand Down Expand Up @@ -64,8 +65,11 @@ struct FunctionLocalState {
///
/// Bytecode can refer to constants using the [Instruction::Constant] instruction, followed by a u8 index.
cp: ConstantPool,
/// Current try catch depth
try_catch_depth: u16,
/// Current `try` depth (note that this does NOT include `catch`es)
try_depth: u16,
/// A stack of try-catch-finally blocks and their optional `finally` label that can be jumped to
finally_labels: Vec<Option<Label>>,
finally_counter: Counter<usize>,
/// The type of function that this FunctionCompiler compiles
ty: FunctionKind,
/// Container, used for storing global labels that can be jumped to
Expand All @@ -89,7 +93,9 @@ impl FunctionLocalState {
Self {
buf: Vec::new(),
cp: ConstantPool::new(),
try_catch_depth: 0,
try_depth: 0,
finally_labels: Vec::new(),
finally_counter: Counter::new(),
ty,
jc: JumpContainer::new(),
breakables: Vec::new(),
Expand Down Expand Up @@ -120,6 +126,7 @@ impl FunctionLocalState {
self.switch_counter += 1;
switch_id
}

fn exit_loop(&mut self) {
let item = self.breakables.pop();
match item {
Expand Down Expand Up @@ -151,6 +158,17 @@ impl FunctionLocalState {
FunctionKind::Generator | FunctionKind::Arrow => false,
}
}

pub fn is_generator_or_async(&self) -> bool {
matches!(
self.ty,
FunctionKind::Function(Asyncness::Yes) | FunctionKind::Generator
)
}

fn enclosing_finally(&self) -> Option<Label> {
self.finally_labels.iter().copied().rev().find_map(|lbl| lbl)
}
}

#[derive(Debug)]
Expand Down Expand Up @@ -1459,9 +1477,18 @@ impl<'interner> Visitor<Result<(), Error>> for FunctionCompiler<'interner> {
}

fn visit_return_statement(&mut self, _span: Span, ReturnStatement(stmt): ReturnStatement) -> Result<(), Error> {
let tc_depth = self.current_function().try_catch_depth;
self.accept_expr(stmt)?;
InstructionBuilder::new(self).build_ret(tc_depth);
let mut ib = InstructionBuilder::new(self);
let finally = ib.current_function().enclosing_finally();

let tc_depth = ib.current_function().try_depth;
ib.accept_expr(stmt)?;
if let Some(finally) = finally {
ib.write_instr(Instruction::DelayedReturn);
ib.build_jmp(finally, false);
} else {
ib.build_ret(tc_depth);
}

Ok(())
}

Expand Down Expand Up @@ -1807,45 +1834,75 @@ impl<'interner> Visitor<Result<(), Error>> for FunctionCompiler<'interner> {
Ok(())
}

fn visit_try_catch(&mut self, span: Span, TryCatch { try_, catch, .. }: TryCatch) -> Result<(), Error> {
fn visit_try_catch(&mut self, span: Span, TryCatch { try_, catch, finally }: TryCatch) -> Result<(), Error> {
let mut ib = InstructionBuilder::new(self);

ib.build_try_block();
let finally = finally.map(|f| (ib.current_function_mut().finally_counter.inc(), f));

ib.current_function_mut().try_catch_depth += 1;
ib.build_try_block(catch.is_some(), finally.as_ref().map(|&(id, _)| id));

ib.current_function_mut().try_depth += 1;
ib.current_function_mut()
.finally_labels
.push(finally.as_ref().map(|&(finally_id, _)| Label::Finally { finally_id }));
ib.current_scope_mut().enter();
let res = ib.accept(*try_); // TODO: some API for making this nicer
ib.current_scope_mut().exit();
ib.current_function_mut().try_catch_depth -= 1;
ib.current_function_mut().try_depth -= 1;
res?;

ib.build_jmp(Label::TryEnd, true);

ib.add_local_label(Label::Catch);

ib.current_scope_mut().enter();

if let Some(ident) = catch.ident {
let id = ib
.current_scope_mut()
.add_local(ident, VariableDeclarationKind::Var, None)
.map_err(|_| Error::LocalLimitExceeded(span))?;
if catch.is_none() && finally.is_none() {
// FIXME: make it a real error
unimplementedc!(span, "try block has no catch or finally");
}

if let Some(catch) = catch {
ib.add_local_label(Label::Catch);

if let Some(ident) = catch.ident {
let id = ib
.current_scope_mut()
.add_local(ident, VariableDeclarationKind::Var, None)
.map_err(|_| Error::LocalLimitExceeded(span))?;

if id == u16::MAX {
// Max u16 value is reserved for "no binding"
return Err(Error::LocalLimitExceeded(span));
if id == u16::MAX {
// Max u16 value is reserved for "no binding"
return Err(Error::LocalLimitExceeded(span));
}

ib.writew(id);
} else {
ib.writew(u16::MAX);
}

ib.writew(id);
} else {
ib.writew(u16::MAX);
ib.accept(*catch.body)?;
}

ib.accept(*catch.body)?;
ib.current_scope_mut().exit();
ib.current_function_mut().finally_labels.pop();

if let Some((finally_id, finally)) = finally {
if ib.current_function().is_generator_or_async() {
// TODO: is this also already an issue without finally?
unimplementedc!(span, "try-finally in generator function");
}

ib.current_function_mut()
.add_global_label(Label::Finally { finally_id });
ib.add_local_label(Label::TryEnd);
ib.build_try_end();

ib.add_local_label(Label::TryEnd);
ib.build_try_end();
ib.accept(*finally)?;

ib.write_instr(Instruction::FinallyEnd);
ib.writew(ib.current_function().try_depth);
} else {
ib.add_local_label(Label::TryEnd);
ib.build_try_end();
}

Ok(())
}
Expand Down Expand Up @@ -2016,6 +2073,11 @@ impl<'interner> Visitor<Result<(), Error>> for FunctionCompiler<'interner> {

fn visit_break(&mut self, span: Span) -> Result<(), Error> {
let mut ib = InstructionBuilder::new(self);

if ib.current_function().enclosing_finally().is_some() {
unimplementedc!(span, "`break` in a try-finally block");
}

let breakable = *ib
.current_function_mut()
.breakables
Expand All @@ -2035,6 +2097,11 @@ impl<'interner> Visitor<Result<(), Error>> for FunctionCompiler<'interner> {

fn visit_continue(&mut self, span: Span) -> Result<(), Error> {
let mut ib = InstructionBuilder::new(self);

if ib.current_function().enclosing_finally().is_some() {
unimplementedc!(span, "`continue` in a try-finally block");
}

let breakable = *ib
.current_function_mut()
.breakables
Expand Down
4 changes: 3 additions & 1 deletion crates/dash_compiler/src/transformations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ pub fn hoist_declarations(ast: &mut Vec<Statement>) {
}
StatementKind::Try(tc_stmt) => {
hoist_function_declaration(prepend_function_assigns, &mut tc_stmt.try_);
hoist_function_declaration(prepend_function_assigns, &mut tc_stmt.catch.body);
if let Some(catch) = &mut tc_stmt.catch {
hoist_function_declaration(prepend_function_assigns, &mut catch.body);
}
if let Some(finally_stmt) = &mut tc_stmt.finally {
hoist_function_declaration(prepend_function_assigns, finally_stmt);
}
Expand Down
8 changes: 3 additions & 5 deletions crates/dash_decompiler/src/decompiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,13 +190,9 @@ impl<'interner, 'buf> FunctionDecompiler<'interner, 'buf> {
}
Instruction::Arguments => self.handle_opless_instr("arguments"),
Instruction::LdGlobal => {
let b = self.read()?;
let b = self.read_i16()?;
self.handle_op_instr("ldglobal", &[&self.display(&self.constants[b as usize])]);
}
Instruction::LdGlobalW => {
let b = self.read_u16()?;
self.handle_op_instr("ldglobalw", &[&self.display(&self.constants[b as usize])]);
}
Instruction::StoreLocal => self.handle_inc_op_instr2("storelocal")?,
Instruction::StoreLocalW => self.handle_inc_op_instr2("storelocalw")?,
Instruction::Call => {
Expand All @@ -218,6 +214,7 @@ impl<'interner, 'buf> FunctionDecompiler<'interner, 'buf> {
self.read_u16()?; // intentionally ignored
self.handle_opless_instr("ret")
}
Instruction::DelayedReturn => self.handle_opless_instr("delayedret"),
Instruction::Pos => self.handle_opless_instr("pos"),
Instruction::Neg => self.handle_opless_instr("neg"),
Instruction::TypeOfGlobalIdent => {
Expand Down Expand Up @@ -286,6 +283,7 @@ impl<'interner, 'buf> FunctionDecompiler<'interner, 'buf> {
Instruction::StrictNe => self.handle_opless_instr("strictne"),
Instruction::Try => self.handle_incw_op_instr("try")?, // TODO: show @offset like in JMP
Instruction::TryEnd => self.handle_opless_instr("tryend"),
Instruction::FinallyEnd => self.handle_opless_instr("finallyend"),
Instruction::Throw => self.handle_opless_instr("throw"),
Instruction::Yield => self.handle_opless_instr("yield"),
Instruction::BitOr => self.handle_opless_instr("bitor"),
Expand Down
3 changes: 2 additions & 1 deletion crates/dash_middle/src/compiler/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ pub enum Instruction {
LdLocal,
LdLocalW,
LdGlobal,
LdGlobalW,
Constant,
ConstantW,
Pos,
Expand Down Expand Up @@ -59,6 +58,7 @@ pub enum Instruction {
StrictNe,
Try,
TryEnd,
FinallyEnd,
Throw,
Yield,
JmpFalseNP,
Expand Down Expand Up @@ -96,6 +96,7 @@ pub enum Instruction {
ObjDestruct,
ArrayDestruct,
AssignProperties,
DelayedReturn,
// Nop exists solely for the sake of benchmarking the raw throughput of the VM dispatch loop
Nop,
}
Expand Down
1 change: 1 addition & 0 deletions crates/dash_middle/src/interner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ pub mod sym {
call,
create,
keys,
getOwnPropertyNames,
getOwnPropertyDescriptor,
getOwnPropertyDescriptors,
defineProperty,
Expand Down
10 changes: 7 additions & 3 deletions crates/dash_middle/src/parser/statement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,14 +205,18 @@ pub struct TryCatch {
pub try_: Box<Statement>,
/// Catch statement
// TODO: make this optional. a try can exist without catch (try finally)
pub catch: Catch,
pub catch: Option<Catch>,
/// Optional finally block
pub finally: Option<Box<Statement>>,
}

impl fmt::Display for TryCatch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "try {{ {} }} {}", self.try_, self.catch)?;
write!(f, "try {{ {} }} ", self.try_)?;

if let Some(catch) = &self.catch {
write!(f, "{catch}")?;
}

if let Some(finally) = &self.finally {
write!(f, " finally {{ {finally} }}")?;
Expand All @@ -224,7 +228,7 @@ impl fmt::Display for TryCatch {

impl TryCatch {
/// Creates a new try catch block
pub fn new(try_: Statement, catch: Catch, finally: Option<Statement>) -> Self {
pub fn new(try_: Statement, catch: Option<Catch>, finally: Option<Statement>) -> Self {
Self {
try_: Box::new(try_),
catch,
Expand Down
2 changes: 1 addition & 1 deletion crates/dash_middle/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ where
Self(start.into(), PhantomData)
}

pub fn advance(&mut self) -> T {
pub fn inc(&mut self) -> T {
let old = self.0;
self.0 += 1;
T::from(old)
Expand Down
6 changes: 5 additions & 1 deletion crates/dash_node_impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,11 @@ impl Object for RequireFunction {
let is_path = matches!(arg.chars().next(), Some('.' | '/' | '~'));
let result = if is_path {
if !arg.ends_with(".js") && !arg.ends_with(".json") {
arg += ".js";
if std::fs::metadata(self.current_dir.join(&arg)).is_ok_and(|md| md.is_dir()) {
arg += "/index.js";
} else {
arg += ".js";
}
}

let canonicalized_path = match self.current_dir.join(&arg).canonicalize() {
Expand Down
Loading

0 comments on commit 2bb12d5

Please sign in to comment.