Browse Source

Implement switch statement for vm (#1407)

pull/1408/head
Halid Odat 3 years ago committed by GitHub
parent
commit
825faaa832
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 164
      boa/src/bytecompiler.rs
  2. 2
      boa/src/vm/code_block.rs
  3. 17
      boa/src/vm/mod.rs
  4. 17
      boa/src/vm/opcode.rs

164
boa/src/bytecompiler.rs

@ -8,30 +8,8 @@ use crate::{
vm::{CodeBlock, Opcode},
JsString, 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(JsString),
@ -45,10 +23,10 @@ struct Label {
}
#[derive(Debug, Clone)]
struct LoopControlInfo {
struct JumpControlInfo {
label: Option<Box<str>>,
loop_start: u32,
continues: Vec<Label>,
start_address: u32,
is_loop: bool,
breaks: Vec<Label>,
}
@ -65,7 +43,7 @@ pub struct ByteCompiler {
code_block: CodeBlock,
literals_map: HashMap<Literal, u32>,
names_map: HashMap<JsString, u32>,
loops: Vec<LoopControlInfo>,
jump_info: Vec<JumpControlInfo>,
}
impl Default for ByteCompiler {
@ -84,7 +62,7 @@ impl ByteCompiler {
code_block: CodeBlock::new(),
literals_map: HashMap::new(),
names_map: HashMap::new(),
loops: Vec::new(),
jump_info: Vec::new(),
}
}
@ -134,17 +112,17 @@ impl ByteCompiler {
#[inline]
fn emit_u64(&mut self, value: u64) {
self.code_block.code.extend(&u64_to_array(value));
self.code_block.code.extend(&value.to_ne_bytes());
}
#[inline]
fn emit_u32(&mut self, value: u32) {
self.code_block.code.extend(&u32_to_array(value));
self.code_block.code.extend(&value.to_ne_bytes());
}
#[inline]
fn emit_u16(&mut self, value: u16) {
self.code_block.code.extend(&u16_to_array(value));
self.code_block.code.extend(&value.to_ne_bytes());
}
#[inline]
@ -232,7 +210,7 @@ impl ByteCompiler {
let index = index as usize;
let bytes = u32_to_array(target);
let bytes = target.to_ne_bytes();
self.code_block.code[index + 1] = bytes[0];
self.code_block.code[index + 2] = bytes[1];
self.code_block.code[index + 3] = bytes[2];
@ -246,28 +224,47 @@ impl ByteCompiler {
}
#[inline]
fn push_loop_control_info(&mut self, label: Option<Box<str>>, loop_start: u32) {
self.loops.push(LoopControlInfo {
fn push_loop_control_info(&mut self, label: Option<Box<str>>, start_address: u32) {
self.jump_info.push(JumpControlInfo {
label,
loop_start,
continues: Vec::new(),
start_address,
is_loop: true,
breaks: Vec::new(),
})
}
#[inline]
fn pop_loop_control_info(&mut self) {
let loop_info = self.loops.pop().unwrap();
let loop_info = self.jump_info.pop().unwrap();
for label in loop_info.continues {
self.patch_jump_with_target(label, loop_info.loop_start);
}
assert!(loop_info.is_loop);
for label in loop_info.breaks {
self.patch_jump(label);
}
}
#[inline]
fn push_switch_control_info(&mut self, label: Option<Box<str>>, start_address: u32) {
self.jump_info.push(JumpControlInfo {
label,
start_address,
is_loop: false,
breaks: Vec::new(),
})
}
#[inline]
fn pop_switch_control_info(&mut self) {
let info = self.jump_info.pop().unwrap();
assert!(!info.is_loop);
for label in info.breaks {
self.patch_jump(label);
}
}
#[inline]
fn compile_access<'a>(&mut self, node: &'a Node) -> Access<'a> {
match node {
@ -331,7 +328,7 @@ impl ByteCompiler {
self.compile_expr(node.obj(), true);
self.emit(Opcode::SetPropertyByValue, &[]);
}
Access::This => todo!("access_get 'this'"),
Access::This => todo!("access_set 'this'"),
}
}
@ -403,6 +400,10 @@ impl ByteCompiler {
NumOp::Exp => self.emit_opcode(Opcode::Pow),
NumOp::Mod => self.emit_opcode(Opcode::Mod),
}
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
BinOp::Bit(op) => {
self.compile_expr(binary.rhs(), true);
@ -414,6 +415,10 @@ impl ByteCompiler {
BitOp::Shr => self.emit_opcode(Opcode::ShiftRight),
BitOp::UShr => self.emit_opcode(Opcode::UnsignedShiftRight),
}
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
BinOp::Comp(op) => {
self.compile_expr(binary.rhs(), true);
@ -429,6 +434,10 @@ impl ByteCompiler {
CompOp::In => self.emit_opcode(Opcode::In),
CompOp::InstanceOf => self.emit_opcode(Opcode::InstanceOf),
}
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
BinOp::Log(op) => {
match op {
@ -450,6 +459,10 @@ impl ByteCompiler {
self.patch_jump(exit);
}
};
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
BinOp::Assign(op) => {
let opcode = match op {
@ -501,13 +514,13 @@ impl ByteCompiler {
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, &[]);
@ -626,48 +639,50 @@ impl ByteCompiler {
}
}
Node::WhileLoop(while_) => {
let loop_start = self.next_opcode_location();
self.push_loop_control_info(while_.label().map(Into::into), loop_start);
let start_address = self.next_opcode_location();
self.push_loop_control_info(while_.label().map(Into::into), start_address);
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.emit(Opcode::Jump, &[start_address]);
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);
let start_address = self.next_opcode_location();
self.push_loop_control_info(do_while.label().map(Into::into), start_address);
self.compile_stmt(do_while.body(), false);
self.compile_expr(do_while.cond(), true);
self.emit(Opcode::JumpIfTrue, &[loop_start]);
self.emit(Opcode::JumpIfTrue, &[start_address]);
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);
let label = self.jump();
let mut items = self.jump_info.iter_mut().rev().filter(|info| info.is_loop);
let target = if node.label().is_none() {
items.next()
} else {
for loop_ in self.loops.iter_mut().rev() {
if loop_.label.as_deref() == node.label() {
loop_.continues.push(jump_label);
}
}
items.find(|info| info.label.as_deref() == node.label())
}
.expect("continue target")
.start_address;
self.patch_jump_with_target(label, target);
}
Node::Break(node) => {
let jump_label = self.jump();
let label = self.jump();
if node.label().is_none() {
self.loops.last_mut().unwrap().breaks.push(jump_label);
self.jump_info.last_mut().unwrap().breaks.push(label);
} else {
for loop_ in self.loops.iter_mut().rev() {
if loop_.label.as_deref() == node.label() {
loop_.breaks.push(jump_label);
for info in self.jump_info.iter_mut().rev() {
if info.label.as_deref() == node.label() {
info.breaks.push(label);
break;
}
}
}
@ -681,6 +696,33 @@ impl ByteCompiler {
self.compile_expr(throw.expr(), true);
self.emit(Opcode::Throw, &[]);
}
Node::Switch(switch) => {
let start_address = self.next_opcode_location();
self.push_switch_control_info(None, start_address);
self.compile_expr(switch.val(), true);
let mut labels = Vec::with_capacity(switch.cases().len());
for case in switch.cases() {
self.compile_expr(case.condition(), true);
labels.push(self.jump_with_custom_opcode(Opcode::Case));
}
let exit = self.jump_with_custom_opcode(Opcode::Default);
for (label, case) in labels.into_iter().zip(switch.cases()) {
self.patch_jump(label);
self.compile_statement_list(case.body(), false);
}
self.patch_jump(exit);
if let Some(body) = switch.default() {
for node in body {
self.compile_stmt(node, false);
}
}
self.pop_switch_control_info();
}
Node::Empty => {}
expr => self.compile_expr(expr, use_expr),
}

2
boa/src/vm/code_block.rs

@ -92,6 +92,8 @@ impl CodeBlock {
| Opcode::Jump
| Opcode::JumpIfFalse
| Opcode::JumpIfTrue
| Opcode::Case
| Opcode::Default
| Opcode::LogicalAnd
| Opcode::LogicalOr
| Opcode::Coalesce => {

17
boa/src/vm/mod.rs

@ -59,6 +59,7 @@ impl<'a> Vm<'a> {
///
/// If there is nothing to pop, then this will panic.
#[inline]
#[track_caller]
pub fn pop(&mut self) -> Value {
self.stack.pop().unwrap()
}
@ -423,6 +424,22 @@ impl<'a> Vm<'a> {
let this = self.context.get_this_binding()?;
self.push(this);
}
Opcode::Case => {
let address = self.read::<u32>();
let cond = self.pop();
let value = self.pop();
if !value.strict_equals(&cond) {
self.push(value);
} else {
self.pc = address as usize;
}
}
Opcode::Default => {
let exit = self.read::<u32>();
let _ = self.pop();
self.pc = exit as usize;
}
}
Ok(())

17
boa/src/vm/opcode.rs

@ -491,6 +491,21 @@ pub enum Opcode {
/// Stack: **=>** `this`
This,
/// Pop the two values of the stack, strict equal compares the two values,
/// if true jumps to address, otherwise push the second poped value.
///
/// Operands: address: `u32`
///
/// Stack: `value`, `cond` **=>** `cond` (if `cond !== value`).
Case,
/// Pops the top of stack and jump to address.
///
/// Operands: address: `u32`
///
/// Stack: `value` **=>**
Default,
/// No-operation instruction, does nothing.
///
/// Operands:
@ -579,6 +594,8 @@ impl Opcode {
Opcode::Throw => "Throw",
Opcode::ToBoolean => "ToBoolean",
Opcode::This => "This",
Opcode::Case => "Case",
Opcode::Default => "Default",
Opcode::Nop => "Nop",
}
}

Loading…
Cancel
Save