Browse Source

Implement functions for vm (#1433)

- Separate builtin function from rust JavaScript function
 - Removed BuiltInFunction struct
pull/1612/head
Halid Odat 3 years ago committed by GitHub
parent
commit
aacdd12ed9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      boa/examples/closures.rs
  2. 4
      boa/src/builtins/array/array_iterator.rs
  3. 357
      boa/src/builtins/function/mod.rs
  4. 4
      boa/src/builtins/function/tests.rs
  5. 4
      boa/src/builtins/map/map_iterator.rs
  6. 7
      boa/src/builtins/number/mod.rs
  7. 4
      boa/src/builtins/object/for_in_iterator.rs
  8. 4
      boa/src/builtins/regexp/regexp_string_iterator.rs
  9. 3
      boa/src/builtins/set/set_iterator.rs
  10. 6
      boa/src/builtins/string/string_iterator.rs
  11. 163
      boa/src/bytecompiler.rs
  12. 15
      boa/src/class.rs
  13. 59
      boa/src/context.rs
  14. 376
      boa/src/object/function.rs
  15. 78
      boa/src/object/gcobject.rs
  16. 51
      boa/src/object/mod.rs
  17. 2
      boa/src/object/operations.rs
  18. 5
      boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs
  19. 5
      boa/src/syntax/ast/node/declaration/function_decl/mod.rs
  20. 5
      boa/src/syntax/ast/node/declaration/function_expr/mod.rs
  21. 426
      boa/src/vm/code_block.rs
  22. 559
      boa/src/vm/mod.rs
  23. 20
      boa/src/vm/opcode.rs

2
boa/examples/closures.rs

@ -60,7 +60,7 @@ fn main() -> Result<(), JsValue> {
// captures.
let js_function = FunctionBuilder::closure_with_captures(
&mut context,
|_, _, context, captures| {
|_, _, captures, context| {
println!("Called `createMessage`");
// We obtain the `name` property of `captures.object`
let name = captures.object.get("name", context)?;

4
boa/src/builtins/array/array_iterator.rs

@ -1,7 +1,7 @@
use crate::{
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue},
builtins::{iterable::create_iter_result_object, Array, JsValue},
gc::{Finalize, Trace},
object::{JsObject, ObjectData},
object::{function::make_builtin_fn, JsObject, ObjectData},
property::{PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult,

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

@ -12,368 +12,21 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
use crate::{
builtins::{Array, BuiltIn},
builtins::BuiltIn,
context::StandardObjects,
environment::lexical_environment::Environment,
gc::{empty_trace, Finalize, Trace},
object::{
internal_methods::get_prototype_from_constructor, ConstructorBuilder, FunctionBuilder,
JsObject, NativeObject, Object, ObjectData,
function::Function, internal_methods::get_prototype_from_constructor, ConstructorBuilder,
FunctionBuilder, ObjectData,
},
property::{Attribute, PropertyDescriptor},
syntax::ast::node::{FormalParameter, RcStatementList},
property::Attribute,
BoaProfiler, Context, JsResult, JsValue,
};
use bitflags::bitflags;
use dyn_clone::DynClone;
use sealed::Sealed;
use std::fmt::{self, Debug};
use std::ops::{Deref, DerefMut};
use super::JsArgs;
#[cfg(test)]
mod tests;
// Allows restricting closures to only `Copy` ones.
// Used the sealed pattern to disallow external implementations
// of `DynCopy`.
mod sealed {
pub trait Sealed {}
impl<T: Copy> Sealed for T {}
}
pub trait DynCopy: Sealed {}
impl<T: Copy> DynCopy for T {}
/// Type representing a native built-in function a.k.a. function pointer.
///
/// Native functions need to have this signature in order to
/// be callable from Javascript.
pub type NativeFunction = fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>;
/// Trait representing a native built-in closure.
///
/// Closures need to have this signature in order to
/// be callable from Javascript, but most of the time the compiler
/// is smart enough to correctly infer the types.
pub trait ClosureFunction:
Fn(&JsValue, &[JsValue], &mut Context, Captures) -> JsResult<JsValue> + DynCopy + DynClone + 'static
{
}
// The `Copy` bound automatically infers `DynCopy` and `DynClone`
impl<T> ClosureFunction for T where
T: Fn(&JsValue, &[JsValue], &mut Context, Captures) -> JsResult<JsValue> + Copy + 'static
{
}
// Allows cloning Box<dyn ClosureFunction>
dyn_clone::clone_trait_object!(ClosureFunction);
#[derive(Clone, Copy, Finalize)]
pub struct BuiltInFunction(pub(crate) NativeFunction);
unsafe impl Trace for BuiltInFunction {
empty_trace!();
}
impl From<NativeFunction> for BuiltInFunction {
fn from(function: NativeFunction) -> Self {
Self(function)
}
}
impl Debug for BuiltInFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("[native]")
}
}
bitflags! {
#[derive(Finalize, Default)]
pub struct FunctionFlags: u8 {
const CONSTRUCTABLE = 0b0000_0010;
const LEXICAL_THIS_MODE = 0b0000_0100;
}
}
impl FunctionFlags {
#[inline]
pub(crate) fn is_constructable(&self) -> bool {
self.contains(Self::CONSTRUCTABLE)
}
#[inline]
pub(crate) fn is_lexical_this_mode(&self) -> bool {
self.contains(Self::LEXICAL_THIS_MODE)
}
}
unsafe impl Trace for FunctionFlags {
empty_trace!();
}
// We don't use a standalone `NativeObject` for `Captures` because it doesn't
// guarantee that the internal type implements `Clone`.
// This private trait guarantees that the internal type passed to `Captures`
// implements `Clone`, and `DynClone` allows us to implement `Clone` for
// `Box<dyn CapturesObject>`.
trait CapturesObject: NativeObject + DynClone {}
impl<T: NativeObject + Clone> CapturesObject for T {}
dyn_clone::clone_trait_object!(CapturesObject);
/// Wrapper for `Box<dyn NativeObject + Clone>` that allows passing additional
/// captures through a `Copy` closure.
///
/// Any type implementing `Trace + Any + Debug + Clone`
/// can be used as a capture context, so you can pass e.g. a String,
/// a tuple or even a full struct.
///
/// You can downcast to any type and handle the fail case as you like
/// with `downcast_ref` and `downcast_mut`, or you can use `try_downcast_ref`
/// and `try_downcast_mut` to automatically throw a `TypeError` if the downcast
/// fails.
#[derive(Debug, Clone, Trace, Finalize)]
pub struct Captures(Box<dyn CapturesObject>);
impl Captures {
/// Creates a new capture context.
pub(crate) fn new<T>(captures: T) -> Self
where
T: NativeObject + Clone,
{
Self(Box::new(captures))
}
/// Downcasts `Captures` to the specified type, returning a reference to the
/// downcasted type if successful or `None` otherwise.
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: NativeObject + Clone,
{
self.0.deref().as_any().downcast_ref::<T>()
}
/// Mutably downcasts `Captures` to the specified type, returning a
/// mutable reference to the downcasted type if successful or `None` otherwise.
pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
where
T: NativeObject + Clone,
{
self.0.deref_mut().as_mut_any().downcast_mut::<T>()
}
/// Downcasts `Captures` to the specified type, returning a reference to the
/// downcasted type if successful or a `TypeError` otherwise.
pub fn try_downcast_ref<T>(&self, context: &mut Context) -> JsResult<&T>
where
T: NativeObject + Clone,
{
self.0
.deref()
.as_any()
.downcast_ref::<T>()
.ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type"))
}
/// Downcasts `Captures` to the specified type, returning a reference to the
/// downcasted type if successful or a `TypeError` otherwise.
pub fn try_downcast_mut<T>(&mut self, context: &mut Context) -> JsResult<&mut T>
where
T: NativeObject + Clone,
{
self.0
.deref_mut()
.as_mut_any()
.downcast_mut::<T>()
.ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type"))
}
}
/// Boa representation of a Function Object.
///
/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node)
///
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects>
#[derive(Clone, Trace, Finalize)]
pub enum Function {
Native {
function: BuiltInFunction,
constructable: bool,
},
Closure {
#[unsafe_ignore_trace]
function: Box<dyn ClosureFunction>,
constructable: bool,
captures: Captures,
},
Ordinary {
flags: FunctionFlags,
body: RcStatementList,
params: Box<[FormalParameter]>,
environment: Environment,
},
}
impl Debug for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Function {{ ... }}")
}
}
impl Function {
// Adds the final rest parameters to the Environment as an array
pub(crate) fn add_rest_param(
&self,
param: &FormalParameter,
index: usize,
args_list: &[JsValue],
context: &mut Context,
local_env: &Environment,
) {
// Create array of values
let array = Array::new_array(context);
Array::add_to_array_object(&array, args_list.get(index..).unwrap_or_default(), context)
.unwrap();
// Create binding
local_env
// Function parameters can share names in JavaScript...
.create_mutable_binding(param.name(), false, true, context)
.expect("Failed to create binding for rest param");
// Set Binding to value
local_env
.initialize_binding(param.name(), array, context)
.expect("Failed to initialize rest param");
}
// Adds an argument to the environment
pub(crate) fn add_arguments_to_environment(
&self,
param: &FormalParameter,
value: JsValue,
local_env: &Environment,
context: &mut Context,
) {
// Create binding
local_env
.create_mutable_binding(param.name(), false, true, context)
.expect("Failed to create binding");
// Set Binding to value
local_env
.initialize_binding(param.name(), value, context)
.expect("Failed to intialize binding");
}
/// Returns true if the function object is constructable.
pub fn is_constructable(&self) -> bool {
match self {
Self::Native { constructable, .. } => *constructable,
Self::Closure { constructable, .. } => *constructable,
Self::Ordinary { flags, .. } => flags.is_constructable(),
}
}
}
/// Arguments.
///
/// <https://tc39.es/ecma262/#sec-createunmappedargumentsobject>
pub fn create_unmapped_arguments_object(
arguments_list: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let len = arguments_list.len();
let obj = JsObject::new(Object::default());
// Set length
let length = PropertyDescriptor::builder()
.value(len)
.writable(true)
.enumerable(false)
.configurable(true);
// Define length as a property
crate::object::internal_methods::ordinary_define_own_property(
&obj,
"length".into(),
length.into(),
context,
)?;
let mut index: usize = 0;
while index < len {
let val = arguments_list.get(index).expect("Could not get argument");
let prop = PropertyDescriptor::builder()
.value(val.clone())
.writable(true)
.enumerable(true)
.configurable(true);
obj.insert(index, prop);
index += 1;
}
Ok(JsValue::new(obj))
}
/// Creates a new member function of a `Object` or `prototype`.
///
/// A function registered using this macro can then be called from Javascript using:
///
/// parent.name()
///
/// See the javascript 'Number.toString()' as an example.
///
/// # Arguments
/// function: The function to register as a built in function.
/// name: The name of the function (how it will be called but without the ()).
/// parent: The object to register the function on, if the global object is used then the function is instead called as name()
/// without requiring the parent, see parseInt() as an example.
/// length: As described at <https://tc39.es/ecma262/#sec-function-instances-length>, The value of the "length" property is an integer that
/// indicates the typical number of arguments expected by the function. However, the language permits the function to be invoked with
/// some other number of arguments.
///
/// If no length is provided, the length will be set to 0.
pub fn make_builtin_fn<N>(
function: NativeFunction,
name: N,
parent: &JsObject,
length: usize,
interpreter: &Context,
) where
N: Into<String>,
{
let name = name.into();
let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init");
let mut function = Object::function(
Function::Native {
function: function.into(),
constructable: false,
},
interpreter
.standard_objects()
.function_object()
.prototype()
.into(),
);
let attribute = PropertyDescriptor::builder()
.writable(false)
.enumerable(false)
.configurable(true);
function.insert_property("length", attribute.clone().value(length));
function.insert_property("name", attribute.value(name.as_str()));
parent.clone().insert_property(
name,
PropertyDescriptor::builder()
.value(function)
.writable(true)
.enumerable(false)
.configurable(true),
);
}
#[derive(Debug, Clone, Copy)]
pub struct BuiltInFunctionObject;
@ -394,7 +47,7 @@ impl BuiltInFunctionObject {
.set_prototype_instance(prototype.into());
this.set_data(ObjectData::function(Function::Native {
function: BuiltInFunction(|_, _, _| Ok(JsValue::undefined())),
function: |_, _, _| Ok(JsValue::undefined()),
constructable: true,
}));
Ok(this)

4
boa/src/builtins/function/tests.rs

@ -238,7 +238,7 @@ fn closure_capture_clone() {
let func = FunctionBuilder::closure_with_captures(
&mut context,
|_, _, context, captures| {
|_, _, captures, context| {
let (string, object) = &captures;
let hw = JsString::concat(
@ -251,7 +251,7 @@ fn closure_capture_clone() {
);
Ok(hw.into())
},
(string.clone(), object.clone()),
(string, object),
)
.name("closure")
.build();

4
boa/src/builtins/map/map_iterator.rs

@ -1,6 +1,6 @@
use crate::{
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, Array, JsValue},
object::{JsObject, ObjectData},
builtins::{iterable::create_iter_result_object, Array, JsValue},
object::{function::make_builtin_fn, JsObject, ObjectData},
property::{PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult,

7
boa/src/builtins/number/mod.rs

@ -14,11 +14,14 @@
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number
use super::string::is_trimmable_whitespace;
use super::{function::make_builtin_fn, JsArgs};
use super::JsArgs;
use crate::context::StandardObjects;
use crate::{
builtins::BuiltIn,
object::{internal_methods::get_prototype_from_constructor, ConstructorBuilder, ObjectData},
object::{
function::make_builtin_fn, internal_methods::get_prototype_from_constructor,
ConstructorBuilder, ObjectData,
},
property::Attribute,
value::{AbstractRelation, IntegerOrInfinity, JsValue},
BoaProfiler, Context, JsResult,

4
boa/src/builtins/object/for_in_iterator.rs

@ -1,7 +1,7 @@
use crate::{
builtins::{function::make_builtin_fn, iterable::create_iter_result_object},
builtins::iterable::create_iter_result_object,
gc::{Finalize, Trace},
object::{JsObject, ObjectData},
object::{function::make_builtin_fn, JsObject, ObjectData},
property::PropertyDescriptor,
property::PropertyKey,
symbol::WellKnownSymbols,

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

@ -12,9 +12,9 @@
use regexp::{advance_string_index, RegExp};
use crate::{
builtins::{function::make_builtin_fn, iterable::create_iter_result_object, regexp},
builtins::{iterable::create_iter_result_object, regexp},
gc::{Finalize, Trace},
object::{JsObject, ObjectData},
object::{function::make_builtin_fn, JsObject, ObjectData},
property::PropertyDescriptor,
symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult, JsString, JsValue,

3
boa/src/builtins/set/set_iterator.rs

@ -1,9 +1,8 @@
use crate::{
builtins::function::make_builtin_fn,
builtins::iterable::create_iter_result_object,
builtins::Array,
builtins::JsValue,
object::{JsObject, ObjectData},
object::{function::make_builtin_fn, JsObject, ObjectData},
property::{PropertyDescriptor, PropertyNameKind},
symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult,

6
boa/src/builtins/string/string_iterator.rs

@ -1,9 +1,7 @@
use crate::{
builtins::{
function::make_builtin_fn, iterable::create_iter_result_object, string::code_point_at,
},
builtins::{iterable::create_iter_result_object, string::code_point_at},
gc::{Finalize, Trace},
object::{JsObject, ObjectData},
object::{function::make_builtin_fn, JsObject, ObjectData},
property::PropertyDescriptor,
symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult, JsValue,

163
boa/src/bytecompiler.rs

@ -1,6 +1,9 @@
use gc::Gc;
use crate::{
object::function::ThisMode,
syntax::ast::{
node::{Declaration, GetConstField, GetField, Identifier, StatementList},
node::{Declaration, GetConstField, GetField, StatementList},
op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp},
Const, Node,
},
@ -31,7 +34,7 @@ struct JumpControlInfo {
#[derive(Debug, Clone, Copy)]
enum Access<'a> {
Variable { name: &'a Identifier },
Variable { index: u32 },
ByName { node: &'a GetConstField },
ByValue { node: &'a GetField },
This,
@ -42,13 +45,9 @@ pub struct ByteCompiler {
code_block: CodeBlock,
literals_map: HashMap<Literal, u32>,
names_map: HashMap<JsString, u32>,
functions_map: HashMap<JsString, u32>,
jump_info: Vec<JumpControlInfo>,
}
impl Default for ByteCompiler {
fn default() -> Self {
Self::new()
}
top_level: bool,
}
impl ByteCompiler {
@ -56,12 +55,14 @@ impl ByteCompiler {
const DUMMY_ADDRESS: u32 = u32::MAX;
#[inline]
pub fn new() -> Self {
pub fn new(name: JsString, strict: bool) -> Self {
Self {
code_block: CodeBlock::new(),
code_block: CodeBlock::new(name, 0, strict, false),
literals_map: HashMap::new(),
names_map: HashMap::new(),
functions_map: HashMap::new(),
jump_info: Vec::new(),
top_level: true,
}
}
@ -89,8 +90,8 @@ impl ByteCompiler {
}
let name = JsString::new(name);
let index = self.code_block.names.len() as u32;
self.code_block.names.push(name.clone());
let index = self.code_block.variables.len() as u32;
self.code_block.variables.push(name.clone());
self.names_map.insert(name, index);
index
}
@ -267,7 +268,10 @@ impl ByteCompiler {
#[inline]
fn compile_access<'a>(&mut self, node: &'a Node) -> Access<'a> {
match node {
Node::Identifier(name) => Access::Variable { name },
Node::Identifier(name) => {
let index = self.get_or_insert_name(name.as_ref());
Access::Variable { index }
}
Node::GetConstField(node) => Access::ByName { node },
Node::GetField(node) => Access::ByValue { node },
Node::This => Access::This,
@ -278,9 +282,8 @@ impl ByteCompiler {
#[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::Variable { index: name } => {
self.emit(Opcode::GetName, &[name]);
}
Access::ByName { node } => {
let index = self.get_or_insert_name(node.field());
@ -313,8 +316,7 @@ impl ByteCompiler {
}
match access {
Access::Variable { name } => {
let index = self.get_or_insert_name(name.as_ref());
Access::Variable { index } => {
self.emit(Opcode::SetName, &[index]);
}
Access::ByName { node } => {
@ -532,7 +534,8 @@ impl ByteCompiler {
}
}
Node::Identifier(name) => {
let access = Access::Variable { name };
let index = self.get_or_insert_name(name.as_ref());
let access = Access::Variable { index };
self.access_get(access, use_expr);
}
Node::Assign(assign) => {
@ -579,6 +582,37 @@ impl ByteCompiler {
Node::This => {
self.access_get(Access::This, use_expr);
}
Node::FunctionExpr(_function) => self.function(expr, use_expr),
Node::ArrowFunctionDecl(_function) => self.function(expr, use_expr),
Node::Call(call) => {
for arg in call.args().iter().rev() {
self.compile_expr(arg, true);
}
match call.expr() {
Node::GetConstField(field) => {
self.compile_expr(field.obj(), true);
self.emit(Opcode::Dup, &[]);
let index = self.get_or_insert_name(field.field());
self.emit(Opcode::GetPropertyByName, &[index]);
}
Node::GetField(field) => {
self.compile_expr(field.obj(), true);
self.emit(Opcode::Dup, &[]);
self.compile_expr(field.field(), true);
self.emit(Opcode::Swap, &[]);
self.emit(Opcode::GetPropertyByValue, &[]);
}
expr => {
self.emit(Opcode::This, &[]);
self.compile_expr(expr, true);
}
}
self.emit(Opcode::Call, &[call.args().len() as u32]);
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
expr => todo!("TODO compile: {}", expr),
}
}
@ -767,11 +801,102 @@ impl ByteCompiler {
self.pop_switch_control_info();
}
Node::FunctionDecl(_function) => self.function(node, false),
Node::Return(ret) => {
if let Some(expr) = ret.expr() {
self.compile_expr(expr, true);
} else {
self.emit(Opcode::PushUndefined, &[]);
}
self.emit(Opcode::Return, &[]);
}
Node::Empty => {}
expr => self.compile_expr(expr, use_expr),
}
}
pub(crate) fn function(&mut self, function: &Node, use_expr: bool) {
#[derive(Debug, Clone, Copy, PartialEq)]
enum FunctionKind {
Declaration,
Expression,
Arrow,
}
let (kind, name, paramaters, body) = match function {
Node::FunctionDecl(function) => (
FunctionKind::Declaration,
Some(function.name()),
function.parameters(),
function.body(),
),
Node::FunctionExpr(function) => (
FunctionKind::Expression,
function.name(),
function.parameters(),
function.body(),
),
Node::ArrowFunctionDecl(function) => (
FunctionKind::Arrow,
None,
function.params(),
function.body(),
),
_ => unreachable!(),
};
let length = paramaters.len() as u32;
let mut code = CodeBlock::new(name.unwrap_or("").into(), length, false, true);
if let FunctionKind::Arrow = kind {
code.constructable = false;
code.this_mode = ThisMode::Lexical;
}
let mut compiler = ByteCompiler {
code_block: code,
literals_map: HashMap::new(),
names_map: HashMap::new(),
functions_map: HashMap::new(),
jump_info: Vec::new(),
top_level: false,
};
for node in body.items() {
compiler.compile_stmt(node, false);
}
compiler.code_block.params = paramaters.to_owned().into_boxed_slice();
compiler.emit(Opcode::PushUndefined, &[]);
compiler.emit(Opcode::Return, &[]);
let code = Gc::new(compiler.finish());
let index = self.code_block.functions.len() as u32;
self.code_block.functions.push(code);
self.emit(Opcode::GetFunction, &[index]);
match kind {
FunctionKind::Declaration => {
let index = self.get_or_insert_name(name.unwrap());
let access = Access::Variable { index };
self.access_set(access, None, false);
}
FunctionKind::Expression => {
if use_expr {
self.emit(Opcode::Dup, &[]);
}
}
FunctionKind::Arrow => {
if !use_expr {
self.emit(Opcode::Pop, &[]);
}
}
}
}
#[inline]
pub fn finish(self) -> CodeBlock {
self.code_block

15
boa/src/class.rs

@ -62,8 +62,10 @@
//! [class-trait]: ./trait.Class.html
use crate::{
builtins::function::NativeFunction,
object::{ConstructorBuilder, JsObject, NativeObject, ObjectData, PROTOTYPE},
object::{
function::NativeFunctionSignature, ConstructorBuilder, JsObject, NativeObject, ObjectData,
PROTOTYPE,
},
property::{Attribute, PropertyDescriptor, PropertyKey},
Context, JsResult, JsValue,
};
@ -174,7 +176,12 @@ impl<'context> ClassBuilder<'context> {
///
/// It is added to `prototype`.
#[inline]
pub fn method<N>(&mut self, name: N, length: usize, function: NativeFunction) -> &mut Self
pub fn method<N>(
&mut self,
name: N,
length: usize,
function: NativeFunctionSignature,
) -> &mut Self
where
N: AsRef<str>,
{
@ -190,7 +197,7 @@ impl<'context> ClassBuilder<'context> {
&mut self,
name: N,
length: usize,
function: NativeFunction,
function: NativeFunctionSignature,
) -> &mut Self
where
N: AsRef<str>,

59
boa/src/context.rs

@ -1,14 +1,13 @@
//! Javascript context.
use crate::{
builtins::{
self,
function::{Function, FunctionFlags, NativeFunction},
iterable::IteratorPrototypes,
},
builtins::{self, iterable::IteratorPrototypes},
class::{Class, ClassBuilder},
exec::Interpreter,
object::{FunctionBuilder, JsObject, Object, PROTOTYPE},
object::{
function::{Function, NativeFunctionSignature, ThisMode},
FunctionBuilder, JsObject, Object, PROTOTYPE,
},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
syntax::{
@ -279,11 +278,11 @@ pub struct Context {
/// Cached standard objects and their prototypes.
standard_objects: StandardObjects,
/// Whether or not to show trace of instructions being ran
pub trace: bool,
/// Whether or not strict mode is active.
strict: StrictType,
#[cfg(feature = "vm")]
pub(crate) vm: Vm,
}
impl Default for Context {
@ -297,8 +296,14 @@ impl Default for Context {
console: Console::default(),
iterator_prototypes: IteratorPrototypes::default(),
standard_objects: Default::default(),
trace: false,
strict: StrictType::Off,
#[cfg(feature = "vm")]
vm: Vm {
frame: None,
stack: Vec::with_capacity(1024),
trace: false,
stack_size_limit: 1024,
},
};
// Add new builtIns to Context Realm
@ -566,7 +571,8 @@ impl Context {
name: N,
params: P,
mut body: StatementList,
flags: FunctionFlags,
constructable: bool,
this_mode: ThisMode,
) -> JsResult<JsValue>
where
N: Into<JsString>,
@ -587,7 +593,8 @@ impl Context {
let params = params.into();
let params_len = params.len();
let func = Function::Ordinary {
flags,
constructable,
this_mode,
body: RcStatementList::from(body),
params,
environment: self.get_current_environment().clone(),
@ -647,7 +654,7 @@ impl Context {
&mut self,
name: &str,
length: usize,
body: NativeFunction,
body: NativeFunctionSignature,
) -> JsResult<()> {
let function = FunctionBuilder::native(self, body)
.name(name)
@ -876,6 +883,10 @@ impl Context {
#[cfg(feature = "vm")]
#[allow(clippy::unit_arg, clippy::drop_copy)]
pub fn eval<T: AsRef<[u8]>>(&mut self, src: T) -> JsResult<JsValue> {
use gc::Gc;
use crate::vm::CallFrame;
let main_timer = BoaProfiler::global().start_event("Main", "Main");
let src_bytes: &[u8] = src.as_ref();
@ -888,11 +899,24 @@ impl Context {
Err(e) => return self.throw_syntax_error(e),
};
let mut compiler = crate::bytecompiler::ByteCompiler::default();
let mut compiler = crate::bytecompiler::ByteCompiler::new(JsString::new("<main>"), false);
compiler.compile_statement_list(&statement_list, true);
let code_block = compiler.finish();
let mut vm = Vm::new(code_block, self);
let result = vm.run();
let environment = self.get_current_environment().clone();
let fp = self.vm.stack.len();
let global_object = self.global_object().into();
self.vm.push_frame(CallFrame {
prev: None,
code: Gc::new(code_block),
this: global_object,
pc: 0,
fp,
exit_on_return: true,
environment,
});
let result = self.run();
// The main_timer needs to be dropped before the BoaProfiler is.
drop(main_timer);
@ -914,7 +938,8 @@ impl Context {
}
/// Set the value of trace on the context
#[cfg(feature = "vm")]
pub fn set_trace(&mut self, trace: bool) {
self.trace = trace;
self.vm.trace = trace;
}
}

376
boa/src/object/function.rs

@ -0,0 +1,376 @@
//! This module implements the global `Function` object as well as creates Native Functions.
//!
//! Objects wrap `Function`s and expose them via call/construct slots.
//!
//! `The `Function` object is used for matching text with a pattern.
//!
//! More information:
//! - [ECMAScript reference][spec]
//! - [MDN documentation][mdn]
//!
//! [spec]: https://tc39.es/ecma262/#sec-function-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function
use crate::{
environment::lexical_environment::Environment,
gc::{Finalize, Trace},
object::{JsObject, Object},
property::PropertyDescriptor,
syntax::ast::node::{FormalParameter, RcStatementList},
BoaProfiler, Context, JsResult, JsValue,
};
use dyn_clone::DynClone;
use std::{
fmt,
ops::{Deref, DerefMut},
};
use super::NativeObject;
/// Type representing a native built-in function a.k.a. function pointer.
///
/// Native functions need to have this signature in order to
/// be callable from Javascript.
pub type NativeFunctionSignature = fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue>;
// Allows restricting closures to only `Copy` ones.
// Used the sealed pattern to disallow external implementations
// of `DynCopy`.
mod sealed {
pub trait Sealed {}
impl<T: Copy> Sealed for T {}
}
pub trait DynCopy: sealed::Sealed {}
impl<T: Copy> DynCopy for T {}
/// Trait representing a native built-in closure.
///
/// Closures need to have this signature in order to
/// be callable from Javascript, but most of the time the compiler
/// is smart enough to correctly infer the types.
pub trait ClosureFunctionSignature:
Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult<JsValue> + DynCopy + DynClone + 'static
{
}
// The `Copy` bound automatically infers `DynCopy` and `DynClone`
impl<T> ClosureFunctionSignature for T where
T: Fn(&JsValue, &[JsValue], Captures, &mut Context) -> JsResult<JsValue> + Copy + 'static
{
}
// Allows cloning Box<dyn ClosureFunctionSignature>
dyn_clone::clone_trait_object!(ClosureFunctionSignature);
#[derive(Debug, Trace, Finalize, PartialEq, Clone)]
pub enum ThisMode {
Lexical,
Strict,
Global,
}
impl ThisMode {
/// Returns `true` if the this mode is `Lexical`.
pub fn is_lexical(&self) -> bool {
matches!(self, Self::Lexical)
}
/// Returns `true` if the this mode is `Strict`.
pub fn is_strict(&self) -> bool {
matches!(self, Self::Strict)
}
/// Returns `true` if the this mode is `Global`.
pub fn is_global(&self) -> bool {
matches!(self, Self::Global)
}
}
#[derive(Debug, Trace, Finalize, PartialEq, Clone)]
pub enum ConstructorKind {
Base,
Derived,
}
impl ConstructorKind {
/// Returns `true` if the constructor kind is `Base`.
pub fn is_base(&self) -> bool {
matches!(self, Self::Base)
}
/// Returns `true` if the constructor kind is `Derived`.
pub fn is_derived(&self) -> bool {
matches!(self, Self::Derived)
}
}
// We don't use a standalone `NativeObject` for `Captures` because it doesn't
// guarantee that the internal type implements `Clone`.
// This private trait guarantees that the internal type passed to `Captures`
// implements `Clone`, and `DynClone` allows us to implement `Clone` for
// `Box<dyn CapturesObject>`.
trait CapturesObject: NativeObject + DynClone {}
impl<T: NativeObject + Clone> CapturesObject for T {}
dyn_clone::clone_trait_object!(CapturesObject);
/// Wrapper for `Box<dyn NativeObject + Clone>` that allows passing additional
/// captures through a `Copy` closure.
///
/// Any type implementing `Trace + Any + Debug + Clone`
/// can be used as a capture context, so you can pass e.g. a String,
/// a tuple or even a full struct.
///
/// You can downcast to any type and handle the fail case as you like
/// with `downcast_ref` and `downcast_mut`, or you can use `try_downcast_ref`
/// and `try_downcast_mut` to automatically throw a `TypeError` if the downcast
/// fails.
#[derive(Debug, Clone, Trace, Finalize)]
pub struct Captures(Box<dyn CapturesObject>);
impl Captures {
/// Creates a new capture context.
pub(crate) fn new<T>(captures: T) -> Self
where
T: NativeObject + Clone,
{
Self(Box::new(captures))
}
/// Downcasts `Captures` to the specified type, returning a reference to the
/// downcasted type if successful or `None` otherwise.
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: NativeObject + Clone,
{
self.0.deref().as_any().downcast_ref::<T>()
}
/// Mutably downcasts `Captures` to the specified type, returning a
/// mutable reference to the downcasted type if successful or `None` otherwise.
pub fn downcast_mut<T>(&mut self) -> Option<&mut T>
where
T: NativeObject + Clone,
{
self.0.deref_mut().as_mut_any().downcast_mut::<T>()
}
/// Downcasts `Captures` to the specified type, returning a reference to the
/// downcasted type if successful or a `TypeError` otherwise.
pub fn try_downcast_ref<T>(&self, context: &mut Context) -> JsResult<&T>
where
T: NativeObject + Clone,
{
self.0
.deref()
.as_any()
.downcast_ref::<T>()
.ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type"))
}
/// Downcasts `Captures` to the specified type, returning a reference to the
/// downcasted type if successful or a `TypeError` otherwise.
pub fn try_downcast_mut<T>(&mut self, context: &mut Context) -> JsResult<&mut T>
where
T: NativeObject + Clone,
{
self.0
.deref_mut()
.as_mut_any()
.downcast_mut::<T>()
.ok_or_else(|| context.construct_type_error("cannot downcast `Captures` to given type"))
}
}
/// Boa representation of a Function Object.
///
/// FunctionBody is specific to this interpreter, it will either be Rust code or JavaScript code (AST Node)
///
/// <https://tc39.es/ecma262/#sec-ecmascript-function-objects>
#[derive(Clone, Trace, Finalize)]
pub enum Function {
Native {
#[unsafe_ignore_trace]
function: NativeFunctionSignature,
constructable: bool,
},
Closure {
#[unsafe_ignore_trace]
function: Box<dyn ClosureFunctionSignature>,
constructable: bool,
captures: Captures,
},
Ordinary {
constructable: bool,
this_mode: ThisMode,
body: RcStatementList,
params: Box<[FormalParameter]>,
environment: Environment,
},
#[cfg(feature = "vm")]
VmOrdinary {
code: gc::Gc<crate::vm::CodeBlock>,
environment: Environment,
},
}
impl fmt::Debug for Function {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Function {{ ... }}")
}
}
impl Function {
// Adds the final rest parameters to the Environment as an array
#[cfg(not(feature = "vm"))]
pub(crate) fn add_rest_param(
param: &FormalParameter,
index: usize,
args_list: &[JsValue],
context: &mut Context,
local_env: &Environment,
) {
use crate::builtins::Array;
// Create array of values
let array = Array::new_array(context);
Array::add_to_array_object(&array, args_list.get(index..).unwrap_or_default(), context)
.unwrap();
// Create binding
local_env
// Function parameters can share names in JavaScript...
.create_mutable_binding(param.name(), false, true, context)
.expect("Failed to create binding for rest param");
// Set Binding to value
local_env
.initialize_binding(param.name(), array, context)
.expect("Failed to initialize rest param");
}
// Adds an argument to the environment
pub(crate) fn add_arguments_to_environment(
param: &FormalParameter,
value: JsValue,
local_env: &Environment,
context: &mut Context,
) {
// Create binding
local_env
.create_mutable_binding(param.name(), false, true, context)
.expect("Failed to create binding");
// Set Binding to value
local_env
.initialize_binding(param.name(), value, context)
.expect("Failed to intialize binding");
}
/// Returns true if the function object is constructable.
pub fn is_constructable(&self) -> bool {
match self {
Self::Native { constructable, .. } => *constructable,
Self::Closure { constructable, .. } => *constructable,
Self::Ordinary { constructable, .. } => *constructable,
#[cfg(feature = "vm")]
Self::VmOrdinary { code, .. } => code.constructable,
}
}
}
/// Arguments.
///
/// <https://tc39.es/ecma262/#sec-createunmappedargumentsobject>
pub fn create_unmapped_arguments_object(
arguments_list: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
let len = arguments_list.len();
let obj = JsObject::new(Object::default());
// Set length
let length = PropertyDescriptor::builder()
.value(len)
.writable(true)
.enumerable(false)
.configurable(true)
.build();
// Define length as a property
crate::object::internal_methods::ordinary_define_own_property(
&obj,
"length".into(),
length,
context,
)?;
let mut index: usize = 0;
while index < len {
let val = arguments_list.get(index).expect("Could not get argument");
let prop = PropertyDescriptor::builder()
.value(val.clone())
.writable(true)
.enumerable(true)
.configurable(true);
obj.insert(index, prop);
index += 1;
}
Ok(JsValue::new(obj))
}
/// Creates a new member function of a `Object` or `prototype`.
///
/// A function registered using this macro can then be called from Javascript using:
///
/// parent.name()
///
/// See the javascript 'Number.toString()' as an example.
///
/// # Arguments
/// function: The function to register as a built in function.
/// name: The name of the function (how it will be called but without the ()).
/// parent: The object to register the function on, if the global object is used then the function is instead called as name()
/// without requiring the parent, see parseInt() as an example.
/// length: As described at <https://tc39.es/ecma262/#sec-function-instances-length>, The value of the "length" property is an integer that
/// indicates the typical number of arguments expected by the function. However, the language permits the function to be invoked with
/// some other number of arguments.
///
/// If no length is provided, the length will be set to 0.
// TODO: deprecate/remove this.
pub(crate) fn make_builtin_fn<N>(
function: NativeFunctionSignature,
name: N,
parent: &JsObject,
length: usize,
interpreter: &Context,
) where
N: Into<String>,
{
let name = name.into();
let _timer = BoaProfiler::global().start_event(&format!("make_builtin_fn: {}", &name), "init");
let mut function = Object::function(
Function::Native {
function,
constructable: false,
},
interpreter
.standard_objects()
.function_object()
.prototype()
.into(),
);
let attribute = PropertyDescriptor::builder()
.writable(false)
.enumerable(false)
.configurable(true);
function.insert_property("length", attribute.clone().value(length));
function.insert_property("name", attribute.value(name.as_str()));
parent.clone().insert_property(
name,
PropertyDescriptor::builder()
.value(function)
.writable(true)
.enumerable(false)
.configurable(true),
);
}

78
boa/src/object/gcobject.rs

@ -2,22 +2,12 @@
//!
//! The `JsObject` is a garbage collected Object.
use super::{NativeObject, Object, PROTOTYPE};
use super::{NativeObject, Object};
use crate::{
builtins::function::{
create_unmapped_arguments_object, Captures, ClosureFunction, Function, NativeFunction,
},
environment::{
environment_record_trait::EnvironmentRecordTrait,
function_environment_record::{BindingStatus, FunctionEnvironmentRecord},
lexical_environment::Environment,
},
exec::InterpreterState,
object::{ObjectData, ObjectKind},
property::{PropertyDescriptor, PropertyKey},
syntax::ast::node::RcStatementList,
value::PreferredType,
Context, Executable, JsResult, JsValue,
Context, JsResult, JsValue,
};
use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace};
use std::{
@ -28,6 +18,25 @@ use std::{
result::Result as StdResult,
};
#[cfg(not(feature = "vm"))]
use crate::{
environment::{
environment_record_trait::EnvironmentRecordTrait,
function_environment_record::{BindingStatus, FunctionEnvironmentRecord},
lexical_environment::Environment,
},
exec::InterpreterState,
object::{
function::{
create_unmapped_arguments_object, Captures, ClosureFunctionSignature, Function,
NativeFunctionSignature,
},
PROTOTYPE,
},
syntax::ast::node::RcStatementList,
Executable,
};
/// A wrapper type for an immutably borrowed type T.
pub type Ref<'a, T> = GcCellRef<'a, T>;
@ -42,11 +51,12 @@ pub struct JsObject(Gc<GcCell<Object>>);
///
/// This is needed for the call method since we cannot mutate the function itself since we
/// already borrow it so we get the function body clone it then drop the borrow and run the body
#[cfg(not(feature = "vm"))]
enum FunctionBody {
BuiltInFunction(NativeFunction),
BuiltInConstructor(NativeFunction),
BuiltInFunction(NativeFunctionSignature),
BuiltInConstructor(NativeFunctionSignature),
Closure {
function: Box<dyn ClosureFunction>,
function: Box<dyn ClosureFunctionSignature>,
captures: Captures,
},
Ordinary(RcStatementList),
@ -125,6 +135,7 @@ impl JsObject {
/// <https://tc39.es/ecma262/#sec-runtime-semantics-evaluatebody>
/// <https://tc39.es/ecma262/#sec-ordinarycallevaluatebody>
#[track_caller]
#[cfg(not(feature = "vm"))]
pub(super) fn call_construct(
&self,
this_target: &JsValue,
@ -137,10 +148,7 @@ impl JsObject {
let body = if let Some(function) = self.borrow().as_function() {
if construct && !function.is_constructable() {
let name = self
.__get__(&"name".into(), self.clone().into(), context)?
.display()
.to_string();
let name = self.get("name", context)?.display().to_string();
return context.throw_type_error(format!("{} is not a constructor", name));
} else {
match function {
@ -149,9 +157,9 @@ impl JsObject {
constructable,
} => {
if *constructable || construct {
FunctionBody::BuiltInConstructor(function.0)
FunctionBody::BuiltInConstructor(*function)
} else {
FunctionBody::BuiltInFunction(function.0)
FunctionBody::BuiltInFunction(*function)
}
}
Function::Closure {
@ -161,10 +169,11 @@ impl JsObject {
captures: captures.clone(),
},
Function::Ordinary {
constructable: _,
this_mode,
body,
params,
environment,
flags,
} => {
let this = if construct {
// If the prototype of the constructor is not an object, then use the default object
@ -194,14 +203,14 @@ impl JsObject {
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = FunctionEnvironmentRecord::new(
this_function_object.clone(),
if construct || !flags.is_lexical_this_mode() {
if construct || !this_mode.is_lexical() {
Some(this.clone())
} else {
None
},
Some(environment.clone()),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if flags.is_lexical_this_mode() {
if this_mode.is_lexical() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
@ -225,7 +234,7 @@ impl JsObject {
// - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18)
//
// https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
if !flags.is_lexical_this_mode()
if !this_mode.is_lexical()
&& !arguments_in_parameter_names
&& (has_parameter_expressions
|| (!body.lexically_declared_names().contains("arguments")
@ -247,7 +256,7 @@ impl JsObject {
for (i, param) in params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
function.add_rest_param(param, i, args, context, &local_env);
Function::add_rest_param(param, i, args, context, &local_env);
break;
}
@ -260,8 +269,9 @@ impl JsObject {
Some(value) => value,
};
function
.add_arguments_to_environment(param, value, &local_env, context);
Function::add_arguments_to_environment(
param, value, &local_env, context,
);
}
if has_parameter_expressions {
@ -271,14 +281,14 @@ impl JsObject {
// https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
let second_env = FunctionEnvironmentRecord::new(
this_function_object,
if construct || !flags.is_lexical_this_mode() {
if construct || !this_mode.is_lexical() {
Some(this)
} else {
None
},
Some(local_env),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if flags.is_lexical_this_mode() {
if this_mode.is_lexical() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
@ -291,6 +301,10 @@ impl JsObject {
FunctionBody::Ordinary(body.clone())
}
#[cfg(feature = "vm")]
Function::VmOrdinary { .. } => {
todo!("vm call")
}
}
}
} else {
@ -306,7 +320,7 @@ impl JsObject {
}
FunctionBody::BuiltInFunction(function) => function(this_target, args, context),
FunctionBody::Closure { function, captures } => {
(function)(this_target, args, context, captures)
(function)(this_target, args, captures, context)
}
FunctionBody::Ordinary(body) => {
let result = body.run(context);
@ -893,7 +907,7 @@ impl JsObject {
self.borrow().is_constructable()
}
/// Returns true if the GcObject is the global for a Realm
/// Returns true if the JsObject is the global for a Realm
pub fn is_global(&self) -> bool {
matches!(
self.borrow().data,

51
boa/src/object/mod.rs

@ -2,18 +2,14 @@
use crate::{
builtins::{
array::array_iterator::ArrayIterator,
function::{Captures, Function, NativeFunction},
map::map_iterator::MapIterator,
map::ordered_map::OrderedMap,
regexp::regexp_string_iterator::RegExpStringIterator,
set::ordered_set::OrderedSet,
set::set_iterator::SetIterator,
string::string_iterator::StringIterator,
Date, RegExp,
array::array_iterator::ArrayIterator, map::map_iterator::MapIterator,
map::ordered_map::OrderedMap, regexp::regexp_string_iterator::RegExpStringIterator,
set::ordered_set::OrderedSet, set::set_iterator::SetIterator,
string::string_iterator::StringIterator, Date, RegExp,
},
context::StandardConstructor,
gc::{Finalize, Trace},
object::function::{Captures, Function, NativeFunctionSignature},
property::{Attribute, PropertyDescriptor, PropertyKey},
BoaProfiler, Context, JsBigInt, JsResult, JsString, JsSymbol, JsValue,
};
@ -26,6 +22,7 @@ use std::{
#[cfg(test)]
mod tests;
pub mod function;
mod gcobject;
pub(crate) mod internal_methods;
mod operations;
@ -1114,11 +1111,11 @@ pub struct FunctionBuilder<'context> {
impl<'context> FunctionBuilder<'context> {
/// Create a new `FunctionBuilder` for creating a native function.
#[inline]
pub fn native(context: &'context mut Context, function: NativeFunction) -> Self {
pub fn native(context: &'context mut Context, function: NativeFunctionSignature) -> Self {
Self {
context,
function: Some(Function::Native {
function: function.into(),
function,
constructable: false,
}),
name: JsString::default(),
@ -1135,7 +1132,7 @@ impl<'context> FunctionBuilder<'context> {
Self {
context,
function: Some(Function::Closure {
function: Box::new(move |this, args, context, _| function(this, args, context)),
function: Box::new(move |this, args, _, context| function(this, args, context)),
constructable: false,
captures: Captures::new(()),
}),
@ -1157,15 +1154,15 @@ impl<'context> FunctionBuilder<'context> {
captures: C,
) -> Self
where
F: Fn(&JsValue, &[JsValue], &mut Context, &mut C) -> JsResult<JsValue> + Copy + 'static,
F: Fn(&JsValue, &[JsValue], &mut C, &mut Context) -> JsResult<JsValue> + Copy + 'static,
C: NativeObject + Clone,
{
Self {
context,
function: Some(Function::Closure {
function: Box::new(move |this, args, context, mut captures: Captures| {
function: Box::new(move |this, args, mut captures: Captures, context| {
let data = captures.try_downcast_mut::<C>(context)?;
function(this, args, context, data)
function(this, args, data, context)
}),
constructable: false,
captures: Captures::new(captures),
@ -1299,7 +1296,12 @@ impl<'context> ObjectInitializer<'context> {
/// Add a function to the object.
#[inline]
pub fn function<B>(&mut self, function: NativeFunction, binding: B, length: usize) -> &mut Self
pub fn function<B>(
&mut self,
function: NativeFunctionSignature,
binding: B,
length: usize,
) -> &mut Self
where
B: Into<FunctionBinding>,
{
@ -1347,7 +1349,7 @@ impl<'context> ObjectInitializer<'context> {
/// Builder for creating constructors objects, like `Array`.
pub struct ConstructorBuilder<'context> {
context: &'context mut Context,
constructor_function: NativeFunction,
constructor_function: NativeFunctionSignature,
constructor_object: JsObject,
prototype: JsObject,
name: JsString,
@ -1374,7 +1376,7 @@ impl Debug for ConstructorBuilder<'_> {
impl<'context> ConstructorBuilder<'context> {
/// Create a new `ConstructorBuilder`.
#[inline]
pub fn new(context: &'context mut Context, constructor: NativeFunction) -> Self {
pub fn new(context: &'context mut Context, constructor: NativeFunctionSignature) -> Self {
Self {
context,
constructor_function: constructor,
@ -1391,7 +1393,7 @@ impl<'context> ConstructorBuilder<'context> {
#[inline]
pub(crate) fn with_standard_object(
context: &'context mut Context,
constructor: NativeFunction,
constructor: NativeFunctionSignature,
object: StandardConstructor,
) -> Self {
Self {
@ -1409,7 +1411,12 @@ impl<'context> ConstructorBuilder<'context> {
/// Add new method to the constructors prototype.
#[inline]
pub fn method<B>(&mut self, function: NativeFunction, binding: B, length: usize) -> &mut Self
pub fn method<B>(
&mut self,
function: NativeFunctionSignature,
binding: B,
length: usize,
) -> &mut Self
where
B: Into<FunctionBinding>,
{
@ -1435,7 +1442,7 @@ impl<'context> ConstructorBuilder<'context> {
#[inline]
pub fn static_method<B>(
&mut self,
function: NativeFunction,
function: NativeFunctionSignature,
binding: B,
length: usize,
) -> &mut Self
@ -1617,7 +1624,7 @@ impl<'context> ConstructorBuilder<'context> {
pub fn build(&mut self) -> JsObject {
// Create the native function
let function = Function::Native {
function: self.constructor_function.into(),
function: self.constructor_function,
constructable: self.constructable,
};

2
boa/src/object/operations.rs

@ -287,6 +287,7 @@ impl JsObject {
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
#[track_caller]
#[inline]
#[cfg(not(feature = "vm"))]
pub fn call(
&self,
this: &JsValue,
@ -304,6 +305,7 @@ impl JsObject {
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
#[track_caller]
#[inline]
#[cfg(not(feature = "vm"))]
pub fn construct(
&self,
args: &[JsValue],

5
boa/src/syntax/ast/node/declaration/arrow_function_decl/mod.rs

@ -1,7 +1,7 @@
use crate::{
builtins::function::FunctionFlags,
exec::Executable,
gc::{Finalize, Trace},
object::function::ThisMode,
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
Context, JsResult, JsValue,
};
@ -77,7 +77,8 @@ impl Executable for ArrowFunctionDecl {
"",
self.params().to_vec(),
self.body().clone(),
FunctionFlags::LEXICAL_THIS_MODE,
false,
ThisMode::Lexical,
)
}
}

5
boa/src/syntax/ast/node/declaration/function_decl/mod.rs

@ -1,8 +1,8 @@
use crate::{
builtins::function::FunctionFlags,
environment::lexical_environment::VariableScope,
exec::Executable,
gc::{Finalize, Trace},
object::function::ThisMode,
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
BoaProfiler, Context, JsResult, JsValue,
};
@ -92,7 +92,8 @@ impl Executable for FunctionDecl {
self.name(),
self.parameters().to_vec(),
self.body().clone(),
FunctionFlags::CONSTRUCTABLE,
true,
ThisMode::Global,
)?;
if context.has_binding(self.name())? {

5
boa/src/syntax/ast/node/declaration/function_expr/mod.rs

@ -1,7 +1,7 @@
use crate::{
builtins::function::FunctionFlags,
exec::Executable,
gc::{Finalize, Trace},
object::function::ThisMode,
syntax::ast::node::{join_nodes, FormalParameter, Node, StatementList},
Context, JsResult, JsValue,
};
@ -103,7 +103,8 @@ impl Executable for FunctionExpr {
self.name().unwrap_or(""),
self.parameters().to_vec(),
self.body().clone(),
FunctionFlags::CONSTRUCTABLE,
true,
ThisMode::Global,
)?;
Ok(val)

426
boa/src/vm/code_block.rs

@ -1,8 +1,27 @@
use crate::{vm::Opcode, JsString, JsValue};
use crate::{
environment::{
function_environment_record::{BindingStatus, FunctionEnvironmentRecord},
lexical_environment::Environment,
},
gc::{Finalize, Trace},
object::{
function::{
Captures, ClosureFunctionSignature, Function, NativeFunctionSignature, ThisMode,
},
JsObject, Object, PROTOTYPE,
},
property::PropertyDescriptor,
syntax::ast::node::FormalParameter,
vm::Opcode,
Context, JsResult, JsString, JsValue,
};
use gc::Gc;
use std::{convert::TryInto, fmt::Write, mem::size_of};
/// This represents wether an object can be read from [`CodeBlock`] code.
use super::CallFrame;
/// This represents wether a value can be read from [`CodeBlock`] code.
pub unsafe trait Readable {}
unsafe impl Readable for u8 {}
@ -16,8 +35,25 @@ unsafe impl Readable for i64 {}
unsafe impl Readable for f32 {}
unsafe impl Readable for f64 {}
#[derive(Debug)]
#[derive(Debug, Trace, Finalize)]
pub struct CodeBlock {
/// Name of this function
pub(crate) name: JsString,
// The length of this function.
pub(crate) length: u32,
/// Is this function in strict mode.
pub(crate) strict: bool,
/// Is this function constructable.
pub(crate) constructable: bool,
/// [[ThisMode]]
pub(crate) this_mode: ThisMode,
pub(crate) params: Box<[FormalParameter]>,
/// Bytecode
pub(crate) code: Vec<u8>,
@ -25,21 +61,25 @@ pub struct CodeBlock {
pub(crate) literals: Vec<JsValue>,
/// Variables names
pub(crate) names: Vec<JsString>,
}
pub(crate) variables: Vec<JsString>,
impl Default for CodeBlock {
fn default() -> Self {
Self::new()
}
// Functions inside this function
pub(crate) functions: Vec<Gc<CodeBlock>>,
}
impl CodeBlock {
pub fn new() -> Self {
pub fn new(name: JsString, length: u32, strict: bool, constructable: bool) -> Self {
Self {
code: Vec::new(),
literals: Vec::new(),
names: Vec::new(),
variables: Vec::new(),
functions: Vec::new(),
name,
length,
strict,
constructable,
this_mode: ThisMode::Global,
params: Vec::new().into_boxed_slice(),
}
}
@ -55,6 +95,7 @@ impl CodeBlock {
}
/// Read type T from code.
#[track_caller]
pub fn read<T: Readable>(&self, offset: usize) -> T {
assert!(offset + size_of::<T>() - 1 < self.code.len());
@ -96,11 +137,22 @@ impl CodeBlock {
| Opcode::Default
| Opcode::LogicalAnd
| Opcode::LogicalOr
| Opcode::Coalesce => {
| Opcode::Coalesce
| Opcode::Call => {
let result = self.read::<u32>(*pc).to_string();
*pc += size_of::<u32>();
result
}
Opcode::GetFunction => {
let operand = self.read::<u32>(*pc);
*pc += size_of::<u32>();
format!(
"{:04}: '{}' (length: {})",
operand,
self.functions[operand as usize].name,
self.functions[operand as usize].length
)
}
Opcode::DefVar
| Opcode::DefLet
| Opcode::DefConst
@ -111,7 +163,7 @@ impl CodeBlock {
| Opcode::SetPropertyByName => {
let operand = self.read::<u32>(*pc);
*pc += size_of::<u32>();
format!("{:04}: '{}'", operand, self.names[operand as usize])
format!("{:04}: '{}'", operand, self.variables[operand as usize])
}
Opcode::Pop
| Opcode::Dup
@ -159,6 +211,7 @@ impl CodeBlock {
| Opcode::ToBoolean
| Opcode::Throw
| Opcode::This
| Opcode::Return
| Opcode::Nop => String::new(),
}
}
@ -166,7 +219,11 @@ impl CodeBlock {
impl std::fmt::Display for CodeBlock {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Code: \n")?;
write!(
f,
"----------------- name '{}' (length: {}) ------------------",
self.name, self.length
)?;
writeln!(f, " Location Count Opcode Operands")?;
let mut pc = 0;
@ -198,14 +255,351 @@ impl std::fmt::Display for CodeBlock {
f.write_char('\n')?;
f.write_str("Names:\n")?;
if !self.names.is_empty() {
for (i, value) in self.names.iter().enumerate() {
if !self.variables.is_empty() {
for (i, value) in self.variables.iter().enumerate() {
writeln!(f, " {:04}: {}", i, value)?;
}
} else {
writeln!(f, " <empty>")?;
}
f.write_char('\n')?;
f.write_str("Functions:\n")?;
if !self.functions.is_empty() {
for (i, code) in self.functions.iter().enumerate() {
writeln!(
f,
" {:04}: name: '{}' (length: {})",
i, code.name, code.length
)?;
}
} else {
writeln!(f, " <empty>")?;
}
Ok(())
}
}
#[derive(Debug)]
#[allow(missing_copy_implementations)]
pub struct JsVmFunction {
inner: (),
}
impl JsVmFunction {
#[allow(clippy::new_ret_no_self)]
pub fn new(code: Gc<CodeBlock>, environment: Environment, context: &mut Context) -> JsObject {
let function_prototype = context.standard_objects().function_object().prototype();
let prototype = context.construct_object();
let name_property = PropertyDescriptor::builder()
.value(code.name.clone())
.writable(true)
.enumerable(false)
.configurable(true)
.build();
let length_property = PropertyDescriptor::builder()
.value(code.length)
.writable(false)
.enumerable(false)
.configurable(true)
.build();
let function = Function::VmOrdinary { code, environment };
let constructor = JsObject::new(Object::function(function, function_prototype.into()));
let constructor_property = PropertyDescriptor::builder()
.value(constructor.clone())
.writable(true)
.enumerable(false)
.configurable(true)
.build();
prototype
.define_property_or_throw("constructor", constructor_property, context)
.unwrap();
let prototype_property = PropertyDescriptor::builder()
.value(prototype)
.writable(false)
.enumerable(false)
.configurable(true)
.build();
constructor
.define_property_or_throw("prototype", prototype_property, context)
.unwrap();
constructor
.define_property_or_throw("name", name_property, context)
.unwrap();
constructor
.define_property_or_throw("length", length_property, context)
.unwrap();
constructor
}
}
pub(crate) enum FunctionBody {
Ordinary {
code: Gc<CodeBlock>,
environment: Environment,
},
Native {
function: NativeFunctionSignature,
},
Closure {
function: Box<dyn ClosureFunctionSignature>,
captures: Captures,
},
}
impl JsObject {
pub(crate) fn call_internal(
&self,
this: &JsValue,
args: &[JsValue],
context: &mut Context,
exit_on_return: bool,
) -> JsResult<JsValue> {
let this_function_object = self.clone();
// let mut has_parameter_expressions = false;
if !self.is_callable() {
return context.throw_type_error("not a callable function");
}
let body = {
let object = self.borrow();
let function = object.as_function().unwrap();
match function {
Function::Native { function, .. } => FunctionBody::Native {
function: *function,
},
Function::Closure {
function, captures, ..
} => FunctionBody::Closure {
function: function.clone(),
captures: captures.clone(),
},
Function::VmOrdinary { code, environment } => FunctionBody::Ordinary {
code: code.clone(),
environment: environment.clone(),
},
Function::Ordinary { .. } => unreachable!(),
}
};
match body {
FunctionBody::Native { function } => function(this, args, context),
FunctionBody::Closure { function, captures } => {
(function)(this, args, captures, context)
}
FunctionBody::Ordinary { code, environment } => {
let lexical_this_mode = code.this_mode == ThisMode::Lexical;
// Create a new Function environment whose parent is set to the scope of the function declaration (self.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = FunctionEnvironmentRecord::new(
this_function_object,
if !lexical_this_mode {
Some(this.clone())
} else {
None
},
Some(environment.clone()),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if lexical_this_mode {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
JsValue::undefined(),
context,
)?;
// Turn local_env into Environment so it can be cloned
let local_env: Environment = local_env.into();
// Push the environment first so that it will be used by default parameters
context.push_environment(local_env.clone());
// Add argument bindings to the function environment
for (i, param) in code.params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
todo!("Rest parameter");
}
let value = match args.get(i).cloned() {
None => JsValue::undefined(),
Some(value) => value,
};
Function::add_arguments_to_environment(param, value, &local_env, context);
}
context.vm.push_frame(CallFrame {
prev: None,
code,
this: this.clone(),
pc: 0,
fp: context.vm.stack.len(),
exit_on_return,
environment: local_env,
});
let result = context.run();
context.pop_environment();
result
}
}
}
pub fn call(
&self,
this: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
self.call_internal(this, args, context, true)
}
pub(crate) fn construct_internal(
&self,
args: &[JsValue],
this_target: &JsValue,
context: &mut Context,
exit_on_return: bool,
) -> JsResult<JsValue> {
let this_function_object = self.clone();
// let mut has_parameter_expressions = false;
if !self.is_constructable() {
return context.throw_type_error("not a constructable function");
}
let body = {
let object = self.borrow();
let function = object.as_function().unwrap();
match function {
Function::Native { function, .. } => FunctionBody::Native {
function: *function,
},
Function::Closure {
function, captures, ..
} => FunctionBody::Closure {
function: function.clone(),
captures: captures.clone(),
},
Function::VmOrdinary { code, environment } => FunctionBody::Ordinary {
code: code.clone(),
environment: environment.clone(),
},
Function::Ordinary { .. } => unreachable!(),
}
};
match body {
FunctionBody::Native { function, .. } => function(this_target, args, context),
FunctionBody::Closure { function, captures } => {
(function)(this_target, args, captures, context)
}
FunctionBody::Ordinary { code, environment } => {
let this = {
// If the prototype of the constructor is not an object, then use the default object
// prototype as prototype for the new object
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
let proto = this_target.as_object().unwrap().__get__(
&PROTOTYPE.into(),
this_target.clone(),
context,
)?;
let proto = if proto.is_object() {
proto
} else {
context
.standard_objects()
.object_object()
.prototype()
.into()
};
JsValue::from(Object::create(proto))
};
let lexical_this_mode = code.this_mode == ThisMode::Lexical;
// Create a new Function environment whose parent is set to the scope of the function declaration (self.environment)
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
let local_env = FunctionEnvironmentRecord::new(
this_function_object,
Some(this.clone()),
Some(environment),
// Arrow functions do not have a this binding https://tc39.es/ecma262/#sec-function-environment-records
if lexical_this_mode {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
JsValue::undefined(),
context,
)?;
// Turn local_env into Environment so it can be cloned
let local_env: Environment = local_env.into();
// Push the environment first so that it will be used by default parameters
context.push_environment(local_env.clone());
// Add argument bindings to the function environment
for (i, param) in code.params.iter().enumerate() {
// Rest Parameters
if param.is_rest_param() {
todo!("Rest parameter");
}
let value = match args.get(i).cloned() {
None => JsValue::undefined(),
Some(value) => value,
};
Function::add_arguments_to_environment(param, value, &local_env, context);
}
context.vm.push_frame(CallFrame {
prev: None,
code,
this,
pc: 0,
fp: context.vm.stack.len(),
exit_on_return,
environment: local_env,
});
let _result = context.run();
context.pop_environment();
context.get_this_binding()
}
}
}
pub fn construct(
&self,
args: &[JsValue],
this_target: &JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
self.construct_internal(args, this_target, context, true)
}
}

559
boa/src/vm/mod.rs

@ -2,6 +2,7 @@
//! 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::Environment;
use crate::{
builtins::Array, environment::lexical_environment::VariableScope, symbol::WellKnownSymbols,
BoaProfiler, Context, JsResult, JsValue,
@ -11,42 +12,41 @@ mod code_block;
mod opcode;
pub use code_block::CodeBlock;
pub use code_block::JsVmFunction;
use gc::Gc;
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> {
context: &'a mut Context,
pc: usize,
code: CodeBlock,
stack: Vec<JsValue>,
stack_pointer: usize,
is_trace: bool,
}
#[cfg(test)]
mod tests;
impl<'a> Vm<'a> {
pub fn new(code: CodeBlock, context: &'a mut Context) -> Self {
let trace = context.trace;
Self {
context,
pc: 0,
code,
stack: Vec::with_capacity(128),
stack_pointer: 0,
is_trace: trace,
}
}
#[derive(Debug)]
pub struct CallFrame {
pub(crate) prev: Option<Box<Self>>,
pub(crate) code: Gc<CodeBlock>,
pub(crate) pc: usize,
pub(crate) fp: usize,
pub(crate) exit_on_return: bool,
pub(crate) this: JsValue,
pub(crate) environment: Environment,
}
/// Virtual Machine.
#[derive(Debug)]
pub struct Vm {
pub(crate) frame: Option<Box<CallFrame>>,
pub(crate) stack: Vec<JsValue>,
pub(crate) trace: bool,
pub(crate) stack_size_limit: usize,
}
impl Vm {
/// Push a value on the stack.
#[inline]
pub fn push<T>(&mut self, value: T)
pub(crate) fn push<T>(&mut self, value: T)
where
T: Into<JsValue>,
{
@ -60,88 +60,118 @@ impl<'a> Vm<'a> {
/// If there is nothing to pop, then this will panic.
#[inline]
#[track_caller]
pub fn pop(&mut self) -> JsValue {
pub(crate) fn pop(&mut self) -> JsValue {
self.stack.pop().unwrap()
}
fn read<T: Readable>(&mut self) -> T {
let value = self.code.read::<T>(self.pc);
self.pc += size_of::<T>();
#[track_caller]
#[inline]
pub(crate) fn read<T: Readable>(&mut self) -> T {
let value = self.frame().code.read::<T>(self.frame().pc);
self.frame_mut().pc += size_of::<T>();
value
}
fn execute_instruction(&mut self) -> JsResult<()> {
#[inline]
pub(crate) fn frame(&self) -> &CallFrame {
self.frame.as_ref().unwrap()
}
#[inline]
pub(crate) fn frame_mut(&mut self) -> &mut CallFrame {
self.frame.as_mut().unwrap()
}
#[inline]
pub(crate) fn push_frame(&mut self, mut frame: CallFrame) {
let prev = self.frame.take();
frame.prev = prev;
self.frame = Some(Box::new(frame));
}
#[inline]
pub(crate) fn pop_frame(&mut self) -> Option<Box<CallFrame>> {
let mut current = self.frame.take()?;
self.frame = current.prev.take();
Some(current)
}
}
impl Context {
fn execute_instruction(&mut self) -> JsResult<bool> {
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 rhs = self.vm.pop();
let lhs = self.vm.pop();
let value = lhs.$op(&rhs, self)?;
self.vm.push(value)
}};
}
let opcode = self.code.code[self.pc].try_into().unwrap();
self.pc += 1;
let opcode = self.vm.frame().code.code[self.vm.frame().pc]
.try_into()
.unwrap();
self.vm.frame_mut().pc += 1;
match opcode {
Opcode::Nop => {}
Opcode::Pop => {
let _ = self.pop();
let _ = self.vm.pop();
}
Opcode::Dup => {
let value = self.pop();
self.push(value.clone());
self.push(value);
let value = self.vm.pop();
self.vm.push(value.clone());
self.vm.push(value);
}
Opcode::Swap => {
let first = self.pop();
let second = self.pop();
self.push(first);
self.push(second);
}
Opcode::PushUndefined => self.push(JsValue::undefined()),
Opcode::PushNull => self.push(JsValue::null()),
Opcode::PushTrue => self.push(true),
Opcode::PushFalse => self.push(false),
Opcode::PushZero => self.push(0),
Opcode::PushOne => self.push(1),
let first = self.vm.pop();
let second = self.vm.pop();
self.vm.push(first);
self.vm.push(second);
}
Opcode::PushUndefined => self.vm.push(JsValue::undefined()),
Opcode::PushNull => self.vm.push(JsValue::null()),
Opcode::PushTrue => self.vm.push(true),
Opcode::PushFalse => self.vm.push(false),
Opcode::PushZero => self.vm.push(0),
Opcode::PushOne => self.vm.push(1),
Opcode::PushInt8 => {
let value = self.read::<i8>();
self.push(value as i32);
let value = self.vm.read::<i8>();
self.vm.push(value as i32);
}
Opcode::PushInt16 => {
let value = self.read::<i16>();
self.push(value as i32);
let value = self.vm.read::<i16>();
self.vm.push(value as i32);
}
Opcode::PushInt32 => {
let value = self.read::<i32>();
self.push(value);
let value = self.vm.read::<i32>();
self.vm.push(value);
}
Opcode::PushRational => {
let value = self.read::<f64>();
self.push(value);
let value = self.vm.read::<f64>();
self.vm.push(value);
}
Opcode::PushNaN => self.push(JsValue::nan()),
Opcode::PushPositiveInfinity => self.push(JsValue::positive_inifnity()),
Opcode::PushNegativeInfinity => self.push(JsValue::negative_inifnity()),
Opcode::PushNaN => self.vm.push(JsValue::nan()),
Opcode::PushPositiveInfinity => self.vm.push(JsValue::positive_inifnity()),
Opcode::PushNegativeInfinity => self.vm.push(JsValue::negative_inifnity()),
Opcode::PushLiteral => {
let index = self.read::<u32>() as usize;
let value = self.code.literals[index].clone();
self.push(value)
let index = self.vm.read::<u32>() as usize;
let value = self.vm.frame().code.literals[index].clone();
self.vm.push(value)
}
Opcode::PushEmptyObject => self.push(JsValue::new_object(self.context)),
Opcode::PushEmptyObject => self.vm.push(JsValue::new_object(self)),
Opcode::PushNewArray => {
let count = self.read::<u32>();
let count = self.vm.read::<u32>();
let mut elements = Vec::with_capacity(count as usize);
for _ in 0..count {
elements.push(self.pop());
elements.push(self.vm.pop());
}
let array = Array::new_array(self.context);
Array::add_to_array_object(&array, &elements, self.context)?;
self.push(array);
let array = Array::new_array(self);
Array::add_to_array_object(&array, &elements, self)?;
self.vm.push(array);
}
Opcode::Add => bin_op!(add),
Opcode::Sub => bin_op!(sub),
@ -156,289 +186,333 @@ impl<'a> Vm<'a> {
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);
let rhs = self.vm.pop();
let lhs = self.vm.pop();
let value = lhs.equals(&rhs, self)?;
self.vm.push(value);
}
Opcode::NotEq => {
let rhs = self.pop();
let lhs = self.pop();
let value = !lhs.equals(&rhs, self.context)?;
self.push(value);
let rhs = self.vm.pop();
let lhs = self.vm.pop();
let value = !lhs.equals(&rhs, self)?;
self.vm.push(value);
}
Opcode::StrictEq => {
let rhs = self.pop();
let lhs = self.pop();
self.push(lhs.strict_equals(&rhs));
let rhs = self.vm.pop();
let lhs = self.vm.pop();
self.vm.push(lhs.strict_equals(&rhs));
}
Opcode::StrictNotEq => {
let rhs = self.pop();
let lhs = self.pop();
self.push(!lhs.strict_equals(&rhs));
let rhs = self.vm.pop();
let lhs = self.vm.pop();
self.vm.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();
let rhs = self.vm.pop();
let lhs = self.vm.pop();
if !rhs.is_object() {
return Err(self.context.construct_type_error(format!(
return Err(self.construct_type_error(format!(
"right-hand side of 'in' should be an object, got {}",
rhs.type_of()
)));
}
let key = lhs.to_property_key(self.context)?;
let has_property = self.context.has_property(&rhs, &key)?;
self.push(has_property);
let key = lhs.to_property_key(self)?;
let value = self.has_property(&rhs, &key)?;
self.vm.push(value);
}
Opcode::InstanceOf => {
let y = self.pop();
let x = self.pop();
let y = self.vm.pop();
let x = self.vm.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)?
match object.get_method(self, key)? {
Some(instance_of_handler) => {
instance_of_handler.call(&y, &[x], self)?.to_boolean()
}
None if object.is_callable() => object.ordinary_has_instance(self, &x)?,
None => {
return Err(self.context.construct_type_error(
return Err(self.construct_type_error(
"right-hand side of 'instanceof' is not callable",
));
}
}
} else {
return Err(self.context.construct_type_error(format!(
return Err(self.construct_type_error(format!(
"right-hand side of 'instanceof' should be an object, got {}",
y.type_of()
)));
};
self.push(value);
self.vm.push(value);
}
Opcode::Void => {
let _ = self.pop();
self.push(JsValue::undefined());
let _ = self.vm.pop();
self.vm.push(JsValue::undefined());
}
Opcode::TypeOf => {
let value = self.pop();
self.push(value.type_of());
let value = self.vm.pop();
self.vm.push(value.type_of());
}
Opcode::Pos => {
let value = self.pop();
let value = value.to_number(self.context)?;
self.push(value);
let value = self.vm.pop();
let value = value.to_number(self)?;
self.vm.push(value);
}
Opcode::Neg => {
let value = self.pop().neg(self.context)?;
self.push(value);
let value = self.vm.pop().neg(self)?;
self.vm.push(value);
}
Opcode::LogicalNot => {
let value = self.pop();
self.push(!value.to_boolean());
let value = self.vm.pop();
self.vm.push(!value.to_boolean());
}
Opcode::BitNot => {
let target = self.pop();
let num = target.to_number(self.context)?;
let target = self.vm.pop();
let num = target.to_number(self)?;
let value = if num.is_nan() {
-1
} else {
// TODO: this is not spec compliant.
!(num as i32)
};
self.push(value);
self.vm.push(value);
}
Opcode::DefVar => {
let index = self.read::<u32>();
let name = &self.code.names[index as usize];
let index = self.vm.read::<u32>();
let name = self.vm.frame().code.variables[index as usize].clone();
self.context
.create_mutable_binding(name, false, VariableScope::Function)?;
self.create_mutable_binding(name.as_ref(), false, VariableScope::Function)?;
}
Opcode::DefLet => {
let index = self.read::<u32>();
let name = &self.code.names[index as usize];
let index = self.vm.read::<u32>();
let name = self.vm.frame().code.variables[index as usize].clone();
self.context
.create_mutable_binding(name, false, VariableScope::Block)?;
self.create_mutable_binding(name.as_ref(), false, VariableScope::Block)?;
}
Opcode::DefConst => {
let index = self.read::<u32>();
let name = &self.code.names[index as usize];
let index = self.vm.read::<u32>();
let name = self.vm.frame().code.variables[index as usize].clone();
self.context.create_immutable_binding(
name.as_ref(),
false,
VariableScope::Block,
)?;
self.create_immutable_binding(name.as_ref(), false, VariableScope::Block)?;
}
Opcode::InitLexical => {
let index = self.read::<u32>();
let value = self.pop();
let name = &self.code.names[index as usize];
let index = self.vm.read::<u32>();
let value = self.vm.pop();
let name = self.vm.frame().code.variables[index as usize].clone();
self.context.initialize_binding(name, value)?;
self.initialize_binding(&name, value)?;
}
Opcode::GetName => {
let index = self.read::<u32>();
let name = &self.code.names[index as usize];
let index = self.vm.read::<u32>();
let name = self.vm.frame().code.variables[index as usize].clone();
let value = self.context.get_binding_value(name)?;
self.push(value);
let value = self.get_binding_value(&name)?;
self.vm.push(value);
}
Opcode::SetName => {
let index = self.read::<u32>();
let value = self.pop();
let name = &self.code.names[index as usize];
let index = self.vm.read::<u32>();
let value = self.vm.pop();
let name = self.vm.frame().code.variables[index as usize].clone();
if self.context.has_binding(name)? {
if self.has_binding(name.as_ref())? {
// Binding already exists
self.context
.set_mutable_binding(name, value, self.context.strict())?;
self.set_mutable_binding(name.as_ref(), value, self.strict())?;
} else {
self.context
.create_mutable_binding(name, true, VariableScope::Function)?;
self.context.initialize_binding(name, value)?;
self.create_mutable_binding(name.as_ref(), true, VariableScope::Function)?;
self.initialize_binding(name.as_ref(), value)?;
}
}
Opcode::Jump => {
let address = self.read::<u32>();
self.pc = address as usize;
let address = self.vm.read::<u32>();
self.vm.frame_mut().pc = address as usize;
}
Opcode::JumpIfFalse => {
let address = self.read::<u32>();
if !self.pop().to_boolean() {
self.pc = address as usize;
let address = self.vm.read::<u32>();
if !self.vm.pop().to_boolean() {
self.vm.frame_mut().pc = address as usize;
}
}
Opcode::JumpIfTrue => {
let address = self.read::<u32>();
if self.pop().to_boolean() {
self.pc = address as usize;
let address = self.vm.read::<u32>();
if self.vm.pop().to_boolean() {
self.vm.frame_mut().pc = address as usize;
}
}
Opcode::LogicalAnd => {
let exit = self.read::<u32>();
let lhs = self.pop();
let exit = self.vm.read::<u32>();
let lhs = self.vm.pop();
if !lhs.to_boolean() {
self.pc = exit as usize;
self.push(false);
self.vm.frame_mut().pc = exit as usize;
self.vm.push(false);
}
}
Opcode::LogicalOr => {
let exit = self.read::<u32>();
let lhs = self.pop();
let exit = self.vm.read::<u32>();
let lhs = self.vm.pop();
if lhs.to_boolean() {
self.pc = exit as usize;
self.push(true);
self.vm.frame_mut().pc = exit as usize;
self.vm.push(true);
}
}
Opcode::Coalesce => {
let exit = self.read::<u32>();
let lhs = self.pop();
let exit = self.vm.read::<u32>();
let lhs = self.vm.pop();
if !lhs.is_null_or_undefined() {
self.pc = exit as usize;
self.push(lhs);
self.vm.frame_mut().pc = exit as usize;
self.vm.push(lhs);
}
}
Opcode::ToBoolean => {
let value = self.pop();
self.push(value.to_boolean());
let value = self.vm.pop();
self.vm.push(value.to_boolean());
}
Opcode::GetPropertyByName => {
let index = self.read::<u32>();
let index = self.vm.read::<u32>();
let value = self.pop();
let value = self.vm.pop();
let object = if let Some(object) = value.as_object() {
object
} else {
value.to_object(self.context)?
value.to_object(self)?
};
let name = self.code.names[index as usize].clone();
let result = object.get(name, self.context)?;
let name = self.vm.frame().code.variables[index as usize].clone();
let result = object.get(name, self)?;
self.push(result)
self.vm.push(result)
}
Opcode::GetPropertyByValue => {
let value = self.pop();
let key = self.pop();
let value = self.vm.pop();
let key = self.vm.pop();
let object = if let Some(object) = value.as_object() {
object
} else {
value.to_object(self.context)?
value.to_object(self)?
};
let key = key.to_property_key(self.context)?;
let result = object.get(key, self.context)?;
let key = key.to_property_key(self)?;
let result = object.get(key, self)?;
self.push(result)
self.vm.push(result)
}
Opcode::SetPropertyByName => {
let index = self.read::<u32>();
let index = self.vm.read::<u32>();
let object = self.pop();
let value = self.pop();
let object = self.vm.pop();
let value = self.vm.pop();
let object = if let Some(object) = object.as_object() {
object
} else {
object.to_object(self.context)?
object.to_object(self)?
};
let name = self.code.names[index as usize].clone();
let name = self.vm.frame().code.variables[index as usize].clone();
object.set(name, value, true, self.context)?;
object.set(name, value, true, self)?;
}
Opcode::SetPropertyByValue => {
let object = self.pop();
let key = self.pop();
let value = self.pop();
let object = self.vm.pop();
let key = self.vm.pop();
let value = self.vm.pop();
let object = if let Some(object) = object.as_object() {
object
} else {
object.to_object(self.context)?
object.to_object(self)?
};
let key = key.to_property_key(self.context)?;
object.set(key, value, true, self.context)?;
let key = key.to_property_key(self)?;
object.set(key, value, true, self)?;
}
Opcode::Throw => {
let value = self.pop();
let value = self.vm.pop();
return Err(value);
}
Opcode::This => {
let this = self.context.get_this_binding()?;
self.push(this);
let this = self.get_this_binding()?;
self.vm.push(this);
}
Opcode::Case => {
let address = self.read::<u32>();
let cond = self.pop();
let value = self.pop();
let address = self.vm.read::<u32>();
let cond = self.vm.pop();
let value = self.vm.pop();
if !value.strict_equals(&cond) {
self.push(value);
self.vm.push(value);
} else {
self.pc = address as usize;
self.vm.frame_mut().pc = address as usize;
}
}
Opcode::Default => {
let exit = self.read::<u32>();
let _ = self.pop();
self.pc = exit as usize;
let exit = self.vm.read::<u32>();
let _ = self.vm.pop();
self.vm.frame_mut().pc = exit as usize;
}
Opcode::GetFunction => {
let index = self.vm.read::<u32>();
let code = self.vm.frame().code.functions[index as usize].clone();
let environment = self.vm.frame().environment.clone();
let function = JsVmFunction::new(code, environment, self);
self.vm.push(function);
}
Opcode::Call => {
if self.vm.stack_size_limit <= self.vm.stack.len() {
return Err(self.construct_range_error("Maximum call stack size exceeded"));
}
let argc = self.vm.read::<u32>();
let func = self.vm.pop();
let this = self.vm.pop();
let mut args = Vec::with_capacity(argc as usize);
for _ in 0..argc {
args.push(self.vm.pop());
}
let object = match func {
JsValue::Object(ref object) if object.is_callable() => object.clone(),
_ => return Err(self.construct_type_error("not a callable function")),
};
let result = object.call_internal(&this, &args, self, false)?;
self.vm.push(result);
}
Opcode::Return => {
let exit = self.vm.frame().exit_on_return;
let _ = self.vm.pop_frame();
if exit {
return Ok(true);
}
}
}
Ok(())
Ok(false)
}
pub fn run(&mut self) -> JsResult<JsValue> {
/// Unwind the stack.
fn unwind(&mut self) -> bool {
let mut fp = 0;
while let Some(mut frame) = self.vm.frame.take() {
fp = frame.fp;
if frame.exit_on_return {
break;
}
self.vm.frame = frame.prev.take();
}
while self.vm.stack.len() > fp {
let _ = self.vm.pop();
}
true
}
pub(crate) fn run(&mut self) -> JsResult<JsValue> {
let _timer = BoaProfiler::global().start_event("run", "vm");
const COLUMN_WIDTH: usize = 24;
@ -447,8 +521,8 @@ impl<'a> Vm<'a> {
const OPERAND_COLUMN_WIDTH: usize = COLUMN_WIDTH;
const NUMBER_OF_COLUMNS: usize = 4;
if self.is_trace {
println!("{}\n", self.code);
if self.vm.trace {
println!("{}\n", self.vm.frame().code);
println!(
"{:-^width$}",
" Vm Start ",
@ -465,44 +539,77 @@ impl<'a> Vm<'a> {
);
}
self.pc = 0;
while self.pc < self.code.code.len() {
if self.is_trace {
let mut pc = self.pc;
self.vm.frame_mut().pc = 0;
while self.vm.frame().pc < self.vm.frame().code.code.len() {
let result = if self.vm.trace {
let mut pc = self.vm.frame().pc;
let opcode: Opcode = self.vm.frame().code.read::<u8>(pc).try_into().unwrap();
let operands = self.vm.frame().code.instruction_operands(&mut pc);
let instant = Instant::now();
self.execute_instruction()?;
let result = self.execute_instruction();
let duration = instant.elapsed();
let opcode: Opcode = self.code.read::<u8>(pc).try_into().unwrap();
println!(
"{:<time_width$} {:<opcode_width$} {:<operand_width$} {}",
format!("{}μs", duration.as_micros()),
opcode.as_str(),
self.code.instruction_operands(&mut pc),
match self.stack.last() {
operands,
match self.vm.stack.last() {
None => "<empty>".to_string(),
Some(value) => format!("{}", value.display()),
Some(value) => {
if value.is_function() {
"[function]".to_string()
} else if value.is_object() {
"[object]".to_string()
} else {
format!("{}", value.display())
}
}
},
time_width = TIME_COLUMN_WIDTH,
opcode_width = OPCODE_COLUMN_WIDTH,
operand_width = OPERAND_COLUMN_WIDTH,
);
result
} else {
self.execute_instruction()?;
self.execute_instruction()
};
match result {
Ok(should_exit) => {
if should_exit {
let result = self.vm.pop();
return Ok(result);
}
}
Err(e) => {
let should_exit = self.unwind();
if should_exit {
return Err(e);
} else {
self.vm.push(e);
}
}
}
}
if self.is_trace {
if self.vm.trace {
println!("\nStack:");
if !self.stack.is_empty() {
for (i, value) in self.stack.iter().enumerate() {
if !self.vm.stack.is_empty() {
for (i, value) in self.vm.stack.iter().enumerate() {
println!(
"{:04}{:<width$} {}",
i,
"",
value.display(),
if value.is_function() {
"[function]".to_string()
} else if value.is_object() {
"[object]".to_string()
} else {
format!("{}", value.display())
},
width = COLUMN_WIDTH / 2 - 4,
);
}
@ -512,10 +619,10 @@ impl<'a> Vm<'a> {
println!("\n");
}
if self.stack.is_empty() {
if self.vm.stack.is_empty() {
return Ok(JsValue::undefined());
}
Ok(self.pop())
Ok(self.vm.pop())
}
}

20
boa/src/vm/opcode.rs

@ -506,6 +506,23 @@ pub enum Opcode {
/// Stack: `value` **=>**
Default,
/// Get function from the precompiled inner functions.
///
/// Operands: address: `u32`
///
/// Stack: **=>** `func`
GetFunction,
/// Call a function.
///
/// Operands: argc: `u32`
///
/// Stack: `func`, `this`, `arg1`, `arg2`,...`argn` **=>**
Call,
/// Return from a function.
Return,
/// No-operation instruction, does nothing.
///
/// Operands:
@ -596,6 +613,9 @@ impl Opcode {
Opcode::This => "This",
Opcode::Case => "Case",
Opcode::Default => "Default",
Opcode::GetFunction => "GetFunction",
Opcode::Call => "Call",
Opcode::Return => "Return",
Opcode::Nop => "Nop",
}
}

Loading…
Cancel
Save