diff --git a/nova_cli/Cargo.toml b/nova_cli/Cargo.toml index 121d596e..324870ff 100644 --- a/nova_cli/Cargo.toml +++ b/nova_cli/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] clap = { version = "4.3", features = ["derive"] } -oxc_parser = "0.0.6" -oxc_ast = "0.0.6" +oxc_parser = "0.1.0" +oxc_ast = "0.1.0" +oxc_span = "0.1.0" nova_vm = { path = "../nova_vm" } diff --git a/nova_cli/src/main.rs b/nova_cli/src/main.rs index 04ae9b6e..63329b8a 100644 --- a/nova_cli/src/main.rs +++ b/nova_cli/src/main.rs @@ -1,7 +1,6 @@ use clap::{Parser as ClapParser, Subcommand}; -use nova_vm::{heap::Heap, VM}; -use oxc_ast::SourceType; use oxc_parser::Parser; +use oxc_span::SourceType; /// A JavaScript engine #[derive(Debug, ClapParser)] // requires `derive` feature @@ -47,16 +46,16 @@ fn main() -> Result<(), Box> { let parser = Parser::new(&allocator, &file, source_type.with_typescript(false)); let result = parser.parse(); - let mut vm = VM { - source: &file, - pc: 0, - instructions: Vec::new(), - heap: Heap::new(), - }; + // let mut vm = VM { + // source: &file, + // pc: 0, + // instructions: Vec::new(), + // heap: Heap::new(), + // }; - vm.load_program(result.program); - println!("{:?}", vm.instructions); - vm.interpret(); + // vm.load_program(result.program); + // println!("{:?}", vm.instructions); + // vm.interpret(); } } diff --git a/nova_vm/Cargo.toml b/nova_vm/Cargo.toml index bf852d5f..e66e2a7a 100644 --- a/nova_vm/Cargo.toml +++ b/nova_vm/Cargo.toml @@ -8,5 +8,10 @@ edition = "2021" [dependencies] gc = { version = "0.4", features = ["derive"] } wtf8 = "0.1" -oxc_parser = "0.0.6" -oxc_ast = "0.0.6" +oxc_parser = "0.1.0" +oxc_span = "0.1.0" +oxc_ast = "0.1.0" +oxc_allocator = "0.1.0" +oxc_diagnostics = "0.1.0" +num-bigint-dig = "0.8" +small_vec = "0.1" diff --git a/nova_vm/src/builtins.rs b/nova_vm/src/builtins.rs new file mode 100644 index 00000000..a840bcf3 --- /dev/null +++ b/nova_vm/src/builtins.rs @@ -0,0 +1,13 @@ +mod array; +mod builtin_function; +mod ecmascript_function; +mod number; +pub mod ordinary; + +pub use array::ArrayConstructor; +pub use builtin_function::{ + create_builtin_function, todo_builtin, ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs, + ConstructorFn, RegularFn as JsFunction, RegularFn, +}; +pub use ecmascript_function::ECMAScriptFunction; +pub use number::NumberConstructor; diff --git a/nova_vm/src/builtins/array.rs b/nova_vm/src/builtins/array.rs new file mode 100644 index 00000000..46cba49d --- /dev/null +++ b/nova_vm/src/builtins/array.rs @@ -0,0 +1,31 @@ +use super::{ + builtin_function::define_builtin_function, create_builtin_function, ArgumentsList, Behaviour, + Builtin, BuiltinFunctionArgs, +}; +use crate::{ + execution::{Agent, JsResult, Realm}, + types::{Object, Value}, +}; + +pub struct ArrayConstructor; + +impl Builtin for ArrayConstructor { + fn create<'a>(realm: &'a mut Realm<'a, 'a>) -> JsResult { + let object = create_builtin_function( + Behaviour::Regular(Self::behaviour), + BuiltinFunctionArgs::new(1, "Array", realm), + ); + + Ok(object.into_object()) + } +} + +impl ArrayConstructor { + fn behaviour( + agent: &mut Agent, + this_value: Value, + arguments: ArgumentsList, + ) -> JsResult { + todo!(); + } +} diff --git a/nova_vm/src/builtins/builtin_function.rs b/nova_vm/src/builtins/builtin_function.rs new file mode 100644 index 00000000..0abe4468 --- /dev/null +++ b/nova_vm/src/builtins/builtin_function.rs @@ -0,0 +1,140 @@ +use crate::{ + execution::{Agent, Intrinsics, JsResult, Realm}, + heap::{BuiltinObjectIndexes, CreateHeapData, FunctionHeapData, HeapBits, ObjectHeapData}, + types::{Function, Object, PropertyDescriptor, PropertyKey, Value}, +}; + +#[derive(Debug)] +pub struct ArgumentsList<'a>(&'a [Value]); + +impl ArgumentsList<'_> { + #[inline] + pub fn get(&self, index: usize) -> Value { + *self.0.get(index).unwrap_or(&Value::Undefined) + } +} + +pub type RegularFn = fn(&mut Agent, Value, ArgumentsList<'_>) -> JsResult; +pub type ConstructorFn = + fn(&mut Agent, Value, ArgumentsList<'_>, Option) -> JsResult; + +#[derive(Debug, Clone, Copy)] +pub enum Behaviour { + Regular(RegularFn), + Constructor(ConstructorFn), +} + +pub trait Builtin { + fn create<'a>(realm: &'a mut Realm<'a, 'a>) -> JsResult; +} + +#[derive(Debug, Default)] +pub struct BuiltinFunctionArgs<'a, 'ctx, 'host> { + pub length: u32, + pub name: &'a str, + pub realm: Option<&'a mut Realm<'ctx, 'host>>, + pub prototype: Option, + pub prefix: Option, +} + +impl<'a, 'ctx: 'a, 'host: 'ctx> BuiltinFunctionArgs<'a, 'ctx, 'host> { + pub fn new(length: u32, name: &'a str, realm: &'a mut Realm<'ctx, 'host>) -> Self { + Self { + length, + name, + realm: Some(realm), + ..Default::default() + } + } +} + +/// 10.3.3 CreateBuiltinFunction ( behaviour, length, name, additionalInternalSlotsList [ , realm [ , prototype [ , prefix ] ] ] ) +/// https://tc39.es/ecma262/#sec-createbuiltinfunction +pub fn create_builtin_function<'a, 'b: 'a>( + behaviour: Behaviour, + args: BuiltinFunctionArgs<'a, 'b, 'b>, +) -> Function { + // 1. If realm is not present, set realm to the current Realm Record. + let realm = args.realm.unwrap(); // TODO: load record + + // 2. If prototype is not present, set prototype to realm.[[Intrinsics]].[[%Function.prototype%]]. + let prototype = args + .prototype + .unwrap_or_else(Intrinsics::function_prototype); + + // TODO: Steps 3-4 + // 3. Let internalSlotsList be a List containing the names of all the internal slots that 10.3 + // requires for the built-in function object that is about to be created. + // 4. Append to internalSlotsList the elements of additionalInternalSlotsList. + + // 5. Let func be a new built-in function object that, when called, performs the action + // described by behaviour using the provided arguments as the values of the corresponding + // parameters specified by behaviour. The new function object has internal slots whose names + // are the elements of internalSlotsList, and an [[InitialName]] internal slot. + let object = realm.heap.create(ObjectHeapData { + bits: HeapBits::new(), + // 6. Set func.[[Prototype]] to prototype. + prototype: PropertyDescriptor { + value: Some(prototype.into_value()), + ..Default::default() + }, + // 7. Set func.[[Extensible]] to true. + extensible: true, + // 8. Set func.[[Realm]] to realm. + // NOTE: Heap data is implicitly attached to the Realm so I don't think + // this matters. + entries: Vec::new(), + }); + + let initial_name = realm.heap.create(args.name).into_value(); + let func = realm.heap.create(FunctionHeapData { + bits: HeapBits::new(), + object: None, + behaviour, + // TODO: 9. Set func.[[InitialName]] to null. + // NOTE: This is non-standard. + initial_name, + // 10. Perform SetFunctionLength(func, length). + length: args.length as i64, + }); + + // TODO: Steps 11-12 + // 11. If prefix is not present, then + // a. Perform SetFunctionName(func, name). + // 12. Else, + // a. Perform SetFunctionName(func, name, prefix). + + // 13. Return func. + func +} + +pub fn define_builtin_function<'a, 'b>( + object: Object, + name: &'a str, + behaviour: RegularFn, + length: u32, + realm: &'a mut Realm<'b, 'b>, +) -> JsResult<()> { + let function = create_builtin_function( + Behaviour::Regular(behaviour), + BuiltinFunctionArgs::new(length, name, realm), + ); + + Ok(()) +} + +pub fn define_builtin_property( + object: Object, + name: &'static str, + descriptor: PropertyDescriptor, +) -> JsResult<()> { + Ok(()) +} + +pub fn todo_builtin(agent: &mut Agent, _: Value, _: ArgumentsList) -> JsResult { + agent.throw_exception( + crate::execution::agent::ExceptionType::SyntaxError, + "TODO: Builtin not implemented.", + ); + Err(()) +} diff --git a/nova_vm/src/builtins/ecmascript_function.rs b/nova_vm/src/builtins/ecmascript_function.rs new file mode 100644 index 00000000..433edbe8 --- /dev/null +++ b/nova_vm/src/builtins/ecmascript_function.rs @@ -0,0 +1,89 @@ +use std::{cell::RefCell, rc::Rc}; + +use oxc_ast::ast::{FormalParameters, FunctionBody}; + +use crate::{ + execution::{Agent, Environment, JsResult, PrivateEnvironment, Realm, ScriptOrModule}, + types::{Number, Object, PropertyDescriptor, PropertyKey, Value}, +}; + +#[derive(Debug, Clone, Copy)] +pub enum ConstructorKind { + Base, + Derived, +} + +#[derive(Debug, Clone, Copy)] +pub enum ThisMode { + Lexical, + Strict, + Global, +} + +/// 10.2 ECMAScript Function Objects +/// https://tc39.es/ecma262/#sec-ecmascript-function-objects +#[derive(Debug, Clone)] +pub struct ECMAScriptFunction<'ctx, 'host> { + /// [[Environment]] + pub environment: Environment, + + /// [[PrivateEnvironment]] + pub private_environment: Option>>, + + /// [[FormalParameters]] + pub formal_parameters: &'host FormalParameters<'host>, + + /// [[ECMAScriptCode]] + pub ecmascript_code: &'host FunctionBody<'host>, + + /// [[ConstructorKind]] + pub constructor_kind: ConstructorKind, + + /// [[Realm]] + pub realm: Rc>>, + + /// [[ScriptOrModule]] + pub script_or_module: ScriptOrModule<'ctx, 'host>, + + /// [[ThisMode]] + pub this_mode: ThisMode, + + /// [[Strict]] + pub strict: bool, + + /// [[HomeObject]] + pub home_object: Option, + + /// [[SourceText]] + pub source_text: &'host str, + + // TODO: [[Fields]], [[PrivateMethods]], [[ClassFieldInitializerName]] + /// [[IsClassConstructor]] + pub is_class_constructor: bool, +} + +impl Object { + /// 10.2.10 SetFunctionLength ( F, length ) + /// https://tc39.es/ecma262/#sec-setfunctionlength + pub fn set_function_length(self, agent: &mut Agent, length: i64) -> JsResult<()> { + let function = self; + + // TODO: 1. Assert: F is an extensible object that does not have a "length" own property. + + // 2. Perform ! DefinePropertyOrThrow(F, "length", PropertyDescriptor { [[Value]]: ๐”ฝ(length), [[Writable]]: false, [[Enumerable]]: false, [[Configurable]]: true }). + function.define_property_or_throw( + agent, + PropertyKey::try_from(Value::try_from("length").unwrap()).unwrap(), + PropertyDescriptor { + value: Some(Number::try_from(length).unwrap().into_value()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(true), + ..Default::default() + }, + )?; + + // 3. Return unused. + Ok(()) + } +} diff --git a/nova_vm/src/builtins/number.rs b/nova_vm/src/builtins/number.rs new file mode 100644 index 00000000..ea40666e --- /dev/null +++ b/nova_vm/src/builtins/number.rs @@ -0,0 +1,221 @@ +use super::{ + builtin_function::{define_builtin_function, define_builtin_property}, + create_builtin_function, ordinary, todo_builtin, ArgumentsList, Behaviour, Builtin, + BuiltinFunctionArgs, +}; +use crate::{ + execution::{Agent, Intrinsics, JsResult, Realm}, + heap::{BuiltinObjectIndexes, CreateHeapData}, + types::{Number, Object, PropertyDescriptor, PropertyKey, Value}, + SmallInteger, +}; + +pub struct NumberConstructor; + +impl Builtin for NumberConstructor { + fn create<'a>(realm: &'a mut Realm<'a, 'a>) -> JsResult { + let object: Object = create_builtin_function( + Behaviour::Constructor(Self::behaviour), + BuiltinFunctionArgs { + length: 1, + name: "Number", + realm: Some(realm), + prototype: Some(Intrinsics::function_prototype()), + ..Default::default() + }, + ) + .into_object(); + + // 21.1.2.1 Number.EPSILON + // https://tc39.es/ecma262/#sec-number.epsilon + define_builtin_property( + object, + "EPSILON", + PropertyDescriptor { + value: Some(realm.heap.create(f64::EPSILON).into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.2.6 Number.MAX_SAFE_INTEGER + // https://tc39.es/ecma262/#sec-number.max_safe_integer + define_builtin_property( + object, + "MAX_SAFE_INTEGER", + PropertyDescriptor { + value: Some(Number::from(SmallInteger::MAX_NUMBER).into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.2.7 Number.MAX_VALUE + // https://tc39.es/ecma262/#sec-number.max_value + define_builtin_property( + object, + "MAX_VALUE", + PropertyDescriptor { + value: Some(realm.heap.create(f64::MAX).into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.2.8 Number.MIN_SAFE_INTEGER + // https://tc39.es/ecma262/#sec-number.min_safe_integer + define_builtin_property( + object, + "MIN_SAFE_INTEGER", + PropertyDescriptor { + value: Some(Number::from(SmallInteger::MIN_NUMBER).into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.2.8 Number.MIN_VALUE + // https://tc39.es/ecma262/#sec-number.min_value + define_builtin_property( + object, + "MIN_VALUE", + PropertyDescriptor { + value: Some(realm.heap.create(f64::MIN).into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.2.10 Number.NaN + // https://tc39.es/ecma262/#sec-number.nan + define_builtin_property( + object, + "NaN", + PropertyDescriptor { + value: Some(Number::nan().into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.2.11 Number.NEGATIVE_INFINITY + // https://tc39.es/ecma262/#sec-number.negative_infinity + define_builtin_property( + object, + "NEGATIVE_INFINITY", + PropertyDescriptor { + value: Some(Number::neg_inf().into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.2.14 Number.POSITIVE_INFINITY + // https://tc39.es/ecma262/#sec-number.positive_infinity + define_builtin_property( + object, + "POSITIVE_INFINITY", + PropertyDescriptor { + value: Some(Number::pos_inf().into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + define_builtin_function(object, "isFinite", todo_builtin, 1, realm)?; + define_builtin_function(object, "isNaN", todo_builtin, 1, realm)?; + define_builtin_function(object, "isSafeInteger", todo_builtin, 1, realm)?; + define_builtin_function(object, "parseFloat", todo_builtin, 1, realm)?; + define_builtin_function(object, "parseInt", todo_builtin, 2, realm)?; + + // 21.1.2.15 Number.prototype + // https://tc39.es/ecma262/#sec-number.prototype + define_builtin_property( + object, + "prototype", + PropertyDescriptor { + value: Some(Intrinsics::number_prototype().into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.3.1 Number.prototype.constructor + // https://tc39.es/ecma262/#sec-number.prototype.constructor + define_builtin_property( + Intrinsics::number_prototype(), + "constructor", + PropertyDescriptor { + value: Some(object.into_value()), + writable: Some(true), + enumerable: Some(false), + configurable: Some(true), + ..Default::default() + }, + )?; + + Ok(object) + } +} + +impl NumberConstructor { + /// 21.1.1.1 Number ( value ) + /// https://tc39.es/ecma262/#sec-number-constructor-number-value + fn behaviour( + agent: &mut Agent, + this_value: Value, + arguments: ArgumentsList, + new_target: Option, + ) -> JsResult { + let value = arguments.get(0); + + // 1. If value is present, then + let n = if !value.is_undefined() { + // a. Let prim be ? ToNumeric(value). + let prim = value.to_numeric(agent)?; + + // b. If prim is a BigInt, let n be ๐”ฝ(โ„(prim)). + if prim.is_bigint() { + todo!() + } + // c. Otherwise, let n be prim. + else { + prim + } + } + // 2. Else, + else { + // a. Let n be +0๐”ฝ. + Value::from(0) + }; + + // 3. If NewTarget is undefined, return n. + let Some(new_target) = new_target else { + return Ok(n); + }; + + todo!(); + + // 4. Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%Number.prototype%", ยซ [[NumberData]] ยป). + // 5. Set O.[[NumberData]] to n. + // 6. Return O. + } +} diff --git a/nova_vm/src/builtins/ordinary.rs b/nova_vm/src/builtins/ordinary.rs new file mode 100644 index 00000000..38bf1b9a --- /dev/null +++ b/nova_vm/src/builtins/ordinary.rs @@ -0,0 +1,795 @@ +use crate::{ + execution::{Agent, JsResult}, + types::{InternalMethods, Object, PropertyDescriptor, PropertyKey, Value}, +}; + +/// 10.1 Ordinary Object Internal Methods and Internal Slots +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots +pub static METHODS: InternalMethods = InternalMethods { + get_prototype_of, + set_prototype_of, + is_extensible, + prevent_extensions, + get_own_property, + define_own_property, + has_property, + get, + set, + delete, + own_property_keys, + // call: todo!(), + // construct: todo!(), + call: None, + construct: None, +}; + +/// 10.1.1 [[GetPrototypeOf]] ( ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getprototypeof +fn get_prototype_of(agent: &mut Agent, object: Object) -> Option { + // 1. Return OrdinaryGetPrototypeOf(O). + ordinary_get_prototype_of(agent, object) +} + +/// 10.1.1.1 OrdinaryGetPrototypeOf ( O ) +/// https://tc39.es/ecma262/#sec-ordinarygetprototypeof +pub fn ordinary_get_prototype_of(agent: &mut Agent, object: Object) -> Option { + // 1. Return O.[[Prototype]]. + object.prototype(agent) +} + +/// 10.1.2 [[SetPrototypeOf]] ( V ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v +fn set_prototype_of( + agent: &mut Agent, + object: Object, + prototype: Option, +) -> JsResult { + // 1. Return OrdinarySetPrototypeOf(O, V). + return ordinary_set_prototype_of(agent, object, prototype); +} + +/// 10.1.2.1 OrdinarySetPrototypeOf ( O, V ) +/// https://tc39.es/ecma262/#sec-ordinarysetprototypeof +pub fn ordinary_set_prototype_of( + agent: &mut Agent, + object: Object, + prototype: Option, +) -> JsResult { + // 1. Let current be O.[[Prototype]]. + let current = object.prototype(agent); + + // 2. If SameValue(V, current) is true, return true. + match (prototype, current) { + (Some(prototype), Some(current)) + if prototype + .into_value() + .same_value(agent, current.into_value()) => + { + return Ok(true) + } + (None, None) => return Ok(true), + _ => {} + } + + // 3. Let extensible be O.[[Extensible]]. + let extensible = object.extensible(agent); + + // 4. If extensible is false, return false. + if !extensible { + return Ok(false); + } + + // 5. Let p be V. + let mut parent_prototype_outer = prototype; + + // 6. Let done be false. + // 7. Repeat, while done is false, + while let Some(parent_prototype) = parent_prototype_outer { + // a. If p is null, then + // i. Set done to true. + + // b. Else if SameValue(p, O) is true, then + if parent_prototype + .into_value() + .same_value(agent, object.into_value()) + { + // i. Return false. + return Ok(false); + } + + // c. Else, + // i. If p.[[GetPrototypeOf]] is not the ordinary object internal method defined in 10.1.1, + // set done to true. + if parent_prototype.internal_methods(agent).get_prototype_of != get_prototype_of { + break; + } + + // ii. Else, set p to p.[[Prototype]]. + parent_prototype_outer = parent_prototype.prototype(agent); + } + + // 8. Set O.[[Prototype]] to V. + object.set_prototype(agent, parent_prototype_outer); + + // 9. Return true. + Ok(true) +} + +/// 10.1.3 [[IsExtensible]] ( ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible +fn is_extensible(agent: &mut Agent, object: Object) -> JsResult { + // 1. Return OrdinaryIsExtensible(O). + Ok(ordinary_is_extensible(agent, object)) +} + +/// 10.1.3.1 OrdinaryIsExtensible ( O ) +/// https://tc39.es/ecma262/#sec-ordinaryisextensible +pub fn ordinary_is_extensible(agent: &mut Agent, object: Object) -> bool { + // 1. Return O.[[Extensible]]. + object.extensible(agent) +} + +/// 10.1.4 [[PreventExtensions]] ( ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-preventextensions +fn prevent_extensions(agent: &mut Agent, object: Object) -> JsResult { + // 1. Return OrdinaryPreventExtensions(O). + Ok(ordinary_prevent_extensions(agent, object)) +} + +/// 10.1.4.1 OrdinaryPreventExtensions ( O ) +/// https://tc39.es/ecma262/#sec-ordinarypreventextensions +pub fn ordinary_prevent_extensions(agent: &mut Agent, object: Object) -> bool { + // 1. Set O.[[Extensible]] to false. + object.set_extensible(agent, false); + + // 2. Return true. + true +} + +/// 10.1.5 [[GetOwnProperty]] ( P ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-getownproperty-p +fn get_own_property( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, +) -> JsResult> { + // 1. Return OrdinaryGetOwnProperty(O, P). + Ok(ordinary_get_own_property(agent, object, property_key)) +} + +/// 10.1.5.1 OrdinaryGetOwnProperty ( O, P ) +/// https://tc39.es/ecma262/#sec-ordinarygetownproperty +pub fn ordinary_get_own_property( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, +) -> Option { + // 1. If O does not have an own property with key P, return undefined. + // 3. Let X be O's own property whose key is P. + let x = object.property_storage().get(agent, property_key)?; + + // 2. Let D be a newly created Property Descriptor with no fields. + let mut descriptor = PropertyDescriptor::default(); + + // 4. If X is a data property, then + if x.is_data_descriptor() { + // a. Set D.[[Value]] to the value of X's [[Value]] attribute. + descriptor.value = x.value; + + // b. Set D.[[Writable]] to the value of X's [[Writable]] attribute. + descriptor.writable = x.writable; + } + // 5. Else, + else { + // a. Assert: X is an accessor property. + debug_assert!(x.is_accessor_descriptor()); + + // b. Set D.[[Get]] to the value of X's [[Get]] attribute. + descriptor.get = x.get; + + // c. Set D.[[Set]] to the value of X's [[Set]] attribute. + descriptor.set = x.set; + } + + // 6. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute. + descriptor.enumerable = x.enumerable; + + // 7. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute. + descriptor.configurable = x.configurable; + + // 8. Return D. + Some(descriptor) +} + +/// 10.1.6 [[DefineOwnProperty]] ( P, Desc ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-defineownproperty-p-desc +pub fn define_own_property( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, + descriptor: PropertyDescriptor, +) -> JsResult { + ordinary_define_own_property(agent, object, property_key, descriptor) +} + +/// 10.1.6.1 OrdinaryDefineOwnProperty ( O, P, Desc ) +/// https://tc39.es/ecma262/#sec-ordinarydefineownproperty +pub fn ordinary_define_own_property( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, + descriptor: PropertyDescriptor, +) -> JsResult { + // 1. Let current be ? O.[[GetOwnProperty]](P). + let current = (object.internal_methods(agent).get_own_property)(agent, object, property_key)?; + + // 2. Let extensible be ? IsExtensible(O). + let extensible = object.extensible(agent); + + // 3. Return ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current). + validate_and_apply_property_descriptor( + agent, + Some(object), + property_key, + extensible, + descriptor, + current, + ) +} + +/// 10.1.6.3 ValidateAndApplyPropertyDescriptor ( O, P, extensible, Desc, current ) +/// https://tc39.es/ecma262/#sec-validateandapplypropertydescriptor +fn validate_and_apply_property_descriptor( + agent: &mut Agent, + object: Option, + property_key: PropertyKey, + extensible: bool, + descriptor: PropertyDescriptor, + current: Option, +) -> JsResult { + // 1. Assert: IsPropertyKey(P) is true. + + // 2. If current is undefined, then + let Some(current) = current else { + // a. If extensible is false, return false. + if !extensible { + return Ok(false); + } + + // b. If O is undefined, return true. + let Some(object) = object else { + return Ok(true); + }; + + // c. If IsAccessorDescriptor(Desc) is true, then + if descriptor.is_accessor_descriptor() { + // i. Create an own accessor property named P of object O whose [[Get]], [[Set]], + // [[Enumerable]], and [[Configurable]] attributes are set to the value of the + // corresponding field in Desc if Desc has that field, or to the attribute's default + // value otherwise. + object.property_storage().set(agent, property_key, PropertyDescriptor{ + get: descriptor.get, + set: descriptor.set, + enumerable: Some(descriptor.enumerable.unwrap_or(false)), + configurable: Some(descriptor.configurable.unwrap_or(false)), + ..Default::default() + }) + } + // d. Else, + else { + // i. Create an own data property named P of object O whose [[Value]], [[Writable]], + // [[Enumerable]], and [[Configurable]] attributes are set to the value of the + // corresponding field in Desc if Desc has that field, or to the attribute's default + // value otherwise. + // try object.propertyStorage().set(property_key, PropertyDescriptor{ + // .value = descriptor.value orelse .undefined, + // .writable = descriptor.writable orelse false, + // .enumerable = descriptor.enumerable orelse false, + // .configurable = descriptor.configurable orelse false, + // }); + object.property_storage().set(agent, property_key, PropertyDescriptor{ + value: Some(descriptor.value.unwrap_or(Value::Undefined)), + enumerable: Some(descriptor.enumerable.unwrap_or(false)), + configurable: Some(descriptor.configurable.unwrap_or(false)), + ..Default::default() + }) + } + + // e. Return true. + return Ok(true); + }; + + // 3. Assert: current is a fully populated Property Descriptor. + debug_assert!(current.is_fully_populated()); + + // 4. If Desc does not have any fields, return true. + if !descriptor.has_fields() { + return Ok(true); + } + + // 5. If current.[[Configurable]] is false, then + if !current.configurable.unwrap() { + // a. If Desc has a [[Configurable]] field and Desc.[[Configurable]] is true, return false. + if let Some(true) = descriptor.configurable { + return Ok(false); + } + + // b. If Desc has an [[Enumerable]] field and SameValue(Desc.[[Enumerable]], current.[[Enumerable]]) + // is false, return false. + if let Some(true) = descriptor.enumerable { + if descriptor.enumerable != current.enumerable { + return Ok(false); + } + } + + // c. If IsGenericDescriptor(Desc) is false and SameValue(IsAccessorDescriptor(Desc), IsAccessorDescriptor(current)) + // is false, return false. + if !descriptor.is_generic_descriptor() + && descriptor.is_accessor_descriptor() != current.is_accessor_descriptor() + { + return Ok(false); + } + + // d. If IsAccessorDescriptor(current) is true, then + if current.is_accessor_descriptor() { + // i. If Desc has a [[Get]] field and SameValue(Desc.[[Get]], current.[[Get]]) is false, + // return false. + if let Some(desc_get) = descriptor.get { + if let Some(cur_get) = current.get { + if !desc_get + .into_value() + .same_value(agent, cur_get.into_value()) + { + return Ok(false); + } + } else { + return Ok(false); + } + } + + // ii. If Desc has a [[Set]] field and SameValue(Desc.[[Set]], current.[[Set]]) is + // false, return false. + if let Some(desc_set) = descriptor.set { + if let Some(cur_set) = current.set { + if !desc_set + .into_value() + .same_value(agent, cur_set.into_value()) + { + return Ok(false); + } + } else { + return Ok(false); + } + } + } + // e. Else if current.[[Writable]] is false, then + else if let Some(true) = current.writable { + // i. If Desc has a [[Writable]] field and Desc.[[Writable]] is true, return false. + if let Some(true) = descriptor.writable { + return Ok(false); + } + + // ii. If Desc has a [[Value]] field and SameValue(Desc.[[Value]], current.[[Value]]) + // is false, return false. + if let Some(desc_value) = descriptor.value { + if let Some(cur_value) = current.value { + if !desc_value.same_value(agent, cur_value) { + return Ok(false); + } + } else { + return Ok(false); + } + } + } + } + + // 6. If O is not undefined, then + if let Some(object) = object { + // a. If IsDataDescriptor(current) is true and IsAccessorDescriptor(Desc) is true, then + if current.is_data_descriptor() && descriptor.is_accessor_descriptor() { + // i. If Desc has a [[Configurable]] field, let configurable be Desc.[[Configurable]]; + // else let configurable be current.[[Configurable]]. + let configurable = descriptor + .configurable + .unwrap_or_else(|| current.configurable.unwrap()); + + // ii. If Desc has a [[Enumerable]] field, let enumerable be Desc.[[Enumerable]]; else + // let enumerable be current.[[Enumerable]]. + let enumerable = descriptor + .enumerable + .unwrap_or_else(|| current.enumerable.unwrap()); + + // iii. Replace the property named P of object O with an accessor property whose + // [[Configurable]] and [[Enumerable]] attributes are set to configurable and + // enumerable, respectively, and whose [[Get]] and [[Set]] attributes are set to + // the value of the corresponding field in Desc if Desc has that field, or to the + // attribute's default value otherwise. + object.property_storage().set( + agent, + property_key, + PropertyDescriptor { + get: descriptor.get, + set: descriptor.set, + enumerable: Some(enumerable), + configurable: Some(configurable), + ..Default::default() + }, + ); + } + // b. Else if IsAccessorDescriptor(current) is true and IsDataDescriptor(Desc) is true, then + else if current.is_accessor_descriptor() && descriptor.is_data_descriptor() { + // i. If Desc has a [[Configurable]] field, let configurable be Desc.[[Configurable]]; + // else let configurable be current.[[Configurable]]. + let configurable = descriptor + .configurable + .unwrap_or_else(|| current.configurable.unwrap()); + + // ii. If Desc has a [[Enumerable]] field, let enumerable be Desc.[[Enumerable]]; else + // let enumerable be current.[[Enumerable]]. + let enumerable = descriptor + .enumerable + .unwrap_or_else(|| current.enumerable.unwrap()); + + // iii. Replace the property named P of object O with a data property whose + // [[Configurable]] and [[Enumerable]] attributes are set to configurable and + // enumerable, respectively, and whose [[Value]] and [[Writable]] attributes are + // set to the value of the corresponding field in Desc if Desc has that field, or + // to the attribute's default value otherwise. + // try object.propertyStorage().set(property_key, PropertyDescriptor{ + // .value = descriptor.value orelse .undefined, + // .writable = descriptor.writable orelse false, + // .enumerable = enumerable, + // .configurable = configurable, + // }); + object.property_storage().set( + agent, + property_key, + PropertyDescriptor { + value: Some(descriptor.value.unwrap_or(Value::Undefined)), + writable: Some(descriptor.writable.unwrap_or(false)), + enumerable: Some(enumerable), + configurable: Some(configurable), + ..Default::default() + }, + ); + } + // c. Else, + else { + // i. For each field of Desc, set the corresponding attribute of the property named P + // of object O to the value of the field. + object.property_storage().set( + agent, + property_key, + PropertyDescriptor { + value: descriptor.value.or(current.value), + writable: Some(descriptor.writable.unwrap_or(false)), + get: descriptor.get.or(current.get), + set: descriptor.set.or(current.set), + enumerable: descriptor.enumerable.or(current.enumerable), + configurable: descriptor.configurable.or(current.configurable), + ..Default::default() + }, + ); + } + } + + // 7. Return true. + Ok(true) +} + +/// 10.1.7 [[HasProperty]] ( P ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p +pub fn has_property( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, +) -> JsResult { + // 1. Return ? OrdinaryHasProperty(O, P). + ordinary_has_property(agent, object, property_key) +} + +/// 10.1.7.1 OrdinaryHasProperty ( O, P ) +/// https://tc39.es/ecma262/#sec-ordinaryhasproperty +pub fn ordinary_has_property( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, +) -> JsResult { + // 1. Let hasOwn be ? O.[[GetOwnProperty]](P). + let has_own = (object.internal_methods(agent).get_own_property)(agent, object, property_key)?; + + // 2. If hasOwn is not undefined, return true. + if let Some(_) = has_own { + return Ok(true); + } + + // 3. Let parent be ? O.[[GetPrototypeOf]](). + let parent = (object.internal_methods(agent).get_prototype_of)(agent, object); + + // 4. If parent is not null, then + if let Some(parent) = parent { + // a. Return ? parent.[[HasProperty]](P). + return (parent.internal_methods(agent).has_property)(agent, parent, property_key); + } + + // 5. Return false. + Ok(false) +} + +/// 10.1.8 [[Get]] ( P, Receiver ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-get-p-receiver +fn get( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, + receiver: Value, +) -> JsResult { + // 1. Return ? OrdinaryGet(O, P, Receiver). + ordinary_get(agent, object, property_key, receiver) +} + +/// 10.1.8.1 OrdinaryGet ( O, P, Receiver ) +/// https://tc39.es/ecma262/#sec-ordinaryget +pub fn ordinary_get( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, + receiver: Value, +) -> JsResult { + // 1. Let desc be ? O.[[GetOwnProperty]](P). + let Some(descriptor) = (object.internal_methods(agent).get_own_property)(agent, object, property_key)? else { + // 2. If desc is undefined, then + + // a. Let parent be ? O.[[GetPrototypeOf]](). + let Some(parent) = (object.internal_methods(agent).get_prototype_of)(agent, object) else { + return Ok(Value::Undefined); + }; + + // c. Return ? parent.[[Get]](P, Receiver). + return (parent.internal_methods(agent).get)(agent, parent, property_key, receiver); + }; + + // 3. If IsDataDescriptor(desc) is true, return desc.[[Value]]. + if let Some(value) = descriptor.value { + debug_assert!(descriptor.is_data_descriptor()); + return Ok(value); + } + + // 4. Assert: IsAccessorDescriptor(desc) is true. + debug_assert!(descriptor.is_accessor_descriptor()); + + // 5. Let getter be desc.[[Get]]. + // 6. If getter is undefined, return undefined. + let Some(getter) = descriptor.get else { + return Ok(Value::Undefined); + }; + + // 7. Return ? Call(getter, Receiver). + todo!() +} + +/// 10.1.9 [[Set]] ( P, V, Receiver ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver +fn set( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, + value: Value, + receiver: Value, +) -> JsResult { + // 1. Return ? OrdinarySet(O, P, V, Receiver). + ordinary_set(agent, object, property_key, value, receiver) +} + +/// 10.1.9.1 OrdinarySet ( O, P, V, Receiver ) +/// https://tc39.es/ecma262/#sec-ordinaryset +pub fn ordinary_set( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, + value: Value, + receiver: Value, +) -> JsResult { + // 1. Let ownDesc be ? O.[[GetOwnProperty]](P). + let own_descriptor = + (object.internal_methods(agent).get_own_property)(agent, object, property_key)?; + + // 2. Return ? OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc). + ordinary_set_with_own_descriptor(agent, object, property_key, value, receiver, own_descriptor) +} + +/// 10.1.9.2 OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc ) +/// https://tc39.es/ecma262/#sec-ordinarysetwithowndescriptor +pub fn ordinary_set_with_own_descriptor( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, + value: Value, + receiver: Value, + own_descriptor: Option, +) -> JsResult { + let own_descriptor = if let Some(own_descriptor) = own_descriptor { + own_descriptor + } else { + // 1. If ownDesc is undefined, then + // a. Let parent be ? O.[[GetPrototypeOf]](). + let parent = (object.internal_methods(agent).get_prototype_of)(agent, object); + + // b. If parent is not null, then + if let Some(parent) = parent { + // i. Return ? parent.[[Set]](P, V, Receiver). + return (parent.internal_methods(agent).set)( + agent, + parent, + property_key, + value, + receiver, + ); + } + // c. Else, + else { + // i. Set ownDesc to the PropertyDescriptor { + // [[Value]]: undefined, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true + // }. + PropertyDescriptor { + value: Some(Value::Undefined), + writable: Some(true), + enumerable: Some(true), + configurable: Some(true), + ..Default::default() + } + } + }; + + // 2. If IsDataDescriptor(ownDesc) is true, then + if own_descriptor.is_data_descriptor() { + // a. If ownDesc.[[Writable]] is false, return false. + if own_descriptor.writable == Some(false) { + return Ok(false); + } + + // b. If Receiver is not an Object, return false. + let Ok(receiver) = Object::try_from(receiver) else { + return Ok(false); + }; + + // c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P). + let existing_descriptor = + (receiver.internal_methods(agent).get_own_property)(agent, receiver, property_key)?; + + // d. If existingDescriptor is not undefined, then + if let Some(existing_descriptor) = existing_descriptor { + // i. If IsAccessorDescriptor(existingDescriptor) is true, return false. + if existing_descriptor.is_accessor_descriptor() { + return Ok(false); + } + + // ii. If existingDescriptor.[[Writable]] is false, return false. + if existing_descriptor.writable == Some(false) { + return Ok(false); + } + + // iii. Let valueDesc be the PropertyDescriptor { [[Value]]: V }. + let value_descriptor = PropertyDescriptor { + value: Some(value), + ..Default::default() + }; + + // iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc). + return (receiver.internal_methods(agent).define_own_property)( + agent, + receiver, + property_key, + value_descriptor, + ); + } + // e. Else, + else { + // i. Assert: Receiver does not currently have a property P. + debug_assert!(!receiver.property_storage().has(agent, property_key)); + + // ii. Return ? CreateDataProperty(Receiver, P, V). + return receiver.create_data_property(agent, property_key, value); + } + } + + // 3. Assert: IsAccessorDescriptor(ownDesc) is true. + debug_assert!(own_descriptor.is_accessor_descriptor()); + + // 4. Let setter be ownDesc.[[Set]]. + // 5. If setter is undefined, return false. + let Some(setter) = own_descriptor.set else { + return Ok(false); + }; + + // 6. Perform ? Call(setter, Receiver, ยซ V ยป). + todo!(); + // 7. Return true. +} + +/// 10.1.10 [[Delete]] ( P ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-delete-p +fn delete(agent: &mut Agent, object: Object, property_key: PropertyKey) -> JsResult { + // 1. Return ? OrdinaryDelete(O, P). + ordinary_delete(agent, object, property_key) +} + +/// 10.1.10.1 OrdinaryDelete ( O, P ) +/// https://tc39.es/ecma262/#sec-ordinarydelete +pub fn ordinary_delete( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, +) -> JsResult { + // 1. Let desc be ? O.[[GetOwnProperty]](P). + let descriptor = + (object.internal_methods(agent).get_own_property)(agent, object, property_key)?; + + // 2. If desc is undefined, return true. + let Some(descriptor) = descriptor else { + return Ok(true); + }; + + // 3. If desc.[[Configurable]] is true, then + if let Some(true) = descriptor.configurable { + // a. Remove the own property with name P from O. + object.property_storage().remove(agent, property_key); + + // b. Return true. + return Ok(true); + } + + // 4. Return false. + Ok(false) +} + +/// 10.1.11 [[OwnPropertyKeys]] ( ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys +fn own_property_keys(agent: &mut Agent, object: Object) -> JsResult> { + // 1. Return OrdinaryOwnPropertyKeys(O). + ordinary_own_property_keys(agent, object) +} + +/// 10.1.11.1 OrdinaryOwnPropertyKeys ( O ) +/// https://tc39.es/ecma262/#sec-ordinaryownpropertykeys +pub fn ordinary_own_property_keys(agent: &mut Agent, object: Object) -> JsResult> { + // 1. Let keys be a new empty List. + let mut keys = Vec::new(); + + // 2. For each own property key P of O such that P is an array index, in ascending numeric + // index order, do + // for entry in object.property_storage().entries(agent) { + // if entry.key.is_array_index() { + // // a. Append P to keys. + // keys.push(entry.key); + // } + // } + + // for (object.property_storage().hash_map.keys()) |property_key| { + // if (property_key.is_array_index()) { + // // a. Append P to keys. + // keys.appendAssumeCapacity(property_key); + // } + // } + + // 3. For each own property key P of O such that P is a String and P is not an array index, in + // ascending chronological order of property creation, do + // for (object.propertyStorage().hash_map.keys()) |property_key| { + // if (property_key == .string or (property_key == .integer_index and !property_key.isArrayIndex())) { + // // a. Append P to keys. + // keys.appendAssumeCapacity(property_key); + // } + // } + + // 4. For each own property key P of O such that P is a Symbol, in ascending chronological + // order of property creation, do + // for (object.propertyStorage().hash_map.keys()) |property_key| { + // if (property_key == .symbol) { + // // a. Append P to keys. + // keys.appendAssumeCapacity(property_key); + // } + // } + + // 5. Return keys. + Ok(keys) +} diff --git a/nova_vm/src/execution.rs b/nova_vm/src/execution.rs new file mode 100644 index 00000000..6f33692c --- /dev/null +++ b/nova_vm/src/execution.rs @@ -0,0 +1,13 @@ +pub mod agent; +mod default_host_hooks; +mod environments; +mod execution_context; +mod realm; + +pub use agent::{Agent, JsResult}; +pub use environments::{ + DeclarativeEnvironment, Environment, FunctionEnvironment, GlobalEnvironment, ObjectEnvironment, + PrivateEnvironment, +}; +pub use execution_context::{ECMAScriptCode, ExecutionContext, ScriptOrModule}; +pub use realm::{Intrinsics, Realm}; diff --git a/nova_vm/src/execution/agent.rs b/nova_vm/src/execution/agent.rs new file mode 100644 index 00000000..f3550716 --- /dev/null +++ b/nova_vm/src/execution/agent.rs @@ -0,0 +1,59 @@ +use super::{ExecutionContext, Realm}; +use crate::{ + types::{Object, Symbol, Value}, + Heap, +}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +#[derive(Debug, Default)] +pub struct Options { + pub disable_gc: bool, + pub print_ast: bool, + pub print_bytecode: bool, +} + +pub type JsResult = std::result::Result; + +// #[derive(Debug)] +// pub struct PreAllocated; + +#[derive(Debug)] +pub struct HostHooks { + pub host_ensure_can_compile_strings: fn(callee_realm: &mut Realm) -> JsResult<()>, + pub host_has_source_text_available: fn(func: Object) -> bool, +} + +/// 9.7 Agents +/// https://tc39.es/ecma262/#sec-agents +#[derive(Debug)] +pub struct Agent<'ctx, 'host> { + pub options: Options, + // pre_allocated: PreAllocated, + pub exception: Option, + pub symbol_id: usize, + pub global_symbol_registry: HashMap<&'static str, Symbol>, + pub host_hooks: HostHooks, + pub execution_context_stack: Vec>, +} + +impl<'ctx, 'host> Agent<'ctx, 'host> { + pub fn current_realm(&self) -> Rc>> { + self.execution_context_stack.last().unwrap().realm.clone() + } + + /// 5.2.3.2 Throw an Exception + /// https://tc39.es/ecma262/#sec-throw-an-exception + pub fn throw_exception(&mut self, kind: ExceptionType, message: &'static str) -> () { + todo!() + } +} + +#[derive(Debug)] +pub enum ExceptionType { + EvalError, + RangeError, + ReferenceError, + SyntaxError, + TypeError, + UriError, +} diff --git a/nova_vm/src/execution/default_host_hooks.rs b/nova_vm/src/execution/default_host_hooks.rs new file mode 100644 index 00000000..33916ab4 --- /dev/null +++ b/nova_vm/src/execution/default_host_hooks.rs @@ -0,0 +1,15 @@ +use super::{JsResult, Realm}; +use crate::types::Object; + +/// 19.2.1.2 HostEnsureCanCompileStrings ( calleeRealm ) +/// https://tc39.es/ecma262/#sec-hostensurecancompilestrings +pub fn host_ensure_can_compile_strings(_: &mut Realm) -> JsResult<()> { + Ok(()) +} + +/// 20.2.5 HostHasSourceTextAvailable ( func ) +/// https://tc39.es/ecma262/#sec-hosthassourcetextavailable +pub fn host_has_source_text_available(_: Object) -> bool { + // The default implementation of HostHasSourceTextAvailable is to return true. + return true; +} diff --git a/nova_vm/src/execution/environments.rs b/nova_vm/src/execution/environments.rs new file mode 100644 index 00000000..dfffc310 --- /dev/null +++ b/nova_vm/src/execution/environments.rs @@ -0,0 +1,25 @@ +//! 9.1 Environment Records +//! https://tc39.es/ecma262/#sec-environment-records + +pub mod declarative_environment; +pub mod function_environment; +pub mod global_environment; +pub mod object_environment; +pub mod private_environment; + +pub use declarative_environment::DeclarativeEnvironment; +pub use function_environment::FunctionEnvironment; +pub use global_environment::GlobalEnvironment; +pub use object_environment::ObjectEnvironment; +pub use private_environment::PrivateEnvironment; +use std::{cell::RefCell, rc::Rc}; + +/// 9.1.1 The Environment Record Type Hierarchy +/// https://tc39.es/ecma262/#sec-the-environment-record-type-hierarchy +#[derive(Debug, Clone)] +pub enum Environment { + DeclarativeEnvironment(Rc>), + ObjectEnvironment(Rc>), + FunctionEnvironment(Rc>), + GlobalEnvironment(Rc>), +} diff --git a/nova_vm/src/execution/environments/declarative_environment.rs b/nova_vm/src/execution/environments/declarative_environment.rs new file mode 100644 index 00000000..c1e75df0 --- /dev/null +++ b/nova_vm/src/execution/environments/declarative_environment.rs @@ -0,0 +1,29 @@ +use super::Environment; +use crate::types::Value; +use std::collections::HashMap; + +/// 9.1.1.1 Declarative Environment Records +/// https://tc39.es/ecma262/#sec-declarative-environment-records +#[derive(Debug)] +pub struct DeclarativeEnvironment { + pub outer_env: Option, + pub bindings: HashMap<&'static str, Binding>, +} + +#[derive(Debug)] +pub struct Binding { + pub value: Option, + pub strict: bool, + pub mutable: bool, + pub deletable: bool, +} + +impl DeclarativeEnvironment { + /// 9.1.1.1.1 HasBinding ( N ) + /// https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n + pub fn has_binding(self, name: &str) -> bool { + // 1. If envRec has a binding for N, return true. + // 2. Return false. + return self.bindings.contains_key(name); + } +} diff --git a/nova_vm/src/execution/environments/function_environment.rs b/nova_vm/src/execution/environments/function_environment.rs new file mode 100644 index 00000000..2347ffc3 --- /dev/null +++ b/nova_vm/src/execution/environments/function_environment.rs @@ -0,0 +1,37 @@ +use super::{DeclarativeEnvironment, Environment}; +use crate::types::Value; +use std::{cell::RefCell, rc::Rc}; + +#[derive(Debug)] +pub enum ThisBindingStatus { + Lexical, + Initialized, + Uninitialized, +} + +#[derive(Debug)] +struct ECMAScriptFunction; + +/// 9.1.1.3 Function Environment Records +/// https://tc39.es/ecma262/#sec-function-environment-records +#[derive(Debug)] +pub struct FunctionEnvironment { + /// [[ThisValue]] + this_value: Value, + + /// [[ThisBindingStatus]] + this_binding_status: ThisBindingStatus, + + /// [[FunctionObject]] + function_object: ECMAScriptFunction, + + /// [[NewTarget]] + new_target: Option, + + /// [[OuterEnv]] + outer_env: Option, + + // NOTE: This is how we implement the spec's inheritance of function + // environments. + declarative_environment: Rc>, +} diff --git a/nova_vm/src/execution/environments/global_environment.rs b/nova_vm/src/execution/environments/global_environment.rs new file mode 100644 index 00000000..4f1d07da --- /dev/null +++ b/nova_vm/src/execution/environments/global_environment.rs @@ -0,0 +1,23 @@ +use super::{DeclarativeEnvironment, Environment, ObjectEnvironment}; +use crate::types::Object; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +/// 9.1.1.4 Global Environment Records +/// https://tc39.es/ecma262/#sec-global-environment-records +#[derive(Debug)] +pub struct GlobalEnvironment { + // [[ObjectRecord]] + object_record: Rc>, + + /// [[GlobalThisValue]] + global_this_value: Object, + + /// [[DeclarativeRecord]] + declarative_record: Rc>, + + /// [[VarNames]] + var_names: HashMap<&'static str, ()>, + + /// [[OuterEnv]] + outer_env: Option, +} diff --git a/nova_vm/src/execution/environments/object_environment.rs b/nova_vm/src/execution/environments/object_environment.rs new file mode 100644 index 00000000..2200038f --- /dev/null +++ b/nova_vm/src/execution/environments/object_environment.rs @@ -0,0 +1,16 @@ +use super::Environment; +use crate::types::Object; + +/// 9.1.1.2 Object Environment Records +/// https://tc39.es/ecma262/#sec-object-environment-records +#[derive(Debug)] +pub struct ObjectEnvironment { + /// [[BindingObject]] + binding_object: Object, + + /// [[IsWithEnvironment]] + is_with_environment: bool, + + /// [[OuterEnv]] + outer_env: Option, +} diff --git a/nova_vm/src/execution/environments/private_environment.rs b/nova_vm/src/execution/environments/private_environment.rs new file mode 100644 index 00000000..9fd9b48e --- /dev/null +++ b/nova_vm/src/execution/environments/private_environment.rs @@ -0,0 +1,12 @@ +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +/// 9.2 PrivateEnvironment Records +/// https://tc39.es/ecma262/#sec-privateenvironment-records +#[derive(Debug)] +pub struct PrivateEnvironment { + /// [[OuterPrivateEnvironment]] + outer_private_environment: Option>>, + + /// [[Names]] + names: HashMap<&'static str, ()>, // TODO: Implement private names +} diff --git a/nova_vm/src/execution/execution_context.rs b/nova_vm/src/execution/execution_context.rs new file mode 100644 index 00000000..9ce50452 --- /dev/null +++ b/nova_vm/src/execution/execution_context.rs @@ -0,0 +1,42 @@ +use super::{Environment, PrivateEnvironment}; +use crate::{execution::Realm, language::Script, types::*}; +use std::{cell::RefCell, rc::Rc}; + +#[derive(Debug)] +pub struct Module; + +#[derive(Debug, Clone)] +pub enum ScriptOrModule<'ctx, 'host> { + Script(Rc>>), + Module(Rc>), +} + +#[derive(Debug)] +pub struct ECMAScriptCode { + /// LexicalEnvironment + pub lexical_environment: Environment, + + /// VariableEnvironment + pub variable_environment: Environment, + + /// PrivateEnvironment + pub private_environment: Option>>, +} + +/// 9.4 Execution Contexts +/// https://tc39.es/ecma262/#sec-execution-contexts +#[derive(Debug)] +pub struct ExecutionContext<'ctx, 'host> { + /// Function + pub function: Option, + + /// Realm + pub realm: Rc>>, + + /// ScriptOrModule + pub script_or_module: Option>, + + /// ECMAScript code execution contexts have the additional state components listed in Table 26. + /// https://tc39.es/ecma262/#ecmascript-code-execution-context + pub ecmascript_code: Option, +} diff --git a/nova_vm/src/execution/realm.rs b/nova_vm/src/execution/realm.rs new file mode 100644 index 00000000..1f1333d5 --- /dev/null +++ b/nova_vm/src/execution/realm.rs @@ -0,0 +1,28 @@ +mod intrinsics; + +use super::{Agent, GlobalEnvironment}; +use crate::{types::Object, Heap}; +pub use intrinsics::Intrinsics; +use std::{any::Any, cell::RefCell, rc::Rc}; + +/// 9.3 Realms +/// https://tc39.es/ecma262/#sec-code-realms +#[derive(Debug)] +pub struct Realm<'ctx, 'host> { + pub heap: Heap, + + pub agent: Rc>>, + + // NOTE: We will need an rng here at some point. + + // NOTE: [[Intrinsics]] are statically known via the [`Intrinsics`] struct. + /// [[GlobalObject]] + pub global_object: Object, + + /// [[GlobalEnv]] + pub global_env: Rc>, + + /// [[HostDefined]] + pub host_defined: Option>>, + // TODO: [[TemplateMap]], [[LoadedModules]] +} diff --git a/nova_vm/src/execution/realm/intrinsics.rs b/nova_vm/src/execution/realm/intrinsics.rs new file mode 100644 index 00000000..d97ace85 --- /dev/null +++ b/nova_vm/src/execution/realm/intrinsics.rs @@ -0,0 +1,235 @@ +use crate::{ + heap::{BuiltinObjectIndexes, Handle}, + types::Object, +}; + +// TODO: We should probably consider lazily loading intrinsics. This would +// contain a mutable reference to [`Realm`] and be created via a +// `Realm::intrinsic()` method to guarantee safety. + +pub struct Intrinsics; + +impl Intrinsics { + /// %Array% + pub const fn array() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::ArrayConstructorIndex as u32, + )) + } + + /// %Array.prototype% + pub const fn array_prototype() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::ArrayPrototypeIndex as u32, + )) + } + + /// %BigInt% + pub const fn big_int() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::BigintConstructorIndex as u32, + )) + } + + /// %BigInt.prototype% + pub const fn big_int_prototype() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::BigintPrototypeIndex as u32, + )) + } + + /// %Boolean% + pub const fn boolean() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::BooleanConstructorIndex as u32, + )) + } + + /// %Boolean.prototype% + pub const fn boolean_prototype() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::BooleanPrototypeIndex as u32, + )) + } + + /// %Error% + pub const fn error() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::ErrorConstructorIndex as u32, + )) + } + + /// %Error.prototype% + pub const fn error_prototype() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::ErrorPrototypeIndex as u32, + )) + } + + /// %eval% + pub const fn eval() -> Object { + todo!() + } + + /// %EvalError% + pub const fn eval_error() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::ArrayConstructorIndex as u32, + )) + } + + /// %EvalError.prototype% + pub const fn eval_error_prototype() -> Object { + todo!() + } + + /// %Function% + pub const fn function() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::FunctionConstructorIndex as u32, + )) + } + + /// %Function.prototype% + pub const fn function_prototype() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::FunctionPrototypeIndex as u32, + )) + } + + /// %isFinite% + pub const fn is_finite() -> Object { + todo!() + } + + /// %isNaN% + pub const fn is_nan() -> Object { + todo!() + } + + /// %Math% + pub const fn math() -> Object { + Object::Object(Handle::new(BuiltinObjectIndexes::MathObjectIndex as u32)) + } + + /// %Number% + pub const fn number() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::NumberConstructorIndex as u32, + )) + } + + /// %Number.prototype% + pub const fn number_prototype() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::NumberPrototypeIndex as u32, + )) + } + + /// %Object% + pub const fn object() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::ObjectConstructorIndex as u32, + )) + } + + /// %Object.prototype% + pub const fn object_prototype() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::ObjectPrototypeIndex as u32, + )) + } + + /// %Object.prototype.toString% + pub const fn object_prototype_to_string() -> Object { + todo!() + } + + /// %RangeError% + pub const fn range_error() -> Object { + todo!() + } + + /// %RangeError.prototype% + pub const fn range_error_prototype() -> Object { + todo!() + } + + /// %ReferenceError% + pub const fn reference_error() -> Object { + todo!() + } + + /// %ReferenceError.prototype% + pub const fn reference_error_prototype() -> Object { + todo!() + } + + /// %Reflect% + pub const fn reflect() -> Object { + todo!() + } + + /// %String% + pub const fn string() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::StringConstructorIndex as u32, + )) + } + + /// %String.prototype% + pub const fn string_prototype() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::StringPrototypeIndex as u32, + )) + } + + /// %Symbol% + pub const fn symbol() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::SymbolConstructorIndex as u32, + )) + } + + /// %Symbol.prototype% + pub const fn symbol_prototype() -> Object { + Object::Object(Handle::new( + BuiltinObjectIndexes::SymbolPrototypeIndex as u32, + )) + } + + /// %SyntaxError% + pub const fn syntax_error() -> Object { + todo!() + } + + /// %SyntaxError.prototype% + pub const fn syntax_error_prototype() -> Object { + todo!() + } + + /// %ThrowTypeError% + pub const fn throw_type_error() -> Object { + todo!() + } + + /// %TypeError% + pub const fn type_error() -> Object { + todo!() + } + + /// %TypeError.prototype% + pub const fn type_error_prototype() -> Object { + todo!() + } + + /// %URIError% + pub const fn uri_error() -> Object { + todo!() + } + + /// %URIError.prototype% + pub const fn uri_error_prototype() -> Object { + todo!() + } +} diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index ec9cbd7e..7579bcf4 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -1,122 +1,210 @@ mod array; mod bigint; -mod boolean; -mod date; -mod error; mod function; mod heap_constants; mod heap_trace; -mod math; mod number; mod object; -mod regexp; mod string; mod symbol; -use self::{ - array::{initialize_array_heap, ArrayHeapData}, - bigint::{initialize_bigint_heap, BigIntHeapData}, - boolean::initialize_boolean_heap, - date::{initialize_date_heap, DateHeapData}, - error::{initialize_error_heap, ErrorHeapData}, - function::{initialize_function_heap, FunctionHeapData, JsBindingFunction}, - heap_constants::{ - BuiltinObjectIndexes, FIRST_CONSTRUCTOR_INDEX, LAST_BUILTIN_OBJECT_INDEX, - LAST_WELL_KNOWN_SYMBOL_INDEX, - }, - heap_trace::HeapTrace, - math::initialize_math_object, - number::{initialize_number_heap, NumberHeapData}, - object::{ - initialize_object_heap, ObjectEntry, ObjectHeapData, PropertyDescriptor, PropertyKey, - }, - regexp::{initialize_regexp_heap, RegExpHeapData}, - string::{initialize_string_heap, StringHeapData}, - symbol::{initialize_symbol_heap, SymbolHeapData}, -}; -use crate::value::Value; -use std::cell::Cell; -use wtf8::Wtf8; +pub use array::ArrayHeapData; +pub use bigint::BigIntHeapData; +pub use function::FunctionHeapData; +pub use heap_constants::BuiltinObjectIndexes; +pub use number::NumberHeapData; +pub use object::{ObjectEntry, ObjectHeapData}; +pub use string::StringHeapData; +pub use symbol::SymbolHeapData; + +use self::heap_trace::HeapTrace; +use crate::types::{Function, Number, Object, String, Value}; +use std::{cell::Cell, marker::PhantomData, num::NonZeroU32}; +use wtf8::{Wtf8, Wtf8Buf}; + +/// A handle to GC-managed memory. +#[derive(Clone)] +pub struct Handle { + id: NonZeroU32, + _marker: &'static PhantomData, +} + +impl Copy for Handle {} + +impl PartialEq for Handle { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Handle { + /// Id must not be 0. + pub const fn new(id: u32) -> Self { + debug_assert!(id != 0); + Self { + id: unsafe { NonZeroU32::new_unchecked(id) }, + // SAFETY: We hopefully will make sure handles are safe. + _marker: unsafe { + std::mem::transmute::<&PhantomData, &'static PhantomData>(&PhantomData) + }, + } + } +} + +macro_rules! impl_handle_debug { + ($name: ty) => { + impl std::fmt::Debug for Handle<$name> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "$name(0x{:x})", self.id) + } + } + }; +} + +impl_handle_debug!(StringHeapData); +impl_handle_debug!(SymbolHeapData); +impl_handle_debug!(NumberHeapData); +impl_handle_debug!(BigIntHeapData); +impl_handle_debug!(ObjectHeapData); +impl_handle_debug!(ArrayHeapData); +impl_handle_debug!(FunctionHeapData); #[derive(Debug)] pub struct Heap { - pub(crate) arrays: Vec>, - pub(crate) bigints: Vec>, - pub(crate) errors: Vec>, - pub(crate) functions: Vec>, - pub(crate) dates: Vec>, - pub(crate) globals: Vec, - pub(crate) numbers: Vec>, - pub(crate) objects: Vec>, - pub(crate) regexps: Vec>, pub(crate) strings: Vec>, pub(crate) symbols: Vec>, + pub(crate) numbers: Vec>, + pub(crate) bigints: Vec>, + pub(crate) objects: Vec>, + pub(crate) arrays: Vec>, + pub(crate) functions: Vec>, } fn stop_the_world() {} fn start_the_world() {} +pub trait CreateHeapData { + /// Creates a [`Value`] from the given data. Allocating the data is **not** + /// guaranteed. + fn create(&mut self, data: T) -> F; +} + +pub trait GetHeapData<'a, T, F> { + fn get(&'a self, handle: Handle) -> &'a F; + fn get_mut(&'a mut self, handle: Handle) -> &'a mut F; +} + +impl CreateHeapData for Heap { + fn create(&mut self, data: f64) -> Number { + if let Ok(value) = Value::try_from(data) { + Number::new(value) + } else if data as f32 as f64 == data { + Number::new(Value::FloatNumber(data as f32)) + } else { + let id = self.alloc_number(data); + Number::new(Value::Number(Handle::new(id))) + } + } +} + +macro_rules! impl_heap_data { + ($table: ident, $in: ty, $out: ty) => { + impl<'a> GetHeapData<'a, $in, $out> for Heap { + fn get(&'a self, handle: Handle<$in>) -> &'a $out { + self.$table + .get(handle.id.get() as usize) + .unwrap() + .as_ref() + .unwrap() + } + + fn get_mut(&'a mut self, handle: Handle<$in>) -> &'a mut $out { + self.$table + .get_mut(handle.id.get() as usize) + .unwrap() + .as_mut() + .unwrap() + } + } + }; + ($table: ident, $in: ty, $out: ty, $accessor: ident) => { + impl<'a> GetHeapData<'a, $in, $out> for Heap { + fn get(&'a self, handle: Handle<$in>) -> &'a $out { + &self + .$table + .get(handle.id.get() as usize) + .as_ref() + .unwrap() + .as_ref() + .unwrap() + .$accessor + } + + fn get_mut(&'a mut self, handle: Handle<$in>) -> &'a mut $out { + &mut self + .$table + .get_mut(handle.id.get() as usize) + .unwrap() + .as_mut() + .unwrap() + .$accessor + } + } + }; +} + +impl_heap_data!(numbers, NumberHeapData, f64, data); +impl_heap_data!(objects, ObjectHeapData, ObjectHeapData); +impl_heap_data!(strings, StringHeapData, Wtf8Buf, data); +impl_heap_data!(functions, FunctionHeapData, FunctionHeapData); +impl_heap_data!(arrays, ArrayHeapData, ArrayHeapData); + +impl CreateHeapData<&str, String> for Heap { + fn create(&mut self, data: &str) -> String { + if let Ok(value) = String::try_from(data) { + value + } else { + let id = self.alloc_string(data); + String::from(Handle::new(id)) + } + } +} + +impl CreateHeapData for Heap { + fn create(&mut self, data: FunctionHeapData) -> Function { + let id = self.functions.len(); + self.functions.push(Some(data)); + Function(Handle::new(id as u32)) + } +} + +impl CreateHeapData for Heap { + fn create(&mut self, data: ObjectHeapData) -> Object { + let id: usize = self.functions.len(); + self.objects.push(Some(data)); + Object::Object(Handle::new(id as u32)) + } +} + impl Heap { pub fn new() -> Heap { let mut heap = Heap { - arrays: Vec::with_capacity(1024), - bigints: Vec::with_capacity(1024), - errors: Vec::with_capacity(1024), - functions: Vec::with_capacity(1024), - dates: Vec::with_capacity(1024), - globals: Vec::with_capacity(1024), - numbers: Vec::with_capacity(1024), - objects: Vec::with_capacity(1024), - regexps: Vec::with_capacity(1024), strings: Vec::with_capacity(1024), symbols: Vec::with_capacity(1024), + numbers: Vec::with_capacity(1024), + bigints: Vec::with_capacity(1024), + objects: Vec::with_capacity(1024), + arrays: Vec::with_capacity(1024), + functions: Vec::with_capacity(1024), }; - for _ in 0..LAST_WELL_KNOWN_SYMBOL_INDEX + 1 { - // Initialize well known symbol slots - heap.symbols.push(None); - } - for i in 0..LAST_BUILTIN_OBJECT_INDEX + 1 { - // Initialize all static slots in heap objects. - heap.objects.push(None); - if i >= FIRST_CONSTRUCTOR_INDEX { - heap.functions.push(None); - } - } - initialize_object_heap(&mut heap); - initialize_function_heap(&mut heap); - initialize_boolean_heap(&mut heap); - initialize_symbol_heap(&mut heap); - initialize_error_heap(&mut heap); - initialize_number_heap(&mut heap); - initialize_bigint_heap(&mut heap); - initialize_math_object(&mut heap); - initialize_date_heap(&mut heap); - initialize_string_heap(&mut heap); - initialize_regexp_heap(&mut heap); - initialize_array_heap(&mut heap); - // initialize_typedarray_heap(&mut heap); - // initialize_map_heap(&mut heap); - // initialize_set_heap(&mut heap); - // initialize_weak_map_heap(&mut heap); - // initialize_weak_set_heap(&mut heap); - // initialize_array_buffer_heap(&mut heap); - // initialize_shared_array_buffer_heap(&mut heap); - // initialize_data_view_heap(&mut heap); - // initialize_json_heap(&mut heap); - // initialize_atomics_heap(&mut heap); - // initialize_weak_ref_heap(&mut heap); - // initialize_finalization_registry_heap(&mut heap); - // initialize_iterator_heap(&mut heap); - // initialize_async_iterator_heap(&mut heap); - // initialize_promise_heap(&mut heap); - // initialize_generator_function_heap(&mut heap); - // initialize_async_generator_function_heap(&mut heap); - // initialize_generator_heap(&mut heap); - // initialize_async_generator_heap(&mut heap); - // initialize_async_function_heap(&mut heap); - // initialize_reflect_heap(&mut heap); - // initialize_proxy_heap(&mut heap); - // initialize_module_heap(&mut heap); + + heap.strings.push(Some(StringHeapData::dummy())); + heap.symbols.push(Some(SymbolHeapData::dummy())); + heap.numbers.push(Some(NumberHeapData::new(0.0))); + heap.bigints.push(Some(BigIntHeapData::dummy())); + heap.objects.push(Some(ObjectHeapData::dummy())); + heap.arrays.push(Some(ArrayHeapData::dummy())); + heap.functions.push(Some(FunctionHeapData::dummy())); heap } @@ -145,255 +233,46 @@ impl Heap { self.numbers.len() as u32 } - pub(crate) fn create_function( - &mut self, - name: Value, - length: u8, - uses_arguments: bool, - binding: JsBindingFunction, - ) -> u32 { - let func_object_data = ObjectHeapData { - _extensible: true, - bits: HeapBits::new(), - entries: vec![ - ObjectEntry::new( - PropertyKey::from_str(self, "length"), - PropertyDescriptor::roxh(Value::SmiU(length as u32)), - ), - ObjectEntry::new( - PropertyKey::from_str(self, "name"), - PropertyDescriptor::roxh(name), - ), - ], - prototype: PropertyDescriptor::roh(Value::Object( - BuiltinObjectIndexes::FunctionPrototypeIndex as u32, - )), - }; - self.objects.push(Some(func_object_data)); - let func_data = FunctionHeapData { - binding, - bits: HeapBits::new(), - bound: None, - length, - object_index: self.objects.len() as u32, - uses_arguments, - visible: None, - }; - self.functions.push(Some(func_data)); - self.functions.len() as u32 - } - - pub(crate) fn create_object(&mut self, entries: Vec) -> u32 { - let object_data = ObjectHeapData { - _extensible: true, - bits: HeapBits::new(), - entries, - prototype: PropertyDescriptor::roh(Value::Object( - BuiltinObjectIndexes::ObjectPrototypeIndex as u32, - )), - }; - self.objects.push(Some(object_data)); - self.objects.len() as u32 - } - - pub(crate) fn create_null_object(&mut self, entries: Vec) -> u32 { - let object_data = ObjectHeapData { - _extensible: true, - bits: HeapBits::new(), - entries, - prototype: PropertyDescriptor::roh(Value::Null), - }; - self.objects.push(Some(object_data)); - self.objects.len() as u32 - } - - fn partial_trace(&mut self) -> () { - // TODO: Consider roots count - for global in self.globals.iter() { - global.trace(self); - } - for error in self.errors.iter() { - let Some(data) = error else { - continue; - }; - let marked = data.bits.marked.take(); - data.bits.marked.set(marked); - if !marked { - continue; - } - let dirty = data.bits.dirty.take(); - data.bits.dirty.set(dirty); - if dirty { - error.trace(self); - } - } - for function in self.functions.iter() { - let Some(data) = function else { - continue; - }; - let marked = data.bits.marked.take(); - data.bits.marked.set(marked); - if !marked { - continue; - } - let dirty = data.bits.dirty.take(); - data.bits.dirty.set(dirty); - if dirty { - function.trace(self); - } - } - for object in self.objects.iter() { - let Some(data) = object else { - continue; - }; - let marked = data.bits.marked.take(); - data.bits.marked.set(marked); - if !marked { - continue; - } - let dirty = data.bits.dirty.take(); - data.bits.dirty.set(dirty); - if dirty { - object.trace(self); - } - } - for symbol in self.symbols.iter() { - let Some(data) = symbol else { - continue; - }; - let marked = data.bits.marked.take(); - data.bits.marked.set(marked); - if !marked { - continue; - } - let dirty = data.bits.dirty.take(); - data.bits.dirty.set(dirty); - if dirty { - symbol.trace(self); - } - } - stop_the_world(); - // Repeat above tracing to check for mutations that happened while we were tracing. - for object in self.objects.iter_mut() { - let Some(data) = object else { - continue; - }; - let marked = data.bits.marked.replace(true); - if !marked { - let _ = object.take(); - } - } - for string in self.strings.iter_mut() { - let Some(data) = string else { - continue; - }; - let marked = data.bits.marked.replace(true); - if !marked { - let _ = string.take(); - } - } - for symbol in self.symbols.iter_mut() { - let Some(data) = symbol else { - continue; - }; - let marked = data.bits.marked.replace(true); - if !marked { - let _ = symbol.take(); - } - } - for number in self.numbers.iter_mut() { - let Some(data) = number else { - continue; - }; - let marked = data.bits.marked.replace(true); - if !marked { - let _ = number.take(); - } - } - for bigint in self.bigints.iter_mut() { - let Some(data) = bigint else { - continue; - }; - let marked = data.bits.marked.replace(true); - if !marked { - let _ = bigint.take(); - } - } - while self.objects.last().is_none() { - self.objects.pop(); - } - while self.strings.last().is_none() { - self.strings.pop(); - } - while self.symbols.last().is_none() { - self.symbols.pop(); - } - while self.numbers.last().is_none() { - self.numbers.pop(); - } - while self.bigints.last().is_none() { - self.bigints.pop(); - } - start_the_world(); - } - fn complete_trace(&mut self) -> () { // TODO: Consider roots count - for error in self.errors.iter() { - let Some(data) = error else { - continue; - }; - data.bits.marked.set(false); - data.bits.dirty.set(false); - } - for function in self.functions.iter() { - let Some(data) = function else { - continue; - }; - data.bits.marked.set(false); - data.bits.dirty.set(false); - } - for object in self.objects.iter() { + for object in self.objects.iter().skip(1) { let Some(data) = object else { continue; }; data.bits.marked.set(false); data.bits.dirty.set(false); } - for string in self.strings.iter() { + for string in self.strings.iter().skip(1) { let Some(data) = string else { continue; }; data.bits.marked.set(false); data.bits.dirty.set(false); } - for symbol in self.symbols.iter() { + for symbol in self.symbols.iter().skip(1) { let Some(data) = symbol else { continue; }; data.bits.marked.set(false); data.bits.dirty.set(false); } - for number in self.numbers.iter() { + for number in self.numbers.iter().skip(1) { let Some(data) = number else { continue; }; data.bits.marked.set(false); data.bits.dirty.set(false); } - for bigint in self.bigints.iter() { + for bigint in self.bigints.iter().skip(1) { let Some(data) = bigint else { continue; }; data.bits.marked.set(false); data.bits.dirty.set(false); } - for global in self.globals.iter() { - global.trace(self); - } stop_the_world(); // Trace from dirty objects and symbols. - for object in self.objects.iter_mut() { + for object in self.objects.iter_mut().skip(1) { let Some(data) = object else { continue; }; @@ -402,7 +281,7 @@ impl Heap { let _ = object.take(); } } - for string in self.strings.iter_mut() { + for string in self.strings.iter_mut().skip(1) { let Some(data) = string else { continue; }; @@ -411,7 +290,7 @@ impl Heap { let _ = string.take(); } } - for symbol in self.symbols.iter_mut() { + for symbol in self.symbols.iter_mut().skip(1) { let Some(data) = symbol else { continue; }; @@ -420,7 +299,7 @@ impl Heap { let _ = symbol.take(); } } - for number in self.numbers.iter_mut() { + for number in self.numbers.iter_mut().skip(1) { let Some(data) = number else { continue; }; @@ -429,7 +308,7 @@ impl Heap { let _ = number.take(); } } - for bigint in self.bigints.iter_mut() { + for bigint in self.bigints.iter_mut().skip(1) { let Some(data) = bigint else { continue; }; @@ -438,6 +317,24 @@ impl Heap { let _ = bigint.take(); } } + for object in self.objects.iter_mut().skip(1) { + let Some(data) = object else { + continue; + }; + let marked = data.bits.marked.replace(true); + if !marked { + let _ = object.take(); + } + } + for function in self.functions.iter_mut().skip(1) { + let Some(data) = function else { + continue; + }; + let marked = data.bits.marked.replace(true); + if !marked { + let _ = function.take(); + } + } while self.objects.last().is_none() { self.objects.pop(); } @@ -453,6 +350,12 @@ impl Heap { while self.bigints.last().is_none() { self.bigints.pop(); } + while self.objects.last().is_none() { + self.objects.pop(); + } + while self.functions.last().is_none() { + self.functions.pop(); + } start_the_world(); } } @@ -460,39 +363,39 @@ impl Heap { impl HeapTrace for Value { fn trace(&self, heap: &Heap) { match self { - &Value::Error(idx) => heap.errors[idx as usize].trace(heap), - &Value::Function(idx) => heap.functions[idx as usize].trace(heap), - &Value::HeapBigInt(idx) => heap.bigints[idx as usize].trace(heap), - &Value::HeapNumber(idx) => heap.numbers[idx as usize].trace(heap), - &Value::HeapString(idx) => heap.strings[idx as usize].trace(heap), - &Value::Object(idx) => heap.objects[idx as usize].trace(heap), - &Value::Symbol(idx) => heap.symbols[idx as usize].trace(heap), + &Value::String(handle) => heap.strings[handle.id.get() as usize].trace(heap), + &Value::Symbol(handle) => heap.symbols[handle.id.get() as usize].trace(heap), + &Value::Number(handle) => heap.numbers[handle.id.get() as usize].trace(heap), + &Value::BigInt(handle) => heap.bigints[handle.id.get() as usize].trace(heap), + &Value::Object(handle) => heap.objects[handle.id.get() as usize].trace(heap), + &Value::ArrayObject(handle) => heap.arrays[handle.id.get() as usize].trace(heap), + &Value::Function(handle) => heap.functions[handle.id.get() as usize].trace(heap), _ => {} } } fn root(&self, heap: &Heap) { match self { - &Value::Error(idx) => heap.errors[idx as usize].root(heap), - &Value::Function(idx) => heap.functions[idx as usize].root(heap), - &Value::HeapBigInt(idx) => heap.bigints[idx as usize].root(heap), - &Value::HeapNumber(idx) => heap.numbers[idx as usize].root(heap), - &Value::HeapString(idx) => heap.strings[idx as usize].root(heap), - &Value::Object(idx) => heap.objects[idx as usize].root(heap), - &Value::Symbol(idx) => heap.symbols[idx as usize].root(heap), + &Value::String(handle) => heap.strings[handle.id.get() as usize].root(heap), + &Value::Symbol(handle) => heap.symbols[handle.id.get() as usize].root(heap), + &Value::Number(handle) => heap.numbers[handle.id.get() as usize].root(heap), + &Value::BigInt(handle) => heap.bigints[handle.id.get() as usize].root(heap), + &Value::Object(handle) => heap.objects[handle.id.get() as usize].root(heap), + &Value::ArrayObject(handle) => heap.arrays[handle.id.get() as usize].root(heap), + &Value::Function(handle) => heap.functions[handle.id.get() as usize].root(heap), _ => {} } } fn unroot(&self, heap: &Heap) { match self { - &Value::Error(idx) => heap.errors[idx as usize].unroot(heap), - &Value::Function(idx) => heap.functions[idx as usize].unroot(heap), - &Value::HeapBigInt(idx) => heap.bigints[idx as usize].unroot(heap), - &Value::HeapNumber(idx) => heap.numbers[idx as usize].unroot(heap), - &Value::HeapString(idx) => heap.strings[idx as usize].unroot(heap), - &Value::Object(idx) => heap.objects[idx as usize].unroot(heap), - &Value::Symbol(idx) => heap.symbols[idx as usize].unroot(heap), + &Value::String(handle) => heap.strings[handle.id.get() as usize].unroot(heap), + &Value::Symbol(handle) => heap.symbols[handle.id.get() as usize].unroot(heap), + &Value::Number(handle) => heap.numbers[handle.id.get() as usize].unroot(heap), + &Value::BigInt(handle) => heap.bigints[handle.id.get() as usize].unroot(heap), + &Value::Object(handle) => heap.objects[handle.id.get() as usize].unroot(heap), + &Value::ArrayObject(handle) => heap.arrays[handle.id.get() as usize].unroot(heap), + &Value::Function(handle) => heap.functions[handle.id.get() as usize].unroot(heap), _ => {} } } @@ -503,7 +406,7 @@ impl HeapTrace for Value { } // TODO: Change to using vectors of u8 bitfields for mark and dirty bits. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct HeapBits { marked: Cell, _weak_marked: Cell, @@ -539,10 +442,3 @@ impl HeapBits { } unsafe impl Sync for HeapBits {} - -#[test] -fn init_heap() { - let heap = Heap::new(); - assert!(heap.objects.len() >= LAST_BUILTIN_OBJECT_INDEX as usize); - println!("{:#?}", heap); -} diff --git a/nova_vm/src/heap/array.rs b/nova_vm/src/heap/array.rs index aff90c98..541bec3d 100644 --- a/nova_vm/src/heap/array.rs +++ b/nova_vm/src/heap/array.rs @@ -1,30 +1,42 @@ +use super::{heap_trace::HeapTrace, Handle, ObjectHeapData}; use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - Heap, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, + heap::{GetHeapData, Heap, HeapBits}, + types::{Object, Value}, }; -use super::{ - function::FunctionHeapData, - heap_constants::WellKnownSymbolIndexes, - heap_trace::HeapTrace, - object::{ObjectEntry, PropertyKey}, -}; - -#[derive(Debug)] -pub(crate) struct ArrayHeapData { - pub(super) bits: HeapBits, - pub(super) object_index: u32, +#[derive(Debug, Clone)] +pub struct ArrayHeapData { + pub(crate) bits: HeapBits, + pub(crate) object: Option, // TODO: Use SmallVec<[Value; 4]> - pub(super) elements: Vec>, + pub(crate) elements: Vec>, +} + +impl ArrayHeapData { + pub fn dummy() -> Self { + Self { + bits: HeapBits::new(), + object: None, + elements: Vec::new(), + } + } } impl HeapTrace for Option { fn trace(&self, heap: &Heap) { assert!(self.is_some()); - heap.objects[self.as_ref().unwrap().object_index as usize].trace(heap); + + let data = self.as_ref().unwrap(); + + if let Some(object) = data.object { + object.into_value().trace(heap); + } + + for value in data.elements.iter() { + if let Some(value) = value { + value.trace(heap); + } + } } fn root(&self, _heap: &Heap) { assert!(self.is_some()); @@ -40,161 +52,3 @@ impl HeapTrace for Option { self.take(); } } - -pub fn initialize_array_heap(heap: &mut Heap) { - let species_function_name = Value::new_string(heap, "get [Symbol.species]"); - let at_key = PropertyKey::from_str(heap, "at"); - let copy_within_key = PropertyKey::from_str(heap, "copyWithin"); - let entries_key = PropertyKey::from_str(heap, "entries"); - let fill_key = PropertyKey::from_str(heap, "fill"); - let find_key = PropertyKey::from_str(heap, "find"); - let find_index_key = PropertyKey::from_str(heap, "findIndex"); - let find_last_key = PropertyKey::from_str(heap, "findLast"); - let find_last_index_key = PropertyKey::from_str(heap, "findLastIndex"); - let flat_key = PropertyKey::from_str(heap, "flat"); - let flat_map_key = PropertyKey::from_str(heap, "flatMap"); - let includes_key = PropertyKey::from_str(heap, "includes"); - let keys_key = PropertyKey::from_str(heap, "keys"); - let to_reversed_key = PropertyKey::from_str(heap, "toReversed"); - let to_sorted_key = PropertyKey::from_str(heap, "toSorted"); - let to_spliced_key = PropertyKey::from_str(heap, "toSpliced"); - let values_key = PropertyKey::from_str(heap, "values"); - heap.objects[BuiltinObjectIndexes::ArrayConstructorIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ - ObjectEntry::new_prototype_function_entry(heap, "from", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "isArray", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "of", 0, true, array_todo), - ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::ArrayPrototypeIndex as u32, - ), - ObjectEntry::new( - PropertyKey::Symbol(WellKnownSymbolIndexes::Species as u32), - PropertyDescriptor::ReadOnly { - get: heap.create_function(species_function_name, 0, false, array_species), - enumerable: false, - configurable: true, - }, - ), - ], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::ArrayConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::ArrayConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: array_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::ArrayPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new_prototype_function_entry(heap, "at", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "concat", 1, true, array_todo), - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::ArrayConstructorIndex, - ))), - ), - ObjectEntry::new_prototype_function_entry(heap, "copyWithin", 2, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "entries", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "every", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "fill", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "filter", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "find", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "findIndex", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "findLast", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "findLastIndex", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "flat", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "flatMap", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "forEach", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "includes", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "indexOf", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "join", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "keys", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "lastIndexOf", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "map", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "pop", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "push", 1, true, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "reduce", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "reduceRight", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "reverse", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "shift", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "slice", 2, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "some", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "sort", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "splice", 2, true, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "toLocaleString", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "toReversed", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "toSorted", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "toSpliced", 2, true, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "unshift", 1, true, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "values", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "with", 2, false, array_todo), - // TODO: These symbol function properties are actually rwxh, this helper generates roxh instead. - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.iterator]", - WellKnownSymbolIndexes::Iterator as u32, - 0, - false, - array_todo, - ), - ObjectEntry::new( - PropertyKey::Symbol(WellKnownSymbolIndexes::Unscopables as u32), - PropertyDescriptor::roxh(Value::Object(heap.create_object(vec![ - ObjectEntry::new(at_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new( - copy_within_key, - PropertyDescriptor::rwx(Value::Boolean(true)), - ), - ObjectEntry::new(entries_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new(fill_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new(find_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new( - find_index_key, - PropertyDescriptor::rwx(Value::Boolean(true)), - ), - ObjectEntry::new(find_last_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new( - find_last_index_key, - PropertyDescriptor::rwx(Value::Boolean(true)), - ), - ObjectEntry::new(flat_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new(flat_map_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new(includes_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new(keys_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new( - to_reversed_key, - PropertyDescriptor::rwx(Value::Boolean(true)), - ), - ObjectEntry::new(to_sorted_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new( - to_spliced_key, - PropertyDescriptor::rwx(Value::Boolean(true)), - ), - ObjectEntry::new(values_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ]))), - ), - ], - )); -} - -fn array_constructor_binding(_heap: &mut Heap, _this: Value, _args: &[Value]) -> JsResult { - Ok(Value::Function(0)) -} - -fn array_species(_heap: &mut Heap, this: Value, _args: &[Value]) -> JsResult { - Ok(this) -} - -fn array_todo(_heap: &mut Heap, _this: Value, _args: &[Value]) -> JsResult { - todo!() -} diff --git a/nova_vm/src/heap/bigint.rs b/nova_vm/src/heap/bigint.rs index 5eddbb70..16310904 100644 --- a/nova_vm/src/heap/bigint.rs +++ b/nova_vm/src/heap/bigint.rs @@ -1,32 +1,18 @@ -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - FunctionHeapData, Heap, HeapBits, ObjectEntry, ObjectHeapData, PropertyDescriptor, - PropertyKey, - }, - value::{JsResult, Value}, -}; - use super::heap_trace::HeapTrace; +use crate::heap::{Heap, HeapBits}; +use num_bigint_dig::BigInt; -#[derive(Debug)] -pub(crate) struct BigIntHeapData { +#[derive(Debug, Clone)] +pub struct BigIntHeapData { pub(super) bits: HeapBits, - pub(super) sign: bool, - pub(super) len: u32, - pub(super) parts: Box<[u64]>, + pub(super) data: BigInt, } impl BigIntHeapData { - pub(crate) fn len(&self) -> u32 { - self.len - } - - pub(crate) fn try_into_f64(&self) -> Option { - if self.len == 1 { - Some(self.parts[0] as f64) - } else { - None + pub fn dummy() -> Self { + Self { + bits: HeapBits::new(), + data: BigInt::default(), } } } @@ -48,109 +34,3 @@ impl HeapTrace for Option { self.take(); } } - -pub fn initialize_bigint_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::BigintConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ - ObjectEntry::new_prototype_function_entry( - heap, - "asIntN", - 2, - false, - bigint_as_int_n, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "asUintN", - 2, - false, - bigint_as_uint_n, - ), - ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::BigintPrototypeIndex as u32, - ), - ], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::BigintConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: heap.objects.len() as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: bigint_constructor, - }); - heap.objects[BuiltinObjectIndexes::BigintPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::BigintConstructorIndex, - ))), - ), - ObjectEntry::new_prototype_function_entry( - heap, - "toLocaleString", - 0, - false, - bigint_prototype_to_locale_string, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "toString", - 0, - false, - bigint_prototype_to_string, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "valueOf", - 0, - false, - bigint_prototype_value_of, - ), - // @@ToStringTag - // ObjectEntry { key: PropertyKey::Symbol(), PropertyDescriptor } - ], - )); -} - -fn bigint_constructor(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult { - if !this.is_undefined() { - // TODO: Throw TypeError - return Err(Value::Error(0)); - } else { - return Ok(Value::SmallBigInt(3)); - } -} - -fn bigint_as_int_n(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::SmallBigInt(3)) -} - -fn bigint_as_uint_n(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult { - Ok(Value::SmallBigIntU(3)) -} - -fn bigint_prototype_to_locale_string( - heap: &mut Heap, - this: Value, - args: &[Value], -) -> JsResult { - Ok(Value::new_string(heap, "BigInt(3n)")) -} - -fn bigint_prototype_to_string(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult { - Ok(Value::new_string(heap, "BigInt(3n)")) -} - -fn bigint_prototype_value_of(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult { - Ok(Value::new_string(heap, "BigInt(3n)")) -} diff --git a/nova_vm/src/heap/boolean.rs b/nova_vm/src/heap/boolean.rs deleted file mode 100644 index e4e11d94..00000000 --- a/nova_vm/src/heap/boolean.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - FunctionHeapData, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, -}; - -use super::{ - object::{ObjectEntry, PropertyKey}, - Heap, -}; - -pub fn initialize_boolean_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::BooleanConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::BooleanPrototypeIndex as u32, - )], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::BooleanConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::BooleanConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: boolean_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::BooleanPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::BooleanConstructorIndex, - ))), - ), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, boolean_todo), - ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false, boolean_todo), - ], - )); -} - -fn boolean_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::Boolean(false)) -} - -fn boolean_todo(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - todo!(); -} diff --git a/nova_vm/src/heap/date.rs b/nova_vm/src/heap/date.rs deleted file mode 100644 index e89545f6..00000000 --- a/nova_vm/src/heap/date.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::time::SystemTime; - -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - Heap, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, -}; - -use super::{ - function::FunctionHeapData, - heap_constants::WellKnownSymbolIndexes, - heap_trace::HeapTrace, - object::{ObjectEntry, PropertyKey}, -}; - -#[derive(Debug)] -pub(crate) struct DateHeapData { - pub(super) bits: HeapBits, - pub(super) object_index: u32, - pub(super) _date: SystemTime, -} - -impl HeapTrace for Option { - fn trace(&self, heap: &Heap) { - assert!(self.is_some()); - heap.objects[self.as_ref().unwrap().object_index as usize].trace(heap); - } - fn root(&self, _heap: &Heap) { - assert!(self.is_some()); - self.as_ref().unwrap().bits.root(); - } - - fn unroot(&self, _heap: &Heap) { - assert!(self.is_some()); - self.as_ref().unwrap().bits.unroot(); - } - - fn finalize(&mut self, _heap: &Heap) { - self.take(); - } -} - -pub fn initialize_date_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::DateConstructorIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ - ObjectEntry::new_prototype_function_entry(heap, "now", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "parse", 1, false, date_todo), - ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::DatePrototypeIndex as u32, - ), - ObjectEntry::new_prototype_function_entry(heap, "UTC", 7, false, date_todo), - ], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::DateConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::DateConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: date_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::DatePrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::DateConstructorIndex, - ))), - ), - ObjectEntry::new_prototype_function_entry(heap, "getDate", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getDay", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getFullYear", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getHours", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getMilliseconds", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getMinutes", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getMonth", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getSeconds", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getTime", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "getTimezoneOffset", - 0, - false, - date_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "getUTCDate", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getUTCDay", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getUTCFullYear", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getUTCHours", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "getUTCMilliseconds", - 0, - false, - date_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "getUTCMinutes", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getUTCMonth", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getUTCSeconds", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setDate", 1, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setFullYear", 3, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setHours", 4, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setMilliseconds", 1, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setMinutes", 3, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setMonth", 2, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setSeconds", 2, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setTime", 1, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setUTCDate", 1, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setUTCFullYear", 3, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setUTCHours", 4, false, date_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "setUTCMilliseconds", - 1, - false, - date_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "setUTCMinutes", 3, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setUTCMonth", 2, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setUTCSeconds", 2, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "toDateString", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "toJSON", 1, false, date_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "toLocaleDateString", - 0, - false, - date_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "toLocaleString", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "toLocaleTimeString", - 0, - false, - date_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "toTimeString", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "toUTCString", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false, date_todo), - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.toPrimitive]", - WellKnownSymbolIndexes::ToPrimitive as u32, - 1, - false, - date_todo, - ), - ], - )); -} - -fn date_constructor_binding(_heap: &mut Heap, _this: Value, _args: &[Value]) -> JsResult { - Ok(Value::Function(0)) -} - -fn date_todo(_heap: &mut Heap, _this: Value, _args: &[Value]) -> JsResult { - todo!() -} diff --git a/nova_vm/src/heap/error.rs b/nova_vm/src/heap/error.rs deleted file mode 100644 index c4027107..00000000 --- a/nova_vm/src/heap/error.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - Heap, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, -}; - -use super::{ - function::FunctionHeapData, - heap_trace::HeapTrace, - object::{ObjectEntry, PropertyKey}, -}; - -#[derive(Debug)] -pub(crate) struct ErrorHeapData { - pub(super) bits: HeapBits, - pub(super) object_index: u32, - // TODO: stack? name? -} - -impl HeapTrace for Option { - fn trace(&self, heap: &Heap) { - assert!(self.is_some()); - heap.objects[self.as_ref().unwrap().object_index as usize].trace(heap); - } - fn root(&self, _heap: &Heap) { - assert!(self.is_some()); - self.as_ref().unwrap().bits.root(); - } - - fn unroot(&self, _heap: &Heap) { - assert!(self.is_some()); - self.as_ref().unwrap().bits.unroot(); - } - - fn finalize(&mut self, _heap: &Heap) { - self.take(); - } -} - -pub fn initialize_error_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::ErrorConstructorIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::ErrorPrototypeIndex as u32, - )], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::ErrorConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::ErrorConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: error_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::ErrorPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::ErrorConstructorIndex, - ))), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "name"), - PropertyDescriptor::rwx(Value::EmptyString), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "name"), - PropertyDescriptor::rwx(Value::new_string(heap, "Error")), - ), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, error_todo), - ], - )); -} - -fn error_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::Function(0)) -} - -fn error_todo(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - todo!() -} diff --git a/nova_vm/src/heap/function.rs b/nova_vm/src/heap/function.rs index 851feb3d..0c2ef74d 100644 --- a/nova_vm/src/heap/function.rs +++ b/nova_vm/src/heap/function.rs @@ -1,35 +1,43 @@ +use super::{heap_trace::HeapTrace, Handle, HeapBits, ObjectHeapData}; use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - Heap, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, + builtins::{todo_builtin, Behaviour}, + types::{Object, Value}, + Heap, }; -use super::{ - heap_constants::WellKnownSymbolIndexes, - heap_trace::HeapTrace, - object::{ObjectEntry, PropertyKey}, -}; - -pub type JsBindingFunction = fn(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult; +#[derive(Debug, Clone)] +pub struct FunctionHeapData { + pub(crate) bits: HeapBits, + pub(crate) object: Option, + pub(crate) initial_name: Value, + pub(crate) length: i64, + pub(crate) behaviour: Behaviour, + // TODO: Should we create a `BoundFunctionHeapData` for an exotic object + // that allows setting fields and other deoptimizations? + // pub(super) uses_arguments: bool, + // pub(super) bound: Option>, + // pub(super) visible: Option>, +} -#[derive(Debug)] -pub(crate) struct FunctionHeapData { - pub(super) bits: HeapBits, - pub(super) object_index: u32, - pub(super) length: u8, - pub(super) uses_arguments: bool, - pub(super) bound: Option>, - pub(super) visible: Option>, - pub(super) binding: JsBindingFunction, - // TODO: Should name be here as an "internal slot" of sorts? +impl FunctionHeapData { + pub fn dummy() -> Self { + Self { + bits: HeapBits::new(), + object: None, + initial_name: Value::Null, + length: 0, + behaviour: Behaviour::Regular(todo_builtin), + } + } } impl HeapTrace for Option { fn trace(&self, heap: &Heap) { assert!(self.is_some()); - heap.objects[self.as_ref().unwrap().object_index as usize].trace(heap); + + if let Some(object) = self.as_ref().unwrap().object { + object.into_value().trace(heap); + } } fn root(&self, _heap: &Heap) { assert!(self.is_some()); @@ -45,68 +53,3 @@ impl HeapTrace for Option { self.take(); } } - -pub fn initialize_function_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::FunctionConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::FunctionPrototypeIndex as u32, - )], - )); - heap.functions - [get_constructor_index(BuiltinObjectIndexes::FunctionConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::FunctionConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: function_constructor_binding, - }); - // NOTE: According to ECMAScript spec https://tc39.es/ecma262/#sec-properties-of-the-function-prototype-object - // the %Function.prototype% object should itself be a function that always returns undefined. This is not - // upheld here and we probably do not care. It's seemingly the only prototype that is a function. - heap.objects[BuiltinObjectIndexes::FunctionPrototypeIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new_prototype_function_entry(heap, "apply", 2, false, function_todo), - ObjectEntry::new_prototype_function_entry(heap, "bind", 1, true, function_todo), - ObjectEntry::new_prototype_function_entry(heap, "call", 1, true, function_todo), - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::FunctionConstructorIndex, - ))), - ), - ObjectEntry::new_prototype_function_entry( - heap, - "toString", - 0, - false, - function_todo, - ), - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "hasInstance", - WellKnownSymbolIndexes::HasInstance as u32, - 1, - false, - function_todo, - ), - ], - )); -} - -fn function_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::Function(0)) -} - -fn function_todo(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - todo!() -} diff --git a/nova_vm/src/heap/heap_constants.rs b/nova_vm/src/heap/heap_constants.rs index f4921ce7..3b24c7e3 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -9,6 +9,7 @@ // +==================================================================+ #[repr(u32)] +#[derive(Debug, Clone, Copy)] pub enum BuiltinObjectIndexes { // Fundamental objects ObjectPrototypeIndex, @@ -131,6 +132,12 @@ pub enum BuiltinObjectIndexes { ProxyConstructorIndex, } +impl Default for BuiltinObjectIndexes { + fn default() -> Self { + Self::ObjectPrototypeIndex + } +} + pub const LAST_BUILTIN_OBJECT_INDEX: u32 = BuiltinObjectIndexes::ProxyConstructorIndex as u32; pub const FIRST_CONSTRUCTOR_INDEX: u32 = BuiltinObjectIndexes::ObjectConstructorIndex as u32; diff --git a/nova_vm/src/heap/math.rs b/nova_vm/src/heap/math.rs deleted file mode 100644 index 39e1c750..00000000 --- a/nova_vm/src/heap/math.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::value::{JsResult, Value}; - -use super::{ - heap_constants::WellKnownSymbolIndexes, - object::{ObjectEntry, PropertyDescriptor, PropertyKey}, - Heap, -}; - -pub(super) fn initialize_math_object(heap: &mut Heap) { - let e = Value::from_f64(heap, std::f64::consts::E); - let ln10 = Value::from_f64(heap, std::f64::consts::LN_10); - let ln2 = Value::from_f64(heap, std::f64::consts::LN_2); - let log10e = Value::from_f64(heap, std::f64::consts::LOG10_E); - let log2e = Value::from_f64(heap, std::f64::consts::LOG2_E); - let pi = Value::from_f64(heap, std::f64::consts::PI); - let sqrt1_2 = Value::from_f64(heap, std::f64::consts::FRAC_1_SQRT_2); - let sqrt2 = Value::from_f64(heap, std::f64::consts::SQRT_2); - let abs = ObjectEntry::new_prototype_function_entry(heap, "abs", 1, false, math_todo); - let acos = ObjectEntry::new_prototype_function_entry(heap, "acos", 1, false, math_todo); - let acosh = ObjectEntry::new_prototype_function_entry(heap, "acosh", 1, false, math_todo); - let asin = ObjectEntry::new_prototype_function_entry(heap, "asin", 1, false, math_todo); - let asinh = ObjectEntry::new_prototype_function_entry(heap, "asinh", 1, false, math_todo); - let atan = ObjectEntry::new_prototype_function_entry(heap, "atan", 1, false, math_todo); - let atanh = ObjectEntry::new_prototype_function_entry(heap, "atanh", 1, false, math_todo); - let atan2 = ObjectEntry::new_prototype_function_entry(heap, "atan2", 2, false, math_todo); - let cbrt = ObjectEntry::new_prototype_function_entry(heap, "cbrt", 1, false, math_todo); - let ceil = ObjectEntry::new_prototype_function_entry(heap, "ceil", 1, false, math_todo); - let clz32 = ObjectEntry::new_prototype_function_entry(heap, "clz32", 1, false, math_todo); - let cos = ObjectEntry::new_prototype_function_entry(heap, "cos", 1, false, math_todo); - let cosh = ObjectEntry::new_prototype_function_entry(heap, "cosh", 1, false, math_todo); - let exp = ObjectEntry::new_prototype_function_entry(heap, "exp", 1, false, math_todo); - let expm1 = ObjectEntry::new_prototype_function_entry(heap, "expm1", 1, false, math_todo); - let floor = ObjectEntry::new_prototype_function_entry(heap, "floor", 1, false, math_todo); - let fround = ObjectEntry::new_prototype_function_entry(heap, "fround", 1, false, math_todo); - let hypot = ObjectEntry::new_prototype_function_entry(heap, "hypot", 2, true, math_todo); - let imul = ObjectEntry::new_prototype_function_entry(heap, "imul", 2, false, math_todo); - let log = ObjectEntry::new_prototype_function_entry(heap, "log", 1, false, math_todo); - let log1p = ObjectEntry::new_prototype_function_entry(heap, "log1p", 1, false, math_todo); - let log10 = ObjectEntry::new_prototype_function_entry(heap, "log10", 1, false, math_todo); - let log2 = ObjectEntry::new_prototype_function_entry(heap, "log2", 1, false, math_todo); - let max = ObjectEntry::new_prototype_function_entry(heap, "max", 2, true, math_todo); - let min = ObjectEntry::new_prototype_function_entry(heap, "min", 2, true, math_todo); - let pow = ObjectEntry::new_prototype_function_entry(heap, "pow", 2, false, math_todo); - let random = ObjectEntry::new_prototype_function_entry(heap, "random", 0, false, math_todo); - let round = ObjectEntry::new_prototype_function_entry(heap, "round", 1, false, math_todo); - let sign = ObjectEntry::new_prototype_function_entry(heap, "sign", 1, false, math_todo); - let sin = ObjectEntry::new_prototype_function_entry(heap, "sin", 1, false, math_todo); - let sinh = ObjectEntry::new_prototype_function_entry(heap, "sinh", 1, false, math_todo); - let sqrt = ObjectEntry::new_prototype_function_entry(heap, "sqrt", 1, false, math_todo); - let tan = ObjectEntry::new_prototype_function_entry(heap, "tan", 1, false, math_todo); - let tanh = ObjectEntry::new_prototype_function_entry(heap, "tanh", 1, false, math_todo); - let trunc = ObjectEntry::new_prototype_function_entry(heap, "trunc", 1, false, math_todo); - let entries = vec![ - ObjectEntry::new_frozen_entry(heap, "E", e), - ObjectEntry::new_frozen_entry(heap, "LN10", ln10), - ObjectEntry::new_frozen_entry(heap, "LN2", ln2), - ObjectEntry::new_frozen_entry(heap, "LOG10E", log10e), - ObjectEntry::new_frozen_entry(heap, "LOG2E", log2e), - ObjectEntry::new_frozen_entry(heap, "PI", pi), - ObjectEntry::new_frozen_entry(heap, "SQRT1_2", sqrt1_2), - ObjectEntry::new_frozen_entry(heap, "SQRT2", sqrt2), - ObjectEntry::new( - PropertyKey::Symbol(WellKnownSymbolIndexes::ToStringTag as u32), - PropertyDescriptor::roxh(Value::new_string(heap, "Math")), - ), - abs, - acos, - acosh, - asin, - asinh, - atan, - atanh, - atan2, - cbrt, - ceil, - clz32, - cos, - cosh, - exp, - expm1, - floor, - fround, - hypot, - imul, - log, - log1p, - log10, - log2, - max, - min, - pow, - random, - round, - sign, - sin, - sinh, - sqrt, - tan, - tanh, - trunc, - ]; - let _ = heap.create_object(entries); -} - -fn math_todo(_heap: &mut Heap, _this: Value, _args: &[Value]) -> JsResult { - todo!(); -} diff --git a/nova_vm/src/heap/number.rs b/nova_vm/src/heap/number.rs index 11305c6b..c3290d1d 100644 --- a/nova_vm/src/heap/number.rs +++ b/nova_vm/src/heap/number.rs @@ -1,20 +1,8 @@ -use std::vec; +use super::{heap_trace::HeapTrace, Heap}; +use crate::heap::HeapBits; -use super::{ - heap_trace::HeapTrace, - object::{ObjectEntry, PropertyKey}, - Heap, -}; -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - FunctionHeapData, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, -}; - -#[derive(Debug)] -pub(crate) struct NumberHeapData { +#[derive(Debug, Clone)] +pub struct NumberHeapData { pub(super) bits: HeapBits, pub(super) data: f64, } @@ -49,109 +37,3 @@ impl HeapTrace for Option { self.take(); } } - -pub fn initialize_number_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::NumberConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "EPSILON"), - PropertyDescriptor::roh(Value::from_f64(heap, f64::EPSILON)), - ), - ObjectEntry::new_prototype_function_entry(heap, "isFinite", 1, false, number_todo), - ObjectEntry::new_prototype_function_entry(heap, "isInteger", 1, false, number_todo), - ObjectEntry::new_prototype_function_entry(heap, "isNan", 1, false, number_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "isSafeInteger", - 1, - false, - number_todo, - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "MAX_SAFE_INTEGER"), - PropertyDescriptor::roh(Value::from_f64(heap, 9007199254740991.0)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "MAX_VALUE"), - PropertyDescriptor::roh(Value::from_f64(heap, f64::MAX)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "MIN_SAFE_INTEGER"), - PropertyDescriptor::roh(Value::from_f64(heap, -9007199254740991.0)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "MIN_VALUE"), - PropertyDescriptor::roh(Value::from_f64(heap, f64::MIN)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "NaN"), - PropertyDescriptor::roh(Value::NaN), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "NEGATIVE_INFINITY"), - PropertyDescriptor::roh(Value::NegativeInfinity), - ), - ObjectEntry::new_prototype_function_entry( - heap, - "parseFloat", - 1, - false, - number_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "parseInt", 2, false, number_todo), - ObjectEntry::new( - PropertyKey::from_str(heap, "POSITIVE_INFINITY"), - PropertyDescriptor::roh(Value::Infinity), - ), - ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::NumberPrototypeIndex as u32, - ), - ], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::NumberConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::NumberConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: number_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::NumberPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::NumberConstructorIndex, - ))), - ), - ObjectEntry::new_prototype_function_entry(heap, "toExponential", 1, false, number_todo), - ObjectEntry::new_prototype_function_entry(heap, "toExponential", 1, false, number_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "toLocaleString", - 0, - false, - number_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "toPrecision", 1, false, number_todo), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, number_todo), - ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false, number_todo), - ], - )); -} - -fn number_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::SmiU(0)) -} - -fn number_todo(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - todo!(); -} diff --git a/nova_vm/src/heap/object.rs b/nova_vm/src/heap/object.rs index 6bdbd8d0..f9409ae7 100644 --- a/nova_vm/src/heap/object.rs +++ b/nova_vm/src/heap/object.rs @@ -1,228 +1,14 @@ +use std::collections::HashMap; + use crate::{ - heap::{ - function::JsBindingFunction, - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - heap_trace::HeapTrace, - FunctionHeapData, Heap, HeapBits, - }, - stack_string::StackString, - value::{FunctionIndex, JsResult, StringIndex, SymbolIndex, Value}, + heap::{heap_trace::HeapTrace, Heap, HeapBits}, + types::{PropertyDescriptor, PropertyKey}, }; -use std::fmt::Debug; - -#[derive(Debug)] -pub struct ObjectEntry { - key: PropertyKey, - value: PropertyDescriptor, -} - -impl ObjectEntry { - pub(crate) fn new(key: PropertyKey, value: PropertyDescriptor) -> Self { - ObjectEntry { key, value } - } - - pub(crate) fn new_prototype_function_entry( - heap: &mut Heap, - name: &str, - length: u8, - uses_arguments: bool, - binding: JsBindingFunction, - ) -> Self { - let key = PropertyKey::from_str(heap, name); - let name = match key { - PropertyKey::SmallAsciiString(data) => Value::StackString(data.clone()), - PropertyKey::Smi(_) => unreachable!("No prototype functions should have SMI names"), - PropertyKey::String(idx) => Value::HeapString(idx), - PropertyKey::Symbol(idx) => Value::Symbol(idx), - }; - let func_index = heap.create_function(name, length, uses_arguments, binding); - let value = PropertyDescriptor::rwxh(Value::Function(func_index)); - ObjectEntry { key, value } - } - - pub(crate) fn new_prototype_symbol_function_entry( - heap: &mut Heap, - name: &str, - symbol_index: u32, - length: u8, - uses_arguments: bool, - binding: JsBindingFunction, - ) -> Self { - let name = Value::new_string(heap, name); - let key = PropertyKey::Symbol(symbol_index); - let func_index = heap.create_function(name, length, uses_arguments, binding); - let value = PropertyDescriptor::roxh(Value::Function(func_index)); - ObjectEntry { key, value } - } - - pub(crate) fn new_constructor_prototype_entry(heap: &mut Heap, idx: u32) -> Self { - ObjectEntry { - key: PropertyKey::from_str(heap, "prototype"), - value: PropertyDescriptor::Data { - value: Value::Object(idx), - writable: false, - enumerable: false, - configurable: false, - }, - } - } - - pub(crate) fn new_frozen_entry(heap: &mut Heap, key: &str, value: Value) -> Self { - ObjectEntry { - key: PropertyKey::from_str(heap, key), - value: PropertyDescriptor::roh(value), - } - } -} -#[derive(Debug)] -pub enum PropertyKey { - SmallAsciiString(StackString), - Smi(i32), - String(StringIndex), - Symbol(SymbolIndex), -} - -impl PropertyKey { - pub fn from_str(heap: &mut Heap, str: &str) -> Self { - if let Some(ascii_string) = StackString::try_from_str(str) { - PropertyKey::SmallAsciiString(ascii_string) - } else { - PropertyKey::String(heap.alloc_string(str)) - } - } -} - -#[derive(Debug)] -pub enum PropertyDescriptor { - Data { - value: Value, - writable: bool, - enumerable: bool, - configurable: bool, - }, - Blocked { - enumerable: bool, - configurable: bool, - }, - ReadOnly { - get: FunctionIndex, - enumerable: bool, - configurable: bool, - }, - WriteOnly { - set: FunctionIndex, - enumerable: bool, - configurable: bool, - }, - ReadWrite { - get: FunctionIndex, - set: FunctionIndex, - enumerable: bool, - configurable: bool, - }, -} - -impl PropertyDescriptor { - #[inline(always)] - pub const fn prototype_slot(idx: u32) -> Self { - Self::Data { - value: Value::Object(idx), - writable: false, - enumerable: false, - configurable: false, - } - } - - #[inline(always)] - /// Read-only, unconfigurable, enumerable data descriptor - pub const fn ro(value: Value) -> Self { - Self::Data { - value, - writable: false, - enumerable: true, - configurable: false, - } - } - #[inline(always)] - /// Read-only, unconfigurable, unenumerable data descriptor - pub const fn roh(value: Value) -> Self { - Self::Data { - value, - writable: false, - enumerable: false, - configurable: false, - } - } - - #[inline(always)] - /// Read-only, configurable, enumerable data descriptor - pub const fn rox(value: Value) -> Self { - Self::Data { - value, - writable: false, - enumerable: true, - configurable: true, - } - } - #[inline(always)] - /// Read-only, configurable, unenumerable data descriptor - pub const fn roxh(value: Value) -> Self { - Self::Data { - value, - writable: false, - enumerable: false, - configurable: true, - } - } - - #[inline(always)] - /// Writable, unconfigurable, enumerable data descriptor - pub const fn rw(value: Value) -> Self { - Self::Data { - value, - writable: true, - enumerable: false, - configurable: false, - } - } - #[inline(always)] - /// Writable, unconfigurable, unenumerable data descriptor - pub const fn rwh(value: Value) -> Self { - Self::Data { - value, - writable: true, - enumerable: false, - configurable: false, - } - } - - #[inline(always)] - /// Writable, configurable, enumerable data descriptor - pub const fn rwx(value: Value) -> Self { - Self::Data { - value, - writable: true, - enumerable: false, - configurable: true, - } - } - #[inline(always)] - /// Writable, configurable, unenumerable data descriptor - pub const fn rwxh(value: Value) -> Self { - Self::Data { - value, - writable: true, - enumerable: false, - configurable: true, - } - } -} - -#[derive(Debug)] -pub(crate) struct ObjectHeapData { +#[derive(Debug, Clone)] +pub struct ObjectHeapData { pub(crate) bits: HeapBits, - pub(crate) _extensible: bool, + pub(crate) extensible: bool, // TODO: It's probably not necessary to have a whole data descriptor here. // A prototype can only be set to be null or an object, meaning that most of the // possible Value options are impossible. @@ -230,24 +16,23 @@ pub(crate) struct ObjectHeapData { // with functions and possible other special object cases we want to track with partially // separate heap fields later down the line. pub(crate) prototype: PropertyDescriptor, + // TODO: Consider using detached vectors for keys/descriptors. pub(crate) entries: Vec, } +#[derive(Debug, Clone)] +pub struct ObjectEntry { + pub(crate) key: PropertyKey, + pub(crate) value: PropertyDescriptor, +} + impl ObjectHeapData { - pub fn new(extensible: bool, prototype: PropertyDescriptor, entries: Vec) -> Self { + pub fn dummy() -> Self { Self { bits: HeapBits::new(), - _extensible: extensible, - // TODO: Number, Boolean, etc. objects exist. These can all be - // modeled with their own heap vector or alternatively by adding - // a [[PrimitiveValue]] field to objects: Normally this field is None - // to signal that the object is its own primitive value. For - // Number objects etc the field is Some(Value). - // TODO: Move prototype and key vector into shapes - prototype, - // TODO: Separate entries into key and value vectors - // TODO: Use SmallVec<[T; 4]> - entries, + extensible: false, + prototype: PropertyDescriptor::default(), + entries: Vec::new(), } } } @@ -256,45 +41,23 @@ impl HeapTrace for Option { fn trace(&self, heap: &Heap) { assert!(self.is_some()); let data = self.as_ref().unwrap(); + let dirty = data.bits.dirty.replace(false); let marked = data.bits.marked.replace(true); + if marked && !dirty { // Do not keep recursing into already-marked heap values. return; } - match &data.prototype { - PropertyDescriptor::Data { value, .. } => value.trace(heap), - PropertyDescriptor::Blocked { .. } => {} - PropertyDescriptor::ReadOnly { get, .. } => { - heap.functions[*get as usize].trace(heap); - } - PropertyDescriptor::WriteOnly { set, .. } => { - heap.functions[*set as usize].trace(heap); - } - PropertyDescriptor::ReadWrite { get, set, .. } => { - heap.functions[*get as usize].trace(heap); - heap.functions[*set as usize].trace(heap); - } + + if let Some(value) = data.prototype.value { + value.trace(heap); } - for reference in data.entries.iter() { - match reference.key { - PropertyKey::SmallAsciiString(_) | PropertyKey::Smi(_) => {} - PropertyKey::String(idx) => heap.strings[idx as usize].trace(heap), - PropertyKey::Symbol(idx) => heap.symbols[idx as usize].trace(heap), - } - match &reference.value { - PropertyDescriptor::Data { value, .. } => value.trace(heap), - PropertyDescriptor::Blocked { .. } => {} - PropertyDescriptor::ReadOnly { get, .. } => { - heap.functions[*get as usize].trace(heap); - } - PropertyDescriptor::WriteOnly { set, .. } => { - heap.functions[*set as usize].trace(heap); - } - PropertyDescriptor::ReadWrite { get, set, .. } => { - heap.functions[*get as usize].trace(heap); - heap.functions[*set as usize].trace(heap); - } + + for entry in data.entries.iter() { + entry.key.into_value().trace(heap); + if let Some(value) = entry.value.value { + value.trace(heap); } } } @@ -313,166 +76,3 @@ impl HeapTrace for Option { self.take(); } } - -pub fn initialize_object_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::ObjectConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ - ObjectEntry::new_prototype_function_entry(heap, "assign", 1, true, object_todo), - ObjectEntry::new_prototype_function_entry(heap, "create", 2, false, object_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "defineProperties", - 2, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "defineProperty", - 3, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "entries", 1, false, object_todo), - ObjectEntry::new_prototype_function_entry(heap, "freeze", 1, false, object_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "fromEntries", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "getOwnPropertyDescriptor", - 2, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "getOwnPropertyDescriptors", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "getOwnPropertyNames", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "getOwnPropertySymbols", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "getPrototypeOf", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "hasOwn", 2, false, object_todo), - ObjectEntry::new_prototype_function_entry(heap, "is", 1, false, object_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "isExtensible", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "isFrozen", 1, false, object_todo), - ObjectEntry::new_prototype_function_entry(heap, "isSealed", 1, false, object_todo), - ObjectEntry::new_prototype_function_entry(heap, "keys", 1, false, object_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "preventExtensions", - 1, - false, - object_todo, - ), - ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::ObjectPrototypeIndex as u32, - ), - ObjectEntry::new_prototype_function_entry(heap, "seal", 1, false, object_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "setPrototypeOf", - 2, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "values", 1, false, object_todo), - ], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::ObjectConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::ObjectConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: object_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::ObjectConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::roh(Value::Null), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::ObjectConstructorIndex, - ))), - ), - ObjectEntry::new_prototype_function_entry( - heap, - "hasOwnProperty", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "isPrototypeOf", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "propertyIsEnumerable", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "toLocaleString", - 0, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, object_todo), - ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false, object_todo), - ], - )); -} - -fn object_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::Object(0)) -} - -fn object_todo(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - todo!() -} diff --git a/nova_vm/src/heap/regexp.rs b/nova_vm/src/heap/regexp.rs deleted file mode 100644 index 40e3193f..00000000 --- a/nova_vm/src/heap/regexp.rs +++ /dev/null @@ -1,143 +0,0 @@ -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - Heap, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, -}; - -use super::{ - function::FunctionHeapData, - heap_constants::WellKnownSymbolIndexes, - heap_trace::HeapTrace, - object::{ObjectEntry, PropertyKey}, -}; - -#[derive(Debug)] -pub(crate) struct RegExpHeapData { - pub(super) bits: HeapBits, - pub(super) object_index: u32, - // pub(super) _regex: RegExp, -} - -impl HeapTrace for Option { - fn trace(&self, heap: &Heap) { - assert!(self.is_some()); - heap.objects[self.as_ref().unwrap().object_index as usize].trace(heap); - } - fn root(&self, _heap: &Heap) { - assert!(self.is_some()); - self.as_ref().unwrap().bits.root(); - } - - fn unroot(&self, _heap: &Heap) { - assert!(self.is_some()); - self.as_ref().unwrap().bits.unroot(); - } - - fn finalize(&mut self, _heap: &Heap) { - self.take(); - } -} - -pub fn initialize_regexp_heap(heap: &mut Heap) { - let species_function_name = Value::new_string(heap, "get [Symbol.species]"); - heap.objects[BuiltinObjectIndexes::RegExpConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ - ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::RegExpPrototypeIndex as u32, - ), - ObjectEntry::new( - PropertyKey::Symbol(WellKnownSymbolIndexes::Species as u32), - PropertyDescriptor::ReadOnly { - get: heap.create_function(species_function_name, 0, false, regexp_species), - enumerable: false, - configurable: true, - }, - ), - ], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::RegExpConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::RegExpConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: regexp_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::RegExpPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::RegExpConstructorIndex, - ))), - ), - // TODO: Write out all the getters - ObjectEntry::new_prototype_function_entry(heap, "exec", 1, false, regexp_todo), - // TODO: These symbol function properties are actually rwxh, this helper generates roxh instead. - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.match]", - WellKnownSymbolIndexes::Match as u32, - 1, - false, - regexp_todo, - ), - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.matchAll]", - WellKnownSymbolIndexes::MatchAll as u32, - 1, - false, - regexp_todo, - ), - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.replace]", - WellKnownSymbolIndexes::Replace as u32, - 2, - false, - regexp_todo, - ), - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.search]", - WellKnownSymbolIndexes::Search as u32, - 1, - false, - regexp_todo, - ), - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.split]", - WellKnownSymbolIndexes::Split as u32, - 2, - false, - regexp_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "test", 1, false, regexp_todo), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, regexp_todo), - ], - )); -} - -fn regexp_constructor_binding(_heap: &mut Heap, _this: Value, _args: &[Value]) -> JsResult { - Ok(Value::Function(0)) -} - -fn regexp_species(_heap: &mut Heap, this: Value, _args: &[Value]) -> JsResult { - Ok(this) -} - -fn regexp_todo(_heap: &mut Heap, _this: Value, _args: &[Value]) -> JsResult { - todo!() -} diff --git a/nova_vm/src/heap/string.rs b/nova_vm/src/heap/string.rs index 78b7a330..e5239d93 100644 --- a/nova_vm/src/heap/string.rs +++ b/nova_vm/src/heap/string.rs @@ -1,31 +1,26 @@ -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - heap_trace::HeapTrace, - FunctionHeapData, Heap, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, -}; +use crate::heap::{heap_trace::HeapTrace, Heap, HeapBits}; use wtf8::Wtf8Buf; -#[derive(Debug)] -pub(crate) struct StringHeapData { +#[derive(Debug, Clone)] +pub struct StringHeapData { pub(crate) bits: HeapBits, pub(crate) data: Wtf8Buf, } impl StringHeapData { + pub fn dummy() -> Self { + Self { + bits: HeapBits::new(), + data: Wtf8Buf::new(), + } + } + pub fn from_str(str: &str) -> Self { StringHeapData { bits: HeapBits::new(), data: Wtf8Buf::from_str(str), } } - - pub fn len(&self) -> usize { - // TODO: We should return the UTF-16 length. - self.data.len() - } } impl HeapTrace for Option { @@ -45,33 +40,3 @@ impl HeapTrace for Option { self.take(); } } - -pub fn initialize_string_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::StringConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - // TODO: Methods and properties - Vec::with_capacity(0), - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::StringConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::StringConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: string_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::StringPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - // TODO: Methods and properties - Vec::with_capacity(0), - )); -} - -fn string_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::EmptyString) -} diff --git a/nova_vm/src/heap/symbol.rs b/nova_vm/src/heap/symbol.rs index bab3daf2..121a2b39 100644 --- a/nova_vm/src/heap/symbol.rs +++ b/nova_vm/src/heap/symbol.rs @@ -1,25 +1,26 @@ -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes, WellKnownSymbolIndexes}, - heap_trace::HeapTrace, - FunctionHeapData, Heap, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, StringIndex, Value}, -}; +use super::{Handle, StringHeapData}; +use crate::heap::{heap_trace::HeapTrace, Heap, HeapBits}; -use super::object::{ObjectEntry, PropertyKey}; - -#[derive(Debug)] -pub(crate) struct SymbolHeapData { +#[derive(Debug, Clone)] +pub struct SymbolHeapData { pub(super) bits: HeapBits, - pub(super) descriptor: Option, + pub(super) descriptor: Option>, +} + +impl SymbolHeapData { + pub fn dummy() -> Self { + Self { + bits: HeapBits::new(), + descriptor: None, + } + } } impl HeapTrace for Option { fn trace(&self, heap: &Heap) { assert!(self.is_some()); - if let Some(idx) = self.as_ref().unwrap().descriptor { - heap.strings[idx as usize].trace(heap); + if let Some(handle) = self.as_ref().unwrap().descriptor { + heap.strings[handle.id.get() as usize].trace(heap); } } fn root(&self, _heap: &Heap) { @@ -36,202 +37,3 @@ impl HeapTrace for Option { self.take(); } } - -pub fn initialize_symbol_heap(heap: &mut Heap) { - // AsyncIterator - heap.symbols[WellKnownSymbolIndexes::AsyncIterator as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.asyncIterator")), - }); - // HasInstance - heap.symbols[WellKnownSymbolIndexes::HasInstance as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.hasInstance")), - }); - // IsConcatSpreadable - heap.symbols[WellKnownSymbolIndexes::IsConcatSpreadable as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.isConcatSpreadable")), - }); - // Iterator - heap.symbols[WellKnownSymbolIndexes::Iterator as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.iterator")), - }); - // Match - heap.symbols[WellKnownSymbolIndexes::Match as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.match")), - }); - // MatchAll - heap.symbols[WellKnownSymbolIndexes::MatchAll as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.matchAll")), - }); - // Replace - heap.symbols[WellKnownSymbolIndexes::Replace as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.replace")), - }); - // Search - heap.symbols[WellKnownSymbolIndexes::Search as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.search")), - }); - // Species - heap.symbols[WellKnownSymbolIndexes::Species as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.species")), - }); - // Split - heap.symbols[WellKnownSymbolIndexes::Split as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.split")), - }); - // ToPrimitive - heap.symbols[WellKnownSymbolIndexes::ToPrimitive as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.toPrimitive")), - }); - // ToStringTag - heap.symbols[WellKnownSymbolIndexes::ToStringTag as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.toStringTag")), - }); - // Unscopables - heap.symbols[WellKnownSymbolIndexes::Unscopables as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.unscopables")), - }); - - heap.objects[BuiltinObjectIndexes::SymbolConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "asyncIterator"), - PropertyDescriptor::roh(Value::Symbol( - WellKnownSymbolIndexes::AsyncIterator as u32, - )), - ), - ObjectEntry::new_prototype_function_entry(heap, "for", 1, false, symbol_todo), - ObjectEntry::new( - PropertyKey::from_str(heap, "hasInstance"), - PropertyDescriptor::roh(Value::Symbol( - WellKnownSymbolIndexes::HasInstance as u32, - )), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "isConcatSpreadable"), - PropertyDescriptor::roh(Value::Symbol( - WellKnownSymbolIndexes::IsConcatSpreadable as u32, - )), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "iterator"), - PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::Iterator as u32)), - ), - ObjectEntry::new_prototype_function_entry(heap, "keyFor", 1, false, symbol_todo), - ObjectEntry::new( - PropertyKey::from_str(heap, "Match"), - PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::Match as u32)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "MatchAll"), - PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::MatchAll as u32)), - ), - ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::SymbolPrototypeIndex as u32, - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "Replace"), - PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::Replace as u32)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "Search"), - PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::Search as u32)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "Species"), - PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::Species as u32)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "Split"), - PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::Split as u32)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "ToPrimitive"), - PropertyDescriptor::roh(Value::Symbol( - WellKnownSymbolIndexes::ToPrimitive as u32, - )), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "ToStringTag"), - PropertyDescriptor::roh(Value::Symbol( - WellKnownSymbolIndexes::ToStringTag as u32, - )), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "Unscopables"), - PropertyDescriptor::roh(Value::Symbol( - WellKnownSymbolIndexes::Unscopables as u32, - )), - ), - ], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::SymbolConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::SymbolConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: symbol_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::SymbolPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::SymbolConstructorIndex, - ))), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "description"), - // TODO: create description getter function - PropertyDescriptor::ReadOnly { - get: 0, - enumerable: false, - configurable: true, - }, - ), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, symbol_todo), - ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false, symbol_todo), - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.toPrimitive]", - WellKnownSymbolIndexes::ToPrimitive as u32, - 1, - false, - symbol_todo, - ), - ObjectEntry::new( - PropertyKey::Symbol(WellKnownSymbolIndexes::ToStringTag as u32), - PropertyDescriptor::roxh(Value::new_string(heap, "Symbol")), - ), - ], - )); -} - -fn symbol_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::Symbol(0)) -} - -fn symbol_todo(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - todo!(); -} diff --git a/nova_vm/src/language.rs b/nova_vm/src/language.rs new file mode 100644 index 00000000..5dc24586 --- /dev/null +++ b/nova_vm/src/language.rs @@ -0,0 +1,4 @@ +mod bytecode; +mod script; + +pub use script::Script; diff --git a/nova_vm/src/language/bytecode.rs b/nova_vm/src/language/bytecode.rs new file mode 100644 index 00000000..b153f88a --- /dev/null +++ b/nova_vm/src/language/bytecode.rs @@ -0,0 +1,7 @@ +mod executable; +mod instructions; +mod vm; + +pub use executable::{Executable, IndexType, JumpIndex}; +pub use instructions::{Instr, Instruction, InstructionIter}; +pub use vm::Vm; diff --git a/nova_vm/src/language/bytecode/executable.rs b/nova_vm/src/language/bytecode/executable.rs new file mode 100644 index 00000000..ea9e3438 --- /dev/null +++ b/nova_vm/src/language/bytecode/executable.rs @@ -0,0 +1,70 @@ +use super::Instruction; +use crate::{types::Value, Heap}; +use oxc_span::Atom; + +pub type IndexType = u16; + +#[derive(Debug)] +pub struct Executable<'ctx> { + pub heap: &'ctx mut Heap, + pub instructions: Vec, + pub constants: Vec, + pub identifiers: Vec, + // TODO: function_expressions +} + +impl<'ctx> Executable<'ctx> { + pub fn add_instruction(&mut self, instruction: Instruction) { + self.instructions.push(instruction); + } + + pub fn add_constant(&mut self, constant: Value) { + self.constants.push(constant); + } + + pub fn add_identifier(&mut self, identifier: Atom) { + self.identifiers.push(identifier); + } + + pub fn add_instruction_with_constant(&mut self, instruction: Instruction, constant: Value) { + debug_assert!(instruction.has_constant_index()); + self.add_instruction(instruction); + self.add_constant(constant); + } + + pub fn add_instruction_with_identifier(&mut self, instruction: Instruction, identifier: Atom) { + debug_assert!(instruction.has_identifier_index()); + self.add_instruction(instruction); + self.add_identifier(identifier); + } + + pub fn add_index(&mut self, index: usize) { + assert!(index < IndexType::MAX as usize); + let bytes: [u8; 2] = unsafe { std::mem::transmute(index as IndexType) }; + self.instructions[index] = unsafe { std::mem::transmute(bytes[0]) }; + self.instructions[index + 1] = unsafe { std::mem::transmute(bytes[0]) }; + } + + pub fn add_jump_index(&mut self) -> JumpIndex { + self.add_index(0); + JumpIndex { + index: self.instructions.len() - std::mem::size_of::(), + } + } + + pub fn set_jump_target(&mut self, jump: JumpIndex, index: usize) { + assert!(index < IndexType::MAX as usize); + let bytes: [u8; 2] = unsafe { std::mem::transmute(index as IndexType) }; + self.instructions[jump.index] = unsafe { std::mem::transmute(bytes[0]) }; + self.instructions[jump.index + 1] = unsafe { std::mem::transmute(bytes[0]) }; + } + + pub fn set_jump_target_here(&mut self, jump: JumpIndex) { + self.set_jump_target(jump, self.instructions.len()); + } +} + +#[derive(Debug)] +pub struct JumpIndex { + pub index: usize, +} diff --git a/nova_vm/src/language/bytecode/instructions.rs b/nova_vm/src/language/bytecode/instructions.rs new file mode 100644 index 00000000..d81f8168 --- /dev/null +++ b/nova_vm/src/language/bytecode/instructions.rs @@ -0,0 +1,198 @@ +use super::IndexType; + +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub enum Instruction { + /// Store ApplyStringOrNumericBinaryOperator() as the result value. + ApplyStringOrNumericBinaryOperator, + /// Store ArrayCreate(0) as the result value. + ArrayCreate, + /// Set an array's value at the given index. + ArraySetValue, + /// Set the length property of an array to the given index. + ArraySetLength, + /// Apply bitwise NOT to the last value on the stack and store it as the result value. + BitwiseNot, + /// Create a catch binding for the given name and populate it with the stored exception. + CreateCatchBinding, + /// Apply the delete operation to the evaluated expression and set it as the result value. + Delete, + /// Store EvaluateCall() as the result value. + /// This instruction has the number of argument values that need to be popped from the stack + /// (last to first) as an argument, the values on the stack afterwards are the this value and + /// lastly the function to call. + EvaluateCall, + /// Store EvaluateNew() as the result value. + /// This instruction has the number of argument values that need to be popped from the stack + /// (last to first) as an argument, the value on the stack afterwards is the constructor to + /// call. + EvaluateNew, + /// Store EvaluatePropertyAccessWithExpressionKey() as the result value. + EvaluatePropertyAccessWithExpressionKey, + /// Store EvaluatePropertyAccessWithIdentifierKey() as the result value. + EvaluatePropertyAccessWithIdentifierKey, + /// Store GetValue() as the result value. + GetValue, + /// Compare the last two values on the stack using the '>' operator rules. + GreaterThan, + /// Compare the last two values on the stack using the '>=' operator rules. + GreaterThanEquals, + /// Store HasProperty() as the result value. + HasProperty, + /// Store InstanceofOperator() as the result value. + InstanceofOperator, + /// Store InstantiateArrowFunctionExpression() as the result value. + InstantiateArrowFunctionExpression, + /// Store InstantiateOrdinaryFunctionExpression() as the result value. + InstantiateOrdinaryFunctionExpression, + /// Store IsLooselyEqual() as the result value. + IsLooselyEqual, + /// Store IsStrictlyEqual() as the result value. + IsStrictlyEqual, + /// Jump to another instruction by setting the instruction pointer. + Jump, + /// Jump to one of two other instructions depending on whether the last value on the stack is + /// truthy or not. + JumpConditional, + /// Compare the last two values on the stack using the '<' operator rules. + LessThan, + /// Compare the last two values on the stack using the '<=' operator rules. + LessThanEquals, + /// Load the result value and add it to the stack. + Load, + /// Load a constant and add it to the stack. + LoadConstant, + /// Determine the this value for an upcoming evaluate_call instruction and add it to the stack. + LoadThisValue, + /// Apply logical NOT to the last value on the stack and store it as the result value. + LogicalNot, + /// Store OrdinaryObjectCreate(%Object.prototype%) as the result value. + ObjectCreate, + /// Set an object's property to the key/value pair from the last two values on the stack. + ObjectSetProperty, + /// Pop a jump target for uncaught exceptions + PopExceptionJumpTarget, + /// Pop the last stored reference. + PopReference, + /// Push a jump target for uncaught exceptions + PushExceptionJumpTarget, + /// Push the last evaluated reference, if any. + PushReference, + /// Call PutValue() with the last reference on the reference stack and the result value. + PutValue, + /// Store ResolveBinding() as the result value. + ResolveBinding, + /// Store ResolveThisBinding() as the result value. + ResolveThisBinding, + /// Rethrow the stored exception, if any. + RethrowExceptionIfAny, + /// Stop bytecode execution, indicating a return from the current function. + Return, + /// Store the last value from the stack as the result value. + Store, + /// Store a constant as the result value. + StoreConstant, + /// Throw the last value from the stack as an exception. + Throw, + /// Store ToNumber() as the result value. + ToNumber, + /// Store ToNumeric() as the result value. + ToNumeric, + /// Apply the typeof operation to the evaluated expression and set it as the result value. + Typeof, + /// Store Number::unaryMinus() / BigInt::unaryMinus() as the result value. + UnaryMinus, +} + +impl Instruction { + pub fn argument_count(self) -> u8 { + return match self { + Self::EvaluateCall + | Self::EvaluatePropertyAccessWithIdentifierKey + | Self::JumpConditional + | Self::ResolveBinding => 2, + Self::ApplyStringOrNumericBinaryOperator + | Self::ArraySetLength + | Self::ArraySetValue + | Self::CreateCatchBinding + | Self::EvaluateNew + | Self::EvaluatePropertyAccessWithExpressionKey + | Self::InstantiateArrowFunctionExpression + | Self::InstantiateOrdinaryFunctionExpression + | Self::Jump + | Self::LoadConstant + | Self::PushExceptionJumpTarget + | Self::StoreConstant => 1, + _ => 0, + }; + } + + pub fn has_constant_index(self) -> bool { + return match self { + Self::LoadConstant | Self::StoreConstant => true, + _ => false, + }; + } + + pub fn has_identifier_index(self) -> bool { + return match self { + Self::CreateCatchBinding + | Self::EvaluatePropertyAccessWithIdentifierKey + | Self::ResolveBinding => true, + _ => false, + }; + } + + pub fn has_function_expression_index(self) -> bool { + return match self { + Self::InstantiateArrowFunctionExpression + | Self::InstantiateOrdinaryFunctionExpression => true, + _ => false, + }; + } +} + +#[derive(Debug)] +pub struct Instr { + pub kind: Instruction, + pub args: [Option; 2], +} + +#[derive(Debug)] +pub struct InstructionIter<'a> { + instructions: &'a [Instruction], + index: usize, +} + +impl<'a> InstructionIter<'a> { + pub fn new(instructions: &'a [Instruction]) -> Self { + Self { + instructions, + index: 0, + } + } +} + +impl Iterator for InstructionIter<'_> { + type Item = Instr; + + fn next(&mut self) -> Option { + if self.index >= self.instructions.len() { + return None; + } + + let kind = self.instructions[self.index]; + self.index += 1; + + let mut args: [Option; 2] = [None, None]; + + for i in 0..kind.argument_count() as usize { + let bytes: &[IndexType] = + unsafe { std::mem::transmute(&self.instructions[self.index..]) }; + self.index += 2; + args[i] = Some(bytes[0]); + } + + Some(Instr { kind, args }) + } +} diff --git a/nova_vm/src/language/bytecode/vm.rs b/nova_vm/src/language/bytecode/vm.rs new file mode 100644 index 00000000..27694b40 --- /dev/null +++ b/nova_vm/src/language/bytecode/vm.rs @@ -0,0 +1,60 @@ +use oxc_span::Atom; + +use crate::{ + execution::Agent, + types::{Reference, Value}, +}; + +use super::{Executable, IndexType, Instruction}; + +#[derive(Debug)] +pub struct Vm<'ctx, 'host> { + agent: &'ctx mut Agent<'ctx, 'host>, + ip: usize, + stack: Vec, + reference_stack: Vec>, + exception_jump_target_stack: Vec, + result: Option, + exception: Option, + reference: Option, +} + +impl<'ctx, 'host> Vm<'ctx, 'host> { + pub fn new(agent: &'ctx mut Agent<'ctx, 'host>) -> Self { + Self { + agent, + ip: 0, + stack: Vec::with_capacity(32), + reference_stack: Vec::new(), + exception_jump_target_stack: Vec::new(), + result: None, + exception: None, + reference: None, + } + } + + fn fetch_instruction(&mut self, executable: &Executable) -> Option { + executable.instructions.get(self.ip).map(|kind| { + self.ip += 1; + *kind + }) + } + + fn fetch_constant(&mut self, executable: &Executable) -> Value { + let index = self.fetch_index(executable); + executable.constants[index as usize].clone() + } + + fn fetch_identifier(&mut self, executable: &Executable) -> Atom { + let index = self.fetch_index(executable); + executable.identifiers[index as usize].clone() + } + + fn fetch_index(&mut self, executable: &Executable) -> IndexType { + let bytes: [u8; std::mem::size_of::()] = [ + unsafe { std::mem::transmute(self.fetch_instruction(executable).unwrap()) }, + unsafe { std::mem::transmute(self.fetch_instruction(executable).unwrap()) }, + ]; + unsafe { std::mem::transmute(bytes) } + } +} diff --git a/nova_vm/src/language/script.rs b/nova_vm/src/language/script.rs new file mode 100644 index 00000000..7cd334cf --- /dev/null +++ b/nova_vm/src/language/script.rs @@ -0,0 +1,158 @@ +use crate::{ + execution::{ECMAScriptCode, Environment, ExecutionContext, Realm, ScriptOrModule}, + types::Value, +}; +use oxc_allocator::Allocator; +use oxc_ast::ast::{BindingPatternKind, Declaration, Program, Statement}; +use oxc_parser::Parser; +use oxc_span::SourceType; +use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc}; + +pub type HostDefined<'ctx> = &'ctx mut dyn Any; + +/// 16.1.4 Script Records +/// https://tc39.es/ecma262/#sec-script-records +#[derive(Debug)] +pub struct Script<'ctx, 'host> { + /// [[Realm]] + pub realm: Rc>>, + + /// [[ECMAScriptCode]] + pub ecmascript_code: Rc>, + + // TODO: [[LoadedModules]] + /// [[HostDefined]] + pub host_defined: Option>, +} + +#[derive(Debug)] +pub enum ScriptOrErrors<'ctx, 'host> { + Script(Script<'ctx, 'host>), + Errors(Vec), +} + +impl<'ctx, 'host: 'ctx> Script<'ctx, 'host> { + /// 16.1.5 ParseScript ( sourceText, realm, hostDefined ) + /// https://tc39.es/ecma262/#sec-parse-script + pub fn parse( + allocator: &'host Allocator, + source_text: &'host str, + realm: Rc>>, + host_defined: Option>, + ) -> ScriptOrErrors<'ctx, 'host> { + // 1. Let script be ParseText(sourceText, Script). + // 2. If script is a List of errors, return script. + let parser = Parser::new(&allocator, source_text, SourceType::default()); + let script = parser.parse(); + + if script.errors.len() != 0 { + return ScriptOrErrors::Errors(script.errors); + } + + // 3. Return Script Record { + // [[Realm]]: realm, [[ECMAScriptCode]]: script, [[LoadedModules]]: ยซ ยป, [[HostDefined]]: hostDefined + // }. + ScriptOrErrors::Script(Self { + realm, + ecmascript_code: Rc::new(script.program), + host_defined, + }) + } + + /// 16.1.6 ScriptEvaluation ( scriptRecord ) + /// https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation + pub fn evaluate(self) -> Value { + let ecmascript_code = self.ecmascript_code.clone(); + let realm = self.realm.clone(); + let agent = { + let realm = self.realm.borrow(); + realm.agent.clone() + }; + + // 1. Let globalEnv be scriptRecord.[[Realm]].[[GlobalEnv]]. + let global_env = { + let realm = self.realm.borrow(); + realm.global_env.clone() + }; + + // 2. Let scriptContext be a new ECMAScript code execution context. + let script_context = ExecutionContext { + // 3. Set the Function of scriptContext to null. + function: None, + + // 4. Set the Realm of scriptContext to scriptRecord.[[Realm]]. + realm, + + // 5. Set the ScriptOrModule of scriptContext to scriptRecord. + script_or_module: Some(ScriptOrModule::Script(Rc::new(RefCell::new(self)))), + + ecmascript_code: Some(ECMAScriptCode { + // 6. Set the VariableEnvironment of scriptContext to globalEnv. + variable_environment: Environment::GlobalEnvironment(global_env.clone()), + + // 7. Set the LexicalEnvironment of scriptContext to globalEnv. + lexical_environment: Environment::GlobalEnvironment(global_env.clone()), + + // 8. Set the PrivateEnvironment of scriptContext to null. + private_environment: None, + }), + }; + + // TODO: 9. Suspend the running execution context. + + // 10. Push scriptContext onto the execution context stack; scriptContext is now the running execution context. + agent + .borrow_mut() + .execution_context_stack + .push(script_context); + + // 11. Let script be scriptRecord.[[ECMAScriptCode]]. + let script = ecmascript_code.as_ref(); + + // TODO: 12. Let result be Completion(GlobalDeclarationInstantiation(script, globalEnv)). + // NOTE: This is totally ad-hoc for now. + let mut seen = HashMap::new(); + for variable_declaration in script.body.iter() { + match &variable_declaration { + Statement::Declaration(decl) => match decl { + Declaration::VariableDeclaration(decl) => { + if decl.kind.is_var() { + for decl in decl.declarations.iter() { + let var_name = match &decl.id.kind { + BindingPatternKind::BindingIdentifier(name) => { + name.name.as_str() + } + _ => continue, + }; + + if !seen.contains_key(var_name) { + // global_env.create_global_var_binding(agent, var_name, false); + _ = seen.insert(var_name, ()); + } + } + } + } + _ => {} + }, + _ => {} + } + } + + // 13. If result.[[Type]] is normal, then + // a. Set result to Completion(Evaluation of script). + // b. If result.[[Type]] is normal and result.[[Value]] is empty, then + // i. Set result to NormalCompletion(undefined). + + // 14. Suspend scriptContext and remove it from the execution context stack. + _ = agent.borrow_mut().execution_context_stack.pop(); + + // 15. Assert: The execution context stack is not empty. + debug_assert!(agent.borrow().execution_context_stack.len() > 0); + + // TODO: 16. Resume the context that is now on the top of the execution context stack as the + // running execution context. + + // 17. Return ? result. + todo!() + } +} diff --git a/nova_vm/src/lib.rs b/nova_vm/src/lib.rs index 422922e9..5b3357cd 100644 --- a/nova_vm/src/lib.rs +++ b/nova_vm/src/lib.rs @@ -1,465 +1,10 @@ -#![feature(try_trait_v2)] - +pub mod builtins; +pub mod execution; pub mod heap; -mod stack_string; -pub mod value; -use heap::Heap; -use oxc_ast::{ - ast::{ - AssignmentOperator, AssignmentTarget, BinaryOperator, BindingPatternKind, Declaration, - Expression, LogicalOperator, Program, SimpleAssignmentTarget, Statement, - VariableDeclarator, - }, - syntax_directed_operations::PropName, -}; -use std::fmt::Debug; -use value::{JsResult, Value}; - -/// https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-ecmascript-language-types -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Type { - BigInt, - Boolean, - Function, - Null, - Number, - Object, - String, - Symbol, - Undefined, -} - -#[repr(u32)] -pub enum Instruction { - LoadInteger, - LoadBoolean, - LoadNull, - LoadString, - - // [out] [in] - CopyValue, - - // [out] [left] [right] - Add, - Sub, - Mul, - Div, - Mod, - StrictEquality, - Equality, - StrictInequality, - Inequality, - - /// `[jump_rel]` - /// - /// Jumps to the relative instruction. - Jump, - - /// `[addr] [jump_rel]` - /// - /// If `addr` is a true when converted to a boolean, then the instruction - /// will skip `jump_rel` instructions forward. - Test, - - /// `[addr] [jump_rel]` - /// If `addr` is false when converted to a boolean, then the instruction - /// will skip `jump_rel` instructions forward. - TestNot, -} - -impl Into for Instruction { - fn into(self) -> u32 { - self as u32 - } -} - -pub struct VM<'a> { - pub source: &'a str, - pub instructions: Vec, - /// Program counter. - pub pc: u32, - pub heap: Heap, -} - -#[derive(Debug, Default)] -pub struct Env<'a> { - pub map: std::collections::HashMap<&'a str, u32>, - pub parent: Option>>, -} - -impl<'a> VM<'a> { - pub fn interpret(&mut self) -> JsResult<()> { - let mut memory = Vec::::with_capacity(self.pc as usize); - - for _ in 0..self.pc { - memory.push(Value::Undefined); - } - - let instructions = self.instructions.clone(); - let mut iter = instructions.iter(); - while let Some(leading) = iter.next() { - match unsafe { std::mem::transmute::(*leading) } { - Instruction::LoadInteger => { - let addr = *iter.next().unwrap() as usize; - memory[addr] = Value::from_i32(*iter.next().unwrap() as i32); - } - Instruction::LoadBoolean => { - let addr = *iter.next().unwrap() as usize; - memory[addr] = Value::Boolean(*iter.next().unwrap() == 1); - } - Instruction::LoadNull => { - let addr = *iter.next().unwrap() as usize; - memory[addr] = Value::Null; - } - Instruction::LoadString => { - let addr = *iter.next().unwrap() as usize; - memory[addr] = Value::HeapString(*iter.next().unwrap() as u32); - } - Instruction::CopyValue => { - let addr = *iter.next().unwrap() as usize; - memory[addr] = memory[*iter.next().unwrap() as usize].clone(); - } - Instruction::Add => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr].try_into_f64(self)?; - let right = &memory[*iter.next().unwrap() as usize].try_into_f64(self)?; - memory[addr] = Value::from_f64(&mut self.heap, left + right); - } - Instruction::Sub => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr].try_into_f64(self)?; - let right = &memory[*iter.next().unwrap() as usize].try_into_f64(self)?; - memory[addr] = Value::from_f64(&mut self.heap, left - right); - } - Instruction::Mul => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr].try_into_f64(self)?; - let right = &memory[*iter.next().unwrap() as usize].try_into_f64(self)?; - memory[addr] = Value::from_f64(&mut self.heap, left * right); - } - Instruction::Mod => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr].try_into_f64(self)?; - let right = &memory[*iter.next().unwrap() as usize].try_into_f64(self)?; - memory[addr] = Value::from_f64(&mut self.heap, left % right); - } - Instruction::Div => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr].try_into_f64(self)?; - let right = &memory[*iter.next().unwrap() as usize].try_into_f64(self)?; - memory[addr] = Value::from_f64(&mut self.heap, left / right); - } - Instruction::StrictEquality => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr]; - let right = &memory[*iter.next().unwrap() as usize]; - memory[addr] = Value::Boolean(left.is_strictly_equal(self, right)?); - } - Instruction::Equality => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr]; - let right = &memory[*iter.next().unwrap() as usize]; - memory[addr] = Value::Boolean(left.is_loosely_equal(self, right)?); - } - Instruction::StrictInequality => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr]; - let right = &memory[*iter.next().unwrap() as usize]; - memory[addr] = Value::Boolean(!left.is_strictly_equal(self, right)?); - } - Instruction::Inequality => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr]; - let right = &memory[*iter.next().unwrap() as usize]; - memory[addr] = Value::Boolean(!left.is_loosely_equal(self, right)?); - } - Instruction::Test => { - let addr = *iter.next().unwrap() as usize; - let jump_rel = *iter.next().unwrap() as usize; - - if memory[addr].into_bool() { - _ = iter.nth(jump_rel); - }; - } - Instruction::TestNot => { - let addr = *iter.next().unwrap() as usize; - let jump_rel = *iter.next().unwrap() as usize; - - if !memory[addr].into_bool() { - _ = iter.nth(jump_rel); - }; - } - Instruction::Jump => { - let jump_rel = *iter.next().unwrap() as usize; - _ = iter.nth(jump_rel); - } - } - } - - println!("{:?}", memory.as_slice()); - - Ok(()) - } - - /// Builds the bytecode to run an expression. - /// - /// Assumes the memory location is valid to use throughout the evaluation as - /// a scratch. - fn build_expr(&mut self, addr: u32, expr: &Expression, env: &mut Env) { - match expr { - Expression::NullLiteral(_) => { - self.instructions.push(Instruction::LoadNull.into()); - self.instructions.push(addr); - } - Expression::BooleanLiteral(l) => { - self.instructions.push(Instruction::LoadBoolean.into()); - self.instructions.push(addr); - self.instructions.push(l.value.into()); - } - Expression::Identifier(ident) => { - // TODO: figure out how to return the ident's memory addr as - // an optimization - self.instructions.push(Instruction::CopyValue.into()); - self.instructions.push(addr); - // TODO: support recursive ident lookups - self.instructions - .push(*env.map.get(ident.name.as_str()).unwrap()); - } - Expression::NumberLiteral(num) => { - self.instructions.push(Instruction::LoadInteger.into()); - self.instructions.push(addr); - self.instructions - .push(unsafe { std::mem::transmute(*num.value.as_f32()) }); - } - Expression::LogicalExpression(expr) => match expr.operator { - LogicalOperator::And => { - self.build_expr(addr, &expr.left, env); - self.instructions.push(Instruction::Test.into()); - self.instructions.push(addr); - let jump_addr = self.instructions.len(); - self.instructions.push(0); - self.build_expr(addr, &expr.right, env); - self.instructions[jump_addr] = (self.instructions.len() - jump_addr) as u32; - } - LogicalOperator::Or => { - self.build_expr(addr, &expr.left, env); - self.instructions.push(Instruction::TestNot.into()); - self.instructions.push(addr); - let jump_addr = self.instructions.len(); - self.instructions.push(0); - self.build_expr(addr, &expr.right, env); - self.instructions[jump_addr] = (self.instructions.len() - jump_addr) as u32; - } - _ => panic!(), - }, - Expression::ConditionalExpression(cond) => { - self.build_expr(addr, &cond.test, env); - - self.instructions.push(Instruction::Test.into()); - self.instructions.push(addr); - let finish_idx = self.instructions.len(); - self.instructions.push(0); - - self.build_expr(addr, &cond.alternate, env); - - self.instructions.push(Instruction::Jump.into()); - let alternate_idx = self.instructions.len(); - self.instructions.push(0); - - self.instructions[finish_idx] = (self.instructions.len() - finish_idx - 2) as u32; - self.build_expr(addr, &cond.consequent, env); - - self.instructions[alternate_idx] = - (self.instructions.len() - alternate_idx - 2) as u32; - } - Expression::ObjectExpression(obj) => { - for prop in obj.properties.iter() { - prop.prop_name(); - } - } - Expression::BinaryExpression(binary_op) => { - macro_rules! binary_op { - ($name: ident) => {{ - let right = self.pc; - self.pc += 1; - - self.build_expr(addr, &binary_op.left, env); - self.build_expr(right, &binary_op.right, env); - - self.instructions.push(Instruction::$name.into()); - self.instructions.push(addr); - self.instructions.push(right); - }}; - } - - match binary_op.operator { - BinaryOperator::Addition => binary_op!(Add), - BinaryOperator::Subtraction => binary_op!(Sub), - BinaryOperator::Multiplication => binary_op!(Mul), - BinaryOperator::Remainder => binary_op!(Mod), - BinaryOperator::Division => binary_op!(Div), - BinaryOperator::StrictEquality => binary_op!(StrictEquality), - BinaryOperator::Equality => binary_op!(Equality), - BinaryOperator::StrictInequality => binary_op!(StrictInequality), - BinaryOperator::Inequality => binary_op!(Inequality), - _ => todo!(), - } - } - Expression::StringLiteral(s) => { - let string_idx = self.heap.alloc_string(&*s.value.as_str()); - - self.instructions.push(Instruction::LoadString.into()); - self.instructions.push(addr); - self.instructions.push(string_idx as u32); - } - Expression::ParenthesizedExpression(data) => { - return self.build_expr(addr, &data.expression, env); - } - Expression::SequenceExpression(data) => { - for expr in data.expressions.iter() { - self.build_expr(addr, expr, env); - } - } - Expression::AssignmentExpression(s) => match s.operator { - AssignmentOperator::Assign => match &s.left { - AssignmentTarget::SimpleAssignmentTarget(target) => match target { - SimpleAssignmentTarget::AssignmentTargetIdentifier(ident) => { - let Some(addr) = env.map.get(ident.name.as_str()) else { - panic!("Unknown ident."); - }; - self.build_expr(*addr, &s.right, env); - } - _ => todo!(), - }, - _ => todo!(), - }, - _ => todo!(), - }, - Expression::ArrayExpression(_) => todo!(), - Expression::BigintLiteral(_) => todo!(), - Expression::RegExpLiteral(_) => todo!(), - Expression::TemplateLiteral(_) => todo!(), - Expression::MetaProperty(_) => todo!(), - Expression::Super(_) => todo!(), - Expression::ArrowFunctionExpression(_) => todo!(), - Expression::AwaitExpression(_) => todo!(), - Expression::CallExpression(_) => todo!(), - Expression::ChainExpression(_) => todo!(), - Expression::ClassExpression(_) => todo!(), - Expression::FunctionExpression(_) => todo!(), - Expression::ImportExpression(_) => todo!(), - Expression::MemberExpression(_) => todo!(), - Expression::NewExpression(_) => todo!(), - Expression::TaggedTemplateExpression(_) => todo!(), - Expression::ThisExpression(_) => todo!(), - Expression::UnaryExpression(_) => todo!(), - Expression::UpdateExpression(_) => todo!(), - Expression::YieldExpression(_) => todo!(), - Expression::PrivateInExpression(_) => todo!(), - // TypeScript and JSX not supported - Expression::JSXElement(_) - | Expression::JSXFragment(_) - | Expression::TSAsExpression(_) - | Expression::TSSatisfiesExpression(_) - | Expression::TSTypeAssertion(_) - | Expression::TSNonNullExpression(_) - | Expression::TSInstantiationExpression(_) => unreachable!(), - } - } - - pub fn build_stmt<'b>(&mut self, stmt: &'b Statement, env: &mut Env<'b>) { - match stmt { - Statement::Declaration(Declaration::VariableDeclaration(decl)) => { - for member in decl.declarations.as_slice() { - let member: &VariableDeclarator = member; - env.map.insert( - match &member.id.kind { - BindingPatternKind::BindingIdentifier(ident) => ident.name.as_str(), - _ => panic!(), - }, - self.pc, - ); - let addr = self.pc; - self.pc += 1; - - if let Some(expr) = &member.init { - self.build_expr(addr, expr, env); - } else { - todo!("Load undefined."); - } - } - } - Statement::Declaration(Declaration::FunctionDeclaration(_)) => todo!(), - Statement::Declaration(Declaration::ClassDeclaration(_)) => todo!(), - Statement::ExpressionStatement(expr) => { - self.build_expr(self.pc, &expr.expression, env); - } - Statement::BlockStatement(block) => { - for stmt in block.body.iter() { - self.build_stmt(&stmt, env); - } - } - Statement::IfStatement(s) => { - let addr = self.pc; - self.pc += 1; - - self.build_expr(addr, &s.test, env); - self.instructions.push(Instruction::Test.into()); - self.instructions.push(addr); - let consequent_idx = self.instructions.len(); - self.instructions.push(0); - - if let Some(alternate) = &s.alternate { - self.build_stmt(alternate, env); - } - - self.instructions.push(Instruction::Jump.into()); - let finish_idx = self.instructions.len(); - self.instructions.push(0); - - self.instructions[consequent_idx] = - (self.instructions.len() - consequent_idx - 2) as u32; - self.build_stmt(&s.consequent, env); - - self.instructions[finish_idx] = (self.instructions.len() - finish_idx - 2) as u32; - } - Statement::BreakStatement(_) => todo!(), - Statement::ContinueStatement(_) => todo!(), - Statement::DebuggerStatement(_) => todo!(), - Statement::DoWhileStatement(_) => todo!(), - Statement::EmptyStatement(_) => todo!(), - Statement::ForInStatement(_) => todo!(), - Statement::ForOfStatement(_) => todo!(), - Statement::ForStatement(_) => todo!(), - Statement::LabeledStatement(_) => todo!(), - Statement::ReturnStatement(_) => todo!(), - Statement::SwitchStatement(_) => todo!(), - Statement::ThrowStatement(_) => todo!(), - Statement::TryStatement(_) => todo!(), - Statement::WhileStatement(_) => todo!(), - Statement::WithStatement(_) => todo!(), - Statement::ModuleDeclaration(_) => todo!(), - // TypeScript not supported - Statement::Declaration(Declaration::TSTypeAliasDeclaration(_)) - | Statement::Declaration(Declaration::TSInterfaceDeclaration(_)) - | Statement::Declaration(Declaration::TSEnumDeclaration(_)) - | Statement::Declaration(Declaration::TSModuleDeclaration(_)) - | Statement::Declaration(Declaration::TSImportEqualsDeclaration(_)) => unreachable!(), - } - } - - pub fn load_program(&mut self, program: Program) { - let mut env = Env::default(); - for stmt in program.body.iter() { - self.build_stmt(stmt, &mut env); - } - } -} - -#[cfg(test)] -mod tests { - - use super::*; - - fn foo() {} -} +pub mod language; +pub mod types; +pub use heap::Heap; +mod small_integer; +mod small_string; +pub use small_integer::SmallInteger; +pub use small_string::SmallString; diff --git a/nova_vm/src/small_integer.rs b/nova_vm/src/small_integer.rs new file mode 100644 index 00000000..cd2f9063 --- /dev/null +++ b/nova_vm/src/small_integer.rs @@ -0,0 +1,108 @@ +/// 56-bit signed integer. +#[derive(Clone, Copy, PartialEq)] +pub struct SmallInteger { + data: [u8; 7], +} + +impl std::fmt::Debug for SmallInteger { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", Into::::into(*self)) + } +} + +impl SmallInteger { + pub const MIN_BIGINT: i64 = -2i64.pow(56) / 2 + 1; + pub const MAX_BIGINT: i64 = 2i64.pow(56) / 2 + 1; + + pub const MIN_NUMBER: i64 = -(2 as i64).pow(53) / 2 + 1; + pub const MAX_NUMBER: i64 = (2 as i64).pow(53) / 2 - 1; + + #[inline] + pub fn into_i64(self) -> i64 { + self.into() + } + + pub(crate) fn from_i64_unchecked(value: i64) -> SmallInteger { + debug_assert!(value >= Self::MIN_NUMBER && value <= Self::MAX_NUMBER); + let bytes = i64::to_ne_bytes(value); + + let data = if cfg!(target_endian = "little") { + [ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], + ] + } else { + [ + bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ] + }; + + Self { data } + } +} + +impl TryFrom for SmallInteger { + type Error = (); + fn try_from(value: i64) -> Result { + if value >= Self::MIN_NUMBER && value <= Self::MAX_NUMBER { + Ok(Self::from_i64_unchecked(value)) + } else { + Err(()) + } + } +} + +impl Into for SmallInteger { + fn into(self) -> i64 { + let Self { data } = self; + + #[repr(u8)] + enum Repr { + Data([u8; 7]), + } + + // SAFETY: This matches the format on the endian platform. + let number: i64 = unsafe { std::mem::transmute(Repr::Data(data)) }; + + if cfg!(target_endian = "little") { + number >> 8 + } else { + number << 8 >> 8 + } + } +} + +#[test] +fn valid_small_integers() { + assert_eq!(0i64, SmallInteger::try_from(0).unwrap().into()); + assert_eq!(5i64, SmallInteger::try_from(5).unwrap().into()); + assert_eq!(23i64, SmallInteger::try_from(23).unwrap().into()); + assert_eq!( + SmallInteger::MAX_NUMBER, + SmallInteger::try_from(SmallInteger::MAX_NUMBER) + .unwrap() + .into() + ); + + assert_eq!(-5i64, SmallInteger::try_from(-5).unwrap().into()); + assert_eq!(-59i64, SmallInteger::try_from(-59).unwrap().into()); + assert_eq!( + SmallInteger::MIN_NUMBER, + SmallInteger::try_from(SmallInteger::MIN_NUMBER) + .unwrap() + .into() + ); +} + +#[test] +fn invalid_small_integers() { + assert_eq!( + SmallInteger::try_from(SmallInteger::MAX_NUMBER + 1), + Err(()) + ); + assert_eq!(SmallInteger::try_from(i64::MAX), Err(())); + assert_eq!( + SmallInteger::try_from(SmallInteger::MIN_NUMBER - 1), + Err(()) + ); + assert_eq!(SmallInteger::try_from(i64::MIN), Err(())); +} diff --git a/nova_vm/src/small_string.rs b/nova_vm/src/small_string.rs new file mode 100644 index 00000000..d82e5991 --- /dev/null +++ b/nova_vm/src/small_string.rs @@ -0,0 +1,99 @@ +#[derive(Clone, Copy)] +pub struct SmallString { + bytes: [u8; 7], +} + +impl std::fmt::Debug for SmallString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "\"{}\"", self.as_str()) + } +} + +impl SmallString { + pub fn len(&self) -> usize { + // Find the last non-null character and add one to its index to get length. + self.bytes + .as_slice() + .iter() + .rev() + .position(|&x| x != 0) + .map_or(0, |i| 7 - i) + } + + #[inline] + pub fn as_str(&self) -> &str { + // SAFETY: Guaranteed to be UTF-8. + unsafe { &std::str::from_utf8_unchecked(self.as_bytes()) } + } + + #[inline] + pub fn as_bytes(&self) -> &[u8] { + &self.bytes.as_slice().split_at(self.len()).0 + } + + #[inline] + pub fn data(&self) -> &[u8; 7] { + return &self.bytes; + } + + #[inline] + pub fn is_empty(&self) -> bool { + matches!(self.bytes, [0, 0, 0, 0, 0, 0, 0]) + } + + pub(crate) fn from_str_unchecked(string: &str) -> Self { + let string_bytes = string.as_bytes(); + + // We have only 7 bytes to work with, and we cannot tell apart + // UTF-8 strings that end with a null byte from our null + // terminator so we must fail to convert on those. + debug_assert!(string_bytes.len() < 8 && string_bytes.last() != Some(&0)); + + let mut bytes = [0, 0, 0, 0, 0, 0, 0]; + bytes + .as_mut_slice() + .split_at_mut(string_bytes.len()) + .0 + .copy_from_slice(string_bytes); + + Self { bytes } + } +} + +impl TryFrom<&str> for SmallString { + type Error = (); + fn try_from(value: &str) -> Result { + // We have only 7 bytes to work with, and we cannot tell apart + // UTF-8 strings that end with a null byte from our null + // terminator so we must fail to convert on those. + if value.len() < 8 && value.as_bytes().last() != Some(&0) { + Ok(Self::from_str_unchecked(value)) + } else { + Err(()) + } + } +} + +#[test] +fn valid_stack_strings() { + assert!(SmallString::try_from("").is_ok()); + assert_eq!(SmallString::try_from("").unwrap().len(), 0); + assert!(SmallString::try_from("asd").is_ok()); + assert_eq!(SmallString::try_from("asd").unwrap().len(), 3); + assert!(SmallString::try_from("asdasd").is_ok()); + assert_eq!(SmallString::try_from("asdasd").unwrap().len(), 6); + assert!(SmallString::try_from("asdasda").is_ok()); + assert_eq!(SmallString::try_from("asdasda").unwrap().len(), 7); + assert!(SmallString::try_from("asd76fd").is_ok()); + assert_eq!(SmallString::try_from("asd76fd").unwrap().len(), 7); + assert!(SmallString::try_from("๐Ÿ’ฉ").is_ok()); + assert_eq!(SmallString::try_from("๐Ÿ’ฉ ").unwrap().len(), 5); + assert!(SmallString::try_from("asd\0foo").is_ok()); + assert_eq!(SmallString::try_from("asd\0foo").unwrap().len(), 7); +} + +#[test] +fn not_valid_stack_strings() { + assert!(SmallString::try_from("asd asd r 547 gdfg").is_err()); + assert!(SmallString::try_from("asdfoo\0").is_err()); +} diff --git a/nova_vm/src/stack_string.rs b/nova_vm/src/stack_string.rs deleted file mode 100644 index f6aaf25d..00000000 --- a/nova_vm/src/stack_string.rs +++ /dev/null @@ -1,95 +0,0 @@ -use std::fmt::Debug; - -/// Small UTF-8 string of up to 7 bytes in length -/// -/// The string is terminated after either the last non-null -/// byte or at the end of the byte slice. In essence -/// this is a mix between a CStr and a str. -#[derive(Copy, Clone)] -pub struct StackString { - bytes: [u8; 7], -} - -impl StackString { - // TODO: Need to get the length, and UTF-16 length, of the string. - pub fn utf16_len(&self) -> usize { - self.as_str().encode_utf16().count() - } - - pub fn byte_len(&self) -> usize { - // Find the last non-null character and add one to its index to get length. - self.bytes - .as_slice() - .iter() - .rev() - .position(|&x| x != 0) - .map_or(0, |i| 7 - i) - } - - pub fn as_str(&self) -> &str { - // SAFETY: Guaranteed to be ASCII, which is a subset of UTF-8. - unsafe { &std::str::from_utf8_unchecked(self.as_slice()) } - } - - #[inline(always)] - pub fn as_slice(&self) -> &[u8] { - &self.data().as_slice().split_at(self.byte_len()).0 - } - - #[inline(always)] - pub fn data(&self) -> &[u8; 7] { - return &self.bytes; - } - - // TODO: try_from_X should return Result. Option is smaller and we - // do not care about the reason why conversion failed so we prefer - // that but the method name should be changed. - pub(crate) fn try_from_str(string: &str) -> Option { - let string_bytes = string.as_bytes(); - if string_bytes.len() > 7 || string_bytes.last() == Some(&0) { - // We have only 7 bytes to work with, and we cannot tell apart - // UTF-8 strings that end with a null byte from our null - // terminator so we must fail to convert on those. - return None; - }; - let mut this = StackString { - bytes: [0, 0, 0, 0, 0, 0, 0], - }; - this.bytes - .as_mut_slice() - .split_at_mut(string_bytes.len()) - .0 - .copy_from_slice(string_bytes); - Some(this) - } -} - -impl Debug for StackString { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "\"{}\"", self.as_str()) - } -} - -#[test] -fn valid_stack_strings() { - assert!(StackString::try_from_str("").is_some()); - assert_eq!(StackString::try_from_str("").unwrap().byte_len(), 0); - assert!(StackString::try_from_str("asd").is_some()); - assert_eq!(StackString::try_from_str("asd").unwrap().byte_len(), 3); - assert!(StackString::try_from_str("asdasd").is_some()); - assert_eq!(StackString::try_from_str("asdasd").unwrap().byte_len(), 6); - assert!(StackString::try_from_str("asdasda").is_some()); - assert_eq!(StackString::try_from_str("asdasda").unwrap().byte_len(), 7); - assert!(StackString::try_from_str("asd76fd").is_some()); - assert_eq!(StackString::try_from_str("asd76fd").unwrap().byte_len(), 7); - assert!(StackString::try_from_str("๐Ÿ’ฉ").is_some()); - assert_eq!(StackString::try_from_str("๐Ÿ’ฉ").unwrap().byte_len(), 4); - assert!(StackString::try_from_str("asd\0foo").is_some()); - assert_eq!(StackString::try_from_str("asd\0foo").unwrap().byte_len(), 7); -} - -#[test] -fn not_valid_stack_strings() { - assert!(StackString::try_from_str("asd asd r 547 gdfg").is_none()); - assert!(StackString::try_from_str("asdfoo\0").is_none()); -} diff --git a/nova_vm/src/types.rs b/nova_vm/src/types.rs new file mode 100644 index 00000000..19b28f2b --- /dev/null +++ b/nova_vm/src/types.rs @@ -0,0 +1,8 @@ +mod language; +mod spec; + +pub use language::{Function, InternalMethods, Number, Object, PropertyKey, String, Value}; +pub use spec::{Base, PropertyDescriptor, Reference, ReferencedName}; + +#[derive(Debug)] +pub struct Symbol; diff --git a/nova_vm/src/types/language.rs b/nova_vm/src/types/language.rs new file mode 100644 index 00000000..fb497550 --- /dev/null +++ b/nova_vm/src/types/language.rs @@ -0,0 +1,13 @@ +mod bigint; +mod function; +mod number; +mod object; +mod string; +mod value; + +pub use bigint::BigInt; +pub use function::Function; +pub use number::Number; +pub use object::{InternalMethods, Object, ObjectData, PropertyKey, PropertyStorage}; +pub use string::String; +pub use value::Value; diff --git a/nova_vm/src/types/language/bigint.rs b/nova_vm/src/types/language/bigint.rs new file mode 100644 index 00000000..9e8efb01 --- /dev/null +++ b/nova_vm/src/types/language/bigint.rs @@ -0,0 +1,4 @@ +use super::Value; + +#[derive(Clone, Copy)] +pub struct BigInt(Value); diff --git a/nova_vm/src/types/language/function.rs b/nova_vm/src/types/language/function.rs new file mode 100644 index 00000000..efc2dad0 --- /dev/null +++ b/nova_vm/src/types/language/function.rs @@ -0,0 +1,63 @@ +use crate::heap::{FunctionHeapData, Handle}; + +use super::{Object, Value}; + +/// https://tc39.es/ecma262/#function-object +#[derive(Clone, Copy)] +pub struct Function(pub Handle); + +impl std::fmt::Debug for Function { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl From> for Function { + fn from(value: Handle) -> Self { + Function(value) + } +} + +impl TryFrom for Function { + type Error = (); + fn try_from(value: Object) -> Result { + if let Object::Function(value) = value { + Ok(Function(value)) + } else { + Err(()) + } + } +} + +impl TryFrom for Function { + type Error = (); + fn try_from(value: Value) -> Result { + if let Value::Function(value) = value { + Ok(Function(value)) + } else { + Err(()) + } + } +} + +impl From for Object { + fn from(value: Function) -> Self { + Object::Function(value.0) + } +} + +impl From for Value { + fn from(value: Function) -> Self { + Value::Function(value.0) + } +} + +impl Function { + pub fn into_value(self) -> Value { + self.into() + } + + pub fn into_object(self) -> Object { + Object::Function(self.0) + } +} diff --git a/nova_vm/src/types/language/number.rs b/nova_vm/src/types/language/number.rs new file mode 100644 index 00000000..bc496a0e --- /dev/null +++ b/nova_vm/src/types/language/number.rs @@ -0,0 +1,709 @@ +use super::Value; +use crate::{ + execution::{Agent, JsResult}, + heap::{CreateHeapData, GetHeapData}, + SmallInteger, +}; + +/// 6.1.6.1 The Number Type +/// https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type +#[derive(Clone, Copy)] +pub struct Number(Value); + +impl std::fmt::Debug for Number { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl From for Number { + fn from(value: SmallInteger) -> Self { + Number(Value::IntegerNumber(value)) + } +} + +impl From for Number { + fn from(value: i32) -> Self { + Number(Value::IntegerNumber(SmallInteger::from_i64_unchecked( + value as i64, + ))) + } +} + +impl From for Number { + fn from(value: i64) -> Self { + let n = value + .min(SmallInteger::MAX_NUMBER) + .max(SmallInteger::MIN_NUMBER); + Number(Value::IntegerNumber(SmallInteger::from_i64_unchecked(n))) + } +} + +impl From for Number { + fn from(value: f32) -> Self { + Number(Value::FloatNumber(value)) + } +} + +impl TryFrom for Number { + type Error = (); + fn try_from(value: Value) -> Result { + if matches!( + value, + Value::Number(_) | Value::IntegerNumber(_) | Value::FloatNumber(_) + ) { + Ok(Number(value)) + } else { + Err(()) + } + } +} + +impl Number { + pub(crate) fn new(value: Value) -> Self { + debug_assert!(matches!( + value, + Value::Number(_) | Value::IntegerNumber(_) | Value::FloatNumber(_) + )); + Self(value) + } + + pub fn nan() -> Self { + Self::from(f32::NAN) + } + + pub fn neg_zero() -> Self { + Self::from(-0.0) + } + + pub fn pos_zero() -> Self { + Self::from(0) + } + + pub fn pos_inf() -> Self { + Self::from(f32::INFINITY) + } + + pub fn neg_inf() -> Self { + Self::from(f32::NEG_INFINITY) + } + + pub fn into_value(self) -> Value { + self.0 + } + + pub fn is_nan(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => agent.current_realm().borrow().heap.get(n).is_nan(), + Value::IntegerNumber(_) => false, + Value::FloatNumber(n) => n.is_nan(), + _ => unreachable!(), + } + } + + pub fn is_pos_zero(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => agent + .current_realm() + .borrow() + .heap + .get(n) + .is_sign_positive(), + Value::IntegerNumber(n) => 0i64 == n.into(), + Value::FloatNumber(n) => n.is_sign_positive(), + _ => unreachable!(), + } + } + + pub fn is_neg_zero(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => agent + .current_realm() + .borrow() + .heap + .get(n) + .is_sign_negative(), + Value::IntegerNumber(_) => false, + Value::FloatNumber(n) => n.is_sign_negative(), + _ => unreachable!(), + } + } + + pub fn is_pos_infinity(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => *agent.current_realm().borrow().heap.get(n) == f64::INFINITY, + Value::IntegerNumber(_) => false, + Value::FloatNumber(n) => n == f32::INFINITY, + _ => unreachable!(), + } + } + + pub fn is_neg_infinity(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => *agent.current_realm().borrow().heap.get(n) == f64::NEG_INFINITY, + Value::IntegerNumber(_) => false, + Value::FloatNumber(n) => n == f32::NEG_INFINITY, + _ => unreachable!(), + } + } + + pub fn is_finite(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => agent.current_realm().borrow().heap.get(n).is_finite(), + Value::IntegerNumber(_) => true, + Value::FloatNumber(n) => n.is_finite(), + _ => unreachable!(), + } + } + + pub fn is_nonzero(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => { + let n = *agent.current_realm().borrow().heap.get(n); + !n.is_sign_negative() && !n.is_sign_positive() + } + Value::IntegerNumber(_) => true, + Value::FloatNumber(n) => !n.is_sign_negative() && !n.is_sign_positive(), + _ => unreachable!(), + } + } + + /// https://tc39.es/ecma262/#eqn-truncate + pub fn truncate(self, agent: &mut Agent) -> Number { + let x = self.into_value(); + + match x { + Value::Number(n) => { + let realm = agent.current_realm(); + let mut realm = realm.borrow_mut(); + let n = realm.heap.get(n).trunc(); + realm.heap.create(n) + } + Value::IntegerNumber(_) => self, + Value::FloatNumber(n) => n.trunc().into(), + _ => unreachable!(), + } + } + + pub fn into_f64(self, agent: &Agent) -> f64 { + let x = self.into_value(); + + match x { + Value::Number(n) => *agent.current_realm().borrow().heap.get(n), + Value::IntegerNumber(n) => Into::::into(n) as f64, + Value::FloatNumber(n) => n as f64, + _ => unreachable!(), + } + } + + /// A minimal version of ObjectIs when you know the arguments are numbers. + pub fn is(self, agent: &mut Agent, y: Self) -> bool { + // TODO: Add in spec from Object.is pertaining to numbers. + let x = self.into_value(); + let y = y.into_value(); + + match (x, y) { + (Value::Number(x), Value::Number(y)) => { + agent.current_realm().borrow().heap.get(x) + == agent.current_realm().borrow().heap.get(y) + } + (Value::Number(x), Value::IntegerNumber(y)) => { + *agent.current_realm().borrow().heap.get(x) == y.into_i64() as f64 + } + (Value::Number(x), Value::FloatNumber(y)) => { + *agent.current_realm().borrow().heap.get(x) == y as f64 + } + (Value::IntegerNumber(x), Value::Number(y)) => { + (x.into_i64() as f64) == *agent.current_realm().borrow().heap.get(y) + } + (Value::IntegerNumber(x), Value::IntegerNumber(y)) => x.into_i64() == y.into_i64(), + (Value::IntegerNumber(x), Value::FloatNumber(y)) => (x.into_i64() as f64) == y as f64, + (Value::FloatNumber(x), Value::Number(y)) => { + (x as f64) == *agent.current_realm().borrow().heap.get(y) + } + (Value::FloatNumber(x), Value::IntegerNumber(y)) => (x as f64) == y.into_i64() as f64, + (Value::FloatNumber(x), Value::FloatNumber(y)) => x == y, + _ => unreachable!(), + } + } + + pub fn is_odd_integer(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => { + let n = *agent.current_realm().borrow().heap.get(n); + n % 1.0 == 0.0 && n % 2.0 == 0.0 + } + Value::IntegerNumber(n) => Into::::into(n) % 2 == 0, + Value::FloatNumber(n) => n % 1.0 == 0.0 && n % 2.0 == 0.0, + _ => unreachable!(), + } + } + + pub fn abs(self, agent: &mut Agent) -> Self { + let x = self.into_value(); + + match x { + Value::Number(n) => { + let n = *agent.current_realm().borrow().heap.get(n); + if n > 0.0 { + self + } else { + agent.current_realm().borrow_mut().heap.create(-n) + } + } + Value::IntegerNumber(n) => { + let n = n.into_i64(); + Number(Value::IntegerNumber(SmallInteger::from_i64_unchecked( + n.abs(), + ))) + } + Value::FloatNumber(n) => Number(Value::FloatNumber(n.abs())), + _ => unreachable!(), + } + } + + pub fn greater_than(self, agent: &mut Agent, y: Self) -> Value { + let x = self; + y.less_than(agent, x) + } + + /// 6.1.6.1.1 Number::unaryMinus ( x ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-unaryMinus + pub fn unary_minus(self, agent: &mut Agent) -> Self { + let x = self.into_value(); + + // 1. If x is NaN, return NaN. + // NOTE: Computers do this automatically. + + // 2. Return the result of negating x; that is, compute a Number with the same magnitude but opposite sign. + match x { + Value::Number(n) => { + let realm = agent.current_realm(); + let mut realm = realm.borrow_mut(); + let value = *realm.heap.get(n); + realm.heap.create(-value) + } + Value::IntegerNumber(n) => SmallInteger::from_i64_unchecked(-n.into_i64()).into(), + Value::FloatNumber(n) => (-n).into(), + _ => unreachable!(), + } + } + + /// 6.1.6.1.2 Number::bitwiseNOT ( x ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseNOT + pub fn bitwise_not(self, agent: &mut Agent) -> JsResult { + let x = self.into_value(); + + // 1. Let oldValue be ! ToInt32(x). + let old_value = x.to_int32(agent)?; + + // 2. Return the result of applying bitwise complement to oldValue. The mathematical value of the result is exactly representable as a 32-bit two's complement bit string. + Ok(Number::from(!old_value)) + } + + /// 6.1.6.1.3 Number::exponentiate ( base, exponent ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-exponentiate + pub fn exponentiate(self, agent: &mut Agent, exponent: Self) -> Self { + let base = self; + + // 1. If exponent is NaN, return NaN. + if exponent.is_nan(agent) { + return Number::nan(); + } + + // 2. If exponent is either +0๐”ฝ or -0๐”ฝ, return 1๐”ฝ. + if exponent.is_pos_zero(agent) || exponent.is_neg_zero(agent) { + return Number::from(1); + } + + // 3. If base is NaN, return NaN. + if base.is_nan(agent) { + return Number::nan(); + } + + // 4. If base is +โˆž๐”ฝ, then + if base.is_pos_infinity(agent) { + // a. If exponent > +0๐”ฝ, return +โˆž๐”ฝ. Otherwise, return +0๐”ฝ. + return if exponent.greater_than(agent, Number::from(0)).is_true() { + Number::pos_inf() + } else { + Number::pos_zero() + }; + } + + // 5. If base is -โˆž๐”ฝ, then + if base.is_neg_infinity(agent) { + // a. If exponent > +0๐”ฝ, then + return if exponent.greater_than(agent, 0.into()).is_true() { + // i. If exponent is an odd integral Number, return -โˆž๐”ฝ. Otherwise, return +โˆž๐”ฝ. + if exponent.is_odd_integer(agent) { + Number::neg_inf() + } else { + Number::pos_inf() + } + } + // b. Else, + else { + // i. If exponent is an odd integral Number, return -0๐”ฝ. Otherwise, return +0๐”ฝ. + if exponent.is_odd_integer(agent) { + Number::neg_zero() + } else { + Number::pos_zero() + } + }; + } + + // 6. If base is +0๐”ฝ, then + if base.is_pos_zero(agent) { + // a. If exponent > +0๐”ฝ, return +0๐”ฝ. Otherwise, return +โˆž๐”ฝ. + return if exponent.greater_than(agent, Number::pos_zero()).is_true() { + Number::pos_zero() + } else { + Number::pos_inf() + }; + } + + // 7. If base is -0๐”ฝ, then + if base.is_neg_zero(agent) { + // a. If exponent > +0๐”ฝ, then + return if exponent.greater_than(agent, Number::pos_zero()).is_true() { + // i. If exponent is an odd integral Number, return -0๐”ฝ. Otherwise, return +0๐”ฝ. + if exponent.is_odd_integer(agent) { + Number::neg_zero() + } else { + Number::pos_zero() + } + } + // b. Else, + else { + // i. If exponent is an odd integral Number, return -โˆž๐”ฝ. Otherwise, return +โˆž๐”ฝ. + if exponent.is_odd_integer(agent) { + Number::neg_inf() + } else { + Number::pos_inf() + } + }; + } + + // 8. Assert: base is finite and is neither +0๐”ฝ nor -0๐”ฝ. + debug_assert!(base.is_finite(agent) && base.is_nonzero(agent)); + + // 9. If exponent is +โˆž๐”ฝ, then + if exponent.is_pos_infinity(agent) { + let base = base.abs(agent); + + // a. If abs(โ„(base)) > 1, return +โˆž๐”ฝ. + return if base.greater_than(agent, Number::from(1)).is_true() { + Number::pos_inf() + } + // b. If abs(โ„(base)) = 1, return NaN. + else if base.is(agent, Number::from(1)) { + Number::nan() + } + // c. If abs(โ„(base)) < 1, return +0๐”ฝ. + else { + Number::pos_zero() + }; + } + + // 10. If exponent is -โˆž๐”ฝ, then + if exponent.is_neg_infinity(agent) { + let base = base.into_f64(agent).abs(); + + // a. If abs(โ„(base)) > 1, return +0๐”ฝ. + return if base > 1.0 { + Number::pos_inf() + } + // b. If abs(โ„(base)) = 1, return NaN. + else if base == 1.0 { + Number::nan() + } + // c. If abs(โ„(base)) < 1, return +โˆž๐”ฝ. + else { + Number::pos_inf() + }; + } + + // 11. Assert: exponent is finite and is neither +0๐”ฝ nor -0๐”ฝ. + debug_assert!(exponent.is_finite(agent) && exponent.is_nonzero(agent)); + + // 12. If base < -0๐”ฝ and exponent is not an integral Number, return NaN. + if base.less_than(agent, Number::neg_zero()).is_true() && !exponent.is_odd_integer(agent) { + return Number::nan(); + } + + // 13. Return an implementation-approximated Number value representing the result of raising โ„(base) to the โ„(exponent) power. + agent + .current_realm() + .borrow_mut() + .heap + .create(base.into_f64(agent).powf(exponent.into_f64(agent))) + } + + // ... + + /// 6.1.6.1.12 Number::lessThan ( x, y ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-lessThan + pub fn less_than(self, agent: &mut Agent, y: Self) -> Value { + let x = self; + + // 1. If x is NaN, return undefined. + if x.is_nan(agent) { + return Value::Undefined; + } + + // 2. If y is NaN, return undefined. + if y.is_nan(agent) { + return Value::Undefined; + } + + // 3. If x is y, return false. + if x.is(agent, y) { + return false.into(); + } + + // 4. If x is +0๐”ฝ and y is -0๐”ฝ, return false. + if x.is_pos_zero(agent) && y.is_neg_zero(agent) { + return false.into(); + } + + // 5. If x is -0๐”ฝ and y is +0๐”ฝ, return false. + if x.is_neg_zero(agent) && y.is_pos_zero(agent) { + return false.into(); + } + + // 6. If x is +โˆž๐”ฝ, return false. + if x.is_pos_infinity(agent) { + return false.into(); + } + + // 7. If y is +โˆž๐”ฝ, return true. + if y.is_pos_infinity(agent) { + return true.into(); + } + + // 8. If y is -โˆž๐”ฝ, return false. + if y.is_neg_infinity(agent) { + return false.into(); + } + + // 9. If x is -โˆž๐”ฝ, return true. + if x.is_neg_infinity(agent) { + return true.into(); + } + + // 10. Assert: x and y are finite and non-zero. + debug_assert!( + x.is_finite(agent) && x.is_nonzero(agent) && y.is_finite(agent) && y.is_nonzero(agent) + ); + + // 11. If โ„(x) < โ„(y), return true. Otherwise, return false. + Value::Boolean(match (x.into_value(), y.into_value()) { + (Value::Number(x), Value::Number(y)) => { + agent.current_realm().borrow().heap.get(x) + < agent.current_realm().borrow().heap.get(y) + } + (Value::Number(x), Value::IntegerNumber(y)) => { + *agent.current_realm().borrow().heap.get(x) < y.into_i64() as f64 + } + (Value::Number(x), Value::FloatNumber(y)) => { + *agent.current_realm().borrow().heap.get(x) < y as f64 + } + (Value::IntegerNumber(x), Value::Number(y)) => { + (x.into_i64() as f64) < *agent.current_realm().borrow().heap.get(y) + } + (Value::IntegerNumber(x), Value::IntegerNumber(y)) => x.into_i64() < y.into_i64(), + (Value::IntegerNumber(x), Value::FloatNumber(y)) => (x.into_i64() as f64) < y as f64, + (Value::FloatNumber(x), Value::Number(y)) => { + (x as f64) < *agent.current_realm().borrow().heap.get(y) + } + (Value::FloatNumber(x), Value::IntegerNumber(y)) => (x as f64) < y.into_i64() as f64, + (Value::FloatNumber(x), Value::FloatNumber(y)) => x < y, + _ => unreachable!(), + }) + } + + /// 6.1.6.1.13 Number::equal ( x, y ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-equal + pub fn equal(self, agent: &mut Agent, y: Self) -> bool { + let x = self; + + // 1. If x is NaN, return false. + if x.is_nan(agent) { + return false; + } + + // 2. If y is NaN, return false. + if y.is_nan(agent) { + return false; + } + + // 3. If x is y, return true. + if x.is(agent, y) { + return true; + } + + // 4. If x is +0๐”ฝ and y is -0๐”ฝ, return true. + if x.is_pos_zero(agent) && y.is_neg_zero(agent) { + return true; + } + + // 5. If x is -0๐”ฝ and y is +0๐”ฝ, return true. + if x.is_neg_zero(agent) && y.is_pos_zero(agent) { + return true; + } + + // 6. Return false. + return false; + } + + /// 6.1.6.1.14 Number::sameValue ( x, y ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-sameValue + pub fn same_value(self, agent: &mut Agent, y: Self) -> bool { + let x = self; + + // 1. If x is NaN and y is NaN, return true. + if x.is_nan(agent) && y.is_nan(agent) { + return true; + } + + // 2. If x is +0๐”ฝ and y is -0๐”ฝ, return false. + if x.is_pos_zero(agent) && y.is_neg_zero(agent) { + return false; + } + + // 3. If x is -0๐”ฝ and y is +0๐”ฝ, return false. + if x.is_neg_zero(agent) && y.is_pos_zero(agent) { + return false; + } + + // 4. If x is y, return true. + if x.is(agent, y) { + return true; + } + + // 5. Return false. + return false; + } + + /// 6.1.6.1.15 Number::sameValueZero ( x, y ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero + pub fn same_value_zero(self, agent: &mut Agent, y: Self) -> bool { + let x = self; + + // 1. If x is NaN and y is NaN, return true. + if x.is_nan(agent) && y.is_nan(agent) { + return true; + } + + // 2. If x is +0๐”ฝ and y is -0๐”ฝ, return true. + if x.is_pos_zero(agent) && y.is_neg_zero(agent) { + return true; + } + + // 3. If x is -0๐”ฝ and y is +0๐”ฝ, return true. + if x.is_neg_zero(agent) && y.is_pos_zero(agent) { + return true; + } + + // 4. If x is y, return true. + if x.is(agent, y) { + return true; + } + + // 5. Return false. + return false; + } + + /// 6.1.6.1.16 NumberBitwiseOp ( op, x, y ) + /// https://tc39.es/ecma262/#sec-numberbitwiseop + pub fn bitwise_op(self, agent: &mut Agent, op: BitwiseOp, y: Self) -> JsResult { + let x = self; + + // 1. Let lnum be ! ToInt32(x). + let lnum = x.into_value().to_int32(agent)?; + + // 2. Let rnum be ! ToInt32(y). + let rnum = y.into_value().to_int32(agent)?; + + // 3. Let lbits be the 32-bit two's complement bit string representing โ„(lnum). + let lbits = lnum; + + // 4. Let rbits be the 32-bit two's complement bit string representing โ„(rnum). + let rbits = rnum; + + let result = match op { + // 5. If op is &, then + BitwiseOp::And => { + // a. Let result be the result of applying the bitwise AND operation to lbits and rbits. + lbits & rbits + } + // 6. Else if op is ^, then + BitwiseOp::Xor => { + // a. Let result be the result of applying the bitwise exclusive OR (XOR) operation to lbits and rbits. + lbits ^ rbits + } + // 7. Else, + // a. Assert: op is |. + BitwiseOp::Or => { + // b. Let result be the result of applying the bitwise inclusive OR operation to lbits and rbits. + lbits | rbits + } + }; + + // 8. Return the Number value for the integer represented by the 32-bit two's complement bit string result. + Ok(Number::from(result)) + } + + /// 6.1.6.1.17 Number::bitwiseAND ( x, y ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseAND + pub fn bitwise_and(self, agent: &mut Agent, y: Self) -> JsResult { + let x = self; + + // 1. Return NumberBitwiseOp(&, x, y). + x.bitwise_op(agent, BitwiseOp::And, y) + } + + /// 6.1.6.1.18 Number::bitwiseXOR ( x, y ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseXOR + pub fn bitwise_xor(self, agent: &mut Agent, y: Self) -> JsResult { + let x = self; + + // 1. Return NumberBitwiseOp(^, x, y). + x.bitwise_op(agent, BitwiseOp::Xor, y) + } + + /// 6.1.6.1.19 Number::bitwiseOR ( x, y ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseOR + pub fn bitwise_or(self, agent: &mut Agent, y: Self) -> JsResult { + let x = self; + + // 1. Return NumberBitwiseOp(|, x, y). + x.bitwise_op(agent, BitwiseOp::Or, y) + } + + // ... +} + +#[derive(Debug, Clone, Copy)] +pub enum BitwiseOp { + And, + Xor, + Or, +} diff --git a/nova_vm/src/types/language/object.rs b/nova_vm/src/types/language/object.rs new file mode 100644 index 00000000..b8dfb16e --- /dev/null +++ b/nova_vm/src/types/language/object.rs @@ -0,0 +1,201 @@ +mod data; +mod internal_methods; +mod property_key; +mod property_storage; + +use crate::{ + builtins::ordinary, + execution::{agent::ExceptionType, Agent, Intrinsics, JsResult}, + heap::{ArrayHeapData, FunctionHeapData, GetHeapData, Handle, ObjectHeapData}, + types::PropertyDescriptor, +}; + +use super::Value; +pub use data::ObjectData; +pub use internal_methods::InternalMethods; +pub use property_key::PropertyKey; +pub use property_storage::PropertyStorage; + +/// 6.1.7 The Object Type +/// https://tc39.es/ecma262/#sec-object-type +#[derive(Debug, Clone, Copy)] +pub enum Object { + Object(Handle), + Array(Handle), + Function(Handle), +} + +impl From> for Object { + fn from(value: Handle) -> Self { + Object::Object(value) + } +} + +impl From> for Object { + fn from(value: Handle) -> Self { + Object::Array(value) + } +} + +impl From> for Object { + fn from(value: Handle) -> Self { + Object::Function(value) + } +} + +impl From for Value { + fn from(value: Object) -> Self { + match value { + Object::Object(x) => Value::Object(x), + Object::Array(x) => Value::ArrayObject(x), + Object::Function(x) => Value::Function(x), + } + } +} + +impl TryFrom for Object { + type Error = (); + fn try_from(value: Value) -> Result { + match value { + Value::Object(x) => Ok(Object::Object(x)), + Value::ArrayObject(x) => Ok(Object::Array(x)), + Value::Function(x) => Ok(Object::Function(x)), + _ => Err(()), + } + } +} + +impl Object { + pub fn into_value(self) -> Value { + self.into() + } + + /// [[Extensible]] + pub fn extensible(self, agent: &mut Agent) -> bool { + match self { + Object::Object(object) => agent.current_realm().borrow().heap.get(object).extensible, + Object::Array(_) => true, + Object::Function(_) => true, + } + } + + /// [[Extensible]] + pub fn set_extensible(self, agent: &mut Agent, value: bool) { + match self { + Object::Object(object) => { + let realm = agent.current_realm(); + let mut realm = realm.borrow_mut(); + let object = realm.heap.get_mut(object); + object.extensible = true; + } + // TODO: Correct object/function impl + Object::Array(_) => {} + Object::Function(_) => {} + _ => unreachable!(), + } + } + + /// [[Prototype]] + pub fn prototype(self, agent: &mut Agent) -> Option { + let realm = agent.current_realm(); + let realm = realm.borrow(); + + match self { + Object::Object(object) => { + let object = realm.heap.get(object); + object.prototype.value?.try_into().ok() + } + Object::Array(array) => { + let array = realm.heap.get(array); + + if let Some(object) = array.object { + if let Some(prototype) = object.prototype(agent) { + return Some(prototype); + } + } + + Some(Intrinsics::array_prototype()) + } + Object::Function(_) => Some(Intrinsics::function_prototype()), + } + } + + /// [[Prototype]] + pub fn set_prototype(self, agent: &mut Agent, prototype: Option) { + match self { + Object::Object(object) => { + let realm = agent.current_realm(); + let mut realm = realm.borrow_mut(); + let object = realm.heap.get_mut(object); + object.prototype.value = prototype.map(|object| object.into_value()); + } + Object::Array(_) => todo!(), + Object::Function(_) => todo!(), + } + } + + pub fn internal_methods<'a>(self, agent: &mut Agent) -> &'a InternalMethods { + // TODO: Logic for fetching methods for objects/anything else. + &ordinary::METHODS + } + + pub fn property_storage(self) -> PropertyStorage { + PropertyStorage::new(self) + } + + /// /// 7.3.9 DefinePropertyOrThrow ( O, P, desc ) + /// https://tc39.es/ecma262/#sec-definepropertyorthrow + pub fn define_property_or_throw( + self, + agent: &mut Agent, + property_key: PropertyKey, + property_descriptor: PropertyDescriptor, + ) -> JsResult<()> { + // 1. Let success be ? O.[[DefineOwnProperty]](P, desc). + let success = (self.internal_methods(agent).define_own_property)( + agent, + self, + property_key, + property_descriptor, + )?; + + // 2. If success is false, throw a TypeError exception. + if !success { + return Err(agent.throw_exception( + ExceptionType::TypeError, + "Cannot assign to property on object.", + )); + } + + // 3. Return unused. + Ok(()) + } + + /// 7.3.5 CreateDataProperty ( O, P, V ) + /// https://tc39.es/ecma262/#sec-createdataproperty + pub fn create_data_property( + self: Self, + agent: &mut Agent, + property_key: PropertyKey, + value: Value, + ) -> JsResult { + // 1. Let newDesc be the PropertyDescriptor { + // [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true + // }. + let new_descriptor = PropertyDescriptor { + value: Some(value), + writable: Some(true), + enumerable: Some(true), + configurable: Some(true), + ..Default::default() + }; + + // 2. Return ? O.[[DefineOwnProperty]](P, newDesc). + (self.internal_methods(agent).define_own_property)( + agent, + self, + property_key, + new_descriptor, + ) + } +} diff --git a/nova_vm/src/types/language/object/data.rs b/nova_vm/src/types/language/object/data.rs new file mode 100644 index 00000000..4ab49cd6 --- /dev/null +++ b/nova_vm/src/types/language/object/data.rs @@ -0,0 +1,10 @@ +use super::Object; + +#[derive(Debug)] +pub struct ObjectData { + /// [[Prototype]] + pub prototype: Option, + + /// [[Extensible]] + pub extensible: bool, +} diff --git a/nova_vm/src/types/language/object/internal_methods.rs b/nova_vm/src/types/language/object/internal_methods.rs new file mode 100644 index 00000000..aaa23321 --- /dev/null +++ b/nova_vm/src/types/language/object/internal_methods.rs @@ -0,0 +1,89 @@ +use super::{Object, PropertyKey}; +use crate::{ + builtins::ArgumentsList, + execution::{Agent, JsResult}, + types::{PropertyDescriptor, Value}, +}; + +pub type GetPrototypeOf = fn(agent: &mut Agent, object: Object) -> Option; +pub type SetPrototypeOf = + fn(agent: &mut Agent, object: Object, prototype: Option) -> JsResult; +pub type IsExtensible = fn(agent: &mut Agent, object: Object) -> JsResult; +pub type PreventExtensions = fn(agent: &mut Agent, object: Object) -> JsResult; +pub type GetOwnProperty = fn( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, +) -> JsResult>; +pub type DefineOwnProperty = fn( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, + property_descriptor: PropertyDescriptor, +) -> JsResult; +pub type HasProperty = + fn(agent: &mut Agent, object: Object, property_key: PropertyKey) -> JsResult; +pub type Get = fn( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, + receiver: Value, +) -> JsResult; +pub type Set = fn( + agent: &mut Agent, + object: Object, + property_key: PropertyKey, + value: Value, + receiver: Value, +) -> JsResult; +pub type Delete = + fn(agent: &mut Agent, object: Object, property_key: PropertyKey) -> JsResult; +pub type OwnPropertyKeys = fn(agent: &mut Agent, object: Object) -> JsResult>; +pub type Call = + fn(object: Object, this_value: Value, arguments_list: ArgumentsList) -> JsResult; +pub type Construct = + fn(agent: &mut Agent, object: Object, arguments_list: ArgumentsList) -> JsResult; + +/// 6.1.7.2 Object Internal Methods and Internal Slots +/// https://tc39.es/ecma262/#sec-object-internal-methods-and-internal-slots +#[derive(Debug, Clone)] +pub struct InternalMethods { + /// [[GetPrototypeOf]] + pub get_prototype_of: GetPrototypeOf, + + /// [[SetPrototypeOf]] + pub set_prototype_of: SetPrototypeOf, + + /// [[IsExtensible]] + pub is_extensible: IsExtensible, + + /// [[PreventExtensions]] + pub prevent_extensions: PreventExtensions, + + /// [[GetOwnProperty]] + pub get_own_property: GetOwnProperty, + + /// [[DefineOwnProperty]] + pub define_own_property: DefineOwnProperty, + + /// [[HasProperty]] + pub has_property: HasProperty, + + /// [[Get]] + pub get: Get, + + /// [[Set]] + pub set: Set, + + /// [[Delete]] + pub delete: Delete, + + /// [[OwnPropertyKeys]] + pub own_property_keys: OwnPropertyKeys, + + /// [[Call]] + pub call: Option, + + /// [[Construct]] + pub construct: Option, +} diff --git a/nova_vm/src/types/language/object/property_key.rs b/nova_vm/src/types/language/object/property_key.rs new file mode 100644 index 00000000..8ae87d83 --- /dev/null +++ b/nova_vm/src/types/language/object/property_key.rs @@ -0,0 +1,125 @@ +use crate::{ + execution::Agent, + heap::{GetHeapData, Handle, StringHeapData}, + types::{String, Value}, + SmallInteger, SmallString, +}; + +#[derive(Debug, Clone, Copy)] +pub enum PropertyKey { + String(Handle), + SmallString(SmallString), + SmallInteger(SmallInteger), +} + +impl From> for PropertyKey { + fn from(value: Handle) -> Self { + PropertyKey::String(value) + } +} + +impl From for PropertyKey { + fn from(value: SmallString) -> Self { + PropertyKey::SmallString(value) + } +} + +impl From for PropertyKey { + fn from(value: SmallInteger) -> Self { + PropertyKey::SmallInteger(value) + } +} + +impl From for PropertyKey { + fn from(value: String) -> Self { + match value { + String::String(x) => PropertyKey::String(x), + String::SmallString(x) => PropertyKey::SmallString(x), + } + } +} + +impl TryFrom for PropertyKey { + type Error = (); + fn try_from(value: Value) -> Result { + match value { + Value::String(x) => Ok(PropertyKey::String(x)), + Value::SmallString(x) => Ok(PropertyKey::SmallString(x)), + Value::IntegerNumber(x) => Ok(PropertyKey::SmallInteger(x)), + _ => Err(()), + } + } +} + +impl From for Value { + fn from(value: PropertyKey) -> Self { + match value { + PropertyKey::String(x) => Value::String(x), + PropertyKey::SmallString(x) => Value::SmallString(x), + PropertyKey::SmallInteger(x) => Value::IntegerNumber(x), + } + } +} + +impl PropertyKey { + pub fn into_value(self) -> Value { + self.into() + } + + pub fn is_array_index(self) -> bool { + // TODO: string check + matches!(self.into_value(), Value::IntegerNumber(_)) + } + + pub(self) fn is_str_eq_num(s: &str, n: i64) -> bool { + // TODO: Come up with some advanced algorithm. + s == n.to_string() + } + + pub fn equals(self, agent: &mut Agent, y: Self) -> bool { + let x = self; + + match (x, y) { + // Assumes the interner is working correctly. + (PropertyKey::String(s1), PropertyKey::String(s2)) => s1 == s2, + (PropertyKey::SmallString(s1), PropertyKey::SmallString(s2)) => { + s1.as_str() == s2.as_str() + } + (PropertyKey::String(s), PropertyKey::SmallInteger(n)) => { + let realm = agent.current_realm(); + let realm = realm.borrow(); + let s = realm.heap.get(s); + + let Some(s) = s.as_str() else { + return false; + }; + + Self::is_str_eq_num(s, n.into_i64()) + } + (PropertyKey::SmallString(s), PropertyKey::SmallInteger(n)) => { + Self::is_str_eq_num(s.as_str(), n.into_i64()) + } + (PropertyKey::SmallInteger(n1), PropertyKey::SmallInteger(n2)) => { + n1.into_i64() == n2.into_i64() + } + (PropertyKey::SmallInteger(_), _) => y.equals(agent, self), + _ => false, + } + } +} + +#[test] +fn compare_num_str() { + assert!(PropertyKey::is_str_eq_num("23", 23)); + assert!(PropertyKey::is_str_eq_num("-23", -23)); + assert!(PropertyKey::is_str_eq_num("-120543809", -120543809)); + assert!(PropertyKey::is_str_eq_num("985493", 985493)); + assert!(PropertyKey::is_str_eq_num("0", 0)); + assert!(PropertyKey::is_str_eq_num("5", 5)); + assert!(PropertyKey::is_str_eq_num("-5", -5)); + assert!(PropertyKey::is_str_eq_num("9302", 9302)); + assert!(PropertyKey::is_str_eq_num("19", 19)); + + assert!(!PropertyKey::is_str_eq_num("19", 91)); + assert!(!PropertyKey::is_str_eq_num("-19", 19)); +} diff --git a/nova_vm/src/types/language/object/property_storage.rs b/nova_vm/src/types/language/object/property_storage.rs new file mode 100644 index 00000000..5074e954 --- /dev/null +++ b/nova_vm/src/types/language/object/property_storage.rs @@ -0,0 +1,102 @@ +use std::{ + cell::{Ref, RefCell}, + rc::Rc, +}; + +use crate::{ + execution::{Agent, Realm}, + heap::GetHeapData, + types::{PropertyDescriptor, String, Value}, +}; + +use super::{Object, PropertyKey}; + +#[derive(Clone, Copy)] +pub struct PropertyStorage(Object); + +impl PropertyStorage { + pub(crate) fn new(object: Object) -> Self { + Self(object) + } + + fn into_object(self) -> Object { + self.0 + } + + fn into_value(self) -> Value { + self.into_object().into_value() + } + + pub fn has(self, agent: &mut Agent, key: PropertyKey) -> bool { + let object = self.into_value(); + + match object { + Value::Object(object) => { + let realm = agent.current_realm(); + let realm = realm.borrow(); + let object = realm.heap.get(object); + object + .entries + .iter() + .any(|entry| entry.key.equals(agent, key)) + } + Value::ArrayObject(array) => { + if key.equals( + agent, + PropertyKey::from(String::try_from("length").unwrap()), + ) { + return true; + } + + let realm = agent.current_realm(); + let realm = realm.borrow(); + let array = realm.heap.get(array); + + if let Value::IntegerNumber(number) = key.into_value() { + if let Some(_) = TryInto::::try_into(number.into_i64()) + .map(|idx| array.elements.get(idx)) + .ok() + { + return true; + } + } + + if let Some(object) = array.object { + return object.property_storage().has(agent, key); + } + + false + } + Value::Function(_) => todo!(), + _ => unreachable!(), + } + } + + pub fn get(self, agent: &mut Agent, key: PropertyKey) -> Option { + todo!(); + } + + pub fn set(self, agent: &mut Agent, property_key: PropertyKey, descriptor: PropertyDescriptor) { + } + + pub fn remove(self, agent: &mut Agent, property_key: PropertyKey) {} + + pub fn entries<'a, 'b>(self, agent: &'a Agent<'b, 'b>) -> Entries<'a, 'b> { + todo!() + } +} + +#[derive(Debug)] +pub struct Entries<'a, 'b> { + pub realm: Ref<'a, Realm<'b, 'b>>, + _rc: std::marker::PhantomData<&'a Rc>>>, +} + +impl<'a, 'b> Entries<'a, 'b> { + fn new(realm: Ref<'a, Realm<'b, 'b>>) -> Self { + Self { + realm, + _rc: Default::default(), + } + } +} diff --git a/nova_vm/src/types/language/string.rs b/nova_vm/src/types/language/string.rs new file mode 100644 index 00000000..06050fa6 --- /dev/null +++ b/nova_vm/src/types/language/string.rs @@ -0,0 +1,111 @@ +use super::Value; +use crate::{ + execution::Agent, + heap::{GetHeapData, Handle, StringHeapData}, + SmallString, +}; + +/// 6.1.4 The String Type +/// https://tc39.es/ecma262/#sec-ecmascript-language-types-string-type +#[derive(Debug, Clone, Copy)] +pub enum String { + String(Handle), + SmallString(SmallString), +} + +impl From> for String { + fn from(value: Handle) -> Self { + String::String(value) + } +} + +impl From for String { + fn from(value: SmallString) -> Self { + String::SmallString(value) + } +} + +impl TryFrom<&str> for String { + type Error = (); + fn try_from(value: &str) -> Result { + SmallString::try_from(value).map(|s| String::SmallString(s)) + } +} + +impl TryFrom for String { + type Error = (); + fn try_from(value: Value) -> Result { + match value { + Value::String(x) => Ok(String::String(x)), + Value::SmallString(x) => Ok(String::SmallString(x)), + _ => Err(()), + } + } +} + +impl From for Value { + fn from(value: String) -> Self { + match value { + String::String(x) => Value::String(x), + String::SmallString(x) => Value::SmallString(x), + } + } +} + +impl String { + pub fn into_value(self) -> Value { + self.into() + } + + /// Byte length of the string. + pub fn len(self, agent: &Agent) -> usize { + match self { + String::String(s) => agent.current_realm().borrow().heap.get(s).len(), + String::SmallString(s) => s.len(), + } + } + + pub fn as_str<'a>(&'a self, agent: &mut Agent) -> Option<&'a str> { + match self { + // SAFETY: The mutable reference to the Agent ensures no mutable + // access to the realm. + String::String(s) => unsafe { + std::mem::transmute(agent.current_realm().borrow().heap.get(*s).as_str()) + }, + String::SmallString(s) => Some(s.as_str()), + } + } + + /// 6.1.4.1 StringIndexOf ( string, searchValue, fromIndex ) + /// https://tc39.es/ecma262/#sec-stringindexof + pub fn index_of(self, agent: &mut Agent, search_value: Self, from_index: i64) -> i64 { + // TODO: Figure out what we should do for invalid cases. + let string = self.as_str(agent).unwrap(); + let search_value = search_value.as_str(agent).unwrap(); + + // 1. Let len be the length of string. + let len = string.len() as i64; + + // 2. If searchValue is the empty String and fromIndex โ‰ค len, return fromIndex. + if len == 0 && from_index <= len { + return from_index as i64; + } + + // 3. Let searchLen be the length of searchValue. + let search_len = search_value.len() as i64; + + // 4. For each integer i such that fromIndex โ‰ค i โ‰ค len - searchLen, in ascending order, do + for i in from_index..=(len - search_len) as i64 { + // a. Let candidate be the substring of string from i to i + searchLen. + let candidate = &string[i as usize..(i + search_len) as usize]; + + // b. If candidate is searchValue, return i. + if candidate == search_value { + return i; + } + } + + // 5. Return -1. + -1 + } +} diff --git a/nova_vm/src/types/language/value.rs b/nova_vm/src/types/language/value.rs new file mode 100644 index 00000000..86ed125a --- /dev/null +++ b/nova_vm/src/types/language/value.rs @@ -0,0 +1,697 @@ +use crate::{ + execution::{Agent, JsResult}, + heap::{ + ArrayHeapData, BigIntHeapData, FunctionHeapData, Handle, NumberHeapData, ObjectHeapData, + StringHeapData, SymbolHeapData, + }, + SmallInteger, SmallString, +}; + +use super::{BigInt, Number}; + +/// 6.1 ECMAScript Language Types +/// https://tc39.es/ecma262/#sec-ecmascript-language-types +#[derive(Debug, Clone, Copy)] +pub enum Value { + /// 6.1.1 The Undefined Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-undefined-type + Undefined, + + /// 6.1.2 The Null Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-null-type + Null, + + /// 6.1.3 The Boolean Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-boolean-type + Boolean(bool), + + /// 6.1.4 The String Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-string-type + String(Handle), + SmallString(SmallString), + + /// 6.1.5 The Symbol Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-symbol-type + Symbol(Handle), + + /// 6.1.6.1 The Number Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type + Number(Handle), + IntegerNumber(SmallInteger), // 56-bit signed integer. + FloatNumber(f32), + + /// 6.1.6.2 The BigInt Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-bigint-type + BigInt(Handle), + + /// 6.1.7 The Object Type + /// https://tc39.es/ecma262/#sec-object-type + Object(Handle), + ArrayObject(Handle), + Function(Handle), +} + +impl Default for Value { + fn default() -> Self { + Value::Undefined + } +} + +#[derive(Debug, Clone, Copy)] +pub enum PreferredType { + String, + Number, +} + +impl Value { + pub fn nan() -> Self { + Number::nan().into_value() + } + + pub fn infinity() -> Self { + Number::pos_inf().into_value() + } + + pub fn neg_infinity() -> Self { + Number::neg_inf().into_value() + } + + pub fn is_true(self) -> bool { + matches!(self, Value::Boolean(true)) + } + + pub fn is_false(self) -> bool { + matches!(self, Value::Boolean(false)) + } + + pub fn is_object(self) -> bool { + matches!( + self, + Value::Object(_) | Value::ArrayObject(_) | Value::Function(_) + ) + } + + pub fn is_string(self) -> bool { + matches!(self, Value::String(_) | Value::SmallString(_)) + } + + pub fn is_boolean(self) -> bool { + // TODO: Check for Boolean object instance. + matches!(self, Value::Boolean(_)) + } + + pub fn is_null(self) -> bool { + matches!(self, Value::Null) + } + + pub fn is_undefined(self) -> bool { + matches!(self, Value::Undefined) + } + + pub fn is_pos_zero(self, agent: &mut Agent) -> bool { + Number::try_from(self) + .map(|n| n.is_pos_zero(agent)) + .unwrap_or(false) + } + + pub fn is_neg_zero(self, agent: &mut Agent) -> bool { + Number::try_from(self) + .map(|n| n.is_neg_zero(agent)) + .unwrap_or(false) + } + + pub fn is_nan(self, agent: &mut Agent) -> bool { + Number::try_from(self) + .map(|n| n.is_nan(agent)) + .unwrap_or(false) + } + + pub fn is_bigint(self) -> bool { + // TODO: Check for BigInt object instance. + matches!(self, Value::BigInt(_)) + } + + pub fn is_symbol(self) -> bool { + matches!(self, Value::Symbol(_)) + } + + pub fn is_number(self) -> bool { + matches!( + self, + Value::Number(_) | Value::FloatNumber(_) | Value::IntegerNumber(_) + ) + } + + pub fn is_empty_string(self) -> bool { + if let Value::SmallString(s) = self { + s.is_empty() + } else { + false + } + } + + /// 7.1.1 ToPrimitive ( input [ , preferredType ] ) + /// https://tc39.es/ecma262/#sec-toprimitive + pub fn to_primitive( + self, + agent: &mut Agent, + preferred_type: Option, + ) -> JsResult { + let input = self; + + // 1. If input is an Object, then + if input.is_object() { + // a. Let exoticToPrim be ? GetMethod(input, @@toPrimitive). + // b. If exoticToPrim is not undefined, then + // i. If preferredType is not present, then + // 1. Let hint be "default". + // ii. Else if preferredType is string, then + // 1. Let hint be "string". + // iii. Else, + // 1. Assert: preferredType is number. + // 2. Let hint be "number". + // iv. Let result be ? Call(exoticToPrim, input, ยซ hint ยป). + // v. If result is not an Object, return result. + // vi. Throw a TypeError exception. + // c. If preferredType is not present, let preferredType be number. + // d. Return ? OrdinaryToPrimitive(input, preferredType). + todo!(); + } + + // 2. Return input. + Ok(input) + } + + /// 7.1.1.1 OrdinaryToPrimitive ( O, hint ) + /// https://tc39.es/ecma262/#sec-ordinarytoprimitive + pub fn ordinary_to_primitive(self, agent: &mut Agent, hint: PreferredType) -> JsResult { + // TODO: This takes in an object...so probably put it in Object. + let o = self; + + // 1. If hint is string, then + let method_names = if matches!(hint, PreferredType::String) { + // a. Let methodNames be ยซ "toString", "valueOf" ยป. + &["toString", "valueOf"] + } + // 2. Else, + else { + // a. Let methodNames be ยซ "valueOf", "toString" ยป. + &["valueOf", "toString"] + }; + + // TODO: 3. For each element name of methodNames, do + for name in method_names.iter() { + // a. Let method be ? Get(O, name). + // b. If IsCallable(method) is true, then + // i. Let result be ? Call(method, O). + // ii. If result is not an Object, return result. + // 4. Throw a TypeError exception. + } + + todo!() + } + + /// 7.1.2 ToBoolean ( argument ) + /// https://tc39.es/ecma262/#sec-toboolean + pub fn to_boolean(self, agent: &mut Agent) -> JsResult { + let argument = self; + + // 1. If argument is a Boolean, return argument. + if argument.is_boolean() { + return Ok(argument); + } + + // 2. If argument is one of undefined, null, +0๐”ฝ, -0๐”ฝ, NaN, 0โ„ค, or the empty String, return false. + // TODO: checks for 0โ„ค + if argument.is_undefined() + || argument.is_null() + || argument.is_pos_zero(agent) + || argument.is_neg_zero(agent) + || argument.is_nan(agent) + || argument.is_empty_string() + { + return Ok(false.into()); + } + + // 3. NOTE: This step is replaced in section B.3.6.1. + + // 4. Return true. + return Ok(true.into()); + } + + /// 7.1.3 ToNumeric ( value ) + /// https://tc39.es/ecma262/#sec-tonumeric + pub fn to_numeric(self, agent: &mut Agent) -> JsResult { + let value = self; + + // 1. Let primValue be ? ToPrimitive(value, number). + let prim_value = value.to_primitive(agent, Some(PreferredType::Number))?; + + // 2. If primValue is a BigInt, return primValue. + if prim_value.is_bigint() { + return Ok(prim_value); + } + + // 3. Return ? ToNumber(primValue). + prim_value.to_number(agent).map(|n| n.into_value()) + } + + /// 7.1.4 ToNumber ( argument ) + /// https://tc39.es/ecma262/#sec-tonumber + pub fn to_number(self, agent: &mut Agent) -> JsResult { + let argument = self; + + // 1. If argument is a Number, return argument. + if let Ok(argument) = Number::try_from(argument) { + return Ok(argument); + } + + // 2. If argument is either a Symbol or a BigInt, throw a TypeError exception. + if argument.is_symbol() || argument.is_bigint() { + todo!(); + } + + // 3. If argument is undefined, return NaN. + if argument.is_undefined() { + return Ok(Number::nan()); + } + + // 4. If argument is either null or false, return +0๐”ฝ. + if argument.is_null() || argument.is_false() { + return Ok(Number::from(0)); + } + + // 5. If argument is true, return 1๐”ฝ. + if argument.is_true() { + return Ok(Number::from(1)); + } + + // 6. If argument is a String, return StringToNumber(argument). + if argument.is_string() { + todo!(); + } + + // 7. Assert: argument is an Object. + debug_assert!(argument.is_object()); + + // 8. Let primValue be ? ToPrimitive(argument, number). + let prim_value = argument.to_primitive(agent, Some(PreferredType::Number))?; + + // 9. Assert: primValue is not an Object. + debug_assert!(!prim_value.is_object()); + + // 10. Return ? ToNumber(primValue). + prim_value.to_number(agent) + } + + /// 7.1.5 ToIntegerOrInfinity ( argument ) + /// https://tc39.es/ecma262/#sec-tointegerorinfinity + // TODO: Should we add another [`Value`] newtype for IntegerOrInfinity? + pub fn to_integer_or_infinty(self, agent: &mut Agent) -> JsResult { + let argument: Value = self; + + // 1. Let number be ? ToNumber(argument). + let number = argument.to_number(agent)?; + + // 2. If number is one of NaN, +0๐”ฝ, or -0๐”ฝ, return 0. + if number.is_nan(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(Number::pos_zero()); + } + + // 3. If number is +โˆž๐”ฝ, return +โˆž. + if number.is_pos_infinity(agent) { + return Ok(Number::pos_inf()); + } + + // 4. If number is -โˆž๐”ฝ, return -โˆž. + if number.is_neg_infinity(agent) { + return Ok(Number::neg_inf()); + } + + // 5. Return truncate(โ„(number)). + Ok(number.truncate(agent)) + } + + /// 7.1.6 ToInt32 ( argument ) + /// https://tc39.es/ecma262/#sec-toint32 + pub fn to_int32(self, agent: &mut Agent) -> JsResult { + let argument = self; + + // 1. Let number be ? ToNumber(argument). + let number = argument.to_number(agent)?; + + // 2. If number is not finite or number is either +0๐”ฝ or -0๐”ฝ, return +0๐”ฝ. + if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(0); + } + + // 3. Let int be truncate(โ„(number)). + let int = number.truncate(agent).into_f64(agent); + + // 4. Let int32bit be int modulo 2^32. + let int32bit = int % 2f64.powi(32); + + // 5. If int32bit โ‰ฅ 2^31, return ๐”ฝ(int32bit - 2^32); otherwise return ๐”ฝ(int32bit). + Ok(if int32bit >= 2f64.powi(32) { + int32bit - 2f64.powi(32) + } else { + int32bit + } as i32) + } + + /// 7.1.7 ToUint32 ( argument ) + /// https://tc39.es/ecma262/#sec-touint32 + pub fn to_uint32(self, agent: &mut Agent) -> JsResult { + let argument = self; + + // 1. Let number be ? ToNumber(argument). + let number = argument.to_number(agent)?; + + // 2. If number is not finite or number is either +0๐”ฝ or -0๐”ฝ, return +0๐”ฝ. + if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(0); + } + + // 3. Let int be truncate(โ„(number)). + let int = number.truncate(agent).into_f64(agent); + + // 4. Let int32bit be int modulo 2^32. + let int32bit = int % 2f64.powi(32); + + // 5. Return ๐”ฝ(int32bit). + Ok(int32bit as u32) + } + + /// 7.1.8 ToInt16 ( argument ) + /// https://tc39.es/ecma262/#sec-toint16 + pub fn to_int16(self, agent: &mut Agent) -> JsResult { + let argument = self; + + // 1. Let number be ? ToNumber(argument). + let number = argument.to_number(agent)?; + + // 2. If number is not finite or number is either +0๐”ฝ or -0๐”ฝ, return +0๐”ฝ. + if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(0); + } + + // 3. Let int be truncate(โ„(number)). + let int = number.truncate(agent).into_f64(agent); + + // 4. Let int16bit be int modulo 2^16. + let int16bit = int % 2f64.powi(16); + + // 5. If int16bit โ‰ฅ 2^15, return ๐”ฝ(int16bit - 2^16); otherwise return ๐”ฝ(int16bit). + Ok(if int16bit >= 2f64.powi(15) { + int16bit - 2f64.powi(16) + } else { + int16bit + } as i16) + } + + /// 7.1.9 ToUint16 ( argument ) + /// https://tc39.es/ecma262/#sec-touint16 + pub fn to_uint16(self, agent: &mut Agent) -> JsResult { + let argument = self; + + // 1. Let number be ? ToNumber(argument). + let number = argument.to_number(agent)?; + + // 2. If number is not finite or number is either +0๐”ฝ or -0๐”ฝ, return +0๐”ฝ. + if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(0); + } + + // 3. Let int be truncate(โ„(number)). + let int = number.truncate(agent).into_f64(agent); + + // 4. Let int16bit be int modulo 2^16. + let int16bit = int % 2f64.powi(16); + + // Return ๐”ฝ(int16bit). + Ok(int16bit as i16) + } + + /// 7.1.10 ToInt8 ( argument ) + /// https://tc39.es/ecma262/#sec-toint8 + pub fn to_int8(self, agent: &mut Agent) -> JsResult { + let argument = self; + + // 1. Let number be ? ToNumber(argument). + let number = argument.to_number(agent)?; + + // 2. If number is not finite or number is either +0๐”ฝ or -0๐”ฝ, return +0๐”ฝ. + if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(0); + } + + // 3. Let int be truncate(โ„(number)). + let int = number.truncate(agent).into_f64(agent); + + // 4. Let int8bit be int modulo 2^8. + let int8bit = int % 2f64.powi(8); + + // 5. If int8bit โ‰ฅ 2^7, return ๐”ฝ(int8bit - 2^8); otherwise return ๐”ฝ(int8bit). + Ok(if int8bit >= 2f64.powi(7) { + int8bit - 2f64.powi(8) + } else { + int8bit + } as i8) + } + + /// 7.1.11 ToUint8 ( argument ) + /// https://tc39.es/ecma262/#sec-touint8 + pub fn to_uint8(self, agent: &mut Agent) -> JsResult { + let argument = self; + + // 1. Let number be ? ToNumber(argument). + let number = argument.to_number(agent)?; + + // 2. If number is not finite or number is either +0๐”ฝ or -0๐”ฝ, return +0๐”ฝ. + if !number.is_finite(agent) || number.is_pos_zero(agent) || number.is_neg_zero(agent) { + return Ok(0); + } + + // 3. Let int be truncate(โ„(number)). + let int = number.truncate(agent).into_f64(agent); + + // 4. Let int8bit be int modulo 2^8. + let int8bit = int % 2f64.powi(8); + + // 5. Return ๐”ฝ(int8bit). + Ok(int8bit as u8) + } + + /// 7.1.12 ToUint8Clamp ( argument ) + /// https://tc39.es/ecma262/#sec-touint8clamp + pub fn to_uint8_clamp(self, agent: &mut Agent) -> JsResult { + let argument = self; + + // 1. Let number be ? ToNumber(argument). + let number = argument.to_number(agent)?; + + // 2. If number is NaN, return +0๐”ฝ. + if number.is_nan(agent) { + return Ok(0); + } + + // 3. Let mv be the extended mathematical value of number. + // TODO: Is there a better way? + let mv = number.into_f64(agent); + + // 4. Let clamped be the result of clamping mv between 0 and 255. + let clamped = mv.clamp(0.0, 255.0); + + // 5. Let f be floor(clamped). + let f = clamped.floor(); + + Ok( + // 6. If clamped < f + 0.5, return ๐”ฝ(f). + if clamped < f + 0.5 { + f as u8 + } + // 7. If clamped > f + 0.5, return ๐”ฝ(f + 1). + else if clamped > f + 0.5 { + f as u8 + 1 + } + // 8. If f is even, return ๐”ฝ(f). Otherwise, return ๐”ฝ(f + 1). + else if f % 2.0 == 0.0 { + f as u8 + } else { + f as u8 + 1 + }, + ) + } + + /// 7.1.13 ToBigInt ( argument ) + /// https://tc39.es/ecma262/#sec-tobigint + pub fn to_big_int(self, agent: &mut Agent) -> JsResult { + let argument = self; + + // 1. Let prim be ? ToPrimitive(argument, number). + let prim = argument.to_primitive(agent, Some(PreferredType::Number))?; + + // 2. Return the value that prim corresponds to in Table 12. + todo!() + } + + /// 7.1.17 ToString ( argument ) + /// https://tc39.es/ecma262/#sec-tostring + pub fn to_string(self, agent: &mut Agent) -> JsResult { + let argument = self; + + // TODO: 1. If argument is a String, return argument. + // 2. If argument is a Symbol, throw a TypeError exception. + // 3. If argument is undefined, return "undefined". + // 4. If argument is null, return "null". + // 5. If argument is true, return "true". + // 6. If argument is false, return "false". + // 7. If argument is a Number, return Number::toString(argument, 10). + // 8. If argument is a BigInt, return BigInt::toString(argument, 10). + // 9. Assert: argument is an Object. + // 10. Let primValue be ? ToPrimitive(argument, string). + // 11. Assert: primValue is not an Object. + // 12. Return ? ToString(primValue). + + todo!() + } + + fn is_same_type(self, y: Self) -> bool { + let x = self; + (x.is_undefined() && y.is_undefined()) + || (x.is_null() && y.is_null()) + || (x.is_boolean() && y.is_boolean()) + || (x.is_string() && y.is_string()) + || (x.is_symbol() && y.is_symbol()) + || (x.is_number() && y.is_number()) + || (x.is_object() && y.is_object()) + } + + /// 7.2.10 SameValue ( x, y ) + /// https://tc39.es/ecma262/#sec-samevalue + pub fn same_value(self, agent: &mut Agent, y: Self) -> bool { + let x = self; + + // 1. If Type(x) is not Type(y), return false. + if !x.is_same_type(y) { + return false; + } + + // 2. If x is a Number, then + if let (Ok(x), Ok(y)) = (Number::try_from(x), Number::try_from(y)) { + // a. Return Number::sameValue(x, y). + return x.same_value(agent, y); + } + + // 3. Return SameValueNonNumber(x, y). + x.same_value_non_number(agent, y) + } + + /// 7.2.12 SameValueNonNumber ( x, y ) + /// https://tc39.es/ecma262/#sec-samevaluenonnumber + pub fn same_value_non_number(self, agent: &mut Agent, y: Self) -> bool { + let x = self; + + // 1. Assert: Type(x) is Type(y). + debug_assert!(x.is_same_type(y)); + + // 2. If x is either null or undefined, return true. + if x.is_null() || x.is_undefined() { + return true; + } + + // 3. If x is a BigInt, then + if x.is_bigint() { + // a. Return BigInt::equal(x, y). + todo!(); + } + + // 4. If x is a String, then + if x.is_string() { + // a. If x and y have the same length and the same code units in the same positions, return true; otherwise, return false. + todo!(); + } + + // 5. If x is a Boolean, then + if x.is_boolean() { + // a. If x and y are both true or both false, return true; otherwise, return false. + return x.is_true() == y.is_true(); + } + + // 6. NOTE: All other ECMAScript language values are compared by identity. + // 7. If x is y, return true; otherwise, return false. + todo!() + } +} + +impl From for Value { + fn from(value: bool) -> Self { + Value::Boolean(value) + } +} + +impl From> for Value { + fn from(value: Option) -> Self { + value.unwrap_or(Value::Undefined) + } +} + +impl TryFrom<&str> for Value { + type Error = (); + fn try_from(value: &str) -> Result { + if let Ok(data) = value.try_into() { + Ok(Value::SmallString(data)) + } else { + Err(()) + } + } +} + +impl TryFrom for Value { + type Error = (); + fn try_from(value: f64) -> Result { + // TODO: verify logic + if value as f32 as f64 == value { + Ok(Value::FloatNumber(value as f32)) + } else { + Err(()) + } + } +} + +impl From for Value { + fn from(value: Number) -> Self { + value.into_value() + } +} + +impl From for Value { + fn from(value: f32) -> Self { + Value::FloatNumber(value) + } +} + +impl TryFrom for Value { + type Error = (); + fn try_from(value: i64) -> Result { + Ok(Value::IntegerNumber(SmallInteger::try_from(value)?)) + } +} + +macro_rules! impl_value_from_n { + ($size: ty) => { + impl From<$size> for Value { + fn from(value: $size) -> Self { + let n: i64 = value.into(); + Value::IntegerNumber(SmallInteger::from_i64_unchecked(n)) + } + } + }; +} + +impl_value_from_n!(u8); +impl_value_from_n!(i8); +impl_value_from_n!(u16); +impl_value_from_n!(i16); +impl_value_from_n!(u32); +impl_value_from_n!(i32); diff --git a/nova_vm/src/types/spec.rs b/nova_vm/src/types/spec.rs new file mode 100644 index 00000000..f3e7960e --- /dev/null +++ b/nova_vm/src/types/spec.rs @@ -0,0 +1,5 @@ +mod property_descriptor; +mod reference; + +pub use property_descriptor::PropertyDescriptor; +pub use reference::{Base, Reference, ReferencedName}; diff --git a/nova_vm/src/types/spec/bytecode.rs b/nova_vm/src/types/spec/bytecode.rs new file mode 100644 index 00000000..8d7db609 --- /dev/null +++ b/nova_vm/src/types/spec/bytecode.rs @@ -0,0 +1,7 @@ +mod executable; +mod instructions; +mod vm; + +pub use executable::{Executable, IndexType}; +pub use instructions::{Instruction, InstructionIter}; +pub use vm::Vm; diff --git a/nova_vm/src/types/spec/property_descriptor.rs b/nova_vm/src/types/spec/property_descriptor.rs new file mode 100644 index 00000000..e20f16f8 --- /dev/null +++ b/nova_vm/src/types/spec/property_descriptor.rs @@ -0,0 +1,112 @@ +use crate::{ + execution::{Agent, JsResult}, + types::{Object, Value}, +}; + +/// 6.2.6 The Property Descriptor Specification Type +/// https://tc39.es/ecma262/#sec-property-descriptor-specification-type +#[derive(Debug, Clone, Default)] +pub struct PropertyDescriptor { + /// [[Value]] + pub value: Option, + + /// [[Writable]] + pub writable: Option, + + /// [[Get]] + pub get: Option, + + /// [[Set]] + pub set: Option, + + /// [[Enumerable]] + pub enumerable: Option, + + /// [[Configurable]] + pub configurable: Option, +} + +impl PropertyDescriptor { + /// 6.2.6.1 IsAccessorDescriptor ( Desc ) + /// https://tc39.es/ecma262/#sec-isaccessordescriptor + pub fn is_accessor_descriptor(&self) -> bool { + // 1. If Desc is undefined, return false. + match (self.get, self.set) { + // 2. If Desc has a [[Get]] field, return true. + (Some(_), _) => true, + // 3. If Desc has a [[Set]] field, return true. + (_, Some(_)) => true, + // 4. Return false. + _ => false, + } + } + + /// 6.2.6.2 IsDataDescriptor ( Desc ) + /// https://tc39.es/ecma262/#sec-isdatadescriptor + pub fn is_data_descriptor(&self) -> bool { + // 1. If Desc is undefined, return false. + match (self.value, self.writable) { + // 2. If Desc has a [[Value]] field, return true. + (Some(_), _) => true, + // 3. If Desc has a [[Writable]] field, return true. + (_, Some(_)) => true, + // 4. Return false. + _ => false, + } + } + + /// 6.2.6.3 IsGenericDescriptor ( Desc ) + /// https://tc39.es/ecma262/#sec-isgenericdescriptor + pub fn is_generic_descriptor(&self) -> bool { + // 1. If Desc is undefined, return false. + // 2. If IsAccessorDescriptor(Desc) is true, return false. + // 3. If IsDataDescriptor(Desc) is true, return false. + // 4. Return true. + !self.is_accessor_descriptor() && !self.is_data_descriptor() + } + + /// 6.2.6.4 FromPropertyDescriptor ( Desc ) + /// https://tc39.es/ecma262/#sec-frompropertydescriptor + pub fn from_property_descriptor(&self, agent: &mut Agent) -> JsResult { + let realm = agent.current_realm(); + let realm = realm.borrow_mut(); + + // 1. If Desc is undefined, return undefined. + + // 2. Let obj be OrdinaryObjectCreate(%Object.prototype%). + // 3. Assert: obj is an extensible ordinary object with no own properties. + + // 4. If Desc has a [[Value]] field, then + // a. Perform ! CreateDataPropertyOrThrow(obj, "value", Desc.[[Value]]). + + // 5. If Desc has a [[Writable]] field, then + + // 6. If Desc has a [[Get]] field, then + // a. Perform ! CreateDataPropertyOrThrow(obj, "get", Desc.[[Get]]). + // 7. If Desc has a [[Set]] field, then + // a. Perform ! CreateDataPropertyOrThrow(obj, "set", Desc.[[Set]]). + // 8. If Desc has an [[Enumerable]] field, then + // a. Perform ! CreateDataPropertyOrThrow(obj, "enumerable", Desc.[[Enumerable]]). + + // 9. If Desc has a [[Configurable]] field, then + // a. Perform ! CreateDataPropertyOrThrow(obj, "configurable", Desc.[[Configurable]]). + // 10. Return obj. + todo!() + } + + pub fn is_fully_populated(&self) -> bool { + ((self.value.is_some() && self.writable.is_some()) + || (self.get.is_some() && self.set.is_some())) + && self.enumerable.is_some() + && self.configurable.is_some() + } + + pub fn has_fields(&self) -> bool { + self.value.is_some() + || self.writable.is_some() + || self.get.is_some() + || self.set.is_some() + || self.enumerable.is_some() + || self.configurable.is_some() + } +} diff --git a/nova_vm/src/types/spec/reference.rs b/nova_vm/src/types/spec/reference.rs new file mode 100644 index 00000000..0027e6fb --- /dev/null +++ b/nova_vm/src/types/spec/reference.rs @@ -0,0 +1,66 @@ +use crate::{ + execution::Environment, + types::{Symbol, Value}, +}; +use oxc_span::Atom; + +/// 6.2.5 The Reference Record Specification Type +/// https://tc39.es/ecma262/#sec-reference-record-specification-type +#[derive(Debug)] +pub struct Reference { + /// [[Base]] + pub base: Base, + + /// [[ReferencedName]] + pub referenced_name: ReferencedName, + + /// [[Strict]] + pub strict: bool, + + /// [[ThisValue]] + pub this_value: Option, +} + +impl Reference { + /// 6.2.5.1 IsPropertyReference ( V ) + /// https://tc39.es/ecma262/#sec-ispropertyreference + pub fn is_property_reference(self) -> bool { + match self.base { + // 1. if V.[[Base]] is unresolvable, return false. + Base::Unresolvable => false, + + // 2. If V.[[Base]] is an Environment Record, return false; otherwise return true. + Base::Environment(_) => false, + _ => true, + } + } + + /// 6.2.5.2 IsUnresolvableReference ( V ) + /// https://tc39.es/ecma262/#sec-isunresolvablereference + pub fn is_unresolvable_reference(self) -> bool { + // 1. If V.[[Base]] is unresolvable, return true; otherwise return false. + return matches!(self.base, Base::Unresolvable); + } + + /// 6.2.5.3 IsSuperReference ( V ) + /// https://tc39.es/ecma262/#sec-issuperreference + pub fn is_super_reference(self) -> bool { + // 1. If V.[[ThisValue]] is not empty, return true; otherwise return false. + return !matches!(self.this_value, None); + } +} + +#[derive(Debug)] +pub enum Base { + Value(Value), + Environment(Environment), + Unresolvable, +} + +#[derive(Debug)] +pub enum ReferencedName { + String(Atom), + Symbol(Symbol), + // TODO: implement private names + PrivateName, +} diff --git a/nova_vm/src/value.rs b/nova_vm/src/value.rs deleted file mode 100644 index 9d700094..00000000 --- a/nova_vm/src/value.rs +++ /dev/null @@ -1,370 +0,0 @@ -use crate::{heap::Heap, stack_string::StackString, Type, VM}; -use std::{fmt::Debug, mem::size_of}; - -// TODO(@aapoalas): Use transparent struct (u32)'s to ensure proper indexing. -pub type ArrayIndex = u32; -pub type BigIntIndex = u32; -pub type DateIndex = u32; -pub type ErrorIndex = u32; -pub type FunctionIndex = u32; -pub type NumberIndex = u32; -pub type ObjectIndex = u32; -pub type RegExpIndex = u32; -pub type StringIndex = u32; -pub type SymbolIndex = u32; - -#[derive(Clone)] -#[repr(u8)] -pub enum Value { - Array(ArrayIndex), - BigIntObject(u32), // TODO: Implement primitive value objects :( - BooleanObject(u32), // TODO: Implement primitive value objects :( - Boolean(bool), - Date(DateIndex), - EmptyString, - Error(ErrorIndex), - Function(FunctionIndex), - HeapBigInt(BigIntIndex), - HeapNumber(NumberIndex), - HeapString(StringIndex), - Infinity, - NaN, - NegativeInfinity, - NegativeZero, - Null, - NumberObject(u32), // TODO: Implement primitive value objects :( - Object(ObjectIndex), - RegExp(RegExpIndex), - StackString(StackString), - // TOO: Extend these to i56. - SmallBigInt(i32), - SmallBigIntU(u32), - // TODO: Extend these to i48 or even i56. - // i56 would provide safe integer operations - // superior to f64 but is no longer spec-compliant. - Smi(i32), - SmiU(u32), - StringObject(u32), // TODO: Implement primitive value objects :( - Symbol(SymbolIndex), - SymbolObject(u32), // TODO: Implement primitive value objects :( - Undefined, -} - -/// We want to guarantee that all handles to JS values are register sized. This assert must never be removed or broken. -const _VALUE_SIZE_IS_WORD: () = assert!(size_of::() == size_of::()); -// We may also want to keep Option register sized to allow returning it in some cases. -// This may not be possible in the long run and it may not be necessary as we might want to use Undefined instead. -const _OPTIONAL_VALUE_SIZE_IS_WORD: () = assert!(size_of::>() == size_of::()); - -impl Value { - pub fn new_string(heap: &mut Heap, message: &str) -> Value { - if let Some(ascii_string) = StackString::try_from_str(message) { - Value::StackString(ascii_string) - } else { - Value::HeapString(heap.alloc_string(message)) - } - } - - pub fn create_exception(heap: &mut Heap, message: &str) -> Value { - Value::HeapString(heap.alloc_string(message)) - } - - pub fn get_type(&self) -> Type { - match self { - Value::Boolean(_) => Type::Boolean, - Value::EmptyString | Value::HeapString(_) | Value::StackString(_) => Type::String, - Value::Function(_) => Type::Function, - Value::HeapNumber(_) - | Value::Infinity - | Value::NaN - | Value::NegativeInfinity - | Value::NegativeZero - | Value::Smi(_) - | Value::SmiU(_) => Type::Number, - Value::Null => Type::Null, - Value::Object(_) - | Value::Array(_) - | Value::BigIntObject(_) - | Value::BooleanObject(_) - | Value::Date(_) - | Value::Error(_) - | Value::NumberObject(_) - | Value::RegExp(_) - | Value::StringObject(_) - | Value::SymbolObject(_) => Type::Object, - Value::HeapBigInt(_) | Value::SmallBigInt(_) | Value::SmallBigIntU(_) => Type::BigInt, - Value::Symbol(_) => Type::Symbol, - Value::Undefined => Type::Undefined, - } - } - - /// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-islooselyequal - pub fn is_loosely_equal(&self, vm: &mut VM, other: &Value) -> JsResult { - if self.get_type() == other.get_type() { - return self.is_strictly_equal(vm, other); - } - - Ok(match (self, other) { - (Value::Null | Value::Undefined, Value::Null | Value::Undefined) => true, - ( - Value::SmallBigInt(this) | Value::Smi(this), - Value::SmallBigInt(that) | Value::Smi(that), - ) => this == that, - ( - Value::SmallBigIntU(this) | Value::SmiU(this), - Value::SmallBigIntU(that) | Value::SmiU(that), - ) => this == that, - ( - Value::SmallBigInt(this) | Value::Smi(this), - Value::SmallBigIntU(that) | Value::SmiU(that), - ) => *this as u32 == *that, - ( - Value::SmallBigIntU(this) | Value::SmiU(this), - Value::SmallBigInt(that) | Value::Smi(that), - ) => *this == *that as u32, - (&Value::HeapBigInt(x), &Value::HeapNumber(y)) => { - let big_int = &vm.heap.bigints[x as usize]; - let number = &vm.heap.numbers[y as usize]; - big_int.as_ref().unwrap().try_into_f64() == Some(number.as_ref().unwrap().value()) - } - (&Value::HeapNumber(x), &Value::HeapBigInt(y)) => { - let big_int = &vm.heap.bigints[y as usize]; - let number = &vm.heap.numbers[x as usize]; - big_int.as_ref().unwrap().try_into_f64() == Some(number.as_ref().unwrap().value()) - } - (Value::HeapNumber(_), Value::HeapString(_)) => todo!("use ToNumber() intrinsics"), - (Value::HeapString(_), Value::HeapNumber(_)) => todo!("use ToNumber() intrinsics"), - (Value::HeapBigInt(_), Value::HeapString(_)) => { - todo!("use StringToBigInt() intrinsics") - } - (Value::HeapString(_), Value::HeapBigInt(_)) => other.is_loosely_equal(vm, self)?, - (Value::Boolean(_), _) => { - let self_as_f64 = self.try_into_f64(vm)?; - Value::from_f64(&mut vm.heap, self_as_f64).is_loosely_equal(vm, other)? - } - (_, Value::Boolean(_)) => { - let other_as_f64 = other.try_into_f64(vm)?; - Value::from_f64(&mut vm.heap, other_as_f64).is_loosely_equal(vm, self)? - } - ( - Value::HeapString(_) - | Value::HeapNumber(_) - | Value::HeapBigInt(_) - | Value::Symbol(_), - _, - ) => other.is_loosely_equal(vm, &self.to_primitive()?)?, - ( - Value::Object(_), - Value::HeapString(_) - | Value::HeapNumber(_) - | Value::HeapBigInt(_) - | Value::Symbol(_), - ) => self.to_primitive()?.is_loosely_equal(vm, other)?, - _ => false, - }) - } - - /// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-isstrictlyequal - pub fn is_strictly_equal(&self, vm: &VM, other: &Value) -> JsResult { - if self.get_type() != other.get_type() { - return Ok(false); - } - - Ok(match (self, other) { - (Value::SmiU(n1), Value::NegativeZero) | (Value::NegativeZero, Value::SmiU(n1)) => { - *n1 == 0 - } - (Value::Smi(n1) | Value::SmallBigInt(n1), Value::Smi(n2) | Value::SmallBigInt(n2)) => { - n1 == n2 - } - ( - Value::SmiU(n1) | Value::SmallBigIntU(n1), - Value::SmiU(n2) | Value::SmallBigIntU(n2), - ) => n1 == n2, - - (Value::HeapNumber(n1), Value::HeapNumber(n2)) => { - n1 == n2 - || vm.heap.numbers[*n1 as usize].as_ref().unwrap().value() - == vm.heap.numbers[*n2 as usize].as_ref().unwrap().value() - } - - // https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluenonnumber - (Value::Null | Value::Undefined, _) => true, - (Value::HeapBigInt(n1), Value::HeapBigInt(n2)) => n1 == n2, - (Value::HeapString(s1), Value::HeapString(s2)) => { - s1 == s2 - || vm.heap.strings[*s1 as usize].as_ref().unwrap().data - == vm.heap.strings[*s2 as usize].as_ref().unwrap().data - } - (Value::Boolean(b1), Value::Boolean(b2)) => b1 == b2, - // TODO: implement x is y procedures - (Value::Object(obj1), Value::Object(obj2)) => obj1 == obj2, - _ => false, - }) - } - - pub fn to_primitive(&self) -> JsResult { - Ok(Value::Null) - } - - /// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-toboolean - pub fn to_boolean(&self) -> Value { - match self { - &Value::Boolean(b) => Value::Boolean(b), - &Value::SmiU(n) => Value::Boolean(n == 0), - Value::Null | Value::EmptyString | Value::NaN | Value::NegativeZero => { - Value::Boolean(false) - } - _ => Value::Boolean(true), - } - } - - /// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-tonumber - pub fn to_number(&self, _vm: &mut VM) -> JsResult { - Ok(match self { - Value::HeapNumber(_) - | Value::Smi(_) - | Value::SmiU(_) - | Value::Infinity - | Value::NegativeInfinity - | Value::NegativeZero => self.clone(), - Value::Function(_) - | Value::Symbol(_) - | Value::HeapBigInt(_) - | Value::SmallBigInt(_) - | Value::SmallBigIntU(_) => todo!("type error"), - Value::Undefined | Value::NaN => Value::NaN, - Value::Null | Value::Boolean(false) | Value::EmptyString => Value::SmiU(0), - Value::Boolean(true) => Value::SmiU(1), - Value::StackString(_) | Value::HeapString(_) => todo!("parse number from string"), - Value::Object(_) | Value::Error(_) => todo!("call valueOf"), - _ => todo!("implement primitive value objects :("), - }) - } - - pub fn from_f64(heap: &mut Heap, value: f64) -> Value { - let is_int = value.fract() == 0.0; - if value.is_nan() { - Value::NaN - } else if value.is_infinite() { - if value.is_sign_positive() { - Value::Infinity - } else { - Value::NegativeInfinity - } - } else if !is_int || value > u32::MAX as f64 || value < i32::MIN as f64 { - Value::HeapNumber(heap.alloc_number(value)) - } else if value.is_sign_positive() { - Value::SmiU(value as u32) - } else { - Value::Smi(value as i32) - } - } - - pub fn try_into_f64(&self, vm: &mut VM) -> JsResult { - match self { - &Value::HeapNumber(n) => Ok(vm.heap.numbers[n as usize].as_ref().unwrap().value()), - &Value::Smi(n) => Ok(n as f64), - &Value::SmiU(n) => Ok(n as f64), - Value::Infinity => Ok(f64::INFINITY), - Value::NegativeInfinity => Ok(f64::NEG_INFINITY), - Value::NegativeZero => Ok(0.), - Value::Undefined | Value::NaN => Ok(f64::NAN), - Value::Function(_) - | Value::Symbol(_) - | Value::HeapBigInt(_) - | Value::SmallBigInt(_) - | Value::SmallBigIntU(_) => todo!("type error"), - Value::Null | Value::Boolean(false) | Value::EmptyString => Ok(0.), - Value::Boolean(true) => Ok(1.), - Value::StackString(_) | Value::HeapString(_) => todo!("parse number from string"), - Value::Object(_) => todo!("call valueOf"), - _ => todo!("sigh"), - } - } - - pub fn into_bool(&self) -> bool { - match self { - &Value::Boolean(b) => b, - &Value::SmiU(n) => n == 0, - Value::Null | Value::EmptyString | Value::NaN | Value::NegativeZero => false, - _ => true, - } - } - - pub fn from_u32(value: u32) -> Value { - Value::SmiU(value) - } - - pub fn from_i32(value: i32) -> Value { - if value >= 0 { - Value::from_u32(value as u32) - } else { - Value::Smi(value) - } - } - - pub fn is_undefined(&self) -> bool { - match self { - Value::Undefined => true, - _ => false, - } - } - - pub fn is_null(&self) -> bool { - match self { - Value::Null => true, - _ => false, - } - } - - pub fn is_number(&self) -> bool { - match self { - Value::Smi(_) => true, - Value::SmiU(_) => true, - Value::NaN => true, - Value::Infinity => true, - Value::NegativeInfinity => true, - Value::NegativeZero => true, - Value::HeapNumber(_) => true, - _ => false, - } - } -} - -pub type JsResult = std::result::Result; - -impl Debug for Value { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Value::Array(arg0) => f.debug_tuple("Array").field(arg0).finish(), - Value::BigIntObject(arg0) => f.debug_tuple("BigIntObject").field(arg0).finish(), - Value::Boolean(arg0) => f.debug_tuple("Boolean").field(arg0).finish(), - Value::BooleanObject(arg0) => f.debug_tuple("BooleanObject").field(arg0).finish(), - Value::EmptyString => write!(f, "EmptyString"), - Value::Date(arg0) => f.debug_tuple("Date").field(arg0).finish(), - Value::Error(arg0) => f.debug_tuple("Error").field(arg0).finish(), - Value::Function(arg0) => f.debug_tuple("Function").field(arg0).finish(), - Value::HeapBigInt(arg0) => f.debug_tuple("BigInt").field(arg0).finish(), - Value::HeapNumber(arg0) => f.debug_tuple("Number").field(arg0).finish(), - Value::HeapString(arg0) => f.debug_tuple("String").field(arg0).finish(), - Value::Infinity => write!(f, "Infinity"), - Value::NaN => write!(f, "NaN"), - Value::NegativeInfinity => write!(f, "-Infinity"), - Value::NegativeZero => write!(f, "-0"), - Value::Null => write!(f, "Null"), - Value::NumberObject(arg0) => f.debug_tuple("NumberObject").field(arg0).finish(), - Value::Object(arg0) => f.debug_tuple("Object").field(arg0).finish(), - Value::RegExp(arg0) => f.debug_tuple("RegExp").field(arg0).finish(), - Value::StackString(arg0) => f.debug_tuple("SmallAsciiString").field(arg0).finish(), - Value::SmallBigInt(arg0) => f.debug_tuple("SmallBigInt").field(arg0).finish(), - Value::SmallBigIntU(arg0) => f.debug_tuple("SmallBigIntU").field(arg0).finish(), - Value::Smi(arg0) => f.debug_tuple("Smi").field(arg0).finish(), - Value::SmiU(arg0) => f.debug_tuple("SmiU").field(arg0).finish(), - Value::StringObject(arg0) => f.debug_tuple("StringObject").field(arg0).finish(), - Value::Symbol(arg0) => f.debug_tuple("Symbol").field(arg0).finish(), - Value::SymbolObject(arg0) => f.debug_tuple("SymbolObject").field(arg0).finish(), - Value::Undefined => write!(f, "Undefined"), - } - } -} diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 5d56faf9..00000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "nightly"