Browse Source

Fix internal vm tests (#1718)

This PR fixes some vm implementation code. All our internal tests should now pass with the vm enabled.

There are only a few (~100) 262 tests left that currently break with the vm, that previously worked.
pull/1737/head
raskad 3 years ago
parent
commit
d8a71bc78e
  1. 13
      boa/src/builtins/function/mod.rs
  2. 45
      boa/src/builtins/iterable/mod.rs
  3. 2
      boa/src/builtins/json/mod.rs
  4. 4
      boa/src/builtins/regexp/mod.rs
  5. 397
      boa/src/bytecompiler.rs
  6. 19
      boa/src/context.rs
  7. 5
      boa/src/environment/lexical_environment.rs
  8. 23
      boa/src/exec/tests.rs
  9. 30
      boa/src/lib.rs
  10. 42
      boa/src/syntax/parser/expression/update.rs
  11. 25
      boa/src/vm/call_frame.rs
  12. 153
      boa/src/vm/code_block.rs
  13. 481
      boa/src/vm/mod.rs
  14. 246
      boa/src/vm/opcode.rs
  15. 13
      boa_tester/src/exec/js262.rs

13
boa/src/builtins/function/mod.rs

@ -208,6 +208,7 @@ impl fmt::Debug for Function {
impl Function { impl Function {
// Adds the final rest parameters to the Environment as an array // Adds the final rest parameters to the Environment as an array
#[cfg(not(feature = "vm"))]
pub(crate) fn add_rest_param( pub(crate) fn add_rest_param(
param: &FormalParameter, param: &FormalParameter,
index: usize, index: usize,
@ -240,6 +241,7 @@ impl Function {
} }
// Adds an argument to the environment // Adds an argument to the environment
#[cfg(not(feature = "vm"))]
pub(crate) fn add_arguments_to_environment( pub(crate) fn add_arguments_to_environment(
param: &FormalParameter, param: &FormalParameter,
value: JsValue, value: JsValue,
@ -580,7 +582,16 @@ impl BuiltInFunctionObject {
.into()) .into())
} }
} }
#[cfg(feature = "vm")]
(Function::VmOrdinary { .. }, Some(name)) if name.is_empty() => {
Ok("[Function (anonymous)]".into())
}
#[cfg(feature = "vm")]
(Function::VmOrdinary { .. }, Some(name)) => {
Ok(format!("[Function: {}]", &name).into())
}
#[cfg(feature = "vm")]
(Function::VmOrdinary { .. }, None) => Ok("[Function (anonymous)]".into()),
_ => Ok("TODO".into()), _ => Ok("TODO".into()),
} }
} }

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

@ -230,30 +230,43 @@ impl IteratorRecord {
completion: JsResult<JsValue>, completion: JsResult<JsValue>,
context: &mut Context, context: &mut Context,
) -> JsResult<JsValue> { ) -> JsResult<JsValue> {
let mut inner_result = self.iterator_object.get_field("return", context); // 1. Assert: Type(iteratorRecord.[[Iterator]]) is Object.
// 2. Let iterator be iteratorRecord.[[Iterator]].
// 3. Let innerResult be GetMethod(iterator, "return").
let inner_result = self.iterator_object.get_method("return", context);
//let mut inner_result = self.iterator_object.get_field("return", context);
// 5 // 4. If innerResult.[[Type]] is normal, then
if let Ok(inner_value) = inner_result { if let Ok(inner_value) = inner_result {
// b // a. Let return be innerResult.[[Value]].
if inner_value.is_undefined() { match inner_value {
return completion; // b. If return is undefined, return Completion(completion).
None => return completion,
// c. Set innerResult to Call(return, iterator).
Some(value) => {
let inner_result = value.call(&self.iterator_object, &[], context);
// 5. If completion.[[Type]] is throw, return Completion(completion).
let completion = completion?;
// 6. If innerResult.[[Type]] is throw, return Completion(innerResult).
inner_result?;
// 7. If Type(innerResult.[[Value]]) is not Object, throw a TypeError exception.
// 8. Return Completion(completion).
return Ok(completion);
}
} }
// c
inner_result = context.call(&inner_value, &self.iterator_object, &[]);
} }
// 6 // 5. If completion.[[Type]] is throw, return Completion(completion).
let completion = completion?; let completion = completion?;
// 7 // 6. If innerResult.[[Type]] is throw, return Completion(innerResult).
let inner_result = inner_result?; inner_result?;
// 8
if !inner_result.is_object() {
return context.throw_type_error("`return` method of iterator didn't return an Object");
}
// 9 // 7. If Type(innerResult.[[Value]]) is not Object, throw a TypeError exception.
// 8. Return Completion(completion).
Ok(completion) Ok(completion)
} }
} }

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

@ -96,6 +96,8 @@ impl Json {
// 9. Let unfiltered be completion.[[Value]]. // 9. Let unfiltered be completion.[[Value]].
// 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral. // 10. Assert: unfiltered is either a String, Number, Boolean, Null, or an Object that is defined by either an ArrayLiteral or an ObjectLiteral.
let unfiltered = context.eval(script_string.as_bytes())?; let unfiltered = context.eval(script_string.as_bytes())?;
#[cfg(feature = "vm")]
context.vm.pop_frame();
// 11. If IsCallable(reviver) is true, then // 11. If IsCallable(reviver) is true, then
if let Some(obj) = args.get_or_undefined(1).as_callable() { if let Some(obj) = args.get_or_undefined(1).as_callable() {

4
boa/src/builtins/regexp/mod.rs

@ -38,9 +38,6 @@ pub struct RegExp {
/// Regex matcher. /// Regex matcher.
matcher: Regex, matcher: Regex,
/// Update last_index, set if global or sticky flags are set.
use_last_index: bool,
/// Flag 's' - dot matches newline characters. /// Flag 's' - dot matches newline characters.
dot_all: bool, dot_all: bool,
@ -340,7 +337,6 @@ impl RegExp {
let regexp = RegExp { let regexp = RegExp {
matcher, matcher,
use_last_index: global || sticky,
dot_all, dot_all,
global, global,
ignore_case, ignore_case,

397
boa/src/bytecompiler.rs

@ -16,7 +16,7 @@ use crate::{
vm::{CodeBlock, Opcode}, vm::{CodeBlock, Opcode},
JsBigInt, JsString, JsValue, JsBigInt, JsString, JsValue,
}; };
use std::collections::HashMap; use std::{collections::HashMap, mem::size_of};
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Literal { enum Literal {
@ -36,6 +36,8 @@ struct JumpControlInfo {
start_address: u32, start_address: u32,
kind: JumpControlInfoKind, kind: JumpControlInfoKind,
breaks: Vec<Label>, breaks: Vec<Label>,
try_continues: Vec<Label>,
for_of_in_loop: bool,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
@ -58,9 +60,7 @@ pub struct ByteCompiler {
code_block: CodeBlock, code_block: CodeBlock,
literals_map: HashMap<Literal, u32>, literals_map: HashMap<Literal, u32>,
names_map: HashMap<JsString, u32>, names_map: HashMap<JsString, u32>,
functions_map: HashMap<JsString, u32>,
jump_info: Vec<JumpControlInfo>, jump_info: Vec<JumpControlInfo>,
top_level: bool,
} }
impl ByteCompiler { impl ByteCompiler {
@ -73,9 +73,7 @@ impl ByteCompiler {
code_block: CodeBlock::new(name, 0, strict, false), code_block: CodeBlock::new(name, 0, strict, false),
literals_map: HashMap::new(), literals_map: HashMap::new(),
names_map: HashMap::new(), names_map: HashMap::new(),
functions_map: HashMap::new(),
jump_info: Vec::new(), jump_info: Vec::new(),
top_level: true,
} }
} }
@ -243,6 +241,24 @@ impl ByteCompiler {
start_address, start_address,
kind: JumpControlInfoKind::Loop, kind: JumpControlInfoKind::Loop,
breaks: Vec::new(), breaks: Vec::new(),
try_continues: Vec::new(),
for_of_in_loop: false,
})
}
#[inline]
fn push_loop_control_info_for_of_in_loop(
&mut self,
label: Option<Box<str>>,
start_address: u32,
) {
self.jump_info.push(JumpControlInfo {
label,
start_address,
kind: JumpControlInfoKind::Loop,
breaks: Vec::new(),
try_continues: Vec::new(),
for_of_in_loop: true,
}) })
} }
@ -264,6 +280,8 @@ impl ByteCompiler {
start_address, start_address,
kind: JumpControlInfoKind::Switch, kind: JumpControlInfoKind::Switch,
breaks: Vec::new(), breaks: Vec::new(),
try_continues: Vec::new(),
for_of_in_loop: false,
}) })
} }
@ -280,27 +298,45 @@ impl ByteCompiler {
#[inline] #[inline]
fn push_try_control_info(&mut self) { fn push_try_control_info(&mut self) {
if !self.jump_info.is_empty() {
let start_address = self.jump_info.last().unwrap().start_address;
self.jump_info.push(JumpControlInfo { self.jump_info.push(JumpControlInfo {
label: None, label: None,
start_address: u32::MAX, start_address,
kind: JumpControlInfoKind::Try, kind: JumpControlInfoKind::Try,
breaks: Vec::new(), breaks: Vec::new(),
try_continues: Vec::new(),
for_of_in_loop: false,
}) })
} }
}
#[inline] #[inline]
fn pop_try_control_info(&mut self, finally_start_address: Option<u32>) { fn pop_try_control_info(&mut self, finally_start_address: Option<u32>) {
if !self.jump_info.is_empty() {
let mut info = self.jump_info.pop().unwrap(); let mut info = self.jump_info.pop().unwrap();
assert!(info.kind == JumpControlInfoKind::Try); assert!(info.kind == JumpControlInfoKind::Try);
if let Some(finally_start_address) = finally_start_address {
let mut breaks = Vec::with_capacity(info.breaks.len()); let mut breaks = Vec::with_capacity(info.breaks.len());
let finally_end = self.jump_with_custom_opcode(Opcode::FinallyJump);
if let Some(finally_start_address) = finally_start_address {
for label in info.try_continues {
if label.index < finally_start_address {
self.patch_jump_with_target(label, finally_start_address);
} else {
self.patch_jump_with_target(label, info.start_address)
}
}
for label in info.breaks { for label in info.breaks {
if label.index < finally_start_address { if label.index < finally_start_address {
self.patch_jump_with_target(label, finally_start_address); self.patch_jump_with_target(label, finally_start_address);
breaks.push(finally_end); let Label { mut index } = label;
index -= size_of::<Opcode>() as u32;
index -= size_of::<u32>() as u32;
breaks.push(Label { index });
} else { } else {
breaks.push(label); breaks.push(label);
} }
@ -312,6 +348,7 @@ impl ByteCompiler {
jump_info.breaks.append(&mut info.breaks); jump_info.breaks.append(&mut info.breaks);
} }
} }
}
#[inline] #[inline]
fn compile_access<'a>(&mut self, node: &'a Node) -> Access<'a> { fn compile_access<'a>(&mut self, node: &'a Node) -> Access<'a> {
@ -475,7 +512,17 @@ impl ByteCompiler {
UnaryOp::Plus => Some(Opcode::Pos), UnaryOp::Plus => Some(Opcode::Pos),
UnaryOp::Not => Some(Opcode::LogicalNot), UnaryOp::Not => Some(Opcode::LogicalNot),
UnaryOp::Tilde => Some(Opcode::BitNot), UnaryOp::Tilde => Some(Opcode::BitNot),
UnaryOp::TypeOf => Some(Opcode::TypeOf), UnaryOp::TypeOf => {
match &unary.target() {
Node::Identifier(identifier) => {
let index = self.get_or_insert_name(identifier.as_ref());
self.emit(Opcode::GetNameOrUndefined, &[index]);
}
expr => self.compile_expr(expr, true),
}
self.emit_opcode(Opcode::TypeOf);
None
}
UnaryOp::Void => Some(Opcode::Void), UnaryOp::Void => Some(Opcode::Void),
}; };
@ -545,17 +592,12 @@ impl ByteCompiler {
LogOp::And => { LogOp::And => {
let exit = self.jump_with_custom_opcode(Opcode::LogicalAnd); let exit = self.jump_with_custom_opcode(Opcode::LogicalAnd);
self.compile_expr(binary.rhs(), true); self.compile_expr(binary.rhs(), true);
self.emit(Opcode::ToBoolean, &[]);
self.patch_jump(exit); self.patch_jump(exit);
} }
LogOp::Or => { LogOp::Or => {
self.emit_opcode(Opcode::Dup);
let exit = self.jump_with_custom_opcode(Opcode::LogicalOr); let exit = self.jump_with_custom_opcode(Opcode::LogicalOr);
self.emit_opcode(Opcode::Pop);
self.compile_expr(binary.rhs(), true); self.compile_expr(binary.rhs(), true);
self.emit_opcode(Opcode::Dup);
self.patch_jump(exit); self.patch_jump(exit);
self.emit_opcode(Opcode::Pop);
} }
LogOp::Coalesce => { LogOp::Coalesce => {
let exit = self.jump_with_custom_opcode(Opcode::Coalesce); let exit = self.jump_with_custom_opcode(Opcode::Coalesce);
@ -585,24 +627,25 @@ impl ByteCompiler {
AssignOp::BoolAnd => { AssignOp::BoolAnd => {
let exit = self.jump_with_custom_opcode(Opcode::LogicalAnd); let exit = self.jump_with_custom_opcode(Opcode::LogicalAnd);
self.compile_expr(binary.rhs(), true); self.compile_expr(binary.rhs(), true);
self.emit(Opcode::ToBoolean, &[]); let access = self.compile_access(binary.lhs());
self.access_set(access, None, use_expr);
self.patch_jump(exit); self.patch_jump(exit);
None None
} }
AssignOp::BoolOr => { AssignOp::BoolOr => {
let exit = self.jump_with_custom_opcode(Opcode::LogicalOr); let exit = self.jump_with_custom_opcode(Opcode::LogicalOr);
self.compile_expr(binary.rhs(), true); self.compile_expr(binary.rhs(), true);
self.emit(Opcode::ToBoolean, &[]); let access = self.compile_access(binary.lhs());
self.access_set(access, None, use_expr);
self.patch_jump(exit); self.patch_jump(exit);
None None
} }
AssignOp::Coalesce => { AssignOp::Coalesce => {
let exit = self.jump_with_custom_opcode(Opcode::Coalesce); let exit = self.jump_with_custom_opcode(Opcode::Coalesce);
self.compile_expr(binary.rhs(), true); self.compile_expr(binary.rhs(), true);
let access = self.compile_access(binary.lhs());
self.access_set(access, None, use_expr);
self.patch_jump(exit); self.patch_jump(exit);
None None
} }
}; };
@ -610,11 +653,10 @@ impl ByteCompiler {
if let Some(opcode) = opcode { if let Some(opcode) = opcode {
self.compile_expr(binary.rhs(), true); self.compile_expr(binary.rhs(), true);
self.emit(opcode, &[]); self.emit(opcode, &[]);
}
let access = self.compile_access(binary.lhs()); let access = self.compile_access(binary.lhs());
self.access_set(access, None, use_expr); self.access_set(access, None, use_expr);
} }
}
BinOp::Comma => { BinOp::Comma => {
self.emit(Opcode::Pop, &[]); self.emit(Opcode::Pop, &[]);
self.compile_expr(binary.rhs(), true); self.compile_expr(binary.rhs(), true);
@ -632,115 +674,107 @@ impl ByteCompiler {
match property { match property {
PropertyDefinition::IdentifierReference(identifier_reference) => { PropertyDefinition::IdentifierReference(identifier_reference) => {
let index = self.get_or_insert_name(identifier_reference); let index = self.get_or_insert_name(identifier_reference);
self.emit(Opcode::SetPropertyByName, &[index]); self.emit(Opcode::DefineOwnPropertyByName, &[index]);
} }
PropertyDefinition::Property(name, node) => { PropertyDefinition::Property(name, node) => match name {
PropertyName::Literal(name) => {
self.compile_stmt(node, true); self.compile_stmt(node, true);
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::Swap);
match name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name(name); let index = self.get_or_insert_name(name);
self.emit(Opcode::SetPropertyByName, &[index]); self.emit(Opcode::DefineOwnPropertyByName, &[index]);
} }
PropertyName::Computed(name_node) => { PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true); self.compile_stmt(name_node, true);
self.emit_opcode(Opcode::Swap); self.compile_stmt(node, true);
self.emit_opcode(Opcode::SetPropertyByValue); self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
}
} }
},
PropertyDefinition::MethodDefinition(kind, name, func) => { PropertyDefinition::MethodDefinition(kind, name, func) => {
match kind { match kind {
MethodDefinitionKind::Get => { MethodDefinitionKind::Get => match name {
PropertyName::Literal(name) => {
self.compile_stmt(&func.clone().into(), true); self.compile_stmt(&func.clone().into(), true);
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::Swap);
match name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name(name); let index = self.get_or_insert_name(name);
self.emit(Opcode::SetPropertyGetterByName, &[index]); self.emit(Opcode::SetPropertyGetterByName, &[index]);
} }
PropertyName::Computed(name_node) => { PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true); self.compile_stmt(name_node, true);
self.emit_opcode(Opcode::Swap); self.compile_stmt(&func.clone().into(), true);
self.emit_opcode(Opcode::SetPropertyGetterByValue); self.emit_opcode(Opcode::SetPropertyGetterByValue);
} }
} },
} MethodDefinitionKind::Set => match name {
MethodDefinitionKind::Set => { PropertyName::Literal(name) => {
self.compile_stmt(&func.clone().into(), true); self.compile_stmt(&func.clone().into(), true);
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::Swap);
match name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name(name); let index = self.get_or_insert_name(name);
self.emit(Opcode::SetPropertySetterByName, &[index]); self.emit(Opcode::SetPropertySetterByName, &[index]);
} }
PropertyName::Computed(name_node) => { PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true); self.compile_stmt(name_node, true);
self.emit_opcode(Opcode::Swap); self.compile_stmt(&func.clone().into(), true);
self.emit_opcode(Opcode::SetPropertySetterByValue); self.emit_opcode(Opcode::SetPropertySetterByValue);
} }
} },
} MethodDefinitionKind::Ordinary => match name {
MethodDefinitionKind::Ordinary => { PropertyName::Literal(name) => {
self.compile_stmt(&func.clone().into(), true); self.compile_stmt(&func.clone().into(), true);
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::Swap);
match name {
PropertyName::Literal(name) => {
let index = self.get_or_insert_name(name); let index = self.get_or_insert_name(name);
self.emit(Opcode::SetPropertyByName, &[index]); self.emit(Opcode::DefineOwnPropertyByName, &[index]);
} }
PropertyName::Computed(name_node) => { PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true); self.compile_stmt(name_node, true);
self.emit_opcode(Opcode::Swap); self.compile_stmt(&func.clone().into(), true);
self.emit_opcode(Opcode::SetPropertyByValue); self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
}
} }
},
MethodDefinitionKind::Generator => { MethodDefinitionKind::Generator => {
// TODO: Implement generators // TODO: Implement generators
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Swap);
match name { match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(name); let index = self.get_or_insert_name(name);
self.emit(Opcode::SetPropertyByName, &[index]); self.emit(Opcode::DefineOwnPropertyByName, &[index]);
} }
PropertyName::Computed(name_node) => { PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true); self.compile_stmt(name_node, true);
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::SetPropertyByValue); self.emit_opcode(Opcode::DefineOwnPropertyByValue);
} }
} }
} }
MethodDefinitionKind::Async => { MethodDefinitionKind::Async => {
// TODO: Implement async // TODO: Implement async
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Swap);
match name { match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(name); let index = self.get_or_insert_name(name);
self.emit(Opcode::SetPropertyByName, &[index]) self.emit(Opcode::DefineOwnPropertyByName, &[index])
} }
PropertyName::Computed(name_node) => { PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true); self.compile_stmt(name_node, true);
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::SetPropertyByValue); self.emit_opcode(Opcode::DefineOwnPropertyByValue);
} }
} }
} }
MethodDefinitionKind::AsyncGenerator => { MethodDefinitionKind::AsyncGenerator => {
// TODO: Implement async generators // TODO: Implement async generators
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Swap);
match name { match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(name); let index = self.get_or_insert_name(name);
self.emit(Opcode::SetPropertyByName, &[index]) self.emit(Opcode::DefineOwnPropertyByName, &[index])
} }
PropertyName::Computed(name_node) => { PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true); self.compile_stmt(name_node, true);
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::SetPropertyByValue); self.emit_opcode(Opcode::DefineOwnPropertyByValue);
} }
} }
} }
@ -765,9 +799,14 @@ impl ByteCompiler {
self.access_get(access, use_expr); self.access_get(access, use_expr);
} }
Node::Assign(assign) => { Node::Assign(assign) => {
// Implement destructing assignments like here: https://tc39.es/ecma262/#sec-destructuring-assignment
if let Node::Object(_) = assign.lhs() {
self.emit_opcode(Opcode::PushUndefined);
} else {
let access = self.compile_access(assign.lhs()); let access = self.compile_access(assign.lhs());
self.access_set(access, Some(assign.rhs()), use_expr); self.access_set(access, Some(assign.rhs()), use_expr);
} }
}
Node::GetConstField(node) => { Node::GetConstField(node) => {
let access = Access::ByName { node }; let access = Access::ByName { node };
self.access_get(access, use_expr); self.access_get(access, use_expr);
@ -791,6 +830,7 @@ impl ByteCompiler {
} }
Node::ArrayDecl(array) => { Node::ArrayDecl(array) => {
self.emit_opcode(Opcode::PushNewArray); self.emit_opcode(Opcode::PushNewArray);
self.emit_opcode(Opcode::PopOnReturnAdd);
for element in array.as_ref() { for element in array.as_ref() {
self.compile_expr(element, true); self.compile_expr(element, true);
@ -802,6 +842,7 @@ impl ByteCompiler {
} }
} }
self.emit_opcode(Opcode::PopOnReturnSub);
if !use_expr { if !use_expr {
self.emit(Opcode::Pop, &[]); self.emit(Opcode::Pop, &[]);
} }
@ -815,7 +856,7 @@ impl ByteCompiler {
Node::Call(_) => self.call(expr, use_expr), Node::Call(_) => self.call(expr, use_expr),
Node::New(_) => self.call(expr, use_expr), Node::New(_) => self.call(expr, use_expr),
Node::TemplateLit(template_literal) => { Node::TemplateLit(template_literal) => {
for element in template_literal.elements().iter().rev() { for element in template_literal.elements() {
match element { match element {
TemplateElement::String(s) => { TemplateElement::String(s) => {
self.emit_push_literal(Literal::String(s.as_ref().into())) self.emit_push_literal(Literal::String(s.as_ref().into()))
@ -877,16 +918,6 @@ impl ByteCompiler {
} }
} }
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); self.emit_opcode(Opcode::PushNewArray);
for cooked in template.cookeds() { for cooked in template.cookeds() {
if let Some(cooked) = cooked { if let Some(cooked) = cooked {
@ -896,11 +927,22 @@ impl ByteCompiler {
} }
self.emit_opcode(Opcode::PushValueToArray); self.emit_opcode(Opcode::PushValueToArray);
} }
self.emit_opcode(Opcode::Dup); self.emit_opcode(Opcode::Dup);
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::Swap);
let index = self.get_or_insert_name("raw"); let index = self.get_or_insert_name("raw");
self.emit(Opcode::SetPropertyByName, &[index]); self.emit(Opcode::SetPropertyByName, &[index]);
for expr in template.exprs() {
self.compile_expr(expr, true);
}
self.emit(Opcode::Call, &[(template.exprs().len() + 1) as u32]); self.emit(Opcode::Call, &[(template.exprs().len() + 1) as u32]);
} }
_ => unreachable!(), _ => unreachable!(),
@ -1064,7 +1106,10 @@ impl ByteCompiler {
let early_exit = self.jump_with_custom_opcode(Opcode::ForInLoopInitIterator); let early_exit = self.jump_with_custom_opcode(Opcode::ForInLoopInitIterator);
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
self.push_loop_control_info(for_in_loop.label().map(Into::into), start_address); self.push_loop_control_info_for_of_in_loop(
for_in_loop.label().map(Into::into),
start_address,
);
self.emit_opcode(Opcode::PushDeclarativeEnvironment); self.emit_opcode(Opcode::PushDeclarativeEnvironment);
let exit = self.jump_with_custom_opcode(Opcode::ForInLoopNext); let exit = self.jump_with_custom_opcode(Opcode::ForInLoopNext);
@ -1077,7 +1122,7 @@ impl ByteCompiler {
IterableLoopInitializer::Var(declaration) => match declaration { IterableLoopInitializer::Var(declaration) => match declaration {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref()); let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]); self.emit(Opcode::DefInitVar, &[index]);
} }
Declaration::Pattern(pattern) => { Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitVar); self.compile_declaration_pattern(pattern, Opcode::DefInitVar);
@ -1086,7 +1131,7 @@ impl ByteCompiler {
IterableLoopInitializer::Let(declaration) => match declaration { IterableLoopInitializer::Let(declaration) => match declaration {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref()); let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]); self.emit(Opcode::DefInitLet, &[index]);
} }
Declaration::Pattern(pattern) => { Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitLet); self.compile_declaration_pattern(pattern, Opcode::DefInitLet);
@ -1095,7 +1140,7 @@ impl ByteCompiler {
IterableLoopInitializer::Const(declaration) => match declaration { IterableLoopInitializer::Const(declaration) => match declaration {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref()); let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]); self.emit(Opcode::DefInitConst, &[index]);
} }
Declaration::Pattern(pattern) => { Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitConst); self.compile_declaration_pattern(pattern, Opcode::DefInitConst);
@ -1108,11 +1153,11 @@ impl ByteCompiler {
self.emit(Opcode::Jump, &[start_address]); self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit);
self.pop_loop_control_info(); self.pop_loop_control_info();
self.emit_opcode(Opcode::Pop); self.emit_opcode(Opcode::PushFalse);
self.emit_opcode(Opcode::Pop); self.emit_opcode(Opcode::IteratorClose);
self.patch_jump(exit);
self.patch_jump(early_exit); self.patch_jump(early_exit);
} }
Node::ForOfLoop(for_of_loop) => { Node::ForOfLoop(for_of_loop) => {
@ -1120,7 +1165,10 @@ impl ByteCompiler {
self.emit_opcode(Opcode::InitIterator); self.emit_opcode(Opcode::InitIterator);
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
self.push_loop_control_info(for_of_loop.label().map(Into::into), start_address); self.push_loop_control_info_for_of_in_loop(
for_of_loop.label().map(Into::into),
start_address,
);
self.emit_opcode(Opcode::PushDeclarativeEnvironment); self.emit_opcode(Opcode::PushDeclarativeEnvironment);
let exit = self.jump_with_custom_opcode(Opcode::ForInLoopNext); let exit = self.jump_with_custom_opcode(Opcode::ForInLoopNext);
@ -1133,7 +1181,7 @@ impl ByteCompiler {
IterableLoopInitializer::Var(declaration) => match declaration { IterableLoopInitializer::Var(declaration) => match declaration {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref()); let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]); self.emit(Opcode::DefInitVar, &[index]);
} }
Declaration::Pattern(pattern) => { Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitVar); self.compile_declaration_pattern(pattern, Opcode::DefInitVar);
@ -1142,7 +1190,7 @@ impl ByteCompiler {
IterableLoopInitializer::Let(declaration) => match declaration { IterableLoopInitializer::Let(declaration) => match declaration {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref()); let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]); self.emit(Opcode::DefInitLet, &[index]);
} }
Declaration::Pattern(pattern) => { Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitLet); self.compile_declaration_pattern(pattern, Opcode::DefInitLet);
@ -1151,7 +1199,7 @@ impl ByteCompiler {
IterableLoopInitializer::Const(declaration) => match declaration { IterableLoopInitializer::Const(declaration) => match declaration {
Declaration::Identifier { ident, .. } => { Declaration::Identifier { ident, .. } => {
let index = self.get_or_insert_name(ident.as_ref()); let index = self.get_or_insert_name(ident.as_ref());
self.emit(Opcode::SetName, &[index]); self.emit(Opcode::DefInitConst, &[index]);
} }
Declaration::Pattern(pattern) => { Declaration::Pattern(pattern) => {
self.compile_declaration_pattern(pattern, Opcode::DefInitConst); self.compile_declaration_pattern(pattern, Opcode::DefInitConst);
@ -1165,8 +1213,9 @@ impl ByteCompiler {
self.emit(Opcode::Jump, &[start_address]); self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(exit); self.patch_jump(exit);
self.pop_loop_control_info(); self.pop_loop_control_info();
self.emit_opcode(Opcode::PushFalse);
self.emit_opcode(Opcode::IteratorClose);
} }
Node::WhileLoop(while_) => { Node::WhileLoop(while_) => {
let start_address = self.next_opcode_location(); let start_address = self.next_opcode_location();
@ -1200,23 +1249,58 @@ impl ByteCompiler {
self.patch_jump(exit); self.patch_jump(exit);
} }
Node::Continue(node) => { Node::Continue(node) => {
if let Some(start_address) = self
.jump_info
.last()
.filter(|info| info.kind == JumpControlInfoKind::Try)
.map(|info| info.start_address)
{
self.emit_opcode(Opcode::TryEnd);
self.emit(Opcode::FinallySetJump, &[start_address]);
let label = self.jump(); let label = self.jump();
self.jump_info.last_mut().unwrap().try_continues.push(label);
} else {
let mut items = self let mut items = self
.jump_info .jump_info
.iter_mut() .iter()
.rev() .rev()
.filter(|info| info.kind == JumpControlInfoKind::Loop); .filter(|info| info.kind == JumpControlInfoKind::Loop);
let target = if node.label().is_none() { let address = if node.label().is_none() {
items.next() items.next().expect("continue target").start_address
} else { } else {
items.find(|info| info.label.as_deref() == node.label()) let mut emit_for_of_in_exit = 0;
let mut address_info = None;
for info in items {
if info.label.as_deref() == node.label() {
address_info = Some(info);
break;
}
if info.for_of_in_loop {
emit_for_of_in_exit += 1;
}
}
let address = address_info.expect("continue target").start_address;
for _ in 0..emit_for_of_in_exit {
self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
}
address
};
self.emit(Opcode::Jump, &[address]);
} }
.expect("continue target")
.start_address;
self.patch_jump_with_target(label, target);
} }
Node::Break(node) => { Node::Break(node) => {
if self
.jump_info
.last()
.filter(|info| info.kind == JumpControlInfoKind::Try)
.is_some()
{
self.emit_opcode(Opcode::TryEnd);
self.emit(Opcode::FinallySetJump, &[u32::MAX]);
}
let label = self.jump(); let label = self.jump();
if node.label().is_none() { if node.label().is_none() {
self.jump_info.last_mut().unwrap().breaks.push(label); self.jump_info.last_mut().unwrap().breaks.push(label);
@ -1232,7 +1316,7 @@ impl ByteCompiler {
Node::Block(block) => { Node::Block(block) => {
self.emit_opcode(Opcode::PushDeclarativeEnvironment); self.emit_opcode(Opcode::PushDeclarativeEnvironment);
for node in block.items() { for node in block.items() {
self.compile_stmt(node, false); self.compile_stmt(node, use_expr);
} }
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
} }
@ -1279,19 +1363,24 @@ impl ByteCompiler {
Node::Try(t) => { Node::Try(t) => {
self.push_try_control_info(); self.push_try_control_info();
let try_start = self.jump_with_custom_opcode(Opcode::TryStart); let try_start = self.next_opcode_location();
self.emit(Opcode::TryStart, &[Self::DUMMY_ADDRESS, 0]);
self.emit_opcode(Opcode::PushDeclarativeEnvironment); self.emit_opcode(Opcode::PushDeclarativeEnvironment);
for node in t.block().items() { for node in t.block().items() {
self.compile_stmt(node, false); self.compile_stmt(node, false);
} }
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::TryEnd); self.emit_opcode(Opcode::TryEnd);
let finally = self.jump(); let finally = self.jump();
self.patch_jump(Label { index: try_start });
self.patch_jump(try_start);
if let Some(catch) = t.catch() { if let Some(catch) = t.catch() {
let catch_start = if t.finally().is_some() {
Some(self.jump_with_custom_opcode(Opcode::CatchStart))
} else {
None
};
self.emit_opcode(Opcode::PushDeclarativeEnvironment); self.emit_opcode(Opcode::PushDeclarativeEnvironment);
if let Some(decl) = catch.parameter() { if let Some(decl) = catch.parameter() {
match decl { match decl {
@ -1300,28 +1389,35 @@ impl ByteCompiler {
self.emit(Opcode::DefInitLet, &[index]); self.emit(Opcode::DefInitLet, &[index]);
} }
Declaration::Pattern(pattern) => { Declaration::Pattern(pattern) => {
let idents = pattern.idents(); self.compile_declaration_pattern(pattern, Opcode::DefInitLet);
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]);
}
} }
} }
} else {
self.emit_opcode(Opcode::Pop);
} }
for node in catch.block().items() { for node in catch.block().items() {
self.compile_stmt(node, false); self.compile_stmt(node, use_expr);
} }
self.emit_opcode(Opcode::PopEnvironment); self.emit_opcode(Opcode::PopEnvironment);
self.emit_opcode(Opcode::TryEnd); if let Some(catch_start) = catch_start {
self.emit_opcode(Opcode::CatchEnd);
self.patch_jump(catch_start);
} else {
self.emit_opcode(Opcode::CatchEnd2);
}
} }
self.patch_jump(finally); self.patch_jump(finally);
if let Some(finally) = t.finally() { if let Some(finally) = t.finally() {
self.emit_opcode(Opcode::FinallyStart); self.emit_opcode(Opcode::FinallyStart);
let finally_start_address = self.next_opcode_location(); let finally_start_address = self.next_opcode_location();
self.patch_jump_with_target(
Label {
index: try_start + 4,
},
finally_start_address,
);
for node in finally.items() { for node in finally.items() {
self.compile_stmt(node, false); self.compile_stmt(node, false);
@ -1379,8 +1475,9 @@ impl ByteCompiler {
_ => unreachable!(), _ => unreachable!(),
}; };
let strict = body.strict() || self.code_block.strict;
let length = parameters.len() as u32; let length = parameters.len() as u32;
let mut code = CodeBlock::new(name.unwrap_or("").into(), length, false, true); let mut code = CodeBlock::new(name.unwrap_or("").into(), length, strict, true);
if let FunctionKind::Arrow = kind { if let FunctionKind::Arrow = kind {
code.constructor = false; code.constructor = false;
@ -1391,11 +1488,43 @@ impl ByteCompiler {
code_block: code, code_block: code,
literals_map: HashMap::new(), literals_map: HashMap::new(),
names_map: HashMap::new(), names_map: HashMap::new(),
functions_map: HashMap::new(),
jump_info: Vec::new(), jump_info: Vec::new(),
top_level: false,
}; };
let mut has_rest_parameter = false;
let mut has_parameter_expressions = false;
for parameter in parameters {
has_parameter_expressions = has_parameter_expressions || parameter.init().is_some();
if parameter.is_rest_param() {
has_rest_parameter = true;
compiler.emit_opcode(Opcode::RestParameterInit);
}
match parameter.declaration() {
Declaration::Identifier { ident, .. } => {
let index = compiler.get_or_insert_name(ident.as_ref());
if let Some(init) = parameter.declaration().init() {
let skip = compiler.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
compiler.compile_expr(init, true);
compiler.patch_jump(skip);
}
compiler.emit(Opcode::DefInitArg, &[index]);
}
Declaration::Pattern(pattern) => {
compiler.compile_declaration_pattern(pattern, Opcode::DefInitArg);
}
}
}
if !has_rest_parameter {
compiler.emit_opcode(Opcode::RestParameterPop);
}
if has_parameter_expressions {
compiler.emit_opcode(Opcode::PushFunctionEnvironment)
}
for node in body.items() { for node in body.items() {
compiler.compile_stmt(node, false); compiler.compile_stmt(node, false);
} }
@ -1416,8 +1545,7 @@ impl ByteCompiler {
match kind { match kind {
FunctionKind::Declaration => { FunctionKind::Declaration => {
let index = self.get_or_insert_name(name.unwrap()); let index = self.get_or_insert_name(name.unwrap());
let access = Access::Variable { index }; self.emit(Opcode::DefInitVar, &[index]);
self.access_set(access, None, false);
} }
FunctionKind::Expression | FunctionKind::Arrow => { FunctionKind::Expression | FunctionKind::Arrow => {
if !use_expr { if !use_expr {
@ -1463,7 +1591,7 @@ impl ByteCompiler {
} }
} }
for arg in call.args().iter().rev() { for arg in call.args().iter() {
self.compile_expr(arg, true); self.compile_expr(arg, true);
} }
@ -1523,7 +1651,6 @@ impl ByteCompiler {
if let Some(init) = default_init { if let Some(init) = default_init {
let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
self.emit_opcode(Opcode::Pop);
self.compile_expr(init, true); self.compile_expr(init, true);
self.patch_jump(skip); self.patch_jump(skip);
} }
@ -1559,11 +1686,8 @@ impl ByteCompiler {
if let Some(init) = default_init { if let Some(init) = default_init {
let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
self.emit_opcode(Opcode::Pop);
self.compile_expr(init, true); self.compile_expr(init, true);
self.patch_jump(skip); self.patch_jump(skip);
} else {
self.emit_opcode(Opcode::PushUndefined);
} }
self.compile_declaration_pattern(pattern, def); self.compile_declaration_pattern(pattern, def);
@ -1584,15 +1708,23 @@ impl ByteCompiler {
self.emit_opcode(Opcode::ValueNotNullOrUndefined); self.emit_opcode(Opcode::ValueNotNullOrUndefined);
self.emit_opcode(Opcode::InitIterator); self.emit_opcode(Opcode::InitIterator);
for binding in pattern.bindings() { for (i, binding) in pattern.bindings().iter().enumerate() {
use BindingPatternTypeArray::*; use BindingPatternTypeArray::*;
let next = if i == pattern.bindings().len() - 1 {
Opcode::IteratorNextFull
} else {
Opcode::IteratorNext
};
match binding { match binding {
// ArrayBindingPattern : [ ] // ArrayBindingPattern : [ ]
Empty => {} Empty => {
self.emit_opcode(Opcode::PushFalse);
}
// ArrayBindingPattern : [ Elision ] // ArrayBindingPattern : [ Elision ]
Elision => { Elision => {
self.emit_opcode(Opcode::IteratorNext); self.emit_opcode(next);
self.emit_opcode(Opcode::Pop); self.emit_opcode(Opcode::Pop);
} }
// SingleNameBinding : BindingIdentifier Initializer[opt] // SingleNameBinding : BindingIdentifier Initializer[opt]
@ -1600,10 +1732,9 @@ impl ByteCompiler {
ident, ident,
default_init, default_init,
} => { } => {
self.emit_opcode(Opcode::IteratorNext); self.emit_opcode(next);
if let Some(init) = default_init { if let Some(init) = default_init {
let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined); let skip = self.jump_with_custom_opcode(Opcode::JumpIfNotUndefined);
self.emit_opcode(Opcode::Pop);
self.compile_expr(init, true); self.compile_expr(init, true);
self.patch_jump(skip); self.patch_jump(skip);
} }
@ -1613,7 +1744,7 @@ impl ByteCompiler {
} }
// BindingElement : BindingPattern Initializer[opt] // BindingElement : BindingPattern Initializer[opt]
BindingPattern { pattern } => { BindingPattern { pattern } => {
self.emit_opcode(Opcode::IteratorNext); self.emit_opcode(next);
self.compile_declaration_pattern(pattern, def) self.compile_declaration_pattern(pattern, def)
} }
// BindingRestElement : ... BindingIdentifier // BindingRestElement : ... BindingIdentifier
@ -1622,16 +1753,22 @@ impl ByteCompiler {
let index = self.get_or_insert_name(ident); let index = self.get_or_insert_name(ident);
self.emit(def, &[index]); self.emit(def, &[index]);
self.emit_opcode(Opcode::PushTrue);
} }
// BindingRestElement : ... BindingPattern // BindingRestElement : ... BindingPattern
BindingPatternRest { pattern } => { BindingPatternRest { pattern } => {
self.emit_opcode(Opcode::IteratorToArray); self.emit_opcode(Opcode::IteratorToArray);
self.compile_declaration_pattern(pattern, def); self.compile_declaration_pattern(pattern, def);
self.emit_opcode(Opcode::PushTrue);
} }
} }
} }
self.emit_opcode(Opcode::Pop); if pattern.bindings().is_empty() {
self.emit_opcode(Opcode::PushFalse);
}
self.emit_opcode(Opcode::IteratorClose);
} }
} }
} }

19
boa/src/context.rs

@ -28,7 +28,7 @@ use crate::{
use crate::builtins::console::Console; use crate::builtins::console::Console;
#[cfg(feature = "vm")] #[cfg(feature = "vm")]
use crate::vm::Vm; use crate::vm::{FinallyReturn, Vm};
/// Store a builtin constructor (such as `Object`) and its corresponding prototype. /// Store a builtin constructor (such as `Object`) and its corresponding prototype.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -1054,12 +1054,13 @@ impl Context {
Err(e) => return self.throw_syntax_error(e), Err(e) => return self.throw_syntax_error(e),
}; };
let mut compiler = crate::bytecompiler::ByteCompiler::new(JsString::new("<main>"), false); let mut compiler = crate::bytecompiler::ByteCompiler::new(
JsString::new("<main>"),
statement_list.strict(),
);
compiler.compile_statement_list(&statement_list, true); compiler.compile_statement_list(&statement_list, true);
let code_block = compiler.finish(); let code_block = compiler.finish();
let environment = self.get_current_environment().clone();
let fp = self.vm.stack.len();
let global_object = self.global_object().into(); let global_object = self.global_object().into();
self.vm.push_frame(CallFrame { self.vm.push_frame(CallFrame {
@ -1067,11 +1068,13 @@ impl Context {
code: Gc::new(code_block), code: Gc::new(code_block),
this: global_object, this: global_object,
pc: 0, pc: 0,
fp, catch: Vec::new(),
environment, finally_return: FinallyReturn::None,
catch: None, finally_jump: Vec::new(),
pop_on_return: 0,
pop_env_on_return: 0, pop_env_on_return: 0,
finally_no_jump: false, param_count: 0,
arg_count: 0,
}); });
let result = self.run(); let result = self.run();

5
boa/src/environment/lexical_environment.rs

@ -93,6 +93,11 @@ impl Context {
.recursive_get_this_binding(self) .recursive_get_this_binding(self)
} }
pub(crate) fn get_global_this_binding(&mut self) -> JsResult<JsValue> {
let global = self.realm.global_env.clone();
global.get_this_binding(self)
}
pub(crate) fn create_mutable_binding( pub(crate) fn create_mutable_binding(
&mut self, &mut self,
name: &str, name: &str,

23
boa/src/exec/tests.rs

@ -772,10 +772,13 @@ mod in_operator {
new a(); new a();
"#; "#;
check_output(&[TestAction::TestEq( #[cfg(not(feature = "vm"))]
scenario, let error = "Uncaught \"TypeError\": \"a is not a constructor\"";
"Uncaught \"TypeError\": \"a is not a constructor\"",
)]); #[cfg(feature = "vm")]
let error = "Uncaught \"TypeError\": \"not a constructor\"";
check_output(&[TestAction::TestEq(scenario, error)]);
} }
#[test] #[test]
@ -1265,11 +1268,17 @@ fn not_a_function() {
} }
"#; "#;
#[cfg(not(feature = "vm"))]
let error = "\"TypeError: Value is not callable\"";
#[cfg(feature = "vm")]
let error = "\"TypeError: not a callable function\"";
check_output(&[ check_output(&[
TestAction::Execute(init), TestAction::Execute(init),
TestAction::TestEq(scenario1, "\"TypeError: Value is not callable\""), TestAction::TestEq(scenario1, error),
TestAction::TestEq(scenario2, "\"TypeError: Value is not callable\""), TestAction::TestEq(scenario2, error),
TestAction::TestEq(scenario3, "\"TypeError: Value is not callable\""), TestAction::TestEq(scenario3, error),
]); ]);
} }

30
boa/src/lib.rs

@ -101,26 +101,20 @@ pub fn parse<T: AsRef<[u8]>>(src: T, strict_mode: bool) -> StdResult<StatementLi
#[cfg(test)] #[cfg(test)]
pub(crate) fn forward<T: AsRef<[u8]>>(context: &mut Context, src: T) -> String { pub(crate) fn forward<T: AsRef<[u8]>>(context: &mut Context, src: T) -> String {
let src_bytes: &[u8] = src.as_ref(); let src_bytes: &[u8] = src.as_ref();
let result = context.eval(src_bytes).map_or_else(
// Setup executor
let expr = match parse(src_bytes, false) {
Ok(res) => res,
Err(e) => {
return format!(
"Uncaught {}",
context.construct_syntax_error(e.to_string()).display()
);
}
};
expr.run(context).map_or_else(
|e| format!("Uncaught {}", e.display()), |e| format!("Uncaught {}", e.display()),
|v| v.display().to_string(), |v| v.display().to_string(),
) );
#[cfg(feature = "vm")]
context.vm.pop_frame();
result
} }
/// Execute the code using an existing Context. /// Execute the code using an existing Context.
/// The str is consumed and the state of the Context is changed /// The str is consumed and the state of the Context is changed
/// Similar to `forward`, except the current value is returned instad of the string /// Similar to `forward`, except the current value is returned instead of the string
/// If the interpreter fails parsing an error value is returned instead (error object) /// If the interpreter fails parsing an error value is returned instead (error object)
#[allow(clippy::unit_arg, clippy::drop_copy)] #[allow(clippy::unit_arg, clippy::drop_copy)]
#[cfg(test)] #[cfg(test)]
@ -128,10 +122,10 @@ pub(crate) fn forward_val<T: AsRef<[u8]>>(context: &mut Context, src: T) -> JsRe
let main_timer = BoaProfiler::global().start_event("Main", "Main"); let main_timer = BoaProfiler::global().start_event("Main", "Main");
let src_bytes: &[u8] = src.as_ref(); let src_bytes: &[u8] = src.as_ref();
// Setup executor let result = context.eval(src_bytes);
let result = parse(src_bytes, false)
.map_err(|e| context.construct_syntax_error(e.to_string())) #[cfg(feature = "vm")]
.and_then(|expr| expr.run(context)); context.vm.pop_frame();
// The main_timer needs to be dropped before the BoaProfiler is. // The main_timer needs to be dropped before the BoaProfiler is.
drop(main_timer); drop(main_timer);

42
boa/src/syntax/parser/expression/update.rs

@ -10,7 +10,7 @@ use crate::{
profiler::BoaProfiler, profiler::BoaProfiler,
syntax::{ syntax::{
ast::{node, op::UnaryOp, Node, Punctuator}, ast::{node, op::UnaryOp, Node, Punctuator},
lexer::TokenKind, lexer::{Error as LexError, TokenKind},
parser::{ parser::{
expression::unary::UnaryExpression, AllowAwait, AllowYield, Cursor, ParseError, expression::unary::UnaryExpression, AllowAwait, AllowYield, Cursor, ParseError,
ParseResult, TokenParser, ParseResult, TokenParser,
@ -77,14 +77,54 @@ where
} }
let lhs = LeftHandSideExpression::new(self.allow_yield, self.allow_await).parse(cursor)?; let lhs = LeftHandSideExpression::new(self.allow_yield, self.allow_await).parse(cursor)?;
let strict = cursor.strict_mode();
if let Some(tok) = cursor.peek(0)? { if let Some(tok) = cursor.peek(0)? {
let token_start = tok.span().start();
match tok.kind() { match tok.kind() {
TokenKind::Punctuator(Punctuator::Inc) => { TokenKind::Punctuator(Punctuator::Inc) => {
cursor.next()?.expect("Punctuator::Inc token disappeared"); cursor.next()?.expect("Punctuator::Inc token disappeared");
// https://tc39.es/ecma262/#sec-update-expressions-static-semantics-early-errors
let ok = match &lhs {
Node::Identifier(_) if !strict => true,
Node::Identifier(ident)
if !["eval", "arguments"].contains(&ident.as_ref()) =>
{
true
}
Node::GetConstField(_) => true,
Node::GetField(_) => true,
_ => false,
};
if !ok {
return Err(ParseError::lex(LexError::Syntax(
"Invalid left-hand side in assignment".into(),
token_start,
)));
}
return Ok(node::UnaryOp::new(UnaryOp::IncrementPost, lhs).into()); return Ok(node::UnaryOp::new(UnaryOp::IncrementPost, lhs).into());
} }
TokenKind::Punctuator(Punctuator::Dec) => { TokenKind::Punctuator(Punctuator::Dec) => {
cursor.next()?.expect("Punctuator::Dec token disappeared"); cursor.next()?.expect("Punctuator::Dec token disappeared");
// https://tc39.es/ecma262/#sec-update-expressions-static-semantics-early-errors
let ok = match &lhs {
Node::Identifier(_) if !strict => true,
Node::Identifier(ident)
if !["eval", "arguments"].contains(&ident.as_ref()) =>
{
true
}
Node::GetConstField(_) => true,
Node::GetField(_) => true,
_ => false,
};
if !ok {
return Err(ParseError::lex(LexError::Syntax(
"Invalid left-hand side in assignment".into(),
token_start,
)));
}
return Ok(node::UnaryOp::new(UnaryOp::DecrementPost, lhs).into()); return Ok(node::UnaryOp::new(UnaryOp::DecrementPost, lhs).into());
} }
_ => {} _ => {}

25
boa/src/vm/call_frame.rs

@ -2,7 +2,7 @@
//! This module will provides everything needed to implement the CallFrame //! This module will provides everything needed to implement the CallFrame
use super::CodeBlock; use super::CodeBlock;
use crate::{environment::lexical_environment::Environment, JsValue}; use crate::JsValue;
use gc::Gc; use gc::Gc;
#[derive(Debug)] #[derive(Debug)]
@ -10,10 +10,25 @@ pub struct CallFrame {
pub(crate) prev: Option<Box<Self>>, pub(crate) prev: Option<Box<Self>>,
pub(crate) code: Gc<CodeBlock>, pub(crate) code: Gc<CodeBlock>,
pub(crate) pc: usize, pub(crate) pc: usize,
pub(crate) fp: usize,
pub(crate) this: JsValue, pub(crate) this: JsValue,
pub(crate) environment: Environment, pub(crate) catch: Vec<CatchAddresses>,
pub(crate) catch: Option<u32>, pub(crate) finally_return: FinallyReturn,
pub(crate) finally_jump: Vec<Option<u32>>,
pub(crate) pop_on_return: usize,
pub(crate) pop_env_on_return: usize, pub(crate) pop_env_on_return: usize,
pub(crate) finally_no_jump: bool, pub(crate) param_count: usize,
pub(crate) arg_count: usize,
}
#[derive(Debug)]
pub(crate) struct CatchAddresses {
pub(crate) next: u32,
pub(crate) finally: Option<u32>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) enum FinallyReturn {
None,
Ok,
Err,
} }

153
boa/src/vm/code_block.rs

@ -16,15 +16,12 @@ use crate::{
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData}, object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData},
property::PropertyDescriptor, property::PropertyDescriptor,
syntax::ast::node::FormalParameter, syntax::ast::node::FormalParameter,
vm::Opcode, vm::{call_frame::FinallyReturn, CallFrame, Opcode},
Context, JsResult, JsString, JsValue, Context, JsResult, JsString, JsValue,
}; };
use gc::Gc; use gc::Gc;
use std::{convert::TryInto, fmt::Write, mem::size_of}; use std::{convert::TryInto, fmt::Write, mem::size_of};
use super::CallFrame;
/// This represents whether a value can be read from [`CodeBlock`] code. /// This represents whether a value can be read from [`CodeBlock`] code.
pub unsafe trait Readable {} pub unsafe trait Readable {}
@ -103,7 +100,7 @@ impl CodeBlock {
/// ///
/// Does not check if read happens out-of-bounds. /// Does not check if read happens out-of-bounds.
pub unsafe fn read_unchecked<T: Readable>(&self, offset: usize) -> T { pub unsafe fn read_unchecked<T: Readable>(&self, offset: usize) -> T {
// This has to be an unaligned read because we can't gurantee that // This has to be an unaligned read because we can't guarantee that
// the types are aligned. // the types are aligned.
self.code.as_ptr().add(offset).cast::<T>().read_unaligned() self.code.as_ptr().add(offset).cast::<T>().read_unaligned()
} }
@ -150,8 +147,8 @@ impl CodeBlock {
| Opcode::Jump | Opcode::Jump
| Opcode::JumpIfFalse | Opcode::JumpIfFalse
| Opcode::JumpIfNotUndefined | Opcode::JumpIfNotUndefined
| Opcode::FinallyJump | Opcode::CatchStart
| Opcode::TryStart | Opcode::FinallySetJump
| Opcode::Case | Opcode::Case
| Opcode::Default | Opcode::Default
| Opcode::LogicalAnd | Opcode::LogicalAnd
@ -167,6 +164,13 @@ impl CodeBlock {
*pc += size_of::<u32>(); *pc += size_of::<u32>();
result result
} }
Opcode::TryStart => {
let operand1 = self.read::<u32>(*pc);
*pc += size_of::<u32>();
let operand2 = self.read::<u32>(*pc);
*pc += size_of::<u32>();
format!("{}, {}", operand1, operand2)
}
Opcode::GetFunction => { Opcode::GetFunction => {
let operand = self.read::<u32>(*pc); let operand = self.read::<u32>(*pc);
*pc += size_of::<u32>(); *pc += size_of::<u32>();
@ -177,15 +181,18 @@ impl CodeBlock {
self.functions[operand as usize].length self.functions[operand as usize].length
) )
} }
Opcode::DefVar Opcode::DefInitArg
| Opcode::DefVar
| Opcode::DefInitVar | Opcode::DefInitVar
| Opcode::DefLet | Opcode::DefLet
| Opcode::DefInitLet | Opcode::DefInitLet
| Opcode::DefInitConst | Opcode::DefInitConst
| Opcode::GetName | Opcode::GetName
| Opcode::GetNameOrUndefined
| Opcode::SetName | Opcode::SetName
| Opcode::GetPropertyByName | Opcode::GetPropertyByName
| Opcode::SetPropertyByName | Opcode::SetPropertyByName
| Opcode::DefineOwnPropertyByName
| Opcode::SetPropertyGetterByName | Opcode::SetPropertyGetterByName
| Opcode::SetPropertySetterByName | Opcode::SetPropertySetterByName
| Opcode::DeletePropertyByName | Opcode::DeletePropertyByName
@ -240,26 +247,36 @@ impl CodeBlock {
| Opcode::Dec | Opcode::Dec
| Opcode::GetPropertyByValue | Opcode::GetPropertyByValue
| Opcode::SetPropertyByValue | Opcode::SetPropertyByValue
| Opcode::DefineOwnPropertyByValue
| Opcode::SetPropertyGetterByValue | Opcode::SetPropertyGetterByValue
| Opcode::SetPropertySetterByValue | Opcode::SetPropertySetterByValue
| Opcode::DeletePropertyByValue | Opcode::DeletePropertyByValue
| Opcode::ToBoolean | Opcode::ToBoolean
| Opcode::Throw | Opcode::Throw
| Opcode::TryEnd | Opcode::TryEnd
| Opcode::CatchEnd
| Opcode::CatchEnd2
| Opcode::FinallyStart | Opcode::FinallyStart
| Opcode::FinallyEnd | Opcode::FinallyEnd
| Opcode::This | Opcode::This
| Opcode::Return | Opcode::Return
| Opcode::PushDeclarativeEnvironment | Opcode::PushDeclarativeEnvironment
| Opcode::PushFunctionEnvironment
| Opcode::PopEnvironment | Opcode::PopEnvironment
| Opcode::InitIterator | Opcode::InitIterator
| Opcode::IteratorNext | Opcode::IteratorNext
| Opcode::IteratorNextFull
| Opcode::IteratorClose
| Opcode::IteratorToArray | Opcode::IteratorToArray
| Opcode::RequireObjectCoercible | Opcode::RequireObjectCoercible
| Opcode::ValueNotNullOrUndefined | Opcode::ValueNotNullOrUndefined
| Opcode::RestParameterInit
| Opcode::RestParameterPop
| Opcode::PushValueToArray | Opcode::PushValueToArray
| Opcode::PushIteratorToArray | Opcode::PushIteratorToArray
| Opcode::PushNewArray | Opcode::PushNewArray
| Opcode::PopOnReturnAdd
| Opcode::PopOnReturnSub
| Opcode::Nop => String::new(), | Opcode::Nop => String::new(),
} }
} }
@ -343,9 +360,7 @@ impl std::fmt::Display for CodeBlock {
#[derive(Debug)] #[derive(Debug)]
#[allow(missing_copy_implementations)] #[allow(missing_copy_implementations)]
pub struct JsVmFunction { pub struct JsVmFunction {}
inner: (),
}
impl JsVmFunction { impl JsVmFunction {
#[allow(clippy::new_ret_no_self)] #[allow(clippy::new_ret_no_self)]
@ -512,8 +527,10 @@ impl JsObject {
has_parameter_expressions = has_parameter_expressions || param.init().is_some(); has_parameter_expressions = has_parameter_expressions || param.init().is_some();
arguments_in_parameter_names = arguments_in_parameter_names =
arguments_in_parameter_names || param.names().contains(&"arguments"); arguments_in_parameter_names || param.names().contains(&"arguments");
is_simple_parameter_list = is_simple_parameter_list = is_simple_parameter_list
is_simple_parameter_list && !param.is_rest_param() && param.init().is_none() && !param.is_rest_param()
&& param.is_identifier()
&& param.init().is_none()
} }
// An arguments object is added when all of the following conditions are met // An arguments object is added when all of the following conditions are met
@ -543,37 +560,53 @@ impl JsObject {
local_env.initialize_binding("arguments", arguments_obj.into(), context)?; local_env.initialize_binding("arguments", arguments_obj.into(), context)?;
} }
// Add argument bindings to the function environment let arg_count = args.len();
for (i, param) in code.params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
Function::add_rest_param(param, i, args, context, &local_env);
break;
}
let value = match args.get(i).cloned() { // Push function arguments to the stack.
None => JsValue::undefined(), let args = if code.params.len() > args.len() {
Some(value) => value, let mut v = args.to_vec();
v.extend(vec![JsValue::Undefined; code.params.len() - args.len()]);
v
} else {
args.to_vec()
}; };
Function::add_arguments_to_environment(param, value, &local_env, context); for arg in args.iter().rev() {
context.vm.push(arg)
} }
let param_count = code.params.len();
let this = if this.is_null_or_undefined() {
context
.get_global_this_binding()
.expect("global env must have this binding")
} else {
this.to_object(context)
.expect("conversion to object cannot fail here")
.into()
};
context.vm.push_frame(CallFrame { context.vm.push_frame(CallFrame {
prev: None, prev: None,
code, code,
this: this.clone(), this,
pc: 0, pc: 0,
fp: context.vm.stack.len(), catch: Vec::new(),
environment: local_env, finally_return: FinallyReturn::None,
catch: None, finally_jump: Vec::new(),
pop_on_return: 0,
pop_env_on_return: 0, pop_env_on_return: 0,
finally_no_jump: false, param_count,
arg_count,
}); });
let result = context.run(); let result = context.run();
context.pop_environment(); context.pop_environment();
if has_parameter_expressions {
context.pop_environment();
}
result result
} }
@ -665,8 +698,10 @@ impl JsObject {
has_parameter_expressions = has_parameter_expressions || param.init().is_some(); has_parameter_expressions = has_parameter_expressions || param.init().is_some();
arguments_in_parameter_names = arguments_in_parameter_names =
arguments_in_parameter_names || param.names().contains(&"arguments"); arguments_in_parameter_names || param.names().contains(&"arguments");
is_simple_parameter_list = is_simple_parameter_list = is_simple_parameter_list
is_simple_parameter_list && !param.is_rest_param() && param.init().is_none() && !param.is_rest_param()
&& param.is_identifier()
&& param.init().is_none()
} }
// An arguments object is added when all of the following conditions are met // An arguments object is added when all of the following conditions are met
@ -696,41 +731,61 @@ impl JsObject {
local_env.initialize_binding("arguments", arguments_obj.into(), context)?; local_env.initialize_binding("arguments", arguments_obj.into(), context)?;
} }
// Add argument bindings to the function environment let arg_count = args.len();
for (i, param) in code.params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
Function::add_rest_param(param, i, args, context, &local_env);
break;
}
let value = match args.get(i).cloned() { // Push function arguments to the stack.
None => JsValue::undefined(), let args = if code.params.len() > args.len() {
Some(value) => value, let mut v = args.to_vec();
v.extend(vec![JsValue::Undefined; code.params.len() - args.len()]);
v
} else {
args.to_vec()
}; };
Function::add_arguments_to_environment(param, value, &local_env, context); for arg in args.iter().rev() {
context.vm.push(arg)
} }
let param_count = code.params.len();
let this = if this.is_null_or_undefined() {
context
.get_global_this_binding()
.expect("global env must have this binding")
} else {
this.to_object(context)
.expect("conversion to object cannot fail here")
.into()
};
context.vm.push_frame(CallFrame { context.vm.push_frame(CallFrame {
prev: None, prev: None,
code, code,
this, this,
pc: 0, pc: 0,
fp: context.vm.stack.len(), catch: Vec::new(),
environment: local_env, finally_return: FinallyReturn::None,
catch: None, finally_jump: Vec::new(),
pop_on_return: 0,
pop_env_on_return: 0, pop_env_on_return: 0,
finally_no_jump: false, param_count,
arg_count,
}); });
let _result = context.run()?; let result = context.run()?;
let result = context.get_this_binding(); let this = context.get_this_binding();
context.pop_environment(); context.pop_environment();
if has_parameter_expressions {
context.pop_environment();
}
result if result.is_object() {
Ok(result)
} else {
this
}
} }
} }
} }

481
boa/src/vm/mod.rs

@ -3,22 +3,25 @@
//! plus an interpreter to execute those instructions //! plus an interpreter to execute those instructions
use crate::{ use crate::{
builtins::{iterable::IteratorRecord, Array, ForInIterator}, builtins::{iterable::IteratorRecord, Array, ForInIterator, Number},
environment::{ environment::{
declarative_environment_record::DeclarativeEnvironmentRecord, declarative_environment_record::DeclarativeEnvironmentRecord,
lexical_environment::VariableScope, function_environment_record::{BindingStatus, FunctionEnvironmentRecord},
lexical_environment::{Environment, VariableScope},
}, },
property::PropertyDescriptor, property::PropertyDescriptor,
vm::code_block::Readable, value::Numeric,
vm::{call_frame::CatchAddresses, code_block::Readable},
BoaProfiler, Context, JsBigInt, JsResult, JsString, JsValue, BoaProfiler, Context, JsBigInt, JsResult, JsString, JsValue,
}; };
use std::{convert::TryInto, mem::size_of, time::Instant}; use std::{convert::TryInto, mem::size_of, ops::Neg, time::Instant};
mod call_frame; mod call_frame;
mod code_block; mod code_block;
mod opcode; mod opcode;
pub use call_frame::CallFrame; pub use call_frame::CallFrame;
pub(crate) use call_frame::FinallyReturn;
pub use code_block::{CodeBlock, JsVmFunction}; pub use code_block::{CodeBlock, JsVmFunction};
pub use opcode::Opcode; pub use opcode::Opcode;
@ -166,10 +169,10 @@ impl Context {
} }
Opcode::PushIteratorToArray => { Opcode::PushIteratorToArray => {
let next_function = self.vm.pop(); let next_function = self.vm.pop();
let for_in_iterator = self.vm.pop(); let iterator = self.vm.pop();
let array = self.vm.pop(); let array = self.vm.pop();
let iterator = IteratorRecord::new(for_in_iterator, next_function); let iterator = IteratorRecord::new(iterator, next_function);
loop { loop {
let next = iterator.next(self)?; let next = iterator.next(self)?;
@ -255,14 +258,17 @@ impl Context {
self.vm.push(value); self.vm.push(value);
} }
Opcode::Neg => { Opcode::Neg => {
let value = self.vm.pop().neg(self)?; let value = self.vm.pop();
self.vm.push(value); match value.to_numeric(self)? {
Numeric::Number(number) => self.vm.push(number.neg()),
Numeric::BigInt(bigint) => self.vm.push(JsBigInt::neg(&bigint)),
}
} }
Opcode::Inc => { Opcode::Inc => {
let value = self.vm.pop(); let value = self.vm.pop();
match value.to_numeric(self)? { match value.to_numeric(self)? {
crate::value::Numeric::Number(number) => self.vm.push(number + 1f64), Numeric::Number(number) => self.vm.push(number + 1f64),
crate::value::Numeric::BigInt(bigint) => { Numeric::BigInt(bigint) => {
self.vm.push(JsBigInt::add(&bigint, &JsBigInt::one())) self.vm.push(JsBigInt::add(&bigint, &JsBigInt::one()))
} }
} }
@ -270,8 +276,8 @@ impl Context {
Opcode::Dec => { Opcode::Dec => {
let value = self.vm.pop(); let value = self.vm.pop();
match value.to_numeric(self)? { match value.to_numeric(self)? {
crate::value::Numeric::Number(number) => self.vm.push(number - 1f64), Numeric::Number(number) => self.vm.push(number - 1f64),
crate::value::Numeric::BigInt(bigint) => { Numeric::BigInt(bigint) => {
self.vm.push(JsBigInt::sub(&bigint, &JsBigInt::one())) self.vm.push(JsBigInt::sub(&bigint, &JsBigInt::one()))
} }
} }
@ -281,15 +287,21 @@ impl Context {
self.vm.push(!value.to_boolean()); self.vm.push(!value.to_boolean());
} }
Opcode::BitNot => { Opcode::BitNot => {
let target = self.vm.pop(); let value = self.vm.pop();
let num = target.to_number(self)?; match value.to_numeric(self)? {
let value = if num.is_nan() { Numeric::Number(number) => self.vm.push(Number::not(number)),
-1 Numeric::BigInt(bigint) => self.vm.push(JsBigInt::not(&bigint)),
} else { }
// TODO: this is not spec compliant. }
!(num as i32) Opcode::DefInitArg => {
}; let index = self.vm.read::<u32>();
self.vm.push(value); let name = self.vm.frame().code.variables[index as usize].clone();
let value = self.vm.pop();
let local_env = self.get_current_environment();
local_env
.create_mutable_binding(name.as_ref(), false, true, self)
.expect("Failed to create argument binding");
self.initialize_binding(name.as_ref(), value)?;
} }
Opcode::DefVar => { Opcode::DefVar => {
let index = self.vm.read::<u32>(); let index = self.vm.read::<u32>();
@ -342,18 +354,27 @@ impl Context {
let value = self.get_binding_value(&name)?; let value = self.get_binding_value(&name)?;
self.vm.push(value); self.vm.push(value);
} }
Opcode::SetName => { Opcode::GetNameOrUndefined => {
let index = self.vm.read::<u32>(); let index = self.vm.read::<u32>();
let value = self.vm.pop();
let name = self.vm.frame().code.variables[index as usize].clone(); let name = self.vm.frame().code.variables[index as usize].clone();
if self.has_binding(name.as_ref())? { let value = if self.has_binding(&name)? {
// Binding already exists self.get_binding_value(&name)?
self.set_mutable_binding(name.as_ref(), value, self.strict())?;
} else { } else {
self.create_mutable_binding(name.as_ref(), true, VariableScope::Function)?; JsValue::Undefined
self.initialize_binding(name.as_ref(), value)?; };
self.vm.push(value)
} }
Opcode::SetName => {
let index = self.vm.read::<u32>();
let value = self.vm.pop();
let name = self.vm.frame().code.variables[index as usize].clone();
self.set_mutable_binding(
name.as_ref(),
value,
self.strict() || self.vm.frame().code.strict,
)?;
} }
Opcode::Jump => { Opcode::Jump => {
let address = self.vm.read::<u32>(); let address = self.vm.read::<u32>();
@ -370,15 +391,15 @@ impl Context {
let value = self.vm.pop(); let value = self.vm.pop();
if !value.is_undefined() { if !value.is_undefined() {
self.vm.frame_mut().pc = address as usize; self.vm.frame_mut().pc = address as usize;
self.vm.push(value)
} }
self.vm.push(value);
} }
Opcode::LogicalAnd => { Opcode::LogicalAnd => {
let exit = self.vm.read::<u32>(); let exit = self.vm.read::<u32>();
let lhs = self.vm.pop(); let lhs = self.vm.pop();
if !lhs.to_boolean() { if !lhs.to_boolean() {
self.vm.frame_mut().pc = exit as usize; self.vm.frame_mut().pc = exit as usize;
self.vm.push(false); self.vm.push(lhs);
} }
} }
Opcode::LogicalOr => { Opcode::LogicalOr => {
@ -386,7 +407,7 @@ impl Context {
let lhs = self.vm.pop(); let lhs = self.vm.pop();
if lhs.to_boolean() { if lhs.to_boolean() {
self.vm.frame_mut().pc = exit as usize; self.vm.frame_mut().pc = exit as usize;
self.vm.push(true); self.vm.push(lhs);
} }
} }
Opcode::Coalesce => { Opcode::Coalesce => {
@ -417,18 +438,18 @@ impl Context {
self.vm.push(result) self.vm.push(result)
} }
Opcode::GetPropertyByValue => { Opcode::GetPropertyByValue => {
let value = self.vm.pop(); let object = self.vm.pop();
let key = self.vm.pop(); let key = self.vm.pop();
let object = if let Some(object) = value.as_object() { let object = if let Some(object) = object.as_object() {
object.clone() object.clone()
} else { } else {
value.to_object(self)? object.to_object(self)?
}; };
let key = key.to_property_key(self)?; let key = key.to_property_key(self)?;
let result = object.get(key, self)?; let value = object.get(key, self)?;
self.vm.push(result) self.vm.push(value)
} }
Opcode::SetPropertyByName => { Opcode::SetPropertyByName => {
let index = self.vm.read::<u32>(); let index = self.vm.read::<u32>();
@ -443,7 +464,36 @@ impl Context {
let name = self.vm.frame().code.variables[index as usize].clone(); let name = self.vm.frame().code.variables[index as usize].clone();
object.set(name, value, true, self)?; object.set(
name,
value,
self.strict() || self.vm.frame().code.strict,
self,
)?;
}
Opcode::DefineOwnPropertyByName => {
let index = self.vm.read::<u32>();
let object = self.vm.pop();
let value = self.vm.pop();
let object = if let Some(object) = object.as_object() {
object.clone()
} else {
object.to_object(self)?
};
let name = self.vm.frame().code.variables[index as usize].clone();
object.__define_own_property__(
name.into(),
PropertyDescriptor::builder()
.value(value)
.writable(true)
.enumerable(true)
.configurable(true)
.build(),
self,
)?;
} }
Opcode::SetPropertyByValue => { Opcode::SetPropertyByValue => {
let object = self.vm.pop(); let object = self.vm.pop();
@ -456,7 +506,35 @@ impl Context {
}; };
let key = key.to_property_key(self)?; let key = key.to_property_key(self)?;
object.set(key, value, true, self)?; object.set(
key,
value,
self.strict() || self.vm.frame().code.strict,
self,
)?;
}
Opcode::DefineOwnPropertyByValue => {
let value = self.vm.pop();
let key = self.vm.pop();
let object = self.vm.pop();
let object = if let Some(object) = object.as_object() {
object.clone()
} else {
object.to_object(self)?
};
let key = key.to_property_key(self)?;
object.__define_own_property__(
key,
PropertyDescriptor::builder()
.value(value)
.writable(true)
.enumerable(true)
.configurable(true)
.build(),
self,
)?;
} }
Opcode::SetPropertyGetterByName => { Opcode::SetPropertyGetterByName => {
let index = self.vm.read::<u32>(); let index = self.vm.read::<u32>();
@ -484,9 +562,9 @@ impl Context {
)?; )?;
} }
Opcode::SetPropertyGetterByValue => { Opcode::SetPropertyGetterByValue => {
let object = self.vm.pop();
let key = self.vm.pop();
let value = self.vm.pop(); let value = self.vm.pop();
let key = self.vm.pop();
let object = self.vm.pop();
let object = object.to_object(self)?; let object = object.to_object(self)?;
let name = key.to_property_key(self)?; let name = key.to_property_key(self)?;
let set = object let set = object
@ -530,9 +608,9 @@ impl Context {
)?; )?;
} }
Opcode::SetPropertySetterByValue => { Opcode::SetPropertySetterByValue => {
let object = self.vm.pop();
let key = self.vm.pop();
let value = self.vm.pop(); let value = self.vm.pop();
let key = self.vm.pop();
let object = self.vm.pop();
let object = object.to_object(self)?; let object = object.to_object(self)?;
let name = key.to_property_key(self)?; let name = key.to_property_key(self)?;
let get = object let get = object
@ -556,6 +634,9 @@ impl Context {
let key = self.vm.frame().code.variables[index as usize].clone(); let key = self.vm.frame().code.variables[index as usize].clone();
let object = self.vm.pop(); let object = self.vm.pop();
let result = object.to_object(self)?.__delete__(&key.into(), self)?; let result = object.to_object(self)?.__delete__(&key.into(), self)?;
if !result && self.strict() || self.vm.frame().code.strict {
return Err(self.construct_type_error("Cannot delete property"));
}
self.vm.push(result); self.vm.push(result);
} }
Opcode::DeletePropertyByValue => { Opcode::DeletePropertyByValue => {
@ -564,6 +645,9 @@ impl Context {
let result = object let result = object
.to_object(self)? .to_object(self)?
.__delete__(&key.to_property_key(self)?, self)?; .__delete__(&key.to_property_key(self)?, self)?;
if !result && self.strict() || self.vm.frame().code.strict {
return Err(self.construct_type_error("Cannot delete property"));
}
self.vm.push(result); self.vm.push(result);
} }
Opcode::CopyDataProperties => { Opcode::CopyDataProperties => {
@ -574,8 +658,8 @@ impl Context {
} }
let value = self.vm.pop(); let value = self.vm.pop();
let object = value.as_object().unwrap(); let object = value.as_object().unwrap();
let rest_obj = self.vm.pop(); let source = self.vm.pop();
object.copy_data_properties(&rest_obj, excluded_keys, self)?; object.copy_data_properties(&source, excluded_keys, self)?;
self.vm.push(value); self.vm.push(value);
} }
Opcode::Throw => { Opcode::Throw => {
@ -583,27 +667,77 @@ impl Context {
return Err(value); return Err(value);
} }
Opcode::TryStart => { Opcode::TryStart => {
let index = self.vm.read::<u32>(); let next = self.vm.read::<u32>();
self.vm.frame_mut().catch = Some(index); let finally = self.vm.read::<u32>();
self.vm.frame_mut().finally_no_jump = false; let finally = if finally != 0 { Some(finally) } else { None };
self.vm
.frame_mut()
.catch
.push(CatchAddresses { next, finally });
self.vm.frame_mut().finally_jump.push(None);
self.vm.frame_mut().finally_return = FinallyReturn::None;
} }
Opcode::TryEnd => { Opcode::TryEnd => {
self.vm.frame_mut().catch = None; self.vm.frame_mut().catch.pop();
self.vm.frame_mut().finally_return = FinallyReturn::None;
}
Opcode::CatchStart => {
let finally = self.vm.read::<u32>();
self.vm.frame_mut().catch.push(CatchAddresses {
next: finally,
finally: Some(finally),
});
}
Opcode::CatchEnd => {
self.vm.frame_mut().catch.pop();
self.vm.frame_mut().finally_return = FinallyReturn::None;
}
Opcode::CatchEnd2 => {
self.vm.frame_mut().finally_return = FinallyReturn::None;
} }
Opcode::FinallyStart => { Opcode::FinallyStart => {
self.vm.frame_mut().finally_no_jump = true; *self
.vm
.frame_mut()
.finally_jump
.last_mut()
.expect("finally jump must exist here") = None;
} }
Opcode::FinallyEnd => { Opcode::FinallyEnd => {
if let Some(value) = self.vm.stack.pop() { let address = self
return Err(value); .vm
.frame_mut()
.finally_jump
.pop()
.expect("finally jump must exist here");
match self.vm.frame_mut().finally_return {
FinallyReturn::None => {
if let Some(address) = address {
self.vm.frame_mut().pc = address as usize;
} }
} }
Opcode::FinallyJump => { FinallyReturn::Ok => {
let address = self.vm.read::<u32>(); for _ in 0..self.vm.frame().pop_env_on_return {
if !self.vm.frame().finally_no_jump { self.pop_environment();
self.vm.frame_mut().pc = address as usize; }
self.vm.frame_mut().pop_env_on_return = 0;
let _ = self.vm.pop_frame();
return Ok(true);
} }
self.vm.frame_mut().finally_no_jump = false; FinallyReturn::Err => {
self.vm.frame_mut().finally_return = FinallyReturn::None;
return Err(self.vm.pop());
}
}
}
Opcode::FinallySetJump => {
let address = self.vm.read::<u32>();
*self
.vm
.frame_mut()
.finally_jump
.last_mut()
.expect("finally jump must exist here") = Some(address);
} }
Opcode::This => { Opcode::This => {
let this = self.get_this_binding()?; let this = self.get_this_binding()?;
@ -628,7 +762,7 @@ impl Context {
Opcode::GetFunction => { Opcode::GetFunction => {
let index = self.vm.read::<u32>(); let index = self.vm.read::<u32>();
let code = self.vm.frame().code.functions[index as usize].clone(); let code = self.vm.frame().code.functions[index as usize].clone();
let environment = self.vm.frame().environment.clone(); let environment = self.get_current_environment();
let function = JsVmFunction::new(code, environment, self); let function = JsVmFunction::new(code, environment, self);
self.vm.push(function); self.vm.push(function);
} }
@ -636,21 +770,26 @@ impl Context {
if self.vm.stack_size_limit <= self.vm.stack.len() { if self.vm.stack_size_limit <= self.vm.stack.len() {
return self.throw_range_error("Maximum call stack size exceeded"); return self.throw_range_error("Maximum call stack size exceeded");
} }
let argc = self.vm.read::<u32>(); let argument_count = self.vm.read::<u32>();
let mut args = Vec::with_capacity(argc as usize); let mut arguments = Vec::with_capacity(argument_count as usize);
for _ in 0..argc { for _ in 0..argument_count {
args.push(self.vm.pop()); arguments.push(self.vm.pop());
} }
arguments.reverse();
let func = self.vm.pop(); let func = self.vm.pop();
let this = self.vm.pop(); let mut this = self.vm.pop();
let object = match func { let object = match func {
JsValue::Object(ref object) if object.is_callable() => object.clone(), JsValue::Object(ref object) if object.is_callable() => object.clone(),
_ => return self.throw_type_error("not a callable function"), _ => return self.throw_type_error("not a callable function"),
}; };
let result = object.__call__(&this, &args, self)?; if this.is_null_or_undefined() {
this = self.global_object().into();
}
let result = object.__call__(&this, &arguments, self)?;
self.vm.push(result); self.vm.push(result);
} }
@ -658,32 +797,37 @@ impl Context {
if self.vm.stack_size_limit <= self.vm.stack.len() { if self.vm.stack_size_limit <= self.vm.stack.len() {
return self.throw_range_error("Maximum call stack size exceeded"); return self.throw_range_error("Maximum call stack size exceeded");
} }
let argc = self.vm.read::<u32>(); let argument_count = self.vm.read::<u32>();
let mut args = Vec::with_capacity(argc as usize); let rest_argument = self.vm.pop();
for _ in 0..(argc - 1) { let mut arguments = Vec::with_capacity(argument_count as usize);
args.push(self.vm.pop()); for _ in 0..(argument_count - 1) {
arguments.push(self.vm.pop());
} }
let rest_arg_value = self.vm.pop(); arguments.reverse();
let func = self.vm.pop(); let func = self.vm.pop();
let this = self.vm.pop(); let mut this = self.vm.pop();
let iterator_record = rest_arg_value.get_iterator(self, None, None)?; let iterator_record = rest_argument.get_iterator(self, None, None)?;
let mut rest_args = Vec::new(); let mut rest_arguments = Vec::new();
loop { loop {
let next = iterator_record.next(self)?; let next = iterator_record.next(self)?;
if next.done { if next.done {
break; break;
} }
rest_args.push(next.value); rest_arguments.push(next.value);
} }
args.append(&mut rest_args); arguments.append(&mut rest_arguments);
let object = match func { let object = match func {
JsValue::Object(ref object) if object.is_callable() => object.clone(), JsValue::Object(ref object) if object.is_callable() => object.clone(),
_ => return self.throw_type_error("not a callable function"), _ => return self.throw_type_error("not a callable function"),
}; };
let result = object.__call__(&this, &args, self)?; if this.is_null_or_undefined() {
this = self.global_object().into();
}
let result = object.__call__(&this, &arguments, self)?;
self.vm.push(result); self.vm.push(result);
} }
@ -691,17 +835,18 @@ impl Context {
if self.vm.stack_size_limit <= self.vm.stack.len() { if self.vm.stack_size_limit <= self.vm.stack.len() {
return self.throw_range_error("Maximum call stack size exceeded"); return self.throw_range_error("Maximum call stack size exceeded");
} }
let argc = self.vm.read::<u32>(); let argument_count = self.vm.read::<u32>();
let mut args = Vec::with_capacity(argc as usize); let mut arguments = Vec::with_capacity(argument_count as usize);
for _ in 0..argc { for _ in 0..argument_count {
args.push(self.vm.pop()); arguments.push(self.vm.pop());
} }
arguments.reverse();
let func = self.vm.pop(); let func = self.vm.pop();
let result = func let result = func
.as_constructor() .as_constructor()
.ok_or_else(|| self.construct_type_error("not a constructor")) .ok_or_else(|| self.construct_type_error("not a constructor"))
.and_then(|cons| cons.__construct__(&args, &cons.clone().into(), self))?; .and_then(|cons| cons.__construct__(&arguments, &cons.clone().into(), self))?;
self.vm.push(result); self.vm.push(result);
} }
@ -709,33 +854,41 @@ impl Context {
if self.vm.stack_size_limit <= self.vm.stack.len() { if self.vm.stack_size_limit <= self.vm.stack.len() {
return self.throw_range_error("Maximum call stack size exceeded"); return self.throw_range_error("Maximum call stack size exceeded");
} }
let argc = self.vm.read::<u32>(); let argument_count = self.vm.read::<u32>();
let mut args = Vec::with_capacity(argc as usize); let rest_argument = self.vm.pop();
for _ in 0..(argc - 1) { let mut arguments = Vec::with_capacity(argument_count as usize);
args.push(self.vm.pop()); for _ in 0..(argument_count - 1) {
arguments.push(self.vm.pop());
} }
let rest_arg_value = self.vm.pop(); arguments.reverse();
let func = self.vm.pop(); let func = self.vm.pop();
let iterator_record = rest_arg_value.get_iterator(self, None, None)?; let iterator_record = rest_argument.get_iterator(self, None, None)?;
let mut rest_args = Vec::new(); let mut rest_arguments = Vec::new();
loop { loop {
let next = iterator_record.next(self)?; let next = iterator_record.next(self)?;
if next.done { if next.done {
break; break;
} }
rest_args.push(next.value); rest_arguments.push(next.value);
} }
args.append(&mut rest_args); arguments.append(&mut rest_arguments);
let result = func let result = func
.as_constructor() .as_constructor()
.ok_or_else(|| self.construct_type_error("not a constructor")) .ok_or_else(|| self.construct_type_error("not a constructor"))
.and_then(|cons| cons.__construct__(&args, &cons.clone().into(), self))?; .and_then(|cons| cons.__construct__(&arguments, &cons.clone().into(), self))?;
self.vm.push(result); self.vm.push(result);
} }
Opcode::Return => { Opcode::Return => {
if let Some(finally_address) = self.vm.frame().catch.last().and_then(|c| c.finally)
{
let frame = self.vm.frame_mut();
frame.pc = finally_address as usize;
frame.finally_return = FinallyReturn::Ok;
frame.catch.pop();
} else {
for _ in 0..self.vm.frame().pop_env_on_return { for _ in 0..self.vm.frame().pop_env_on_return {
self.pop_environment(); self.pop_environment();
} }
@ -743,11 +896,41 @@ impl Context {
let _ = self.vm.pop_frame(); let _ = self.vm.pop_frame();
return Ok(true); return Ok(true);
} }
}
Opcode::PushDeclarativeEnvironment => { Opcode::PushDeclarativeEnvironment => {
let env = self.get_current_environment(); let env = self.get_current_environment();
self.push_environment(DeclarativeEnvironmentRecord::new(Some(env))); self.push_environment(DeclarativeEnvironmentRecord::new(Some(env)));
self.vm.frame_mut().pop_env_on_return += 1; self.vm.frame_mut().pop_env_on_return += 1;
} }
Opcode::PushFunctionEnvironment => {
let is_constructor = self.vm.frame().code.constructor;
let is_lexical = self.vm.frame().code.this_mode.is_lexical();
let current_env = self.get_current_environment();
let this = &self.vm.frame().this;
let new_env = FunctionEnvironmentRecord::new(
this.clone()
.as_object()
.expect("this must always be an object")
.clone(),
if is_constructor || !is_lexical {
Some(this.clone())
} else {
None
},
Some(current_env),
if is_lexical {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
JsValue::undefined(),
self,
)?;
let new_env: Environment = new_env.into();
self.push_environment(new_env);
}
Opcode::PopEnvironment => { Opcode::PopEnvironment => {
let _ = self.pop_environment(); let _ = self.pop_environment();
self.vm.frame_mut().pop_env_on_return -= 1; self.vm.frame_mut().pop_env_on_return -= 1;
@ -761,50 +944,67 @@ impl Context {
} }
let object = object.to_object(self)?; let object = object.to_object(self)?;
let for_in_iterator = let iterator = ForInIterator::create_for_in_iterator(JsValue::new(object), self);
ForInIterator::create_for_in_iterator(JsValue::new(object), self); let next_function = iterator
let next_function = for_in_iterator
.get_property("next") .get_property("next")
.as_ref() .as_ref()
.map(|p| p.expect_value()) .map(|p| p.expect_value())
.cloned() .cloned()
.ok_or_else(|| self.construct_type_error("Could not find property `next`"))?; .ok_or_else(|| self.construct_type_error("Could not find property `next`"))?;
self.vm.push(for_in_iterator); self.vm.push(iterator);
self.vm.push(next_function); self.vm.push(next_function);
} }
Opcode::InitIterator => { Opcode::InitIterator => {
let iterable = self.vm.pop(); let object = self.vm.pop();
let iterator = iterable.get_iterator(self, None, None)?; let iterator = object.get_iterator(self, None, None)?;
self.vm.push(iterator.iterator_object()); self.vm.push(iterator.iterator_object());
self.vm.push(iterator.next_function()); self.vm.push(iterator.next_function());
} }
Opcode::IteratorNext => { Opcode::IteratorNext => {
let next_function = self.vm.pop(); let next_function = self.vm.pop();
let for_in_iterator = self.vm.pop(); let iterator = self.vm.pop();
let iterator = IteratorRecord::new(for_in_iterator.clone(), next_function.clone()); let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone());
let iterator_result = iterator.next(self)?; let iterator_result = iterator_record.next(self)?;
self.vm.push(for_in_iterator); self.vm.push(iterator);
self.vm.push(next_function); self.vm.push(next_function);
self.vm.push(iterator_result.value); self.vm.push(iterator_result.value);
} }
Opcode::IteratorNextFull => {
let next_function = self.vm.pop();
let iterator = self.vm.pop();
let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone());
let iterator_result = iterator_record.next(self)?;
self.vm.push(iterator);
self.vm.push(next_function);
self.vm.push(iterator_result.done);
self.vm.push(iterator_result.value);
}
Opcode::IteratorClose => {
let done = self.vm.pop();
let next_function = self.vm.pop();
let iterator = self.vm.pop();
if !done.as_boolean().unwrap() {
let iterator_record = IteratorRecord::new(iterator, next_function);
iterator_record.close(Ok(JsValue::Null), self)?;
}
}
Opcode::IteratorToArray => { Opcode::IteratorToArray => {
let next_function = self.vm.pop(); let next_function = self.vm.pop();
let for_in_iterator = self.vm.pop(); let iterator = self.vm.pop();
let iterator = IteratorRecord::new(for_in_iterator.clone(), next_function.clone()); let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone());
let mut values = Vec::new(); let mut values = Vec::new();
loop { loop {
let next = iterator.next(self)?; let next = iterator_record.next(self)?;
if next.done { if next.done {
break; break;
} }
values.push(next.value); values.push(next.value);
} }
@ -813,7 +1013,7 @@ impl Context {
Array::add_to_array_object(&array.clone().into(), &values, self)?; Array::add_to_array_object(&array.clone().into(), &values, self)?;
self.vm.push(for_in_iterator); self.vm.push(iterator);
self.vm.push(next_function); self.vm.push(next_function);
self.vm.push(array); self.vm.push(array);
} }
@ -821,29 +1021,32 @@ impl Context {
let address = self.vm.read::<u32>(); let address = self.vm.read::<u32>();
let next_function = self.vm.pop(); let next_function = self.vm.pop();
let for_in_iterator = self.vm.pop(); let iterator = self.vm.pop();
let iterator = IteratorRecord::new(for_in_iterator.clone(), next_function.clone()); let iterator_record = IteratorRecord::new(iterator.clone(), next_function.clone());
let iterator_result = iterator.next(self)?; let iterator_result = iterator_record.next(self)?;
if iterator_result.done { if iterator_result.done {
self.vm.frame_mut().pc = address as usize; self.vm.frame_mut().pc = address as usize;
self.vm.frame_mut().pop_env_on_return -= 1; self.vm.frame_mut().pop_env_on_return -= 1;
self.pop_environment(); self.pop_environment();
self.vm.push(iterator);
self.vm.push(next_function);
} else { } else {
self.vm.push(for_in_iterator); self.vm.push(iterator);
self.vm.push(next_function); self.vm.push(next_function);
self.vm.push(iterator_result.value); self.vm.push(iterator_result.value);
} }
} }
Opcode::ConcatToString => { Opcode::ConcatToString => {
let n = self.vm.read::<u32>(); let value_count = self.vm.read::<u32>();
let mut s = JsString::new(""); let mut strings = Vec::with_capacity(value_count as usize);
for _ in 0..value_count {
for _ in 0..n { strings.push(self.vm.pop().to_string(self)?);
let obj = self.vm.pop(); }
s = JsString::concat(s, obj.to_string(self)?); strings.reverse();
} let s = JsString::concat_array(
&strings.iter().map(|s| s.as_str()).collect::<Vec<&str>>(),
);
self.vm.push(s); self.vm.push(s);
} }
Opcode::RequireObjectCoercible => { Opcode::RequireObjectCoercible => {
@ -861,6 +1064,39 @@ impl Context {
} }
self.vm.push(value); self.vm.push(value);
} }
Opcode::RestParameterInit => {
let arg_count = self.vm.frame().arg_count;
let param_count = self.vm.frame().param_count;
if arg_count >= param_count {
let rest_count = arg_count - param_count + 1;
let mut args = Vec::with_capacity(rest_count);
for _ in 0..rest_count {
args.push(self.vm.pop());
}
let array = Array::new_array(self);
Array::add_to_array_object(&array, &args, self).unwrap();
self.vm.push(array);
} else {
self.vm.pop();
let array = Array::new_array(self);
self.vm.push(array);
}
}
Opcode::RestParameterPop => {
let arg_count = self.vm.frame().arg_count;
let param_count = self.vm.frame().param_count;
if arg_count > param_count {
for _ in 0..(arg_count - param_count) {
self.vm.pop();
}
}
}
Opcode::PopOnReturnAdd => {
self.vm.frame_mut().pop_on_return += 1;
}
Opcode::PopOnReturnSub => {
self.vm.frame_mut().pop_on_return -= 1;
}
} }
Ok(false) Ok(false)
@ -945,13 +1181,18 @@ impl Context {
} }
} }
Err(e) => { Err(e) => {
if let Some(address) = self.vm.frame().catch { if let Some(address) = self.vm.frame().catch.last() {
let address = address.next;
if self.vm.frame().pop_env_on_return > 0 { if self.vm.frame().pop_env_on_return > 0 {
self.pop_environment(); self.pop_environment();
self.vm.frame_mut().pop_env_on_return -= 1; self.vm.frame_mut().pop_env_on_return -= 1;
} }
for _ in 0..self.vm.frame().pop_on_return {
self.vm.pop();
}
self.vm.frame_mut().pc = address as usize; self.vm.frame_mut().pc = address as usize;
self.vm.frame_mut().catch = None; self.vm.frame_mut().catch.pop();
self.vm.frame_mut().finally_return = FinallyReturn::Err;
self.vm.push(e); self.vm.push(e);
} else { } else {
for _ in 0..self.vm.frame().pop_env_on_return { for _ in 0..self.vm.frame().pop_env_on_return {

246
boa/src/vm/opcode.rs

@ -22,21 +22,21 @@ pub enum Opcode {
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: v1, v2 **=>** v2, v1 /// Stack: second, first **=>** first, second
Swap, Swap,
/// Push integer `0` on the stack. /// Push integer `0` on the stack.
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: **=>** 0 /// Stack: **=>** `0`
PushZero, PushZero,
/// Push integer `1` on the stack. /// Push integer `1` on the stack.
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: **=>** 1 /// Stack: **=>** `1`
PushOne, PushOne,
/// Push `i8` value on the stack. /// Push `i8` value on the stack.
@ -67,7 +67,7 @@ pub enum Opcode {
/// Stack: **=>** value /// Stack: **=>** value
PushRational, PushRational,
/// Push `NaN` teger on the stack. /// Push `NaN` integer on the stack.
/// ///
/// Operands: /// Operands:
/// ///
@ -118,7 +118,7 @@ pub enum Opcode {
/// Push literal value on the stack. /// Push literal value on the stack.
/// ///
/// Like strings and bigints. The index oprand is used to index into the `literals` /// Like strings and bigints. The index operand is used to index into the `literals`
/// array to get the value. /// array to get the value.
/// ///
/// Operands: index: `u32` /// Operands: index: `u32`
@ -130,22 +130,28 @@ pub enum Opcode {
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: **=>** object /// Stack: **=>** `{}`
PushEmptyObject, PushEmptyObject,
/// Push an empty array value on the stack. /// Push an empty array value on the stack.
/// ///
/// Stack: **=>** `array` /// Operands:
///
/// Stack: **=>** `[]`
PushNewArray, PushNewArray,
/// Push a value to an array. /// Push a value to an array.
/// ///
/// Stack: `array`, `value` **=>** `array` /// Operands:
///
/// Stack: array, value **=>** array
PushValueToArray, PushValueToArray,
/// Push all iterator values to an array. /// Push all iterator values to an array.
/// ///
/// Stack: `array`, `iterator`, `next_function` **=>** `array` /// Operands:
///
/// Stack: array, iterator, next_function **=>** array
PushIteratorToArray, PushIteratorToArray,
/// Binary `+` operator. /// Binary `+` operator.
@ -311,7 +317,7 @@ pub enum Opcode {
/// Binary logical `&&` operator. /// Binary logical `&&` operator.
/// ///
/// This is a short-circit operator, if the `lhs` value is `false`, then it jumps to `exit` address. /// This is a short-circuit operator, if the `lhs` value is `false`, then it jumps to `exit` address.
/// ///
/// Operands: exit: `u32` /// Operands: exit: `u32`
/// ///
@ -320,7 +326,7 @@ pub enum Opcode {
/// Binary logical `||` operator. /// Binary logical `||` operator.
/// ///
/// This is a short-circit operator, if the `lhs` value is `true`, then it jumps to `exit` address. /// This is a short-circuit operator, if the `lhs` value is `true`, then it jumps to `exit` address.
/// ///
/// Operands: exit: `u32` /// Operands: exit: `u32`
/// ///
@ -329,7 +335,7 @@ pub enum Opcode {
/// Binary `??` operator. /// Binary `??` operator.
/// ///
/// This is a short-circit operator, if the `lhs` value is **not** `null` or `undefined`, /// This is a short-circuit operator, if the `lhs` value is **not** `null` or `undefined`,
/// then it jumps to `exit` address. /// then it jumps to `exit` address.
/// ///
/// Operands: exit: `u32` /// Operands: exit: `u32`
@ -386,6 +392,13 @@ pub enum Opcode {
/// Stack: value **=>** (value - 1) /// Stack: value **=>** (value - 1)
Dec, Dec,
/// Declare and initialize a function argument.
///
/// Operands: name_index: `u32`
///
/// Stack: value **=>**
DefInitArg,
/// Declare `var` type variable. /// Declare `var` type variable.
/// ///
/// Operands: name_index: `u32` /// Operands: name_index: `u32`
@ -428,6 +441,13 @@ pub enum Opcode {
/// Stack: **=>** value /// Stack: **=>** value
GetName, GetName,
/// Find a binding on the environment chain and push its value. If the binding does not exist push undefined.
///
/// Operands: name_index: `u32`
///
/// Stack: **=>** value
GetNameOrUndefined,
/// Find a binding on the environment chain and assign its value. /// Find a binding on the environment chain and assign its value.
/// ///
/// Operands: name_index: `u32` /// Operands: name_index: `u32`
@ -462,6 +482,13 @@ pub enum Opcode {
/// Stack: value, object **=>** /// Stack: value, object **=>**
SetPropertyByName, SetPropertyByName,
/// Defines a own property of an object by name.
///
/// Operands: name_index: `u32`
///
/// Stack: value, object **=>**
DefineOwnPropertyByName,
/// Sets a property by value of an object. /// Sets a property by value of an object.
/// ///
/// Like `object[key] = value` /// Like `object[key] = value`
@ -471,6 +498,13 @@ pub enum Opcode {
/// Stack: value, key, object **=>** /// Stack: value, key, object **=>**
SetPropertyByValue, SetPropertyByValue,
/// Defines a own property of an object by value.
///
/// Operands:
///
/// Stack: object, key, value **=>**
DefineOwnPropertyByValue,
/// Sets a getter property by name of an object. /// Sets a getter property by name of an object.
/// ///
/// Like `get name() value` /// Like `get name() value`
@ -486,7 +520,7 @@ pub enum Opcode {
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: value, key, object **=>** /// Stack: object, key, value **=>**
SetPropertyGetterByValue, SetPropertyGetterByValue,
/// Sets a setter property by name of an object. /// Sets a setter property by name of an object.
@ -504,7 +538,7 @@ pub enum Opcode {
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: value, key, object **=>** /// Stack: object, key, value **=>**
SetPropertySetterByValue, SetPropertySetterByValue,
/// Deletes a property by name of an object. /// Deletes a property by name of an object.
@ -527,9 +561,9 @@ pub enum Opcode {
/// Copy all properties of one object to another object. /// Copy all properties of one object to another object.
/// ///
/// Operands: number of excluded keys: `u32` /// Operands: excluded_key_count: `u32`
/// ///
/// Stack: object, rest_object, excluded_key_0 ... excluded_key_n **=>** object /// Stack: source, value, excluded_key_0 ... excluded_key_n **=>** value
CopyDataProperties, CopyDataProperties,
/// Unconditional jump to address. /// Unconditional jump to address.
@ -563,27 +597,64 @@ pub enum Opcode {
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: `exc` **=>** /// Stack: value **=>**
Throw, Throw,
/// Start of a try block. /// Start of a try block.
/// ///
/// Operands: address: `u32` /// Operands: next_address: `u32`, finally_address: `u32`
///
/// Stack: **=>**
TryStart, TryStart,
/// End of a try block. /// End of a try block.
///
/// Operands:
///
/// Stack: **=>**
TryEnd, TryEnd,
/// Start of a catch block.
///
/// Operands:
///
/// Stack: **=>**
CatchStart,
/// End of a catch block.
///
/// Operands:
///
/// Stack: **=>**
CatchEnd,
/// End of a catch block.
///
/// Operands:
///
/// Stack: **=>**
CatchEnd2,
/// Start of a finally block. /// Start of a finally block.
///
/// Operands:
///
/// Stack: **=>**
FinallyStart, FinallyStart,
/// End of a finally block. /// End of a finally block.
///
/// Operands:
///
/// Stack: **=>**
FinallyEnd, FinallyEnd,
/// Jump if the finally block was entered trough a break statement. /// Set the address for a finally jump.
/// ///
/// Operands: address: `u32` /// Operands:
FinallyJump, ///
/// Stack: **=>**
FinallySetJump,
/// Pops value converts it to boolean and pushes it back. /// Pops value converts it to boolean and pushes it back.
/// ///
@ -596,15 +667,15 @@ pub enum Opcode {
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: **=>** `this` /// Stack: **=>** this
This, This,
/// Pop the two values of the stack, strict equal compares the two values, /// Pop the two values of the stack, strict equal compares the two values,
/// if true jumps to address, otherwise push the second poped value. /// if true jumps to address, otherwise push the second pop'ed value.
/// ///
/// Operands: address: `u32` /// Operands: address: `u32`
/// ///
/// Stack: `value`, `cond` **=>** `cond` (if `cond !== value`). /// Stack: value, cond **=>** cond (if `cond !== value`).
Case, Case,
/// Pops the top of stack and jump to address. /// Pops the top of stack and jump to address.
@ -614,96 +685,169 @@ pub enum Opcode {
/// Stack: `value` **=>** /// Stack: `value` **=>**
Default, Default,
/// Get function from the precompiled inner functions. /// Get function from the pre-compiled inner functions.
/// ///
/// Operands: address: `u32` /// Operands: address: `u32`
/// ///
/// Stack: **=>** `func` /// Stack: **=>** func
GetFunction, GetFunction,
/// Call a function. /// Call a function.
/// ///
/// Operands: argc: `u32` /// Operands: argument_count: `u32`
/// ///
/// Stack: `func`, `this`, `arg1`, `arg2`,...`argn` **=>** /// Stack: func, this, argument_1, ... argument_n **=>** result
Call, Call,
/// Call a function where the last argument is a rest parameter. /// Call a function where the last argument is a rest parameter.
/// ///
/// Operands: argc: `u32` /// Operands: argument_count: `u32`
/// ///
/// Stack: `func`, `this`, `arg1`, `arg2`,...`argn` **=>** /// Stack: func, this, argument_1, ... argument_n **=>** result
CallWithRest, CallWithRest,
/// Call construct on a function. /// Call construct on a function.
/// ///
/// Operands: argc: `u32` /// Operands: argument_count: `u32`
/// ///
/// Stack: `func`, `arg1`, `arg2`,...`argn` **=>** /// Stack: func, argument_1, ... argument_n **=>** result
New, New,
/// Call construct on a function where the last argument is a rest parameter. /// Call construct on a function where the last argument is a rest parameter.
/// ///
/// Operands: argc: `u32` /// Operands: argument_count: `u32`
/// ///
/// Stack: `func`, `arg1`, `arg2`,...`argn` **=>** /// Stack: func, argument_1, ... argument_n **=>** result
NewWithRest, NewWithRest,
/// Return from a function. /// Return from a function.
///
/// Operands:
///
/// Stack: **=>**
Return, Return,
/// Push a declarative environment. /// Push a declarative environment.
///
/// Operands:
///
/// Stack: **=>**
PushDeclarativeEnvironment, PushDeclarativeEnvironment,
/// Push a function environment.
///
/// Operands:
///
/// Stack: **=>**
PushFunctionEnvironment,
/// Pop the current environment. /// Pop the current environment.
///
/// Operands:
///
/// Stack: **=>**
PopEnvironment, PopEnvironment,
/// Initialize the iterator for a for..in loop or jump to after the loop if object is null or undefined. /// Initialize the iterator for a for..in loop or jump to after the loop if object is null or undefined.
/// ///
/// Operands: address: `u32` /// Operands: address: `u32`
/// ///
/// Stack: `object` **=>** `for_in_iterator`, `next_function` /// Stack: object **=>** iterator, next_function
ForInLoopInitIterator, ForInLoopInitIterator,
/// Initialize an iterator. /// Initialize an iterator.
/// ///
/// Stack: `object` **=>** `iterator`, `next_function` /// Operands:
///
/// Stack: object **=>** iterator, next_function
InitIterator, InitIterator,
/// Advance the iterator by one and put the value on the stack. /// Advance the iterator by one and put the value on the stack.
/// ///
/// Stack: `iterator`, `next_function` **=>** `for_of_iterator`, `next_function`, `next_value` /// Operands:
///
/// Stack: iterator, next_function **=>** iterator, next_function, next_value
IteratorNext, IteratorNext,
/// Advance the iterator by one and put done and value on the stack.
///
/// Operands:
///
/// Stack: iterator, next_function **=>** iterator, next_function, next_done, next_value
IteratorNextFull,
/// Close an iterator.
///
/// Operands:
///
/// Stack: iterator, next_function, done **=>**
IteratorClose,
/// Consume the iterator and construct and array with all the values. /// Consume the iterator and construct and array with all the values.
/// ///
/// Stack: `iterator`, `next_function` **=>** `for_of_iterator`, `next_function`, `array` /// Operands:
///
/// Stack: iterator, next_function **=>** iterator, next_function, array
IteratorToArray, IteratorToArray,
/// Move to the next value in a for..in loop or jump to exit of the loop if done. /// Move to the next value in a for..in loop or jump to exit of the loop if done.
/// ///
/// Note: next_result is only pushed if the iterator is not done.
///
/// Operands: address: `u32` /// Operands: address: `u32`
/// ///
/// Stack: `for_in_iterator`, `next_function` **=>** `for_in_iterator`, `next_function`, `next_result` (if not done) /// Stack: iterator, next_function **=>** iterator, next_function, next_result
ForInLoopNext, ForInLoopNext,
/// Concat multiple stack objects into a string. /// Concat multiple stack objects into a string.
/// ///
/// Operands: number of stack objects: `u32` /// Operands: value_count: `u32`
/// ///
/// Stack: `value1`,...`valuen` **=>** `string` /// Stack: value_1,...value_n **=>** string
ConcatToString, ConcatToString,
/// Call RequireObjectCoercible on the stack value. /// Call RequireObjectCoercible on the stack value.
/// ///
/// Stack: `value` **=>** `value` /// Operands:
///
/// Stack: value **=>** value
RequireObjectCoercible, RequireObjectCoercible,
/// Require the stack value to be neither null nor undefined. /// Require the stack value to be neither null nor undefined.
/// ///
/// Stack: `value` **=>** `value` /// Operands:
///
/// Stack: value **=>** value
ValueNotNullOrUndefined, ValueNotNullOrUndefined,
/// Initialize the rest parameter value of a function from the remaining arguments.
///
/// Operands:
///
/// Stack: `argument_1` .. `argument_n` **=>** `array`
RestParameterInit,
/// Pop the remaining arguments of a function.
///
/// Operands:
///
/// Stack: `argument_1` .. `argument_n` **=>**
RestParameterPop,
/// Add one to the pop on return count.
///
/// Operands:
///
/// Stack: **=>**
PopOnReturnAdd,
/// Subtract one from the pop on return count.
///
/// Operands:
///
/// Stack: **=>**
PopOnReturnSub,
/// No-operation instruction, does nothing. /// No-operation instruction, does nothing.
/// ///
/// Operands: /// Operands:
@ -780,17 +924,21 @@ impl Opcode {
Opcode::Neg => "Neg", Opcode::Neg => "Neg",
Opcode::Inc => "Inc", Opcode::Inc => "Inc",
Opcode::Dec => "Dec", Opcode::Dec => "Dec",
Opcode::DefInitArg => "DefInitArg",
Opcode::DefVar => "DefVar", Opcode::DefVar => "DefVar",
Opcode::DefInitVar => "DefInitVar", Opcode::DefInitVar => "DefInitVar",
Opcode::DefLet => "DefLet", Opcode::DefLet => "DefLet",
Opcode::DefInitLet => "DefInitLet", Opcode::DefInitLet => "DefInitLet",
Opcode::DefInitConst => "DefInitConst", Opcode::DefInitConst => "DefInitConst",
Opcode::GetName => "GetName", Opcode::GetName => "GetName",
Opcode::GetNameOrUndefined => "GetNameOrUndefined",
Opcode::SetName => "SetName", Opcode::SetName => "SetName",
Opcode::GetPropertyByName => "GetPropertyByName", Opcode::GetPropertyByName => "GetPropertyByName",
Opcode::GetPropertyByValue => "GetPropertyByValue", Opcode::GetPropertyByValue => "GetPropertyByValue",
Opcode::SetPropertyByName => "SetPropertyByName", Opcode::SetPropertyByName => "SetPropertyByName",
Opcode::DefineOwnPropertyByName => "DefineOwnPropertyByName",
Opcode::SetPropertyByValue => "SetPropertyByValue", Opcode::SetPropertyByValue => "SetPropertyByValue",
Opcode::DefineOwnPropertyByValue => "DefineOwnPropertyByValue",
Opcode::SetPropertyGetterByName => "SetPropertyGetterByName", Opcode::SetPropertyGetterByName => "SetPropertyGetterByName",
Opcode::SetPropertyGetterByValue => "SetPropertyGetterByValue", Opcode::SetPropertyGetterByValue => "SetPropertyGetterByValue",
Opcode::SetPropertySetterByName => "SetPropertySetterByName", Opcode::SetPropertySetterByName => "SetPropertySetterByName",
@ -804,9 +952,12 @@ impl Opcode {
Opcode::Throw => "Throw", Opcode::Throw => "Throw",
Opcode::TryStart => "TryStart", Opcode::TryStart => "TryStart",
Opcode::TryEnd => "TryEnd", Opcode::TryEnd => "TryEnd",
Opcode::CatchStart => "CatchStart",
Opcode::CatchEnd => "CatchEnd",
Opcode::CatchEnd2 => "CatchEnd2",
Opcode::FinallyStart => "FinallyStart", Opcode::FinallyStart => "FinallyStart",
Opcode::FinallyEnd => "FinallyEnd", Opcode::FinallyEnd => "FinallyEnd",
Opcode::FinallyJump => "FinallyJump", Opcode::FinallySetJump => "FinallySetJump",
Opcode::ToBoolean => "ToBoolean", Opcode::ToBoolean => "ToBoolean",
Opcode::This => "This", Opcode::This => "This",
Opcode::Case => "Case", Opcode::Case => "Case",
@ -818,15 +969,22 @@ impl Opcode {
Opcode::NewWithRest => "NewWithRest", Opcode::NewWithRest => "NewWithRest",
Opcode::Return => "Return", Opcode::Return => "Return",
Opcode::PushDeclarativeEnvironment => "PushDeclarativeEnvironment", Opcode::PushDeclarativeEnvironment => "PushDeclarativeEnvironment",
Opcode::PushFunctionEnvironment => "PushFunctionEnvironment",
Opcode::PopEnvironment => "PopEnvironment", Opcode::PopEnvironment => "PopEnvironment",
Opcode::ForInLoopInitIterator => "ForInLoopInitIterator", Opcode::ForInLoopInitIterator => "ForInLoopInitIterator",
Opcode::InitIterator => "InitIterator", Opcode::InitIterator => "InitIterator",
Opcode::IteratorNext => "IteratorNext", Opcode::IteratorNext => "IteratorNext",
Opcode::IteratorNextFull => "IteratorNextFull",
Opcode::IteratorClose => "IteratorClose",
Opcode::IteratorToArray => "IteratorToArray", Opcode::IteratorToArray => "IteratorToArray",
Opcode::ForInLoopNext => "ForInLoopNext", Opcode::ForInLoopNext => "ForInLoopNext",
Opcode::ConcatToString => "ConcatToString", Opcode::ConcatToString => "ConcatToString",
Opcode::RequireObjectCoercible => "RequireObjectCoercible", Opcode::RequireObjectCoercible => "RequireObjectCoercible",
Opcode::ValueNotNullOrUndefined => "ValueNotNullOrUndefined", Opcode::ValueNotNullOrUndefined => "ValueNotNullOrUndefined",
Opcode::RestParameterInit => "FunctionRestParameter",
Opcode::RestParameterPop => "RestParameterPop",
Opcode::PopOnReturnAdd => "PopOnReturnAdd",
Opcode::PopOnReturnSub => "PopOnReturnSub",
Opcode::Nop => "Nop", Opcode::Nop => "Nop",
} }
} }

13
boa_tester/src/exec/js262.rs

@ -1,11 +1,13 @@
use boa::{ use boa::{
builtins::JsArgs, builtins::JsArgs,
exec::Executable,
object::{JsObject, ObjectInitializer}, object::{JsObject, ObjectInitializer},
property::Attribute, property::Attribute,
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
#[cfg(not(feature = "vm"))]
use boa::exec::Executable;
/// Initializes the object in the context. /// Initializes the object in the context.
pub(super) fn init(context: &mut Context) -> JsObject { pub(super) fn init(context: &mut Context) -> JsObject {
let global_obj = context.global_object(); let global_obj = context.global_object();
@ -84,13 +86,16 @@ fn detach_array_buffer(
/// ///
/// Accepts a string value as its first argument and executes it as an ECMAScript script. /// Accepts a string value as its first argument and executes it as an ECMAScript script.
fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> { fn eval_script(_this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
// eprintln!("called $262.evalScript()");
if let Some(source_text) = args.get(0).and_then(|val| val.as_string()) { if let Some(source_text) = args.get(0).and_then(|val| val.as_string()) {
match boa::parse(source_text.as_str(), false) { match boa::parse(source_text.as_str(), false) {
// TODO: check strict // TODO: check strict
Err(e) => context.throw_type_error(format!("Uncaught Syntax Error: {}", e)), Err(e) => context.throw_type_error(format!("Uncaught Syntax Error: {}", e)),
Ok(script) => script.run(context), #[cfg(not(feature = "vm"))]
Ok(statement_list) => statement_list.run(context),
// Calling eval here parses the code a second time.
// TODO: We can fix this after we have have defined the public api for the vm executer.
#[cfg(feature = "vm")]
Ok(_) => context.eval(source_text.as_str()),
} }
} else { } else {
Ok(JsValue::undefined()) Ok(JsValue::undefined())

Loading…
Cancel
Save