Browse Source

Divide byte compiler (#2425)

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.
pull/2489/head
e-codes-stuff 2 years ago
parent
commit
ce51449d67
  1. 537
      boa_engine/src/bytecompiler/class.rs
  2. 258
      boa_engine/src/bytecompiler/declaration/declaration_pattern.rs
  3. 1
      boa_engine/src/bytecompiler/declaration/mod.rs
  4. 90
      boa_engine/src/bytecompiler/expression/assign.rs
  5. 98
      boa_engine/src/bytecompiler/expression/binary.rs
  6. 317
      boa_engine/src/bytecompiler/expression/mod.rs
  7. 170
      boa_engine/src/bytecompiler/expression/object_literal.rs
  8. 113
      boa_engine/src/bytecompiler/expression/unary.rs
  9. 2226
      boa_engine/src/bytecompiler/mod.rs
  10. 96
      boa_engine/src/bytecompiler/statement/continue.rs
  11. 363
      boa_engine/src/bytecompiler/statement/loop.rs
  12. 228
      boa_engine/src/bytecompiler/statement/mod.rs
  13. 120
      boa_engine/src/bytecompiler/statement/try.rs

537
boa_engine/src/bytecompiler/class.rs

@ -0,0 +1,537 @@
use boa_ast::{
declaration::Binding,
expression::Identifier,
function::{Class, ClassElement},
operations::bound_names,
property::{MethodDefinition, PropertyName},
};
use boa_gc::Gc;
use boa_interner::Sym;
use rustc_hash::FxHashMap;
use crate::{
vm::{BindingOpcode, CodeBlock, Opcode},
JsResult,
};
use super::{ByteCompiler, Literal, NodeKind};
impl ByteCompiler<'_> {
/// This function compiles a class declaration or expression.
///
/// The compilation of a class declaration and expression is mostly equal.
/// A class declaration binds the resulting class object to it's identifier.
/// A class expression leaves the resulting class object on the stack for following operations.
pub(crate) fn compile_class(&mut self, class: &Class, expression: bool) -> JsResult<()> {
let code = CodeBlock::new(
class.name().map_or(Sym::EMPTY_STRING, Identifier::sym),
0,
true,
);
let mut compiler = ByteCompiler {
code_block: code,
literals_map: FxHashMap::default(),
names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(),
jump_info: Vec::new(),
in_async_generator: false,
json_parse: self.json_parse,
context: self.context,
};
compiler.context.push_compile_time_environment(true);
if let Some(expr) = class.constructor() {
compiler.code_block.length = expr.parameters().length();
compiler.code_block.params = expr.parameters().clone();
compiler
.context
.create_mutable_binding(Sym::ARGUMENTS.into(), false, false);
compiler.code_block.arguments_binding = Some(
compiler
.context
.initialize_mutable_binding(Sym::ARGUMENTS.into(), false),
);
for parameter in expr.parameters().as_ref() {
if parameter.is_rest_param() {
compiler.emit_opcode(Opcode::RestParameterInit);
}
match parameter.variable().binding() {
Binding::Identifier(ident) => {
compiler
.context
.create_mutable_binding(*ident, false, false);
if let Some(init) = parameter.variable().init() {
let skip =
compiler.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
compiler.compile_expr(init, true)?;
compiler.patch_jump(skip);
}
compiler.emit_binding(BindingOpcode::InitArg, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
compiler.context.create_mutable_binding(ident, false, false);
}
compiler.compile_declaration_pattern(pattern, BindingOpcode::InitArg)?;
}
}
}
if !expr.parameters().has_rest_parameter() {
compiler.emit_opcode(Opcode::RestParameterPop);
}
let env_label = if expr.parameters().has_expressions() {
compiler.code_block.num_bindings = compiler.context.get_binding_number();
compiler.context.push_compile_time_environment(true);
compiler.code_block.function_environment_push_location =
compiler.next_opcode_location();
Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment))
} else {
None
};
compiler.create_decls(expr.body(), false);
compiler.compile_statement_list(expr.body(), false, false)?;
if let Some(env_label) = env_label {
let (num_bindings, compile_environment) =
compiler.context.pop_compile_time_environment();
let index_compile_environment =
compiler.push_compile_environment(compile_environment);
compiler.patch_jump_with_target(env_label.0, num_bindings as u32);
compiler.patch_jump_with_target(env_label.1, index_compile_environment as u32);
let (_, compile_environment) = compiler.context.pop_compile_time_environment();
compiler.push_compile_environment(compile_environment);
} else {
let (num_bindings, compile_environment) =
compiler.context.pop_compile_time_environment();
compiler.push_compile_environment(compile_environment);
compiler.code_block.num_bindings = num_bindings;
compiler.code_block.is_class_constructor = true;
}
} else {
if class.super_ref().is_some() {
compiler.emit_opcode(Opcode::SuperCallDerived);
}
let (num_bindings, compile_environment) =
compiler.context.pop_compile_time_environment();
compiler.push_compile_environment(compile_environment);
compiler.code_block.num_bindings = num_bindings;
compiler.code_block.is_class_constructor = true;
}
compiler.emit_opcode(Opcode::PushUndefined);
compiler.emit_opcode(Opcode::Return);
let code = Gc::new(compiler.finish());
let index = self.code_block.functions.len() as u32;
self.code_block.functions.push(code);
self.emit(Opcode::GetFunction, &[index]);
self.emit_opcode(Opcode::Dup);
if let Some(node) = class.super_ref() {
self.compile_expr(node, true)?;
self.emit_opcode(Opcode::PushClassPrototype);
} else {
self.emit_opcode(Opcode::PushUndefined);
}
self.emit_opcode(Opcode::SetClassPrototype);
self.emit_opcode(Opcode::Swap);
// TODO: set function name for getter and setters
for element in class.elements() {
match element {
ClassElement::StaticMethodDefinition(name, method_definition) => {
self.emit_opcode(Opcode::Dup);
match &method_definition {
MethodDefinition::Get(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassGetterByName, &[index]);
}
PropertyName::Computed(ref name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::DefineClassGetterByValue);
}
},
MethodDefinition::Set(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassSetterByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::DefineClassSetterByValue);
}
},
MethodDefinition::Ordinary(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::DefineClassMethodByValue);
}
},
MethodDefinition::Async(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::DefineClassMethodByValue);
}
},
MethodDefinition::Generator(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::DefineClassMethodByValue);
}
},
MethodDefinition::AsyncGenerator(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::DefineClassMethodByValue);
}
},
}
}
// TODO: set names for private methods
ClassElement::PrivateStaticMethodDefinition(name, method_definition) => {
self.emit_opcode(Opcode::Dup);
match &method_definition {
MethodDefinition::Get(expr) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::SetPrivateGetter, &[index]);
}
MethodDefinition::Set(expr) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::SetPrivateSetter, &[index]);
}
MethodDefinition::Ordinary(expr) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::SetPrivateMethod, &[index]);
}
MethodDefinition::Async(expr) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::SetPrivateMethod, &[index]);
}
MethodDefinition::Generator(expr) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::SetPrivateMethod, &[index]);
}
MethodDefinition::AsyncGenerator(expr) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::SetPrivateMethod, &[index]);
}
}
}
ClassElement::FieldDefinition(name, field) => {
self.emit_opcode(Opcode::Dup);
match name {
PropertyName::Literal(name) => {
self.emit_push_literal(Literal::String(
self.interner().resolve_expect(*name).into_common(false),
));
}
PropertyName::Computed(name) => {
self.compile_expr(name, true)?;
}
}
let field_code = CodeBlock::new(Sym::EMPTY_STRING, 0, true);
let mut field_compiler = ByteCompiler {
code_block: field_code,
literals_map: FxHashMap::default(),
names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(),
jump_info: Vec::new(),
in_async_generator: false,
json_parse: self.json_parse,
context: self.context,
};
field_compiler.context.push_compile_time_environment(true);
if let Some(node) = field {
field_compiler.compile_expr(node, true)?;
} else {
field_compiler.emit_opcode(Opcode::PushUndefined);
}
let (num_bindings, compile_environment) =
field_compiler.context.pop_compile_time_environment();
field_compiler.push_compile_environment(compile_environment);
field_compiler.code_block.num_bindings = num_bindings;
field_compiler.emit_opcode(Opcode::Return);
let code = Gc::new(field_compiler.finish());
let index = self.code_block.functions.len() as u32;
self.code_block.functions.push(code);
self.emit(Opcode::GetFunction, &[index]);
self.emit_opcode(Opcode::PushClassField);
}
ClassElement::PrivateFieldDefinition(name, field) => {
self.emit_opcode(Opcode::Dup);
let name_index = self.get_or_insert_name((*name).into());
let field_code = CodeBlock::new(Sym::EMPTY_STRING, 0, true);
let mut field_compiler = ByteCompiler {
code_block: field_code,
literals_map: FxHashMap::default(),
names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(),
jump_info: Vec::new(),
in_async_generator: false,
json_parse: self.json_parse,
context: self.context,
};
field_compiler.context.push_compile_time_environment(true);
if let Some(node) = field {
field_compiler.compile_expr(node, true)?;
} else {
field_compiler.emit_opcode(Opcode::PushUndefined);
}
let (num_bindings, compile_environment) =
field_compiler.context.pop_compile_time_environment();
field_compiler.push_compile_environment(compile_environment);
field_compiler.code_block.num_bindings = num_bindings;
field_compiler.emit_opcode(Opcode::Return);
let code = Gc::new(field_compiler.finish());
let index = self.code_block.functions.len() as u32;
self.code_block.functions.push(code);
self.emit(Opcode::GetFunction, &[index]);
self.emit(Opcode::PushClassFieldPrivate, &[name_index]);
}
ClassElement::StaticFieldDefinition(name, field) => {
self.emit_opcode(Opcode::Dup);
match name {
PropertyName::Literal(name) => {
if let Some(node) = field {
self.compile_expr(node, true)?;
} else {
self.emit_opcode(Opcode::PushUndefined);
}
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineOwnPropertyByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
if let Some(node) = field {
self.compile_expr(node, true)?;
} else {
self.emit_opcode(Opcode::PushUndefined);
}
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
}
}
ClassElement::PrivateStaticFieldDefinition(name, field) => {
self.emit_opcode(Opcode::Dup);
if let Some(node) = field {
self.compile_expr(node, true)?;
} else {
self.emit_opcode(Opcode::PushUndefined);
}
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::SetPrivateField, &[index]);
}
ClassElement::StaticBlock(statement_list) => {
self.emit_opcode(Opcode::Dup);
let mut compiler =
ByteCompiler::new(Sym::EMPTY_STRING, true, false, self.context);
compiler.context.push_compile_time_environment(true);
compiler.create_decls(statement_list, false);
compiler.compile_statement_list(statement_list, false, false)?;
let (num_bindings, compile_environment) =
compiler.context.pop_compile_time_environment();
compiler.push_compile_environment(compile_environment);
compiler.code_block.num_bindings = num_bindings;
let code = Gc::new(compiler.finish());
let index = self.code_block.functions.len() as u32;
self.code_block.functions.push(code);
self.emit(Opcode::GetFunction, &[index]);
self.emit_opcode(Opcode::SetHomeObject);
self.emit(Opcode::Call, &[0]);
self.emit_opcode(Opcode::Pop);
}
// TODO: set names for private methods
ClassElement::PrivateMethodDefinition(name, method_definition) => {
self.emit_opcode(Opcode::Dup);
match method_definition {
MethodDefinition::Get(expr) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::PushClassPrivateGetter, &[index]);
}
MethodDefinition::Set(expr) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::PushClassPrivateSetter, &[index]);
}
MethodDefinition::Ordinary(expr) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::PushClassPrivateMethod, &[index]);
}
MethodDefinition::Async(expr) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::PushClassPrivateMethod, &[index]);
}
MethodDefinition::Generator(expr) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::PushClassPrivateMethod, &[index]);
}
MethodDefinition::AsyncGenerator(expr) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::PushClassPrivateMethod, &[index]);
}
}
}
ClassElement::MethodDefinition(..) => {}
}
}
self.emit_opcode(Opcode::Swap);
for element in class.elements() {
match element {
ClassElement::MethodDefinition(name, method_definition) => {
self.emit_opcode(Opcode::Dup);
// TODO: set names for getters and setters
match method_definition {
MethodDefinition::Get(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassGetterByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::DefineClassGetterByValue);
}
},
MethodDefinition::Set(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassSetterByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::DefineClassSetterByValue);
}
},
MethodDefinition::Ordinary(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::DefineClassMethodByValue);
}
},
MethodDefinition::Async(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::DefineClassMethodByValue);
}
},
MethodDefinition::Generator(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::DefineClassMethodByValue);
}
},
MethodDefinition::AsyncGenerator(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineClassMethodByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::DefineClassMethodByValue);
}
},
}
}
ClassElement::PrivateMethodDefinition(..)
| ClassElement::PrivateFieldDefinition(..)
| ClassElement::StaticFieldDefinition(..)
| ClassElement::PrivateStaticFieldDefinition(..)
| ClassElement::StaticMethodDefinition(..)
| ClassElement::PrivateStaticMethodDefinition(..)
| ClassElement::StaticBlock(..)
| ClassElement::FieldDefinition(..) => {}
}
}
self.emit_opcode(Opcode::Pop);
if !expression {
self.emit_binding(
BindingOpcode::InitVar,
class.name().expect("class statements must have a name"),
);
}
Ok(())
}
}

258
boa_engine/src/bytecompiler/declaration/declaration_pattern.rs

@ -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
boa_engine/src/bytecompiler/declaration/mod.rs

@ -0,0 +1 @@
mod declaration_pattern;

90
boa_engine/src/bytecompiler/expression/assign.rs

@ -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(())
}
}

98
boa_engine/src/bytecompiler/expression/binary.rs

@ -0,0 +1,98 @@
use boa_ast::expression::operator::{
binary::{ArithmeticOp, BinaryOp, BitwiseOp, LogicalOp, RelationalOp},
Binary,
};
use crate::{bytecompiler::ByteCompiler, vm::Opcode, JsResult};
impl ByteCompiler<'_> {
pub(crate) fn compile_binary(&mut self, binary: &Binary, use_expr: bool) -> JsResult<()> {
self.compile_expr(binary.lhs(), true)?;
match binary.op() {
BinaryOp::Arithmetic(op) => {
self.compile_expr(binary.rhs(), true)?;
match op {
ArithmeticOp::Add => self.emit_opcode(Opcode::Add),
ArithmeticOp::Sub => self.emit_opcode(Opcode::Sub),
ArithmeticOp::Div => self.emit_opcode(Opcode::Div),
ArithmeticOp::Mul => self.emit_opcode(Opcode::Mul),
ArithmeticOp::Exp => self.emit_opcode(Opcode::Pow),
ArithmeticOp::Mod => self.emit_opcode(Opcode::Mod),
}
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
BinaryOp::Bitwise(op) => {
self.compile_expr(binary.rhs(), true)?;
match op {
BitwiseOp::And => self.emit_opcode(Opcode::BitAnd),
BitwiseOp::Or => self.emit_opcode(Opcode::BitOr),
BitwiseOp::Xor => self.emit_opcode(Opcode::BitXor),
BitwiseOp::Shl => self.emit_opcode(Opcode::ShiftLeft),
BitwiseOp::Shr => self.emit_opcode(Opcode::ShiftRight),
BitwiseOp::UShr => self.emit_opcode(Opcode::UnsignedShiftRight),
}
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
BinaryOp::Relational(op) => {
self.compile_expr(binary.rhs(), true)?;
match op {
RelationalOp::Equal => self.emit_opcode(Opcode::Eq),
RelationalOp::NotEqual => self.emit_opcode(Opcode::NotEq),
RelationalOp::StrictEqual => self.emit_opcode(Opcode::StrictEq),
RelationalOp::StrictNotEqual => self.emit_opcode(Opcode::StrictNotEq),
RelationalOp::GreaterThan => self.emit_opcode(Opcode::GreaterThan),
RelationalOp::GreaterThanOrEqual => {
self.emit_opcode(Opcode::GreaterThanOrEq);
}
RelationalOp::LessThan => self.emit_opcode(Opcode::LessThan),
RelationalOp::LessThanOrEqual => self.emit_opcode(Opcode::LessThanOrEq),
RelationalOp::In => self.emit_opcode(Opcode::In),
RelationalOp::InstanceOf => self.emit_opcode(Opcode::InstanceOf),
}
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
BinaryOp::Logical(op) => {
match op {
LogicalOp::And => {
let exit = self.emit_opcode_with_operand(Opcode::LogicalAnd);
self.compile_expr(binary.rhs(), true)?;
self.patch_jump(exit);
}
LogicalOp::Or => {
let exit = self.emit_opcode_with_operand(Opcode::LogicalOr);
self.compile_expr(binary.rhs(), true)?;
self.patch_jump(exit);
}
LogicalOp::Coalesce => {
let exit = self.emit_opcode_with_operand(Opcode::Coalesce);
self.compile_expr(binary.rhs(), true)?;
self.patch_jump(exit);
}
};
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
BinaryOp::Comma => {
self.emit(Opcode::Pop, &[]);
self.compile_expr(binary.rhs(), true)?;
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
};
Ok(())
}
}

317
boa_engine/src/bytecompiler/expression/mod.rs

@ -0,0 +1,317 @@
use crate::{
bytecompiler::{ByteCompiler, Literal},
vm::Opcode,
JsResult,
};
use boa_ast::{
expression::{
access::{PropertyAccess, PropertyAccessField},
literal::{Literal as AstLiteral, TemplateElement, TemplateLiteral},
operator::Conditional,
},
Expression,
};
mod assign;
mod binary;
mod object_literal;
mod unary;
use boa_interner::Sym;
use super::{Access, Callable, NodeKind};
impl ByteCompiler<'_> {
fn compile_literal(&mut self, lit: &AstLiteral, use_expr: bool) {
match lit {
AstLiteral::String(v) => self.emit_push_literal(Literal::String(
self.interner().resolve_expect(*v).into_common(false),
)),
AstLiteral::Int(v) => self.emit_push_integer(*v),
AstLiteral::Num(v) => self.emit_push_rational(*v),
AstLiteral::BigInt(v) => {
self.emit_push_literal(Literal::BigInt(v.clone().into()));
}
AstLiteral::Bool(true) => self.emit(Opcode::PushTrue, &[]),
AstLiteral::Bool(false) => self.emit(Opcode::PushFalse, &[]),
AstLiteral::Null => self.emit(Opcode::PushNull, &[]),
AstLiteral::Undefined => self.emit(Opcode::PushUndefined, &[]),
}
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
fn compile_conditional(&mut self, op: &Conditional, use_expr: bool) -> JsResult<()> {
self.compile_expr(op.condition(), true)?;
let jelse = self.jump_if_false();
self.compile_expr(op.if_true(), true)?;
let exit = self.jump();
self.patch_jump(jelse);
self.compile_expr(op.if_false(), true)?;
self.patch_jump(exit);
if !use_expr {
self.emit(Opcode::Pop, &[]);
};
Ok(())
}
fn compile_template_literal(
&mut self,
template_literal: &TemplateLiteral,
use_expr: bool,
) -> JsResult<()> {
for element in template_literal.elements() {
match element {
TemplateElement::String(s) => self.emit_push_literal(Literal::String(
self.interner().resolve_expect(*s).into_common(false),
)),
TemplateElement::Expr(expr) => {
self.compile_expr(expr, true)?;
}
}
}
self.emit(
Opcode::ConcatToString,
&[template_literal.elements().len() as u32],
);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
Ok(())
}
pub(crate) fn compile_expr_impl(&mut self, expr: &Expression, use_expr: bool) -> JsResult<()> {
match expr {
Expression::Literal(lit) => self.compile_literal(lit, use_expr),
Expression::Unary(unary) => self.compile_unary(unary, use_expr)?,
Expression::Binary(binary) => self.compile_binary(binary, use_expr)?,
Expression::Assign(assign) => self.compile_assign(assign, use_expr)?,
Expression::ObjectLiteral(object) => {
self.compile_object_literal(object, use_expr)?;
}
Expression::Identifier(name) => {
self.access_get(Access::Variable { name: *name }, use_expr)?;
}
Expression::PropertyAccess(access) => {
self.access_get(Access::Property { access }, use_expr)?;
}
Expression::Conditional(op) => self.compile_conditional(op, use_expr)?,
Expression::ArrayLiteral(array) => {
self.emit_opcode(Opcode::PushNewArray);
self.emit_opcode(Opcode::PopOnReturnAdd);
for element in array.as_ref() {
if let Some(element) = element {
self.compile_expr(element, true)?;
if let Expression::Spread(_) = element {
self.emit_opcode(Opcode::InitIterator);
self.emit_opcode(Opcode::PushIteratorToArray);
} else {
self.emit_opcode(Opcode::PushValueToArray);
}
} else {
self.emit_opcode(Opcode::PushElisionToArray);
}
}
self.emit_opcode(Opcode::PopOnReturnSub);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
Expression::This => {
self.access_get(Access::This, use_expr)?;
}
Expression::Spread(spread) => self.compile_expr(spread.target(), true)?,
Expression::Function(function) => {
self.function(function.into(), NodeKind::Expression, use_expr)?;
}
Expression::ArrowFunction(function) => {
self.function(function.into(), NodeKind::Expression, use_expr)?;
}
Expression::AsyncArrowFunction(function) => {
self.function(function.into(), NodeKind::Expression, use_expr)?;
}
Expression::Generator(function) => {
self.function(function.into(), NodeKind::Expression, use_expr)?;
}
Expression::AsyncFunction(function) => {
self.function(function.into(), NodeKind::Expression, use_expr)?;
}
Expression::AsyncGenerator(function) => {
self.function(function.into(), NodeKind::Expression, use_expr)?;
}
Expression::Call(call) => self.call(Callable::Call(call), use_expr)?,
Expression::New(new) => self.call(Callable::New(new), use_expr)?,
Expression::TemplateLiteral(template_literal) => {
self.compile_template_literal(template_literal, use_expr)?;
}
Expression::Await(expr) => {
self.compile_expr(expr.target(), true)?;
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext);
if !use_expr {
self.emit_opcode(Opcode::Pop);
}
}
Expression::Yield(r#yield) => {
if let Some(expr) = r#yield.target() {
self.compile_expr(expr, true)?;
} else {
self.emit_opcode(Opcode::PushUndefined);
}
if r#yield.delegate() {
if self.in_async_generator {
self.emit_opcode(Opcode::InitIteratorAsync);
} else {
self.emit_opcode(Opcode::InitIterator);
}
self.emit_opcode(Opcode::PushUndefined);
let start_address = self.next_opcode_location();
let start = self.emit_opcode_with_operand(Opcode::GeneratorNextDelegate);
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(start);
} else if self.in_async_generator {
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::AsyncGeneratorNext);
let jump_return = self.emit_opcode_with_operand(Opcode::JumpIfFalse);
let jump = self.emit_opcode_with_operand(Opcode::JumpIfFalse);
self.emit_opcode(Opcode::Yield);
self.emit_opcode(Opcode::GeneratorNext);
self.patch_jump(jump);
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext);
self.patch_jump(jump_return);
} else {
self.emit_opcode(Opcode::Yield);
self.emit_opcode(Opcode::GeneratorNext);
}
if !use_expr {
self.emit_opcode(Opcode::Pop);
}
}
Expression::TaggedTemplate(template) => {
match template.tag() {
Expression::PropertyAccess(PropertyAccess::Simple(access)) => {
self.compile_expr(access.target(), true)?;
self.emit(Opcode::Dup, &[]);
match access.field() {
PropertyAccessField::Const(field) => {
let index = self.get_or_insert_name((*field).into());
self.emit(Opcode::GetPropertyByName, &[index]);
}
PropertyAccessField::Expr(field) => {
self.compile_expr(field, true)?;
self.emit(Opcode::GetPropertyByValue, &[]);
}
}
}
Expression::PropertyAccess(PropertyAccess::Private(access)) => {
self.compile_expr(access.target(), true)?;
self.emit(Opcode::Dup, &[]);
let index = self.get_or_insert_name(access.field().into());
self.emit(Opcode::GetPrivateField, &[index]);
}
expr => {
self.compile_expr(expr, true)?;
self.emit_opcode(Opcode::This);
self.emit_opcode(Opcode::Swap);
}
}
self.emit_opcode(Opcode::PushNewArray);
for cooked in template.cookeds() {
if let Some(cooked) = cooked {
self.emit_push_literal(Literal::String(
self.interner().resolve_expect(*cooked).into_common(false),
));
} else {
self.emit_opcode(Opcode::PushUndefined);
}
self.emit_opcode(Opcode::PushValueToArray);
}
self.emit_opcode(Opcode::Dup);
self.emit_opcode(Opcode::PushNewArray);
for raw in template.raws() {
self.emit_push_literal(Literal::String(
self.interner().resolve_expect(*raw).into_common(false),
));
self.emit_opcode(Opcode::PushValueToArray);
}
let index = self.get_or_insert_name(Sym::RAW.into());
self.emit(Opcode::SetPropertyByName, &[index]);
self.emit(Opcode::Pop, &[]);
for expr in template.exprs() {
self.compile_expr(expr, true)?;
}
self.emit(Opcode::Call, &[(template.exprs().len() + 1) as u32]);
}
Expression::Class(class) => self.class(class, true)?,
Expression::SuperCall(super_call) => {
let contains_spread = super_call
.arguments()
.iter()
.any(|arg| matches!(arg, Expression::Spread(_)));
if contains_spread {
self.emit_opcode(Opcode::PushNewArray);
for arg in super_call.arguments() {
self.compile_expr(arg, true)?;
if let Expression::Spread(_) = arg {
self.emit_opcode(Opcode::InitIterator);
self.emit_opcode(Opcode::PushIteratorToArray);
} else {
self.emit_opcode(Opcode::PushValueToArray);
}
}
} else {
for arg in super_call.arguments() {
self.compile_expr(arg, true)?;
}
}
if contains_spread {
self.emit_opcode(Opcode::SuperCallSpread);
} else {
self.emit(Opcode::SuperCall, &[super_call.arguments().len() as u32]);
}
if !use_expr {
self.emit_opcode(Opcode::Pop);
}
}
Expression::NewTarget => {
if use_expr {
self.emit_opcode(Opcode::PushNewTarget);
}
}
Expression::Optional(opt) => {
self.compile_optional_preserve_this(opt)?;
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::Pop);
if !use_expr {
self.emit_opcode(Opcode::Pop);
}
}
// TODO: try to remove this variant somehow
Expression::FormalParameterList(_) => unreachable!(),
}
Ok(())
}
}

170
boa_engine/src/bytecompiler/expression/object_literal.rs

@ -0,0 +1,170 @@
use boa_ast::{
expression::literal::ObjectLiteral,
property::{MethodDefinition, PropertyDefinition, PropertyName},
};
use boa_interner::Sym;
use crate::{
bytecompiler::{Access, ByteCompiler, NodeKind},
vm::Opcode,
JsNativeError, JsResult,
};
impl ByteCompiler<'_> {
pub(crate) fn compile_object_literal(
&mut self,
object: &ObjectLiteral,
use_expr: bool,
) -> JsResult<()> {
self.emit_opcode(Opcode::PushEmptyObject);
for property in object.properties() {
self.emit_opcode(Opcode::Dup);
match property {
PropertyDefinition::IdentifierReference(ident) => {
let index = self.get_or_insert_name(*ident);
self.access_get(Access::Variable { name: *ident }, true)?;
self.emit(Opcode::DefineOwnPropertyByName, &[index]);
}
PropertyDefinition::Property(name, expr) => match name {
PropertyName::Literal(name) => {
self.compile_expr(expr, true)?;
let index = self.get_or_insert_name((*name).into());
if *name == Sym::__PROTO__ && !self.json_parse {
self.emit_opcode(Opcode::SetPrototype);
} else {
self.emit(Opcode::DefineOwnPropertyByName, &[index]);
}
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
if expr.is_function_definition() {
self.emit_opcode(Opcode::Dup);
self.compile_expr(expr, true)?;
self.emit_opcode(Opcode::SetFunctionName);
self.emit_u8(0);
} else {
self.compile_expr(expr, true)?;
}
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
},
PropertyDefinition::MethodDefinition(name, kind) => match kind {
MethodDefinition::Get(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::SetPropertyGetterByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.emit_opcode(Opcode::Dup);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::SetFunctionName);
self.emit_u8(1);
self.emit_opcode(Opcode::SetPropertyGetterByValue);
}
},
MethodDefinition::Set(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::SetPropertySetterByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.emit_opcode(Opcode::Dup);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::SetFunctionName);
self.emit_u8(2);
self.emit_opcode(Opcode::SetPropertySetterByValue);
}
},
MethodDefinition::Ordinary(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineOwnPropertyByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.emit_opcode(Opcode::Dup);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::SetFunctionName);
self.emit_u8(0);
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
},
MethodDefinition::Async(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineOwnPropertyByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.emit_opcode(Opcode::Dup);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::SetFunctionName);
self.emit_u8(0);
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
},
MethodDefinition::Generator(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineOwnPropertyByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.emit_opcode(Opcode::Dup);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::SetFunctionName);
self.emit_u8(0);
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
},
MethodDefinition::AsyncGenerator(expr) => match name {
PropertyName::Literal(name) => {
self.function(expr.into(), NodeKind::Expression, true)?;
let index = self.get_or_insert_name((*name).into());
self.emit(Opcode::DefineOwnPropertyByName, &[index]);
}
PropertyName::Computed(name_node) => {
self.compile_expr(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey);
self.emit_opcode(Opcode::Dup);
self.function(expr.into(), NodeKind::Expression, true)?;
self.emit_opcode(Opcode::SetFunctionName);
self.emit_u8(0);
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
},
},
PropertyDefinition::SpreadObject(expr) => {
self.compile_expr(expr, true)?;
self.emit_opcode(Opcode::Swap);
self.emit(Opcode::CopyDataProperties, &[0, 0]);
self.emit_opcode(Opcode::Pop);
}
// TODO: Promote to early errors
PropertyDefinition::CoverInitializedName(_, _) => {
return Err(JsNativeError::syntax()
.with_message("invalid assignment pattern in object literal")
.into())
}
}
}
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
Ok(())
}
}

113
boa_engine/src/bytecompiler/expression/unary.rs

@ -0,0 +1,113 @@
use boa_ast::{
expression::operator::{unary::UnaryOp, Unary},
Expression,
};
use crate::{
bytecompiler::{Access, ByteCompiler},
vm::Opcode,
JsNativeError, JsResult,
};
impl ByteCompiler<'_> {
pub(crate) fn compile_unary(&mut self, unary: &Unary, use_expr: bool) -> JsResult<()> {
let opcode = match unary.op() {
UnaryOp::IncrementPre => {
// TODO: promote to an early error.
let access = Access::from_expression(unary.target()).ok_or_else(|| {
JsNativeError::syntax().with_message("Invalid increment operand")
})?;
self.access_set(access, true, |compiler, _| {
compiler.compile_expr(unary.target(), true)?;
compiler.emit(Opcode::Inc, &[]);
Ok(())
})?;
None
}
UnaryOp::DecrementPre => {
// TODO: promote to an early error.
let access = Access::from_expression(unary.target()).ok_or_else(|| {
JsNativeError::syntax().with_message("Invalid decrement operand")
})?;
self.access_set(access, true, |compiler, _| {
compiler.compile_expr(unary.target(), true)?;
compiler.emit(Opcode::Dec, &[]);
Ok(())
})?;
None
}
UnaryOp::IncrementPost => {
// TODO: promote to an early error.
let access = Access::from_expression(unary.target()).ok_or_else(|| {
JsNativeError::syntax().with_message("Invalid increment operand")
})?;
self.access_set(access, false, |compiler, level| {
compiler.compile_expr(unary.target(), true)?;
compiler.emit(Opcode::IncPost, &[]);
compiler.emit_opcode(Opcode::RotateRight);
compiler.emit_u8(level + 2);
Ok(())
})?;
None
}
UnaryOp::DecrementPost => {
// TODO: promote to an early error.
let access = Access::from_expression(unary.target()).ok_or_else(|| {
JsNativeError::syntax().with_message("Invalid decrement operand")
})?;
self.access_set(access, false, |compiler, level| {
compiler.compile_expr(unary.target(), true)?;
compiler.emit(Opcode::DecPost, &[]);
compiler.emit_opcode(Opcode::RotateRight);
compiler.emit_u8(level + 2);
Ok(())
})?;
None
}
UnaryOp::Delete => {
if let Some(access) = Access::from_expression(unary.target()) {
self.access_delete(access)?;
} else {
self.compile_expr(unary.target(), false)?;
self.emit(Opcode::PushTrue, &[]);
}
None
}
UnaryOp::Minus => Some(Opcode::Neg),
UnaryOp::Plus => Some(Opcode::Pos),
UnaryOp::Not => Some(Opcode::LogicalNot),
UnaryOp::Tilde => Some(Opcode::BitNot),
UnaryOp::TypeOf => {
match &unary.target() {
Expression::Identifier(identifier) => {
let binding = self.context.get_binding_value(*identifier);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::GetNameOrUndefined, &[index]);
}
expr => self.compile_expr(expr, true)?,
}
self.emit_opcode(Opcode::TypeOf);
None
}
UnaryOp::Void => Some(Opcode::Void),
};
if let Some(opcode) = opcode {
self.compile_expr(unary.target(), true)?;
self.emit(opcode, &[]);
}
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
Ok(())
}
}

2226
boa_engine/src/bytecompiler/mod.rs

File diff suppressed because it is too large Load Diff

96
boa_engine/src/bytecompiler/statement/continue.rs

@ -0,0 +1,96 @@
use boa_ast::statement::Continue;
use crate::{
bytecompiler::{ByteCompiler, JumpControlInfoKind},
vm::Opcode,
JsNativeError, JsResult,
};
impl ByteCompiler<'_> {
pub(crate) fn compile_continue(&mut self, node: Continue) -> JsResult<()> {
let next = self.next_opcode_location();
if let Some(info) = self
.jump_info
.last()
.filter(|info| info.kind == JumpControlInfoKind::Try)
{
let start_address = info.start_address;
let in_finally = if let Some(finally_start) = info.finally_start {
next > finally_start.index
} else {
false
};
let in_catch_no_finally = !info.has_finally && info.in_catch;
if in_finally {
self.emit_opcode(Opcode::PopIfThrown);
}
if in_finally || in_catch_no_finally {
self.emit_opcode(Opcode::CatchEnd2);
} else {
self.emit_opcode(Opcode::TryEnd);
}
self.emit(Opcode::FinallySetJump, &[start_address]);
let label = self.jump();
self.jump_info
.last_mut()
.expect("no jump information found")
.try_continues
.push(label);
} else {
let mut items = self
.jump_info
.iter()
.rev()
.filter(|info| info.kind == JumpControlInfoKind::Loop);
let address = if let Some(label_name) = node.label() {
let mut num_loops = 0;
let mut emit_for_of_in_exit = 0;
let mut address_info = None;
for info in items {
if info.label == node.label() {
address_info = Some(info);
break;
}
num_loops += 1;
if info.for_of_in_loop {
emit_for_of_in_exit += 1;
}
}
// TODO: promote to an early error.
let address = address_info
.ok_or_else(|| {
JsNativeError::syntax().with_message(format!(
"Cannot use the undeclared label '{}'",
self.context.interner().resolve_expect(label_name)
))
})?
.start_address;
for _ in 0..emit_for_of_in_exit {
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
}
for _ in 0..num_loops {
self.emit_opcode(Opcode::LoopEnd);
}
address
} else {
items
.next()
// TODO: promote to an early error.
.ok_or_else(|| {
JsNativeError::syntax().with_message("continue must be inside loop")
})?
.start_address
};
self.emit_opcode(Opcode::LoopEnd);
self.emit_opcode(Opcode::LoopStart);
self.emit(Opcode::Jump, &[address]);
}
Ok(())
}
}

363
boa_engine/src/bytecompiler/statement/loop.rs

@ -0,0 +1,363 @@
use boa_ast::{
declaration::Binding,
operations::bound_names,
statement::{
iteration::{ForLoopInitializer, IterableLoopInitializer},
DoWhileLoop, ForInLoop, ForLoop, ForOfLoop, WhileLoop,
},
};
use boa_interner::Sym;
use crate::{
bytecompiler::{Access, ByteCompiler},
vm::{BindingOpcode, Opcode},
JsResult,
};
impl ByteCompiler<'_> {
pub(crate) fn compile_for_loop(
&mut self,
for_loop: &ForLoop,
label: Option<Sym>,
configurable_globals: bool,
) -> JsResult<()> {
self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
if let Some(init) = for_loop.init() {
match init {
ForLoopInitializer::Expression(expr) => self.compile_expr(expr, false)?,
ForLoopInitializer::Var(decl) => {
self.create_decls_from_var_decl(decl, configurable_globals);
self.compile_var_decl(decl)?;
}
ForLoopInitializer::Lexical(decl) => {
self.create_decls_from_lexical_decl(decl);
self.compile_lexical_decl(decl)?;
}
}
}
self.emit_opcode(Opcode::LoopStart);
let initial_jump = self.jump();
let start_address = self.next_opcode_location();
self.push_loop_control_info(label, start_address);
self.emit_opcode(Opcode::LoopContinue);
if let Some(final_expr) = for_loop.final_expr() {
self.compile_expr(final_expr, false)?;
}
self.patch_jump(initial_jump);
if let Some(condition) = for_loop.condition() {
self.compile_expr(condition, true)?;
} else {
self.emit_opcode(Opcode::PushTrue);
}
let exit = self.jump_if_false();
self.compile_stmt(for_loop.body(), false, configurable_globals)?;
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment);
Ok(())
}
pub(crate) fn compile_for_in_loop(
&mut self,
for_in_loop: &ForInLoop,
label: Option<Sym>,
configurable_globals: bool,
) -> JsResult<()> {
let init_bound_names = bound_names(for_in_loop.initializer());
if init_bound_names.is_empty() {
self.compile_expr(for_in_loop.target(), true)?;
} else {
self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
for name in init_bound_names {
self.context.create_mutable_binding(name, false, false);
}
self.compile_expr(for_in_loop.target(), true)?;
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment);
}
let early_exit = self.emit_opcode_with_operand(Opcode::ForInLoopInitIterator);
self.emit_opcode(Opcode::LoopStart);
let start_address = self.next_opcode_location();
self.push_loop_control_info_for_of_in_loop(label, start_address);
self.emit_opcode(Opcode::LoopContinue);
self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
let exit = self.emit_opcode_with_operand(Opcode::ForInLoopNext);
match for_in_loop.initializer() {
IterableLoopInitializer::Identifier(ident) => {
self.context.create_mutable_binding(*ident, true, true);
let binding = self.context.set_mutable_binding(*ident);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::DefInitVar, &[index]);
}
IterableLoopInitializer::Access(access) => {
self.access_set(
Access::Property { access },
false,
ByteCompiler::access_set_top_of_stack_expr_fn,
)?;
}
IterableLoopInitializer::Var(declaration) => match declaration {
Binding::Identifier(ident) => {
self.context
.create_mutable_binding(*ident, true, configurable_globals);
self.emit_binding(BindingOpcode::InitVar, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.context.create_mutable_binding(ident, true, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?;
}
},
IterableLoopInitializer::Let(declaration) => match declaration {
Binding::Identifier(ident) => {
self.context.create_mutable_binding(*ident, false, false);
self.emit_binding(BindingOpcode::InitLet, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.context.create_mutable_binding(ident, false, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLet)?;
}
},
IterableLoopInitializer::Const(declaration) => match declaration {
Binding::Identifier(ident) => {
self.context.create_immutable_binding(*ident, true);
self.emit_binding(BindingOpcode::InitConst, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.context.create_immutable_binding(ident, true);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitConst)?;
}
},
IterableLoopInitializer::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.context.create_mutable_binding(ident, true, true);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?;
}
}
self.compile_stmt(for_in_loop.body(), false, configurable_globals)?;
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment);
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
self.emit_opcode(Opcode::IteratorClose);
self.patch_jump(early_exit);
Ok(())
}
pub(crate) fn compile_for_of_loop(
&mut self,
for_of_loop: &ForOfLoop,
label: Option<Sym>,
configurable_globals: bool,
) -> JsResult<()> {
let init_bound_names = bound_names(for_of_loop.initializer());
if init_bound_names.is_empty() {
self.compile_expr(for_of_loop.iterable(), true)?;
} else {
self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
for name in init_bound_names {
self.context.create_mutable_binding(name, false, false);
}
self.compile_expr(for_of_loop.iterable(), true)?;
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment);
}
if for_of_loop.r#await() {
self.emit_opcode(Opcode::InitIteratorAsync);
} else {
self.emit_opcode(Opcode::InitIterator);
}
self.emit_opcode(Opcode::LoopStart);
let start_address = self.next_opcode_location();
self.push_loop_control_info_for_of_in_loop(label, start_address);
self.emit_opcode(Opcode::LoopContinue);
self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
let exit = if for_of_loop.r#await() {
self.emit_opcode(Opcode::ForAwaitOfLoopIterate);
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext);
self.emit_opcode_with_operand(Opcode::ForAwaitOfLoopNext)
} else {
self.emit_opcode_with_operand(Opcode::ForInLoopNext)
};
match for_of_loop.initializer() {
IterableLoopInitializer::Identifier(ref ident) => {
self.context.create_mutable_binding(*ident, true, true);
let binding = self.context.set_mutable_binding(*ident);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::DefInitVar, &[index]);
}
IterableLoopInitializer::Access(access) => {
self.access_set(
Access::Property { access },
false,
ByteCompiler::access_set_top_of_stack_expr_fn,
)?;
}
IterableLoopInitializer::Var(declaration) => match declaration {
Binding::Identifier(ident) => {
self.context.create_mutable_binding(*ident, true, false);
self.emit_binding(BindingOpcode::InitVar, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.context.create_mutable_binding(ident, true, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?;
}
},
IterableLoopInitializer::Let(declaration) => match declaration {
Binding::Identifier(ident) => {
self.context.create_mutable_binding(*ident, false, false);
self.emit_binding(BindingOpcode::InitLet, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.context.create_mutable_binding(ident, false, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLet)?;
}
},
IterableLoopInitializer::Const(declaration) => match declaration {
Binding::Identifier(ident) => {
self.context.create_immutable_binding(*ident, true);
self.emit_binding(BindingOpcode::InitConst, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.context.create_immutable_binding(ident, true);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitConst)?;
}
},
IterableLoopInitializer::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.context.create_mutable_binding(ident, true, true);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar)?;
}
}
self.compile_stmt(for_of_loop.body(), false, configurable_globals)?;
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment);
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
self.emit_opcode(Opcode::IteratorClose);
Ok(())
}
pub(crate) fn compile_while_loop(
&mut self,
while_loop: &WhileLoop,
label: Option<Sym>,
configurable_globals: bool,
) -> JsResult<()> {
self.emit_opcode(Opcode::LoopStart);
let start_address = self.next_opcode_location();
self.push_loop_control_info(label, start_address);
self.emit_opcode(Opcode::LoopContinue);
self.compile_expr(while_loop.condition(), true)?;
let exit = self.jump_if_false();
self.compile_stmt(while_loop.body(), false, configurable_globals)?;
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
Ok(())
}
pub(crate) fn compile_do_while_loop(
&mut self,
do_while_loop: &DoWhileLoop,
label: Option<Sym>,
configurable_globals: bool,
) -> JsResult<()> {
self.emit_opcode(Opcode::LoopStart);
let initial_label = self.jump();
let start_address = self.next_opcode_location();
self.push_loop_control_info(label, start_address);
self.emit_opcode(Opcode::LoopContinue);
let condition_label_address = self.next_opcode_location();
self.compile_expr(do_while_loop.cond(), true)?;
let exit = self.jump_if_false();
self.patch_jump(initial_label);
self.compile_stmt(do_while_loop.body(), false, configurable_globals)?;
self.emit(Opcode::Jump, &[condition_label_address]);
self.patch_jump(exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
Ok(())
}
}

228
boa_engine/src/bytecompiler/statement/mod.rs

@ -0,0 +1,228 @@
use boa_ast::{
statement::{Block, Break, If, Labelled, LabelledItem, Switch},
Statement,
};
use boa_interner::Sym;
use crate::{vm::Opcode, JsNativeError, JsResult};
use super::{ByteCompiler, JumpControlInfoKind, NodeKind};
mod r#continue;
mod r#loop;
mod r#try;
impl ByteCompiler<'_> {
pub(crate) fn compile_if(&mut self, node: &If, configurable_globals: bool) -> JsResult<()> {
self.compile_expr(node.cond(), true)?;
let jelse = self.jump_if_false();
self.compile_stmt(node.body(), false, configurable_globals)?;
match node.else_node() {
None => {
self.patch_jump(jelse);
}
Some(else_body) => {
let exit = self.jump();
self.patch_jump(jelse);
self.compile_stmt(else_body, false, configurable_globals)?;
self.patch_jump(exit);
}
}
Ok(())
}
pub(crate) fn compile_labelled(
&mut self,
labelled: &Labelled,
use_expr: bool,
configurable_globals: bool,
) -> JsResult<()> {
match labelled.item() {
LabelledItem::Statement(stmt) => match stmt {
Statement::ForLoop(for_loop) => {
self.compile_for_loop(for_loop, Some(labelled.label()), configurable_globals)?;
}
Statement::ForInLoop(for_in_loop) => {
self.compile_for_in_loop(
for_in_loop,
Some(labelled.label()),
configurable_globals,
)?;
}
Statement::ForOfLoop(for_of_loop) => {
self.compile_for_of_loop(
for_of_loop,
Some(labelled.label()),
configurable_globals,
)?;
}
Statement::WhileLoop(while_loop) => {
self.compile_while_loop(
while_loop,
Some(labelled.label()),
configurable_globals,
)?;
}
Statement::DoWhileLoop(do_while_loop) => {
self.compile_do_while_loop(
do_while_loop,
Some(labelled.label()),
configurable_globals,
)?;
}
Statement::Block(block) => {
self.compile_block(
block,
Some(labelled.label()),
use_expr,
configurable_globals,
)?;
}
stmt => self.compile_stmt(stmt, use_expr, configurable_globals)?,
},
LabelledItem::Function(f) => {
self.function(f.into(), NodeKind::Declaration, false)?;
}
}
Ok(())
}
pub(crate) fn compile_break(&mut self, node: Break) -> JsResult<()> {
let next = self.next_opcode_location();
if let Some(info) = self
.jump_info
.last()
.filter(|info| info.kind == JumpControlInfoKind::Try)
{
let in_finally = if let Some(finally_start) = info.finally_start {
next >= finally_start.index
} else {
false
};
let in_catch_no_finally = !info.has_finally && info.in_catch;
if in_finally {
self.emit_opcode(Opcode::PopIfThrown);
}
if in_finally || in_catch_no_finally {
self.emit_opcode(Opcode::CatchEnd2);
} else {
self.emit_opcode(Opcode::TryEnd);
}
self.emit(Opcode::FinallySetJump, &[u32::MAX]);
}
let label = self.jump();
if let Some(label_name) = node.label() {
let mut found = false;
for info in self.jump_info.iter_mut().rev() {
if info.label == Some(label_name) {
info.breaks.push(label);
found = true;
break;
}
}
// TODO: promote to an early error.
if !found {
return Err(JsNativeError::syntax()
.with_message(format!(
"Cannot use the undeclared label '{}'",
self.interner().resolve_expect(label_name)
))
.into());
}
} else {
self.jump_info
.last_mut()
// TODO: promote to an early error.
.ok_or_else(|| {
JsNativeError::syntax()
.with_message("unlabeled break must be inside loop or switch")
})?
.breaks
.push(label);
}
Ok(())
}
pub(crate) fn compile_switch(
&mut self,
switch: &Switch,
configurable_globals: bool,
) -> JsResult<()> {
self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
for case in switch.cases() {
self.create_decls(case.body(), configurable_globals);
}
self.emit_opcode(Opcode::LoopStart);
let start_address = self.next_opcode_location();
self.push_switch_control_info(None, start_address);
self.compile_expr(switch.val(), true)?;
let mut labels = Vec::with_capacity(switch.cases().len());
for case in switch.cases() {
self.compile_expr(case.condition(), true)?;
labels.push(self.emit_opcode_with_operand(Opcode::Case));
}
let exit = self.emit_opcode_with_operand(Opcode::Default);
for (label, case) in labels.into_iter().zip(switch.cases()) {
self.patch_jump(label);
self.compile_statement_list(case.body(), false, configurable_globals)?;
}
self.patch_jump(exit);
if let Some(body) = switch.default() {
self.create_decls(body, configurable_globals);
self.compile_statement_list(body, false, configurable_globals)?;
}
self.pop_switch_control_info();
self.emit_opcode(Opcode::LoopEnd);
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment);
Ok(())
}
pub(crate) fn compile_block(
&mut self,
block: &Block,
label: Option<Sym>,
use_expr: bool,
configurable_globals: bool,
) -> JsResult<()> {
if let Some(label) = label {
let next = self.next_opcode_location();
self.push_labelled_block_control_info(label, next);
}
self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.create_decls(block.statement_list(), configurable_globals);
self.compile_statement_list(block.statement_list(), use_expr, configurable_globals)?;
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
if label.is_some() {
self.pop_labelled_block_control_info();
}
self.emit_opcode(Opcode::PopEnvironment);
Ok(())
}
}

120
boa_engine/src/bytecompiler/statement/try.rs

@ -0,0 +1,120 @@
use boa_ast::{declaration::Binding, operations::bound_names, statement::Try};
use crate::{
bytecompiler::{ByteCompiler, Label},
vm::{BindingOpcode, Opcode},
JsResult,
};
impl ByteCompiler<'_> {
pub(crate) fn compile_try(
&mut self,
t: &Try,
use_expr: bool,
configurable_globals: bool,
) -> JsResult<()> {
self.push_try_control_info(t.finally().is_some());
let try_start = self.next_opcode_location();
self.emit(Opcode::TryStart, &[ByteCompiler::DUMMY_ADDRESS, 0]);
self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.create_decls(t.block().statement_list(), configurable_globals);
self.compile_statement_list(t.block().statement_list(), use_expr, configurable_globals)?;
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::TryEnd);
let finally = self.jump();
self.patch_jump(Label { index: try_start });
if let Some(catch) = t.catch() {
self.push_try_control_info_catch_start();
let catch_start = if t.finally().is_some() {
Some(self.emit_opcode_with_operand(Opcode::CatchStart))
} else {
None
};
self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
if let Some(binding) = catch.parameter() {
match binding {
Binding::Identifier(ident) => {
self.context.create_mutable_binding(*ident, false, false);
self.emit_binding(BindingOpcode::InitLet, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.context.create_mutable_binding(ident, false, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLet)?;
}
}
} else {
self.emit_opcode(Opcode::Pop);
}
self.create_decls(catch.block().statement_list(), configurable_globals);
self.compile_statement_list(
catch.block().statement_list(),
use_expr,
configurable_globals,
)?;
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment);
if let Some(catch_start) = catch_start {
self.emit_opcode(Opcode::CatchEnd);
self.patch_jump(catch_start);
} else {
self.emit_opcode(Opcode::CatchEnd2);
}
}
self.patch_jump(finally);
if let Some(finally) = t.finally() {
self.emit_opcode(Opcode::FinallyStart);
let finally_start_address = self.next_opcode_location();
self.push_try_control_info_finally_start(Label {
index: finally_start_address,
});
self.patch_jump_with_target(
Label {
index: try_start + 4,
},
finally_start_address,
);
self.context.push_compile_time_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.create_decls(finally.block().statement_list(), configurable_globals);
self.compile_statement_list(
finally.block().statement_list(),
false,
configurable_globals,
)?;
let (num_bindings, compile_environment) = self.context.pop_compile_time_environment();
let index_compile_environment = self.push_compile_environment(compile_environment);
self.patch_jump_with_target(push_env.0, num_bindings as u32);
self.patch_jump_with_target(push_env.1, index_compile_environment as u32);
self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::FinallyEnd);
self.pop_try_control_info(Some(finally_start_address));
} else {
self.pop_try_control_info(None);
}
Ok(())
}
}
Loading…
Cancel
Save