Skip to content

Commit

Permalink
Divide byte compiler (#2425)
Browse files Browse the repository at this point in the history
This Pull Request is currently unfinished but will fix/close #1808 after some review and more work

It changes the following:

- Divides byte compiler logic into separate files

I would like some review on the current code I have to know if the patterns I'm using are acceptable for the codebase, if everything looks good I will try to separate more code into different small modules to finish the work here.
  • Loading branch information
e-codes-stuff committed Dec 19, 2022
1 parent 9912c37 commit ce51449
Show file tree
Hide file tree
Showing 13 changed files with 2,417 additions and 2,200 deletions.
537 changes: 537 additions & 0 deletions boa_engine/src/bytecompiler/class.rs

Large diffs are not rendered by default.

258 changes: 258 additions & 0 deletions boa_engine/src/bytecompiler/declaration/declaration_pattern.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
use boa_ast::{
pattern::{ArrayPatternElement, ObjectPatternElement, Pattern},
property::PropertyName,
};

use crate::{
bytecompiler::{Access, ByteCompiler, Literal},
vm::{BindingOpcode, Opcode},
JsResult,
};

impl ByteCompiler<'_> {
pub(crate) fn compile_declaration_pattern_impl(
&mut self,
pattern: &Pattern,
def: BindingOpcode,
) -> JsResult<()> {
match pattern {
Pattern::Object(pattern) => {
self.emit_opcode(Opcode::ValueNotNullOrUndefined);

self.emit_opcode(Opcode::RequireObjectCoercible);

let mut additional_excluded_keys_count = 0;
let rest_exits = pattern.has_rest();

for binding in pattern.bindings() {
use ObjectPatternElement::{
AssignmentPropertyAccess, AssignmentRestPropertyAccess, Pattern,
RestProperty, SingleName,
};

match binding {
// SingleNameBinding : BindingIdentifier Initializer[opt]
SingleName {
ident,
name,
default_init,
} => {
self.emit_opcode(Opcode::Dup);
match name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::GetPropertyByName, &[index]);
}
PropertyName::Computed(node) => {
self.compile_expr(node, true)?;
if rest_exits {
self.emit_opcode(Opcode::GetPropertyByValuePush);
} else {
self.emit_opcode(Opcode::GetPropertyByValue);
}
}
}

if let Some(init) = default_init {
let skip =
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true)?;
self.patch_jump(skip);
}
self.emit_binding(def, *ident);

if rest_exits && name.computed().is_some() {
self.emit_opcode(Opcode::Swap);
additional_excluded_keys_count += 1;
}
}
// BindingRestProperty : ... BindingIdentifier
RestProperty {
ident,
excluded_keys,
} => {
self.emit_opcode(Opcode::PushEmptyObject);

for key in excluded_keys {
self.emit_push_literal(Literal::String(
self.interner().resolve_expect(key.sym()).into_common(false),
));
}

self.emit(
Opcode::CopyDataProperties,
&[excluded_keys.len() as u32, additional_excluded_keys_count],
);
self.emit_binding(def, *ident);
}
AssignmentRestPropertyAccess {
access,
excluded_keys,
} => {
self.emit_opcode(Opcode::Dup);
self.emit_opcode(Opcode::PushEmptyObject);
for key in excluded_keys {
self.emit_push_literal(Literal::String(
self.interner().resolve_expect(key.sym()).into_common(false),
));
}
self.emit(Opcode::CopyDataProperties, &[excluded_keys.len() as u32, 0]);
self.access_set(
Access::Property { access },
false,
ByteCompiler::access_set_top_of_stack_expr_fn,
)?;
}
AssignmentPropertyAccess {
name,
access,
default_init,
} => {
self.emit_opcode(Opcode::Dup);
match name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::GetPropertyByName, &[index]);
}
PropertyName::Computed(node) => {
self.compile_expr(node, true)?;
if rest_exits {
self.emit_opcode(Opcode::GetPropertyByValuePush);
} else {
self.emit_opcode(Opcode::GetPropertyByValue);
}
}
}

if let Some(init) = default_init {
let skip =
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true)?;
self.patch_jump(skip);
}

self.access_set(
Access::Property { access },
false,
ByteCompiler::access_set_top_of_stack_expr_fn,
)?;

if rest_exits && name.computed().is_some() {
self.emit_opcode(Opcode::Swap);
additional_excluded_keys_count += 1;
}
}
Pattern {
name,
pattern,
default_init,
} => {
self.emit_opcode(Opcode::Dup);
match name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::GetPropertyByName, &[index]);
}
PropertyName::Computed(node) => {
self.compile_expr(node, true)?;
self.emit_opcode(Opcode::GetPropertyByValue);
}
}

if let Some(init) = default_init {
let skip =
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true)?;
self.patch_jump(skip);
}

self.compile_declaration_pattern(pattern, def)?;
}
}
}

if !rest_exits {
self.emit_opcode(Opcode::Pop);
}
}
Pattern::Array(pattern) => {
self.emit_opcode(Opcode::ValueNotNullOrUndefined);
self.emit_opcode(Opcode::InitIterator);

for binding in pattern.bindings().iter() {
use ArrayPatternElement::{
Elision, Pattern, PatternRest, PropertyAccess, PropertyAccessRest,
SingleName, SingleNameRest,
};

match binding {
// ArrayBindingPattern : [ Elision ]
Elision => {
self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(Opcode::Pop);
}
// SingleNameBinding : BindingIdentifier Initializer[opt]
SingleName {
ident,
default_init,
} => {
self.emit_opcode(Opcode::IteratorNext);
if let Some(init) = default_init {
let skip =
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true)?;
self.patch_jump(skip);
}
self.emit_binding(def, *ident);
}
PropertyAccess { access } => {
self.emit_opcode(Opcode::IteratorNext);
self.access_set(
Access::Property { access },
false,
ByteCompiler::access_set_top_of_stack_expr_fn,
)?;
}
// BindingElement : BindingPattern Initializer[opt]
Pattern {
pattern,
default_init,
} => {
self.emit_opcode(Opcode::IteratorNext);

if let Some(init) = default_init {
let skip =
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true)?;
self.patch_jump(skip);
}

self.compile_declaration_pattern(pattern, def)?;
}
// BindingRestElement : ... BindingIdentifier
SingleNameRest { ident } => {
self.emit_opcode(Opcode::IteratorToArray);
self.emit_binding(def, *ident);
}
PropertyAccessRest { access } => {
self.emit_opcode(Opcode::IteratorToArray);
self.access_set(
Access::Property { access },
false,
ByteCompiler::access_set_top_of_stack_expr_fn,
)?;
}
// BindingRestElement : ... BindingPattern
PatternRest { pattern } => {
self.emit_opcode(Opcode::IteratorToArray);
self.compile_declaration_pattern(pattern, def)?;
}
}
}

self.emit_opcode(Opcode::IteratorClose);
}
}
Ok(())
}
}
1 change: 1 addition & 0 deletions boa_engine/src/bytecompiler/declaration/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod declaration_pattern;
90 changes: 90 additions & 0 deletions boa_engine/src/bytecompiler/expression/assign.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use boa_ast::expression::operator::{assign::AssignOp, Assign};

use crate::{
bytecompiler::{Access, ByteCompiler},
vm::{BindingOpcode, Opcode},
JsResult,
};

impl ByteCompiler<'_> {
pub(crate) fn compile_assign(&mut self, assign: &Assign, use_expr: bool) -> JsResult<()> {
if assign.op() == AssignOp::Assign {
match Access::from_assign_target(assign.lhs()) {
Ok(access) => self.access_set(access, use_expr, |compiler, _| {
compiler.compile_expr(assign.rhs(), true)?;
Ok(())
})?,
Err(pattern) => {
self.compile_expr(assign.rhs(), true)?;
if use_expr {
self.emit_opcode(Opcode::Dup);
}
self.compile_declaration_pattern(pattern, BindingOpcode::SetName)?;
}
}
} else {
let access = Access::from_assign_target(assign.lhs())
.expect("patterns should throw early errors on complex assignment operators");

let shortcircuit_operator_compilation =
|compiler: &mut ByteCompiler<'_>, opcode: Opcode| -> JsResult<()> {
let (early_exit, pop_count) =
compiler.access_set(access, use_expr, |compiler, level| {
compiler.access_get(access, true)?;
let early_exit = compiler.emit_opcode_with_operand(opcode);
compiler.compile_expr(assign.rhs(), true)?;
Ok((early_exit, level))
})?;
if pop_count == 0 {
compiler.patch_jump(early_exit);
} else {
let exit = compiler.emit_opcode_with_operand(Opcode::Jump);
compiler.patch_jump(early_exit);
for _ in 0..pop_count {
compiler.emit_opcode(Opcode::Swap);
compiler.emit_opcode(Opcode::Pop);
}
compiler.patch_jump(exit);
}
Ok(())
};

let opcode = match assign.op() {
AssignOp::Assign => unreachable!(),
AssignOp::Add => Opcode::Add,
AssignOp::Sub => Opcode::Sub,
AssignOp::Mul => Opcode::Mul,
AssignOp::Div => Opcode::Div,
AssignOp::Mod => Opcode::Mod,
AssignOp::Exp => Opcode::Pow,
AssignOp::And => Opcode::BitAnd,
AssignOp::Or => Opcode::BitOr,
AssignOp::Xor => Opcode::BitXor,
AssignOp::Shl => Opcode::ShiftLeft,
AssignOp::Shr => Opcode::ShiftRight,
AssignOp::Ushr => Opcode::UnsignedShiftRight,
AssignOp::BoolAnd => {
shortcircuit_operator_compilation(self, Opcode::LogicalAnd)?;
return Ok(());
}
AssignOp::BoolOr => {
shortcircuit_operator_compilation(self, Opcode::LogicalOr)?;
return Ok(());
}
AssignOp::Coalesce => {
shortcircuit_operator_compilation(self, Opcode::Coalesce)?;
return Ok(());
}
};

self.access_set(access, use_expr, |compiler, _| {
compiler.access_get(access, true)?;
compiler.compile_expr(assign.rhs(), true)?;
compiler.emit(opcode, &[]);
Ok(())
})?;
}

Ok(())
}
}
Loading

0 comments on commit ce51449

Please sign in to comment.