Skip to content

Commit

Permalink
Split vm/opcode into modules (#2343)
Browse files Browse the repository at this point in the history
<!---
Thank you for contributing to Boa! Please fill out the template below, and remove or add any
information as you feel necessary.
--->
Hi!

This isn't really related to a pull request that I know of. I was trying to better wrap my head around Boa's VM and thought I'd break it apart so that the file wasn't 2500+ lines. I figured I'd submit it as a draft and get feedback/see if anyone was interested in it. The way the modules were broken apart was primarily based off the opcode name (`GetFunction` & `GetFunctionAsync` -> `./get/function.rs`).

It changes the following:

- Adds an `Operation` trait to opcode/mod.rs
- Implements `Operation` for each Opcode variant, moving the executable instruction code from `vm/mod.rs` to the respective module



Co-authored-by: raskad <32105367+raskad@users.noreply.github.com>
  • Loading branch information
nekevss and raskad committed Oct 22, 2022
1 parent 80017fd commit 9a05b1e
Show file tree
Hide file tree
Showing 67 changed files with 5,431 additions and 2,853 deletions.
2,702 changes: 173 additions & 2,529 deletions boa_engine/src/vm/mod.rs

Large diffs are not rendered by default.

119 changes: 119 additions & 0 deletions boa_engine/src/vm/opcode/await_stm/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use crate::{
builtins::{JsArgs, Promise},
object::FunctionBuilder,
vm::{call_frame::GeneratorResumeKind, opcode::Operation, ShouldExit},
Context, JsResult, JsValue,
};

/// `Await` implements the Opcode Operation for `Opcode::Await`
///
/// Operation:
/// - Stops the current Async function and schedules it to resume later.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct Await;

impl Operation for Await {
const NAME: &'static str = "Await";
const INSTRUCTION: &'static str = "INST - Await";

fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let value = context.vm.pop();

// 2. Let promise be ? PromiseResolve(%Promise%, value).
let promise = Promise::promise_resolve(
context.intrinsics().constructors().promise().constructor(),
value,
context,
)?;

// 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called:
// 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
let on_fulfilled = FunctionBuilder::closure_with_captures(
context,
|_this, args, (environment, stack, frame), context| {
// a. Let prevContext be the running execution context.
// b. Suspend prevContext.
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
// d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it.
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
// f. Return undefined.

std::mem::swap(&mut context.realm.environments, environment);
std::mem::swap(&mut context.vm.stack, stack);
context.vm.push_frame(frame.clone());

context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal;
context.vm.push(args.get_or_undefined(0));
context.run()?;

*frame = context
.vm
.pop_frame()
.expect("generator call frame must exist");
std::mem::swap(&mut context.realm.environments, environment);
std::mem::swap(&mut context.vm.stack, stack);

Ok(JsValue::undefined())
},
(
context.realm.environments.clone(),
context.vm.stack.clone(),
context.vm.frame().clone(),
),
)
.name("")
.length(1)
.build();

// 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called:
// 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
let on_rejected = FunctionBuilder::closure_with_captures(
context,
|_this, args, (environment, stack, frame), context| {
// a. Let prevContext be the running execution context.
// b. Suspend prevContext.
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
// d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it.
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
// f. Return undefined.

std::mem::swap(&mut context.realm.environments, environment);
std::mem::swap(&mut context.vm.stack, stack);
context.vm.push_frame(frame.clone());

context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw;
context.vm.push(args.get_or_undefined(0));
context.run()?;

*frame = context
.vm
.pop_frame()
.expect("generator call frame must exist");
std::mem::swap(&mut context.realm.environments, environment);
std::mem::swap(&mut context.vm.stack, stack);

Ok(JsValue::undefined())
},
(
context.realm.environments.clone(),
context.vm.stack.clone(),
context.vm.frame().clone(),
),
)
.name("")
.length(1)
.build();

// 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
promise
.as_object()
.expect("promise was not an object")
.borrow_mut()
.as_promise_mut()
.expect("promise was not a promise")
.perform_promise_then(&on_fulfilled.into(), &on_rejected.into(), None, context);

context.vm.push(JsValue::undefined());
Ok(ShouldExit::Await)
}
}
70 changes: 70 additions & 0 deletions boa_engine/src/vm/opcode/binary_ops/logical.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use crate::{
vm::{opcode::Operation, ShouldExit},
Context, JsResult,
};

/// `LogicalAnd` implements the Opcode Operation for `Opcode::LogicalAnd`
///
/// Operation:
/// - Binary logical `&&` operation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct LogicalAnd;

impl Operation for LogicalAnd {
const NAME: &'static str = "LogicalAnd";
const INSTRUCTION: &'static str = "INST - LogicalAnd";

fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let exit = context.vm.read::<u32>();
let lhs = context.vm.pop();
if !lhs.to_boolean() {
context.vm.frame_mut().pc = exit as usize;
context.vm.push(lhs);
}
Ok(ShouldExit::False)
}
}

/// `LogicalOr` implements the Opcode Operation for `Opcode::LogicalOr`
///
/// Operation:
/// - Binary logical `||` operation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct LogicalOr;

impl Operation for LogicalOr {
const NAME: &'static str = "LogicalOr";
const INSTRUCTION: &'static str = "INST - LogicalOr";

fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let exit = context.vm.read::<u32>();
let lhs = context.vm.pop();
if lhs.to_boolean() {
context.vm.frame_mut().pc = exit as usize;
context.vm.push(lhs);
}
Ok(ShouldExit::False)
}
}

/// `Coalesce` implements the Opcode Operation for `Opcode::Coalesce`
///
/// Operation:
/// - Binary logical `||` operation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct Coalesce;

impl Operation for Coalesce {
const NAME: &'static str = "Coalesce";
const INSTRUCTION: &'static str = "INST - Coalesce";

fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let exit = context.vm.read::<u32>();
let lhs = context.vm.pop();
if !lhs.is_null_or_undefined() {
context.vm.frame_mut().pc = exit as usize;
context.vm.push(lhs);
}
Ok(ShouldExit::False)
}
}
46 changes: 46 additions & 0 deletions boa_engine/src/vm/opcode/binary_ops/macro_defined.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
use crate::{
vm::{opcode::Operation, ShouldExit},
Context, JsResult,
};

macro_rules! implement_bin_ops {
($name:ident, $op:ident, $doc_string:literal) => {
#[doc= concat!("`", stringify!($name), "` implements the OpCode Operation for `Opcode::", stringify!($name), "`\n")]
#[doc= "\n"]
#[doc="Operation:\n"]
#[doc= concat!(" - ", $doc_string)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct $name;

impl Operation for $name {
const NAME: &'static str = stringify!($name);
const INSTRUCTION: &'static str = stringify!("INST - " + $name);

fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let rhs = context.vm.pop();
let lhs = context.vm.pop();
let value = lhs.$op(&rhs, context)?;
context.vm.push(value);
Ok(ShouldExit::False)
}
}
};
}

implement_bin_ops!(Add, add, "Binary `+` operator.");
implement_bin_ops!(Sub, sub, "Binary `-` operator.");
implement_bin_ops!(Mul, mul, "Binary `*` operator.");
implement_bin_ops!(Div, div, "Binary `/` operator.");
implement_bin_ops!(Pow, pow, "Binary `**` operator.");
implement_bin_ops!(Mod, rem, "Binary `%` operator.");
implement_bin_ops!(BitAnd, bitand, "Binary `&` operator.");
implement_bin_ops!(BitOr, bitor, "Binary `|` operator.");
implement_bin_ops!(BitXor, bitxor, "Binary `^` operator.");
implement_bin_ops!(ShiftLeft, shl, "Binary `<<` operator.");
implement_bin_ops!(ShiftRight, shr, "Binary `>>` operator.");
implement_bin_ops!(UnsignedShiftRight, ushr, "Binary `>>>` operator.");
implement_bin_ops!(Eq, equals, "Binary `==` operator.");
implement_bin_ops!(GreaterThan, gt, "Binary `>` operator.");
implement_bin_ops!(GreaterThanOrEq, ge, "Binary `>=` operator.");
implement_bin_ops!(LessThan, lt, "Binary `<` operator.");
implement_bin_ops!(LessThanOrEq, le, "Binary `<=` operator.");
120 changes: 120 additions & 0 deletions boa_engine/src/vm/opcode/binary_ops/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use crate::{
error::JsNativeError,
vm::{opcode::Operation, ShouldExit},
Context, JsResult,
};

pub(crate) mod logical;
pub(crate) mod macro_defined;

pub(crate) use logical::*;
pub(crate) use macro_defined::*;

/// `NotEq` implements the Opcode Operation for `Opcode::NotEq`
///
/// Operation:
/// - Binary `!=` operation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct NotEq;

impl Operation for NotEq {
const NAME: &'static str = "NotEq";
const INSTRUCTION: &'static str = "INST - NotEq";

fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let rhs = context.vm.pop();
let lhs = context.vm.pop();
let value = !lhs.equals(&rhs, context)?;
context.vm.push(value);
Ok(ShouldExit::False)
}
}

/// `StrictEq` implements the Opcode Operation for `Opcode::StrictEq`
///
/// Operation:
/// - Binary `===` operation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct StrictEq;

impl Operation for StrictEq {
const NAME: &'static str = "StrictEq";
const INSTRUCTION: &'static str = "INST - StrictEq";

fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let rhs = context.vm.pop();
let lhs = context.vm.pop();
context.vm.push(lhs.strict_equals(&rhs));
Ok(ShouldExit::False)
}
}

/// `StrictNotEq` implements the Opcode Operation for `Opcode::StrictNotEq`
///
/// Operation:
/// - Binary `!==` operation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct StrictNotEq;

impl Operation for StrictNotEq {
const NAME: &'static str = "StrictNotEq";
const INSTRUCTION: &'static str = "INST - StrictNotEq";

fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let rhs = context.vm.pop();
let lhs = context.vm.pop();
context.vm.push(!lhs.strict_equals(&rhs));
Ok(ShouldExit::False)
}
}

/// `In` implements the Opcode Operation for `Opcode::In`
///
/// Operation:
/// - Binary `in` operation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct In;

impl Operation for In {
const NAME: &'static str = "In";
const INSTRUCTION: &'static str = "INST - In";

fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let rhs = context.vm.pop();
let lhs = context.vm.pop();

if !rhs.is_object() {
return Err(JsNativeError::typ()
.with_message(format!(
"right-hand side of 'in' should be an object, got {}",
rhs.type_of().to_std_string_escaped()
))
.into());
}
let key = lhs.to_property_key(context)?;
let value = context.has_property(&rhs, &key)?;
context.vm.push(value);
Ok(ShouldExit::False)
}
}

/// `InstanceOf` implements the Opcode Operation for `Opcode::InstanceOf`
///
/// Operation:
/// - Binary `instanceof` operation
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct InstanceOf;

impl Operation for InstanceOf {
const NAME: &'static str = "InstanceOf";
const INSTRUCTION: &'static str = "INST - InstanceOf";

fn execute(context: &mut Context) -> JsResult<ShouldExit> {
let target = context.vm.pop();
let v = context.vm.pop();
let value = v.instance_of(&target, context)?;

context.vm.push(value);
Ok(ShouldExit::False)
}
}
Loading

0 comments on commit 9a05b1e

Please sign in to comment.