Browse Source

Refactor binding declarations (#2887)

pull/2901/head
raskad 2 years ago committed by GitHub
parent
commit
e3d2056f5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 487
      boa_ast/src/operations.rs
  2. 30
      boa_ast/src/statement_list.rs
  3. 3
      boa_ast/src/visitor.rs
  4. 56
      boa_engine/src/builtins/eval/mod.rs
  5. 2
      boa_engine/src/builtins/function/arguments.rs
  6. 2
      boa_engine/src/builtins/json/mod.rs
  7. 69
      boa_engine/src/bytecompiler/class.rs
  8. 917
      boa_engine/src/bytecompiler/declarations.rs
  9. 54
      boa_engine/src/bytecompiler/env.rs
  10. 92
      boa_engine/src/bytecompiler/function.rs
  11. 278
      boa_engine/src/bytecompiler/mod.rs
  12. 35
      boa_engine/src/bytecompiler/module.rs
  13. 11
      boa_engine/src/bytecompiler/statement/block.rs
  14. 6
      boa_engine/src/bytecompiler/statement/if.rs
  15. 35
      boa_engine/src/bytecompiler/statement/labelled.rs
  16. 78
      boa_engine/src/bytecompiler/statement/loop.rs
  17. 24
      boa_engine/src/bytecompiler/statement/mod.rs
  18. 19
      boa_engine/src/bytecompiler/statement/switch.rs
  19. 53
      boa_engine/src/bytecompiler/statement/try.rs
  20. 4
      boa_engine/src/bytecompiler/statement/with.rs
  21. 235
      boa_engine/src/context/mod.rs
  22. 31
      boa_engine/src/environments/compile.rs
  23. 30
      boa_engine/src/environments/runtime.rs
  24. 4
      boa_engine/src/vm/mod.rs
  25. 1
      boa_engine/src/vm/opcode/mod.rs
  26. 18
      boa_parser/src/parser/statement/block/tests.rs
  27. 2
      boa_parser/src/parser/statement/mod.rs
  28. 18
      boa_parser/src/parser/tests/mod.rs

487
boa_ast/src/operations.rs

@ -9,17 +9,20 @@ use boa_interner::{Interner, Sym};
use rustc_hash::{FxHashMap, FxHashSet};
use crate::{
declaration::{ExportDeclaration, ImportDeclaration, VarDeclaration},
declaration::{ExportDeclaration, ImportDeclaration, VarDeclaration, Variable},
expression::{access::SuperPropertyAccess, Await, Identifier, SuperCall, Yield},
function::{
ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class, ClassElement,
Function, Generator, PrivateName,
},
property::{MethodDefinition, PropertyDefinition},
statement::LabelledItem,
statement::{
iteration::{ForLoopInitializer, IterableLoopInitializer},
LabelledItem,
},
try_break,
visitor::{NodeRef, VisitWith, Visitor, VisitorMut},
Declaration, Expression, Statement, StatementList, StatementListItem,
Declaration, Expression, ModuleItem, Statement, StatementList, StatementListItem,
};
/// Represents all the possible symbols searched for by the [`Contains`][contains] operation.
@ -555,25 +558,125 @@ struct VarDeclaredNamesVisitor<'a>(&'a mut FxHashSet<Identifier>);
impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
type BreakTy = Infallible;
fn visit_expression(&mut self, _: &'ast Expression) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> {
match node {
Statement::Empty
| Statement::Expression(_)
| Statement::Continue(_)
| Statement::Break(_)
| Statement::Return(_)
| Statement::Throw(_) => ControlFlow::Continue(()),
Statement::Block(node) => self.visit(node),
Statement::Var(node) => self.visit(node),
Statement::If(node) => self.visit(node),
Statement::DoWhileLoop(node) => self.visit(node),
Statement::WhileLoop(node) => self.visit(node),
Statement::ForLoop(node) => self.visit(node),
Statement::ForInLoop(node) => self.visit(node),
Statement::ForOfLoop(node) => self.visit(node),
Statement::Switch(node) => self.visit(node),
Statement::Labelled(node) => self.visit(node),
Statement::Try(node) => self.visit(node),
Statement::With(node) => self.visit(node),
}
}
fn visit_declaration(&mut self, _: &'ast Declaration) -> ControlFlow<Self::BreakTy> {
ControlFlow::Continue(())
fn visit_statement_list_item(
&mut self,
node: &'ast StatementListItem,
) -> ControlFlow<Self::BreakTy> {
match node {
StatementListItem::Statement(stmt) => self.visit_statement(stmt),
StatementListItem::Declaration(_) => ControlFlow::Continue(()),
}
}
fn visit_var_declaration(&mut self, node: &'ast VarDeclaration) -> ControlFlow<Self::BreakTy> {
BoundNamesVisitor(self.0).visit_var_declaration(node)
fn visit_variable(&mut self, node: &'ast Variable) -> ControlFlow<Self::BreakTy> {
BoundNamesVisitor(self.0).visit_variable(node)
}
fn visit_if(&mut self, node: &'ast crate::statement::If) -> ControlFlow<Self::BreakTy> {
if let Some(node) = node.else_node() {
self.visit(node);
}
self.visit(node.body())
}
fn visit_do_while_loop(
&mut self,
node: &'ast crate::statement::DoWhileLoop,
) -> ControlFlow<Self::BreakTy> {
self.visit(node.body())
}
fn visit_while_loop(
&mut self,
node: &'ast crate::statement::WhileLoop,
) -> ControlFlow<Self::BreakTy> {
self.visit(node.body())
}
fn visit_for_loop(
&mut self,
node: &'ast crate::statement::ForLoop,
) -> ControlFlow<Self::BreakTy> {
if let Some(ForLoopInitializer::Var(node)) = node.init() {
BoundNamesVisitor(self.0).visit_var_declaration(node);
}
self.visit(node.body())
}
fn visit_for_in_loop(
&mut self,
node: &'ast crate::statement::ForInLoop,
) -> ControlFlow<Self::BreakTy> {
if let IterableLoopInitializer::Var(node) = node.initializer() {
BoundNamesVisitor(self.0).visit_variable(node);
}
self.visit(node.body())
}
fn visit_for_of_loop(
&mut self,
node: &'ast crate::statement::ForOfLoop,
) -> ControlFlow<Self::BreakTy> {
if let IterableLoopInitializer::Var(node) = node.initializer() {
BoundNamesVisitor(self.0).visit_variable(node);
}
self.visit(node.body())
}
fn visit_with(&mut self, node: &'ast crate::statement::With) -> ControlFlow<Self::BreakTy> {
self.visit(node.statement())
}
fn visit_switch(&mut self, node: &'ast crate::statement::Switch) -> ControlFlow<Self::BreakTy> {
for case in node.cases() {
self.visit(case);
}
if let Some(node) = node.default() {
self.visit(node);
}
ControlFlow::Continue(())
}
fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> {
match node {
LabelledItem::Function(_) => ControlFlow::Continue(()),
LabelledItem::Statement(stmt) => stmt.visit_with(self),
LabelledItem::Statement(stmt) => self.visit(stmt),
}
}
fn visit_try(&mut self, node: &'ast crate::statement::Try) -> ControlFlow<Self::BreakTy> {
if let Some(node) = node.finally() {
self.visit(node);
}
if let Some(node) = node.catch() {
self.visit(node.block());
}
self.visit(node.block())
}
fn visit_function(&mut self, node: &'ast Function) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
@ -594,19 +697,6 @@ impl<'ast> Visitor<'ast> for VarDeclaredNamesVisitor<'_> {
ControlFlow::Continue(())
}
fn visit_arrow_function(&mut self, node: &'ast ArrowFunction) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_async_arrow_function(
&mut self,
node: &'ast AsyncArrowFunction,
) -> ControlFlow<Self::BreakTy> {
top_level_vars(node.body(), self.0);
ControlFlow::Continue(())
}
fn visit_class_element(&mut self, node: &'ast ClassElement) -> ControlFlow<Self::BreakTy> {
if let ClassElement::StaticBlock(stmts) = node {
top_level_vars(stmts, self.0);
@ -1173,10 +1263,7 @@ where
ControlFlow::Continue(())
}
fn visit_module_item(
&mut self,
node: &'ast crate::ModuleItem,
) -> ControlFlow<Self::BreakTy> {
fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> {
match node {
crate::ModuleItem::ImportDeclaration(_)
| crate::ModuleItem::ExportDeclaration(_) => ControlFlow::Continue(()),
@ -1230,3 +1317,349 @@ where
node.visit_with(&mut visitor).is_break()
}
/// Returns a list of lexically scoped declarations of the given node.
///
/// This is equivalent to the [`LexicallyScopedDeclarations`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-lexicallyscopeddeclarations
#[must_use]
pub fn lexically_scoped_declarations<'a, N>(node: &'a N) -> Vec<Declaration>
where
&'a N: Into<NodeRef<'a>>,
{
let mut declarations = Vec::new();
LexicallyScopedDeclarationsVisitor(&mut declarations).visit(node.into());
declarations
}
/// The [`Visitor`] used to obtain the lexically scoped declarations of a node.
#[derive(Debug)]
struct LexicallyScopedDeclarationsVisitor<'a>(&'a mut Vec<Declaration>);
impl<'ast> Visitor<'ast> for LexicallyScopedDeclarationsVisitor<'_> {
type BreakTy = Infallible;
fn visit_statement_list_item(
&mut self,
node: &'ast StatementListItem,
) -> ControlFlow<Self::BreakTy> {
match node {
StatementListItem::Statement(Statement::Labelled(labelled)) => {
self.visit_labelled(labelled)
}
StatementListItem::Statement(_) => ControlFlow::Continue(()),
StatementListItem::Declaration(declaration) => {
self.0.push(declaration.clone());
ControlFlow::Continue(())
}
}
}
fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> {
match node {
LabelledItem::Function(f) => {
self.0.push(f.clone().into());
ControlFlow::Continue(())
}
LabelledItem::Statement(_) => ControlFlow::Continue(()),
}
}
fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> {
match node {
ModuleItem::StatementListItem(item) => self.visit_statement_list_item(item),
ModuleItem::ExportDeclaration(ExportDeclaration::Declaration(declaration)) => {
self.0.push(declaration.clone());
ControlFlow::Continue(())
}
ModuleItem::ExportDeclaration(ExportDeclaration::DefaultFunction(f)) => {
self.0.push(f.clone().into());
ControlFlow::Continue(())
}
ModuleItem::ExportDeclaration(ExportDeclaration::DefaultGenerator(f)) => {
self.0.push(f.clone().into());
ControlFlow::Continue(())
}
ModuleItem::ExportDeclaration(ExportDeclaration::DefaultAsyncFunction(f)) => {
self.0.push(f.clone().into());
ControlFlow::Continue(())
}
ModuleItem::ExportDeclaration(ExportDeclaration::DefaultAsyncGenerator(f)) => {
self.0.push(f.clone().into());
ControlFlow::Continue(())
}
ModuleItem::ExportDeclaration(ExportDeclaration::DefaultClassDeclaration(f)) => {
self.0.push(f.clone().into());
ControlFlow::Continue(())
}
ModuleItem::ImportDeclaration(_) | ModuleItem::ExportDeclaration(_) => {
ControlFlow::Continue(())
}
}
}
}
/// The type of a var scoped declaration.
#[derive(Clone, Debug)]
pub enum VarScopedDeclaration {
/// See [`VarDeclaration`]
VariableDeclaration(Variable),
/// See [`Function`]
Function(Function),
/// See [`Generator`]
Generator(Generator),
/// See [`AsyncFunction`]
AsyncFunction(AsyncFunction),
/// See [`AsyncGenerator`]
AsyncGenerator(AsyncGenerator),
}
impl VarScopedDeclaration {
/// Return the bound names of the declaration.
#[must_use]
pub fn bound_names(&self) -> Vec<Identifier> {
match self {
Self::VariableDeclaration(v) => bound_names(v),
Self::Function(f) => bound_names(f),
Self::Generator(g) => bound_names(g),
Self::AsyncFunction(f) => bound_names(f),
Self::AsyncGenerator(g) => bound_names(g),
}
}
}
/// Returns a list of var scoped declarations of the given node.
///
/// This is equivalent to the [`VarScopedDeclarations`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-varscopeddeclarations
#[must_use]
pub fn var_scoped_declarations<'a, N>(node: &'a N) -> Vec<VarScopedDeclaration>
where
&'a N: Into<NodeRef<'a>>,
{
let mut declarations = Vec::new();
VarScopedDeclarationsVisitor(&mut declarations).visit(node.into());
declarations
}
/// The [`Visitor`] used to obtain the var scoped declarations of a node.
#[derive(Debug)]
struct VarScopedDeclarationsVisitor<'a>(&'a mut Vec<VarScopedDeclaration>);
impl<'ast> Visitor<'ast> for VarScopedDeclarationsVisitor<'_> {
type BreakTy = Infallible;
fn visit_statement(&mut self, node: &'ast Statement) -> ControlFlow<Self::BreakTy> {
match node {
Statement::Block(s) => self.visit(s),
Statement::Var(s) => self.visit(s),
Statement::If(s) => self.visit(s),
Statement::DoWhileLoop(s) => self.visit(s),
Statement::WhileLoop(s) => self.visit(s),
Statement::ForLoop(s) => self.visit(s),
Statement::ForInLoop(s) => self.visit(s),
Statement::ForOfLoop(s) => self.visit(s),
Statement::Switch(s) => self.visit(s),
Statement::Labelled(s) => self.visit(s),
Statement::Try(s) => self.visit(s),
Statement::With(s) => self.visit(s),
Statement::Empty
| Statement::Expression(_)
| Statement::Continue(_)
| Statement::Break(_)
| Statement::Return(_)
| Statement::Throw(_) => ControlFlow::Continue(()),
}
}
fn visit_statement_list_item(
&mut self,
node: &'ast StatementListItem,
) -> ControlFlow<Self::BreakTy> {
match node {
StatementListItem::Declaration(_) => ControlFlow::Continue(()),
StatementListItem::Statement(s) => self.visit(s),
}
}
fn visit_var_declaration(&mut self, node: &'ast VarDeclaration) -> ControlFlow<Self::BreakTy> {
for var in node.0.as_ref() {
self.0
.push(VarScopedDeclaration::VariableDeclaration(var.clone()));
}
ControlFlow::Continue(())
}
fn visit_if(&mut self, node: &'ast crate::statement::If) -> ControlFlow<Self::BreakTy> {
self.visit(node.body());
if let Some(else_node) = node.else_node() {
self.visit(else_node);
}
ControlFlow::Continue(())
}
fn visit_do_while_loop(
&mut self,
node: &'ast crate::statement::DoWhileLoop,
) -> ControlFlow<Self::BreakTy> {
self.visit(node.body());
ControlFlow::Continue(())
}
fn visit_while_loop(
&mut self,
node: &'ast crate::statement::WhileLoop,
) -> ControlFlow<Self::BreakTy> {
self.visit(node.body());
ControlFlow::Continue(())
}
fn visit_for_loop(
&mut self,
node: &'ast crate::statement::ForLoop,
) -> ControlFlow<Self::BreakTy> {
if let Some(ForLoopInitializer::Var(v)) = node.init() {
self.visit(v);
}
self.visit(node.body());
ControlFlow::Continue(())
}
fn visit_for_in_loop(
&mut self,
node: &'ast crate::statement::ForInLoop,
) -> ControlFlow<Self::BreakTy> {
if let IterableLoopInitializer::Var(var) = node.initializer() {
self.0
.push(VarScopedDeclaration::VariableDeclaration(var.clone()));
}
self.visit(node.body());
ControlFlow::Continue(())
}
fn visit_for_of_loop(
&mut self,
node: &'ast crate::statement::ForOfLoop,
) -> ControlFlow<Self::BreakTy> {
if let IterableLoopInitializer::Var(var) = node.initializer() {
self.0
.push(VarScopedDeclaration::VariableDeclaration(var.clone()));
}
self.visit(node.body());
ControlFlow::Continue(())
}
fn visit_with(&mut self, node: &'ast crate::statement::With) -> ControlFlow<Self::BreakTy> {
self.visit(node.statement());
ControlFlow::Continue(())
}
fn visit_switch(&mut self, node: &'ast crate::statement::Switch) -> ControlFlow<Self::BreakTy> {
for case in node.cases() {
self.visit(case);
}
if let Some(default) = node.default() {
self.visit(default);
}
ControlFlow::Continue(())
}
fn visit_case(&mut self, node: &'ast crate::statement::Case) -> ControlFlow<Self::BreakTy> {
self.visit(node.body());
ControlFlow::Continue(())
}
fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> {
match node {
LabelledItem::Statement(s) => self.visit(s),
LabelledItem::Function(_) => ControlFlow::Continue(()),
}
}
fn visit_catch(&mut self, node: &'ast crate::statement::Catch) -> ControlFlow<Self::BreakTy> {
self.visit(node.block());
ControlFlow::Continue(())
}
fn visit_module_item(&mut self, node: &'ast ModuleItem) -> ControlFlow<Self::BreakTy> {
match node {
ModuleItem::ExportDeclaration(ExportDeclaration::VarStatement(var)) => self.visit(var),
ModuleItem::StatementListItem(item) => self.visit(item),
_ => ControlFlow::Continue(()),
}
}
}
/// Returns a list of top level var scoped declarations of the given node.
///
/// This is equivalent to the [`TopLevelVarScopedDeclarations`][spec] syntax operation in the spec.
///
/// [spec]: https://tc39.es/ecma262/#sec-static-semantics-toplevelvarscopeddeclarations
#[must_use]
pub fn top_level_var_scoped_declarations<'a, N>(node: &'a N) -> Vec<VarScopedDeclaration>
where
&'a N: Into<NodeRef<'a>>,
{
let mut declarations = Vec::new();
TopLevelVarScopedDeclarationsVisitor(&mut declarations).visit(node.into());
declarations
}
/// The [`Visitor`] used to obtain the top level var scoped declarations of a node.
#[derive(Debug)]
struct TopLevelVarScopedDeclarationsVisitor<'a>(&'a mut Vec<VarScopedDeclaration>);
impl<'ast> Visitor<'ast> for TopLevelVarScopedDeclarationsVisitor<'_> {
type BreakTy = Infallible;
fn visit_statement_list_item(
&mut self,
node: &'ast StatementListItem,
) -> ControlFlow<Self::BreakTy> {
match node {
StatementListItem::Declaration(d) => {
match d {
Declaration::Function(f) => {
self.0.push(VarScopedDeclaration::Function(f.clone()));
}
Declaration::Generator(f) => {
self.0.push(VarScopedDeclaration::Generator(f.clone()));
}
Declaration::AsyncFunction(f) => {
self.0.push(VarScopedDeclaration::AsyncFunction(f.clone()));
}
Declaration::AsyncGenerator(f) => {
self.0.push(VarScopedDeclaration::AsyncGenerator(f.clone()));
}
_ => {}
}
ControlFlow::Continue(())
}
StatementListItem::Statement(Statement::Labelled(s)) => self.visit(s),
StatementListItem::Statement(s) => {
VarScopedDeclarationsVisitor(self.0).visit(s);
ControlFlow::Continue(())
}
}
}
fn visit_labelled_item(&mut self, node: &'ast LabelledItem) -> ControlFlow<Self::BreakTy> {
match node {
LabelledItem::Statement(Statement::Labelled(s)) => self.visit(s),
LabelledItem::Statement(s) => {
VarScopedDeclarationsVisitor(self.0).visit(s);
ControlFlow::Continue(())
}
LabelledItem::Function(f) => {
self.0.push(VarScopedDeclaration::Function(f.clone()));
ControlFlow::Continue(())
}
}
}
}

30
boa_ast/src/statement_list.rs

@ -9,8 +9,6 @@ use crate::{
use boa_interner::{Interner, ToIndentedString};
use core::ops::ControlFlow;
use std::cmp::Ordering;
/// An item inside a [`StatementList`] Parse Node, as defined by the [spec].
///
/// Items in a `StatementList` can be either [`Declaration`]s (functions, classes, let/const declarations)
@ -27,34 +25,6 @@ pub enum StatementListItem {
Declaration(Declaration),
}
impl StatementListItem {
/// Returns a node ordering based on the hoistability of each statement.
#[must_use]
pub const fn hoistable_order(a: &Self, b: &Self) -> Ordering {
match (a, b) {
(
_,
Self::Declaration(
Declaration::Function(_)
| Declaration::Generator(_)
| Declaration::AsyncFunction(_)
| Declaration::AsyncGenerator(_),
),
) => Ordering::Greater,
(
Self::Declaration(
Declaration::Function(_)
| Declaration::Generator(_)
| Declaration::AsyncFunction(_)
| Declaration::AsyncGenerator(_),
),
_,
) => Ordering::Less,
(_, _) => Ordering::Equal,
}
}
}
impl ToIndentedString for StatementListItem {
/// Creates a string of the value of the node with the given indentation. For example, an
/// indent level of 2 would produce this:

3
boa_ast/src/visitor.rs

@ -142,6 +142,7 @@ node_ref! {
Break,
Return,
Labelled,
With,
Throw,
Try,
Identifier,
@ -336,6 +337,7 @@ pub trait Visitor<'ast>: Sized {
NodeRef::Break(n) => self.visit_break(n),
NodeRef::Return(n) => self.visit_return(n),
NodeRef::Labelled(n) => self.visit_labelled(n),
NodeRef::With(n) => self.visit_with(n),
NodeRef::Throw(n) => self.visit_throw(n),
NodeRef::Try(n) => self.visit_try(n),
NodeRef::Identifier(n) => self.visit_identifier(n),
@ -532,6 +534,7 @@ pub trait VisitorMut<'ast>: Sized {
NodeRefMut::Break(n) => self.visit_break_mut(n),
NodeRefMut::Return(n) => self.visit_return_mut(n),
NodeRefMut::Labelled(n) => self.visit_labelled_mut(n),
NodeRefMut::With(n) => self.visit_with_mut(n),
NodeRefMut::Throw(n) => self.visit_throw_mut(n),
NodeRefMut::Try(n) => self.visit_try_mut(n),
NodeRefMut::Identifier(n) => self.visit_identifier_mut(n),

56
boa_engine/src/builtins/eval/mod.rs

@ -11,12 +11,10 @@
use crate::{
builtins::BuiltInObject, bytecompiler::ByteCompiler, context::intrinsics::Intrinsics,
environments::Environment, error::JsNativeError, object::JsObject, realm::Realm, Context,
JsArgs, JsResult, JsString, JsValue,
};
use boa_ast::operations::{
contains, contains_arguments, top_level_var_declared_names, ContainsSymbol,
environments::Environment, error::JsNativeError, object::JsObject, realm::Realm, vm::Opcode,
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_ast::operations::{contains, contains_arguments, ContainsSymbol};
use boa_gc::Gc;
use boa_interner::Sym;
use boa_parser::{Parser, Source};
@ -224,35 +222,27 @@ impl Eval {
}
});
// Only need to check on non-strict mode since strict mode automatically creates a function
// environment for all eval calls.
if !strict {
// Error if any var declaration in the eval code already exists as a let/const declaration in the current running environment.
if let Some(name) = context
.vm
.environments
.has_lex_binding_until_function_environment(&top_level_var_declared_names(&body))
{
let name = context.interner().resolve_expect(name.sym());
let msg = format!("variable declaration {name} in eval function already exists as a lexical variable");
return Err(JsNativeError::syntax().with_message(msg).into());
}
}
let mut compiler = ByteCompiler::new(
Sym::MAIN,
body.strict(),
false,
context.vm.environments.current_compile_environment(),
context,
);
compiler.push_compile_environment(strict);
let push_env = compiler.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
compiler.eval_declaration_instantiation(&body, strict)?;
compiler.compile_statement_list(&body, true);
let env_info = compiler.pop_compile_environment();
compiler.patch_jump_with_target(push_env.0, env_info.num_bindings as u32);
compiler.patch_jump_with_target(push_env.1, env_info.index as u32);
compiler.emit_opcode(Opcode::PopEnvironment);
let code_block = Gc::new(compiler.finish());
// TODO: check if private identifiers inside `eval` are valid.
// Compile and execute the eval statement list.
let code_block = {
let mut compiler = ByteCompiler::new(
Sym::MAIN,
body.strict(),
false,
context.vm.environments.current_compile_environment(),
context,
);
compiler.compile_statement_list_with_new_declarative(&body, true, strict);
Gc::new(compiler.finish())
};
// Indirect calls don't need extensions, because a non-strict indirect call modifies only
// the global object.
// Strict direct calls also don't need extensions, since all strict eval calls push a new

2
boa_engine/src/builtins/function/arguments.rs

@ -176,7 +176,7 @@ impl Arguments {
if property_index >= len {
break;
}
let binding_index = bindings.len() + 1;
let binding_index = bindings.len();
let entry = bindings
.entry(name)
.or_insert((binding_index, property_index));

2
boa_engine/src/builtins/json/mod.rs

@ -123,7 +123,7 @@ impl Json {
context.realm().environment().compile_env(),
context,
);
compiler.compile_statement_list(&statement_list, true, false);
compiler.compile_statement_list(&statement_list, true);
Gc::new(compiler.finish())
};
let unfiltered = context.execute(code_block)?;

69
boa_engine/src/bytecompiler/class.rs

@ -1,10 +1,8 @@
use super::{ByteCompiler, Literal, NodeKind};
use crate::vm::{BindingOpcode, Opcode};
use boa_ast::{
declaration::Binding,
expression::Identifier,
function::{Class, ClassElement},
operations::bound_names,
function::{Class, ClassElement, FormalParameterList},
property::{MethodDefinition, PropertyName},
};
use boa_gc::Gc;
@ -40,51 +38,22 @@ impl ByteCompiler<'_, '_> {
if let Some(expr) = class.constructor() {
compiler.length = expr.parameters().length();
compiler.params = expr.parameters().clone();
compiler.create_mutable_binding(Sym::ARGUMENTS.into(), false, false);
compiler.arguments_binding =
Some(compiler.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.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.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.num_bindings = compiler.current_environment.borrow().num_bindings();
compiler.push_compile_environment(true);
compiler.function_environment_push_location = compiler.next_opcode_location();
Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment))
} else {
None
};
compiler.compile_statement_list(expr.body(), false, false);
let (env_labels, _) = compiler.function_declaration_instantiation(
expr.body(),
expr.parameters(),
false,
true,
false,
);
compiler.compile_statement_list(expr.body(), false);
let env_info = compiler.pop_compile_environment();
if let Some(env_label) = env_label {
compiler.patch_jump_with_target(env_label.0, env_info.num_bindings as u32);
compiler.patch_jump_with_target(env_label.1, env_info.index as u32);
if let Some(env_labels) = env_labels {
compiler.patch_jump_with_target(env_labels.0, env_info.num_bindings as u32);
compiler.patch_jump_with_target(env_labels.1, env_info.index as u32);
compiler.pop_compile_environment();
} else {
compiler.num_bindings = env_info.num_bindings;
@ -396,8 +365,16 @@ impl ByteCompiler<'_, '_> {
compiler.push_compile_environment(false);
compiler.create_immutable_binding(class_name.into(), true);
compiler.push_compile_environment(true);
compiler.create_declarations(statement_list, false);
compiler.compile_statement_list(statement_list, false, false);
compiler.function_declaration_instantiation(
statement_list,
&FormalParameterList::default(),
false,
true,
false,
);
compiler.compile_statement_list(statement_list, false);
let env_info = compiler.pop_compile_environment();
compiler.pop_compile_environment();
compiler.num_bindings = env_info.num_bindings;

917
boa_engine/src/bytecompiler/declarations.rs

@ -0,0 +1,917 @@
use crate::{
bytecompiler::{ByteCompiler, FunctionCompiler, FunctionSpec, Label, NodeKind},
vm::{create_function_object_fast, create_generator_function_object, BindingOpcode, Opcode},
JsNativeError, JsResult,
};
use boa_ast::{
declaration::{Binding, LexicalDeclaration, VariableList},
function::FormalParameterList,
operations::{
bound_names, lexically_scoped_declarations, top_level_lexically_declared_names,
top_level_var_declared_names, top_level_var_scoped_declarations, VarScopedDeclaration,
},
visitor::NodeRef,
Declaration, StatementList, StatementListItem,
};
use boa_interner::Sym;
impl ByteCompiler<'_, '_> {
/// `GlobalDeclarationInstantiation ( script, env )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
pub(crate) fn global_declaration_instantiation(
&mut self,
script: &StatementList,
) -> JsResult<()> {
// Note: Step 1-4 Are currently handled while parsing.
// 1. Let lexNames be the LexicallyDeclaredNames of script.
// 2. Let varNames be the VarDeclaredNames of script.
// 3. For each element name of lexNames, do
// a. If env.HasVarDeclaration(name) is true, throw a SyntaxError exception.
// b. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
// c. Let hasRestrictedGlobal be ? env.HasRestrictedGlobalProperty(name).
// d. If hasRestrictedGlobal is true, throw a SyntaxError exception.
// 4. For each element name of varNames, do
// a. If env.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
// 5. Let varDeclarations be the VarScopedDeclarations of script.
// Note: VarScopedDeclarations for a Script node is TopLevelVarScopedDeclarations.
let var_declarations = top_level_var_scoped_declarations(script);
// 6. Let functionsToInitialize be a new empty List.
let mut functions_to_initialize = Vec::new();
// 7. Let declaredFunctionNames be a new empty List.
let mut declared_function_names = Vec::new();
// 8. For each element d of varDeclarations, in reverse List order, do
for declaration in var_declarations.iter().rev() {
// a. If d is not either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then
// a.i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration.
// a.ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used.
let name = match declaration {
VarScopedDeclaration::Function(f) => f.name(),
VarScopedDeclaration::Generator(f) => f.name(),
VarScopedDeclaration::AsyncFunction(f) => f.name(),
VarScopedDeclaration::AsyncGenerator(f) => f.name(),
VarScopedDeclaration::VariableDeclaration(_) => {
continue;
}
};
// a.iii. Let fn be the sole element of the BoundNames of d.
let name = name.expect("function declaration must have a name");
// a.iv. If declaredFunctionNames does not contain fn, then
if !declared_function_names.contains(&name) {
// 1. Let fnDefinable be ? env.CanDeclareGlobalFunction(fn).
let fn_definable = self.context.can_declare_global_function(name)?;
// 2. If fnDefinable is false, throw a TypeError exception.
if !fn_definable {
return Err(JsNativeError::typ()
.with_message("cannot declare global function")
.into());
}
// 3. Append fn to declaredFunctionNames.
declared_function_names.push(name);
// 4. Insert d as the first element of functionsToInitialize.
functions_to_initialize.push(declaration.clone());
}
}
functions_to_initialize.reverse();
// 9. Let declaredVarNames be a new empty List.
let mut declared_var_names = Vec::new();
// 10. For each element d of varDeclarations, do
// a. If d is either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then
for declaration in var_declarations {
let VarScopedDeclaration::VariableDeclaration(declaration) = declaration else {
continue;
};
// i. For each String vn of the BoundNames of d, do
for name in bound_names(&declaration) {
// 1. If declaredFunctionNames does not contain vn, then
if !declared_function_names.contains(&name) {
// a. Let vnDefinable be ? env.CanDeclareGlobalVar(vn).
let definable = self.context.can_declare_global_var(name)?;
// b. If vnDefinable is false, throw a TypeError exception.
if !definable {
return Err(JsNativeError::typ()
.with_message("cannot declare global variable")
.into());
}
// c. If declaredVarNames does not contain vn, then
if !declared_var_names.contains(&name) {
// i. Append vn to declaredVarNames.
declared_var_names.push(name);
}
}
}
}
// 11. NOTE: No abnormal terminations occur after this algorithm step if the global object is an ordinary object.
// However, if the global object is a Proxy exotic object it may exhibit behaviours
// that cause abnormal terminations in some of the following steps.
// 12. NOTE: Annex B.3.2.2 adds additional steps at this point.
// TODO: Support B.3.2.2.
// 13. Let lexDeclarations be the LexicallyScopedDeclarations of script.
// 14. Let privateEnv be null.
// 15. For each element d of lexDeclarations, do
for statement in script.statements() {
// a. NOTE: Lexically declared names are only instantiated here but not initialized.
// b. For each element dn of the BoundNames of d, do
// i. If IsConstantDeclaration of d is true, then
// 1. Perform ? env.CreateImmutableBinding(dn, true).
// ii. Else,
// 1. Perform ? env.CreateMutableBinding(dn, false).
if let StatementListItem::Declaration(declaration) = statement {
match declaration {
Declaration::Class(class) => {
for name in bound_names(class) {
self.create_mutable_binding(name, false);
}
}
Declaration::Lexical(LexicalDeclaration::Let(declaration)) => {
for name in bound_names(declaration) {
self.create_mutable_binding(name, false);
}
}
Declaration::Lexical(LexicalDeclaration::Const(declaration)) => {
for name in bound_names(declaration) {
self.create_immutable_binding(name, true);
}
}
_ => {}
}
}
}
// 16. For each Parse Node f of functionsToInitialize, do
for function in functions_to_initialize {
// a. Let fn be the sole element of the BoundNames of f.
let (name, generator, r#async, parameters, body) = match &function {
VarScopedDeclaration::Function(f) => {
(f.name(), false, false, f.parameters(), f.body())
}
VarScopedDeclaration::Generator(f) => {
(f.name(), true, false, f.parameters(), f.body())
}
VarScopedDeclaration::AsyncFunction(f) => {
(f.name(), false, true, f.parameters(), f.body())
}
VarScopedDeclaration::AsyncGenerator(f) => {
(f.name(), true, true, f.parameters(), f.body())
}
VarScopedDeclaration::VariableDeclaration(_) => {
continue;
}
};
let name = name.expect("function declaration must have a name");
let code = FunctionCompiler::new()
.name(name.sym())
.generator(generator)
.r#async(r#async)
.strict(self.strict)
.binding_identifier(Some(name.sym()))
.compile(
parameters,
body,
self.current_environment.clone(),
self.context,
);
// b. Let fo be InstantiateFunctionObject of f with arguments env and privateEnv.
let function = if generator {
create_generator_function_object(code, r#async, false, None, self.context)
} else {
create_function_object_fast(code, r#async, false, false, self.context)
};
// c. Perform ? env.CreateGlobalFunctionBinding(fn, fo, false).
self.context
.create_global_function_binding(name, function, false)?;
}
// 17. For each String vn of declaredVarNames, do
for var in declared_var_names {
// a. Perform ? env.CreateGlobalVarBinding(vn, false).
self.context.create_global_var_binding(var, false)?;
}
// 18. Return unused.
Ok(())
}
/// `BlockDeclarationInstantiation ( code, env )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-blockdeclarationinstantiation
pub(crate) fn block_declaration_instantiation<'a, N>(&mut self, block: &'a N)
where
&'a N: Into<NodeRef<'a>>,
{
// 1. Let declarations be the LexicallyScopedDeclarations of code.
let declarations = lexically_scoped_declarations(block);
// 2. Let privateEnv be the running execution context's PrivateEnvironment.
// Note: Private environments are currently handled differently.
// 3. For each element d of declarations, do
for d in &declarations {
// i. If IsConstantDeclaration of d is true, then
if let Declaration::Lexical(LexicalDeclaration::Const(d)) = d {
// a. For each element dn of the BoundNames of d, do
for dn in bound_names::<'_, VariableList>(d) {
// 1. Perform ! env.CreateImmutableBinding(dn, true).
self.create_immutable_binding(dn, true);
}
}
// ii. Else,
else {
// a. For each element dn of the BoundNames of d, do
for dn in bound_names::<'_, Declaration>(d) {
// 1. Perform ! env.CreateMutableBinding(dn, false). NOTE: This step is replaced in section B.3.2.6.
// TODO: Support B.3.2.6.
self.create_mutable_binding(dn, false);
}
}
}
// Note: Not sure if the spec is wrong here or if our implementation just differs too much,
// but we need 3.a to be finished for all declarations before 3.b can be done.
// b. If d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then
// i. Let fn be the sole element of the BoundNames of d.
// ii. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv.
// iii. Perform ! env.InitializeBinding(fn, fo). NOTE: This step is replaced in section B.3.2.6.
// TODO: Support B.3.2.6.
for d in &declarations {
match d {
Declaration::Function(function) => {
self.function(function.into(), NodeKind::Declaration, false);
}
Declaration::Generator(function) => {
self.function(function.into(), NodeKind::Declaration, false);
}
Declaration::AsyncFunction(function) => {
self.function(function.into(), NodeKind::Declaration, false);
}
Declaration::AsyncGenerator(function) => {
self.function(function.into(), NodeKind::Declaration, false);
}
_ => {}
}
}
// 4. Return unused.
}
/// `EvalDeclarationInstantiation ( body, varEnv, lexEnv, privateEnv, strict )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-evaldeclarationinstantiation
pub(crate) fn eval_declaration_instantiation(
&mut self,
body: &StatementList,
strict: bool,
) -> JsResult<()> {
let var_environment_is_global = self
.context
.vm
.environments
.is_next_outer_function_environment_global()
&& !strict;
// 2. Let varDeclarations be the VarScopedDeclarations of body.
let var_declarations = top_level_var_scoped_declarations(body);
// 3. If strict is false, then
if !strict {
// 1. Let varNames be the VarDeclaredNames of body.
let var_names = top_level_var_declared_names(body);
// a. If varEnv is a Global Environment Record, then
// i. For each element name of varNames, do
// 1. If varEnv.HasLexicalDeclaration(name) is true, throw a SyntaxError exception.
// 2. NOTE: eval will not create a global var declaration that would be shadowed by a global lexical declaration.
// b. Let thisEnv be lexEnv.
// c. Assert: The following loop will terminate.
// d. Repeat, while thisEnv is not varEnv,
// i. If thisEnv is not an Object Environment Record, then
// 1. NOTE: The environment of with statements cannot contain any lexical declaration so it doesn't need to be checked for var/let hoisting conflicts.
// 2. For each element name of varNames, do
// a. If ! thisEnv.HasBinding(name) is true, then
// i. Throw a SyntaxError exception.
// ii. NOTE: Annex B.3.4 defines alternate semantics for the above step.
// b. NOTE: A direct eval will not hoist var declaration over a like-named lexical declaration.
// ii. Set thisEnv to thisEnv.[[OuterEnv]].
if let Some(name) = self
.context
.vm
.environments
.has_lex_binding_until_function_environment(&var_names)
{
let name = self.context.interner().resolve_expect(name.sym());
let msg = format!("variable declaration {name} in eval function already exists as a lexical variable");
return Err(JsNativeError::syntax().with_message(msg).into());
}
}
// 4. Let privateIdentifiers be a new empty List.
// 5. Let pointer be privateEnv.
// 6. Repeat, while pointer is not null,
// a. For each Private Name binding of pointer.[[Names]], do
// i. If privateIdentifiers does not contain binding.[[Description]], append binding.[[Description]] to privateIdentifiers.
// b. Set pointer to pointer.[[OuterPrivateEnvironment]].
// 7. If AllPrivateIdentifiersValid of body with argument privateIdentifiers is false, throw a SyntaxError exception.
// 8. Let functionsToInitialize be a new empty List.
let mut functions_to_initialize = Vec::new();
// 9. Let declaredFunctionNames be a new empty List.
let mut declared_function_names = Vec::new();
// 10. For each element d of varDeclarations, in reverse List order, do
for declaration in var_declarations.iter().rev() {
// a. If d is not either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then
// a.i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration.
// a.ii. NOTE: If there are multiple function declarations for the same name, the last declaration is used.
let name = match &declaration {
VarScopedDeclaration::Function(f) => f.name(),
VarScopedDeclaration::Generator(f) => f.name(),
VarScopedDeclaration::AsyncFunction(f) => f.name(),
VarScopedDeclaration::AsyncGenerator(f) => f.name(),
VarScopedDeclaration::VariableDeclaration(_) => {
continue;
}
};
// a.iii. Let fn be the sole element of the BoundNames of d.
let name = name.expect("function declaration must have a name");
// a.iv. If declaredFunctionNames does not contain fn, then
if !declared_function_names.contains(&name) {
// 1. If varEnv is a Global Environment Record, then
if var_environment_is_global {
// a. Let fnDefinable be ? varEnv.CanDeclareGlobalFunction(fn).
let fn_definable = self.context.can_declare_global_function(name)?;
// b. If fnDefinable is false, throw a TypeError exception.
if !fn_definable {
return Err(JsNativeError::typ()
.with_message("cannot declare global function")
.into());
}
}
// 2. Append fn to declaredFunctionNames.
declared_function_names.push(name);
// 3. Insert d as the first element of functionsToInitialize.
functions_to_initialize.push(declaration.clone());
}
}
functions_to_initialize.reverse();
// 11. NOTE: Annex B.3.2.3 adds additional steps at this point.
// TODO: Support B.3.2.3
// 12. Let declaredVarNames be a new empty List.
let mut declared_var_names = Vec::new();
// 13. For each element d of varDeclarations, do
for declaration in var_declarations {
// a. If d is either a VariableDeclaration, a ForBinding, or a BindingIdentifier, then
let VarScopedDeclaration::VariableDeclaration(declaration) = declaration else {
continue;
};
// a.i. For each String vn of the BoundNames of d, do
for name in bound_names(&declaration) {
// 1. If declaredFunctionNames does not contain vn, then
if !declared_function_names.contains(&name) {
// a. If varEnv is a Global Environment Record, then
if var_environment_is_global {
// i. Let vnDefinable be ? varEnv.CanDeclareGlobalVar(vn).
let vn_definable = self.context.can_declare_global_var(name)?;
// ii. If vnDefinable is false, throw a TypeError exception.
if !vn_definable {
return Err(JsNativeError::typ()
.with_message("cannot declare global variable")
.into());
}
}
// b. If declaredVarNames does not contain vn, then
if !declared_var_names.contains(&name) {
// i. Append vn to declaredVarNames.
declared_var_names.push(name);
}
}
}
}
// 14. NOTE: No abnormal terminations occur after this algorithm step unless varEnv is a
// Global Environment Record and the global object is a Proxy exotic object.
// 15. Let lexDeclarations be the LexicallyScopedDeclarations of body.
// 16. For each element d of lexDeclarations, do
for statement in body.statements() {
// a. NOTE: Lexically declared names are only instantiated here but not initialized.
// b. For each element dn of the BoundNames of d, do
// i. If IsConstantDeclaration of d is true, then
// 1. Perform ? lexEnv.CreateImmutableBinding(dn, true).
// ii. Else,
// 1. Perform ? lexEnv.CreateMutableBinding(dn, false).
if let StatementListItem::Declaration(declaration) = statement {
match declaration {
Declaration::Class(class) => {
for name in bound_names(class) {
self.create_mutable_binding(name, false);
}
}
Declaration::Lexical(LexicalDeclaration::Let(declaration)) => {
for name in bound_names(declaration) {
self.create_mutable_binding(name, false);
}
}
Declaration::Lexical(LexicalDeclaration::Const(declaration)) => {
for name in bound_names(declaration) {
self.create_immutable_binding(name, true);
}
}
_ => {}
}
}
}
// 17. For each Parse Node f of functionsToInitialize, do
for function in functions_to_initialize {
// a. Let fn be the sole element of the BoundNames of f.
let (name, generator, r#async, parameters, body) = match &function {
VarScopedDeclaration::Function(f) => {
(f.name(), false, false, f.parameters(), f.body())
}
VarScopedDeclaration::Generator(f) => {
(f.name(), true, false, f.parameters(), f.body())
}
VarScopedDeclaration::AsyncFunction(f) => {
(f.name(), false, true, f.parameters(), f.body())
}
VarScopedDeclaration::AsyncGenerator(f) => {
(f.name(), true, true, f.parameters(), f.body())
}
VarScopedDeclaration::VariableDeclaration(_) => {
continue;
}
};
let name = name.expect("function declaration must have a name");
let code = FunctionCompiler::new()
.name(name.sym())
.generator(generator)
.r#async(r#async)
.strict(self.strict)
.binding_identifier(Some(name.sym()))
.compile(
parameters,
body,
self.context.vm.environments.current_compile_environment(),
self.context,
);
// c. If varEnv is a Global Environment Record, then
if var_environment_is_global {
// b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv.
let function = if generator {
create_generator_function_object(code, r#async, false, None, self.context)
} else {
create_function_object_fast(code, r#async, false, false, self.context)
};
// i. Perform ? varEnv.CreateGlobalFunctionBinding(fn, fo, true).
self.context
.create_global_function_binding(name, function, true)?;
}
// d. Else,
else {
// b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv.
let index = self.functions.len() as u32;
self.functions.push(code);
if r#async && generator {
self.emit(Opcode::GetGeneratorAsync, &[index]);
} else if generator {
self.emit(Opcode::GetGenerator, &[index]);
} else if r#async {
self.emit(Opcode::GetFunctionAsync, &[index]);
} else {
self.emit(Opcode::GetFunction, &[index]);
}
self.emit_u8(0);
// i. Let bindingExists be ! varEnv.HasBinding(fn).
let binding_exists = self.has_binding_eval(name, strict);
// ii. If bindingExists is false, then
// iii. Else,
if binding_exists {
// 1. Perform ! varEnv.SetMutableBinding(fn, fo, false).
let binding = self.set_mutable_binding(name);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::SetName, &[index]);
} else {
// 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14.
// 2. Perform ! varEnv.CreateMutableBinding(fn, true).
// 3. Perform ! varEnv.InitializeBinding(fn, fo).
self.create_mutable_binding(name, !strict);
let binding = self.initialize_mutable_binding(name, !strict);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::DefInitVar, &[index]);
}
}
}
// 18. For each String vn of declaredVarNames, do
for name in declared_var_names {
// a. If varEnv is a Global Environment Record, then
if var_environment_is_global {
// i. Perform ? varEnv.CreateGlobalVarBinding(vn, true).
self.context.create_global_var_binding(name, true)?;
}
// b. Else,
else {
// i. Let bindingExists be ! varEnv.HasBinding(vn).
let binding_exists = self.has_binding_eval(name, strict);
// ii. If bindingExists is false, then
if !binding_exists {
// 1. NOTE: The following invocation cannot return an abrupt completion because of the validation preceding step 14.
// 2. Perform ! varEnv.CreateMutableBinding(vn, true).
// 3. Perform ! varEnv.InitializeBinding(vn, undefined).
self.create_mutable_binding(name, !strict);
let binding = self.initialize_mutable_binding(name, !strict);
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined);
self.emit(Opcode::DefInitVar, &[index]);
}
}
}
// 19. Return unused.
Ok(())
}
/// `FunctionDeclarationInstantiation ( func, argumentsList )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
pub(crate) fn function_declaration_instantiation(
&mut self,
code: &StatementList,
formals: &FormalParameterList,
arrow: bool,
strict: bool,
generator: bool,
) -> (Option<(Label, Label)>, bool) {
let mut env_labels = None;
let mut additional_env = false;
// 1. Let calleeContext be the running execution context.
// 2. Let code be func.[[ECMAScriptCode]].
// 3. Let strict be func.[[Strict]].
// 4. Let formals be func.[[FormalParameters]].
// 5. Let parameterNames be the BoundNames of formals.
let mut parameter_names = bound_names(formals);
// 6. If parameterNames has any duplicate entries, let hasDuplicates be true. Otherwise, let hasDuplicates be false.
// let has_duplicates = formals.has_duplicates();
// 7. Let simpleParameterList be IsSimpleParameterList of formals.
// let simple_parameter_list = formals.is_simple();
// 8. Let hasParameterExpressions be ContainsExpression of formals.
let has_parameter_expressions = formals.has_expressions();
// 9. Let varNames be the VarDeclaredNames of code.
let var_names = top_level_var_declared_names(code);
// 10. Let varDeclarations be the VarScopedDeclarations of code.
let var_declarations = top_level_var_scoped_declarations(code);
// 11. Let lexicalNames be the LexicallyDeclaredNames of code.
let lexical_names = top_level_lexically_declared_names(code);
// 12. Let functionNames be a new empty List.
let mut function_names = Vec::new();
// 13. Let functionsToInitialize be a new empty List.
let mut functions_to_initialize = Vec::new();
// 14. For each element d of varDeclarations, in reverse List order, do
for declaration in var_declarations.iter().rev() {
// a. If d is neither a VariableDeclaration nor a ForBinding nor a BindingIdentifier, then
// a.i. Assert: d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration.
let function = match declaration {
VarScopedDeclaration::Function(f) => FunctionSpec::from(f),
VarScopedDeclaration::Generator(f) => FunctionSpec::from(f),
VarScopedDeclaration::AsyncFunction(f) => FunctionSpec::from(f),
VarScopedDeclaration::AsyncGenerator(f) => FunctionSpec::from(f),
VarScopedDeclaration::VariableDeclaration(_) => continue,
};
// a.ii. Let fn be the sole element of the BoundNames of d.
let name = function
.name
.expect("function declaration must have a name");
// a.iii. If functionNames does not contain fn, then
if !function_names.contains(&name) {
// 1. Insert fn as the first element of functionNames.
function_names.push(name);
// 2. NOTE: If there are multiple function declarations for the same name, the last declaration is used.
// 3. Insert d as the first element of functionsToInitialize.
functions_to_initialize.push(function);
}
}
function_names.reverse();
functions_to_initialize.reverse();
//15. Let argumentsObjectNeeded be true.
let mut arguments_object_needed = true;
let arguments = Sym::ARGUMENTS.into();
// 16. If func.[[ThisMode]] is lexical, then
// 17. Else if parameterNames contains "arguments", then
if arrow || parameter_names.contains(&arguments) {
// 16.a. NOTE: Arrow functions never have an arguments object.
// 16.b. Set argumentsObjectNeeded to false.
// 17.a. Set argumentsObjectNeeded to false.
arguments_object_needed = false;
}
// 18. Else if hasParameterExpressions is false, then
else if !has_parameter_expressions {
//a. If functionNames contains "arguments" or lexicalNames contains "arguments", then
if function_names.contains(&arguments) || lexical_names.contains(&arguments) {
// i. Set argumentsObjectNeeded to false.
arguments_object_needed = false;
}
}
// 19. If strict is true or hasParameterExpressions is false, then
// a. NOTE: Only a single Environment Record is needed for the parameters,
// since calls to eval in strict mode code cannot create new bindings which are visible outside of the eval.
// b. Let env be the LexicalEnvironment of calleeContext.
// 20. Else,
if !strict && has_parameter_expressions {
// a. NOTE: A separate Environment Record is needed to ensure that bindings created by
// direct eval calls in the formal parameter list are outside the environment where parameters are declared.
// b. Let calleeEnv be the LexicalEnvironment of calleeContext.
// c. Let env be NewDeclarativeEnvironment(calleeEnv).
// d. Assert: The VariableEnvironment of calleeContext is calleeEnv.
// e. Set the LexicalEnvironment of calleeContext to env.
self.push_compile_environment(false);
additional_env = true;
}
// 21. For each String paramName of parameterNames, do
for param_name in &parameter_names {
// a. Let alreadyDeclared be ! env.HasBinding(paramName).
let already_declared = self.has_binding(*param_name);
// b. NOTE: Early errors ensure that duplicate parameter names can only occur in non-strict
// functions that do not have parameter default values or rest parameters.
// c. If alreadyDeclared is false, then
if !already_declared {
// i. Perform ! env.CreateMutableBinding(paramName, false).
self.create_mutable_binding(*param_name, false);
// Note: These steps are not necessary in our implementation.
// ii. If hasDuplicates is true, then
// 1. Perform ! env.InitializeBinding(paramName, undefined).
}
}
// 22. If argumentsObjectNeeded is true, then
if arguments_object_needed {
// Note: This happens at runtime.
// a. If strict is true or simpleParameterList is false, then
// i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
// b. Else,
// i. NOTE: A mapped argument object is only provided for non-strict functions that don't have a rest parameter, any parameter default value initializers, or any destructured parameters.
// ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env).
// c. If strict is true, then
if strict {
// i. Perform ! env.CreateImmutableBinding("arguments", false).
// ii. NOTE: In strict mode code early errors prevent attempting to assign to this binding, so its mutability is not observable.
self.create_immutable_binding(arguments, false);
self.arguments_binding = Some(self.initialize_immutable_binding(arguments));
}
// d. Else,
else {
// i. Perform ! env.CreateMutableBinding("arguments", false).
self.create_mutable_binding(arguments, false);
self.arguments_binding = Some(self.initialize_mutable_binding(arguments, false));
}
// Note: This happens at runtime.
// e. Perform ! env.InitializeBinding("arguments", ao).
// f. Let parameterBindings be the list-concatenation of parameterNames and « "arguments" ».
parameter_names.push(arguments);
}
// 23. Else,
// a. Let parameterBindings be parameterNames.
let parameter_bindings = parameter_names;
// 24. Let iteratorRecord be CreateListIteratorRecord(argumentsList).
// 25. If hasDuplicates is true, then
// a. Perform ? IteratorBindingInitialization of formals with arguments iteratorRecord and undefined.
// 26. Else,
// a. Perform ? IteratorBindingInitialization of formals with arguments iteratorRecord and env.
for parameter in formals.as_ref() {
if parameter.is_rest_param() {
self.emit_opcode(Opcode::RestParameterInit);
}
match parameter.variable().binding() {
Binding::Identifier(ident) => {
self.create_mutable_binding(*ident, false);
if let Some(init) = parameter.variable().init() {
let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true);
self.patch_jump(skip);
}
self.emit_binding(BindingOpcode::InitLet, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.create_mutable_binding(ident, false);
}
if let Some(init) = parameter.variable().init() {
let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true);
self.patch_jump(skip);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLet);
}
}
}
if !formals.has_rest_parameter() {
self.emit_opcode(Opcode::RestParameterPop);
}
if generator {
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Yield);
}
// 27. If hasParameterExpressions is false, then
// 28. Else,
if has_parameter_expressions {
// a. NOTE: A separate Environment Record is needed to ensure that closures created by expressions in the formal parameter list do not have visibility of declarations in the function body.
// b. Let varEnv be NewDeclarativeEnvironment(env).
// c. Set the VariableEnvironment of calleeContext to varEnv.
self.push_compile_environment(true);
self.function_environment_push_location = self.next_opcode_location();
env_labels = Some(self.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment));
// d. Let instantiatedVarNames be a new empty List.
let mut instantiated_var_names = Vec::new();
// e. For each element n of varNames, do
for n in var_names {
// i. If instantiatedVarNames does not contain n, then
if !instantiated_var_names.contains(&n) {
// 1. Append n to instantiatedVarNames.
instantiated_var_names.push(n);
// 2. Perform ! varEnv.CreateMutableBinding(n, false).
self.create_mutable_binding(n, true);
// 3. If parameterBindings does not contain n, or if functionNames contains n, then
if !parameter_bindings.contains(&n) || function_names.contains(&n) {
// a. Let initialValue be undefined.
self.emit_opcode(Opcode::PushUndefined);
}
// 4. Else,
else {
// a. Let initialValue be ! env.GetBindingValue(n, false).
let binding = self.get_binding_value(n);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::GetName, &[index]);
}
// 5. Perform ! varEnv.InitializeBinding(n, initialValue).
let binding = self.initialize_mutable_binding(n, true);
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined);
self.emit(Opcode::DefInitVar, &[index]);
// 6. NOTE: A var with the same name as a formal parameter initially has
// the same value as the corresponding initialized parameter.
}
}
} else {
// a. NOTE: Only a single Environment Record is needed for the parameters and top-level vars.
// b. Let instantiatedVarNames be a copy of the List parameterBindings.
let mut instantiated_var_names = parameter_bindings;
// c. For each element n of varNames, do
for n in var_names {
// i. If instantiatedVarNames does not contain n, then
if !instantiated_var_names.contains(&n) {
// 1. Append n to instantiatedVarNames.
instantiated_var_names.push(n);
// 2. Perform ! env.CreateMutableBinding(n, false).
self.create_mutable_binding(n, true);
// 3. Perform ! env.InitializeBinding(n, undefined).
let binding = self.initialize_mutable_binding(n, true);
let index = self.get_or_insert_binding(binding);
self.emit_opcode(Opcode::PushUndefined);
self.emit(Opcode::DefInitVar, &[index]);
}
}
// d. Let varEnv be env.
};
// 29. NOTE: Annex B.3.2.1 adds additional steps at this point.
// TODO: Support B.3.2.1
// 29. If strict is false, then
// a. Let lexEnv be NewDeclarativeEnvironment(varEnv).
// b. NOTE: Non-strict functions use a separate Environment Record for top-level lexical declarations so that a direct eval can determine whether any var scoped declarations introduced by the eval code conflict with pre-existing top-level lexically scoped declarations. This is not needed for strict functions because a strict direct eval always places all declarations into a new Environment Record.
// 31. Else,
// a. Let lexEnv be varEnv.
// 32. Set the LexicalEnvironment of calleeContext to lexEnv.
// 33. Let lexDeclarations be the LexicallyScopedDeclarations of code.
// 34. For each element d of lexDeclarations, do
// a. NOTE: A lexically declared name cannot be the same as a function/generator declaration, formal parameter, or a var name. Lexically declared names are only instantiated here but not initialized.
// b. For each element dn of the BoundNames of d, do
// i. If IsConstantDeclaration of d is true, then
// 1. Perform ! lexEnv.CreateImmutableBinding(dn, true).
// ii. Else,
// 1. Perform ! lexEnv.CreateMutableBinding(dn, false).
for statement in code.statements() {
if let StatementListItem::Declaration(declaration) = statement {
match declaration {
Declaration::Class(class) => {
for name in bound_names(class) {
self.create_mutable_binding(name, false);
}
}
Declaration::Lexical(LexicalDeclaration::Let(declaration)) => {
for name in bound_names(declaration) {
self.create_mutable_binding(name, false);
}
}
Declaration::Lexical(LexicalDeclaration::Const(declaration)) => {
for name in bound_names(declaration) {
self.create_immutable_binding(name, true);
}
}
_ => {}
}
}
}
// 35. Let privateEnv be the PrivateEnvironment of calleeContext.
// 36. For each Parse Node f of functionsToInitialize, do
for function in functions_to_initialize {
// a. Let fn be the sole element of the BoundNames of f.
// b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv.
// c. Perform ! varEnv.SetMutableBinding(fn, fo, false).
self.function(function, NodeKind::Declaration, false);
}
// 37. Return unused.
(env_labels, additional_env)
}
}

54
boa_engine/src/bytecompiler/env.rs

@ -1,14 +1,8 @@
use super::ByteCompiler;
use crate::environments::{BindingLocator, CompileTimeEnvironment};
use boa_ast::expression::Identifier;
use boa_gc::{Gc, GcRefCell};
use crate::{
environments::{BindingLocator, CompileTimeEnvironment},
property::PropertyDescriptor,
JsString, JsValue,
};
use super::ByteCompiler;
/// Info returned by the [`ByteCompiler::pop_compile_environment`] method.
#[derive(Debug, Clone, Copy)]
pub(crate) struct PopEnvironmentInfo {
@ -64,47 +58,25 @@ impl ByteCompiler<'_, '_> {
.has_binding_recursive(name)
}
/// Check if a binding name exists in a environment.
/// If strict is `false` check until a function scope is reached.
pub(crate) fn has_binding_eval(&self, name: Identifier, strict: bool) -> bool {
self.current_environment
.borrow()
.has_binding_eval(name, strict)
}
/// Create a mutable binding at bytecode compile time.
/// This function returns a syntax error, if the binding is a redeclaration.
///
/// # Panics
///
/// Panics if the global environment is not function scoped.
pub(crate) fn create_mutable_binding(
&mut self,
name: Identifier,
function_scope: bool,
configurable: bool,
) {
if !self
pub(crate) fn create_mutable_binding(&mut self, name: Identifier, function_scope: bool) {
assert!(self
.current_environment
.borrow_mut()
.create_mutable_binding(name, function_scope)
{
let name_str = self
.context
.interner()
.resolve_expect(name.sym())
.into_common::<JsString>(false);
let global_obj = self.context.global_object();
// TODO: defer global initialization to execution time.
if !global_obj
.has_own_property(name_str.clone(), self.context)
.unwrap_or_default()
{
global_obj.borrow_mut().insert(
name_str,
PropertyDescriptor::builder()
.value(JsValue::Undefined)
.writable(true)
.enumerable(true)
.configurable(configurable)
.build(),
);
}
}
.create_mutable_binding(name, function_scope));
}
/// Initialize a mutable binding at bytecode compile time and return it's binding locator.

92
boa_engine/src/bytecompiler/function.rs

@ -2,12 +2,10 @@ use crate::{
builtins::function::ThisMode,
bytecompiler::ByteCompiler,
environments::CompileTimeEnvironment,
vm::{BindingOpcode, CodeBlock, Opcode},
vm::{CodeBlock, Opcode},
Context,
};
use boa_ast::{
declaration::Binding, function::FormalParameterList, operations::bound_names, StatementList,
};
use boa_ast::{function::FormalParameterList, StatementList};
use boa_gc::{Gc, GcRefCell};
use boa_interner::Sym;
@ -119,85 +117,23 @@ impl FunctionCompiler {
// Function environment
compiler.push_compile_environment(true);
// Only used to initialize bindings
if !self.strict && parameters.has_expressions() {
compiler.push_compile_environment(false);
};
// An arguments object is added when all of the following conditions are met
// - If not in an arrow function (10.2.11.16)
// - If the parameter list does not contain `arguments` (10.2.11.17)
// Note: This following just means, that we add an extra environment for the arguments.
// - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18)
if !(self.arrow) && !parameters.has_arguments() {
let arguments = Sym::ARGUMENTS.into();
compiler.arguments_binding = Some(if self.strict {
compiler.create_immutable_binding(arguments, true);
compiler.initialize_immutable_binding(arguments)
} else {
compiler.create_mutable_binding(arguments, false, false);
compiler.initialize_mutable_binding(arguments, false)
});
}
for parameter in parameters.as_ref() {
if parameter.is_rest_param() {
compiler.emit_opcode(Opcode::RestParameterInit);
}
match parameter.variable().binding() {
Binding::Identifier(ident) => {
compiler.create_mutable_binding(*ident, false, false);
// TODO: throw custom error if ident is in init
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::InitLet, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
compiler.create_mutable_binding(ident, false, false);
}
// TODO: throw custom error if ident is in init
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.compile_declaration_pattern(pattern, BindingOpcode::InitLet);
}
}
}
if !parameters.has_rest_parameter() {
compiler.emit_opcode(Opcode::RestParameterPop);
}
let env_label = if parameters.has_expressions() {
compiler.push_compile_environment(true);
compiler.function_environment_push_location = compiler.next_opcode_location();
Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment))
} else {
None
};
// When a generator object is created from a generator function, the generator executes until here to init parameters.
if self.generator {
compiler.emit_opcode(Opcode::PushUndefined);
compiler.emit_opcode(Opcode::Yield);
}
let (env_labels, additional_env) = compiler.function_declaration_instantiation(
body,
parameters,
self.arrow,
self.strict,
self.generator,
);
compiler.compile_statement_list(body, false, false);
compiler.compile_statement_list(body, false);
if let Some(env_label) = env_label {
if let Some(env_labels) = env_labels {
let env_info = compiler.pop_compile_environment();
compiler.patch_jump_with_target(env_label.0, env_info.num_bindings as u32);
compiler.patch_jump_with_target(env_label.1, env_info.index as u32);
compiler.patch_jump_with_target(env_labels.0, env_info.num_bindings as u32);
compiler.patch_jump_with_target(env_labels.1, env_info.index as u32);
}
if !self.strict && parameters.has_expressions() {
if additional_env {
compiler.parameters_env_bindings =
Some(compiler.pop_compile_environment().num_bindings);
}

278
boa_engine/src/bytecompiler/mod.rs

@ -2,6 +2,7 @@
mod class;
mod declaration;
mod declarations;
mod env;
mod expression;
mod function;
@ -27,7 +28,6 @@ use boa_ast::{
ArrowFunction, AsyncArrowFunction, AsyncFunction, AsyncGenerator, Class,
FormalParameterList, Function, Generator, PrivateName,
},
operations::bound_names,
pattern::Pattern,
Declaration, Expression, Statement, StatementList, StatementListItem,
};
@ -40,7 +40,7 @@ pub(crate) use jump_control::JumpControlInfo;
/// Describes how a node has been defined in the source code.
#[derive(Debug, Clone, Copy, PartialEq)]
enum NodeKind {
pub(crate) enum NodeKind {
Declaration,
Expression,
}
@ -59,9 +59,9 @@ enum FunctionKind {
/// Describes the complete specification of a function node.
#[derive(Debug, Clone, Copy, PartialEq)]
#[allow(single_use_lifetimes)]
struct FunctionSpec<'a> {
pub(crate) struct FunctionSpec<'a> {
kind: FunctionKind,
name: Option<Identifier>,
pub(crate) name: Option<Identifier>,
parameters: &'a FormalParameterList,
body: &'a StatementList,
has_binding_identifier: bool,
@ -286,6 +286,7 @@ pub struct ByteCompiler<'ctx, 'host> {
jump_info: Vec<JumpControlInfo>,
in_async_generator: bool,
json_parse: bool,
// TODO: remove when we separate scripts from the context
context: &'ctx mut Context<'host>,
}
@ -381,7 +382,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
}
#[inline]
fn get_or_insert_binding(&mut self, binding: BindingLocator) -> u32 {
pub(crate) fn get_or_insert_binding(&mut self, binding: BindingLocator) -> u32 {
if let Some(index) = self.bindings_map.get(&binding) {
return *index;
}
@ -418,11 +419,6 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::DefInitLet, &[index]);
}
BindingOpcode::InitArg => {
let binding = self.initialize_mutable_binding(name, true);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::DefInitArg, &[index]);
}
BindingOpcode::InitConst => {
let binding = self.initialize_immutable_binding(name);
let index = self.get_or_insert_binding(binding);
@ -441,7 +437,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
self.bytecode.len() as u32
}
fn emit(&mut self, opcode: Opcode, operands: &[u32]) {
pub(crate) fn emit(&mut self, opcode: Opcode, operands: &[u32]) {
self.emit_opcode(opcode);
for operand in operands {
self.emit_u32(*operand);
@ -460,7 +456,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
self.bytecode.extend(value.to_ne_bytes());
}
fn emit_opcode(&mut self, opcode: Opcode) {
pub(crate) fn emit_opcode(&mut self, opcode: Opcode) {
self.emit_u8(opcode as u8);
}
@ -540,13 +536,13 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
/// Emit an opcode with two dummy operands.
/// Return the `Label`s of the two operands.
fn emit_opcode_with_two_operands(&mut self, opcode: Opcode) -> (Label, Label) {
pub(crate) fn emit_opcode_with_two_operands(&mut self, opcode: Opcode) -> (Label, Label) {
let index = self.next_opcode_location();
self.emit(opcode, &[Self::DUMMY_ADDRESS, Self::DUMMY_ADDRESS]);
(Label { index }, Label { index: index + 4 })
}
fn patch_jump_with_target(&mut self, label: Label, target: u32) {
pub(crate) fn patch_jump_with_target(&mut self, label: Label, target: u32) {
const U32_SIZE: usize = std::mem::size_of::<u32>();
let Label { index } = label;
@ -743,13 +739,7 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
}
/// Compile a [`StatementList`].
pub fn compile_statement_list(
&mut self,
list: &StatementList,
use_expr: bool,
configurable_globals: bool,
) {
self.create_declarations(list, configurable_globals);
pub fn compile_statement_list(&mut self, list: &StatementList, use_expr: bool) {
if use_expr {
let expr_index = list
.statements()
@ -765,56 +755,15 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
.count();
for (i, item) in list.statements().iter().enumerate() {
self.compile_stmt_list_item(item, i + 1 == expr_index, configurable_globals);
self.compile_stmt_list_item(item, i + 1 == expr_index);
}
} else {
for item in list.statements() {
self.compile_stmt_list_item(item, false, configurable_globals);
self.compile_stmt_list_item(item, false);
}
}
}
/// Compile a statement list in a new declarative environment.
pub(crate) fn compile_statement_list_with_new_declarative(
&mut self,
list: &StatementList,
use_expr: bool,
strict: bool,
) {
self.push_compile_environment(strict);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.create_declarations(list, true);
if use_expr {
let expr_index = list
.statements()
.iter()
.rev()
.skip_while(|item| {
matches!(
item,
&&StatementListItem::Statement(Statement::Empty | Statement::Var(_))
| &&StatementListItem::Declaration(_)
)
})
.count();
for (i, item) in list.statements().iter().enumerate() {
self.compile_stmt_list_item(item, i + 1 == expr_index, true);
}
} else {
for item in list.statements() {
self.compile_stmt_list_item(item, false, true);
}
}
let env_info = self.pop_compile_environment();
self.patch_jump_with_target(push_env.0, env_info.num_bindings as u32);
self.patch_jump_with_target(push_env.1, env_info.index as u32);
self.emit_opcode(Opcode::PopEnvironment);
}
/// Compile an [`Expression`].
#[inline]
pub fn compile_expr(&mut self, expr: &Expression, use_expr: bool) {
@ -1071,15 +1020,10 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
}
/// Compile a [`StatementListItem`].
fn compile_stmt_list_item(
&mut self,
item: &StatementListItem,
use_expr: bool,
configurable_globals: bool,
) {
fn compile_stmt_list_item(&mut self, item: &StatementListItem, use_expr: bool) {
match item {
StatementListItem::Statement(stmt) => {
self.compile_stmt(stmt, use_expr, configurable_globals);
self.compile_stmt(stmt, use_expr);
}
StatementListItem::Declaration(decl) => self.compile_decl(decl),
}
@ -1088,25 +1032,19 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
/// Compile a [`Declaration`].
pub fn compile_decl(&mut self, decl: &Declaration) {
match decl {
Declaration::Function(function) => {
self.function(function.into(), NodeKind::Declaration, false);
}
Declaration::Generator(function) => {
self.function(function.into(), NodeKind::Declaration, false);
}
Declaration::AsyncFunction(function) => {
self.function(function.into(), NodeKind::Declaration, false);
}
Declaration::AsyncGenerator(function) => {
self.function(function.into(), NodeKind::Declaration, false);
}
Declaration::Class(class) => self.class(class, false),
Declaration::Lexical(lexical) => self.compile_lexical_decl(lexical),
_ => {}
}
}
/// Compile a function AST Node into bytecode.
fn function(&mut self, function: FunctionSpec<'_>, node_kind: NodeKind, use_expr: bool) {
pub(crate) fn function(
&mut self,
function: FunctionSpec<'_>,
node_kind: NodeKind,
use_expr: bool,
) {
let (generator, r#async, arrow) = (
function.is_generator(),
function.is_async(),
@ -1362,178 +1300,6 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
self.compile_declaration_pattern_impl(pattern, def);
}
/// Creates the declarations for a script.
pub(crate) fn create_declarations(
&mut self,
stmt_list: &StatementList,
configurable_globals: bool,
) {
for node in stmt_list.statements() {
self.create_decls_from_stmt_list_item(node, configurable_globals);
}
}
pub(crate) fn create_decls_from_var_decl(
&mut self,
list: &VarDeclaration,
configurable_globals: bool,
) -> bool {
let mut has_identifier_argument = false;
for decl in list.0.as_ref() {
match decl.binding() {
Binding::Identifier(ident) => {
let ident = ident;
if *ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.create_mutable_binding(*ident, true, configurable_globals);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
if ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.create_mutable_binding(ident, true, configurable_globals);
}
}
}
}
has_identifier_argument
}
pub(crate) fn create_decls_from_lexical_decl(&mut self, list: &LexicalDeclaration) -> bool {
let mut has_identifier_argument = false;
match list {
LexicalDeclaration::Let(list) => {
for decl in list.as_ref() {
match decl.binding() {
Binding::Identifier(ident) => {
let ident = ident;
if *ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.create_mutable_binding(*ident, false, false);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
if ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.create_mutable_binding(ident, false, false);
}
}
}
}
}
LexicalDeclaration::Const(list) => {
for decl in list.as_ref() {
match decl.binding() {
Binding::Identifier(ident) => {
let ident = ident;
if *ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.create_immutable_binding(*ident, true);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
if ident == Sym::ARGUMENTS {
has_identifier_argument = true;
}
self.create_immutable_binding(ident, true);
}
}
}
}
}
}
has_identifier_argument
}
pub(crate) fn create_decls_from_decl(
&mut self,
declaration: &Declaration,
configurable_globals: bool,
) -> bool {
match declaration {
Declaration::Lexical(decl) => self.create_decls_from_lexical_decl(decl),
Declaration::Function(decl) => {
let ident = decl.name().expect("function declaration must have a name");
self.create_mutable_binding(ident, true, configurable_globals);
ident == Sym::ARGUMENTS
}
Declaration::Generator(decl) => {
let ident = decl.name().expect("generator declaration must have a name");
self.create_mutable_binding(ident, true, configurable_globals);
ident == Sym::ARGUMENTS
}
Declaration::AsyncFunction(decl) => {
let ident = decl
.name()
.expect("async function declaration must have a name");
self.create_mutable_binding(ident, true, configurable_globals);
ident == Sym::ARGUMENTS
}
Declaration::AsyncGenerator(decl) => {
let ident = decl
.name()
.expect("async generator declaration must have a name");
self.create_mutable_binding(ident, true, configurable_globals);
ident == Sym::ARGUMENTS
}
Declaration::Class(decl) => {
let ident = decl.name().expect("class declaration must have a name");
self.create_mutable_binding(ident, false, configurable_globals);
false
}
}
}
pub(crate) fn create_decls_from_stmt(
&mut self,
statement: &Statement,
configurable_globals: bool,
) -> bool {
match statement {
Statement::Var(var) => self.create_decls_from_var_decl(var, configurable_globals),
Statement::DoWhileLoop(do_while_loop) => {
if !matches!(do_while_loop.body(), Statement::Block(_)) {
self.create_decls_from_stmt(do_while_loop.body(), configurable_globals);
}
false
}
Statement::ForInLoop(for_in_loop) => {
if !matches!(for_in_loop.body(), Statement::Block(_)) {
self.create_decls_from_stmt(for_in_loop.body(), configurable_globals);
}
false
}
Statement::ForOfLoop(for_of_loop) => {
if !matches!(for_of_loop.body(), Statement::Block(_)) {
self.create_decls_from_stmt(for_of_loop.body(), configurable_globals);
}
false
}
_ => false,
}
}
pub(crate) fn create_decls_from_stmt_list_item(
&mut self,
item: &StatementListItem,
configurable_globals: bool,
) -> bool {
match item {
StatementListItem::Declaration(decl) => {
self.create_decls_from_decl(decl, configurable_globals)
}
StatementListItem::Statement(stmt) => {
self.create_decls_from_stmt(stmt, configurable_globals)
}
}
}
fn class(&mut self, class: &Class, expression: bool) {
self.compile_class(class, expression);
}

35
boa_engine/src/bytecompiler/module.rs

@ -6,19 +6,19 @@ use boa_ast::{ModuleItem, ModuleItemList};
impl ByteCompiler<'_, '_> {
/// Compiles a [`ModuleItemList`].
#[inline]
pub fn compile_module_item_list(&mut self, list: &ModuleItemList, configurable_globals: bool) {
pub fn compile_module_item_list(&mut self, list: &ModuleItemList) {
for node in list.items() {
self.compile_module_item(node, configurable_globals);
self.compile_module_item(node);
}
}
/// Compiles a [`ModuleItem`].
#[inline]
#[allow(clippy::single_match_else)]
pub fn compile_module_item(&mut self, item: &ModuleItem, configurable_globals: bool) {
pub fn compile_module_item(&mut self, item: &ModuleItem) {
match item {
ModuleItem::StatementListItem(stmt) => {
self.compile_stmt_list_item(stmt, false, configurable_globals);
self.compile_stmt_list_item(stmt, false);
}
_ => {
// TODO: Remove after implementing modules.
@ -29,31 +29,4 @@ impl ByteCompiler<'_, '_> {
}
}
}
/// Creates the declarations for a module.
pub(crate) fn create_module_decls(
&mut self,
stmt_list: &ModuleItemList,
configurable_globals: bool,
) {
for node in stmt_list.items() {
self.create_decls_from_module_item(node, configurable_globals);
}
}
/// Creates the declarations from a [`ModuleItem`].
#[inline]
pub(crate) fn create_decls_from_module_item(
&mut self,
item: &ModuleItem,
configurable_globals: bool,
) -> bool {
match item {
ModuleItem::StatementListItem(stmt) => {
self.create_decls_from_stmt_list_item(stmt, configurable_globals)
}
// TODO: Implement modules
_ => false,
}
}
}

11
boa_engine/src/bytecompiler/statement/block.rs

@ -1,19 +1,14 @@
use crate::{bytecompiler::ByteCompiler, vm::Opcode};
use boa_ast::statement::Block;
impl ByteCompiler<'_, '_> {
/// Compile a [`Block`] `boa_ast` node
pub(crate) fn compile_block(
&mut self,
block: &Block,
use_expr: bool,
configurable_globals: bool,
) {
pub(crate) fn compile_block(&mut self, block: &Block, use_expr: bool) {
self.push_compile_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.compile_statement_list(block.statement_list(), use_expr, configurable_globals);
self.block_declaration_instantiation(block);
self.compile_statement_list(block.statement_list(), use_expr);
let env_info = self.pop_compile_environment();
self.patch_jump_with_target(push_env.0, env_info.num_bindings as u32);

6
boa_engine/src/bytecompiler/statement/if.rs

@ -2,11 +2,11 @@ use crate::bytecompiler::ByteCompiler;
use boa_ast::statement::If;
impl ByteCompiler<'_, '_> {
pub(crate) fn compile_if(&mut self, node: &If, use_expr: bool, configurable_globals: bool) {
pub(crate) fn compile_if(&mut self, node: &If, use_expr: bool) {
self.compile_expr(node.cond(), true);
let jelse = self.jump_if_false();
self.compile_stmt(node.body(), use_expr, configurable_globals);
self.compile_stmt(node.body(), use_expr);
match node.else_node() {
None => {
@ -15,7 +15,7 @@ impl ByteCompiler<'_, '_> {
Some(else_body) => {
let exit = self.jump();
self.patch_jump(jelse);
self.compile_stmt(else_body, use_expr, configurable_globals);
self.compile_stmt(else_body, use_expr);
self.patch_jump(exit);
}
}

35
boa_engine/src/bytecompiler/statement/labelled.rs

@ -9,12 +9,7 @@ use boa_ast::{
impl ByteCompiler<'_, '_> {
/// Compile a [`Labelled`] `boa_ast` node
pub(crate) fn compile_labelled(
&mut self,
labelled: &Labelled,
use_expr: bool,
configurable_globals: bool,
) {
pub(crate) fn compile_labelled(&mut self, labelled: &Labelled, use_expr: bool) {
let labelled_loc = self.next_opcode_location();
let end_label = self.emit_opcode_with_operand(Opcode::LabelledStart);
self.push_labelled_control_info(labelled.label(), labelled_loc);
@ -22,37 +17,21 @@ impl ByteCompiler<'_, '_> {
match labelled.item() {
LabelledItem::Statement(stmt) => match stmt {
Statement::ForLoop(for_loop) => {
self.compile_for_loop(for_loop, Some(labelled.label()), configurable_globals);
self.compile_for_loop(for_loop, Some(labelled.label()));
}
Statement::ForInLoop(for_in_loop) => {
self.compile_for_in_loop(
for_in_loop,
Some(labelled.label()),
configurable_globals,
);
self.compile_for_in_loop(for_in_loop, Some(labelled.label()));
}
Statement::ForOfLoop(for_of_loop) => {
self.compile_for_of_loop(
for_of_loop,
Some(labelled.label()),
configurable_globals,
);
self.compile_for_of_loop(for_of_loop, Some(labelled.label()));
}
Statement::WhileLoop(while_loop) => {
self.compile_while_loop(
while_loop,
Some(labelled.label()),
configurable_globals,
);
self.compile_while_loop(while_loop, Some(labelled.label()));
}
Statement::DoWhileLoop(do_while_loop) => {
self.compile_do_while_loop(
do_while_loop,
Some(labelled.label()),
configurable_globals,
);
self.compile_do_while_loop(do_while_loop, Some(labelled.label()));
}
stmt => self.compile_stmt(stmt, use_expr, configurable_globals),
stmt => self.compile_stmt(stmt, use_expr),
},
LabelledItem::Function(f) => {
self.function(f.into(), NodeKind::Declaration, false);

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

@ -1,5 +1,5 @@
use boa_ast::{
declaration::Binding,
declaration::{Binding, LexicalDeclaration},
operations::bound_names,
statement::{
iteration::{ForLoopInitializer, IterableLoopInitializer},
@ -14,12 +14,7 @@ use crate::{
};
impl ByteCompiler<'_, '_> {
pub(crate) fn compile_for_loop(
&mut self,
for_loop: &ForLoop,
label: Option<Sym>,
configurable_globals: bool,
) {
pub(crate) fn compile_for_loop(&mut self, for_loop: &ForLoop, label: Option<Sym>) {
self.push_compile_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.push_empty_loop_jump_control();
@ -28,11 +23,21 @@ impl ByteCompiler<'_, '_> {
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);
match decl {
LexicalDeclaration::Const(decl) => {
for name in bound_names(decl) {
self.create_immutable_binding(name, true);
}
}
LexicalDeclaration::Let(decl) => {
for name in bound_names(decl) {
self.create_mutable_binding(name, false);
}
}
}
self.compile_lexical_decl(decl);
}
}
@ -67,7 +72,7 @@ impl ByteCompiler<'_, '_> {
}
let exit = self.jump_if_false();
self.compile_stmt(for_loop.body(), false, configurable_globals);
self.compile_stmt(for_loop.body(), false);
self.emit(Opcode::Jump, &[start_address]);
@ -83,18 +88,12 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::PopEnvironment);
}
pub(crate) fn compile_for_in_loop(
&mut self,
for_in_loop: &ForInLoop,
label: Option<Sym>,
configurable_globals: bool,
) {
pub(crate) fn compile_for_in_loop(&mut self, for_in_loop: &ForInLoop, label: Option<Sym>) {
// Handle https://tc39.es/ecma262/#prod-annexB-ForInOfStatement
if let IterableLoopInitializer::Var(var) = for_in_loop.initializer() {
if let Binding::Identifier(ident) = var.binding() {
if let Some(init) = var.init() {
self.compile_expr(init, true);
self.create_mutable_binding(*ident, true, true);
self.emit_binding(BindingOpcode::InitVar, *ident);
}
}
@ -111,7 +110,7 @@ impl ByteCompiler<'_, '_> {
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
for name in &initializer_bound_names {
self.create_mutable_binding(*name, false, false);
self.create_mutable_binding(*name, false);
}
self.compile_expr(for_in_loop.target(), true);
@ -146,7 +145,6 @@ impl ByteCompiler<'_, '_> {
match for_in_loop.initializer() {
IterableLoopInitializer::Identifier(ident) => {
self.create_mutable_binding(*ident, true, true);
self.emit_binding(BindingOpcode::InitVar, *ident);
}
IterableLoopInitializer::Access(access) => {
@ -158,24 +156,20 @@ impl ByteCompiler<'_, '_> {
}
IterableLoopInitializer::Var(declaration) => match declaration.binding() {
Binding::Identifier(ident) => {
self.create_mutable_binding(*ident, true, configurable_globals);
self.emit_binding(BindingOpcode::InitVar, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.create_mutable_binding(ident, true, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar);
}
},
IterableLoopInitializer::Let(declaration) => match declaration {
Binding::Identifier(ident) => {
self.create_mutable_binding(*ident, false, false);
self.create_mutable_binding(*ident, false);
self.emit_binding(BindingOpcode::InitLet, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.create_mutable_binding(ident, false, false);
self.create_mutable_binding(ident, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLet);
}
@ -197,7 +191,7 @@ impl ByteCompiler<'_, '_> {
}
}
self.compile_stmt(for_in_loop.body(), false, configurable_globals);
self.compile_stmt(for_in_loop.body(), false);
if let Some(iteration_environment) = iteration_environment {
let env_info = self.pop_compile_environment();
@ -220,12 +214,7 @@ impl ByteCompiler<'_, '_> {
self.patch_jump(early_exit);
}
pub(crate) fn compile_for_of_loop(
&mut self,
for_of_loop: &ForOfLoop,
label: Option<Sym>,
configurable_globals: bool,
) {
pub(crate) fn compile_for_of_loop(&mut self, for_of_loop: &ForOfLoop, label: Option<Sym>) {
let initializer_bound_names = match for_of_loop.initializer() {
IterableLoopInitializer::Let(declaration)
| IterableLoopInitializer::Const(declaration) => bound_names(declaration),
@ -238,7 +227,7 @@ impl ByteCompiler<'_, '_> {
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
for name in &initializer_bound_names {
self.create_mutable_binding(*name, false, false);
self.create_mutable_binding(*name, false);
}
self.compile_expr(for_of_loop.iterable(), true);
@ -280,7 +269,6 @@ impl ByteCompiler<'_, '_> {
match for_of_loop.initializer() {
IterableLoopInitializer::Identifier(ref ident) => {
self.create_mutable_binding(*ident, true, true);
let binding = self.set_mutable_binding(*ident);
let index = self.get_or_insert_binding(binding);
self.emit(Opcode::DefInitVar, &[index]);
@ -297,25 +285,21 @@ impl ByteCompiler<'_, '_> {
assert!(declaration.init().is_none());
match declaration.binding() {
Binding::Identifier(ident) => {
self.create_mutable_binding(*ident, true, false);
self.emit_binding(BindingOpcode::InitVar, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.create_mutable_binding(ident, true, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitVar);
}
}
}
IterableLoopInitializer::Let(declaration) => match declaration {
Binding::Identifier(ident) => {
self.create_mutable_binding(*ident, false, false);
self.create_mutable_binding(*ident, false);
self.emit_binding(BindingOpcode::InitLet, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.create_mutable_binding(ident, false, false);
self.create_mutable_binding(ident, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLet);
}
@ -337,7 +321,7 @@ impl ByteCompiler<'_, '_> {
}
}
self.compile_stmt(for_of_loop.body(), false, configurable_globals);
self.compile_stmt(for_of_loop.body(), false);
if let Some(iteration_environment) = iteration_environment {
let env_info = self.pop_compile_environment();
@ -356,12 +340,7 @@ impl ByteCompiler<'_, '_> {
self.iterator_close(for_of_loop.r#await());
}
pub(crate) fn compile_while_loop(
&mut self,
while_loop: &WhileLoop,
label: Option<Sym>,
configurable_globals: bool,
) {
pub(crate) fn compile_while_loop(&mut self, while_loop: &WhileLoop, label: Option<Sym>) {
let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let start_address = self.next_opcode_location();
let (continue_start, continue_exit) =
@ -372,7 +351,7 @@ impl ByteCompiler<'_, '_> {
self.compile_expr(while_loop.condition(), true);
let exit = self.jump_if_false();
self.compile_stmt(while_loop.body(), false, configurable_globals);
self.compile_stmt(while_loop.body(), false);
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
@ -386,7 +365,6 @@ impl ByteCompiler<'_, '_> {
&mut self,
do_while_loop: &DoWhileLoop,
label: Option<Sym>,
configurable_globals: bool,
) {
let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let initial_label = self.jump();
@ -404,7 +382,7 @@ impl ByteCompiler<'_, '_> {
self.patch_jump(initial_label);
self.compile_stmt(do_while_loop.body(), false, configurable_globals);
self.compile_stmt(do_while_loop.body(), false);
self.emit(Opcode::Jump, &[condition_label_address]);
self.patch_jump(exit);
self.patch_jump(loop_exit);

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

@ -14,30 +14,30 @@ mod with;
impl ByteCompiler<'_, '_> {
/// Compiles a [`Statement`] `boa_ast` node.
pub fn compile_stmt(&mut self, node: &Statement, use_expr: bool, configurable_globals: bool) {
pub fn compile_stmt(&mut self, node: &Statement, use_expr: bool) {
match node {
Statement::Var(var) => self.compile_var_decl(var),
Statement::If(node) => self.compile_if(node, use_expr, configurable_globals),
Statement::If(node) => self.compile_if(node, use_expr),
Statement::ForLoop(for_loop) => {
self.compile_for_loop(for_loop, None, configurable_globals);
self.compile_for_loop(for_loop, None);
}
Statement::ForInLoop(for_in_loop) => {
self.compile_for_in_loop(for_in_loop, None, configurable_globals);
self.compile_for_in_loop(for_in_loop, None);
}
Statement::ForOfLoop(for_of_loop) => {
self.compile_for_of_loop(for_of_loop, None, configurable_globals);
self.compile_for_of_loop(for_of_loop, None);
}
Statement::WhileLoop(while_loop) => {
self.compile_while_loop(while_loop, None, configurable_globals);
self.compile_while_loop(while_loop, None);
}
Statement::DoWhileLoop(do_while_loop) => {
self.compile_do_while_loop(do_while_loop, None, configurable_globals);
self.compile_do_while_loop(do_while_loop, None);
}
Statement::Block(block) => {
self.compile_block(block, use_expr, configurable_globals);
self.compile_block(block, use_expr);
}
Statement::Labelled(labelled) => {
self.compile_labelled(labelled, use_expr, configurable_globals);
self.compile_labelled(labelled, use_expr);
}
Statement::Continue(node) => self.compile_continue(*node),
Statement::Break(node) => self.compile_break(*node),
@ -46,7 +46,7 @@ impl ByteCompiler<'_, '_> {
self.emit(Opcode::Throw, &[]);
}
Statement::Switch(switch) => {
self.compile_switch(switch, configurable_globals);
self.compile_switch(switch);
}
Statement::Return(ret) => {
if let Some(expr) = ret.target() {
@ -56,9 +56,9 @@ impl ByteCompiler<'_, '_> {
}
self.emit(Opcode::Return, &[]);
}
Statement::Try(t) => self.compile_try(t, use_expr, configurable_globals),
Statement::Try(t) => self.compile_try(t, use_expr),
Statement::Expression(expr) => self.compile_expr(expr, use_expr),
Statement::With(with) => self.compile_with(with, configurable_globals),
Statement::With(with) => self.compile_with(with),
Statement::Empty => {}
}
}

19
boa_engine/src/bytecompiler/statement/switch.rs

@ -3,17 +3,14 @@ use boa_ast::statement::Switch;
impl ByteCompiler<'_, '_> {
/// Compile a [`Switch`] `boa_ast` node
pub(crate) fn compile_switch(&mut self, switch: &Switch, configurable_globals: bool) {
pub(crate) fn compile_switch(&mut self, switch: &Switch) {
self.compile_expr(switch.val(), true);
self.push_compile_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
for case in switch.cases() {
self.create_declarations(case.body(), configurable_globals);
}
if let Some(body) = switch.default() {
self.create_declarations(body, configurable_globals);
}
self.block_declaration_instantiation(switch);
let (start_label, end_label) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let start_address = self.next_opcode_location();
@ -30,16 +27,12 @@ impl ByteCompiler<'_, '_> {
for (label, case) in labels.into_iter().zip(switch.cases()) {
self.patch_jump(label);
for item in case.body().statements() {
self.compile_stmt_list_item(item, false, configurable_globals);
}
self.compile_statement_list(case.body(), false);
}
self.patch_jump(exit);
if let Some(body) = switch.default() {
for item in body.statements() {
self.compile_stmt_list_item(item, false, configurable_globals);
}
self.compile_statement_list(body, false);
}
self.pop_switch_control_info();

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

@ -9,7 +9,7 @@ use boa_ast::{
};
impl ByteCompiler<'_, '_> {
pub(crate) fn compile_try(&mut self, t: &Try, use_expr: bool, configurable_globals: bool) {
pub(crate) fn compile_try(&mut self, t: &Try, use_expr: bool) {
let try_start = self.next_opcode_location();
let (catch_start, finally_loc) = self.emit_opcode_with_two_operands(Opcode::TryStart);
self.patch_jump_with_target(finally_loc, u32::MAX);
@ -20,22 +20,15 @@ impl ByteCompiler<'_, '_> {
}
self.push_try_control_info(t.finally().is_some(), try_start);
self.push_compile_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.compile_block(t.block(), use_expr);
self.compile_statement_list(t.block().statement_list(), use_expr, configurable_globals);
let env_info = self.pop_compile_environment();
self.patch_jump_with_target(push_env.0, env_info.num_bindings as u32);
self.patch_jump_with_target(push_env.1, env_info.index as u32);
self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::TryEnd);
let finally = self.jump();
self.patch_jump(catch_start);
if t.catch().is_some() {
self.compile_catch_stmt(t, use_expr, configurable_globals);
self.compile_catch_stmt(t, use_expr);
}
self.patch_jump(finally);
@ -49,19 +42,14 @@ impl ByteCompiler<'_, '_> {
self.set_jump_control_start_address(finally_start);
self.patch_jump_with_target(finally_loc, finally_start);
// Compile finally statement body
self.compile_finally_stmt(finally, finally_end, configurable_globals);
self.compile_finally_stmt(finally, finally_end);
} else {
let try_end = self.next_opcode_location();
self.pop_try_control_info(try_end);
}
}
pub(crate) fn compile_catch_stmt(
&mut self,
parent_try: &Try,
use_expr: bool,
configurable_globals: bool,
) {
pub(crate) fn compile_catch_stmt(&mut self, parent_try: &Try, use_expr: bool) {
let catch = parent_try
.catch()
.expect("Catch must exist for compile_catch_stmt to have been invoked");
@ -74,12 +62,12 @@ impl ByteCompiler<'_, '_> {
if let Some(binding) = catch.parameter() {
match binding {
Binding::Identifier(ident) => {
self.create_mutable_binding(*ident, false, false);
self.create_mutable_binding(*ident, false);
self.emit_binding(BindingOpcode::InitLet, *ident);
}
Binding::Pattern(pattern) => {
for ident in bound_names(pattern) {
self.create_mutable_binding(ident, false, false);
self.create_mutable_binding(ident, false);
}
self.compile_declaration_pattern(pattern, BindingOpcode::InitLet);
}
@ -88,11 +76,7 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::Pop);
}
self.compile_statement_list(
catch.block().statement_list(),
use_expr,
configurable_globals,
);
self.compile_block(catch.block(), use_expr);
let env_info = self.pop_compile_environment();
self.patch_jump_with_target(push_env.0, env_info.num_bindings as u32);
@ -108,26 +92,9 @@ impl ByteCompiler<'_, '_> {
self.set_jump_control_in_finally(false);
}
pub(crate) fn compile_finally_stmt(
&mut self,
finally: &Finally,
finally_end_label: Label,
configurable_globals: bool,
) {
self.push_compile_environment(false);
let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment);
self.compile_statement_list(
finally.block().statement_list(),
false,
configurable_globals,
);
pub(crate) fn compile_finally_stmt(&mut self, finally: &Finally, finally_end_label: Label) {
self.compile_block(finally.block(), false);
let env_info = self.pop_compile_environment();
self.patch_jump_with_target(push_env.0, env_info.num_bindings as u32);
self.patch_jump_with_target(push_env.1, env_info.index as u32);
self.emit_opcode(Opcode::PopEnvironment);
self.pop_finally_control_info();
self.patch_jump(finally_end_label);
self.emit_opcode(Opcode::FinallyEnd);

4
boa_engine/src/bytecompiler/statement/with.rs

@ -3,11 +3,11 @@ use boa_ast::statement::With;
impl ByteCompiler<'_, '_> {
/// Compile a [`With`] `boa_ast` node
pub(crate) fn compile_with(&mut self, with: &With, configurable_globals: bool) {
pub(crate) fn compile_with(&mut self, with: &With) {
self.compile_expr(with.expression(), true);
self.push_compile_environment(false);
self.emit_opcode(Opcode::PushObjectEnvironment);
self.compile_stmt(with.statement(), false, configurable_globals);
self.compile_stmt(with.statement(), false);
self.pop_compile_environment();
self.emit_opcode(Opcode::PopEnvironment);
}

235
boa_engine/src/context/mod.rs

@ -18,7 +18,7 @@ use std::{io::Read, rc::Rc};
use crate::{
builtins,
bytecompiler::ByteCompiler,
bytecompiler::{ByteCompiler, NodeKind},
class::{Class, ClassBuilder},
job::{JobQueue, NativeJob, SimpleJobQueue},
native_function::NativeFunction,
@ -26,10 +26,15 @@ use crate::{
optimizer::{Optimizer, OptimizerOptions, OptimizerStatistics},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
vm::{CallFrame, CodeBlock, Vm},
vm::{CallFrame, CodeBlock, Opcode, Vm},
JsResult, JsValue, Source,
};
use boa_ast::{ModuleItemList, StatementList};
use boa_ast::{
declaration::LexicalDeclaration,
expression::Identifier,
operations::{bound_names, lexically_scoped_declarations, var_scoped_declarations},
Declaration, ModuleItemList, StatementList,
};
use boa_gc::Gc;
use boa_interner::{Interner, Sym};
use boa_parser::{Error as ParseError, Parser};
@ -246,6 +251,7 @@ impl<'host> Context<'host> {
/// Compile the script AST into a `CodeBlock` ready to be executed by the VM.
pub fn compile_script(&mut self, statement_list: &StatementList) -> JsResult<Gc<CodeBlock>> {
let _timer = Profiler::global().start_event("Script compilation", "Main");
let mut compiler = ByteCompiler::new(
Sym::MAIN,
statement_list.strict(),
@ -253,7 +259,8 @@ impl<'host> Context<'host> {
self.realm.environment().compile_env(),
self,
);
compiler.compile_statement_list(statement_list, true, false);
compiler.global_declaration_instantiation(statement_list)?;
compiler.compile_statement_list(statement_list, true);
Ok(Gc::new(compiler.finish()))
}
@ -268,8 +275,67 @@ impl<'host> Context<'host> {
self.realm.environment().compile_env(),
self,
);
compiler.create_module_decls(statement_list, false);
compiler.compile_module_item_list(statement_list, false);
let var_declarations = var_scoped_declarations(statement_list);
let mut declared_var_names = Vec::new();
for var in var_declarations {
for name in var.bound_names() {
if !declared_var_names.contains(&name) {
compiler.create_mutable_binding(name, false);
let binding = compiler.initialize_mutable_binding(name, false);
let index = compiler.get_or_insert_binding(binding);
compiler.emit_opcode(Opcode::PushUndefined);
compiler.emit(Opcode::DefInitVar, &[index]);
declared_var_names.push(name);
}
}
}
let lex_declarations = lexically_scoped_declarations(statement_list);
for declaration in lex_declarations {
match &declaration {
Declaration::Lexical(LexicalDeclaration::Const(declaration)) => {
for name in bound_names(declaration) {
compiler.create_immutable_binding(name, true);
}
}
Declaration::Lexical(LexicalDeclaration::Let(declaration)) => {
for name in bound_names(declaration) {
compiler.create_mutable_binding(name, false);
}
}
Declaration::Function(function) => {
for name in bound_names(function) {
compiler.create_mutable_binding(name, false);
}
compiler.function(function.into(), NodeKind::Declaration, false);
}
Declaration::Generator(function) => {
for name in bound_names(function) {
compiler.create_mutable_binding(name, false);
}
compiler.function(function.into(), NodeKind::Declaration, false);
}
Declaration::AsyncFunction(function) => {
for name in bound_names(function) {
compiler.create_mutable_binding(name, false);
}
compiler.function(function.into(), NodeKind::Declaration, false);
}
Declaration::AsyncGenerator(function) => {
for name in bound_names(function) {
compiler.create_mutable_binding(name, false);
}
compiler.function(function.into(), NodeKind::Declaration, false);
}
Declaration::Class(class) => {
for name in bound_names(class) {
compiler.create_mutable_binding(name, false);
}
}
}
}
compiler.compile_module_item_list(statement_list);
Ok(Gc::new(compiler.finish()))
}
@ -558,6 +624,163 @@ impl Context<'_> {
pub(crate) fn swap_realm(&mut self, realm: &mut Realm) {
std::mem::swap(&mut self.realm, realm);
}
/// `CanDeclareGlobalFunction ( N )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalfunction
pub(crate) fn can_declare_global_function(&mut self, name: Identifier) -> JsResult<bool> {
// 1. Let ObjRec be envRec.[[ObjectRecord]].
// 2. Let globalObject be ObjRec.[[BindingObject]].
let global_object = self.realm().global_object().clone();
// 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
let name = self.interner().resolve_expect(name.sym()).utf16().into();
let existing_prop = global_object.__get_own_property__(&name, self)?;
// 4. If existingProp is undefined, return ? IsExtensible(globalObject).
let Some(existing_prop) = existing_prop else {
return global_object.is_extensible(self);
};
// 5. If existingProp.[[Configurable]] is true, return true.
if existing_prop.configurable() == Some(true) {
return Ok(true);
}
// 6. If IsDataDescriptor(existingProp) is true and existingProp has attribute values { [[Writable]]: true, [[Enumerable]]: true }, return true.
if existing_prop.is_data_descriptor()
&& existing_prop.writable() == Some(true)
&& existing_prop.enumerable() == Some(true)
{
return Ok(true);
}
// 7. Return false.
Ok(false)
}
/// `CanDeclareGlobalVar ( N )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-candeclareglobalvar
pub(crate) fn can_declare_global_var(&mut self, name: Identifier) -> JsResult<bool> {
// 1. Let ObjRec be envRec.[[ObjectRecord]].
// 2. Let globalObject be ObjRec.[[BindingObject]].
let global_object = self.realm().global_object().clone();
// 3. Let hasProperty be ? HasOwnProperty(globalObject, N).
let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16());
let has_property = global_object.has_own_property(name, self)?;
// 4. If hasProperty is true, return true.
if has_property {
return Ok(true);
}
// 5. Return ? IsExtensible(globalObject).
global_object.is_extensible(self)
}
/// `CreateGlobalVarBinding ( N, D )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createglobalvarbinding
pub(crate) fn create_global_var_binding(
&mut self,
name: Identifier,
configurable: bool,
) -> JsResult<()> {
// 1. Let ObjRec be envRec.[[ObjectRecord]].
// 2. Let globalObject be ObjRec.[[BindingObject]].
let global_object = self.realm().global_object().clone();
// 3. Let hasProperty be ? HasOwnProperty(globalObject, N).
let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16());
let has_property = global_object.has_own_property(name.clone(), self)?;
// 4. Let extensible be ? IsExtensible(globalObject).
let extensible = global_object.is_extensible(self)?;
// 5. If hasProperty is false and extensible is true, then
if !has_property && extensible {
// a. Perform ? ObjRec.CreateMutableBinding(N, D).
// b. Perform ? ObjRec.InitializeBinding(N, undefined).
global_object.define_property_or_throw(
name,
PropertyDescriptor::builder()
.value(JsValue::undefined())
.writable(true)
.enumerable(true)
.configurable(configurable)
.build(),
self,
)?;
}
// 6. If envRec.[[VarNames]] does not contain N, then
// a. Append N to envRec.[[VarNames]].
// 7. Return unused.
Ok(())
}
/// `CreateGlobalFunctionBinding ( N, V, D )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createglobalfunctionbinding
pub(crate) fn create_global_function_binding(
&mut self,
name: Identifier,
function: JsObject,
configurable: bool,
) -> JsResult<()> {
// 1. Let ObjRec be envRec.[[ObjectRecord]].
// 2. Let globalObject be ObjRec.[[BindingObject]].
let global_object = self.realm().global_object().clone();
// 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N).
let name = PropertyKey::from(self.interner().resolve_expect(name.sym()).utf16());
let existing_prop = global_object.__get_own_property__(&name, self)?;
// 4. If existingProp is undefined or existingProp.[[Configurable]] is true, then
let desc = if existing_prop.is_none()
|| existing_prop.and_then(|p| p.configurable()) == Some(true)
{
// a. Let desc be the PropertyDescriptor { [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: D }.
PropertyDescriptor::builder()
.value(function.clone())
.writable(true)
.enumerable(true)
.configurable(configurable)
.build()
}
// 5. Else,
else {
// a. Let desc be the PropertyDescriptor { [[Value]]: V }.
PropertyDescriptor::builder()
.value(function.clone())
.build()
};
// 6. Perform ? DefinePropertyOrThrow(globalObject, N, desc).
global_object.define_property_or_throw(name.clone(), desc, self)?;
// 7. Perform ? Set(globalObject, N, V, false).
global_object.set(name, function, false, self)?;
// 8. If envRec.[[VarNames]] does not contain N, then
// a. Append N to envRec.[[VarNames]].
// 9. Return unused.
Ok(())
}
}
impl<'host> Context<'host> {

31
boa_engine/src/environments/compile.rs

@ -106,6 +106,23 @@ impl CompileTimeEnvironment {
}
}
/// Check if a binding name exists in a environment.
/// If strict is `false` check until a function scope is reached.
pub(crate) fn has_binding_eval(&self, name: Identifier, strict: bool) -> bool {
let exists = self.bindings.contains_key(&name);
if exists || strict {
return exists;
}
if self.function_scope {
return false;
}
if let Some(outer) = &self.outer {
outer.borrow().has_binding_eval(name, false)
} else {
false
}
}
/// Create a mutable binding.
///
/// If the binding is a function scope binding and this is a declarative environment, try the outer environment.
@ -228,18 +245,4 @@ impl CompileTimeEnvironment {
pub(crate) const fn environment_index(&self) -> usize {
self.environment_index
}
/// Gets the indices of all `var` bindings in this environment.
pub(crate) fn var_binding_indices(&self) -> Vec<usize> {
self.bindings
.iter()
.filter_map(|(_, binding)| {
if binding.lex {
None
} else {
Some(binding.index)
}
})
.collect()
}
}

30
boa_engine/src/environments/runtime.rs

@ -336,6 +336,22 @@ impl DeclarativeEnvironmentStack {
None
}
/// Check if the next outer function environment is the global environment.
pub(crate) fn is_next_outer_function_environment_global(&self) -> bool {
for env in self
.stack
.iter()
.rev()
.filter_map(Environment::as_declarative)
{
let compile = env.compile.borrow();
if compile.is_function() {
return compile.outer().is_none();
}
}
true
}
/// Pop all current environments except the global environment.
pub(crate) fn pop_to_global(&mut self) -> Vec<Environment> {
self.stack.split_off(1)
@ -481,14 +497,9 @@ impl DeclarativeEnvironmentStack {
let this = this.unwrap_or(JsValue::Null);
let mut bindings = vec![None; num_bindings];
for index in compile_environment.borrow().var_binding_indices() {
bindings[index] = Some(JsValue::Undefined);
}
self.stack
.push(Environment::Declarative(Gc::new(DeclarativeEnvironment {
bindings: GcRefCell::new(bindings),
bindings: GcRefCell::new(vec![None; num_bindings]),
compile: compile_environment,
poisoned: Cell::new(poisoned),
with: Cell::new(with),
@ -539,14 +550,9 @@ impl DeclarativeEnvironmentStack {
)
};
let mut bindings = vec![None; num_bindings];
for index in compile_environment.borrow().var_binding_indices() {
bindings[index] = Some(JsValue::Undefined);
}
self.stack
.push(Environment::Declarative(Gc::new(DeclarativeEnvironment {
bindings: GcRefCell::new(bindings),
bindings: GcRefCell::new(vec![None; num_bindings]),
compile: compile_environment,
poisoned: Cell::new(poisoned),
with: Cell::new(with),

4
boa_engine/src/vm/mod.rs

@ -33,7 +33,9 @@ pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode};
pub(crate) use {
call_frame::GeneratorResumeKind,
code_block::{create_function_object, create_generator_function_object},
code_block::{
create_function_object, create_function_object_fast, create_generator_function_object,
},
completion_record::CompletionRecord,
opcode::BindingOpcode,
};

1
boa_engine/src/vm/opcode/mod.rs

@ -1636,7 +1636,6 @@ pub(crate) enum BindingOpcode {
Let,
InitVar,
InitLet,
InitArg,
InitConst,
SetName,
}

18
boa_parser/src/parser/statement/block/tests.rs

@ -119,15 +119,6 @@ fn hoisting() {
function hello() { return 10 }
}",
vec![
Declaration::Function(Function::new(
Some(hello.into()),
FormalParameterList::default(),
vec![StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(10).into())),
))]
.into(),
))
.into(),
Statement::Var(VarDeclaration(
vec![Variable::from_identifier(
a.into(),
@ -142,6 +133,15 @@ fn hoisting() {
UpdateTarget::Identifier(Identifier::new(a)),
)))
.into(),
Declaration::Function(Function::new(
Some(hello.into()),
FormalParameterList::default(),
vec![StatementListItem::Statement(Statement::Return(
Return::new(Some(Literal::from(10).into())),
))]
.into(),
))
.into(),
],
interner,
);

2
boa_parser/src/parser/statement/mod.rs

@ -366,8 +366,6 @@ where
items.push(item);
}
items.sort_by(ast::StatementListItem::hoistable_order);
cursor.set_strict(global_strict);
Ok(ast::StatementList::new(items, strict))

18
boa_parser/src/parser/tests/mod.rs

@ -100,9 +100,7 @@ fn assign_operator_precedence() {
#[test]
fn hoisting() {
let interner = &mut Interner::default();
let hello = interner
.get_or_intern_static("hello", utf16!("hello"))
.into();
let hello = interner.get_or_intern_static("hello", utf16!("hello"));
let a = interner.get_or_intern_static("a", utf16!("a"));
check_script_parser(
r"
@ -111,16 +109,10 @@ fn hoisting() {
function hello() { return 10 }",
vec![
Declaration::Function(Function::new(
Some(hello),
FormalParameterList::default(),
vec![Statement::Return(Return::new(Some(Literal::from(10).into()))).into()].into(),
))
.into(),
Statement::Var(VarDeclaration(
vec![Variable::from_identifier(
a.into(),
Some(Call::new(hello.into(), Box::default()).into()),
Some(Call::new(Identifier::new(hello).into(), Box::default()).into()),
)]
.try_into()
.unwrap(),
@ -131,6 +123,12 @@ fn hoisting() {
UpdateTarget::Identifier(Identifier::new(a)),
)))
.into(),
Declaration::Function(Function::new(
Some(hello.into()),
FormalParameterList::default(),
vec![Statement::Return(Return::new(Some(Literal::from(10).into()))).into()].into(),
))
.into(),
],
interner,
);

Loading…
Cancel
Save