Browse Source

Implement missing vm operations (#1697)

This implements most of the missing vm operations and fixes most of the panics.

The early errors for declarations in `for in` and `for of` loops are moved to the parser. The content of those `Node`s is changed accordingly.
pull/1716/head
raskad 3 years ago
parent
commit
ca922310f2
  1. 10
      boa/src/builtins/iterable/mod.rs
  2. 696
      boa/src/bytecompiler.rs
  3. 4
      boa/src/context.rs
  4. 31
      boa/src/object/internal_methods/function.rs
  5. 2
      boa/src/object/operations.rs
  6. 22
      boa/src/syntax/ast/node/declaration/mod.rs
  7. 73
      boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs
  8. 73
      boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs
  9. 28
      boa/src/syntax/ast/node/iteration/mod.rs
  10. 6
      boa/src/syntax/ast/node/new/mod.rs
  11. 25
      boa/src/syntax/ast/node/template/mod.rs
  12. 84
      boa/src/syntax/parser/statement/iteration/for_statement.rs
  13. 4
      boa/src/vm/call_frame.rs
  14. 206
      boa/src/vm/code_block.rs
  15. 374
      boa/src/vm/mod.rs
  16. 179
      boa/src/vm/opcode.rs

10
boa/src/builtins/iterable/mod.rs

@ -195,6 +195,16 @@ impl IteratorRecord {
}
}
#[cfg(feature = "vm")]
pub(crate) fn iterator_object(&self) -> &JsValue {
&self.iterator_object
}
#[cfg(feature = "vm")]
pub(crate) fn next_function(&self) -> &JsValue {
&self.next_function
}
/// Get the next value in the iterator
///
/// More information:

696
boa/src/bytecompiler.rs

@ -4,6 +4,9 @@ use crate::{
builtins::function::ThisMode,
syntax::ast::{
node::{
declaration::{BindingPatternTypeArray, BindingPatternTypeObject, DeclarationPattern},
iteration::IterableLoopInitializer,
template::TemplateElement,
Declaration, GetConstField, GetField, MethodDefinitionKind, PropertyDefinition,
PropertyName, StatementList,
},
@ -31,10 +34,17 @@ struct Label {
struct JumpControlInfo {
label: Option<Box<str>>,
start_address: u32,
is_loop: bool,
kind: JumpControlInfoKind,
breaks: Vec<Label>,
}
#[derive(Debug, Clone, PartialEq)]
enum JumpControlInfoKind {
Loop,
Switch,
Try,
}
#[derive(Debug, Clone, Copy)]
enum Access<'a> {
Variable { index: u32 },
@ -231,7 +241,7 @@ impl ByteCompiler {
self.jump_info.push(JumpControlInfo {
label,
start_address,
is_loop: true,
kind: JumpControlInfoKind::Loop,
breaks: Vec::new(),
})
}
@ -240,7 +250,7 @@ impl ByteCompiler {
fn pop_loop_control_info(&mut self) {
let loop_info = self.jump_info.pop().unwrap();
assert!(loop_info.is_loop);
assert!(loop_info.kind == JumpControlInfoKind::Loop);
for label in loop_info.breaks {
self.patch_jump(label);
@ -252,7 +262,7 @@ impl ByteCompiler {
self.jump_info.push(JumpControlInfo {
label,
start_address,
is_loop: false,
kind: JumpControlInfoKind::Switch,
breaks: Vec::new(),
})
}
@ -261,13 +271,48 @@ impl ByteCompiler {
fn pop_switch_control_info(&mut self) {
let info = self.jump_info.pop().unwrap();
assert!(!info.is_loop);
assert!(info.kind == JumpControlInfoKind::Switch);
for label in info.breaks {
self.patch_jump(label);
}
}
#[inline]
fn push_try_control_info(&mut self) {
self.jump_info.push(JumpControlInfo {
label: None,
start_address: u32::MAX,
kind: JumpControlInfoKind::Try,
breaks: Vec::new(),
})
}
#[inline]
fn pop_try_control_info(&mut self, finally_start_address: Option<u32>) {
let mut info = self.jump_info.pop().unwrap();
assert!(info.kind == JumpControlInfoKind::Try);
if let Some(finally_start_address) = finally_start_address {
let mut breaks = Vec::with_capacity(info.breaks.len());
let finally_end = self.jump_with_custom_opcode(Opcode::FinallyJump);
for label in info.breaks {
if label.index < finally_start_address {
self.patch_jump_with_target(label, finally_start_address);
breaks.push(finally_end);
} else {
breaks.push(label);
}
}
if let Some(jump_info) = self.jump_info.last_mut() {
jump_info.breaks.append(&mut breaks);
}
} else if let Some(jump_info) = self.jump_info.last_mut() {
jump_info.breaks.append(&mut info.breaks);
}
}
#[inline]
fn compile_access<'a>(&mut self, node: &'a Node) -> Access<'a> {
match node {
@ -374,7 +419,7 @@ impl ByteCompiler {
self.emit(Opcode::Inc, &[]);
let access = self.compile_access(unary.target());
self.access_set(access, None, use_expr);
self.access_set(access, None, true);
None
}
UnaryOp::DecrementPre => {
@ -382,7 +427,7 @@ impl ByteCompiler {
self.emit(Opcode::Dec, &[]);
let access = self.compile_access(unary.target());
self.access_set(access, None, use_expr);
self.access_set(access, None, true);
None
}
UnaryOp::IncrementPost => {
@ -392,10 +437,6 @@ impl ByteCompiler {
let access = self.compile_access(unary.target());
self.access_set(access, None, false);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
None
}
UnaryOp::DecrementPost => {
@ -405,10 +446,6 @@ impl ByteCompiler {
let access = self.compile_access(unary.target());
self.access_set(access, None, false);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
None
}
UnaryOp::Delete => match unary.target() {
@ -512,10 +549,13 @@ impl ByteCompiler {
self.patch_jump(exit);
}
LogOp::Or => {
self.emit_opcode(Opcode::Dup);
let exit = self.jump_with_custom_opcode(Opcode::LogicalOr);
self.emit_opcode(Opcode::Pop);
self.compile_expr(binary.rhs(), true);
self.emit(Opcode::ToBoolean, &[]);
self.emit_opcode(Opcode::Dup);
self.patch_jump(exit);
self.emit_opcode(Opcode::Pop);
}
LogOp::Coalesce => {
let exit = self.jump_with_custom_opcode(Opcode::Coalesce);
@ -706,8 +746,12 @@ impl ByteCompiler {
}
}
}
// TODO: Spread Object
PropertyDefinition::SpreadObject(_) => todo!(),
PropertyDefinition::SpreadObject(expr) => {
self.compile_expr(expr, true);
self.emit_opcode(Opcode::Swap);
self.emit(Opcode::CopyDataProperties, &[0]);
self.emit_opcode(Opcode::Pop);
}
}
}
@ -746,16 +790,17 @@ impl ByteCompiler {
}
}
Node::ArrayDecl(array) => {
let mut count = 0;
for element in array.as_ref().iter().rev() {
self.emit_opcode(Opcode::PushNewArray);
for element in array.as_ref() {
self.compile_expr(element, true);
if let Node::Spread(_) = element {
todo!("array with spread element");
self.emit_opcode(Opcode::InitIterator);
self.emit_opcode(Opcode::PushIteratorToArray);
} else {
self.compile_expr(element, true);
self.emit_opcode(Opcode::PushValueToArray);
}
count += 1;
}
self.emit(Opcode::PushNewArray, &[count]);
if !use_expr {
self.emit(Opcode::Pop, &[]);
@ -764,13 +809,54 @@ impl ByteCompiler {
Node::This => {
self.access_get(Access::This, use_expr);
}
Node::Spread(spread) => self.compile_expr(spread.val(), true),
Node::FunctionExpr(_function) => self.function(expr, use_expr),
Node::ArrowFunctionDecl(_function) => self.function(expr, use_expr),
Node::Call(call) => {
for arg in call.args().iter().rev() {
self.compile_expr(arg, true);
Node::Call(_) => self.call(expr, use_expr),
Node::New(_) => self.call(expr, use_expr),
Node::TemplateLit(template_literal) => {
for element in template_literal.elements().iter().rev() {
match element {
TemplateElement::String(s) => {
self.emit_push_literal(Literal::String(s.as_ref().into()))
}
TemplateElement::Expr(expr) => {
self.compile_expr(expr, true);
}
match call.expr() {
}
}
self.emit(
Opcode::ConcatToString,
&[template_literal.elements().len() as u32],
);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
// TODO: implement AsyncFunctionExpr
Node::AsyncFunctionExpr(_) => {
self.emit_opcode(Opcode::PushUndefined);
}
// TODO: implement AwaitExpr
Node::AwaitExpr(_) => {
self.emit_opcode(Opcode::PushUndefined);
}
// TODO: implement GeneratorExpr
Node::GeneratorExpr(_) => {
self.emit_opcode(Opcode::PushUndefined);
}
// TODO: implement AsyncGeneratorExpr
Node::AsyncGeneratorExpr(_) => {
self.emit_opcode(Opcode::PushUndefined);
}
// TODO: implement Yield
Node::Yield(_) => {
self.emit_opcode(Opcode::PushUndefined);
}
Node::TaggedTemplate(template) => {
match template.tag() {
Node::GetConstField(field) => {
self.compile_expr(field.obj(), true);
self.emit(Opcode::Dup, &[]);
@ -785,17 +871,39 @@ impl ByteCompiler {
self.emit(Opcode::GetPropertyByValue, &[]);
}
expr => {
self.emit(Opcode::This, &[]);
self.compile_expr(expr, true);
self.emit_opcode(Opcode::This);
self.emit_opcode(Opcode::Swap);
}
}
self.emit(Opcode::Call, &[call.args().len() as u32]);
if !use_expr {
self.emit(Opcode::Pop, &[]);
for expr in template.exprs().iter().rev() {
self.compile_expr(expr, true);
}
self.emit_opcode(Opcode::PushNewArray);
for raw in template.raws() {
self.emit_push_literal(Literal::String(raw.as_ref().into()));
self.emit_opcode(Opcode::PushValueToArray);
}
self.emit_opcode(Opcode::PushNewArray);
for cooked in template.cookeds() {
if let Some(cooked) = cooked {
self.emit_push_literal(Literal::String(cooked.as_ref().into()));
} else {
self.emit_opcode(Opcode::PushUndefined);
}
expr => todo!("TODO compile: {}", expr),
self.emit_opcode(Opcode::PushValueToArray);
}
self.emit_opcode(Opcode::Dup);
let index = self.get_or_insert_name("raw");
self.emit(Opcode::SetPropertyByName, &[index]);
self.emit(Opcode::Call, &[(template.exprs().len() + 1) as u32]);
}
_ => unreachable!(),
}
}
@ -806,24 +914,31 @@ impl ByteCompiler {
for decl in list.as_ref() {
match decl {
Declaration::Identifier { ident, .. } => {
if ident.as_ref() == "arguments" {
self.code_block.lexical_name_argument = true;
}
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::DefVar, &[index]);
if let Some(expr) = decl.init() {
self.compile_expr(expr, true);
self.emit(Opcode::InitLexical, &[index]);
};
self.emit(Opcode::DefInitVar, &[index]);
} else {
self.emit(Opcode::DefVar, &[index]);
}
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
let index = self.get_or_insert_name(ident);
self.emit(Opcode::DefVar, &[index]);
if pattern.idents().contains(&"arguments") {
self.code_block.lexical_name_argument = true;
}
if let Some(expr) = decl.init() {
self.compile_expr(expr, true);
self.emit(Opcode::InitLexical, &[index]);
if let Some(init) = decl.init() {
self.compile_expr(init, true);
} else {
self.emit_opcode(Opcode::PushUndefined);
};
}
self.compile_declaration_pattern(pattern, Opcode::DefInitVar);
}
}
}
@ -832,24 +947,31 @@ impl ByteCompiler {
for decl in list.as_ref() {
match decl {
Declaration::Identifier { ident, .. } => {
if ident.as_ref() == "arguments" {
self.code_block.lexical_name_argument = true;
}
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::DefLet, &[index]);
if let Some(expr) = decl.init() {
self.compile_expr(expr, true);
self.emit(Opcode::InitLexical, &[index]);
};
self.emit(Opcode::DefInitLet, &[index]);
} else {
self.emit(Opcode::DefLet, &[index]);
}
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
let index = self.get_or_insert_name(ident);
self.emit(Opcode::DefLet, &[index]);
if pattern.idents().contains(&"arguments") {
self.code_block.lexical_name_argument = true;
}
if let Some(expr) = decl.init() {
self.compile_expr(expr, true);
self.emit(Opcode::InitLexical, &[index]);
if let Some(init) = decl.init() {
self.compile_expr(init, true);
} else {
self.emit_opcode(Opcode::PushUndefined);
};
}
self.compile_declaration_pattern(pattern, Opcode::DefInitLet);
}
}
}
@ -858,24 +980,29 @@ impl ByteCompiler {
for decl in list.as_ref() {
match decl {
Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::DefConst, &[index]);
if ident.as_ref() == "arguments" {
self.code_block.lexical_name_argument = true;
}
if let Some(expr) = decl.init() {
self.compile_expr(expr, true);
self.emit(Opcode::InitLexical, &[index]);
};
let index = self.get_or_insert_name(ident.as_ref());
let init = decl
.init()
.expect("const declaration must have initializer");
self.compile_expr(init, true);
self.emit(Opcode::DefInitConst, &[index]);
}
Declaration::Pattern(pattern) => {
for ident in pattern.idents() {
let index = self.get_or_insert_name(ident);
self.emit(Opcode::DefConst, &[index]);
if pattern.idents().contains(&"arguments") {
self.code_block.lexical_name_argument = true;
}
if let Some(expr) = decl.init() {
self.compile_expr(expr, true);
self.emit(Opcode::InitLexical, &[index]);
if let Some(init) = decl.init() {
self.compile_expr(init, true);
} else {
self.emit_opcode(Opcode::PushUndefined);
};
}
self.compile_declaration_pattern(pattern, Opcode::DefInitConst);
}
}
}
@ -898,6 +1025,149 @@ impl ByteCompiler {
}
}
}
Node::ForLoop(for_loop) => {
self.emit_opcode(Opcode::PushDeclarativeEnvironment);
if let Some(init) = for_loop.init() {
self.compile_stmt(init, false);
}
let initial_jump = self.jump();
let start_address = self.next_opcode_location();
self.push_loop_control_info(for_loop.label().map(Into::into), start_address);
if let Some(final_expr) = for_loop.final_expr() {
self.compile_expr(final_expr, false);
}
self.patch_jump(initial_jump);
if let Some(condition) = for_loop.condition() {
self.compile_expr(condition, true);
} else {
self.emit_opcode(Opcode::PushTrue);
}
let exit = self.jump_if_false();
self.compile_stmt(for_loop.body(), false);
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::PopEnvironment);
}
Node::ForInLoop(for_in_loop) => {
self.compile_expr(for_in_loop.expr(), true);
let early_exit = self.jump_with_custom_opcode(Opcode::ForInLoopInitIterator);
let start_address = self.next_opcode_location();
self.push_loop_control_info(for_in_loop.label().map(Into::into), start_address);
self.emit_opcode(Opcode::PushDeclarativeEnvironment);
let exit = self.jump_with_custom_opcode(Opcode::ForInLoopNext);
match for_in_loop.init() {
IterableLoopInitializer::Identifier(ref ident) => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]);
}
IterableLoopInitializer::Var(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]);
}
Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitVar);
}
},
IterableLoopInitializer::Let(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]);
}
Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitLet);
}
},
IterableLoopInitializer::Const(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]);
}
Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitConst);
}
},
}
self.compile_stmt(for_in_loop.body(), false);
self.emit_opcode(Opcode::PopEnvironment);
self.emit(Opcode::Jump, &[start_address]);
self.pop_loop_control_info();
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
self.patch_jump(exit);
self.patch_jump(early_exit);
}
Node::ForOfLoop(for_of_loop) => {
self.compile_expr(for_of_loop.iterable(), true);
self.emit_opcode(Opcode::InitIterator);
let start_address = self.next_opcode_location();
self.push_loop_control_info(for_of_loop.label().map(Into::into), start_address);
self.emit_opcode(Opcode::PushDeclarativeEnvironment);
let exit = self.jump_with_custom_opcode(Opcode::ForInLoopNext);
match for_of_loop.init() {
IterableLoopInitializer::Identifier(ref ident) => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]);
}
IterableLoopInitializer::Var(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]);
}
Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitVar);
}
},
IterableLoopInitializer::Let(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]);
}
Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitLet);
}
},
IterableLoopInitializer::Const(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]);
}
Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitConst);
}
},
}
self.compile_stmt(for_of_loop.body(), false);
self.emit_opcode(Opcode::PopEnvironment);
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
self.pop_loop_control_info();
}
Node::WhileLoop(while_) => {
let start_address = self.next_opcode_location();
self.push_loop_control_info(while_.label().map(Into::into), start_address);
@ -911,19 +1181,31 @@ impl ByteCompiler {
self.pop_loop_control_info();
}
Node::DoWhileLoop(do_while) => {
let initial_label = self.jump();
let start_address = self.next_opcode_location();
self.push_loop_control_info(do_while.label().map(Into::into), start_address);
self.compile_stmt(do_while.body(), false);
let condition_label_address = self.next_opcode_location();
self.compile_expr(do_while.cond(), true);
self.emit(Opcode::JumpIfTrue, &[start_address]);
let exit = self.jump_if_false();
self.patch_jump(initial_label);
self.compile_stmt(do_while.body(), false);
self.emit(Opcode::Jump, &[condition_label_address]);
self.pop_loop_control_info();
self.patch_jump(exit);
}
Node::Continue(node) => {
let label = self.jump();
let mut items = self.jump_info.iter_mut().rev().filter(|info| info.is_loop);
let mut items = self
.jump_info
.iter_mut()
.rev()
.filter(|info| info.kind == JumpControlInfoKind::Loop);
let target = if node.label().is_none() {
items.next()
} else {
@ -948,9 +1230,11 @@ impl ByteCompiler {
}
}
Node::Block(block) => {
self.emit_opcode(Opcode::PushDeclarativeEnvironment);
for node in block.items() {
self.compile_stmt(node, false);
}
self.emit_opcode(Opcode::PopEnvironment);
}
Node::Throw(throw) => {
self.compile_expr(throw.expr(), true);
@ -992,6 +1276,74 @@ impl ByteCompiler {
}
self.emit(Opcode::Return, &[]);
}
Node::Try(t) => {
self.push_try_control_info();
let try_start = self.jump_with_custom_opcode(Opcode::TryStart);
self.emit_opcode(Opcode::PushDeclarativeEnvironment);
for node in t.block().items() {
self.compile_stmt(node, false);
}
self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::TryEnd);
let finally = self.jump();
self.patch_jump(try_start);
if let Some(catch) = t.catch() {
self.emit_opcode(Opcode::PushDeclarativeEnvironment);
if let Some(decl) = catch.parameter() {
match decl {
Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::DefInitLet, &[index]);
}
Declaration::Pattern(pattern) => {
let idents = pattern.idents();
for (i, ident) in idents.iter().enumerate() {
if i < idents.len() {
self.emit_opcode(Opcode::Dup);
}
let index = self.get_or_insert_name(ident);
self.emit(Opcode::DefInitLet, &[index]);
}
}
}
}
for node in catch.block().items() {
self.compile_stmt(node, false);
}
self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::TryEnd);
}
self.patch_jump(finally);
if let Some(finally) = t.finally() {
self.emit_opcode(Opcode::FinallyStart);
let finally_start_address = self.next_opcode_location();
for node in finally.items() {
self.compile_stmt(node, false);
}
self.emit_opcode(Opcode::FinallyEnd);
self.pop_try_control_info(Some(finally_start_address));
} else {
self.pop_try_control_info(None);
}
}
// TODO: implement AsyncFunctionDecl
Node::AsyncFunctionDecl(_) => {
self.emit_opcode(Opcode::PushUndefined);
}
// TODO: implement GeneratorDecl
Node::GeneratorDecl(_) => {
self.emit_opcode(Opcode::PushUndefined);
}
// TODO: implement AsyncGeneratorDecl
Node::AsyncGeneratorDecl(_) => {
self.emit_opcode(Opcode::PushUndefined);
}
Node::Empty => {}
expr => self.compile_expr(expr, use_expr),
}
@ -1075,8 +1427,212 @@ impl ByteCompiler {
}
}
pub(crate) fn call(&mut self, node: &Node, use_expr: bool) {
#[derive(PartialEq)]
enum CallKind {
Call,
New,
}
let (call, kind) = match node {
Node::Call(call) => (call, CallKind::Call),
Node::New(new) => (new.call(), CallKind::New),
_ => unreachable!(),
};
match call.expr() {
Node::GetConstField(field) => {
self.compile_expr(field.obj(), true);
self.emit(Opcode::Dup, &[]);
let index = self.get_or_insert_name(field.field());
self.emit(Opcode::GetPropertyByName, &[index]);
}
Node::GetField(field) => {
self.compile_expr(field.obj(), true);
self.emit(Opcode::Dup, &[]);
self.compile_expr(field.field(), true);
self.emit(Opcode::Swap, &[]);
self.emit(Opcode::GetPropertyByValue, &[]);
}
expr => {
self.compile_expr(expr, true);
if kind == CallKind::Call {
self.emit_opcode(Opcode::This);
self.emit_opcode(Opcode::Swap);
}
}
}
for arg in call.args().iter().rev() {
self.compile_expr(arg, true);
}
let last_is_rest_parameter = matches!(call.args().last(), Some(Node::Spread(_)));
match kind {
CallKind::Call if last_is_rest_parameter => {
self.emit(Opcode::CallWithRest, &[call.args().len() as u32])
}
CallKind::Call => self.emit(Opcode::Call, &[call.args().len() as u32]),
CallKind::New if last_is_rest_parameter => {
self.emit(Opcode::NewWithRest, &[call.args().len() as u32])
}
CallKind::New => self.emit(Opcode::New, &[call.args().len() as u32]),
}
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
#[inline]
pub fn finish(self) -> CodeBlock {
self.code_block
}
#[inline]
fn compile_declaration_pattern(&mut self, pattern: &DeclarationPattern, def: Opcode) {
match pattern {
DeclarationPattern::Object(pattern) => {
let skip_init = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
if let Some(init) = pattern.init() {
self.compile_expr(init, true);
} else {
self.emit_opcode(Opcode::PushUndefined);
}
self.patch_jump(skip_init);
self.emit_opcode(Opcode::ValueNotNullOrUndefined);
self.emit_opcode(Opcode::RequireObjectCoercible);
for binding in pattern.bindings() {
use BindingPatternTypeObject::*;
match binding {
// ObjectBindingPattern : { }
Empty => {}
// SingleNameBinding : BindingIdentifier Initializer[opt]
SingleName {
ident,
property_name,
default_init,
} => {
self.emit_opcode(Opcode::Dup);
let index = self.get_or_insert_name(property_name);
self.emit(Opcode::GetPropertyByName, &[index]);
if let Some(init) = default_init {
let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
self.emit_opcode(Opcode::Pop);
self.compile_expr(init, true);
self.patch_jump(skip);
}
let index = self.get_or_insert_name(ident);
self.emit(def, &[index]);
}
// BindingRestProperty : ... BindingIdentifier
RestProperty {
ident,
excluded_keys,
} => {
self.emit_opcode(Opcode::Dup);
self.emit_opcode(Opcode::PushEmptyObject);
for key in excluded_keys {
self.emit_push_literal(Literal::String(key.as_ref().into()));
}
self.emit(Opcode::CopyDataProperties, &[excluded_keys.len() as u32]);
let index = self.get_or_insert_name(ident);
self.emit(def, &[index]);
}
BindingPattern {
ident,
pattern,
default_init,
} => {
self.emit_opcode(Opcode::Dup);
let index = self.get_or_insert_name(ident);
self.emit(Opcode::GetPropertyByName, &[index]);
if let Some(init) = default_init {
let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
self.emit_opcode(Opcode::Pop);
self.compile_expr(init, true);
self.patch_jump(skip);
} else {
self.emit_opcode(Opcode::PushUndefined);
}
self.compile_declaration_pattern(pattern, def);
}
}
}
self.emit_opcode(Opcode::Pop);
}
DeclarationPattern::Array(pattern) => {
let skip_init = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
if let Some(init) = pattern.init() {
self.compile_expr(init, true);
} else {
self.emit_opcode(Opcode::PushUndefined);
}
self.patch_jump(skip_init);
self.emit_opcode(Opcode::ValueNotNullOrUndefined);
self.emit_opcode(Opcode::InitIterator);
for binding in pattern.bindings() {
use BindingPatternTypeArray::*;
match binding {
// ArrayBindingPattern : [ ]
Empty => {}
// ArrayBindingPattern : [ Elision ]
Elision => {
self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(Opcode::Pop);
}
// SingleNameBinding : BindingIdentifier Initializer[opt]
SingleName {
ident,
default_init,
} => {
self.emit_opcode(Opcode::IteratorNext);
if let Some(init) = default_init {
let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
self.emit_opcode(Opcode::Pop);
self.compile_expr(init, true);
self.patch_jump(skip);
}
let index = self.get_or_insert_name(ident);
self.emit(def, &[index]);
}
// BindingElement : BindingPattern Initializer[opt]
BindingPattern { pattern } => {
self.emit_opcode(Opcode::IteratorNext);
self.compile_declaration_pattern(pattern, def)
}
// BindingRestElement : ... BindingIdentifier
SingleNameRest { ident } => {
self.emit_opcode(Opcode::IteratorToArray);
let index = self.get_or_insert_name(ident);
self.emit(def, &[index]);
}
// BindingRestElement : ... BindingPattern
BindingPatternRest { pattern } => {
self.emit_opcode(Opcode::IteratorToArray);
self.compile_declaration_pattern(pattern, def);
}
}
}
self.emit_opcode(Opcode::Pop);
}
}
}
}

4
boa/src/context.rs

@ -1068,8 +1068,10 @@ impl Context {
this: global_object,
pc: 0,
fp,
exit_on_return: true,
environment,
catch: None,
pop_env_on_return: 0,
finally_no_jump: false,
});
let result = self.run();

31
boa/src/object/internal_methods/function.rs

@ -1,18 +1,26 @@
use crate::{
builtins::function::{Captures, ClosureFunctionSignature, Function, NativeFunctionSignature},
object::{
internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS},
JsObject,
},
Context, JsResult, JsValue,
};
#[cfg(not(feature = "vm"))]
use crate::{
builtins::function::{
arguments::Arguments, Captures, ClosureFunctionSignature, Function, NativeFunctionSignature,
},
context::StandardObjects,
environment::{
function_environment_record::{BindingStatus, FunctionEnvironmentRecord},
lexical_environment::Environment,
},
exec::{Executable, InterpreterState},
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
object::{internal_methods::get_prototype_from_constructor, ObjectData},
syntax::ast::node::RcStatementList,
Context, JsResult, JsValue,
};
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
use crate::{builtins::function::arguments::Arguments, context::StandardObjects};
/// Definitions of the internal object methods for function objects.
///
/// More information:
@ -46,7 +54,10 @@ fn function_call(
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
call_construct(obj, this, args, context, false)
#[cfg(not(feature = "vm"))]
return call_construct(obj, this, args, context, false);
#[cfg(feature = "vm")]
return obj.call_internal(this, args, context);
}
/// Construct an instance of this object with the specified arguments.
@ -63,7 +74,10 @@ fn function_construct(
new_target: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
call_construct(obj, new_target, args, context, true)
#[cfg(not(feature = "vm"))]
return call_construct(obj, new_target, args, context, true);
#[cfg(feature = "vm")]
return obj.construct_internal(args, new_target, context);
}
/// Internal implementation of [`call`](#method.call) and [`construct`](#method.construct).
@ -77,6 +91,7 @@ fn function_construct(
/// <https://tc39.es/ecma262/#sec-runtime-semantics-evaluatebody>
/// <https://tc39.es/ecma262/#sec-ordinarycallevaluatebody>
#[track_caller]
#[cfg(not(feature = "vm"))]
pub(super) fn call_construct(
obj: &JsObject,
this_target: &JsValue,

2
boa/src/object/operations.rs

@ -259,7 +259,6 @@ impl JsObject {
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
#[track_caller]
#[inline]
#[cfg(not(feature = "vm"))]
pub fn call(
&self,
this: &JsValue,
@ -283,7 +282,6 @@ impl JsObject {
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
#[track_caller]
#[inline]
#[cfg(not(feature = "vm"))]
pub fn construct(
&self,
args: &[JsValue],

22
boa/src/syntax/ast/node/declaration/mod.rs

@ -437,10 +437,17 @@ impl DeclarationPatternObject {
/// Gets the initialization node for the object binding pattern, if any.
#[inline]
pub(in crate::syntax) fn init(&self) -> Option<&Node> {
pub(crate) fn init(&self) -> Option<&Node> {
self.init.as_ref()
}
/// Gets the bindings for the object binding pattern.
#[inline]
#[cfg(feature = "vm")]
pub(crate) fn bindings(&self) -> &Vec<BindingPatternTypeObject> {
&self.bindings
}
/// Initialize the values of an object binding pattern.
///
/// More information:
@ -560,7 +567,7 @@ impl DeclarationPatternObject {
/// Gets the list of identifiers declared by the object binding pattern.
#[inline]
pub(in crate::syntax) fn idents(&self) -> Vec<&str> {
pub(crate) fn idents(&self) -> Vec<&str> {
let mut idents = Vec::new();
for binding in &self.bindings {
@ -645,10 +652,17 @@ impl DeclarationPatternArray {
/// Gets the initialization node for the array binding pattern, if any.
#[inline]
pub(in crate::syntax) fn init(&self) -> Option<&Node> {
pub(crate) fn init(&self) -> Option<&Node> {
self.init.as_ref()
}
/// Gets the bindings for the array binding pattern.
#[inline]
#[cfg(feature = "vm")]
pub(crate) fn bindings(&self) -> &Vec<BindingPatternTypeArray> {
&self.bindings
}
/// Initialize the values of an array binding pattern.
///
/// More information:
@ -847,7 +861,7 @@ impl DeclarationPatternArray {
/// Gets the list of identifiers declared by the array binding pattern.
#[inline]
pub(in crate::syntax) fn idents(&self) -> Vec<&str> {
pub(crate) fn idents(&self) -> Vec<&str> {
let mut idents = Vec::new();
for binding in &self.bindings {

73
boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs

@ -6,7 +6,7 @@ use crate::{
},
exec::{Executable, InterpreterState},
gc::{Finalize, Trace},
syntax::ast::node::{Declaration, Node},
syntax::ast::node::{iteration::IterableLoopInitializer, Declaration, Node},
BoaProfiler, Context, JsResult, JsValue,
};
use std::fmt;
@ -17,29 +17,28 @@ use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct ForInLoop {
variable: Box<Node>,
init: Box<IterableLoopInitializer>,
expr: Box<Node>,
body: Box<Node>,
label: Option<Box<str>>,
}
impl ForInLoop {
pub fn new<V, I, B>(variable: V, expr: I, body: B) -> Self
pub fn new<I, B>(init: IterableLoopInitializer, expr: I, body: B) -> Self
where
V: Into<Node>,
I: Into<Node>,
B: Into<Node>,
{
Self {
variable: Box::new(variable.into()),
init: Box::new(init),
expr: Box::new(expr.into()),
body: Box::new(body.into()),
label: None,
}
}
pub fn variable(&self) -> &Node {
&self.variable
pub fn init(&self) -> &IterableLoopInitializer {
&self.init
}
pub fn expr(&self) -> &Node {
@ -62,7 +61,7 @@ impl ForInLoop {
if let Some(ref label) = self.label {
write!(f, "{}: ", label)?;
}
write!(f, "for ({} in {}) ", self.variable, self.expr)?;
write!(f, "for ({} in {}) ", self.init, self.expr)?;
self.body().display(f, indentation)
}
}
@ -110,8 +109,8 @@ impl Executable for ForInLoop {
}
let next_result = iterator_result.value;
match self.variable() {
Node::Identifier(ref name) => {
match self.init() {
IterableLoopInitializer::Identifier(ref name) => {
if context.has_binding(name.as_ref())? {
// Binding already exists
context.set_mutable_binding(
@ -128,13 +127,7 @@ impl Executable for ForInLoop {
context.initialize_binding(name.as_ref(), next_result)?;
}
}
Node::VarDeclList(ref list) => match list.as_ref() {
[var] => {
if var.init().is_some() {
return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer");
}
match &var {
IterableLoopInitializer::Var(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
if context.has_binding(ident.as_ref())? {
context.set_mutable_binding(
@ -169,21 +162,8 @@ impl Executable for ForInLoop {
}
}
}
}
}
_ => {
return context.throw_syntax_error(
"only one variable can be declared in the head of a for-in loop",
)
}
},
Node::LetDeclList(ref list) => match list.as_ref() {
[var] => {
if var.init().is_some() {
return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer");
}
match &var {
IterableLoopInitializer::Let(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
context.create_mutable_binding(
ident.as_ref(),
@ -202,21 +182,8 @@ impl Executable for ForInLoop {
context.initialize_binding(ident.as_ref(), value)?;
}
}
}
}
_ => {
return context.throw_syntax_error(
"only one variable can be declared in the head of a for-in loop",
)
}
},
Node::ConstDeclList(ref list) => match list.as_ref() {
[var] => {
if var.init().is_some() {
return context.throw_syntax_error("a declaration in the head of a for-in loop can't have an initializer");
}
match &var {
IterableLoopInitializer::Const(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
context.create_immutable_binding(
ident.as_ref(),
@ -235,23 +202,7 @@ impl Executable for ForInLoop {
context.initialize_binding(ident.as_ref(), value)?;
}
}
}
}
_ => {
return context.throw_syntax_error(
"only one variable can be declared in the head of a for-in loop",
)
}
},
Node::Assign(_) => {
return context.throw_syntax_error(
"a declaration in the head of a for-in loop can't have an initializer",
);
}
_ => {
return context
.throw_syntax_error("unknown left hand side in head of for-in loop")
}
}
result = self.body().run(context)?;

73
boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs

@ -5,7 +5,7 @@ use crate::{
},
exec::{Executable, InterpreterState},
gc::{Finalize, Trace},
syntax::ast::node::{Declaration, Node},
syntax::ast::node::{iteration::IterableLoopInitializer, Declaration, Node},
BoaProfiler, Context, JsResult, JsValue,
};
use std::fmt;
@ -16,29 +16,28 @@ use serde::{Deserialize, Serialize};
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct ForOfLoop {
variable: Box<Node>,
init: Box<IterableLoopInitializer>,
iterable: Box<Node>,
body: Box<Node>,
label: Option<Box<str>>,
}
impl ForOfLoop {
pub fn new<V, I, B>(variable: V, iterable: I, body: B) -> Self
pub fn new<I, B>(init: IterableLoopInitializer, iterable: I, body: B) -> Self
where
V: Into<Node>,
I: Into<Node>,
B: Into<Node>,
{
Self {
variable: Box::new(variable.into()),
init: Box::new(init),
iterable: Box::new(iterable.into()),
body: Box::new(body.into()),
label: None,
}
}
pub fn variable(&self) -> &Node {
&self.variable
pub fn init(&self) -> &IterableLoopInitializer {
&self.init
}
pub fn iterable(&self) -> &Node {
@ -61,7 +60,7 @@ impl ForOfLoop {
if let Some(ref label) = self.label {
write!(f, "{}: ", label)?;
}
write!(f, "for ({} of {}) ", self.variable, self.iterable)?;
write!(f, "for ({} of {}) ", self.init, self.iterable)?;
self.body().display(f, indentation)
}
}
@ -97,8 +96,8 @@ impl Executable for ForOfLoop {
}
let next_result = iterator_result.value;
match self.variable() {
Node::Identifier(ref name) => {
match self.init() {
IterableLoopInitializer::Identifier(ref name) => {
if context.has_binding(name.as_ref())? {
// Binding already exists
context.set_mutable_binding(
@ -115,13 +114,7 @@ impl Executable for ForOfLoop {
context.initialize_binding(name.as_ref(), next_result)?;
}
}
Node::VarDeclList(ref list) => match list.as_ref() {
[var] => {
if var.init().is_some() {
return context.throw_syntax_error("a declaration in the head of a for-of loop can't have an initializer");
}
match &var {
IterableLoopInitializer::Var(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
if context.has_binding(ident.as_ref())? {
context.set_mutable_binding(
@ -156,21 +149,8 @@ impl Executable for ForOfLoop {
}
}
}
}
}
_ => {
return context.throw_syntax_error(
"only one variable can be declared in the head of a for-of loop",
)
}
},
Node::LetDeclList(ref list) => match list.as_ref() {
[var] => {
if var.init().is_some() {
return context.throw_syntax_error("a declaration in the head of a for-of loop can't have an initializer");
}
match &var {
IterableLoopInitializer::Let(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
context.create_mutable_binding(
ident.as_ref(),
@ -189,21 +169,8 @@ impl Executable for ForOfLoop {
context.initialize_binding(ident.as_ref(), value)?;
}
}
}
}
_ => {
return context.throw_syntax_error(
"only one variable can be declared in the head of a for-of loop",
)
}
},
Node::ConstDeclList(ref list) => match list.as_ref() {
[var] => {
if var.init().is_some() {
return context.throw_syntax_error("a declaration in the head of a for-of loop can't have an initializer");
}
match &var {
IterableLoopInitializer::Const(declaration) => match declaration {
Declaration::Identifier { ident, .. } => {
context.create_immutable_binding(
ident.as_ref(),
@ -222,23 +189,7 @@ impl Executable for ForOfLoop {
context.initialize_binding(ident.as_ref(), value)?;
}
}
}
}
_ => {
return context.throw_syntax_error(
"only one variable can be declared in the head of a for-of loop",
)
}
},
Node::Assign(_) => {
return context.throw_syntax_error(
"a declaration in the head of a for-of loop can't have an initializer",
);
}
_ => {
return context
.throw_syntax_error("unknown left hand side in head of for-of loop")
}
}
result = self.body().run(context)?;

28
boa/src/syntax/ast/node/iteration/mod.rs

@ -4,6 +4,14 @@ pub use self::{
continue_node::Continue, do_while_loop::DoWhileLoop, for_in_loop::ForInLoop, for_loop::ForLoop,
for_of_loop::ForOfLoop, while_loop::WhileLoop,
};
use crate::{
gc::{Finalize, Trace},
syntax::ast::node::{declaration::Declaration, identifier::Identifier},
};
use std::fmt;
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
#[cfg(test)]
mod tests;
@ -29,6 +37,26 @@ macro_rules! handle_state_with_labels {
}};
}
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub enum IterableLoopInitializer {
Identifier(Identifier),
Var(Declaration),
Let(Declaration),
Const(Declaration),
}
impl fmt::Display for IterableLoopInitializer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IterableLoopInitializer::Identifier(identifier) => write!(f, "{}", identifier),
IterableLoopInitializer::Var(declaration) => write!(f, "var {}", declaration),
IterableLoopInitializer::Let(declaration) => write!(f, "let {}", declaration),
IterableLoopInitializer::Const(declaration) => write!(f, "const {}", declaration),
}
}
}
pub mod continue_node;
pub mod do_while_loop;
pub mod for_in_loop;

6
boa/src/syntax/ast/node/new/mod.rs

@ -44,6 +44,12 @@ impl New {
pub fn args(&self) -> &[Node] {
self.call.args()
}
/// Returns the inner call
#[cfg(feature = "vm")]
pub(crate) fn call(&self) -> &Call {
&self.call
}
}
impl Executable for New {

25
boa/src/syntax/ast/node/template/mod.rs

@ -29,6 +29,11 @@ impl TemplateLit {
pub fn new(elements: Vec<TemplateElement>) -> Self {
TemplateLit { elements }
}
#[cfg(feature = "vm")]
pub(crate) fn elements(&self) -> &Vec<TemplateElement> {
&self.elements
}
}
impl Executable for TemplateLit {
@ -87,6 +92,26 @@ impl TaggedTemplate {
exprs,
}
}
#[cfg(feature = "vm")]
pub(crate) fn tag(&self) -> &Node {
&self.tag
}
#[cfg(feature = "vm")]
pub(crate) fn raws(&self) -> &Vec<Box<str>> {
&self.raws
}
#[cfg(feature = "vm")]
pub(crate) fn cookeds(&self) -> &Vec<Option<Box<str>>> {
&self.cookeds
}
#[cfg(feature = "vm")]
pub(crate) fn exprs(&self) -> &Vec<Node> {
&self.exprs
}
}
impl Executable for TaggedTemplate {

84
boa/src/syntax/parser/statement/iteration/for_statement.rs

@ -7,13 +7,13 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for
//! [spec]: https://tc39.es/ecma262/#sec-for-statement
use crate::syntax::lexer::TokenKind;
use crate::{
syntax::{
ast::{
node::{ForInLoop, ForLoop, ForOfLoop, Node},
node::{iteration::IterableLoopInitializer, ForInLoop, ForLoop, ForOfLoop, Node},
Const, Keyword, Punctuator,
},
lexer::{Error as LexError, Position, TokenKind},
parser::{
expression::Expression,
statement::declaration::Declaration,
@ -23,7 +23,6 @@ use crate::{
},
BoaProfiler,
};
use std::io::Read;
/// For statement parsing
@ -70,7 +69,10 @@ where
fn parse(self, cursor: &mut Cursor<R>) -> Result<Self::Output, ParseError> {
let _timer = BoaProfiler::global().start_event("ForStatement", "Parsing");
cursor.expect(Keyword::For, "for statement")?;
cursor.expect(Punctuator::OpenParen, "for statement")?;
let init_position = cursor
.expect(Punctuator::OpenParen, "for statement")?
.span()
.end();
let init = match cursor.peek(0)?.ok_or(ParseError::AbruptEnd)?.kind() {
TokenKind::Keyword(Keyword::Var) => {
@ -90,6 +92,8 @@ where
match cursor.peek(0)? {
Some(tok) if tok.kind() == &TokenKind::Keyword(Keyword::In) && init.is_some() => {
let init = node_to_iterable_loop_initializer(&init.unwrap(), init_position)?;
let _ = cursor.next();
let expr =
Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?;
@ -107,9 +111,11 @@ where
return Err(ParseError::wrong_function_declaration_non_strict(position));
}
return Ok(ForInLoop::new(init.unwrap(), expr, body).into());
return Ok(ForInLoop::new(init, expr, body).into());
}
Some(tok) if tok.kind() == &TokenKind::Keyword(Keyword::Of) && init.is_some() => {
let init = node_to_iterable_loop_initializer(&init.unwrap(), init_position)?;
let _ = cursor.next();
let iterable =
Expression::new(true, self.allow_yield, self.allow_await).parse(cursor)?;
@ -127,7 +133,7 @@ where
return Err(ParseError::wrong_function_declaration_non_strict(position));
}
return Ok(ForOfLoop::new(init.unwrap(), iterable, body).into());
return Ok(ForOfLoop::new(init, iterable, body).into());
}
_ => {}
}
@ -167,3 +173,69 @@ where
Ok(ForLoop::new(init, cond, step, body).into())
}
}
#[inline]
fn node_to_iterable_loop_initializer(
node: &Node,
position: Position,
) -> Result<IterableLoopInitializer, ParseError> {
match node {
Node::Identifier(ref name) => Ok(IterableLoopInitializer::Identifier(name.clone())),
Node::VarDeclList(ref list) => match list.as_ref() {
[var] => {
if var.init().is_some() {
return Err(ParseError::lex(LexError::Syntax(
"a declaration in the head of a for-of loop can't have an initializer"
.into(),
position,
)));
}
Ok(IterableLoopInitializer::Var(var.clone()))
}
_ => Err(ParseError::lex(LexError::Syntax(
"only one variable can be declared in the head of a for-of loop".into(),
position,
))),
},
Node::LetDeclList(ref list) => match list.as_ref() {
[var] => {
if var.init().is_some() {
return Err(ParseError::lex(LexError::Syntax(
"a declaration in the head of a for-of loop can't have an initializer"
.into(),
position,
)));
}
Ok(IterableLoopInitializer::Let(var.clone()))
}
_ => Err(ParseError::lex(LexError::Syntax(
"only one variable can be declared in the head of a for-of loop".into(),
position,
))),
},
Node::ConstDeclList(ref list) => match list.as_ref() {
[var] => {
if var.init().is_some() {
return Err(ParseError::lex(LexError::Syntax(
"a declaration in the head of a for-of loop can't have an initializer"
.into(),
position,
)));
}
Ok(IterableLoopInitializer::Const(var.clone()))
}
_ => Err(ParseError::lex(LexError::Syntax(
"only one variable can be declared in the head of a for-of loop".into(),
position,
))),
},
Node::Assign(_) => Err(ParseError::lex(LexError::Syntax(
"a declaration in the head of a for-of loop can't have an initializer".into(),
position,
))),
_ => Err(ParseError::lex(LexError::Syntax(
"unknown left hand side in head of for-of loop".into(),
position,
))),
}
}

4
boa/src/vm/call_frame.rs

@ -11,7 +11,9 @@ pub struct CallFrame {
pub(crate) code: Gc<CodeBlock>,
pub(crate) pc: usize,
pub(crate) fp: usize,
pub(crate) exit_on_return: bool,
pub(crate) this: JsValue,
pub(crate) environment: Environment,
pub(crate) catch: Option<u32>,
pub(crate) pop_env_on_return: usize,
pub(crate) finally_no_jump: bool,
}

206
boa/src/vm/code_block.rs

@ -1,6 +1,7 @@
use crate::{
builtins::function::{
Captures, ClosureFunctionSignature, Function, NativeFunctionSignature, ThisMode,
arguments::Arguments, Captures, ClosureFunctionSignature, Function,
NativeFunctionSignature, ThisMode,
},
context::StandardObjects,
environment::{
@ -64,6 +65,9 @@ pub struct CodeBlock {
// Functions inside this function
pub(crate) functions: Vec<Gc<CodeBlock>>,
/// Indicates if the codeblock contains a lexical name `arguments`
pub(crate) lexical_name_argument: bool,
}
impl CodeBlock {
@ -79,6 +83,7 @@ impl CodeBlock {
constructor,
this_mode: ThisMode::Global,
params: Vec::new().into_boxed_slice(),
lexical_name_argument: false,
}
}
@ -128,16 +133,22 @@ impl CodeBlock {
ryu_js::Buffer::new().format(operand).to_string()
}
Opcode::PushLiteral
| Opcode::PushNewArray
| Opcode::Jump
| Opcode::JumpIfFalse
| Opcode::JumpIfTrue
| Opcode::JumpIfNotUndefined
| Opcode::FinallyJump
| Opcode::TryStart
| Opcode::Case
| Opcode::Default
| Opcode::LogicalAnd
| Opcode::LogicalOr
| Opcode::Coalesce
| Opcode::Call => {
| Opcode::Call
| Opcode::CallWithRest
| Opcode::New
| Opcode::NewWithRest
| Opcode::ForInLoopInitIterator
| Opcode::ForInLoopNext => {
let result = self.read::<u32>(*pc).to_string();
*pc += size_of::<u32>();
result
@ -153,16 +164,19 @@ impl CodeBlock {
)
}
Opcode::DefVar
| Opcode::DefInitVar
| Opcode::DefLet
| Opcode::DefConst
| Opcode::InitLexical
| Opcode::DefInitLet
| Opcode::DefInitConst
| Opcode::GetName
| Opcode::SetName
| Opcode::GetPropertyByName
| Opcode::SetPropertyByName
| Opcode::SetPropertyGetterByName
| Opcode::SetPropertySetterByName
| Opcode::DeletePropertyByName => {
| Opcode::DeletePropertyByName
| Opcode::ConcatToString
| Opcode::CopyDataProperties => {
let operand = self.read::<u32>(*pc);
*pc += size_of::<u32>();
format!("{:04}: '{}'", operand, self.variables[operand as usize])
@ -217,8 +231,21 @@ impl CodeBlock {
| Opcode::DeletePropertyByValue
| Opcode::ToBoolean
| Opcode::Throw
| Opcode::TryEnd
| Opcode::FinallyStart
| Opcode::FinallyEnd
| Opcode::This
| Opcode::Return
| Opcode::PushDeclarativeEnvironment
| Opcode::PopEnvironment
| Opcode::InitIterator
| Opcode::IteratorNext
| Opcode::IteratorToArray
| Opcode::RequireObjectCoercible
| Opcode::ValueNotNullOrUndefined
| Opcode::PushValueToArray
| Opcode::PushIteratorToArray
| Opcode::PushNewArray
| Opcode::Nop => String::new(),
}
}
@ -226,20 +253,31 @@ impl CodeBlock {
impl std::fmt::Display for CodeBlock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.name != "<main>" {
f.write_char('\n')?;
}
writeln!(
f,
"{:-^width$}",
format!("Compiled Output: '{}'", self.name),
width = 70
)?;
writeln!(
f,
"----------------- name '{}' (length: {}) ------------------",
self.name, self.length
" Location Count Opcode Operands"
)?;
writeln!(f, " Location Count Opcode Operands")?;
f.write_char('\n')?;
let mut pc = 0;
let mut count = 0;
while pc < self.code.len() {
let opcode: Opcode = self.code[pc].try_into().unwrap();
write!(
f,
" {:06} {:04} {:<20}",
" {:06} {:04} {:<27}",
pc,
count,
opcode.as_str()
@ -304,7 +342,7 @@ impl JsVmFunction {
let name_property = PropertyDescriptor::builder()
.value(code.name.clone())
.writable(true)
.writable(false)
.enumerable(false)
.configurable(true)
.build();
@ -334,9 +372,9 @@ impl JsVmFunction {
let prototype_property = PropertyDescriptor::builder()
.value(prototype)
.writable(false)
.writable(true)
.enumerable(false)
.configurable(true)
.configurable(false)
.build();
constructor
@ -374,7 +412,6 @@ impl JsObject {
this: &JsValue,
args: &[JsValue],
context: &mut Context,
exit_on_return: bool,
) -> JsResult<JsValue> {
let this_function_object = self.clone();
// let mut has_parameter_expressions = false;
@ -383,14 +420,25 @@ impl JsObject {
return context.throw_type_error("not a callable function");
}
let mut construct = false;
let body = {
let object = self.borrow();
let function = object.as_function().unwrap();
match function {
Function::Native { function, .. } => FunctionBody::Native {
Function::Native {
function,
constructor,
} => {
if *constructor {
construct = true;
}
FunctionBody::Native {
function: *function,
},
}
}
Function::Closure {
function, captures, ..
} => FunctionBody::Closure {
@ -406,6 +454,9 @@ impl JsObject {
};
match body {
FunctionBody::Native { function } if construct => {
function(&JsValue::undefined(), args, context)
}
FunctionBody::Native { function } => function(this, args, context),
FunctionBody::Closure { function, captures } => {
(function)(this, args, captures, context)
@ -416,7 +467,7 @@ impl JsObject {
// Create a new Function environment whose parent is set to the scope of the function declaration (self.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = FunctionEnvironmentRecord::new(
this_function_object,
this_function_object.clone(),
if !lexical_this_mode {
Some(this.clone())
} else {
@ -439,11 +490,51 @@ impl JsObject {
// Push the environment first so that it will be used by default parameters
context.push_environment(local_env.clone());
let mut arguments_in_parameter_names = false;
let mut is_simple_parameter_list = true;
let mut has_parameter_expressions = false;
for param in code.params.iter() {
has_parameter_expressions = has_parameter_expressions || param.init().is_some();
arguments_in_parameter_names =
arguments_in_parameter_names || param.names().contains(&"arguments");
is_simple_parameter_list =
is_simple_parameter_list && !param.is_rest_param() && param.init().is_none()
}
// 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)
// - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18)
//
// https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
if !lexical_this_mode
&& !arguments_in_parameter_names
&& (has_parameter_expressions || !code.lexical_name_argument)
{
// Add arguments object
let arguments_obj =
if context.strict() || code.strict || !is_simple_parameter_list {
Arguments::create_unmapped_arguments_object(args, context)
} else {
Arguments::create_mapped_arguments_object(
&this_function_object,
&code.params,
args,
&local_env,
context,
)
};
local_env.create_mutable_binding("arguments", false, true, context)?;
local_env.initialize_binding("arguments", arguments_obj.into(), context)?;
}
// Add argument bindings to the function environment
for (i, param) in code.params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
todo!("Rest parameter");
Function::add_rest_param(param, i, args, context, &local_env);
break;
}
let value = match args.get(i).cloned() {
@ -460,8 +551,10 @@ impl JsObject {
this: this.clone(),
pc: 0,
fp: context.vm.stack.len(),
exit_on_return,
environment: local_env,
catch: None,
pop_env_on_return: 0,
finally_no_jump: false,
});
let result = context.run();
@ -473,21 +566,11 @@ impl JsObject {
}
}
pub fn call(
&self,
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
self.call_internal(this, args, context, true)
}
pub(crate) fn construct_internal(
&self,
args: &[JsValue],
this_target: &JsValue,
context: &mut Context,
exit_on_return: bool,
) -> JsResult<JsValue> {
let this_function_object = self.clone();
// let mut has_parameter_expressions = false;
@ -541,7 +624,7 @@ impl JsObject {
// Create a new Function environment whose parent is set to the scope of the function declaration (self.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = FunctionEnvironmentRecord::new(
this_function_object,
this_function_object.clone(),
Some(this.clone()),
Some(environment),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
@ -560,11 +643,51 @@ impl JsObject {
// Push the environment first so that it will be used by default parameters
context.push_environment(local_env.clone());
let mut arguments_in_parameter_names = false;
let mut is_simple_parameter_list = true;
let mut has_parameter_expressions = false;
for param in code.params.iter() {
has_parameter_expressions = has_parameter_expressions || param.init().is_some();
arguments_in_parameter_names =
arguments_in_parameter_names || param.names().contains(&"arguments");
is_simple_parameter_list =
is_simple_parameter_list && !param.is_rest_param() && param.init().is_none()
}
// 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)
// - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18)
//
// https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
if !lexical_this_mode
&& !arguments_in_parameter_names
&& (has_parameter_expressions || !code.lexical_name_argument)
{
// Add arguments object
let arguments_obj =
if context.strict() || code.strict || !is_simple_parameter_list {
Arguments::create_unmapped_arguments_object(args, context)
} else {
Arguments::create_mapped_arguments_object(
&this_function_object,
&code.params,
args,
&local_env,
context,
)
};
local_env.create_mutable_binding("arguments", false, true, context)?;
local_env.initialize_binding("arguments", arguments_obj.into(), context)?;
}
// Add argument bindings to the function environment
for (i, param) in code.params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
todo!("Rest parameter");
Function::add_rest_param(param, i, args, context, &local_env);
break;
}
let value = match args.get(i).cloned() {
@ -581,25 +704,20 @@ impl JsObject {
this,
pc: 0,
fp: context.vm.stack.len(),
exit_on_return,
environment: local_env,
catch: None,
pop_env_on_return: 0,
finally_no_jump: false,
});
let _result = context.run();
let _result = context.run()?;
let result = context.get_this_binding();
context.pop_environment();
context.get_this_binding()
}
result
}
}
pub fn construct(
&self,
args: &[JsValue],
this_target: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
self.construct_internal(args, this_target, context, true)
}
}

374
boa/src/vm/mod.rs

@ -3,8 +3,14 @@
//! plus an interpreter to execute those instructions
use crate::{
builtins::Array, environment::lexical_environment::VariableScope, property::PropertyDescriptor,
vm::code_block::Readable, BoaProfiler, Context, JsResult, JsValue,
builtins::{iterable::IteratorRecord, Array, ForInIterator},
environment::{
declarative_environment_record::DeclarativeEnvironmentRecord,
lexical_environment::VariableScope,
},
property::PropertyDescriptor,
vm::code_block::Readable,
BoaProfiler, Context, JsBigInt, JsResult, JsString, JsValue,
};
use std::{convert::TryInto, mem::size_of, time::Instant};
@ -148,12 +154,32 @@ impl Context {
}
Opcode::PushEmptyObject => self.vm.push(self.construct_object()),
Opcode::PushNewArray => {
let count = self.vm.read::<u32>();
let mut elements = Vec::with_capacity(count as usize);
for _ in 0..count {
elements.push(self.vm.pop());
let array = Array::array_create(0, None, self)
.expect("Array creation with 0 length should never fail");
self.vm.push(array);
}
Opcode::PushValueToArray => {
let value = self.vm.pop();
let array = self.vm.pop();
let array = Array::add_to_array_object(&array, &[value], self)?;
self.vm.push(array);
}
Opcode::PushIteratorToArray => {
let next_function = self.vm.pop();
let for_in_iterator = self.vm.pop();
let array = self.vm.pop();
let iterator = IteratorRecord::new(for_in_iterator, next_function);
loop {
let next = iterator.next(self)?;
if next.done {
break;
} else {
Array::add_to_array_object(&array, &[next.value], self)?;
}
}
let array = Array::create_array_from_list(elements, self);
self.vm.push(array);
}
Opcode::Add => bin_op!(add),
@ -233,12 +259,22 @@ impl Context {
self.vm.push(value);
}
Opcode::Inc => {
let value = self.vm.pop().add(&JsValue::Integer(1), self)?;
self.vm.push(value);
let value = self.vm.pop();
match value.to_numeric(self)? {
crate::value::Numeric::Number(number) => self.vm.push(number + 1f64),
crate::value::Numeric::BigInt(bigint) => {
self.vm.push(JsBigInt::add(&bigint, &JsBigInt::one()))
}
}
}
Opcode::Dec => {
let value = self.vm.pop().sub(&JsValue::Integer(1), self)?;
self.vm.push(value);
let value = self.vm.pop();
match value.to_numeric(self)? {
crate::value::Numeric::Number(number) => self.vm.push(number - 1f64),
crate::value::Numeric::BigInt(bigint) => {
self.vm.push(JsBigInt::sub(&bigint, &JsBigInt::one()))
}
}
}
Opcode::LogicalNot => {
let value = self.vm.pop();
@ -259,26 +295,45 @@ impl Context {
let index = self.vm.read::<u32>();
let name = self.vm.frame().code.variables[index as usize].clone();
if !self.has_binding(name.as_ref())? {
self.create_mutable_binding(name.as_ref(), false, VariableScope::Function)?;
self.initialize_binding(name.as_ref(), JsValue::Undefined)?;
}
}
Opcode::DefInitVar => {
let index = self.vm.read::<u32>();
let name = self.vm.frame().code.variables[index as usize].clone();
let value = self.vm.pop();
if self.has_binding(name.as_ref())? {
self.set_mutable_binding(name.as_ref(), value, self.strict())?;
} else {
self.create_mutable_binding(name.as_ref(), false, VariableScope::Function)?;
self.initialize_binding(name.as_ref(), value)?;
}
}
Opcode::DefLet => {
let index = self.vm.read::<u32>();
let name = self.vm.frame().code.variables[index as usize].clone();
self.create_mutable_binding(name.as_ref(), false, VariableScope::Block)?;
self.initialize_binding(name.as_ref(), JsValue::Undefined)?;
}
Opcode::DefConst => {
Opcode::DefInitLet => {
let index = self.vm.read::<u32>();
let name = self.vm.frame().code.variables[index as usize].clone();
let value = self.vm.pop();
self.create_immutable_binding(name.as_ref(), false, VariableScope::Block)?;
self.create_mutable_binding(name.as_ref(), false, VariableScope::Block)?;
self.initialize_binding(name.as_ref(), value)?;
}
Opcode::InitLexical => {
Opcode::DefInitConst => {
let index = self.vm.read::<u32>();
let value = self.vm.pop();
let name = self.vm.frame().code.variables[index as usize].clone();
let value = self.vm.pop();
self.initialize_binding(&name, value)?;
self.create_immutable_binding(name.as_ref(), true, VariableScope::Block)?;
self.initialize_binding(name.as_ref(), value)?;
}
Opcode::GetName => {
let index = self.vm.read::<u32>();
@ -310,11 +365,13 @@ impl Context {
self.vm.frame_mut().pc = address as usize;
}
}
Opcode::JumpIfTrue => {
Opcode::JumpIfNotUndefined => {
let address = self.vm.read::<u32>();
if self.vm.pop().to_boolean() {
let value = self.vm.pop();
if !value.is_undefined() {
self.vm.frame_mut().pc = address as usize;
}
self.vm.push(value);
}
Opcode::LogicalAnd => {
let exit = self.vm.read::<u32>();
@ -509,10 +566,45 @@ impl Context {
.__delete__(&key.to_property_key(self)?, self)?;
self.vm.push(result);
}
Opcode::CopyDataProperties => {
let excluded_key_count = self.vm.read::<u32>();
let mut excluded_keys = Vec::with_capacity(excluded_key_count as usize);
for _ in 0..excluded_key_count {
excluded_keys.push(self.vm.pop().as_string().unwrap().clone());
}
let value = self.vm.pop();
let object = value.as_object().unwrap();
let rest_obj = self.vm.pop();
object.copy_data_properties(&rest_obj, excluded_keys, self)?;
self.vm.push(value);
}
Opcode::Throw => {
let value = self.vm.pop();
return Err(value);
}
Opcode::TryStart => {
let index = self.vm.read::<u32>();
self.vm.frame_mut().catch = Some(index);
self.vm.frame_mut().finally_no_jump = false;
}
Opcode::TryEnd => {
self.vm.frame_mut().catch = None;
}
Opcode::FinallyStart => {
self.vm.frame_mut().finally_no_jump = true;
}
Opcode::FinallyEnd => {
if let Some(value) = self.vm.stack.pop() {
return Err(value);
}
}
Opcode::FinallyJump => {
let address = self.vm.read::<u32>();
if !self.vm.frame().finally_no_jump {
self.vm.frame_mut().pc = address as usize;
}
self.vm.frame_mut().finally_no_jump = false;
}
Opcode::This => {
let this = self.get_this_binding()?;
self.vm.push(this);
@ -545,69 +637,251 @@ impl Context {
return Err(self.construct_range_error("Maximum call stack size exceeded"));
}
let argc = self.vm.read::<u32>();
let mut args = Vec::with_capacity(argc as usize);
for _ in 0..argc {
args.push(self.vm.pop());
}
let func = self.vm.pop();
let this = self.vm.pop();
let object = match func {
JsValue::Object(ref object) if object.is_callable() => object.clone(),
_ => return Err(self.construct_type_error("not a callable function")),
};
let result = object.__call__(&this, &args, self)?;
self.vm.push(result);
}
Opcode::CallWithRest => {
if self.vm.stack_size_limit <= self.vm.stack.len() {
return Err(self.construct_range_error("Maximum call stack size exceeded"));
}
let argc = self.vm.read::<u32>();
let mut args = Vec::with_capacity(argc as usize);
for _ in 0..argc {
for _ in 0..(argc - 1) {
args.push(self.vm.pop());
}
let rest_arg_value = self.vm.pop();
let func = self.vm.pop();
let this = self.vm.pop();
let iterator_record = rest_arg_value.get_iterator(self, None, None)?;
let mut rest_args = Vec::new();
loop {
let next = iterator_record.next(self)?;
if next.done {
break;
}
rest_args.push(next.value);
}
args.append(&mut rest_args);
let object = match func {
JsValue::Object(ref object) if object.is_callable() => object.clone(),
_ => return Err(self.construct_type_error("not a callable function")),
};
let result = object.call_internal(&this, &args, self, false)?;
let result = object.__call__(&this, &args, self)?;
self.vm.push(result);
}
Opcode::Return => {
let exit = self.vm.frame().exit_on_return;
Opcode::New => {
if self.vm.stack_size_limit <= self.vm.stack.len() {
return Err(self.construct_range_error("Maximum call stack size exceeded"));
}
let argc = self.vm.read::<u32>();
let mut args = Vec::with_capacity(argc as usize);
for _ in 0..argc {
args.push(self.vm.pop());
}
let func = self.vm.pop();
let _ = self.vm.pop_frame();
let result = func
.as_constructor()
.ok_or_else(|| self.construct_type_error("not a constructor"))
.and_then(|cons| cons.__construct__(&args, &cons.clone().into(), self))?;
self.vm.push(result);
}
Opcode::NewWithRest => {
if self.vm.stack_size_limit <= self.vm.stack.len() {
return Err(self.construct_range_error("Maximum call stack size exceeded"));
}
let argc = self.vm.read::<u32>();
let mut args = Vec::with_capacity(argc as usize);
for _ in 0..(argc - 1) {
args.push(self.vm.pop());
}
let rest_arg_value = self.vm.pop();
let func = self.vm.pop();
let iterator_record = rest_arg_value.get_iterator(self, None, None)?;
let mut rest_args = Vec::new();
loop {
let next = iterator_record.next(self)?;
if next.done {
break;
}
rest_args.push(next.value);
}
args.append(&mut rest_args);
let result = func
.as_constructor()
.ok_or_else(|| self.construct_type_error("not a constructor"))
.and_then(|cons| cons.__construct__(&args, &cons.clone().into(), self))?;
if exit {
self.vm.push(result);
}
Opcode::Return => {
for _ in 0..self.vm.frame().pop_env_on_return {
self.pop_environment();
}
self.vm.frame_mut().pop_env_on_return = 0;
let _ = self.vm.pop_frame();
return Ok(true);
}
Opcode::PushDeclarativeEnvironment => {
let env = self.get_current_environment();
self.push_environment(DeclarativeEnvironmentRecord::new(Some(env)));
self.vm.frame_mut().pop_env_on_return += 1;
}
Opcode::PopEnvironment => {
let _ = self.pop_environment();
self.vm.frame_mut().pop_env_on_return -= 1;
}
Opcode::ForInLoopInitIterator => {
let address = self.vm.read::<u32>();
Ok(false)
let object = self.vm.pop();
if object.is_null_or_undefined() {
self.vm.frame_mut().pc = address as usize;
}
/// Unwind the stack.
fn unwind(&mut self) -> bool {
let mut fp = 0;
while let Some(mut frame) = self.vm.frame.take() {
fp = frame.fp;
if frame.exit_on_return {
let object = object.to_object(self)?;
let for_in_iterator =
ForInIterator::create_for_in_iterator(JsValue::new(object), self);
let next_function = for_in_iterator
.get_property("next")
.as_ref()
.map(|p| p.expect_value())
.cloned()
.ok_or_else(|| self.construct_type_error("Could not find property `next`"))?;
self.vm.push(for_in_iterator);
self.vm.push(next_function);
}
Opcode::InitIterator => {
let iterable = self.vm.pop();
let iterator = iterable.get_iterator(self, None, None)?;
self.vm.push(iterator.iterator_object());
self.vm.push(iterator.next_function());
}
Opcode::IteratorNext => {
let next_function = self.vm.pop();
let for_in_iterator = self.vm.pop();
let iterator = IteratorRecord::new(for_in_iterator.clone(), next_function.clone());
let iterator_result = iterator.next(self)?;
self.vm.push(for_in_iterator);
self.vm.push(next_function);
self.vm.push(iterator_result.value);
}
Opcode::IteratorToArray => {
let next_function = self.vm.pop();
let for_in_iterator = self.vm.pop();
let iterator = IteratorRecord::new(for_in_iterator.clone(), next_function.clone());
let mut values = Vec::new();
loop {
let next = iterator.next(self)?;
if next.done {
break;
}
self.vm.frame = frame.prev.take();
values.push(next.value);
}
while self.vm.stack.len() > fp {
let _ = self.vm.pop();
let array = Array::array_create(0, None, self)
.expect("Array creation with 0 length should never fail");
Array::add_to_array_object(&array.clone().into(), &values, self)?;
self.vm.push(for_in_iterator);
self.vm.push(next_function);
self.vm.push(array);
}
Opcode::ForInLoopNext => {
let address = self.vm.read::<u32>();
let next_function = self.vm.pop();
let for_in_iterator = self.vm.pop();
let iterator = IteratorRecord::new(for_in_iterator.clone(), next_function.clone());
let iterator_result = iterator.next(self)?;
if iterator_result.done {
self.vm.frame_mut().pc = address as usize;
self.vm.frame_mut().pop_env_on_return -= 1;
self.pop_environment();
} else {
self.vm.push(for_in_iterator);
self.vm.push(next_function);
self.vm.push(iterator_result.value);
}
}
Opcode::ConcatToString => {
let n = self.vm.read::<u32>();
let mut s = JsString::new("");
for _ in 0..n {
let obj = self.vm.pop();
s = JsString::concat(s, obj.to_string(self)?);
}
self.vm.push(s);
}
Opcode::RequireObjectCoercible => {
let value = self.vm.pop();
let value = value.require_object_coercible(self)?;
self.vm.push(value);
}
Opcode::ValueNotNullOrUndefined => {
let value = self.vm.pop();
if value.is_null() {
return Err(self.construct_type_error("Cannot destructure 'null' value"));
}
true
if value.is_undefined() {
return Err(self.construct_type_error("Cannot destructure 'undefined' value"));
}
self.vm.push(value);
}
}
Ok(false)
}
pub(crate) fn run(&mut self) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("run", "vm");
const COLUMN_WIDTH: usize = 24;
const COLUMN_WIDTH: usize = 26;
const TIME_COLUMN_WIDTH: usize = COLUMN_WIDTH / 2;
const OPCODE_COLUMN_WIDTH: usize = COLUMN_WIDTH;
const OPERAND_COLUMN_WIDTH: usize = COLUMN_WIDTH;
const NUMBER_OF_COLUMNS: usize = 4;
let msg = if self.vm.frame().exit_on_return {
" VM Start"
} else {
if self.vm.trace {
let msg = if self.vm.frame().prev.is_some() {
" Call Frame "
} else {
" VM Start "
};
if self.vm.trace {
println!("{}\n", self.vm.frame().code);
println!(
"{:-^width$}",
@ -615,7 +889,7 @@ impl Context {
width = COLUMN_WIDTH * NUMBER_OF_COLUMNS - 10
);
println!(
"{:<time_width$} {:<opcode_width$} {:<operand_width$} Top Of Stack",
"{:<time_width$} {:<opcode_width$} {:<operand_width$} Top Of Stack\n",
"Time",
"Opcode",
"Operands",
@ -671,11 +945,21 @@ impl Context {
}
}
Err(e) => {
let should_exit = self.unwind();
if should_exit {
return Err(e);
} else {
if let Some(address) = self.vm.frame().catch {
if self.vm.frame().pop_env_on_return > 0 {
self.pop_environment();
self.vm.frame_mut().pop_env_on_return -= 1;
}
self.vm.frame_mut().pc = address as usize;
self.vm.frame_mut().catch = None;
self.vm.push(e);
} else {
for _ in 0..self.vm.frame().pop_env_on_return {
self.pop_environment();
}
self.vm.pop_frame();
return Err(e);
}
}
}

179
boa/src/vm/opcode.rs

@ -133,13 +133,21 @@ pub enum Opcode {
/// Stack: **=>** object
PushEmptyObject,
/// Push array object `{}` value on the stack.
/// Push an empty array value on the stack.
///
/// Operands: n: `u32`
///
/// Stack: v1, v1, ... vn **=>** [v1, v2, ..., vn]
/// Stack: **=>** `array`
PushNewArray,
/// Push a value to an array.
///
/// Stack: `array`, `value` **=>** `array`
PushValueToArray,
/// Push all iterator values to an array.
///
/// Stack: `array`, `iterator`, `next_function` **=>** `array`
PushIteratorToArray,
/// Binary `+` operator.
///
/// Operands:
@ -378,33 +386,40 @@ pub enum Opcode {
/// Stack: value **=>** (value - 1)
Dec,
/// Declate `var` type variable.
/// Declare `var` type variable.
///
/// Operands: name_index: `u32`
///
/// Stack: **=>**
DefVar,
/// Declate `let` type variable.
/// Declare and initialize `var` type variable.
///
/// Operands: name_index: `u32`
///
/// Stack: value **=>**
DefInitVar,
/// Declare `let` type variable.
///
/// Operands: name_index: `u32`
///
/// Stack: **=>**
DefLet,
/// Declate `const` type variable.
/// Declare and initialize `let` type variable.
///
/// Operands: name_index: `u32`
///
/// Stack: **=>**
DefConst,
/// Stack: value **=>**
DefInitLet,
/// Initialize a lexical binding.
/// Declare and initialize `const` type variable.
///
/// Operands: name_index: `u32`
///
/// Stack: **=>**
InitLexical,
/// Stack: value **=>**
DefInitConst,
/// Find a binding on the environment chain and push its value.
///
@ -510,13 +525,21 @@ pub enum Opcode {
/// Stack: key, object **=>**
DeletePropertyByValue,
/// Copy all properties of one object to another object.
///
/// Operands: number of excluded keys: `u32`
///
/// Stack: object, rest_object, excluded_key_0 ... excluded_key_n **=>** object
CopyDataProperties,
/// Unconditional jump to address.
///
/// Operands: address: `u32`
///
/// Stack: **=>**
Jump,
/// Constional jump to address.
/// Conditional jump to address.
///
/// If the value popped is [`falsy`][falsy] then jump to `address`.
///
@ -527,16 +550,14 @@ pub enum Opcode {
/// [falsy]: https://developer.mozilla.org/en-US/docs/Glossary/Falsy
JumpIfFalse,
/// Constional jump to address.
/// Conditional jump to address.
///
/// If the value popped is [`truthy`][truthy] then jump to `address`.
/// If the value popped is not undefined jump to `address`.
///
/// Operands: address: `u32`
///
/// Stack: cond **=>**
///
/// [truthy]: https://developer.mozilla.org/en-US/docs/Glossary/Truthy
JumpIfTrue,
/// Stack: value **=>** value
JumpIfNotUndefined,
/// Throw exception
///
@ -545,6 +566,25 @@ pub enum Opcode {
/// Stack: `exc` **=>**
Throw,
/// Start of a try block.
///
/// Operands: address: `u32`
TryStart,
/// End of a try block.
TryEnd,
/// Start of a finally block.
FinallyStart,
/// End of a finally block.
FinallyEnd,
/// Jump if the finally block was entered trough a break statement.
///
/// Operands: address: `u32`
FinallyJump,
/// Pops value converts it to boolean and pushes it back.
///
/// Operands:
@ -588,9 +628,82 @@ pub enum Opcode {
/// Stack: `func`, `this`, `arg1`, `arg2`,...`argn` **=>**
Call,
/// Call a function where the last argument is a rest parameter.
///
/// Operands: argc: `u32`
///
/// Stack: `func`, `this`, `arg1`, `arg2`,...`argn` **=>**
CallWithRest,
/// Call construct on a function.
///
/// Operands: argc: `u32`
///
/// Stack: `func`, `arg1`, `arg2`,...`argn` **=>**
New,
/// Call construct on a function where the last argument is a rest parameter.
///
/// Operands: argc: `u32`
///
/// Stack: `func`, `arg1`, `arg2`,...`argn` **=>**
NewWithRest,
/// Return from a function.
Return,
/// Push a declarative environment.
PushDeclarativeEnvironment,
/// Pop the current environment.
PopEnvironment,
/// Initialize the iterator for a for..in loop or jump to after the loop if object is null or undefined.
///
/// Operands: address: `u32`
///
/// Stack: `object` **=>** `for_in_iterator`, `next_function`
ForInLoopInitIterator,
/// Initialize an iterator.
///
/// Stack: `object` **=>** `iterator`, `next_function`
InitIterator,
/// Advance the iterator by one and put the value on the stack.
///
/// Stack: `iterator`, `next_function` **=>** `for_of_iterator`, `next_function`, `next_value`
IteratorNext,
/// Consume the iterator and construct and array with all the values.
///
/// Stack: `iterator`, `next_function` **=>** `for_of_iterator`, `next_function`, `array`
IteratorToArray,
/// Move to the next value in a for..in loop or jump to exit of the loop if done.
///
/// Operands: address: `u32`
///
/// Stack: `for_in_iterator`, `next_function` **=>** `for_in_iterator`, `next_function`, `next_result` (if not done)
ForInLoopNext,
/// Concat multiple stack objects into a string.
///
/// Operands: number of stack objects: `u32`
///
/// Stack: `value1`,...`valuen` **=>** `string`
ConcatToString,
/// Call RequireObjectCoercible on the stack value.
///
/// Stack: `value` **=>** `value`
RequireObjectCoercible,
/// Require the stack value to be neither null nor undefined.
///
/// Stack: `value` **=>** `value`
ValueNotNullOrUndefined,
/// No-operation instruction, does nothing.
///
/// Operands:
@ -632,6 +745,8 @@ impl Opcode {
Opcode::PushLiteral => "PushLiteral",
Opcode::PushEmptyObject => "PushEmptyObject",
Opcode::PushNewArray => "PushNewArray",
Opcode::PushValueToArray => "PushValueToArray",
Opcode::PushIteratorToArray => "PushIteratorToArray",
Opcode::Add => "Add",
Opcode::Sub => "Sub",
Opcode::Div => "Div",
@ -666,9 +781,10 @@ impl Opcode {
Opcode::Inc => "Inc",
Opcode::Dec => "Dec",
Opcode::DefVar => "DefVar",
Opcode::DefInitVar => "DefInitVar",
Opcode::DefLet => "DefLet",
Opcode::DefConst => "DefConst",
Opcode::InitLexical => "InitLexical",
Opcode::DefInitLet => "DefInitLet",
Opcode::DefInitConst => "DefInitConst",
Opcode::GetName => "GetName",
Opcode::SetName => "SetName",
Opcode::GetPropertyByName => "GetPropertyByName",
@ -681,17 +797,36 @@ impl Opcode {
Opcode::SetPropertySetterByValue => "SetPropertySetterByValue",
Opcode::DeletePropertyByName => "DeletePropertyByName",
Opcode::DeletePropertyByValue => "DeletePropertyByValue",
Opcode::CopyDataProperties => "CopyDataProperties",
Opcode::Jump => "Jump",
Opcode::JumpIfFalse => "JumpIfFalse",
Opcode::JumpIfTrue => "JumpIfTrue",
Opcode::JumpIfNotUndefined => "JumpIfNotUndefined",
Opcode::Throw => "Throw",
Opcode::TryStart => "TryStart",
Opcode::TryEnd => "TryEnd",
Opcode::FinallyStart => "FinallyStart",
Opcode::FinallyEnd => "FinallyEnd",
Opcode::FinallyJump => "FinallyJump",
Opcode::ToBoolean => "ToBoolean",
Opcode::This => "This",
Opcode::Case => "Case",
Opcode::Default => "Default",
Opcode::GetFunction => "GetFunction",
Opcode::Call => "Call",
Opcode::CallWithRest => "CallWithRest",
Opcode::New => "New",
Opcode::NewWithRest => "NewWithRest",
Opcode::Return => "Return",
Opcode::PushDeclarativeEnvironment => "PushDeclarativeEnvironment",
Opcode::PopEnvironment => "PopEnvironment",
Opcode::ForInLoopInitIterator => "ForInLoopInitIterator",
Opcode::InitIterator => "InitIterator",
Opcode::IteratorNext => "IteratorNext",
Opcode::IteratorToArray => "IteratorToArray",
Opcode::ForInLoopNext => "ForInLoopNext",
Opcode::ConcatToString => "ConcatToString",
Opcode::RequireObjectCoercible => "RequireObjectCoercible",
Opcode::ValueNotNullOrUndefined => "ValueNotNullOrUndefined",
Opcode::Nop => "Nop",
}
}

Loading…
Cancel
Save