Browse Source

Redesign bytecode virtual machine (#1361)

- Implemented Jump and JumpIfFalse opcodes
 - Implemented If in vm
 - Implement assign to identifiers
 - Implement JumpIfTrue opcode
 - Implemented short circuit operators
 - Implement while loop
 - Implement do-while loop
 - Pop expressions
 - Split compilation for epression and statement
 - simplify boolean compilation
 - Implement loop break and continue with labels
 - Implement getting property from object
 - Implement setting object properties
 - Implement binary assign
 - Implement throw
 - Split variable names from literals
 - Implement conditional operator
 - Implement array expressions without spread
 - Implement some unary operators
 - Add accessor types
 - Implement this
 - Add opcode for pushing rationals
 - Implement instanceof operator
pull/1387/head
Halid Odat 3 years ago committed by GitHub
parent
commit
f33dbcc827
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 693
      boa/src/bytecompiler.rs
  2. 16
      boa/src/context.rs
  3. 2
      boa/src/exec/mod.rs
  4. 6
      boa/src/lib.rs
  5. 10
      boa/src/object/internal_methods.rs
  6. 2
      boa/src/syntax/ast/node/block/mod.rs
  7. 12
      boa/src/syntax/ast/node/identifier/mod.rs
  8. 2
      boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs
  9. 2
      boa/src/syntax/ast/node/iteration/for_in_loop/mod.rs
  10. 2
      boa/src/syntax/ast/node/iteration/for_loop/mod.rs
  11. 2
      boa/src/syntax/ast/node/iteration/for_of_loop/mod.rs
  12. 14
      boa/src/syntax/ast/node/iteration/while_loop/mod.rs
  13. 1
      boa/src/syntax/ast/node/mod.rs
  14. 17
      boa/src/syntax/ast/node/object/mod.rs
  15. 56
      boa/src/syntax/ast/node/operator/bin_op/mod.rs
  16. 27
      boa/src/syntax/ast/node/operator/unary_op/mod.rs
  17. 16
      boa/src/syntax/ast/node/statement_list/mod.rs
  18. 2
      boa/src/syntax/ast/node/switch/mod.rs
  19. 12
      boa/src/value/mod.rs
  20. 215
      boa/src/vm/code_block.rs
  21. 112
      boa/src/vm/compilation.rs
  22. 127
      boa/src/vm/instructions.rs
  23. 727
      boa/src/vm/mod.rs
  24. 614
      boa/src/vm/opcode.rs
  25. 77
      docs/vm.md

693
boa/src/bytecompiler.rs

@ -0,0 +1,693 @@
use crate::{
syntax::ast::{
node::{GetConstField, GetField, Identifier, StatementList},
op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp},
Const, Node,
},
value::{RcBigInt, RcString},
vm::{CodeBlock, Opcode},
Value,
};
use std::collections::HashMap;
#[inline]
fn u16_to_array(value: u16) -> [u8; 2] {
// Safety: Transmuting a `u16` primitive to
// an array of 2 bytes is safe.
unsafe { std::mem::transmute(value) }
}
#[inline]
fn u32_to_array(value: u32) -> [u8; 4] {
// Safety: Transmuting a `u32` primitive to
// an array of 4 bytes is safe.
unsafe { std::mem::transmute(value) }
}
#[inline]
fn u64_to_array(value: u64) -> [u8; 8] {
// Safety: Transmuting a `u64` primitive to
// an array of 8 bytes is safe.
unsafe { std::mem::transmute(value) }
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Literal {
String(RcString),
BigInt(RcBigInt),
}
#[must_use]
#[derive(Debug, Clone, Copy)]
struct Label {
index: u32,
}
#[derive(Debug, Clone)]
struct LoopControlInfo {
label: Option<Box<str>>,
loop_start: u32,
continues: Vec<Label>,
breaks: Vec<Label>,
}
#[derive(Debug, Clone, Copy)]
enum Access<'a> {
Variable { name: &'a Identifier },
ByName { node: &'a GetConstField },
ByValue { node: &'a GetField },
This,
}
#[derive(Debug)]
pub struct ByteCompiler {
code_block: CodeBlock,
literals_map: HashMap<Literal, u32>,
names_map: HashMap<RcString, u32>,
loops: Vec<LoopControlInfo>,
}
impl Default for ByteCompiler {
fn default() -> Self {
Self::new()
}
}
impl ByteCompiler {
/// Represents a placeholder address that will be patched later.
const DUMMY_ADDRESS: u32 = u32::MAX;
#[inline]
pub fn new() -> Self {
Self {
code_block: CodeBlock::new(),
literals_map: HashMap::new(),
names_map: HashMap::new(),
loops: Vec::new(),
}
}
#[inline]
fn get_or_insert_literal(&mut self, liternal: Literal) -> u32 {
if let Some(index) = self.literals_map.get(&liternal) {
return *index;
}
let value = match liternal.clone() {
Literal::String(value) => Value::from(value),
Literal::BigInt(value) => Value::from(value),
};
let index = self.code_block.literals.len() as u32;
self.code_block.literals.push(value);
self.literals_map.insert(liternal, index);
index
}
#[inline]
fn get_or_insert_name(&mut self, name: &str) -> u32 {
if let Some(index) = self.names_map.get(name) {
return *index;
}
let name: RcString = name.into();
let index = self.code_block.names.len() as u32;
self.code_block.names.push(name.clone());
self.names_map.insert(name, index);
index
}
#[inline]
fn next_opcode_location(&mut self) -> u32 {
assert!(self.code_block.code.len() < u32::MAX as usize);
self.code_block.code.len() as u32
}
#[inline]
fn emit(&mut self, opcode: Opcode, operands: &[u32]) {
self.emit_opcode(opcode);
for operand in operands {
self.emit_u32(*operand);
}
}
#[inline]
fn emit_u64(&mut self, value: u64) {
self.code_block.code.extend(&u64_to_array(value));
}
#[inline]
fn emit_u32(&mut self, value: u32) {
self.code_block.code.extend(&u32_to_array(value));
}
#[inline]
fn emit_u16(&mut self, value: u16) {
self.code_block.code.extend(&u16_to_array(value));
}
#[inline]
fn emit_opcode(&mut self, opcode: Opcode) {
self.emit_u8(opcode as u8)
}
#[inline]
fn emit_u8(&mut self, value: u8) {
self.code_block.code.push(value);
}
#[inline]
fn emit_push_integer(&mut self, value: i32) {
match value {
0 => self.emit_opcode(Opcode::PushZero),
1 => self.emit_opcode(Opcode::PushOne),
x if x as i8 as i32 == x => {
self.emit_opcode(Opcode::PushInt8);
self.emit_u8(x as i8 as u8);
}
x if x as i16 as i32 == x => {
self.emit_opcode(Opcode::PushInt16);
self.emit_u16(x as i16 as u16);
}
x => self.emit(Opcode::PushInt32, &[x as _]),
}
}
#[inline]
fn emit_push_literal(&mut self, liternal: Literal) {
let index = self.get_or_insert_literal(liternal);
self.emit(Opcode::PushLiteral, &[index]);
}
#[inline]
fn emit_push_rational(&mut self, value: f64) {
if value.is_nan() {
return self.emit_opcode(Opcode::PushNaN);
}
if value.is_infinite() {
if value.is_sign_positive() {
return self.emit_opcode(Opcode::PushPositiveInfinity);
} else {
return self.emit_opcode(Opcode::PushNegativeInfinity);
}
}
// Check if the f64 value can fit in an i32.
#[allow(clippy::float_cmp)]
if value as i32 as f64 == value {
self.emit_push_integer(value as i32);
} else {
self.emit_opcode(Opcode::PushRational);
self.emit_u64(value.to_bits());
}
}
#[inline]
fn jump(&mut self) -> Label {
let index = self.next_opcode_location();
self.emit(Opcode::Jump, &[Self::DUMMY_ADDRESS]);
Label { index }
}
#[inline]
fn jump_if_false(&mut self) -> Label {
let index = self.next_opcode_location();
self.emit(Opcode::JumpIfFalse, &[Self::DUMMY_ADDRESS]);
Label { index }
}
#[inline]
fn jump_with_custom_opcode(&mut self, opcode: Opcode) -> Label {
let index = self.next_opcode_location();
self.emit(opcode, &[Self::DUMMY_ADDRESS]);
Label { index }
}
#[inline]
fn patch_jump_with_target(&mut self, label: Label, target: u32) {
let Label { index } = label;
let index = index as usize;
let bytes = u32_to_array(target);
self.code_block.code[index + 1] = bytes[0];
self.code_block.code[index + 2] = bytes[1];
self.code_block.code[index + 3] = bytes[2];
self.code_block.code[index + 4] = bytes[3];
}
#[inline]
fn patch_jump(&mut self, label: Label) {
let target = self.next_opcode_location();
self.patch_jump_with_target(label, target);
}
#[inline]
fn push_loop_control_info(&mut self, label: Option<Box<str>>, loop_start: u32) {
self.loops.push(LoopControlInfo {
label,
loop_start,
continues: Vec::new(),
breaks: Vec::new(),
})
}
#[inline]
fn pop_loop_control_info(&mut self) {
let loop_info = self.loops.pop().unwrap();
for label in loop_info.continues {
self.patch_jump_with_target(label, loop_info.loop_start);
}
for label in loop_info.breaks {
self.patch_jump(label);
}
}
#[inline]
fn compile_access<'a>(&mut self, node: &'a Node) -> Access<'a> {
match node {
Node::Identifier(name) => Access::Variable { name },
Node::GetConstField(node) => Access::ByName { node },
Node::GetField(node) => Access::ByValue { node },
Node::This => Access::This,
_ => unreachable!(),
}
}
#[inline]
fn access_get(&mut self, access: Access<'_>, use_expr: bool) {
match access {
Access::Variable { name } => {
let index = self.get_or_insert_name(name.as_ref());
self.emit(Opcode::GetName, &[index]);
}
Access::ByName { node } => {
let index = self.get_or_insert_name(node.field());
self.compile_expr(node.obj(), true);
self.emit(Opcode::GetPropertyByName, &[index]);
}
Access::ByValue { node } => {
self.compile_expr(node.field(), true);
self.compile_expr(node.obj(), true);
self.emit(Opcode::GetPropertyByValue, &[]);
}
Access::This => {
self.emit(Opcode::This, &[]);
}
}
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
#[inline]
fn access_set(&mut self, access: Access<'_>, expr: Option<&Node>, use_expr: bool) {
if let Some(expr) = expr {
self.compile_expr(expr, true);
}
if use_expr {
self.emit(Opcode::Dup, &[]);
}
match access {
Access::Variable { name } => {
let index = self.get_or_insert_name(name.as_ref());
self.emit(Opcode::SetName, &[index]);
}
Access::ByName { node } => {
self.compile_expr(node.obj(), true);
let index = self.get_or_insert_name(node.field());
self.emit(Opcode::SetPropertyByName, &[index]);
}
Access::ByValue { node } => {
self.compile_expr(node.field(), true);
self.compile_expr(node.obj(), true);
self.emit(Opcode::SetPropertyByValue, &[]);
}
Access::This => todo!("access_get 'this'"),
}
}
#[inline]
pub fn compile_statement_list(&mut self, list: &StatementList, use_expr: bool) {
for (i, node) in list.items().iter().enumerate() {
if i + 1 == list.items().len() {
self.compile_stmt(node, use_expr);
break;
}
self.compile_stmt(node, false);
}
}
#[inline]
pub fn compile_expr(&mut self, expr: &Node, use_expr: bool) {
match expr {
Node::Const(c) => {
match c {
Const::String(v) => self.emit_push_literal(Literal::String(v.as_ref().into())),
Const::Int(v) => self.emit_push_integer(*v),
Const::Num(v) => self.emit_push_rational(*v),
Const::BigInt(v) => self.emit_push_literal(Literal::BigInt(v.clone().into())),
Const::Bool(true) => self.emit(Opcode::PushTrue, &[]),
Const::Bool(false) => self.emit(Opcode::PushFalse, &[]),
Const::Null => self.emit(Opcode::PushNull, &[]),
Const::Undefined => self.emit(Opcode::PushUndefined, &[]),
}
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
Node::UnaryOp(unary) => {
let opcode = match unary.op() {
UnaryOp::IncrementPre => todo!(),
UnaryOp::DecrementPre => todo!(),
UnaryOp::IncrementPost => todo!(),
UnaryOp::DecrementPost => todo!(),
UnaryOp::Delete => todo!(),
UnaryOp::Minus => Some(Opcode::Neg),
UnaryOp::Plus => Some(Opcode::Pos),
UnaryOp::Not => Some(Opcode::LogicalNot),
UnaryOp::Tilde => Some(Opcode::BitNot),
UnaryOp::TypeOf => Some(Opcode::TypeOf),
UnaryOp::Void => Some(Opcode::Void),
};
if let Some(opcode) = opcode {
self.compile_expr(unary.target(), true);
self.emit(opcode, &[]);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
}
Node::BinOp(binary) => {
self.compile_expr(binary.lhs(), true);
match binary.op() {
BinOp::Num(op) => {
self.compile_expr(binary.rhs(), true);
match op {
NumOp::Add => self.emit_opcode(Opcode::Add),
NumOp::Sub => self.emit_opcode(Opcode::Sub),
NumOp::Div => self.emit_opcode(Opcode::Div),
NumOp::Mul => self.emit_opcode(Opcode::Mul),
NumOp::Exp => self.emit_opcode(Opcode::Pow),
NumOp::Mod => self.emit_opcode(Opcode::Mod),
}
}
BinOp::Bit(op) => {
self.compile_expr(binary.rhs(), true);
match op {
BitOp::And => self.emit_opcode(Opcode::BitAnd),
BitOp::Or => self.emit_opcode(Opcode::BitOr),
BitOp::Xor => self.emit_opcode(Opcode::BitXor),
BitOp::Shl => self.emit_opcode(Opcode::ShiftLeft),
BitOp::Shr => self.emit_opcode(Opcode::ShiftRight),
BitOp::UShr => self.emit_opcode(Opcode::UnsignedShiftRight),
}
}
BinOp::Comp(op) => {
self.compile_expr(binary.rhs(), true);
match op {
CompOp::Equal => self.emit_opcode(Opcode::Eq),
CompOp::NotEqual => self.emit_opcode(Opcode::NotEq),
CompOp::StrictEqual => self.emit_opcode(Opcode::StrictEq),
CompOp::StrictNotEqual => self.emit_opcode(Opcode::StrictNotEq),
CompOp::GreaterThan => self.emit_opcode(Opcode::GreaterThan),
CompOp::GreaterThanOrEqual => self.emit_opcode(Opcode::GreaterThanOrEq),
CompOp::LessThan => self.emit_opcode(Opcode::LessThan),
CompOp::LessThanOrEqual => self.emit_opcode(Opcode::LessThanOrEq),
CompOp::In => self.emit_opcode(Opcode::In),
CompOp::InstanceOf => self.emit_opcode(Opcode::InstanceOf),
}
}
BinOp::Log(op) => {
match op {
LogOp::And => {
let exit = self.jump_with_custom_opcode(Opcode::LogicalAnd);
self.compile_expr(binary.rhs(), true);
self.emit(Opcode::ToBoolean, &[]);
self.patch_jump(exit);
}
LogOp::Or => {
let exit = self.jump_with_custom_opcode(Opcode::LogicalOr);
self.compile_expr(binary.rhs(), true);
self.emit(Opcode::ToBoolean, &[]);
self.patch_jump(exit);
}
LogOp::Coalesce => {
let exit = self.jump_with_custom_opcode(Opcode::Coalesce);
self.compile_expr(binary.rhs(), true);
self.patch_jump(exit);
}
};
}
BinOp::Assign(op) => {
let opcode = match op {
AssignOp::Add => Some(Opcode::Add),
AssignOp::Sub => Some(Opcode::Sub),
AssignOp::Mul => Some(Opcode::Mul),
AssignOp::Div => Some(Opcode::Div),
AssignOp::Mod => Some(Opcode::Mod),
AssignOp::Exp => Some(Opcode::Pow),
AssignOp::And => Some(Opcode::BitAnd),
AssignOp::Or => Some(Opcode::BitOr),
AssignOp::Xor => Some(Opcode::BitXor),
AssignOp::Shl => Some(Opcode::ShiftLeft),
AssignOp::Shr => Some(Opcode::ShiftRight),
AssignOp::Ushr => Some(Opcode::UnsignedShiftRight),
AssignOp::BoolAnd => {
let exit = self.jump_with_custom_opcode(Opcode::LogicalAnd);
self.compile_expr(binary.rhs(), true);
self.emit(Opcode::ToBoolean, &[]);
self.patch_jump(exit);
None
}
AssignOp::BoolOr => {
let exit = self.jump_with_custom_opcode(Opcode::LogicalOr);
self.compile_expr(binary.rhs(), true);
self.emit(Opcode::ToBoolean, &[]);
self.patch_jump(exit);
None
}
AssignOp::Coalesce => {
let exit = self.jump_with_custom_opcode(Opcode::Coalesce);
self.compile_expr(binary.rhs(), true);
self.patch_jump(exit);
None
}
};
if let Some(opcode) = opcode {
self.compile_expr(binary.rhs(), true);
self.emit(opcode, &[]);
}
let access = self.compile_access(binary.lhs());
self.access_set(access, None, use_expr);
}
BinOp::Comma => {
self.emit(Opcode::Pop, &[]);
self.compile_expr(binary.rhs(), true);
}
}
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
Node::Object(object) => {
if object.properties().is_empty() {
self.emit(Opcode::PushEmptyObject, &[]);
} else {
todo!("object literal with properties");
}
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
Node::Identifier(name) => {
let access = Access::Variable { name };
self.access_get(access, use_expr);
}
Node::Assign(assign) => {
let access = self.compile_access(assign.lhs());
self.access_set(access, Some(assign.rhs()), use_expr);
}
Node::GetConstField(node) => {
let access = Access::ByName { node };
self.access_get(access, use_expr);
}
Node::GetField(node) => {
let access = Access::ByValue { node };
self.access_get(access, use_expr);
}
Node::ConditionalOp(op) => {
self.compile_expr(op.cond(), true);
let jelse = self.jump_if_false();
self.compile_expr(op.if_true(), true);
let exit = self.jump();
self.patch_jump(jelse);
self.compile_expr(op.if_false(), true);
self.patch_jump(exit);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
Node::ArrayDecl(array) => {
let mut count = 0;
for element in array.as_ref().iter().rev() {
if let Node::Spread(_) = element {
todo!("array with spread element");
} else {
self.compile_expr(element, true);
}
count += 1;
}
self.emit(Opcode::PushNewArray, &[count]);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
Node::This => {
self.access_get(Access::This, use_expr);
}
expr => todo!("TODO compile: {}", expr),
}
}
#[inline]
pub fn compile_stmt(&mut self, node: &Node, use_expr: bool) {
match node {
Node::VarDeclList(list) => {
for decl in list.as_ref() {
let index = self.get_or_insert_name(decl.name());
self.emit(Opcode::DefVar, &[index]);
if let Some(expr) = decl.init() {
self.compile_expr(expr, true);
self.emit(Opcode::InitLexical, &[index]);
};
}
}
Node::LetDeclList(list) => {
for decl in list.as_ref() {
let index = self.get_or_insert_name(decl.name());
self.emit(Opcode::DefLet, &[index]);
if let Some(expr) = decl.init() {
self.compile_expr(expr, true);
self.emit(Opcode::InitLexical, &[index]);
};
}
}
Node::ConstDeclList(list) => {
for decl in list.as_ref() {
let index = self.get_or_insert_name(decl.name());
self.emit(Opcode::DefConst, &[index]);
if let Some(expr) = decl.init() {
self.compile_expr(expr, true);
self.emit(Opcode::InitLexical, &[index]);
};
}
}
Node::If(node) => {
self.compile_expr(node.cond(), true);
let jelse = self.jump_if_false();
self.compile_stmt(node.body(), false);
match node.else_node() {
None => {
self.patch_jump(jelse);
}
Some(else_body) => {
let exit = self.jump();
self.patch_jump(jelse);
self.compile_stmt(else_body, false);
self.patch_jump(exit);
}
}
}
Node::WhileLoop(while_) => {
let loop_start = self.next_opcode_location();
self.push_loop_control_info(while_.label().map(Into::into), loop_start);
self.compile_expr(while_.cond(), true);
let exit = self.jump_if_false();
self.compile_stmt(while_.body(), false);
self.emit(Opcode::Jump, &[loop_start]);
self.patch_jump(exit);
self.pop_loop_control_info();
}
Node::DoWhileLoop(do_while) => {
let loop_start = self.next_opcode_location();
self.push_loop_control_info(do_while.label().map(Into::into), loop_start);
self.compile_stmt(do_while.body(), false);
self.compile_expr(do_while.cond(), true);
self.emit(Opcode::JumpIfTrue, &[loop_start]);
self.pop_loop_control_info();
}
Node::Continue(node) => {
let jump_label = self.jump();
if node.label().is_none() {
self.loops.last_mut().unwrap().continues.push(jump_label);
} else {
for loop_ in self.loops.iter_mut().rev() {
if loop_.label.as_deref() == node.label() {
loop_.continues.push(jump_label);
}
}
}
}
Node::Break(node) => {
let jump_label = self.jump();
if node.label().is_none() {
self.loops.last_mut().unwrap().breaks.push(jump_label);
} else {
for loop_ in self.loops.iter_mut().rev() {
if loop_.label.as_deref() == node.label() {
loop_.breaks.push(jump_label);
}
}
}
}
Node::Block(block) => {
for node in block.items() {
self.compile_stmt(node, false);
}
}
Node::Throw(throw) => {
self.compile_expr(throw.expr(), true);
self.emit(Opcode::Throw, &[]);
}
Node::Empty => {}
expr => self.compile_expr(expr, use_expr),
}
}
#[inline]
pub fn finish(self) -> CodeBlock {
self.code_block
}
}

16
boa/src/context.rs

@ -30,10 +30,7 @@ use crate::{
use crate::builtins::console::Console;
#[cfg(feature = "vm")]
use crate::vm::{
compilation::{CodeGen, Compiler},
VM,
};
use crate::vm::Vm;
/// Store a builtin constructor (such as `Object`) and its corresponding prototype.
#[derive(Debug, Clone)]
@ -685,13 +682,12 @@ impl Context {
Err(e) => return self.throw_syntax_error(e),
};
let mut compiler = Compiler::default();
statement_list.compile(&mut compiler);
let mut vm = VM::new(compiler, self);
// Generate Bytecode and place it into instruction_stack
// Interpret the Bytecode
let mut compiler = crate::bytecompiler::ByteCompiler::default();
compiler.compile_statement_list(&statement_list, true);
let code_block = compiler.finish();
let mut vm = Vm::new(code_block, self);
let result = vm.run();
// The main_timer needs to be dropped before the BoaProfiler is.
drop(main_timer);
BoaProfiler::global().drop();

2
boa/src/exec/mod.rs

@ -14,8 +14,6 @@ pub trait Executable {
pub(crate) enum InterpreterState {
Executing,
Return,
#[cfg(feature = "vm")]
Error,
Break(Option<Box<str>>),
Continue(Option<Box<str>>),
}

6
boa/src/lib.rs

@ -46,6 +46,7 @@ This is an experimental Javascript lexer, parser and compiler written in Rust. C
#[allow(clippy::unnecessary_wraps)]
pub mod builtins;
pub mod class;
pub mod context;
pub mod environment;
pub mod exec;
pub mod gc;
@ -58,11 +59,12 @@ pub mod symbol;
#[allow(clippy::upper_case_acronyms)]
pub mod syntax;
pub mod value;
#[cfg(feature = "vm")]
pub mod bytecompiler;
#[cfg(feature = "vm")]
pub mod vm;
pub mod context;
use std::result::Result as StdResult;
pub(crate) use crate::{exec::Executable, profiler::BoaProfiler};

10
boa/src/object/internal_methods.rs

@ -96,7 +96,7 @@ impl GcObject {
pub fn set(
&mut self,
key: PropertyKey,
val: Value,
value: Value,
receiver: Value,
context: &mut Context,
) -> Result<bool> {
@ -106,7 +106,7 @@ impl GcObject {
let own_desc = if let Some(desc) = self.get_own_property(&key) {
desc
} else if let Some(ref mut parent) = self.get_prototype_of().as_object() {
return parent.set(key, val, receiver, context);
return parent.set(key, value, receiver, context);
} else {
DataDescriptor::new(Value::undefined(), Attribute::all()).into()
};
@ -126,7 +126,7 @@ impl GcObject {
}
receiver.define_own_property(
key,
DataDescriptor::new(val, existing_data_desc.attributes())
DataDescriptor::new(value, existing_data_desc.attributes())
.into(),
context,
)
@ -135,7 +135,7 @@ impl GcObject {
} else {
receiver.define_own_property(
key,
DataDescriptor::new(val, Attribute::all()).into(),
DataDescriptor::new(value, Attribute::all()).into(),
context,
)
}
@ -144,7 +144,7 @@ impl GcObject {
}
}
PropertyDescriptor::Accessor(AccessorDescriptor { set: Some(set), .. }) => {
set.call(&receiver, &[val], context)?;
set.call(&receiver, &[value], context)?;
Ok(true)
}
_ => Ok(false),

2
boa/src/syntax/ast/node/block/mod.rs

@ -90,8 +90,6 @@ impl Executable for Block {
InterpreterState::Executing => {
// Continue execution
}
#[cfg(feature = "vm")]
InterpreterState::Error => {}
}
}

12
boa/src/syntax/ast/node/identifier/mod.rs

@ -10,10 +10,6 @@ use std::fmt;
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "vm")]
use crate::vm::{compilation::CodeGen, Compiler, Instruction};
/// An `identifier` is a sequence of characters in the code that identifies a variable,
/// function, or property.
///
@ -44,14 +40,6 @@ impl Executable for Identifier {
}
}
#[cfg(feature = "vm")]
impl CodeGen for Identifier {
fn compile(&self, compiler: &mut Compiler) {
let _timer = BoaProfiler::global().start_event("Identifier", "codeGen");
compiler.add_instruction(Instruction::GetName(String::from(self.ident.as_ref())));
}
}
impl fmt::Display for Identifier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.ident, f)

2
boa/src/syntax/ast/node/iteration/do_while_loop/mod.rs

@ -92,8 +92,6 @@ impl Executable for DoWhileLoop {
InterpreterState::Executing => {
// Continue execution.
}
#[cfg(feature = "vm")]
InterpreterState::Error => {}
}
if !self.cond().run(context)?.to_boolean() {
break;

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

@ -207,8 +207,6 @@ impl Executable for ForInLoop {
InterpreterState::Executing => {
// Continue execution.
}
#[cfg(feature = "vm")]
InterpreterState::Error => {}
}
let _ = context.pop_environment();
}

2
boa/src/syntax/ast/node/iteration/for_loop/mod.rs

@ -133,8 +133,6 @@ impl Executable for ForLoop {
InterpreterState::Executing => {
// Continue execution.
}
#[cfg(feature = "vm")]
InterpreterState::Error => {}
}
if let Some(final_expr) = self.final_expr() {

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

@ -198,8 +198,6 @@ impl Executable for ForOfLoop {
InterpreterState::Executing => {
// Continue execution.
}
#[cfg(feature = "vm")]
InterpreterState::Error => {}
}
let _ = context.pop_environment();
}

14
boa/src/syntax/ast/node/iteration/while_loop/mod.rs

@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub struct WhileLoop {
cond: Box<Node>,
expr: Box<Node>,
body: Box<Node>,
label: Option<Box<str>>,
}
@ -33,8 +33,8 @@ impl WhileLoop {
&self.cond
}
pub fn expr(&self) -> &Node {
&self.expr
pub fn body(&self) -> &Node {
&self.body
}
pub fn label(&self) -> Option<&str> {
@ -53,7 +53,7 @@ impl WhileLoop {
{
Self {
cond: Box::new(condition.into()),
expr: Box::new(body.into()),
body: Box::new(body.into()),
label: None,
}
}
@ -67,7 +67,7 @@ impl WhileLoop {
write!(f, "{}: ", label)?;
}
write!(f, "while ({}) ", self.cond())?;
self.expr().display(f, indentation)
self.body().display(f, indentation)
}
}
@ -75,7 +75,7 @@ impl Executable for WhileLoop {
fn run(&self, context: &mut Context) -> Result<Value> {
let mut result = Value::undefined();
while self.cond().run(context)?.to_boolean() {
result = self.expr().run(context)?;
result = self.body().run(context)?;
match context.executor().get_current_state() {
InterpreterState::Break(label) => {
handle_state_with_labels!(self, label, context, break);
@ -90,8 +90,6 @@ impl Executable for WhileLoop {
InterpreterState::Executing => {
// Continue execution.
}
#[cfg(feature = "vm")]
InterpreterState::Error => {}
}
}
Ok(result)

1
boa/src/syntax/ast/node/mod.rs

@ -60,6 +60,7 @@ use std::{
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
// TODO: This should be split into Expression and Statement.
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Trace, Finalize, PartialEq)]
pub enum Node {

17
boa/src/syntax/ast/node/object/mod.rs

@ -12,9 +12,6 @@ use std::fmt;
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "vm")]
use crate::vm::{compilation::CodeGen, Compiler, Instruction};
#[cfg(test)]
mod tests;
@ -89,20 +86,6 @@ impl Object {
}
}
#[cfg(feature = "vm")]
impl CodeGen for Object {
fn compile(&self, compiler: &mut Compiler) {
let _timer = BoaProfiler::global().start_event("object", "codeGen");
// Is it a new empty object?
if self.properties.len() == 0 {
compiler.add_instruction(Instruction::NewObject);
return;
}
unimplemented!()
}
}
impl Executable for Object {
fn run(&self, context: &mut Context) -> Result<Value> {
let _timer = BoaProfiler::global().start_event("object", "exec");

56
boa/src/syntax/ast/node/operator/bin_op/mod.rs

@ -13,12 +13,6 @@ use std::fmt;
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "vm")]
use crate::{
profiler::BoaProfiler,
vm::{compilation::CodeGen, Compiler, Instruction},
};
/// Binary operators requires two operands, one before the operator and one after the operator.
///
/// More information:
@ -229,56 +223,6 @@ impl Executable for BinOp {
}
}
#[cfg(feature = "vm")]
impl CodeGen for BinOp {
fn compile(&self, compiler: &mut Compiler) {
let _timer = BoaProfiler::global().start_event("binOp", "codeGen");
match self.op() {
op::BinOp::Num(op) => {
self.lhs().compile(compiler);
self.rhs().compile(compiler);
match op {
NumOp::Add => compiler.add_instruction(Instruction::Add),
NumOp::Sub => compiler.add_instruction(Instruction::Sub),
NumOp::Mul => compiler.add_instruction(Instruction::Mul),
NumOp::Div => compiler.add_instruction(Instruction::Div),
NumOp::Exp => compiler.add_instruction(Instruction::Pow),
NumOp::Mod => compiler.add_instruction(Instruction::Mod),
}
}
op::BinOp::Bit(op) => {
self.lhs().compile(compiler);
self.rhs().compile(compiler);
match op {
BitOp::And => compiler.add_instruction(Instruction::BitAnd),
BitOp::Or => compiler.add_instruction(Instruction::BitOr),
BitOp::Xor => compiler.add_instruction(Instruction::BitXor),
BitOp::Shl => compiler.add_instruction(Instruction::Shl),
BitOp::Shr => compiler.add_instruction(Instruction::Shr),
BitOp::UShr => compiler.add_instruction(Instruction::UShr),
}
}
op::BinOp::Comp(op) => {
self.lhs().compile(compiler);
self.rhs().compile(compiler);
match op {
CompOp::Equal => compiler.add_instruction(Instruction::Eq),
CompOp::NotEqual => compiler.add_instruction(Instruction::NotEq),
CompOp::StrictEqual => compiler.add_instruction(Instruction::StrictEq),
CompOp::StrictNotEqual => compiler.add_instruction(Instruction::StrictNotEq),
CompOp::GreaterThan => compiler.add_instruction(Instruction::Gt),
CompOp::GreaterThanOrEqual => compiler.add_instruction(Instruction::Ge),
CompOp::LessThan => compiler.add_instruction(Instruction::Lt),
CompOp::LessThanOrEqual => compiler.add_instruction(Instruction::Le),
CompOp::In => compiler.add_instruction(Instruction::In),
CompOp::InstanceOf => compiler.add_instruction(Instruction::InstanceOf),
}
}
_ => unimplemented!(),
}
}
}
impl fmt::Display for BinOp {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {}", self.lhs, self.op, self.rhs)

27
boa/src/syntax/ast/node/operator/unary_op/mod.rs

@ -9,12 +9,6 @@ use std::fmt;
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "vm")]
use crate::{
profiler::BoaProfiler,
vm::{compilation::CodeGen, Compiler, Instruction},
};
/// A unary operation is an operation with only one operand.
///
/// More information:
@ -137,24 +131,3 @@ impl From<UnaryOp> for Node {
Self::UnaryOp(op)
}
}
#[cfg(feature = "vm")]
impl CodeGen for UnaryOp {
fn compile(&self, compiler: &mut Compiler) {
let _timer = BoaProfiler::global().start_event("UnaryOp", "codeGen");
self.target().compile(compiler);
match self.op {
op::UnaryOp::Void => compiler.add_instruction(Instruction::Void),
op::UnaryOp::Plus => compiler.add_instruction(Instruction::Pos),
op::UnaryOp::Minus => compiler.add_instruction(Instruction::Neg),
op::UnaryOp::TypeOf => compiler.add_instruction(Instruction::TypeOf),
op::UnaryOp::Not => compiler.add_instruction(Instruction::Not),
op::UnaryOp::Tilde => compiler.add_instruction(Instruction::BitNot),
op::UnaryOp::IncrementPost => {}
op::UnaryOp::IncrementPre => {}
op::UnaryOp::DecrementPost => {}
op::UnaryOp::DecrementPre => {}
op::UnaryOp::Delete => {}
}
}
}

16
boa/src/syntax/ast/node/statement_list/mod.rs

@ -11,9 +11,6 @@ use std::{collections::HashSet, fmt, ops::Deref, rc::Rc};
#[cfg(feature = "deser")]
use serde::{Deserialize, Serialize};
#[cfg(feature = "vm")]
use crate::vm::{compilation::CodeGen, Compiler};
/// List of statements.
///
/// Similar to `Node::Block` but without the braces.
@ -122,8 +119,6 @@ impl Executable for StatementList {
InterpreterState::Executing => {
// Continue execution
}
#[cfg(feature = "vm")]
InterpreterState::Error => {}
}
if i + 1 == self.items().len() {
obj = val;
@ -134,17 +129,6 @@ impl Executable for StatementList {
}
}
#[cfg(feature = "vm")]
impl CodeGen for StatementList {
fn compile(&self, compiler: &mut Compiler) {
let _timer = BoaProfiler::global().start_event("StatementList - Code Gen", "codeGen");
for item in self.items().iter() {
item.compile(compiler);
}
}
}
impl<T> From<T> for StatementList
where
T: Into<Box<[Node]>>,

2
boa/src/syntax/ast/node/switch/mod.rs

@ -162,8 +162,6 @@ impl Executable for Switch {
// Continuing execution / falling through to next case statement(s).
fall_through = true;
}
#[cfg(feature = "vm")]
InterpreterState::Error => {}
}
}
}

12
boa/src/value/mod.rs

@ -93,6 +93,18 @@ impl Value {
Self::number(f64::NAN)
}
/// Creates a new number with `Infinity` value.
#[inline]
pub fn positive_inifnity() -> Self {
Self::number(f64::INFINITY)
}
/// Creates a new number with `-Infinity` value.
#[inline]
pub fn negative_inifnity() -> Self {
Self::number(f64::NEG_INFINITY)
}
/// Creates a new string value.
#[inline]
pub fn string<S>(value: S) -> Self

215
boa/src/vm/code_block.rs

@ -0,0 +1,215 @@
use crate::{value::RcString, vm::Opcode, Value};
use std::{convert::TryInto, fmt::Write, mem::size_of};
/// This represents wether an object can be read from [`CodeBlock`] code.
pub unsafe trait Readable {}
unsafe impl Readable for u8 {}
unsafe impl Readable for i8 {}
unsafe impl Readable for u16 {}
unsafe impl Readable for i16 {}
unsafe impl Readable for u32 {}
unsafe impl Readable for i32 {}
unsafe impl Readable for u64 {}
unsafe impl Readable for i64 {}
unsafe impl Readable for f32 {}
unsafe impl Readable for f64 {}
#[derive(Debug)]
pub struct CodeBlock {
/// Bytecode
pub(crate) code: Vec<u8>,
/// Literals
pub(crate) literals: Vec<Value>,
/// Variables names
pub(crate) names: Vec<RcString>,
}
impl Default for CodeBlock {
fn default() -> Self {
Self::new()
}
}
impl CodeBlock {
pub fn new() -> Self {
Self {
code: Vec::new(),
literals: Vec::new(),
names: Vec::new(),
}
}
/// Read type T from code.
///
/// # Safety
///
/// Does not check if read happens out-of-bounds.
pub unsafe fn read_unchecked<T: Readable>(&self, offset: usize) -> T {
// This has to be an unaligned read because we can't gurantee that
// the types are aligned.
self.code.as_ptr().add(offset).cast::<T>().read_unaligned()
}
/// Read type T from code.
pub fn read<T: Readable>(&self, offset: usize) -> T {
assert!(offset + size_of::<T>() - 1 < self.code.len());
// Safety: We checked that it is not an out-of-bounds read,
// so this is safe.
unsafe { self.read_unchecked(offset) }
}
pub(crate) fn instruction_operands(&self, pc: &mut usize) -> String {
let opcode: Opcode = self.code[*pc].try_into().unwrap();
*pc += size_of::<Opcode>();
match opcode {
Opcode::PushInt8 => {
let result = self.read::<i8>(*pc).to_string();
*pc += size_of::<i8>();
result
}
Opcode::PushInt16 => {
let result = self.read::<i16>(*pc).to_string();
*pc += size_of::<i16>();
result
}
Opcode::PushInt32 => {
let result = self.read::<i32>(*pc).to_string();
*pc += size_of::<i32>();
result
}
Opcode::PushRational => {
let operand = self.read::<f64>(*pc);
*pc += size_of::<f64>();
ryu_js::Buffer::new().format(operand).to_string()
}
Opcode::PushLiteral
| Opcode::PushNewArray
| Opcode::Jump
| Opcode::JumpIfFalse
| Opcode::JumpIfTrue
| Opcode::LogicalAnd
| Opcode::LogicalOr
| Opcode::Coalesce => {
let result = self.read::<u32>(*pc).to_string();
*pc += size_of::<u32>();
result
}
Opcode::DefVar
| Opcode::DefLet
| Opcode::DefConst
| Opcode::InitLexical
| Opcode::GetName
| Opcode::SetName
| Opcode::GetPropertyByName
| Opcode::SetPropertyByName => {
let operand = self.read::<u32>(*pc);
*pc += size_of::<u32>();
format!("{:04}: '{}'", operand, self.names[operand as usize])
}
Opcode::Pop
| Opcode::Dup
| Opcode::Swap
| Opcode::PushZero
| Opcode::PushOne
| Opcode::PushNaN
| Opcode::PushPositiveInfinity
| Opcode::PushNegativeInfinity
| Opcode::PushNull
| Opcode::PushTrue
| Opcode::PushFalse
| Opcode::PushUndefined
| Opcode::PushEmptyObject
| Opcode::Add
| Opcode::Sub
| Opcode::Div
| Opcode::Mul
| Opcode::Mod
| Opcode::Pow
| Opcode::ShiftRight
| Opcode::ShiftLeft
| Opcode::UnsignedShiftRight
| Opcode::BitOr
| Opcode::BitAnd
| Opcode::BitXor
| Opcode::BitNot
| Opcode::In
| Opcode::Eq
| Opcode::StrictEq
| Opcode::NotEq
| Opcode::StrictNotEq
| Opcode::GreaterThan
| Opcode::GreaterThanOrEq
| Opcode::LessThan
| Opcode::LessThanOrEq
| Opcode::InstanceOf
| Opcode::TypeOf
| Opcode::Void
| Opcode::LogicalNot
| Opcode::Pos
| Opcode::Neg
| Opcode::GetPropertyByValue
| Opcode::SetPropertyByValue
| Opcode::ToBoolean
| Opcode::Throw
| Opcode::This
| Opcode::Nop => String::new(),
}
}
}
impl std::fmt::Display for CodeBlock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Code: \n")?;
writeln!(f, " Location Count Opcode Operands")?;
let mut pc = 0;
let mut count = 0;
while pc < self.code.len() {
let opcode: Opcode = self.code[pc].try_into().unwrap();
write!(
f,
" {:06} {:04} {:<20}",
pc,
count,
opcode.as_str()
)?;
writeln!(f, "{}", self.instruction_operands(&mut pc))?;
count += 1;
}
f.write_char('\n')?;
f.write_str("Literals:\n")?;
if !self.literals.is_empty() {
for (i, value) in self.literals.iter().enumerate() {
writeln!(
f,
" {:04}: <{}> {}",
i,
value.get_type().as_str(),
value.display()
)?;
}
} else {
writeln!(f, " <empty>")?;
}
f.write_char('\n')?;
f.write_str("Names:\n")?;
if !self.names.is_empty() {
for (i, value) in self.names.iter().enumerate() {
writeln!(f, " {:04}: {}", i, value)?;
}
} else {
writeln!(f, " <empty>")?;
}
Ok(())
}
}

112
boa/src/vm/compilation.rs

@ -1,112 +0,0 @@
use super::*;
use crate::{syntax::ast::Const, syntax::ast::Node, value::RcBigInt, value::RcString};
#[derive(Debug, Default)]
/// The compiler struct holds all the instructions.
pub struct Compiler {
/// Vector of instructions
pub(super) instructions: Vec<Instruction>,
/// The pool stores constant data that can be indexed with the opcodes and pushed on the stack
pub(super) pool: Vec<Value>,
}
impl Compiler {
/// Add a new instruction.
pub fn add_instruction(&mut self, instr: Instruction) {
self.instructions.push(instr);
}
/// This specilaized method puts the string value in the pool then adds an instructions which points to the correct index
pub fn add_string_instruction<S>(&mut self, string: S)
where
S: Into<RcString>,
{
let index = self.pool.len();
self.add_instruction(Instruction::String(index));
self.pool.push(string.into().into());
}
/// This specilaized method puts the BigInt value in the pool then adds an instructions which points to the correct index
pub fn add_bigint_instruction<B>(&mut self, bigint: B)
where
B: Into<RcBigInt>,
{
let index = self.pool.len();
self.add_instruction(Instruction::BigInt(index));
self.pool.push(bigint.into().into());
}
}
pub(crate) trait CodeGen {
fn compile(&self, compiler: &mut Compiler);
}
impl CodeGen for Node {
fn compile(&self, compiler: &mut Compiler) {
let _timer = BoaProfiler::global().start_event(&format!("Node ({})", &self), "codeGen");
match *self {
Node::Const(Const::Undefined) => compiler.add_instruction(Instruction::Undefined),
Node::Const(Const::Null) => compiler.add_instruction(Instruction::Null),
Node::Const(Const::Bool(true)) => compiler.add_instruction(Instruction::True),
Node::Const(Const::Bool(false)) => compiler.add_instruction(Instruction::False),
Node::Const(Const::Num(num)) => compiler.add_instruction(Instruction::Rational(num)),
Node::Const(Const::Int(num)) => match num {
0 => compiler.add_instruction(Instruction::Zero),
1 => compiler.add_instruction(Instruction::One),
_ => compiler.add_instruction(Instruction::Int32(num)),
},
Node::Const(Const::String(ref string)) => {
compiler.add_string_instruction(string.clone())
}
Node::Const(Const::BigInt(ref bigint)) => {
compiler.add_bigint_instruction(bigint.clone())
}
Node::BinOp(ref op) => op.compile(compiler),
Node::UnaryOp(ref op) => op.compile(compiler),
Node::VarDeclList(ref list) => {
for var_decl in list.as_ref() {
let name = var_decl.name();
let index = compiler.pool.len();
compiler.add_instruction(Instruction::DefVar(index));
compiler.pool.push(name.into());
if let Some(v) = var_decl.init() {
v.compile(compiler);
compiler.add_instruction(Instruction::InitLexical(index))
};
}
}
Node::LetDeclList(ref list) => {
for let_decl in list.as_ref() {
let name = let_decl.name();
let index = compiler.pool.len();
compiler.add_instruction(Instruction::DefLet(index));
compiler.pool.push(name.into());
// If name has a value we can init here too
if let Some(v) = let_decl.init() {
v.compile(compiler);
compiler.add_instruction(Instruction::InitLexical(index))
};
}
}
Node::ConstDeclList(ref list) => {
for const_decl in list.as_ref() {
let name = const_decl.name();
let index = compiler.pool.len();
compiler.add_instruction(Instruction::DefConst(index));
compiler.pool.push(name.into());
if let Some(v) = const_decl.init() {
v.compile(compiler);
compiler.add_instruction(Instruction::InitLexical(index))
};
}
}
Node::Identifier(ref name) => name.compile(compiler),
Node::Object(ref obj) => obj.compile(compiler),
_ => unimplemented!(),
}
}
}

127
boa/src/vm/instructions.rs

@ -1,127 +0,0 @@
#[derive(Debug)]
pub enum Instruction {
Undefined,
Null,
True,
False,
Zero,
One,
String(usize),
BigInt(usize),
/// Loads an i32 onto the stack
Int32(i32),
/// Loads an f64 onto the stack
Rational(f64),
/// Adds the values from destination and source and stores the result in destination
Add,
/// subtracts the values from destination and source and stores the result in destination
Sub,
/// Multiplies the values from destination and source and stores the result in destination
Mul,
/// Divides the values from destination and source and stores the result in destination
Div,
Pow,
Mod,
BitAnd,
BitOr,
BitXor,
Shl,
Shr,
UShr,
Eq,
NotEq,
StrictEq,
StrictNotEq,
Gt,
Ge,
Lt,
Le,
In,
InstanceOf,
Void,
TypeOf,
Pos,
Neg,
BitNot,
Not,
/// The usize is the index of the variable name in the pool
DefVar(usize),
/// The usize is the index of the variable name in the pool
DefLet(usize),
/// The usize is the index of the variable name in the pool
DefConst(usize),
/// The usize is the index of the value to initiate the variable with in the pool
InitLexical(usize),
// Binding values
/// Find a binding on the environment chain and push its value.
GetName(String),
// Objects
// Create and push a new object onto the stack
NewObject,
}
impl std::fmt::Display for Instruction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
Self::Undefined => write!(f, "Undefined"),
Self::Null => write!(f, "Null"),
Self::True => write!(f, "True"),
Self::False => write!(f, "False"),
Self::Zero => write!(f, "Zero"),
Self::One => write!(f, "One"),
Self::String(usize) => write!(f, "String({})", usize),
Self::BigInt(usize) => write!(f, "BigInt({})", usize),
Self::Int32(i32) => write!(f, "Int32({})", i32),
Self::Rational(f64) => write!(f, "Rational({})", f64),
Self::Add => write!(f, "Add"),
Self::Sub => write!(f, "Sub"),
Self::Mul => write!(f, "Mul"),
Self::Div => write!(f, "Div"),
Self::Pow => write!(f, "Pow"),
Self::Mod => write!(f, "Mod"),
Self::BitAnd => write!(f, "BitAnd"),
Self::BitOr => write!(f, "BitOr"),
Self::BitXor => write!(f, "BitXor"),
Self::GetName(ref name) => write!(f, "GetName({})", name),
Self::Shl => write!(f, "Shl"),
Self::Shr => write!(f, "Shr"),
Self::UShr => write!(f, "UShr"),
Self::Eq => write!(f, "Eq"),
Self::NotEq => write!(f, "NotEq"),
Self::StrictEq => write!(f, "StrictEq"),
Self::StrictNotEq => write!(f, "StrictNotEq"),
Self::Gt => write!(f, "Gt"),
Self::Ge => write!(f, "Ge"),
Self::Lt => write!(f, "Lt"),
Self::Le => write!(f, "Le"),
Self::In => write!(f, "In"),
Self::InstanceOf => write!(f, "InstanceOf"),
Self::Void => write!(f, "Void"),
Self::TypeOf => write!(f, "TypeOf"),
Self::Pos => write!(f, "Pos"),
Self::Neg => write!(f, "Neg"),
Self::BitNot => write!(f, "BitNot"),
Self::Not => write!(f, "Not"),
Self::DefVar(name) => write!(f, "DefVar({})", name),
Self::DefLet(name) => write!(f, "DefLet({})", name),
Self::DefConst(name) => write!(f, "DefConst({})", name),
Self::InitLexical(value) => write!(f, "InitLexical({})", value),
Self::NewObject => write!(f, "NewObject"),
}
}
}

727
boa/src/vm/mod.rs

@ -1,66 +1,56 @@
//! The Virtual Machine (VM) handles generating instructions, then executing them.
//! This module will provide an instruction set for the AST to use, various traits, plus an interpreter to execute those instructions
//! This module will provide an instruction set for the AST to use, various traits,
//! plus an interpreter to execute those instructions
use crate::{
environment::lexical_environment::VariableScope, exec::InterpreterState, BoaProfiler, Context,
Result, Value,
builtins::Array, environment::lexical_environment::VariableScope, symbol::WellKnownSymbols,
BoaProfiler, Context, Result, Value,
};
pub(crate) mod compilation;
pub(crate) mod instructions;
mod code_block;
mod opcode;
pub use compilation::Compiler;
pub use instructions::Instruction;
use std::time::{Duration, Instant};
pub use code_block::CodeBlock;
pub use opcode::Opcode;
use std::{convert::TryInto, mem::size_of, time::Instant};
use self::code_block::Readable;
/// Virtual Machine.
#[derive(Debug)]
pub struct VM<'a> {
ctx: &'a mut Context,
idx: usize,
instructions: Vec<Instruction>,
pool: Vec<Value>,
pub struct Vm<'a> {
context: &'a mut Context,
pc: usize,
code: CodeBlock,
stack: Vec<Value>,
stack_pointer: usize,
profile: Profiler,
is_trace: bool,
}
/// This profiler is used to output trace information when `--trace` is provided by the CLI or trace is set to `true` on the [`VM`] object
#[derive(Debug)]
struct Profiler {
instant: Instant,
prev_time: Duration,
trace_string: String,
start_flag: bool,
}
#[cfg(test)]
mod tests;
impl<'a> VM<'a> {
pub fn new(compiler: Compiler, ctx: &'a mut Context) -> Self {
let trace = ctx.trace;
impl<'a> Vm<'a> {
pub fn new(code: CodeBlock, context: &'a mut Context) -> Self {
let trace = context.trace;
Self {
ctx,
idx: 0,
instructions: compiler.instructions,
pool: compiler.pool,
stack: vec![],
context,
pc: 0,
code,
stack: Vec::with_capacity(128),
stack_pointer: 0,
is_trace: trace,
profile: Profiler {
instant: Instant::now(),
prev_time: Duration::from_secs(0),
trace_string: String::new(), // Won't allocate if we don't use trace
start_flag: false,
},
}
}
/// Push a value on the stack.
#[inline]
pub fn push(&mut self, value: Value) {
self.stack.push(value);
pub fn push<T>(&mut self, value: T)
where
T: Into<Value>,
{
self.stack.push(value.into());
}
/// Pop a value off the stack.
@ -73,290 +63,449 @@ impl<'a> VM<'a> {
self.stack.pop().unwrap()
}
pub fn run(&mut self) -> Result<Value> {
let _timer = BoaProfiler::global().start_event("runVM", "vm");
self.idx = 0;
fn read<T: Readable>(&mut self) -> T {
let value = self.code.read::<T>(self.pc);
self.pc += size_of::<T>();
value
}
while self.idx < self.instructions.len() {
if self.is_trace {
self.trace_print(false);
};
let _timer =
BoaProfiler::global().start_event(&self.instructions[self.idx].to_string(), "vm");
macro_rules! bin_op {
($op:ident) => {{
let r = self.pop();
let l = self.pop();
let val = l.$op(&r, self.ctx)?;
Some(val.into())
}};
}
let result = match self.instructions[self.idx] {
Instruction::Undefined => Some(Value::undefined()),
Instruction::Null => Some(Value::null()),
Instruction::True => Some(Value::boolean(true)),
Instruction::False => Some(Value::boolean(false)),
Instruction::Zero => Some(Value::integer(0)),
Instruction::One => Some(Value::integer(1)),
Instruction::Int32(i) => Some(Value::integer(i)),
Instruction::Rational(r) => Some(Value::rational(r)),
Instruction::String(index) => Some(self.pool[index].clone()),
Instruction::BigInt(index) => Some(self.pool[index].clone()),
Instruction::Add => {
bin_op!(add)
}
Instruction::Sub => {
bin_op!(sub)
}
Instruction::Mul => {
bin_op!(mul)
}
Instruction::Div => {
bin_op!(div)
}
Instruction::Pow => {
bin_op!(pow)
}
Instruction::Mod => {
bin_op!(rem)
}
Instruction::BitAnd => {
bin_op!(bitand)
}
Instruction::BitOr => {
bin_op!(bitor)
}
Instruction::BitXor => {
bin_op!(bitxor)
}
Instruction::Shl => {
bin_op!(shl)
}
Instruction::Shr => {
bin_op!(shr)
}
Instruction::UShr => {
bin_op!(ushr)
}
Instruction::Eq => {
let r = self.pop();
let l = self.pop();
Some((l.equals(&r, self.ctx)?).into())
}
Instruction::NotEq => {
let r = self.pop();
let l = self.pop();
Some((!l.equals(&r, self.ctx)?).into())
}
Instruction::StrictEq => {
let r = self.pop();
let l = self.pop();
Some((l.strict_equals(&r)).into())
}
Instruction::StrictNotEq => {
let r = self.pop();
let l = self.pop();
Some((!l.strict_equals(&r)).into())
}
Instruction::Gt => {
bin_op!(gt)
}
Instruction::Ge => {
bin_op!(ge)
}
Instruction::Lt => {
bin_op!(lt)
}
Instruction::Le => {
bin_op!(le)
}
Instruction::In => {
let r = self.pop();
let l = self.pop();
if !r.is_object() {
return self.ctx.throw_type_error(format!(
"right-hand side of 'in' should be an object, got {}",
r.get_type().as_str()
));
}
let key = l.to_property_key(self.ctx)?;
Some(self.ctx.has_property(&r, &key).into())
}
Instruction::InstanceOf => {
let r = self.pop();
let _l = self.pop();
if !r.is_object() {
return self.ctx.throw_type_error(format!(
"right-hand side of 'instanceof' should be an object, got {}",
r.get_type().as_str()
));
fn execute_instruction(&mut self) -> Result<()> {
let _timer = BoaProfiler::global().start_event("execute_instruction", "vm");
macro_rules! bin_op {
($op:ident) => {{
let rhs = self.pop();
let lhs = self.pop();
let value = lhs.$op(&rhs, self.context)?;
self.push(value)
}};
}
let opcode = self.code.code[self.pc].try_into().unwrap();
self.pc += 1;
match opcode {
Opcode::Nop => {}
Opcode::Pop => {
let _ = self.pop();
}
Opcode::Dup => {
let value = self.pop();
self.push(value.clone());
self.push(value);
}
Opcode::Swap => {
let first = self.pop();
let second = self.pop();
self.push(first);
self.push(second);
}
Opcode::PushUndefined => self.push(Value::undefined()),
Opcode::PushNull => self.push(Value::null()),
Opcode::PushTrue => self.push(true),
Opcode::PushFalse => self.push(false),
Opcode::PushZero => self.push(0),
Opcode::PushOne => self.push(1),
Opcode::PushInt8 => {
let value = self.read::<i8>();
self.push(value as i32);
}
Opcode::PushInt16 => {
let value = self.read::<i16>();
self.push(value as i32);
}
Opcode::PushInt32 => {
let value = self.read::<i32>();
self.push(value);
}
Opcode::PushRational => {
let value = self.read::<f64>();
self.push(value);
}
Opcode::PushNaN => self.push(Value::nan()),
Opcode::PushPositiveInfinity => self.push(Value::positive_inifnity()),
Opcode::PushNegativeInfinity => self.push(Value::negative_inifnity()),
Opcode::PushLiteral => {
let index = self.read::<u32>() as usize;
let value = self.code.literals[index].clone();
self.push(value)
}
Opcode::PushEmptyObject => self.push(Value::new_object(self.context)),
Opcode::PushNewArray => {
let count = self.read::<u32>();
let mut elements = Vec::with_capacity(count as usize);
for _ in 0..count {
elements.push(self.pop());
}
let array = Array::new_array(self.context);
Array::add_to_array_object(&array, &elements, self.context)?;
self.push(array);
}
Opcode::Add => bin_op!(add),
Opcode::Sub => bin_op!(sub),
Opcode::Mul => bin_op!(mul),
Opcode::Div => bin_op!(div),
Opcode::Pow => bin_op!(pow),
Opcode::Mod => bin_op!(rem),
Opcode::BitAnd => bin_op!(bitand),
Opcode::BitOr => bin_op!(bitor),
Opcode::BitXor => bin_op!(bitxor),
Opcode::ShiftLeft => bin_op!(shl),
Opcode::ShiftRight => bin_op!(shr),
Opcode::UnsignedShiftRight => bin_op!(ushr),
Opcode::Eq => {
let rhs = self.pop();
let lhs = self.pop();
let value = lhs.equals(&rhs, self.context)?;
self.push(value);
}
Opcode::NotEq => {
let rhs = self.pop();
let lhs = self.pop();
let value = !lhs.equals(&rhs, self.context)?;
self.push(value);
}
Opcode::StrictEq => {
let rhs = self.pop();
let lhs = self.pop();
self.push(lhs.strict_equals(&rhs));
}
Opcode::StrictNotEq => {
let rhs = self.pop();
let lhs = self.pop();
self.push(!lhs.strict_equals(&rhs));
}
Opcode::GreaterThan => bin_op!(gt),
Opcode::GreaterThanOrEq => bin_op!(ge),
Opcode::LessThan => bin_op!(lt),
Opcode::LessThanOrEq => bin_op!(le),
Opcode::In => {
let rhs = self.pop();
let lhs = self.pop();
if !rhs.is_object() {
return Err(self.context.construct_type_error(format!(
"right-hand side of 'in' should be an object, got {}",
rhs.get_type().as_str()
)));
}
let key = lhs.to_property_key(self.context)?;
self.push(self.context.has_property(&rhs, &key));
}
Opcode::InstanceOf => {
let y = self.pop();
let x = self.pop();
let value = if let Some(object) = y.as_object() {
let key = WellKnownSymbols::has_instance();
match object.get_method(self.context, key)? {
Some(instance_of_handler) => instance_of_handler
.call(&y, &[x], self.context)?
.to_boolean(),
None if object.is_callable() => {
object.ordinary_has_instance(self.context, &x)?
}
None => {
return Err(self.context.construct_type_error(
"right-hand side of 'instanceof' is not callable",
));
}
}
} else {
return Err(self.context.construct_type_error(format!(
"right-hand side of 'instanceof' should be an object, got {}",
y.get_type().as_str()
)));
};
// spec: https://tc39.es/ecma262/#sec-instanceofoperator
todo!("instanceof operator")
}
Instruction::Void => {
let _value = self.pop();
Some(Value::undefined())
}
Instruction::TypeOf => {
let value = self.pop();
Some(value.get_type().as_str().into())
self.push(value);
}
Opcode::Void => {
let _ = self.pop();
self.push(Value::undefined());
}
Opcode::TypeOf => {
let value = self.pop();
self.push(value.get_type().as_str());
}
Opcode::Pos => {
let value = self.pop();
let value = value.to_number(self.context)?;
self.push(value);
}
Opcode::Neg => {
let value = self.pop().neg(self.context)?;
self.push(value);
}
Opcode::LogicalNot => {
let value = self.pop();
self.push(!value.to_boolean());
}
Opcode::BitNot => {
let target = self.pop();
let num = target.to_number(self.context)?;
let value = if num.is_nan() {
-1
} else {
// TODO: this is not spec compliant.
!(num as i32)
};
self.push(value);
}
Opcode::DefVar => {
let index = self.read::<u32>();
let name = &self.code.names[index as usize];
self.context.create_mutable_binding(
name.to_string(),
false,
VariableScope::Function,
)?;
}
Opcode::DefLet => {
let index = self.read::<u32>();
let name = &self.code.names[index as usize];
self.context.create_mutable_binding(
name.to_string(),
false,
VariableScope::Block,
)?;
}
Opcode::DefConst => {
let index = self.read::<u32>();
let name = &self.code.names[index as usize];
self.context.create_immutable_binding(
name.to_string(),
false,
VariableScope::Block,
)?;
}
Opcode::InitLexical => {
let index = self.read::<u32>();
let value = self.pop();
let name = &self.code.names[index as usize];
self.context.initialize_binding(&name, value)?;
}
Opcode::GetName => {
let index = self.read::<u32>();
let name = &self.code.names[index as usize];
let value = self.context.get_binding_value(&name)?;
self.push(value);
}
Opcode::SetName => {
let index = self.read::<u32>();
let value = self.pop();
let name = &self.code.names[index as usize];
if self.context.has_binding(&name) {
// Binding already exists
self.context.set_mutable_binding(&name, value, true)?;
} else {
self.context.create_mutable_binding(
name.to_string(),
true,
VariableScope::Function,
)?;
self.context.initialize_binding(&name, value)?;
}
Instruction::Pos => {
let value = self.pop();
let value = value.to_number(self.ctx)?;
Some(value.into())
}
Opcode::Jump => {
let address = self.read::<u32>();
self.pc = address as usize;
}
Opcode::JumpIfFalse => {
let address = self.read::<u32>();
if !self.pop().to_boolean() {
self.pc = address as usize;
}
Instruction::Neg => {
let value = self.pop();
Some(Value::from(!value.to_boolean()))
}
Opcode::JumpIfTrue => {
let address = self.read::<u32>();
if self.pop().to_boolean() {
self.pc = address as usize;
}
Instruction::Not => {
let value = self.pop();
Some((!value.to_boolean()).into())
}
Opcode::LogicalAnd => {
let exit = self.read::<u32>();
let lhs = self.pop();
if !lhs.to_boolean() {
self.pc = exit as usize;
self.push(false);
}
Instruction::BitNot => {
let target = self.pop();
let num = target.to_number(self.ctx)?;
let value = if num.is_nan() {
-1
} else {
// TODO: this is not spec compliant.
!(num as i32)
};
Some(value.into())
}
Opcode::LogicalOr => {
let exit = self.read::<u32>();
let lhs = self.pop();
if lhs.to_boolean() {
self.pc = exit as usize;
self.push(true);
}
Instruction::DefVar(name_index) => {
let name: String = self.pool[name_index].to_string(self.ctx)?.to_string();
self.ctx.create_mutable_binding(
name.to_string(),
false,
VariableScope::Function,
)?;
None
}
Opcode::Coalesce => {
let exit = self.read::<u32>();
let lhs = self.pop();
if !lhs.is_null_or_undefined() {
self.pc = exit as usize;
self.push(lhs);
}
Instruction::DefLet(name_index) => {
let name = self.pool[name_index].to_string(self.ctx)?;
}
Opcode::ToBoolean => {
let value = self.pop();
self.push(value.to_boolean());
}
Opcode::GetPropertyByName => {
let index = self.read::<u32>();
self.ctx.create_mutable_binding(
name.to_string(),
false,
VariableScope::Block,
)?;
let value = self.pop();
let object = if let Some(object) = value.as_object() {
object
} else {
value.to_object(self.context)?
};
None
}
Instruction::DefConst(name_index) => {
let name = self.pool[name_index].to_string(self.ctx)?;
let name = self.code.names[index as usize].clone();
let result = object.get(&name.into(), value, self.context)?;
self.ctx.create_immutable_binding(
name.to_string(),
false,
VariableScope::Block,
)?;
self.push(result)
}
Opcode::GetPropertyByValue => {
let value = self.pop();
let key = self.pop();
let object = if let Some(object) = value.as_object() {
object
} else {
value.to_object(self.context)?
};
let key = key.to_property_key(self.context)?;
let result = object.get(&key, value, self.context)?;
self.push(result)
}
Opcode::SetPropertyByName => {
let index = self.read::<u32>();
None
}
Instruction::InitLexical(name_index) => {
let name = self.pool[name_index].to_string(self.ctx)?;
let value = self.pop();
self.ctx.initialize_binding(&name, value.clone())?;
let object = self.pop();
let value = self.pop();
let mut object = if let Some(object) = object.as_object() {
object
} else {
object.to_object(self.context)?
};
None
}
// Find a binding on the environment chain and push its value.
Instruction::GetName(ref name) => match self.ctx.get_binding_value(&name) {
Ok(val) => Some(val),
Err(val) => {
self.ctx
.executor()
.set_current_state(InterpreterState::Error);
Some(val)
}
},
// Create a new object and push to the stack
Instruction::NewObject => Some(Value::new_object(self.ctx)),
};
let name = self.code.names[index as usize].clone();
if let Some(value) = result {
self.push(value);
object.set(name.into(), value, object.clone().into(), self.context)?;
}
self.idx += 1;
if matches!(
self.ctx.executor().get_current_state(),
&InterpreterState::Error,
) {
break;
Opcode::SetPropertyByValue => {
let object = self.pop();
let key = self.pop();
let value = self.pop();
let mut object = if let Some(object) = object.as_object() {
object
} else {
object.to_object(self.context)?
};
let key = key.to_property_key(self.context)?;
object.set(key, value, object.clone().into(), self.context)?;
}
Opcode::Throw => {
let value = self.pop();
return Err(value);
}
Opcode::This => {
let this = self.context.get_this_binding()?;
self.push(this);
}
}
if self.is_trace {
self.trace_print(true);
};
let res = self.pop();
Ok(res)
Ok(())
}
pub fn trace_print(&mut self, end: bool) {
if self.profile.start_flag {
let duration = self.profile.instant.elapsed() - self.profile.prev_time;
pub fn run(&mut self) -> Result<Value> {
let _timer = BoaProfiler::global().start_event("run", "vm");
const COLUMN_WIDTH: usize = 24;
const TIME_COLUMN_WIDTH: usize = COLUMN_WIDTH / 2;
const OPCODE_COLUMN_WIDTH: usize = COLUMN_WIDTH;
const OPERAND_COLUMN_WIDTH: usize = COLUMN_WIDTH;
const NUMBER_OF_COLUMNS: usize = 4;
if self.is_trace {
println!("{}\n", self.code);
println!(
"{:-^width$}",
" Vm Start ",
width = COLUMN_WIDTH * NUMBER_OF_COLUMNS - 10
);
println!(
"{:<time_width$} {:<opcode_width$} {:<operand_width$} Top Of Stack",
"Time",
"Opcode",
"Operands",
time_width = TIME_COLUMN_WIDTH,
opcode_width = OPCODE_COLUMN_WIDTH,
operand_width = OPERAND_COLUMN_WIDTH,
);
}
self.pc = 0;
while self.pc < self.code.code.len() {
if self.is_trace {
let mut pc = self.pc;
let instant = Instant::now();
self.execute_instruction()?;
let duration = instant.elapsed();
let opcode: Opcode = self.code.read::<u8>(pc).try_into().unwrap();
println!(
"{0: <10} {1}",
"{:<time_width$} {:<opcode_width$} {:<operand_width$} {}",
format!("{}μs", duration.as_micros()),
self.profile.trace_string
opcode.as_str(),
self.code.instruction_operands(&mut pc),
match self.stack.last() {
None => "<empty>".to_string(),
Some(value) => format!("{}", value.display()),
},
time_width = TIME_COLUMN_WIDTH,
opcode_width = OPCODE_COLUMN_WIDTH,
operand_width = OPERAND_COLUMN_WIDTH,
);
} else {
self.execute_instruction()?;
}
} else {
let duration = self.profile.instant.elapsed() - self.profile.prev_time;
println!("VM start up time: {}μs", duration.as_micros());
println!(
"{0: <10} {1: <20} {2: <10}",
"Time", "Instr", "Top Of Stack"
);
println!();
}
self.profile.start_flag = true;
if self.is_trace {
self.profile.trace_string = format!(
"{0:<20} {1}",
format!(
"{:<20}",
self.instructions[if end { self.idx - 1 } else { self.idx }]
),
match self.stack.last() {
None => "<empty>".to_string(),
Some(val) => format!("{}\t{:p}", val.display(), val),
}
);
}
if end {
println!();
println!("Pool");
for (i, val) in self.pool.iter().enumerate() {
println!("{:<10} {:<10} {:p}", i, val.display(), val);
println!("\nStack:");
if !self.stack.is_empty() {
for (i, value) in self.stack.iter().enumerate() {
println!(
"{:04}{:<width$} {}",
i,
"",
value.display(),
width = COLUMN_WIDTH / 2 - 4,
);
}
} else {
println!(" <empty>");
}
println!("\n");
}
println!();
println!("Stack");
for (i, val) in self.stack.iter().enumerate() {
println!("{:<10} {:<10} {:p}", i, val.display(), val);
}
println!();
if self.stack.is_empty() {
return Ok(Value::undefined());
}
self.profile.prev_time = self.profile.instant.elapsed();
Ok(self.pop())
}
}

614
boa/src/vm/opcode.rs

@ -0,0 +1,614 @@
use std::convert::TryFrom;
/// The opcodes of the vm.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum Opcode {
/// Pop the top value from the stack.
///
/// Operands:
///
/// Stack: value **=>**
Pop,
/// Push a copy of the top value on the stack.
///
/// Operands:
///
/// Stack: value **=>** value, value
Dup,
/// Swap the top two values on the stack.
///
/// Operands:
///
/// Stack: v1, v2 **=>** v2, v1
Swap,
/// Push integer `0` on the stack.
///
/// Operands:
///
/// Stack: **=>** 0
PushZero,
/// Push integer `1` on the stack.
///
/// Operands:
///
/// Stack: **=>** 1
PushOne,
/// Push `i8` value on the stack.
///
/// Operands: value: `i8`
///
/// Stack: **=>** value
PushInt8,
/// Push i16 value on the stack.
///
/// Operands: value: `i16`
///
/// Stack: **=>** value
PushInt16,
/// Push i32 value on the stack.
///
/// Operands: value: `i32`
///
/// Stack: **=>** value
PushInt32,
/// Push `f64` value on the stack.
///
/// Operands: value: `f64`
///
/// Stack: **=>** value
PushRational,
/// Push `NaN` teger on the stack.
///
/// Operands:
///
/// Stack: **=>** `NaN`
PushNaN,
/// Push `Infinity` value on the stack.
///
/// Operands:
///
/// Stack: **=>** `Infinity`
PushPositiveInfinity,
/// Push `-Infinity` value on the stack.
///
/// Operands:
///
/// Stack: **=>** `-Infinity`
PushNegativeInfinity,
/// Push `null` value on the stack.
///
/// Operands:
///
/// Stack: **=>** `null`
PushNull,
/// Push `true` value on the stack.
///
/// Operands:
///
/// Stack: **=>** `true`
PushTrue,
/// Push `false` value on the stack.
///
/// Operands:
///
/// Stack: **=>** `false`
PushFalse,
/// Push `undefined` value on the stack.
///
/// Operands:
///
/// Stack: **=>** `undefined`
PushUndefined,
/// Push literal value on the stack.
///
/// Like strings and bigints. The index oprand is used to index into the `literals`
/// array to get the value.
///
/// Operands: index: `u32`
///
/// Stack: **=>** (`literals[index]`)
PushLiteral,
/// Push empty object `{}` value on the stack.
///
/// Operands:
///
/// Stack: **=>** object
PushEmptyObject,
/// Push array object `{}` value on the stack.
///
/// Operands: n: `u32`
///
/// Stack: v1, v1, ... vn **=>** [v1, v2, ..., vn]
PushNewArray,
/// Binary `+` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs + rhs)
Add,
/// Binary `-` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs - rhs)
Sub,
/// Binary `/` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs / rhs)
Div,
/// Binary `*` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs * rhs)
Mul,
/// Binary `%` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs % rhs)
Mod,
/// Binary `**` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs ** rhs)
Pow,
/// Binary `>>` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs >> rhs)
ShiftRight,
/// Binary `<<` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs << rhs)
ShiftLeft,
/// Binary `>>>` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs >>> rhs)
UnsignedShiftRight,
/// Binary bitwise `|` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs | rhs)
BitOr,
/// Binary bitwise `&` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs & rhs)
BitAnd,
/// Binary bitwise `^` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs ^ rhs)
BitXor,
/// Unary bitwise `~` operator.
///
/// Operands:
///
/// Stack: value **=>** ~value
BitNot,
/// Binary `in` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs `in` rhs)
In,
/// Binary `==` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs `==` rhs)
Eq,
/// Binary `===` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs `===` rhs)
StrictEq,
/// Binary `!=` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs `!=` rhs)
NotEq,
/// Binary `!==` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs `!==` rhs)
StrictNotEq,
/// Binary `>` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs > rhs)
GreaterThan,
/// Binary `>=` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs >= rhs)
GreaterThanOrEq,
/// Binary `<` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs < rhs)
LessThan,
/// Binary `<=` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs <= rhs)
LessThanOrEq,
/// Binary `instanceof` operator.
///
/// Operands:
///
/// Stack: lhs, rhs **=>** (lhs instanceof rhs)
InstanceOf,
/// Binary logical `&&` operator.
///
/// This is a short-circit operator, if the `lhs` value is `false`, then it jumps to `exit` address.
///
/// Operands: exit: `u32`
///
/// Stack: lhs, rhs **=>** (lhs && rhs)
LogicalAnd,
/// Binary logical `||` operator.
///
/// This is a short-circit operator, if the `lhs` value is `true`, then it jumps to `exit` address.
///
/// Operands: exit: `u32`
///
/// Stack: lhs, rhs **=>** (lhs || rhs)
LogicalOr,
/// Binary `??` operator.
///
/// This is a short-circit operator, if the `lhs` value is **not** `null` or `undefined`,
/// then it jumps to `exit` address.
///
/// Operands: exit: `u32`
///
/// Stack: lhs, rhs **=>** (lhs && rhs)
Coalesce,
/// Unary `typeof` operator.
///
/// Operands:
///
/// Stack: value **=>** (`typeof` value)
TypeOf,
/// Unary `void` operator.
///
/// Operands:
///
/// Stack: value **=>** `undefined`
Void,
/// Unary logical `!` operator.
///
/// Operands:
///
/// Stack: value **=>** (!value)
LogicalNot,
/// Unary `+` operator.
///
/// Operands:
///
/// Stack: value **=>** (+value)
Pos,
/// Unary `-` operator.
///
/// Operands:
///
/// Stack: value **=>** (-value)
Neg,
/// Declate `var` type variable.
///
/// Operands: name_index: `u32`
///
/// Stack: **=>**
DefVar,
/// Declate `let` type variable.
///
/// Operands: name_index: `u32`
///
/// Stack: **=>**
DefLet,
/// Declate `const` type variable.
///
/// Operands: name_index: `u32`
///
/// Stack: **=>**
DefConst,
/// Initialize a lexical binding.
///
/// Operands: name_index: `u32`
///
/// Stack: **=>**
InitLexical,
/// Find a binding on the environment chain and push its value.
///
/// Operands: name_index: `u32`
///
/// Stack: **=>** value
GetName,
/// Find a binding on the environment chain and assign its value.
///
/// Operands: name_index: `u32`
///
/// Stack: value **=>**
SetName,
/// Get a property by name from an object an push it on the stack.
///
/// Like `object.name`
///
/// Operands: name_index: `u32`
///
/// Stack: object **=>** value
GetPropertyByName,
/// Get a property by value from an object an push it on the stack.
///
/// Like `object[key]`
///
/// Operands:
///
/// Stack: key, object **=>** value
GetPropertyByValue,
/// Sets a property by name of an object.
///
/// Like `object.name = value`
///
/// Operands: name_index: `u32`
///
/// Stack: value, object **=>**
SetPropertyByName,
/// Sets a property by value of an object.
///
/// Like `object[key] = value`
///
/// Operands:
///
/// Stack: value, key, object **=>**
SetPropertyByValue,
/// Unconditional jump to address.
///
/// Operands: address: `u32`
/// Stack: **=>**
Jump,
/// Constional jump to address.
///
/// If the value popped is [`falsy`][falsy] then jump to `address`.
///
/// Operands: address: `u32`
///
/// Stack: cond **=>**
///
/// [falsy]: https://developer.mozilla.org/en-US/docs/Glossary/Falsy
JumpIfFalse,
/// Constional jump to address.
///
/// If the value popped is [`truthy`][truthy] then jump to `address`.
///
/// Operands: address: `u32`
///
/// Stack: cond **=>**
///
/// [truthy]: https://developer.mozilla.org/en-US/docs/Glossary/Truthy
JumpIfTrue,
/// Throw exception
///
/// Operands:
///
/// Stack: `exc` **=>**
Throw,
/// Pops value converts it to boolean and pushes it back.
///
/// Operands:
///
/// Stack: value **=>** (`ToBoolean(value)`)
ToBoolean,
/// Pushes `this` value
///
/// Operands:
///
/// Stack: **=>** `this`
This,
/// No-operation instruction, does nothing.
///
/// Operands:
///
/// Stack: **=>**
// Safety: Must be last in the list since, we use this for range checking
// in TryFrom<u8> impl.
Nop,
}
impl Opcode {
/// Create opcode from `u8` byte.
///
/// # Safety
///
/// Does not check if `u8` type is a valid `Opcode`.
pub unsafe fn from_raw(value: u8) -> Self {
std::mem::transmute(value)
}
pub fn as_str(self) -> &'static str {
match self {
Opcode::Pop => "Pop",
Opcode::Dup => "Dup",
Opcode::Swap => "Swap",
Opcode::PushZero => "PushZero",
Opcode::PushOne => "PushOne",
Opcode::PushInt8 => "PushInt8",
Opcode::PushInt16 => "PushInt16",
Opcode::PushInt32 => "PushInt32",
Opcode::PushRational => "PushRational",
Opcode::PushNaN => "PushNaN",
Opcode::PushPositiveInfinity => "PushPositiveInfinity",
Opcode::PushNegativeInfinity => "PushNegativeInfinity",
Opcode::PushNull => "PushNull",
Opcode::PushTrue => "PushTrue",
Opcode::PushFalse => "PushFalse",
Opcode::PushUndefined => "PushUndefined",
Opcode::PushLiteral => "PushLiteral",
Opcode::PushEmptyObject => "PushEmptyObject",
Opcode::PushNewArray => "PushNewArray",
Opcode::Add => "Add",
Opcode::Sub => "Sub",
Opcode::Div => "Div",
Opcode::Mul => "Mul",
Opcode::Mod => "Mod",
Opcode::Pow => "Pow",
Opcode::ShiftRight => "ShiftRight",
Opcode::ShiftLeft => "ShiftLeft",
Opcode::UnsignedShiftRight => "UnsignedShiftRight",
Opcode::BitOr => "BitOr",
Opcode::BitAnd => "BitAnd",
Opcode::BitXor => "BitXor",
Opcode::BitNot => "BitNot",
Opcode::In => "In",
Opcode::Eq => "Eq",
Opcode::StrictEq => "StrictEq",
Opcode::NotEq => "NotEq",
Opcode::StrictNotEq => "StrictNotEq",
Opcode::GreaterThan => "GreaterThan",
Opcode::GreaterThanOrEq => "GreaterThanOrEq",
Opcode::LessThan => "LessThan",
Opcode::LessThanOrEq => "LessThanOrEq",
Opcode::InstanceOf => "InstanceOf",
Opcode::TypeOf => "TypeOf",
Opcode::Void => "Void",
Opcode::LogicalNot => "LogicalNot",
Opcode::LogicalAnd => "LogicalAnd",
Opcode::LogicalOr => "LogicalOr",
Opcode::Coalesce => "Coalesce",
Opcode::Pos => "Pos",
Opcode::Neg => "Neg",
Opcode::DefVar => "DefVar",
Opcode::DefLet => "DefLet",
Opcode::DefConst => "DefConst",
Opcode::InitLexical => "InitLexical",
Opcode::GetName => "GetName",
Opcode::SetName => "SetName",
Opcode::GetPropertyByName => "GetPropertyByName",
Opcode::GetPropertyByValue => "GetPropertyByValue",
Opcode::SetPropertyByName => "SetPropertyByName",
Opcode::SetPropertyByValue => "SetPropertyByValue",
Opcode::Jump => "Jump",
Opcode::JumpIfFalse => "JumpIfFalse",
Opcode::JumpIfTrue => "JumpIfTrue",
Opcode::Throw => "Throw",
Opcode::ToBoolean => "ToBoolean",
Opcode::This => "This",
Opcode::Nop => "Nop",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidOpcodeError {
value: u8,
}
impl std::fmt::Display for InvalidOpcodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "invalid opcode: {:#04x}", self.value)
}
}
impl std::error::Error for InvalidOpcodeError {}
impl TryFrom<u8> for Opcode {
type Error = InvalidOpcodeError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if value > Self::Nop as u8 {
return Err(InvalidOpcodeError { value });
}
// Safety: we already checked if it is in the Opcode range,
// so this is safe.
let opcode = unsafe { Self::from_raw(value) };
Ok(opcode)
}
}

77
docs/vm.md

@ -10,11 +10,11 @@ You can interpret bytecode by passing the "vm" flag (see below). The diagram bel
## Enabling ByteCode interpretation
You need to enable this via a feature flag. If using VSCode you can run `Cargo Run (VM)`. If using the command line you can pass `cargo run --features vm ../tests/js/test.js` from within the boa_cli folder.
You need to enable this via a feature flag. If using VSCode you can run `Cargo Run (VM)`. If using the command line you can pass `cargo run --features vm ../tests/js/test.js` from within the boa_cli folder. You can also pass the `--trace` optional flag to print the trace of the code.
## Understanding the output
## Understanding the trace output
Once set up you should you can try some very simple javascript in your test file. For example:
Once set up you can try some simple javascript in your test file. For example:
```js
let a = 1;
@ -23,44 +23,71 @@ let b = 2;
Should output:
```
VM start up time: 0μs
Time Instr Top Of Stack
```text
Code:
Location Count Opcode Operands
000000 0000 DefLet 0000: 'a'
000005 0001 PushOne
000006 0002 InitLexical 0000: 'a'
000011 0003 DefLet 0001: 'b'
000016 0004 PushInt8 2
000018 0005 InitLexical 0001: 'b'
Literals:
<empty>
Names:
0000: a
0001: b
27μs DefLet(0) <empty>
3μs One <empty>
35μs InitLexical(0) 1 0x7f727f41d0c0
18μs DefLet(1) 1 0x7f727f41d0c0
4μs Int32(2) 1 0x7f727f41d0c0
19μs InitLexical(1) 2 0x7f727f41d0d8
Pool
0 "a" 0x7f727f41d120
1 "b" 0x7f727f41d138
-------------------------------------- Vm Start --------------------------------------
Time Opcode Operands Top Of Stack
64μs DefLet 0000: 'a' <empty>
3μs PushOne 1
21μs InitLexical 0000: 'a' <empty>
32μs DefLet 0001: 'b' <empty>
2μs PushInt8 2 2
17μs InitLexical 0001: 'b' <empty>
Stack
0 1 0x7f727f41d0c0
1 2 0x7f727f41d0d8
Stack:
<empty>
2
undefined
```
The above will output 3 sections: Instructions, pool and Stack. We can go through each one in detail:
The above will output three sections that are divided into subsections:
- The code that will be executed
- `Code`: The bytecode.
- `Location`: Location of the instruction (instructions are not the same size).
- `Count`: Instruction count.
- `Operands`: The operands of the opcode.
- `Literals`: The literals used by the opcode (like strings).
- `Names`: Contains variable names.
- The code being executed (marked by `"Vm Start"`).
- `Time`: The amount of time that instruction took to execute.
- `Opcode`: Opcode name.
- `Operands`: The operands this opcode took.
- `Top Of Stack`: The top element of the stack **after** execution of instruction.
- `Stack`: The trace of the stack after execution ends.
- The result of the execution (The top element of the stack, if the stack is empty then `undefined` is returned).
### Instruction
This shows each instruction being executed and how long it took. This is useful for us to see if a particular instruction is taking too long.
Then you have the instruction itself and its operand. Last you have what is on the top of the stack **before** the instruction is executed, followed by the memory address of that same value. We show the memory address to identify if 2 values are the same or different.
Then you have the instruction itself and its operand. Last you have what is on the top of the stack **after** the instruction is executed, followed by the memory address of that same value. We show the memory address to identify if 2 values are the same or different.
### Pool
### Literals
JSValues can live on the pool, which acts as our heap. Instructions often have an index of where on the pool it refers to a value.
You can use these values to match up with the instructions above. For e.g (using the above output) `DefLet(0)` means take the value off the pool at index `0`, which is `a` and define it in the current scope.
You can use these values to match up with the instructions above. For e.g (using the above output) `DefLet 0` means take the value off the pool at index `0`, which is `a` and define it in the current scope.
### Stack
The stack view shows what the stack looks like for the JS executed.
Using the above output as an exmaple, after `One` has been executed the next instruction (`InitLexical(0)`) has a `1` on the top of the stack. This is because `One` puts `1` on the stack.
Using the above output as an exmaple, after `PushOne` has been executed the next instruction (`InitLexical 0`) has a `1` on the top of the stack. This is because `PushOne` puts `1` on the stack.
### Comparing ByteCode output
@ -68,7 +95,7 @@ If you wanted another engine's bytecode output for the same JS, SpiderMonkey's b
I named the binary `js_shell` as `js` conflicts with NodeJS. Once up and running you should be able to use `js_shell -f tests/js/test.js`. You will get no output to begin with, this is because you need to run `dis()` or `dis([func])` in the code. Once you've done that you should get some output like so:
```
```text
loc op
----- --
00000: GlobalOrEvalDeclInstantiation 0 #

Loading…
Cancel
Save