mirror of https://github.com/boa-dev/boa.git
Browse Source
- 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 operatorpull/1387/head
Halid Odat
3 years ago
committed by
GitHub
25 changed files with 2046 additions and 720 deletions
@ -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 |
||||
} |
||||
} |
@ -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(()) |
||||
} |
||||
} |
@ -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!(), |
||||
} |
||||
} |
||||
} |
@ -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"), |
||||
} |
||||
} |
||||
} |
@ -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) |
||||
} |
||||
} |
Loading…
Reference in new issue