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..b20cca7a --- /dev/null +++ b/nova_vm/src/builtins/array.rs @@ -0,0 +1,30 @@ +use super::{create_builtin_function, ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs}; +use crate::{ + execution::{Agent, JsResult}, + types::{Object, Value}, +}; + +pub struct ArrayConstructor; + +impl Builtin for ArrayConstructor { + fn create<'a>(agent: &'a mut Agent<'a, 'a>) -> JsResult { + let realm = agent.current_realm_id(); + let object = create_builtin_function( + agent, + 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..5ee4f2f5 --- /dev/null +++ b/nova_vm/src/builtins/builtin_function.rs @@ -0,0 +1,137 @@ +use crate::{ + execution::{Agent, Intrinsics, JsResult, Realm, RealmIdentifier}, + heap::CreateHeapData, + types::{Function, Object, PropertyDescriptor, Value}, + Heap, +}; + +#[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>(agent: &'a mut Agent<'a, 'a>) -> JsResult; +} + +#[derive(Debug, Default)] +pub struct BuiltinFunctionArgs<'a, 'ctx, 'host> { + pub length: u32, + pub name: &'a str, + pub realm: Option>, + 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: RealmIdentifier<'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>( + agent: &mut Agent<'b, 'b>, + behaviour: Behaviour, + args: BuiltinFunctionArgs<'a, 'b, 'b>, +) -> Function { + // 1. If realm is not present, set realm to the current Realm Record. + let realm_id = args.realm.unwrap_or(agent.current_realm_id()); + + // 2. If prototype is not present, set prototype to realm.[[Intrinsics]].[[%Function.prototype%]]. + let prototype = args.prototype.unwrap_or_else(|| { + agent + .get_realm(realm_id) + .intrinsics + .function_prototype() + .into() + }); + let heap = &mut agent.heap; + + // 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. + // 6. Set func.[[Prototype]] to prototype. + // 7. Set func.[[Extensible]] to true. + // 8. Set func.[[Realm]] to realm. + // NOTE: Heap data is implicitly attached to the Realm so I don't think + // this matters. + let object = heap.create_object_with_prototype(prototype); + + // 9. Set func.[[InitialName]] to null. + // TODO: This is non-standard. + let initial_name = heap.create(args.name).into_value(); + // 10. Perform SetFunctionLength(func, length). + let length = args.length as u8; + // TODO: Actually set behaviour somewhere + let func = heap.create_function(initial_name, length, false); + + // 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. + Function(func) +} + +pub fn define_builtin_function<'a, 'b>( + agent: &mut Agent<'b, 'b>, + object: Object, + name: &'a str, + behaviour: RegularFn, + length: u32, + realm: RealmIdentifier<'b, 'b>, +) -> JsResult<()> { + let function = create_builtin_function( + agent, + 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..869d231a --- /dev/null +++ b/nova_vm/src/builtins/ecmascript_function.rs @@ -0,0 +1,91 @@ +use std::{cell::RefCell, rc::Rc}; + +use oxc_ast::ast::{FormalParameters, FunctionBody}; + +use crate::{ + execution::{ + Agent, EnvironmentIndex, JsResult, PrivateEnvironmentIndex, RealmIdentifier, 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: EnvironmentIndex, + + /// [[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: RealmIdentifier<'ctx, 'host>, + + /// [[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..b79d083a --- /dev/null +++ b/nova_vm/src/builtins/number.rs @@ -0,0 +1,225 @@ +use super::{ + builtin_function::{define_builtin_function, define_builtin_property}, + create_builtin_function, todo_builtin, ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs, +}; +use crate::{ + execution::{Agent, JsResult}, + heap::CreateHeapData, + types::{Number, Object, PropertyDescriptor, Value}, + SmallInteger, +}; + +pub struct NumberConstructor; + +impl Builtin for NumberConstructor { + fn create<'a>(agent: &'a mut Agent<'a, 'a>) -> JsResult { + let realm_id = agent.current_realm_id(); + + let function_prototype = agent.current_realm().intrinsics.function_prototype(); + let number_prototype = agent.current_realm().intrinsics.number_prototype(); + let object: Object = create_builtin_function( + agent, + Behaviour::Constructor(Self::behaviour), + BuiltinFunctionArgs { + length: 1, + name: "Number", + realm: Some(realm_id), + prototype: Some(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(agent.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(agent.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(agent.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(agent, object, "isFinite", todo_builtin, 1, realm_id)?; + define_builtin_function(agent, object, "isNaN", todo_builtin, 1, realm_id)?; + define_builtin_function(agent, object, "isSafeInteger", todo_builtin, 1, realm_id)?; + define_builtin_function(agent, object, "parseFloat", todo_builtin, 1, realm_id)?; + define_builtin_function(agent, object, "parseInt", todo_builtin, 2, realm_id)?; + + // 21.1.2.15 Number.prototype + // https://tc39.es/ecma262/#sec-number.prototype + define_builtin_property( + object, + "prototype", + PropertyDescriptor { + value: Some(number_prototype.into_value()), + 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( + 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..2f88b282 --- /dev/null +++ b/nova_vm/src/builtins/ordinary.rs @@ -0,0 +1,800 @@ +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]]. + // TODO: This is wrong. + Some(Object::try_from(object.prototype(agent).unwrap()).unwrap()) +} + +/// 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) => { + 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]]. + // TODO: This is wrong + parent_prototype_outer = + Some(Object::try_from(parent_prototype.prototype(agent).unwrap()).unwrap()); + } + + // 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). + let define_own_property = receiver.internal_methods(agent).define_own_property; + return 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/dyn_ObjectHeapData b/nova_vm/src/dyn_ObjectHeapData deleted file mode 100644 index ab8d3603..00000000 --- a/nova_vm/src/dyn_ObjectHeapData +++ /dev/null @@ -1,40 +0,0 @@ -pub trait ObjectHeapData: Trace + Debug { - fn get_prototype_of(&self, heap: &mut Heap) -> JsResult>; - fn set_prototype_of(&self, heap: &mut Heap, prototype: Option) -> JsResult; - fn is_extensible(&self, heap: &mut Heap) -> JsResult; - fn prevent_extensions(&self, heap: &mut Heap) -> JsResult; - fn get_own_property( - &self, - heap: &mut Heap, - key: PropertyKey, - ) -> JsResult>; - fn define_own_property( - &self, - heap: &mut Heap, - key: PropertyKey, - descriptor: PropertyDescriptor, - ) -> JsResult; - fn has_property(&self, heap: &mut Heap, key: PropertyKey) -> JsResult; - fn get(&self, heap: &mut Heap, key: PropertyKey, receiver: &Value) -> JsResult; - fn set( - &self, - heap: &mut Heap, - key: PropertyKey, - value: Value, - receiver: &Value, - ) -> JsResult; - fn delete(&self, heap: &mut Heap, key: PropertyKey) -> JsResult; - fn own_property_keys(&self, heap: &mut Heap) -> JsResult>; - - // Tracing helpers - fn get_strong_references(&self) -> Vec; - fn get_weak_references(&self) -> Vec; -} - -pub trait FunctionHeapData: ObjectHeapData { - fn call(&self, this: &Value, args: &[Value]) -> JsResult; -} - -pub trait ConstructorHeapData: FunctionHeapData { - fn construct(&self, args: &[Value], target: ObjectIndex) -> JsResult; -} \ No newline at end of file diff --git a/nova_vm/src/execution.rs b/nova_vm/src/execution.rs new file mode 100644 index 00000000..e9087dd4 --- /dev/null +++ b/nova_vm/src/execution.rs @@ -0,0 +1,14 @@ +pub mod agent; +mod default_host_hooks; +mod environments; +mod execution_context; +mod realm; + +pub use agent::{Agent, JsResult}; +pub use environments::{ + DeclarativeEnvironment, DeclarativeEnvironmentIndex, EnvironmentIndex, Environments, + FunctionEnvironment, FunctionEnvironmentIndex, GlobalEnvironment, GlobalEnvironmentIndex, + ObjectEnvironment, ObjectEnvironmentIndex, PrivateEnvironment, PrivateEnvironmentIndex, +}; +pub use execution_context::{ECMAScriptCode, ExecutionContext, ScriptOrModule}; +pub use realm::{Intrinsics, Realm, RealmIdentifier}; diff --git a/nova_vm/src/execution/agent.rs b/nova_vm/src/execution/agent.rs new file mode 100644 index 00000000..27d839f0 --- /dev/null +++ b/nova_vm/src/execution/agent.rs @@ -0,0 +1,81 @@ +use super::{ExecutionContext, Realm, RealmIdentifier}; +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 heap: Heap, + 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 realms: Vec>, + pub execution_context_stack: Vec>, +} + +impl<'ctx, 'host> Agent<'ctx, 'host> { + pub fn current_realm_id(&self) -> RealmIdentifier<'ctx, 'host> { + self.execution_context_stack.last().unwrap().realm + } + + pub fn current_realm(&self) -> &Realm<'ctx, 'host> { + self.get_realm(self.current_realm_id()) + } + + pub fn current_realm_mut(&mut self) -> &mut Realm<'ctx, 'host> { + self.get_realm_mut(self.current_realm_id()) + } + + pub fn get_realm(&self, id: RealmIdentifier<'ctx, 'host>) -> &Realm<'ctx, 'host> { + self.realms + .get(id.into_index()) + .expect("RealmIdentifier did not match a Realm") + } + + pub fn get_realm_mut(&mut self, id: RealmIdentifier<'ctx, 'host>) -> &mut Realm<'ctx, 'host> { + self.realms + .get_mut(id.into_index()) + .expect("RealmIdentifier did not match a Realm") + } + + /// 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..1679d217 --- /dev/null +++ b/nova_vm/src/execution/default_host_hooks.rs @@ -0,0 +1,15 @@ +use super::{JsResult, Realm}; +use crate::types::Function; + +/// 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(_: Function) -> 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..06f769e7 --- /dev/null +++ b/nova_vm/src/execution/environments.rs @@ -0,0 +1,91 @@ +//! 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, DeclarativeEnvironmentIndex}; +pub use function_environment::{FunctionEnvironment, FunctionEnvironmentIndex}; +pub use global_environment::{GlobalEnvironment, GlobalEnvironmentIndex}; +pub use object_environment::{ObjectEnvironment, ObjectEnvironmentIndex}; +pub use private_environment::{PrivateEnvironment, PrivateEnvironmentIndex}; + +/// 9.1.1 The Environment Record Type Hierarchy +/// https://tc39.es/ecma262/#sec-the-environment-record-type-hierarchy +#[derive(Debug, Clone)] +#[repr(u8)] +pub enum EnvironmentIndex { + // Leave 0 for None option + DeclarativeEnvironment(DeclarativeEnvironmentIndex) = 1, + FunctionEnvironment(FunctionEnvironmentIndex), + GlobalEnvironment(GlobalEnvironmentIndex), + ObjectEnvironment(ObjectEnvironmentIndex), +} + +#[derive(Debug)] +pub struct Environments { + declarative: Vec>, + function: Vec>, + global: Vec>, + object: Vec>, +} + +impl Default for Environments { + fn default() -> Self { + Self { + declarative: Vec::with_capacity(256), + function: Vec::with_capacity(1024), + global: Vec::with_capacity(1), + object: Vec::with_capacity(1024), + } + } +} + +impl Environments { + pub fn get_declarative_environment<'a>( + &'a self, + index: DeclarativeEnvironmentIndex, + ) -> &'a DeclarativeEnvironment { + self.declarative + .get(index.into_index()) + .expect("DeclarativeEnvironmentIndex did not match to any vector index") + .as_ref() + .expect("DeclarativeEnvironmentIndex pointed to a None") + } + + pub fn get_function_environment<'a>( + &'a self, + index: FunctionEnvironmentIndex, + ) -> &'a FunctionEnvironment { + self.function + .get(index.into_index()) + .expect("FunctionEnvironmentIndex did not match to any vector index") + .as_ref() + .expect("FunctionEnvironmentIndex pointed to a None") + } + + pub fn get_global_environment<'a>( + &'a self, + index: GlobalEnvironmentIndex, + ) -> &'a GlobalEnvironment { + self.global + .get(index.into_index()) + .expect("GlobalEnvironmentIndex did not match to any vector index") + .as_ref() + .expect("GlobalEnvironmentIndex pointed to a None") + } + + pub fn get_object_environment<'a>( + &'a self, + index: ObjectEnvironmentIndex, + ) -> &'a ObjectEnvironment { + self.object + .get(index.into_index()) + .expect("ObjectEnvironmentIndex did not match to any vector index") + .as_ref() + .expect("ObjectEnvironmentIndex pointed to a None") + } +} 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..f5ab8c9a --- /dev/null +++ b/nova_vm/src/execution/environments/declarative_environment.rs @@ -0,0 +1,48 @@ +use super::EnvironmentIndex; +use crate::types::{String, Value}; +use std::{collections::HashMap, marker::PhantomData, num::NonZeroU32}; + +#[derive(Debug, Clone, Copy)] +pub struct DeclarativeEnvironmentIndex(NonZeroU32, PhantomData); + +impl DeclarativeEnvironmentIndex { + pub const fn from_u32_index(value: u32) -> Self { + assert!(value != u32::MAX); + // SAFETY: Number is not max value and will not overflow to zero. + // This check is done manually to allow const context. + Self(unsafe { NonZeroU32::new_unchecked(value + 1) }, PhantomData) + } + + pub const fn into_index(self) -> usize { + self.0.get() as usize - 1 + } +} + +/// 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, +} + +// TODO: Consider splitting binding this into ValueBinding +// and BindingOptions. The options can fit a u8 and are fairly +// often not needed by algorithms. +#[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: String) -> 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..378090c7 --- /dev/null +++ b/nova_vm/src/execution/environments/function_environment.rs @@ -0,0 +1,63 @@ +use std::{collections::HashMap, marker::PhantomData, num::NonZeroU32}; + +use super::{declarative_environment::Binding, EnvironmentIndex}; +use crate::{ + heap::indexes::FunctionIndex, + types::{Object, String, Value}, +}; + +#[derive(Debug, Clone, Copy)] +pub struct FunctionEnvironmentIndex(NonZeroU32, PhantomData); + +impl FunctionEnvironmentIndex { + pub const fn from_u32_index(value: u32) -> Self { + assert!(value != u32::MAX); + // SAFETY: Number is not max value and will not overflow to zero. + // This check is done manually to allow const context. + Self(unsafe { NonZeroU32::new_unchecked(value + 1) }, PhantomData) + } + + pub const fn into_index(self) -> usize { + self.0.get() as usize - 1 + } +} + +#[derive(Debug)] +pub enum ThisBindingStatus { + /// Function is an ArrowFunction and does not have a local `this` value. + Lexical, + /// Function is a normal function and does not have a bound `this` value. + Initialized, + /// Function is a normal function and has a bound `this` value. + Uninitialized, +} + +/// 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: FunctionIndex, + + /// [[NewTarget]] + /// + /// If this FunctionEnvironment was created with a [[Construct]] internal method, + /// this is the value of the _newTarget_ parameter. Otherwise, its value is **undefined** + /// (implementation wise here None). + new_target: Option, + + /// [[OuterEnv]] + outer_env: Option, + + /// Per https://tc39.es/ecma262/#sec-the-environment-record-type-hierarchy: + /// > A _Function Environment Record_ is a _Declarative Environment Record_ [...] + /// + /// The Declaration Environment Record is inlined here. + declarative_environment: HashMap, +} 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..db3e359f --- /dev/null +++ b/nova_vm/src/execution/environments/global_environment.rs @@ -0,0 +1,48 @@ +use std::collections::HashMap; +use std::marker::PhantomData; +use std::num::NonZeroU32; + +use super::declarative_environment::Binding; +use crate::heap::element_array::ElementsVector; +use crate::types::{Object, String}; + +#[derive(Debug, Clone, Copy)] +pub struct GlobalEnvironmentIndex(NonZeroU32, PhantomData); + +impl GlobalEnvironmentIndex { + pub const fn from_u32_index(value: u32) -> Self { + assert!(value != u32::MAX); + // SAFETY: Number is not max value and will not overflow to zero. + // This check is done manually to allow const context. + Self(unsafe { NonZeroU32::new_unchecked(value + 1) }, PhantomData) + } + + pub const fn into_index(self) -> usize { + self.0.get() as usize - 1 + } +} + +/// 9.1.1.4 Global Environment Records +/// https://tc39.es/ecma262/#sec-global-environment-records +#[derive(Debug)] +pub struct GlobalEnvironment { + /// [[ObjectRecord]] + /// The Object Environment Record is inlined here. + object_record: Object, + + /// [[GlobalThisValue]] + global_this_value: Object, + + /// [[DeclarativeRecord]] + /// The Declaration Environment Record is inlined here. + declarative_record: HashMap, + + /// [[VarNames]] + var_names: ElementsVector, + + /// [[OuterEnv]] + /// + /// Per https://tc39.es/ecma262/#sec-the-environment-record-type-hierarchy: + /// > A _Global Environment Record_ is used for Script global declarations. It does not have an outer environment; its \[\[OuterEnv\]\] is null. + outer_env: (), +} 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..be64eabd --- /dev/null +++ b/nova_vm/src/execution/environments/object_environment.rs @@ -0,0 +1,34 @@ +use std::{marker::PhantomData, num::NonZeroU32}; + +use super::EnvironmentIndex; +use crate::types::Object; + +#[derive(Debug, Clone, Copy)] +pub struct ObjectEnvironmentIndex(NonZeroU32, PhantomData); + +impl ObjectEnvironmentIndex { + pub const fn from_u32_index(value: u32) -> Self { + assert!(value != u32::MAX); + // SAFETY: Number is not max value and will not overflow to zero. + // This check is done manually to allow const context. + Self(unsafe { NonZeroU32::new_unchecked(value + 1) }, PhantomData) + } + + pub const fn into_index(self) -> usize { + self.0.get() as usize - 1 + } +} + +/// 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..0cf13586 --- /dev/null +++ b/nova_vm/src/execution/environments/private_environment.rs @@ -0,0 +1,37 @@ +use crate::types::{Function, String, Value}; +use std::{collections::HashMap, marker::PhantomData, num::NonZeroU32}; + +#[derive(Debug, Clone, Copy)] +pub struct PrivateEnvironmentIndex(NonZeroU32, PhantomData); + +impl PrivateEnvironmentIndex { + pub const fn from_u32_index(value: u32) -> Self { + assert!(value != u32::MAX); + // SAFETY: Number is not max value and will not overflow to zero. + // This check is done manually to allow const context. + Self(unsafe { NonZeroU32::new_unchecked(value + 1) }, PhantomData) + } + + pub const fn into_index(self) -> usize { + self.0.get() as usize - 1 + } +} + +#[derive(Debug)] +pub enum PrivateElement { + Field(Option), + Method(Option), + /// Accssor(get, set) + Accessor(Option, Option), +} + +/// 9.2 PrivateEnvironment Records +/// https://tc39.es/ecma262/#sec-privateenvironment-records +#[derive(Debug)] +pub struct PrivateEnvironment { + /// [[OuterPrivateEnvironment]] + outer_private_environment: Option, + + /// [[Names]] + names: HashMap, // 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..8f7a74de --- /dev/null +++ b/nova_vm/src/execution/execution_context.rs @@ -0,0 +1,46 @@ +use super::{EnvironmentIndex, PrivateEnvironmentIndex, RealmIdentifier}; +use crate::{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: EnvironmentIndex, + + /// VariableEnvironment + pub variable_environment: EnvironmentIndex, + + /// PrivateEnvironment + pub private_environment: Option, +} + +/// 9.4 Execution Contexts +/// https://tc39.es/ecma262/#sec-execution-contexts +#[derive(Debug)] +pub struct ExecutionContext<'ctx, 'host> { + /// Function + /// + /// > If this execution context is evaluating the code of a function object, then the value + /// > of this component is that function object. If the context is evaluating the code of + /// > a *Script* or *Module*, the value is **null** (here represented by None). + pub function: Option, + + /// Realm + pub realm: RealmIdentifier<'ctx, 'host>, + + /// 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..08a60048 --- /dev/null +++ b/nova_vm/src/execution/realm.rs @@ -0,0 +1,41 @@ +mod intrinsics; + +use super::{environments::global_environment::GlobalEnvironmentIndex, Agent}; +use crate::types::Object; +pub use intrinsics::Intrinsics; +use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc}; + +#[derive(Debug, Clone, Copy)] +pub struct RealmIdentifier<'ctx, 'host>(u32, PhantomData>); + +impl<'ctx, 'host> RealmIdentifier<'ctx, 'host> { + pub const fn from_u32_index(value: u32) -> Self { + Self(value, PhantomData) + } + + pub const fn into_index(self) -> usize { + self.0 as usize + } +} + +/// 9.3 Realms +/// https://tc39.es/ecma262/#sec-code-realms +#[derive(Debug)] +pub struct Realm<'ctx, 'host> { + pub agent: Rc>>, + + // NOTE: We will need an rng here at some point. + + // [[Intrinsics]] + pub intrinsics: Intrinsics, + + /// [[GlobalObject]] + pub global_object: Object, + + /// [[GlobalEnv]] + pub global_env: GlobalEnvironmentIndex, + + /// [[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..7b91d12a --- /dev/null +++ b/nova_vm/src/execution/realm/intrinsics.rs @@ -0,0 +1,375 @@ +use crate::{ + heap::{ + indexes::{FunctionIndex, ObjectIndex}, + BuiltinObjectIndexes, + }, + types::{Function, Object}, +}; + +#[derive(Debug, Clone, Copy)] +pub struct Intrinsics { + /// %Array% + array: FunctionIndex, + /// %Array.prototype% + array_prototype: ObjectIndex, + /// %BigInt% + big_int: FunctionIndex, + /// %BigInt.prototype% + big_int_prototype: ObjectIndex, + /// %Boolean% + boolean: FunctionIndex, + /// %Boolean.prototype% + boolean_prototype: ObjectIndex, + /// %Error% + error: FunctionIndex, + /// %Error.prototype% + error_prototype: ObjectIndex, + /// %eval% + eval: FunctionIndex, + /// %EvalError% + eval_error: FunctionIndex, + /// %EvalError.prototype% + eval_error_prototype: ObjectIndex, + /// %Function% + function: FunctionIndex, + /// %Function.prototype% + /// + /// NOTE: This is not spec-compliant. Function prototype should + /// be a function that always returns undefined no matter how + /// it is called. That's stupid so we do not have that. + function_prototype: ObjectIndex, + /// %isFinite% + is_finite: FunctionIndex, + /// %isNaN% + is_nan: FunctionIndex, + /// %Math% + math: ObjectIndex, + /// %Number% + number: FunctionIndex, + /// %Number.prototype% + number_prototype: ObjectIndex, + /// %Object% + object: FunctionIndex, + /// %Object.prototype% + object_prototype: ObjectIndex, + /// %Object.prototype.toString% + object_prototype_to_string: FunctionIndex, + /// %RangeError% + range_error: FunctionIndex, + /// %RangeError.prototype% + range_error_prototype: ObjectIndex, + /// %ReferenceError% + reference_error: FunctionIndex, + /// %ReferenceError.prototype% + reference_error_prototype: ObjectIndex, + /// %Reflect% + reflect: FunctionIndex, + /// %String% + string: FunctionIndex, + /// %String.prototype% + string_prototype: ObjectIndex, + /// %Symbol% + symbol: FunctionIndex, + /// %Symbol.prototype% + symbol_prototype: ObjectIndex, + /// %SyntaxError% + syntax_error: FunctionIndex, + /// %SyntaxError.prototype% + syntax_error_prototype: ObjectIndex, + /// %ThrowTypeError% + throw_type_error: FunctionIndex, + /// %TypeError% + type_error: FunctionIndex, + /// %TypeError.prototype% + type_error_prototype: ObjectIndex, + /// %URIError% + uri_error: FunctionIndex, + /// %URIError.prototype% + uri_error_prototype: ObjectIndex, +} + +impl Default for Intrinsics { + fn default() -> Self { + let array = BuiltinObjectIndexes::ArrayConstructorIndex.into(); + let array_prototype = BuiltinObjectIndexes::ArrayPrototypeIndex.into(); + let big_int = BuiltinObjectIndexes::BigintConstructorIndex.into(); + let big_int_prototype = BuiltinObjectIndexes::BigintPrototypeIndex.into(); + let boolean = BuiltinObjectIndexes::BooleanConstructorIndex.into(); + let boolean_prototype = BuiltinObjectIndexes::BooleanPrototypeIndex.into(); + let error = BuiltinObjectIndexes::ErrorConstructorIndex.into(); + let error_prototype = BuiltinObjectIndexes::ErrorPrototypeIndex.into(); + // TODO: Placeholder. + let eval = FunctionIndex::from_u32_index(0); + // TODO: Placeholder. + let eval_error = FunctionIndex::from_u32_index(0); + // TODO: Placeholder. + let eval_error_prototype = ObjectIndex::from_u32_index(0); + let function = BuiltinObjectIndexes::FunctionConstructorIndex.into(); + let function_prototype = BuiltinObjectIndexes::FunctionPrototypeIndex.into(); + // TODO: Placeholder. + let is_finite = FunctionIndex::from_u32_index(0); + // TODO: Placeholder. + let is_nan = FunctionIndex::from_u32_index(0); + let math = BuiltinObjectIndexes::MathObjectIndex.into(); + let number = BuiltinObjectIndexes::NumberConstructorIndex.into(); + let number_prototype = BuiltinObjectIndexes::NumberPrototypeIndex.into(); + let object = BuiltinObjectIndexes::ObjectConstructorIndex.into(); + let object_prototype = BuiltinObjectIndexes::ObjectPrototypeIndex.into(); + // TODO: Placeholder. + let object_prototype_to_string = FunctionIndex::from_u32_index(0); + // TODO: Placeholder. + let range_error = FunctionIndex::from_u32_index(0); + // TODO: Placeholder. + let range_error_prototype = ObjectIndex::from_u32_index(0); + // TODO: Placeholder. + let reference_error = FunctionIndex::from_u32_index(0); + // TODO: Placeholder. + let reference_error_prototype = ObjectIndex::from_u32_index(0); + let reflect = BuiltinObjectIndexes::ReflectObjectIndex.into(); + let string = BuiltinObjectIndexes::StringConstructorIndex.into(); + let string_prototype = BuiltinObjectIndexes::StringPrototypeIndex.into(); + let symbol = BuiltinObjectIndexes::SymbolConstructorIndex.into(); + let symbol_prototype = BuiltinObjectIndexes::SymbolPrototypeIndex.into(); + // TODO: Placeholder. + let syntax_error = FunctionIndex::from_u32_index(0); + // TODO: Placeholder. + let syntax_error_prototype = ObjectIndex::from_u32_index(0); + // TODO: Placeholder. + let throw_type_error = FunctionIndex::from_u32_index(0); + // TODO: Placeholder. + let type_error = FunctionIndex::from_u32_index(0); + // TODO: Placeholder. + let type_error_prototype = ObjectIndex::from_u32_index(0); + // TODO: Placeholder. + let uri_error = FunctionIndex::from_u32_index(0); + // TODO: Placeholder. + let uri_error_prototype = ObjectIndex::from_u32_index(0); + + Self { + array, + array_prototype, + big_int, + big_int_prototype, + boolean, + boolean_prototype, + error, + error_prototype, + eval, + eval_error, + eval_error_prototype, + function, + function_prototype, + is_finite, + is_nan, + math, + number, + number_prototype, + object, + object_prototype, + object_prototype_to_string, + range_error, + range_error_prototype, + reference_error, + reference_error_prototype, + reflect, + string, + string_prototype, + symbol, + symbol_prototype, + syntax_error, + syntax_error_prototype, + throw_type_error, + type_error, + type_error_prototype, + uri_error, + uri_error_prototype, + } + } +} + +impl Intrinsics { + /// %Array% + pub const fn array(&self) -> Function { + Function(self.array) + } + + /// %Array.prototype% + pub const fn array_prototype(&self) -> Object { + Object::Object(self.array_prototype) + } + + /// %BigInt% + pub const fn big_int(&self) -> Function { + Function(self.big_int) + } + + /// %BigInt.prototype% + pub const fn big_int_prototype(&self) -> Object { + Object::Object(self.big_int_prototype) + } + + /// %Boolean% + pub const fn boolean(&self) -> Function { + Function(self.boolean) + } + + /// %Boolean.prototype% + pub const fn boolean_prototype(&self) -> Object { + Object::Object(self.boolean_prototype) + } + + /// %Error% + pub const fn error(&self) -> Function { + Function(self.error) + } + + /// %Error.prototype% + pub const fn error_prototype(&self) -> Object { + Object::Object(self.error_prototype) + } + + /// %eval% + pub const fn eval(&self) -> Function { + todo!() + } + + /// %EvalError% + pub const fn eval_error(&self) -> Function { + Function(self.eval_error) + } + + /// %EvalError.prototype% + pub const fn eval_error_prototype(&self) -> Object { + todo!() + } + + /// %Function% + pub const fn function(&self) -> Function { + Function(self.function) + } + + /// %Function.prototype% + pub const fn function_prototype(&self) -> Object { + Object::Object(self.function_prototype) + } + + /// %isFinite% + pub const fn is_finite(&self) -> Function { + todo!() + } + + /// %isNaN% + pub const fn is_nan(&self) -> Function { + todo!() + } + + /// %Math% + pub const fn math(&self) -> Object { + Object::Object(self.math) + } + + /// %Number% + pub const fn number(&self) -> Function { + Function(self.number) + } + + /// %Number.prototype% + pub const fn number_prototype(&self) -> Object { + Object::Object(self.number_prototype) + } + + /// %Object% + pub const fn object(&self) -> Function { + Function(self.object) + } + + /// %Object.prototype% + pub const fn object_prototype(&self) -> Object { + Object::Object(self.object_prototype) + } + + /// %Object.prototype.toString% + pub const fn object_prototype_to_string(&self) -> Object { + todo!() + } + + /// %RangeError% + pub const fn range_error(&self) -> Object { + todo!() + } + + /// %RangeError.prototype% + pub const fn range_error_prototype(&self) -> Object { + todo!() + } + + /// %ReferenceError% + pub const fn reference_error(&self) -> Object { + todo!() + } + + /// %ReferenceError.prototype% + pub const fn reference_error_prototype(&self) -> Object { + todo!() + } + + /// %Reflect% + pub const fn reflect(&self) -> Object { + todo!() + } + + /// %String% + pub const fn string(&self) -> Function { + Function(self.string) + } + + /// %String.prototype% + pub const fn string_prototype(&self) -> Object { + Object::Object(self.string_prototype) + } + + /// %Symbol% + pub const fn symbol(&self) -> Function { + Function(self.symbol) + } + + /// %Symbol.prototype% + pub const fn symbol_prototype(&self) -> Object { + Object::Object(self.symbol_prototype) + } + + /// %SyntaxError% + pub const fn syntax_error(&self) -> Object { + todo!() + } + + /// %SyntaxError.prototype% + pub const fn syntax_error_prototype(&self) -> Object { + todo!() + } + + /// %ThrowTypeError% + pub const fn throw_type_error(&self) -> Object { + todo!() + } + + /// %TypeError% + pub const fn type_error(&self) -> Object { + todo!() + } + + /// %TypeError.prototype% + pub const fn type_error_prototype(&self) -> Object { + todo!() + } + + /// %URIError% + pub const fn uri_error(&self) -> Object { + todo!() + } + + /// %URIError.prototype% + pub const fn uri_error_prototype(&self) -> Object { + todo!() + } +} diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index f1206817..4b05ea81 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -2,13 +2,13 @@ mod array; mod bigint; mod boolean; mod date; -mod element_array; +pub mod element_array; mod error; mod function; mod heap_bits; mod heap_constants; mod heap_gc; -pub(crate) mod indexes; +pub mod indexes; mod math; mod number; mod object; @@ -16,6 +16,8 @@ mod regexp; mod string; mod symbol; +pub use self::heap_constants::BuiltinObjectIndexes; + use self::{ array::{initialize_array_heap, ArrayHeapData}, bigint::{initialize_bigint_heap, BigIntHeapData}, @@ -27,12 +29,11 @@ use self::{ ElementsVector, }, error::{initialize_error_heap, ErrorHeapData}, - function::{initialize_function_heap, FunctionHeapData, JsBindingFunction}, + function::{initialize_function_heap, FunctionHeapData}, heap_constants::{ - BuiltinObjectIndexes, FIRST_CONSTRUCTOR_INDEX, LAST_BUILTIN_OBJECT_INDEX, - LAST_WELL_KNOWN_SYMBOL_INDEX, + FIRST_CONSTRUCTOR_INDEX, LAST_BUILTIN_OBJECT_INDEX, LAST_WELL_KNOWN_SYMBOL_INDEX, }, - indexes::{FunctionIndex, NumberIndex, ObjectIndex, StringIndex}, + indexes::{BaseIndex, FunctionIndex, NumberIndex, ObjectIndex, StringIndex}, math::initialize_math_object, number::{initialize_number_heap, NumberHeapData}, object::{ @@ -42,31 +43,133 @@ use self::{ string::{initialize_string_heap, StringHeapData}, symbol::{initialize_symbol_heap, SymbolHeapData}, }; -use crate::value::Value; -use wtf8::Wtf8; +use crate::{ + execution::Environments, + types::{Function, Number, Object, String, Value}, +}; +use wtf8::{Wtf8, Wtf8Buf}; #[derive(Debug)] pub struct Heap { + pub environments: Environments, /// ElementsArrays is where all element arrays live; /// Element arrays are static arrays of Values plus /// a HashMap of possible property descriptors. - pub(crate) elements: ElementArrays, - 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 elements: ElementArrays, + pub arrays: Vec>, + pub bigints: Vec>, + pub errors: Vec>, + pub functions: Vec>, + pub dates: Vec>, + pub globals: Vec, + pub numbers: Vec>, + pub objects: Vec>, + pub regexps: Vec>, + pub strings: Vec>, + pub symbols: Vec>, +} + +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: 'a> { + fn get(&'a self, id: BaseIndex) -> &'a F; + fn get_mut(&'a mut self, id: BaseIndex) -> &'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::Float(data as f32)) + } else { + let id = self.alloc_number(data); + Value::Number(id).try_into().unwrap() + } + } +} + +macro_rules! impl_heap_data { + ($table: ident, $in: ty, $out: ty) => { + impl<'a> GetHeapData<'a, $in, $out> for Heap { + fn get(&'a self, id: BaseIndex<$in>) -> &'a $out { + self.$table.get(id.into_index()).unwrap().as_ref().unwrap() + } + + fn get_mut(&'a mut self, id: BaseIndex<$in>) -> &'a mut $out { + self.$table + .get_mut(id.into_index()) + .unwrap() + .as_mut() + .unwrap() + } + } + }; + ($table: ident, $in: ty, $out: ty, $accessor: ident) => { + impl<'a> GetHeapData<'a, $in, $out> for Heap { + fn get(&'a self, id: BaseIndex<$in>) -> &'a $out { + &self + .$table + .get(id.into_index()) + .as_ref() + .unwrap() + .as_ref() + .unwrap() + .$accessor + } + + fn get_mut(&'a mut self, id: BaseIndex<$in>) -> &'a mut $out { + &mut self + .$table + .get_mut(id.into_index()) + .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); + Value::String(id).try_into().unwrap() + } + } +} + +impl CreateHeapData for Heap { + fn create(&mut self, data: FunctionHeapData) -> Function { + self.functions.push(Some(data)); + Function(FunctionIndex::last(&self.functions)) + } +} + +impl CreateHeapData for Heap { + fn create(&mut self, data: ObjectHeapData) -> Object { + self.objects.push(Some(data)); + Object::Object(ObjectIndex::last(&self.objects)) + } } impl Heap { pub fn new() -> Heap { let mut heap = Heap { + environments: Default::default(), elements: ElementArrays { e2pow4: ElementArray2Pow4::with_capacity(1024), e2pow6: ElementArray2Pow6::with_capacity(1024), @@ -100,46 +203,23 @@ impl Heap { heap.functions.push(None); } } - initialize_object_heap(&mut heap); - initialize_function_heap(&mut heap); + initialize_array_heap(&mut heap); + initialize_bigint_heap(&mut heap); initialize_boolean_heap(&mut heap); - initialize_symbol_heap(&mut heap); + initialize_date_heap(&mut heap); initialize_error_heap(&mut heap); - initialize_number_heap(&mut heap); - initialize_bigint_heap(&mut heap); + initialize_function_heap(&mut heap); initialize_math_object(&mut heap); - initialize_date_heap(&mut heap); - initialize_string_heap(&mut heap); + initialize_number_heap(&mut heap); + initialize_object_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); + initialize_string_heap(&mut heap); + initialize_symbol_heap(&mut heap); heap } - pub(crate) fn alloc_string(&mut self, message: &str) -> StringIndex { + pub fn alloc_string(&mut self, message: &str) -> StringIndex { let found = self.strings.iter().position(|opt| { opt.as_ref() .map_or(false, |data| data.data == Wtf8::from_str(message)) @@ -158,22 +238,22 @@ impl Heap { } } - pub(crate) fn alloc_number(&mut self, number: f64) -> NumberIndex { + pub fn alloc_number(&mut self, number: f64) -> NumberIndex { self.numbers.push(Some(NumberHeapData::new(number))); NumberIndex::last(&self.numbers) } - pub(crate) fn create_function( + pub fn create_function( &mut self, name: Value, length: u8, uses_arguments: bool, - binding: JsBindingFunction, + // behaviour: Behaviour, ) -> FunctionIndex { let entries = vec![ ObjectEntry::new( PropertyKey::from_str(self, "length"), - PropertyDescriptor::roxh(Value::SmiU(length as u32)), + PropertyDescriptor::roxh(Value::from(length)), ), ObjectEntry::new( PropertyKey::from_str(self, "name"), @@ -183,59 +263,76 @@ impl Heap { let (keys, values): (ElementsVector, ElementsVector) = self.elements.create_object_entries(entries); let func_object_data = ObjectHeapData { - _extensible: true, + extensible: true, keys, values, - prototype: Value::Object(BuiltinObjectIndexes::FunctionPrototypeIndex.into()), + prototype: Some(Object::Object( + BuiltinObjectIndexes::FunctionPrototypeIndex.into(), + )), }; self.objects.push(Some(func_object_data)); let func_data = FunctionHeapData { - binding, - bound: None, + // behaviour, + // bound: None, length, - object_index: ObjectIndex::last(&self.objects), - uses_arguments, - visible: None, + object_index: Some(ObjectIndex::last(&self.objects)), + // uses_arguments, + // visible: None, + initial_name: Value::Null, }; let index = FunctionIndex::from_index(self.functions.len()); self.functions.push(Some(func_data)); index } - pub(crate) fn create_object(&mut self, entries: Vec) -> ObjectIndex { + pub fn create_object(&mut self, entries: Vec) -> ObjectIndex { let (keys, values) = self.elements.create_object_entries(entries); let object_data = ObjectHeapData { - _extensible: true, + extensible: true, keys, values, - prototype: Value::Object(BuiltinObjectIndexes::ObjectPrototypeIndex.into()), + prototype: Some(Object::Object( + BuiltinObjectIndexes::ObjectPrototypeIndex.into(), + )), }; self.objects.push(Some(object_data)); ObjectIndex::last(&self.objects) } - pub(crate) fn create_null_object(&mut self, entries: Vec) -> ObjectIndex { + pub fn create_null_object(&mut self, entries: Vec) -> ObjectIndex { let (keys, values) = self.elements.create_object_entries(entries); let object_data = ObjectHeapData { - _extensible: true, + extensible: true, + keys, + values, + prototype: None, + }; + self.objects.push(Some(object_data)); + ObjectIndex::last(&self.objects) + } + + pub fn create_object_with_prototype(&mut self, prototype: Object) -> ObjectIndex { + let (keys, values) = self.elements.create_object_entries(vec![]); + let object_data = ObjectHeapData { + extensible: true, keys, values, - prototype: Value::Null, + prototype: Some(prototype), }; self.objects.push(Some(object_data)); ObjectIndex::last(&self.objects) } - pub(crate) fn insert_builtin_object( + pub fn insert_builtin_object( &mut self, index: BuiltinObjectIndexes, extensible: bool, - prototype: Value, + prototype: Option, entries: Vec, ) -> ObjectIndex { let (keys, values) = self.elements.create_object_entries(entries); let object_data = ObjectHeapData { - _extensible: extensible, + extensible, keys, values, prototype, diff --git a/nova_vm/src/heap/array.rs b/nova_vm/src/heap/array.rs index aa876b62..2a789c1e 100644 --- a/nova_vm/src/heap/array.rs +++ b/nova_vm/src/heap/array.rs @@ -1,9 +1,10 @@ use crate::{ + execution::JsResult, heap::{ heap_constants::{get_constructor_index, BuiltinObjectIndexes}, Heap, PropertyDescriptor, }, - value::{JsResult, Value}, + types::{Object, Value}, }; use super::{ @@ -15,14 +16,14 @@ use super::{ }; #[derive(Debug, Clone, Copy)] -pub(crate) struct ArrayHeapData { - pub(super) object_index: ObjectIndex, +pub struct ArrayHeapData { + pub object_index: Option, // TODO: Use SmallVec<[Value; 4]> - pub(super) elements: ElementsVector, + pub elements: ElementsVector, } pub fn initialize_array_heap(heap: &mut Heap) { - let species_function_name = Value::new_string(heap, "get [Symbol.species]"); + let species_function_name = Value::from_str(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"); @@ -40,9 +41,9 @@ pub fn initialize_array_heap(heap: &mut Heap) { let to_spliced_key = PropertyKey::from_str(heap, "toSpliced"); let values_key = PropertyKey::from_str(heap, "values"); let entries = 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_prototype_function_entry(heap, "from", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "isArray", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "of", 0, true), ObjectEntry::new_constructor_prototype_entry( heap, BuiltinObjectIndexes::ArrayPrototypeIndex.into(), @@ -50,7 +51,7 @@ pub fn initialize_array_heap(heap: &mut Heap) { ObjectEntry::new( PropertyKey::Symbol(WellKnownSymbolIndexes::Species.into()), PropertyDescriptor::ReadOnly { - get: heap.create_function(species_function_name, 0, false, array_species), + get: heap.create_function(species_function_name, 0, false), enumerable: false, configurable: true, }, @@ -59,64 +60,66 @@ pub fn initialize_array_heap(heap: &mut Heap) { heap.insert_builtin_object( BuiltinObjectIndexes::ArrayConstructorIndex, true, - Value::Function(BuiltinObjectIndexes::FunctionPrototypeIndex.into()), + Some(Object::Function( + BuiltinObjectIndexes::FunctionPrototypeIndex.into(), + )), entries, ); heap.functions [get_constructor_index(BuiltinObjectIndexes::ArrayConstructorIndex).into_index()] = Some(FunctionHeapData { - object_index: BuiltinObjectIndexes::ArrayConstructorIndex.into(), + object_index: Some(BuiltinObjectIndexes::ArrayConstructorIndex.into()), length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: array_constructor_binding, + // uses_arguments: false, + // bound: None, + // visible: None, + initial_name: Value::Null, }); let entries = 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_prototype_function_entry(heap, "at", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "concat", 1, true), 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), + ObjectEntry::new_prototype_function_entry(heap, "copyWithin", 2, false), + ObjectEntry::new_prototype_function_entry(heap, "entries", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "every", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "fill", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "filter", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "find", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "findIndex", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "findLast", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "findLastIndex", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "flat", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "flatMap", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "forEach", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "includes", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "indexOf", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "join", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "keys", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "lastIndexOf", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "map", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "pop", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "push", 1, true), + ObjectEntry::new_prototype_function_entry(heap, "reduce", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "reduceRight", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "reverse", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "shift", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "slice", 2, false), + ObjectEntry::new_prototype_function_entry(heap, "some", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "sort", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "splice", 2, true), + ObjectEntry::new_prototype_function_entry(heap, "toLocaleString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "toReversed", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "toSorted", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "toSpliced", 2, true), + ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "unshift", 1, true), + ObjectEntry::new_prototype_function_entry(heap, "values", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "with", 2, false), // TODO: These symbol function properties are actually rwxh, this helper generates roxh instead. ObjectEntry::new_prototype_symbol_function_entry( heap, @@ -124,7 +127,6 @@ pub fn initialize_array_heap(heap: &mut Heap) { WellKnownSymbolIndexes::Iterator.into(), 0, false, - array_todo, ), ObjectEntry::new( PropertyKey::Symbol(WellKnownSymbolIndexes::Unscopables.into()), @@ -166,7 +168,9 @@ pub fn initialize_array_heap(heap: &mut Heap) { heap.insert_builtin_object( BuiltinObjectIndexes::ArrayPrototypeIndex, true, - Value::Object(BuiltinObjectIndexes::ObjectPrototypeIndex.into()), + Some(Object::Object( + BuiltinObjectIndexes::ObjectPrototypeIndex.into(), + )), entries, ); } diff --git a/nova_vm/src/heap/bigint.rs b/nova_vm/src/heap/bigint.rs index 9231e6c3..2f3ca62c 100644 --- a/nova_vm/src/heap/bigint.rs +++ b/nova_vm/src/heap/bigint.rs @@ -1,38 +1,29 @@ +use super::indexes::ObjectIndex; use crate::{ + execution::JsResult, heap::{ heap_constants::{get_constructor_index, BuiltinObjectIndexes}, FunctionHeapData, Heap, ObjectEntry, PropertyDescriptor, PropertyKey, }, - value::{JsResult, Value}, + types::{Object, Value}, }; - -use super::indexes::{ErrorIndex, ObjectIndex}; +use num_bigint_dig::BigInt; #[derive(Debug, Clone)] -pub(crate) struct BigIntHeapData { - pub(super) sign: bool, - pub(super) len: u32, - pub(super) parts: Box<[u64]>, +pub struct BigIntHeapData { + 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 try_into_f64(&self) -> Option { + None } } pub fn initialize_bigint_heap(heap: &mut Heap) { let entries = 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_prototype_function_entry(heap, "asIntN", 2, false), + ObjectEntry::new_prototype_function_entry(heap, "asUintN", 2, false), ObjectEntry::new_constructor_prototype_entry( heap, BuiltinObjectIndexes::BigintPrototypeIndex.into(), @@ -41,18 +32,20 @@ pub fn initialize_bigint_heap(heap: &mut Heap) { heap.insert_builtin_object( BuiltinObjectIndexes::BigintConstructorIndex, true, - Value::Function(BuiltinObjectIndexes::FunctionPrototypeIndex.into()), + Some(Object::Function( + BuiltinObjectIndexes::FunctionPrototypeIndex.into(), + )), entries, ); heap.functions [get_constructor_index(BuiltinObjectIndexes::BigintConstructorIndex).into_index()] = Some(FunctionHeapData { - object_index: ObjectIndex::last(&heap.objects), + object_index: Some(ObjectIndex::last(&heap.objects)), length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: bigint_constructor, + // uses_arguments: false, + // bound: None, + // visible: None, + initial_name: Value::Null, }); let entries = vec![ ObjectEntry::new( @@ -61,53 +54,40 @@ pub fn initialize_bigint_heap(heap: &mut Heap) { 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, - ), + ObjectEntry::new_prototype_function_entry(heap, "toLocaleString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false), // @@ToStringTag // ObjectEntry { key: PropertyKey::Symbol(), PropertyDescriptor } ]; heap.insert_builtin_object( BuiltinObjectIndexes::BigintPrototypeIndex, true, - Value::Object(BuiltinObjectIndexes::ObjectPrototypeIndex.into()), + Some(Object::Object( + BuiltinObjectIndexes::ObjectPrototypeIndex.into(), + )), entries, ); } fn bigint_constructor(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult { - if !this.is_undefined() { - // TODO: Throw TypeError - return Err(Value::Error(ErrorIndex::from_index(0))); - } else { - return Ok(Value::SmallBigInt(3)); - } + // if !this.is_undefined() { + // // TODO: Throw TypeError + // return Err(Value::Error(ErrorIndex::from_index(0))); + // } else { + // Ok(Value::SmallBigInt(3)) + // } + Ok(Value::Null) } fn bigint_as_int_n(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::SmallBigInt(3)) + // Ok(Value::SmallBigInt(3)) + Ok(Value::Null) } fn bigint_as_uint_n(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult { - Ok(Value::SmallBigIntU(3)) + // Ok(Value::SmallBigIntU(3)) + Ok(Value::Null) } fn bigint_prototype_to_locale_string( @@ -115,13 +95,13 @@ fn bigint_prototype_to_locale_string( this: Value, args: &[Value], ) -> JsResult { - Ok(Value::new_string(heap, "BigInt(3n)")) + Ok(Value::from_str(heap, "BigInt(3n)")) } fn bigint_prototype_to_string(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult { - Ok(Value::new_string(heap, "BigInt(3n)")) + Ok(Value::from_str(heap, "BigInt(3n)")) } fn bigint_prototype_value_of(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult { - Ok(Value::new_string(heap, "BigInt(3n)")) + Ok(Value::from_str(heap, "BigInt(3n)")) } diff --git a/nova_vm/src/heap/boolean.rs b/nova_vm/src/heap/boolean.rs index 22103013..3ad74702 100644 --- a/nova_vm/src/heap/boolean.rs +++ b/nova_vm/src/heap/boolean.rs @@ -1,9 +1,10 @@ use crate::{ + execution::JsResult, heap::{ heap_constants::{get_constructor_index, BuiltinObjectIndexes}, FunctionHeapData, PropertyDescriptor, }, - value::{JsResult, Value}, + types::{Object, Value}, }; use super::{ @@ -19,18 +20,20 @@ pub fn initialize_boolean_heap(heap: &mut Heap) { heap.insert_builtin_object( BuiltinObjectIndexes::BooleanConstructorIndex, true, - Value::Function(BuiltinObjectIndexes::FunctionPrototypeIndex.into()), + Some(Object::Function( + BuiltinObjectIndexes::FunctionPrototypeIndex.into(), + )), entries, ); heap.functions [get_constructor_index(BuiltinObjectIndexes::BooleanConstructorIndex).into_index()] = Some(FunctionHeapData { - object_index: BuiltinObjectIndexes::BooleanConstructorIndex.into(), + object_index: Some(BuiltinObjectIndexes::BooleanConstructorIndex.into()), length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: boolean_constructor_binding, + // uses_arguments: false, + // bound: None, + // visible: None, + initial_name: Value::Null, }); let entries = vec![ ObjectEntry::new( @@ -39,13 +42,15 @@ pub fn initialize_boolean_heap(heap: &mut Heap) { BuiltinObjectIndexes::BooleanConstructorIndex, ))), ), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, boolean_todo), - ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false, boolean_todo), + ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false), ]; heap.insert_builtin_object( BuiltinObjectIndexes::BooleanPrototypeIndex, true, - Value::Object(BuiltinObjectIndexes::ObjectPrototypeIndex.into()), + Some(Object::Object( + BuiltinObjectIndexes::ObjectPrototypeIndex.into(), + )), entries, ); } diff --git a/nova_vm/src/heap/date.rs b/nova_vm/src/heap/date.rs index c11d49ed..eaff453b 100644 --- a/nova_vm/src/heap/date.rs +++ b/nova_vm/src/heap/date.rs @@ -1,11 +1,12 @@ use std::time::SystemTime; use crate::{ + execution::JsResult, heap::{ heap_constants::{get_constructor_index, BuiltinObjectIndexes}, Heap, PropertyDescriptor, }, - value::{JsResult, Value}, + types::{Object, Value}, }; use super::{ @@ -16,36 +17,38 @@ use super::{ }; #[derive(Debug, Clone, Copy)] -pub(crate) struct DateHeapData { +pub struct DateHeapData { pub(super) object_index: ObjectIndex, pub(super) _date: SystemTime, } pub fn initialize_date_heap(heap: &mut Heap) { let entries = 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_prototype_function_entry(heap, "now", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "parse", 1, false), ObjectEntry::new_constructor_prototype_entry( heap, BuiltinObjectIndexes::DatePrototypeIndex.into(), ), - ObjectEntry::new_prototype_function_entry(heap, "UTC", 7, false, date_todo), + ObjectEntry::new_prototype_function_entry(heap, "UTC", 7, false), ]; heap.insert_builtin_object( BuiltinObjectIndexes::DateConstructorIndex, true, - Value::Function(BuiltinObjectIndexes::FunctionPrototypeIndex.into()), + Some(Object::Function( + BuiltinObjectIndexes::FunctionPrototypeIndex.into(), + )), entries, ); heap.functions [get_constructor_index(BuiltinObjectIndexes::DateConstructorIndex).into_index()] = Some(FunctionHeapData { - object_index: BuiltinObjectIndexes::DateConstructorIndex.into(), + object_index: Some(BuiltinObjectIndexes::DateConstructorIndex.into()), length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: date_constructor_binding, + // uses_arguments: false, + // bound: None, + // visible: None, + initial_name: Value::Null, }); let entries = vec![ ObjectEntry::new( @@ -54,61 +57,62 @@ pub fn initialize_date_heap(heap: &mut Heap) { 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_function_entry(heap, "getDate", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getDay", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getFullYear", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getHours", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getMilliseconds", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getMinutes", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getMonth", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getSeconds", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getTime", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getTimezoneOffset", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getUTCDate", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getUTCDay", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getUTCFullYear", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getUTCHours", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getUTCMilliseconds", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getUTCMinutes", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getUTCMonth", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "getUTCSeconds", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "setDate", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "setFullYear", 3, false), + ObjectEntry::new_prototype_function_entry(heap, "setHours", 4, false), + ObjectEntry::new_prototype_function_entry(heap, "setMilliseconds", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "setMinutes", 3, false), + ObjectEntry::new_prototype_function_entry(heap, "setMonth", 2, false), + ObjectEntry::new_prototype_function_entry(heap, "setSeconds", 2, false), + ObjectEntry::new_prototype_function_entry(heap, "setTime", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "setUTCDate", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "setUTCFullYear", 3, false), + ObjectEntry::new_prototype_function_entry(heap, "setUTCHours", 4, false), + ObjectEntry::new_prototype_function_entry(heap, "setUTCMilliseconds", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "setUTCMinutes", 3, false), + ObjectEntry::new_prototype_function_entry(heap, "setUTCMonth", 2, false), + ObjectEntry::new_prototype_function_entry(heap, "setUTCSeconds", 2, false), + ObjectEntry::new_prototype_function_entry(heap, "toDateString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "toJSON", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "toLocaleDateString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "toLocaleString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "toLocaleTimeString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "toTimeString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "toUTCString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false), ObjectEntry::new_prototype_symbol_function_entry( heap, "[Symbol.toPrimitive]", WellKnownSymbolIndexes::ToPrimitive.into(), 1, false, - date_todo, ), ]; heap.insert_builtin_object( BuiltinObjectIndexes::DatePrototypeIndex, true, - Value::Object(BuiltinObjectIndexes::ObjectPrototypeIndex.into()), + Some(Object::Object( + BuiltinObjectIndexes::ObjectPrototypeIndex.into(), + )), entries, ); } diff --git a/nova_vm/src/heap/element_array.rs b/nova_vm/src/heap/element_array.rs index ca37239d..ddecad42 100644 --- a/nova_vm/src/heap/element_array.rs +++ b/nova_vm/src/heap/element_array.rs @@ -2,12 +2,12 @@ use super::{ indexes::{ElementIndex, FunctionIndex}, object::{ObjectEntry, PropertyDescriptor, PropertyKey}, }; -use crate::value::Value; +use crate::{types::Value, Heap}; use core::panic; -use std::{collections::HashMap, mem::MaybeUninit, num::NonZeroU16}; +use std::{collections::HashMap, mem::MaybeUninit, num::NonZeroU16, vec}; #[derive(Debug, Clone, Copy)] -pub(crate) enum ElementArrayKey { +pub enum ElementArrayKey { /// up to 16 elements E4, /// up to 64 elements @@ -51,13 +51,14 @@ impl From for ElementArrayKey { } #[derive(Debug, Clone, Copy)] -pub(crate) struct ElementsVector { - pub(crate) elements_index: ElementIndex, - pub(crate) cap: ElementArrayKey, - pub(crate) len: u32, +pub struct ElementsVector { + pub elements_index: ElementIndex, + pub cap: ElementArrayKey, + pub len: u32, } #[derive(Debug, Clone, Copy)] +#[repr(u8)] pub enum ElementDescriptor { /// ```js /// { value, writable: true, enumerable: true, configurable: true } @@ -386,9 +387,9 @@ impl ElementDescriptor { /// Element arrays of up to 16 elements #[derive(Debug)] -pub(crate) struct ElementArray2Pow4 { - pub(crate) values: Vec; usize::pow(2, 4)]>>, - pub(crate) descriptors: HashMap>, +pub struct ElementArray2Pow4 { + pub values: Vec; usize::pow(2, 4)]>>, + pub descriptors: HashMap>, } impl Default for ElementArray2Pow4 { @@ -411,9 +412,9 @@ impl ElementArray2Pow4 { /// Element arrays of up to 64 elements #[derive(Debug)] -pub(crate) struct ElementArray2Pow6 { - pub(crate) values: Vec; usize::pow(2, 6)]>>, - pub(crate) descriptors: HashMap>, +pub struct ElementArray2Pow6 { + pub values: Vec; usize::pow(2, 6)]>>, + pub descriptors: HashMap>, } impl Default for ElementArray2Pow6 { @@ -436,9 +437,9 @@ impl ElementArray2Pow6 { /// Element arrays of up to 256 elements #[derive(Debug)] -pub(crate) struct ElementArray2Pow8 { - pub(crate) values: Vec; usize::pow(2, 8)]>>, - pub(crate) descriptors: HashMap>, +pub struct ElementArray2Pow8 { + pub values: Vec; usize::pow(2, 8)]>>, + pub descriptors: HashMap>, } impl Default for ElementArray2Pow8 { @@ -461,9 +462,9 @@ impl ElementArray2Pow8 { /// Element arrays of up to 1024 elements #[derive(Debug)] -pub(crate) struct ElementArray2Pow10 { - pub(crate) values: Vec; usize::pow(2, 10)]>>, - pub(crate) descriptors: HashMap>, +pub struct ElementArray2Pow10 { + pub values: Vec; usize::pow(2, 10)]>>, + pub descriptors: HashMap>, } impl Default for ElementArray2Pow10 { @@ -486,9 +487,9 @@ impl ElementArray2Pow10 { /// Element arrays of up to 4096 elements #[derive(Debug)] -pub(crate) struct ElementArray2Pow12 { - pub(crate) values: Vec; usize::pow(2, 12)]>>, - pub(crate) descriptors: HashMap>, +pub struct ElementArray2Pow12 { + pub values: Vec; usize::pow(2, 12)]>>, + pub descriptors: HashMap>, } impl Default for ElementArray2Pow12 { @@ -511,9 +512,9 @@ impl ElementArray2Pow12 { /// Element arrays of up to 65536 elements #[derive(Debug)] -pub(crate) struct ElementArray2Pow16 { - pub(crate) values: Vec; usize::pow(2, 16)]>>, - pub(crate) descriptors: HashMap>, +pub struct ElementArray2Pow16 { + pub values: Vec; usize::pow(2, 16)]>>, + pub descriptors: HashMap>, } impl Default for ElementArray2Pow16 { @@ -536,9 +537,9 @@ impl ElementArray2Pow16 { /// Element arrays of up to 16777216 elements #[derive(Debug)] -pub(crate) struct ElementArray2Pow24 { - pub(crate) values: Vec; usize::pow(2, 24)]>>, - pub(crate) descriptors: HashMap>, +pub struct ElementArray2Pow24 { + pub values: Vec; usize::pow(2, 24)]>>, + pub descriptors: HashMap>, } impl Default for ElementArray2Pow24 { @@ -561,9 +562,9 @@ impl ElementArray2Pow24 { /// Element arrays of up to 4294967296 elements #[derive(Debug)] -pub(crate) struct ElementArray2Pow32 { - pub(crate) values: Vec; usize::pow(2, 32)]>>, - pub(crate) descriptors: HashMap>, +pub struct ElementArray2Pow32 { + pub values: Vec; usize::pow(2, 32)]>>, + pub descriptors: HashMap>, } impl Default for ElementArray2Pow32 { @@ -585,23 +586,23 @@ impl ElementArray2Pow32 { } #[derive(Debug)] -pub(crate) struct ElementArrays { +pub struct ElementArrays { /// up to 16 elements - pub(crate) e2pow4: ElementArray2Pow4, + pub e2pow4: ElementArray2Pow4, /// up to 64 elements - pub(crate) e2pow6: ElementArray2Pow6, + pub e2pow6: ElementArray2Pow6, /// up to 256 elements - pub(crate) e2pow8: ElementArray2Pow8, + pub e2pow8: ElementArray2Pow8, /// up to 1024 elements - pub(crate) e2pow10: ElementArray2Pow10, + pub e2pow10: ElementArray2Pow10, /// up to 4096 elements - pub(crate) e2pow12: ElementArray2Pow12, + pub e2pow12: ElementArray2Pow12, /// up to 65536 elements - pub(crate) e2pow16: ElementArray2Pow16, + pub e2pow16: ElementArray2Pow16, /// up to 16777216 elements - pub(crate) e2pow24: ElementArray2Pow24, + pub e2pow24: ElementArray2Pow24, /// up to 4294967296 elements - pub(crate) e2pow32: ElementArray2Pow32, + pub e2pow32: ElementArray2Pow32, } impl ElementArrays { @@ -775,7 +776,7 @@ impl ElementArrays { } } - pub(crate) fn create_object_entries( + pub fn create_object_entries( &mut self, mut entries: Vec, ) -> (ElementsVector, ElementsVector) { @@ -788,9 +789,9 @@ impl ElementArrays { let (maybe_descriptor, maybe_value) = ElementDescriptor::from_property_descriptor(value); let key = match key { - PropertyKey::SmallAsciiString(data) => Value::StackString(data), - PropertyKey::Smi(data) => Value::Smi(data), - PropertyKey::String(data) => Value::HeapString(data), + PropertyKey::SmallString(data) => Value::SmallString(data), + PropertyKey::Smi(data) => Value::from(data), + PropertyKey::String(data) => Value::String(data), PropertyKey::Symbol(data) => Value::Symbol(data), }; keys.push(Some(key)); @@ -822,4 +823,134 @@ impl ElementArrays { }, ) } + + pub fn get<'a>(&'a self, vector: ElementsVector) -> () { + // match vector.cap { + // ElementArrayKey::E4 => &self + // .e2pow4 + // .values + // .get(vector.elements_index.into_index()) + // .unwrap() + // .unwrap() + // .as_slice()[0..vector.len as usize], + // ElementArrayKey::E6 => &self + // .e2pow6 + // .values + // .get(vector.elements_index.into_index()) + // .unwrap() + // .unwrap() + // .as_slice()[0..vector.len as usize], + // ElementArrayKey::E8 => &self + // .e2pow8 + // .values + // .get(vector.elements_index.into_index()) + // .unwrap() + // .unwrap() + // .as_slice()[0..vector.len as usize], + // ElementArrayKey::E10 => &self + // .e2pow10 + // .values + // .get(vector.elements_index.into_index()) + // .unwrap() + // .unwrap() + // .as_slice()[0..vector.len as usize], + // ElementArrayKey::E12 => &self + // .e2pow12 + // .values + // .get(vector.elements_index.into_index()) + // .unwrap() + // .unwrap() + // .as_slice()[0..vector.len as usize], + // ElementArrayKey::E16 => &self + // .e2pow16 + // .values + // .get(vector.elements_index.into_index()) + // .unwrap() + // .unwrap() + // .as_slice()[0..vector.len as usize], + // ElementArrayKey::E24 => &self + // .e2pow24 + // .values + // .get(vector.elements_index.into_index()) + // .unwrap() + // .unwrap() + // .as_slice()[0..vector.len as usize], + // ElementArrayKey::E32 => &self + // .e2pow32 + // .values + // .get(vector.elements_index.into_index()) + // .unwrap() + // .unwrap() + // .as_slice()[0..vector.len as usize], + // _ => unreachable!(), + } + + pub fn has(&self, vector: ElementsVector, element: Value) -> bool { + match vector.cap { + ElementArrayKey::E4 => self + .e2pow4 + .values + .get(vector.elements_index.into_index()) + .unwrap() + .unwrap() + .as_slice()[0..vector.len as usize] + .contains(&Some(element)), + ElementArrayKey::E6 => self + .e2pow6 + .values + .get(vector.elements_index.into_index()) + .unwrap() + .unwrap() + .as_slice()[0..vector.len as usize] + .contains(&Some(element)), + ElementArrayKey::E8 => self + .e2pow8 + .values + .get(vector.elements_index.into_index()) + .unwrap() + .unwrap() + .as_slice()[0..vector.len as usize] + .contains(&Some(element)), + ElementArrayKey::E10 => self + .e2pow10 + .values + .get(vector.elements_index.into_index()) + .unwrap() + .unwrap() + .as_slice()[0..vector.len as usize] + .contains(&Some(element)), + ElementArrayKey::E12 => self + .e2pow12 + .values + .get(vector.elements_index.into_index()) + .unwrap() + .unwrap() + .as_slice()[0..vector.len as usize] + .contains(&Some(element)), + ElementArrayKey::E16 => self + .e2pow16 + .values + .get(vector.elements_index.into_index()) + .unwrap() + .unwrap() + .as_slice()[0..vector.len as usize] + .contains(&Some(element)), + ElementArrayKey::E24 => self + .e2pow24 + .values + .get(vector.elements_index.into_index()) + .unwrap() + .unwrap() + .as_slice()[0..vector.len as usize] + .contains(&Some(element)), + ElementArrayKey::E32 => self + .e2pow32 + .values + .get(vector.elements_index.into_index()) + .unwrap() + .unwrap() + .as_slice()[0..vector.len as usize] + .contains(&Some(element)), + } + } } diff --git a/nova_vm/src/heap/error.rs b/nova_vm/src/heap/error.rs index d13ba4c3..e16dec45 100644 --- a/nova_vm/src/heap/error.rs +++ b/nova_vm/src/heap/error.rs @@ -1,9 +1,10 @@ use crate::{ + execution::JsResult, heap::{ heap_constants::{get_constructor_index, BuiltinObjectIndexes}, Heap, PropertyDescriptor, }, - value::{JsResult, Value}, + types::{Object, Value}, }; use super::{ @@ -13,7 +14,7 @@ use super::{ }; #[derive(Debug, Clone, Copy)] -pub(crate) struct ErrorHeapData { +pub struct ErrorHeapData { pub(super) object_index: ObjectIndex, // TODO: stack? name? } @@ -26,18 +27,20 @@ pub fn initialize_error_heap(heap: &mut Heap) { heap.insert_builtin_object( BuiltinObjectIndexes::ErrorConstructorIndex, true, - Value::Function(BuiltinObjectIndexes::FunctionPrototypeIndex.into()), + Some(Object::Function( + BuiltinObjectIndexes::FunctionPrototypeIndex.into(), + )), entries, ); heap.functions [get_constructor_index(BuiltinObjectIndexes::ErrorConstructorIndex).into_index()] = Some(FunctionHeapData { - object_index: BuiltinObjectIndexes::ErrorConstructorIndex.into(), + object_index: Some(BuiltinObjectIndexes::ErrorConstructorIndex.into()), length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: error_constructor_binding, + // uses_arguments: false, + // bound: None, + // visible: None, + initial_name: Value::Null, }); let entries = vec![ ObjectEntry::new( @@ -48,18 +51,20 @@ pub fn initialize_error_heap(heap: &mut Heap) { ), ObjectEntry::new( PropertyKey::from_str(heap, "name"), - PropertyDescriptor::rwx(Value::EmptyString), + PropertyDescriptor::rwx(Value::try_from("").unwrap()), ), ObjectEntry::new( PropertyKey::from_str(heap, "name"), - PropertyDescriptor::rwx(Value::new_string(heap, "Error")), + PropertyDescriptor::rwx(Value::from_str(heap, "Error")), ), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, error_todo), + ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false), ]; heap.insert_builtin_object( BuiltinObjectIndexes::ErrorPrototypeIndex, true, - Value::Object(BuiltinObjectIndexes::ObjectPrototypeIndex.into()), + Some(Object::Object( + BuiltinObjectIndexes::ObjectPrototypeIndex.into(), + )), entries, ); } diff --git a/nova_vm/src/heap/function.rs b/nova_vm/src/heap/function.rs index d7d6bc46..bc176e01 100644 --- a/nova_vm/src/heap/function.rs +++ b/nova_vm/src/heap/function.rs @@ -1,9 +1,11 @@ use crate::{ + builtins::Behaviour, + execution::JsResult, heap::{ heap_constants::{get_constructor_index, BuiltinObjectIndexes}, Heap, PropertyDescriptor, }, - value::{JsResult, Value}, + types::{Object, Value}, }; use super::{ @@ -12,16 +14,17 @@ use super::{ object::{ObjectEntry, PropertyKey}, }; -pub type JsBindingFunction = fn(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult; - #[derive(Debug, Clone)] -pub(crate) struct FunctionHeapData { - pub(super) object_index: ObjectIndex, - pub(super) length: u8, - pub(super) uses_arguments: bool, - pub(super) bound: Option>, - pub(super) visible: Option>, - pub(super) binding: JsBindingFunction, +pub struct FunctionHeapData { + pub(crate) object_index: Option, + pub(crate) length: u8, + pub initial_name: Value, + // pub 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>, // TODO: Should name be here as an "internal slot" of sorts? } @@ -33,37 +36,38 @@ pub fn initialize_function_heap(heap: &mut Heap) { heap.insert_builtin_object( BuiltinObjectIndexes::FunctionConstructorIndex, true, - Value::Function(BuiltinObjectIndexes::FunctionPrototypeIndex.into()), + Some(Object::Function( + BuiltinObjectIndexes::FunctionPrototypeIndex.into(), + )), entries, ); heap.functions [get_constructor_index(BuiltinObjectIndexes::FunctionConstructorIndex).into_index()] = Some(FunctionHeapData { - object_index: BuiltinObjectIndexes::FunctionConstructorIndex.into(), + object_index: Some(BuiltinObjectIndexes::FunctionConstructorIndex.into()), length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: function_constructor_binding, + // uses_arguments: false, + // bound: None, + // visible: None, + initial_name: Value::Null, }); let entries = 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_prototype_function_entry(heap, "apply", 2, false), + ObjectEntry::new_prototype_function_entry(heap, "bind", 1, true), + ObjectEntry::new_prototype_function_entry(heap, "call", 1, true), 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_function_entry(heap, "toString", 0, false), ObjectEntry::new_prototype_symbol_function_entry( heap, "hasInstance", WellKnownSymbolIndexes::HasInstance.into(), 1, false, - function_todo, ), ]; // NOTE: According to ECMAScript spec https://tc39.es/ecma262/#sec-properties-of-the-function-prototype-object @@ -72,7 +76,9 @@ pub fn initialize_function_heap(heap: &mut Heap) { heap.insert_builtin_object( BuiltinObjectIndexes::FunctionPrototypeIndex, true, - Value::Object(BuiltinObjectIndexes::ObjectPrototypeIndex.into()), + Some(Object::Object( + BuiltinObjectIndexes::ObjectPrototypeIndex.into(), + )), entries, ); } diff --git a/nova_vm/src/heap/heap_bits.rs b/nova_vm/src/heap/heap_bits.rs index e9113919..8cca9ec2 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -1,6 +1,6 @@ use std::sync::atomic::AtomicBool; -use crate::value::Value; +use crate::types::Value; use super::{ indexes::{ @@ -10,7 +10,7 @@ use super::{ Heap, }; -pub(crate) struct HeapBits { +pub struct HeapBits { pub e_2_4: Box<[AtomicBool]>, pub e_2_6: Box<[AtomicBool]>, pub e_2_8: Box<[AtomicBool]>, @@ -31,7 +31,7 @@ pub(crate) struct HeapBits { pub symbols: Box<[AtomicBool]>, } -pub(crate) struct WorkQueues { +pub struct WorkQueues { pub e_2_4: Vec, pub e_2_6: Vec, pub e_2_8: Vec, @@ -53,7 +53,7 @@ pub(crate) struct WorkQueues { } impl HeapBits { - pub(crate) fn new(heap: &Heap) -> Self { + pub fn new(heap: &Heap) -> Self { Self { e_2_4: Vec::with_capacity(heap.elements.e2pow4.values.len()).into_boxed_slice(), e_2_6: Vec::with_capacity(heap.elements.e2pow6.values.len()).into_boxed_slice(), @@ -78,7 +78,7 @@ impl HeapBits { } impl WorkQueues { - pub(crate) fn new(heap: &Heap) -> Self { + pub fn new(heap: &Heap) -> Self { Self { e_2_4: Vec::with_capacity(heap.elements.e2pow4.values.len() / 4), e_2_6: Vec::with_capacity(heap.elements.e2pow6.values.len() / 4), @@ -101,40 +101,34 @@ impl WorkQueues { } } - pub(crate) fn push_value(&mut self, value: Value) { + pub fn push_value(&mut self, value: Value) { match value { Value::Array(idx) => self.arrays.push(idx), - Value::BigIntObject(_) => todo!(), - Value::BooleanObject(idx) => todo!(), + // Value::BigIntObject(_) => todo!(), + // Value::BooleanObject(idx) => todo!(), Value::Boolean(_) => {} Value::Date(idx) => self.dates.push(idx), - Value::EmptyString => {} Value::Error(idx) => self.errors.push(idx), Value::Function(idx) => todo!(), - Value::HeapBigInt(idx) => self.bigints.push(idx), - Value::HeapNumber(idx) => self.numbers.push(idx), - Value::HeapString(idx) => self.strings.push(idx), - Value::Infinity => {} - Value::NaN => {} - Value::NegativeInfinity => {} - Value::NegativeZero => {} + Value::BigInt(idx) => self.bigints.push(idx), + Value::Number(idx) => self.numbers.push(idx), + Value::String(idx) => self.strings.push(idx), Value::Null => {} - Value::NumberObject(_) => todo!(), + // Value::NumberObject(_) => todo!(), Value::Object(idx) => self.objects.push(idx), Value::RegExp(idx) => self.regexps.push(idx), - Value::StackString(_) => {} + Value::SmallString(_) => {} Value::SmallBigInt(_) => {} - Value::SmallBigIntU(_) => {} - Value::Smi(_) => {} - Value::SmiU(_) => {} - Value::StringObject(_) => todo!(), + // Value::StringObject(_) => todo!(), Value::Symbol(idx) => self.symbols.push(idx), - Value::SymbolObject(_) => todo!(), + // Value::SymbolObject(_) => todo!(), Value::Undefined => {} + Value::Integer(_) => {} + Value::Float(_) => {} } } - pub(crate) fn is_empty(&self) -> bool { + pub fn is_empty(&self) -> bool { self.e_2_4.is_empty() && self.e_2_6.is_empty() && self.e_2_8.is_empty() diff --git a/nova_vm/src/heap/heap_constants.rs b/nova_vm/src/heap/heap_constants.rs index d3c53417..b95d5868 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -10,8 +10,8 @@ use super::indexes::{FunctionIndex, ObjectIndex, SymbolIndex}; -#[derive(Debug, Clone, Copy)] #[repr(u32)] +#[derive(Debug, Clone, Copy)] pub enum BuiltinObjectIndexes { // Fundamental objects ObjectPrototypeIndex, @@ -148,6 +148,12 @@ impl Into for BuiltinObjectIndexes { } } +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/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index 1f5b0114..d9cbb617 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -1,6 +1,6 @@ use std::sync::atomic::Ordering; -use crate::value::Value; +use crate::types::Value; use super::{ element_array::ElementArrayKey, @@ -12,7 +12,7 @@ use super::{ ElementsVector, Heap, }; -pub(crate) fn heap_gc(heap: &mut Heap) { +pub fn heap_gc(heap: &mut Heap) { let bits = HeapBits::new(heap); let mut queues = WorkQueues::new(heap); @@ -32,7 +32,9 @@ pub(crate) fn heap_gc(heap: &mut Heap) { } marked.store(true, Ordering::Relaxed); let heap_data = heap.arrays.get(index).unwrap().as_ref().unwrap(); - queues.push_value(Value::Object(heap_data.object_index)); + if let Some(object_index) = heap_data.object_index { + queues.push_value(Value::Object(object_index)); + } let ElementsVector { elements_index, cap, @@ -75,17 +77,19 @@ pub(crate) fn heap_gc(heap: &mut Heap) { } marked.store(true, Ordering::Relaxed); let data = heap.functions.get(index).unwrap().as_ref().unwrap(); - queues.objects.push(data.object_index); - if let Some(bound) = &data.bound { - bound.iter().for_each(|&value| { - queues.push_value(value); - }) - } - if let Some(visible) = &data.visible { - visible.iter().for_each(|&value| { - queues.push_value(value); - }) + if let Some(object_index) = data.object_index { + queues.objects.push(object_index); } + // if let Some(bound) = &data.bound { + // bound.iter().for_each(|&value| { + // queues.push_value(value); + // }) + // } + // if let Some(visible) = &data.visible { + // visible.iter().for_each(|&value| { + // queues.push_value(value); + // }) + // } } }); let mut dates: Box<[DateIndex]> = queues.dates.drain(..).collect(); @@ -183,7 +187,7 @@ pub(crate) fn heap_gc(heap: &mut Heap) { marked.store(true, Ordering::Relaxed); let data = heap.symbols.get(index).unwrap().as_ref().unwrap(); if let Some(string_index) = data.descriptor { - queues.push_value(Value::HeapString(string_index)); + queues.push_value(Value::String(string_index)); } } }); diff --git a/nova_vm/src/heap/indexes.rs b/nova_vm/src/heap/indexes.rs index 7472b552..8ccf236f 100644 --- a/nova_vm/src/heap/indexes.rs +++ b/nova_vm/src/heap/indexes.rs @@ -1,4 +1,5 @@ -use crate::value::Value; +use crate::types::Value; +use crate::Heap; use super::{ array::ArrayHeapData, bigint::BigIntHeapData, date::DateHeapData, error::ErrorHeapData, @@ -15,7 +16,7 @@ use std::{marker::PhantomData, mem::size_of, num::NonZeroU32}; /// /// This index implies a tracing reference count from this /// struct to T at the given index. -pub(crate) struct BaseIndex(NonZeroU32, PhantomData); +pub struct BaseIndex(NonZeroU32, PhantomData); const _INDEX_SIZE_IS_U32: () = assert!(size_of::>() == size_of::()); const _OPTION_INDEX_SIZE_IS_U32: () = @@ -95,7 +96,6 @@ impl BaseIndex { } pub const fn from_u32_index(value: u32) -> Self { - let value = value as u32; assert!(value != u32::MAX); // SAFETY: Number is not max value and will not overflow to zero. // This check is done manually to allow const context. @@ -114,7 +114,6 @@ impl BaseIndex { } pub const fn from_u32(value: u32) -> Self { - let value = value as u32; assert!(value != 0); // SAFETY: Number is not zero. // This check is done manually to allow const context. @@ -127,14 +126,24 @@ impl BaseIndex { } } -pub(crate) type ArrayIndex = BaseIndex; -pub(crate) type BigIntIndex = BaseIndex; -pub(crate) type DateIndex = BaseIndex; -pub(crate) type ErrorIndex = BaseIndex; -pub(crate) type FunctionIndex = BaseIndex; -pub(crate) type NumberIndex = BaseIndex; -pub(crate) type ObjectIndex = BaseIndex; -pub(crate) type RegExpIndex = BaseIndex; -pub(crate) type StringIndex = BaseIndex; -pub(crate) type SymbolIndex = BaseIndex; -pub(crate) type ElementIndex = BaseIndex<[Option]>; +pub type ArrayIndex = BaseIndex; +pub type BigIntIndex = BaseIndex; +pub type DateIndex = BaseIndex; +pub type ErrorIndex = BaseIndex; +pub type FunctionIndex = BaseIndex; +pub type NumberIndex = BaseIndex; +pub type ObjectIndex = BaseIndex; +pub type RegExpIndex = BaseIndex; +pub type StringIndex = BaseIndex; +pub type SymbolIndex = BaseIndex; +pub type ElementIndex = BaseIndex<[Option]>; + +impl ObjectIndex { + pub fn get<'a>(self, heap: &'a Heap) -> &'a ObjectHeapData { + heap.objects + .get(self.into_index()) + .unwrap() + .as_ref() + .unwrap() + } +} diff --git a/nova_vm/src/heap/math.rs b/nova_vm/src/heap/math.rs index c8905ad0..c46fb255 100644 --- a/nova_vm/src/heap/math.rs +++ b/nova_vm/src/heap/math.rs @@ -1,4 +1,4 @@ -use crate::value::{JsResult, Value}; +use crate::{execution::JsResult, types::Value}; use super::{ heap_constants::WellKnownSymbolIndexes, @@ -15,41 +15,41 @@ pub(super) fn initialize_math_object(heap: &mut Heap) { 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 abs = ObjectEntry::new_prototype_function_entry(heap, "abs", 1, false); + let acos = ObjectEntry::new_prototype_function_entry(heap, "acos", 1, false); + let acosh = ObjectEntry::new_prototype_function_entry(heap, "acosh", 1, false); + let asin = ObjectEntry::new_prototype_function_entry(heap, "asin", 1, false); + let asinh = ObjectEntry::new_prototype_function_entry(heap, "asinh", 1, false); + let atan = ObjectEntry::new_prototype_function_entry(heap, "atan", 1, false); + let atanh = ObjectEntry::new_prototype_function_entry(heap, "atanh", 1, false); + let atan2 = ObjectEntry::new_prototype_function_entry(heap, "atan2", 2, false); + let cbrt = ObjectEntry::new_prototype_function_entry(heap, "cbrt", 1, false); + let ceil = ObjectEntry::new_prototype_function_entry(heap, "ceil", 1, false); + let clz32 = ObjectEntry::new_prototype_function_entry(heap, "clz32", 1, false); + let cos = ObjectEntry::new_prototype_function_entry(heap, "cos", 1, false); + let cosh = ObjectEntry::new_prototype_function_entry(heap, "cosh", 1, false); + let exp = ObjectEntry::new_prototype_function_entry(heap, "exp", 1, false); + let expm1 = ObjectEntry::new_prototype_function_entry(heap, "expm1", 1, false); + let floor = ObjectEntry::new_prototype_function_entry(heap, "floor", 1, false); + let fround = ObjectEntry::new_prototype_function_entry(heap, "fround", 1, false); + let hypot = ObjectEntry::new_prototype_function_entry(heap, "hypot", 2, true); + let imul = ObjectEntry::new_prototype_function_entry(heap, "imul", 2, false); + let log = ObjectEntry::new_prototype_function_entry(heap, "log", 1, false); + let log1p = ObjectEntry::new_prototype_function_entry(heap, "log1p", 1, false); + let log10 = ObjectEntry::new_prototype_function_entry(heap, "log10", 1, false); + let log2 = ObjectEntry::new_prototype_function_entry(heap, "log2", 1, false); + let max = ObjectEntry::new_prototype_function_entry(heap, "max", 2, true); + let min = ObjectEntry::new_prototype_function_entry(heap, "min", 2, true); + let pow = ObjectEntry::new_prototype_function_entry(heap, "pow", 2, false); + let random = ObjectEntry::new_prototype_function_entry(heap, "random", 0, false); + let round = ObjectEntry::new_prototype_function_entry(heap, "round", 1, false); + let sign = ObjectEntry::new_prototype_function_entry(heap, "sign", 1, false); + let sin = ObjectEntry::new_prototype_function_entry(heap, "sin", 1, false); + let sinh = ObjectEntry::new_prototype_function_entry(heap, "sinh", 1, false); + let sqrt = ObjectEntry::new_prototype_function_entry(heap, "sqrt", 1, false); + let tan = ObjectEntry::new_prototype_function_entry(heap, "tan", 1, false); + let tanh = ObjectEntry::new_prototype_function_entry(heap, "tanh", 1, false); + let trunc = ObjectEntry::new_prototype_function_entry(heap, "trunc", 1, false); let entries = vec![ ObjectEntry::new_frozen_entry(heap, "E", e), ObjectEntry::new_frozen_entry(heap, "LN10", ln10), @@ -61,7 +61,7 @@ pub(super) fn initialize_math_object(heap: &mut Heap) { ObjectEntry::new_frozen_entry(heap, "SQRT2", sqrt2), ObjectEntry::new( PropertyKey::Symbol(WellKnownSymbolIndexes::ToStringTag.into()), - PropertyDescriptor::roxh(Value::new_string(heap, "Math")), + PropertyDescriptor::roxh(Value::from_str(heap, "Math")), ), abs, acos, diff --git a/nova_vm/src/heap/number.rs b/nova_vm/src/heap/number.rs index 2792aa85..7afe6553 100644 --- a/nova_vm/src/heap/number.rs +++ b/nova_vm/src/heap/number.rs @@ -1,19 +1,18 @@ -use std::vec; - use super::{ object::{ObjectEntry, PropertyKey}, Heap, }; use crate::{ + execution::JsResult, heap::{ heap_constants::{get_constructor_index, BuiltinObjectIndexes}, FunctionHeapData, PropertyDescriptor, }, - value::{JsResult, Value}, + types::{Object, Value}, }; #[derive(Debug, Clone, Copy)] -pub(crate) struct NumberHeapData { +pub struct NumberHeapData { pub(super) data: f64, } @@ -22,7 +21,7 @@ impl NumberHeapData { NumberHeapData { data } } - pub(crate) fn value(&self) -> f64 { + pub fn value(&self) -> f64 { self.data } } @@ -33,10 +32,10 @@ pub fn initialize_number_heap(heap: &mut Heap) { 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_prototype_function_entry(heap, "isFinite", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "isInteger", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "isNan", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "isSafeInteger", 1, false), ObjectEntry::new( PropertyKey::from_str(heap, "MAX_SAFE_INTEGER"), PropertyDescriptor::roh(Value::from_f64(heap, 9007199254740991.0)), @@ -55,17 +54,17 @@ pub fn initialize_number_heap(heap: &mut Heap) { ), ObjectEntry::new( PropertyKey::from_str(heap, "NaN"), - PropertyDescriptor::roh(Value::NaN), + PropertyDescriptor::roh(Value::from(f32::NAN)), ), ObjectEntry::new( PropertyKey::from_str(heap, "NEGATIVE_INFINITY"), - PropertyDescriptor::roh(Value::NegativeInfinity), + PropertyDescriptor::roh(Value::from(f32::NEG_INFINITY)), ), - ObjectEntry::new_prototype_function_entry(heap, "parseFloat", 1, false, number_todo), - ObjectEntry::new_prototype_function_entry(heap, "parseInt", 2, false, number_todo), + ObjectEntry::new_prototype_function_entry(heap, "parseFloat", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "parseInt", 2, false), ObjectEntry::new( PropertyKey::from_str(heap, "POSITIVE_INFINITY"), - PropertyDescriptor::roh(Value::Infinity), + PropertyDescriptor::roh(Value::from(f32::INFINITY)), ), ObjectEntry::new_constructor_prototype_entry( heap, @@ -75,18 +74,20 @@ pub fn initialize_number_heap(heap: &mut Heap) { heap.insert_builtin_object( BuiltinObjectIndexes::NumberConstructorIndex, true, - Value::Function(BuiltinObjectIndexes::FunctionPrototypeIndex.into()), + Some(Object::Function( + BuiltinObjectIndexes::FunctionPrototypeIndex.into(), + )), entries, ); heap.functions [get_constructor_index(BuiltinObjectIndexes::NumberConstructorIndex).into_index()] = Some(FunctionHeapData { - object_index: BuiltinObjectIndexes::NumberConstructorIndex.into(), + object_index: Some(BuiltinObjectIndexes::NumberConstructorIndex.into()), length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: number_constructor_binding, + // uses_arguments: false, + // bound: None, + // visible: None, + initial_name: Value::Null, }); let entries = vec![ ObjectEntry::new( @@ -95,23 +96,25 @@ pub fn initialize_number_heap(heap: &mut Heap) { 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), + ObjectEntry::new_prototype_function_entry(heap, "toExponential", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "toExponential", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "toLocaleString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "toPrecision", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false), ]; heap.insert_builtin_object( BuiltinObjectIndexes::NumberPrototypeIndex, true, - Value::Object(BuiltinObjectIndexes::ObjectPrototypeIndex.into()), + Some(Object::Object( + BuiltinObjectIndexes::ObjectPrototypeIndex.into(), + )), entries, ); } fn number_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::SmiU(0)) + Ok(Value::from(0)) } fn number_todo(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { diff --git a/nova_vm/src/heap/object.rs b/nova_vm/src/heap/object.rs index 8ab5c754..95043f27 100644 --- a/nova_vm/src/heap/object.rs +++ b/nova_vm/src/heap/object.rs @@ -1,11 +1,13 @@ +use std::collections::HashMap; + use crate::{ + execution::JsResult, heap::{ - function::JsBindingFunction, heap_constants::{get_constructor_index, BuiltinObjectIndexes}, FunctionHeapData, Heap, }, - stack_string::StackString, - value::{JsResult, Value}, + types::{Object, Value}, + SmallString, }; use std::{fmt::Debug, vec}; @@ -16,50 +18,50 @@ use super::{ #[derive(Debug)] pub struct ObjectEntry { - pub(crate) key: PropertyKey, - pub(crate) value: PropertyDescriptor, + pub key: PropertyKey, + pub value: PropertyDescriptor, } impl ObjectEntry { - pub(crate) fn new(key: PropertyKey, value: PropertyDescriptor) -> Self { + pub fn new(key: PropertyKey, value: PropertyDescriptor) -> Self { ObjectEntry { key, value } } - pub(crate) fn new_prototype_function_entry( + pub fn new_prototype_function_entry( heap: &mut Heap, name: &str, length: u8, uses_arguments: bool, - binding: JsBindingFunction, + // behaviour: Behaviour, ) -> Self { let key = PropertyKey::from_str(heap, name); let name = match key { - PropertyKey::SmallAsciiString(data) => Value::StackString(data.clone()), + PropertyKey::SmallString(data) => Value::SmallString(data.clone()), PropertyKey::Smi(_) => unreachable!("No prototype functions should have SMI names"), - PropertyKey::String(idx) => Value::HeapString(idx), + PropertyKey::String(idx) => Value::String(idx), PropertyKey::Symbol(idx) => Value::Symbol(idx), }; - let func_index = heap.create_function(name, length, uses_arguments, binding); + let func_index = heap.create_function(name, length, uses_arguments); let value = PropertyDescriptor::rwxh(Value::Function(func_index)); ObjectEntry { key, value } } - pub(crate) fn new_prototype_symbol_function_entry( + pub fn new_prototype_symbol_function_entry( heap: &mut Heap, name: &str, symbol_index: SymbolIndex, length: u8, uses_arguments: bool, - binding: JsBindingFunction, + // behaviour: Behaviour, ) -> Self { - let name = Value::new_string(heap, name); + let name = Value::from_str(heap, name); let key = PropertyKey::Symbol(symbol_index); - let func_index = heap.create_function(name, length, uses_arguments, binding); + let func_index = heap.create_function(name, length, uses_arguments); let value = PropertyDescriptor::roxh(Value::Function(func_index)); ObjectEntry { key, value } } - pub(crate) fn new_constructor_prototype_entry(heap: &mut Heap, idx: ObjectIndex) -> Self { + pub fn new_constructor_prototype_entry(heap: &mut Heap, idx: ObjectIndex) -> Self { ObjectEntry { key: PropertyKey::from_str(heap, "prototype"), value: PropertyDescriptor::Data { @@ -71,7 +73,7 @@ impl ObjectEntry { } } - pub(crate) fn new_frozen_entry(heap: &mut Heap, key: &str, value: Value) -> Self { + pub fn new_frozen_entry(heap: &mut Heap, key: &str, value: Value) -> Self { ObjectEntry { key: PropertyKey::from_str(heap, key), value: PropertyDescriptor::roh(value), @@ -81,7 +83,7 @@ impl ObjectEntry { #[derive(Debug)] pub enum PropertyKey { - SmallAsciiString(StackString), + SmallString(SmallString), Smi(i32), String(StringIndex), Symbol(SymbolIndex), @@ -89,8 +91,8 @@ pub enum PropertyKey { 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) + if let Ok(ascii_string) = SmallString::try_from(str) { + PropertyKey::SmallString(ascii_string) } else { PropertyKey::String(heap.alloc_string(str)) } @@ -224,17 +226,11 @@ impl PropertyDescriptor { } #[derive(Debug, Clone, Copy)] -pub(crate) struct ObjectHeapData { - 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. - // We could possibly do with just a `Option` but it would cause issues - // with functions and possible other special object cases we want to track with partially - // separate heap fields later down the line. - pub(crate) prototype: Value, - pub(crate) keys: ElementsVector, - pub(crate) values: ElementsVector, +pub struct ObjectHeapData { + pub extensible: bool, + pub prototype: Option, + pub keys: ElementsVector, + pub values: ElementsVector, } impl ObjectHeapData { @@ -244,8 +240,14 @@ impl ObjectHeapData { keys: ElementsVector, values: ElementsVector, ) -> Self { + let prototype = if prototype.is_null() { + None + } else { + // TODO: Throw error. + Some(Object::try_from(prototype).unwrap()) + }; Self { - _extensible: 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 @@ -258,76 +260,59 @@ impl ObjectHeapData { values, } } + + pub fn has(&self, heap: &Heap, key: Value) -> bool { + debug_assert!(key.is_string() || key.is_number() || key.is_symbol()); + heap.elements.has(self.keys, key) + } } pub fn initialize_object_heap(heap: &mut Heap) { let entries = 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_prototype_function_entry(heap, "assign", 1, true), + ObjectEntry::new_prototype_function_entry(heap, "create", 2, false), + ObjectEntry::new_prototype_function_entry(heap, "defineProperties", 2, false), + ObjectEntry::new_prototype_function_entry(heap, "defineProperty", 3, false), + ObjectEntry::new_prototype_function_entry(heap, "entries", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "freeze", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "fromEntries", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "getOwnPropertyDescriptor", 2, false), + ObjectEntry::new_prototype_function_entry(heap, "getOwnPropertyDescriptors", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "getOwnPropertyNames", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "getOwnPropertySymbols", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "getPrototypeOf", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "hasOwn", 2, false), + ObjectEntry::new_prototype_function_entry(heap, "is", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "isExtensible", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "isFrozen", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "isSealed", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "keys", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "preventExtensions", 1, false), ObjectEntry::new_constructor_prototype_entry( heap, BuiltinObjectIndexes::ObjectPrototypeIndex.into(), ), - 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), + ObjectEntry::new_prototype_function_entry(heap, "seal", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "setPrototypeOf", 2, false), + ObjectEntry::new_prototype_function_entry(heap, "values", 1, false), ]; heap.insert_builtin_object( BuiltinObjectIndexes::ObjectConstructorIndex, true, - Value::Function(BuiltinObjectIndexes::FunctionPrototypeIndex.into()), + Some(Object::Function( + BuiltinObjectIndexes::FunctionPrototypeIndex.into(), + )), entries, ); heap.functions [get_constructor_index(BuiltinObjectIndexes::ObjectConstructorIndex).into_index()] = Some(FunctionHeapData { - object_index: BuiltinObjectIndexes::ObjectConstructorIndex.into(), + object_index: Some(BuiltinObjectIndexes::ObjectConstructorIndex.into()), length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: object_constructor_binding, + // uses_arguments: false, + // bound: None, + // visible: None, + initial_name: Value::Null, }); let entries = vec![ ObjectEntry::new( @@ -336,23 +321,17 @@ pub fn initialize_object_heap(heap: &mut Heap) { 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), + ObjectEntry::new_prototype_function_entry(heap, "hasOwnProperty", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "isPrototypeOf", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "propertyIsEnumerable", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "toLocaleString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false), ]; heap.insert_builtin_object( BuiltinObjectIndexes::ObjectConstructorIndex, true, - Value::Null, + None, entries, ); } diff --git a/nova_vm/src/heap/regexp.rs b/nova_vm/src/heap/regexp.rs index c23cd1d7..d8ba79b1 100644 --- a/nova_vm/src/heap/regexp.rs +++ b/nova_vm/src/heap/regexp.rs @@ -1,9 +1,10 @@ use crate::{ + execution::JsResult, heap::{ heap_constants::{get_constructor_index, BuiltinObjectIndexes}, Heap, PropertyDescriptor, }, - value::{JsResult, Value}, + types::{Object, Value}, }; use super::{ @@ -14,13 +15,13 @@ use super::{ }; #[derive(Debug, Clone, Copy)] -pub(crate) struct RegExpHeapData { +pub struct RegExpHeapData { pub(super) object_index: ObjectIndex, // pub(super) _regex: RegExp, } pub fn initialize_regexp_heap(heap: &mut Heap) { - let species_function_name = Value::new_string(heap, "get [Symbol.species]"); + let species_function_name = Value::from_str(heap, "get [Symbol.species]"); let entries = vec![ ObjectEntry::new_constructor_prototype_entry( heap, @@ -29,7 +30,7 @@ pub fn initialize_regexp_heap(heap: &mut Heap) { ObjectEntry::new( PropertyKey::Symbol(WellKnownSymbolIndexes::Species.into()), PropertyDescriptor::ReadOnly { - get: heap.create_function(species_function_name, 0, false, regexp_species), + get: heap.create_function(species_function_name, 0, false), enumerable: false, configurable: true, }, @@ -38,18 +39,20 @@ pub fn initialize_regexp_heap(heap: &mut Heap) { heap.insert_builtin_object( BuiltinObjectIndexes::RegExpConstructorIndex, true, - Value::Function(BuiltinObjectIndexes::FunctionPrototypeIndex.into()), + Some(Object::Function( + BuiltinObjectIndexes::FunctionPrototypeIndex.into(), + )), entries, ); heap.functions [get_constructor_index(BuiltinObjectIndexes::RegExpConstructorIndex).into_index()] = Some(FunctionHeapData { - object_index: BuiltinObjectIndexes::RegExpConstructorIndex.into(), + object_index: Some(BuiltinObjectIndexes::RegExpConstructorIndex.into()), length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: regexp_constructor_binding, + // uses_arguments: false, + // bound: None, + // visible: None, + initial_name: Value::Null, }); let entries = vec![ ObjectEntry::new( @@ -59,7 +62,7 @@ pub fn initialize_regexp_heap(heap: &mut Heap) { ))), ), // TODO: Write out all the getters - ObjectEntry::new_prototype_function_entry(heap, "exec", 1, false, regexp_todo), + ObjectEntry::new_prototype_function_entry(heap, "exec", 1, false), // TODO: These symbol function properties are actually rwxh, this helper generates roxh instead. ObjectEntry::new_prototype_symbol_function_entry( heap, @@ -67,7 +70,6 @@ pub fn initialize_regexp_heap(heap: &mut Heap) { WellKnownSymbolIndexes::Match.into(), 1, false, - regexp_todo, ), ObjectEntry::new_prototype_symbol_function_entry( heap, @@ -75,7 +77,6 @@ pub fn initialize_regexp_heap(heap: &mut Heap) { WellKnownSymbolIndexes::MatchAll.into(), 1, false, - regexp_todo, ), ObjectEntry::new_prototype_symbol_function_entry( heap, @@ -83,7 +84,6 @@ pub fn initialize_regexp_heap(heap: &mut Heap) { WellKnownSymbolIndexes::Replace.into(), 2, false, - regexp_todo, ), ObjectEntry::new_prototype_symbol_function_entry( heap, @@ -91,7 +91,6 @@ pub fn initialize_regexp_heap(heap: &mut Heap) { WellKnownSymbolIndexes::Search.into(), 1, false, - regexp_todo, ), ObjectEntry::new_prototype_symbol_function_entry( heap, @@ -99,15 +98,16 @@ pub fn initialize_regexp_heap(heap: &mut Heap) { WellKnownSymbolIndexes::Split.into(), 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), + ObjectEntry::new_prototype_function_entry(heap, "test", 1, false), + ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false), ]; heap.insert_builtin_object( BuiltinObjectIndexes::RegExpPrototypeIndex, true, - Value::Object(BuiltinObjectIndexes::ObjectPrototypeIndex.into()), + Some(Object::Object( + BuiltinObjectIndexes::ObjectPrototypeIndex.into(), + )), entries, ); } diff --git a/nova_vm/src/heap/string.rs b/nova_vm/src/heap/string.rs index 8d8a8814..58889d68 100644 --- a/nova_vm/src/heap/string.rs +++ b/nova_vm/src/heap/string.rs @@ -1,57 +1,63 @@ use crate::{ + execution::JsResult, heap::{ heap_constants::{get_constructor_index, BuiltinObjectIndexes}, FunctionHeapData, Heap, }, - value::{JsResult, Value}, + types::{Object, Value}, }; use wtf8::Wtf8Buf; #[derive(Debug, Clone)] -pub(crate) struct StringHeapData { - pub(crate) data: Wtf8Buf, +pub struct StringHeapData { + pub data: Wtf8Buf, } impl StringHeapData { + pub fn dummy() -> Self { + Self { + data: Wtf8Buf::new(), + } + } + pub fn from_str(str: &str) -> Self { StringHeapData { data: Wtf8Buf::from_str(str), } } - - pub fn len(&self) -> usize { - // TODO: We should return the UTF-16 length. - self.data.len() - } } pub fn initialize_string_heap(heap: &mut Heap) { heap.insert_builtin_object( BuiltinObjectIndexes::StringConstructorIndex, true, - Value::Function(BuiltinObjectIndexes::FunctionPrototypeIndex.into()), + Some(Object::Function( + BuiltinObjectIndexes::FunctionPrototypeIndex.into(), + )), // TODO: Methods and properties Vec::with_capacity(0), ); heap.functions [get_constructor_index(BuiltinObjectIndexes::StringConstructorIndex).into_index()] = Some(FunctionHeapData { - object_index: BuiltinObjectIndexes::StringConstructorIndex.into(), + object_index: Some(BuiltinObjectIndexes::StringConstructorIndex.into()), length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: string_constructor_binding, + // uses_arguments: false, + // bound: None, + // visible: None, + initial_name: Value::Null, }); heap.insert_builtin_object( BuiltinObjectIndexes::StringPrototypeIndex, true, - Value::Object(BuiltinObjectIndexes::ObjectPrototypeIndex.into()), + Some(Object::Object( + BuiltinObjectIndexes::ObjectPrototypeIndex.into(), + )), // TODO: Methods and properties Vec::with_capacity(0), ); } fn string_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::EmptyString) + Ok(Value::Null) } diff --git a/nova_vm/src/heap/symbol.rs b/nova_vm/src/heap/symbol.rs index 86a5c575..c6725921 100644 --- a/nova_vm/src/heap/symbol.rs +++ b/nova_vm/src/heap/symbol.rs @@ -1,9 +1,10 @@ use crate::{ + execution::JsResult, heap::{ heap_constants::{get_constructor_index, BuiltinObjectIndexes, WellKnownSymbolIndexes}, FunctionHeapData, Heap, PropertyDescriptor, }, - value::{JsResult, Value}, + types::{Object, Value}, }; use super::{ @@ -12,7 +13,7 @@ use super::{ }; #[derive(Debug, Clone, Copy)] -pub(crate) struct SymbolHeapData { +pub struct SymbolHeapData { pub(super) descriptor: Option, } @@ -75,7 +76,7 @@ pub fn initialize_symbol_heap(heap: &mut Heap) { PropertyKey::from_str(heap, "asyncIterator"), PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::AsyncIterator.into())), ), - ObjectEntry::new_prototype_function_entry(heap, "for", 1, false, symbol_todo), + ObjectEntry::new_prototype_function_entry(heap, "for", 1, false), ObjectEntry::new( PropertyKey::from_str(heap, "hasInstance"), PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::HasInstance.into())), @@ -90,7 +91,7 @@ pub fn initialize_symbol_heap(heap: &mut Heap) { PropertyKey::from_str(heap, "iterator"), PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::Iterator.into())), ), - ObjectEntry::new_prototype_function_entry(heap, "keyFor", 1, false, symbol_todo), + ObjectEntry::new_prototype_function_entry(heap, "keyFor", 1, false), ObjectEntry::new( PropertyKey::from_str(heap, "Match"), PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::Match.into())), @@ -135,18 +136,20 @@ pub fn initialize_symbol_heap(heap: &mut Heap) { heap.insert_builtin_object( BuiltinObjectIndexes::SymbolConstructorIndex, true, - Value::Function(BuiltinObjectIndexes::FunctionPrototypeIndex.into()), + Some(Object::Function( + BuiltinObjectIndexes::FunctionPrototypeIndex.into(), + )), entries, ); heap.functions [get_constructor_index(BuiltinObjectIndexes::SymbolConstructorIndex).into_index()] = Some(FunctionHeapData { - object_index: BuiltinObjectIndexes::SymbolConstructorIndex.into(), + object_index: Some(BuiltinObjectIndexes::SymbolConstructorIndex.into()), length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: symbol_constructor_binding, + // uses_arguments: false, + // bound: None, + // visible: None, + initial_name: Value::Null, }); let entries = vec![ ObjectEntry::new( @@ -164,25 +167,26 @@ pub fn initialize_symbol_heap(heap: &mut Heap) { 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_function_entry(heap, "toString", 0, false), + ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false), ObjectEntry::new_prototype_symbol_function_entry( heap, "[Symbol.toPrimitive]", WellKnownSymbolIndexes::ToPrimitive.into(), 1, false, - symbol_todo, ), ObjectEntry::new( PropertyKey::Symbol(WellKnownSymbolIndexes::ToStringTag.into()), - PropertyDescriptor::roxh(Value::new_string(heap, "Symbol")), + PropertyDescriptor::roxh(Value::from_str(heap, "Symbol")), ), ]; heap.insert_builtin_object( BuiltinObjectIndexes::SymbolPrototypeIndex, true, - Value::Object(BuiltinObjectIndexes::ObjectPrototypeIndex.into()), + Some(Object::Object( + BuiltinObjectIndexes::ObjectPrototypeIndex.into(), + )), entries, ); } 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..4f41bd91 --- /dev/null +++ b/nova_vm/src/language/bytecode/executable.rs @@ -0,0 +1,71 @@ +use super::Instruction; +use crate::{types::Value, Heap}; +use oxc_span::Atom; +use std::marker::PhantomData; + +pub type IndexType = u16; + +#[derive(Debug)] +pub struct Executable<'ctx> { + pub heap: PhantomData<&'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] = (index as IndexType).to_ne_bytes(); + 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] = (index as IndexType).to_ne_bytes(); + 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..8680cd92 --- /dev/null +++ b/nova_vm/src/language/bytecode/instructions.rs @@ -0,0 +1,199 @@ +use super::IndexType; + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +#[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..53b727f4 --- /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 = IndexType::from_ne_bytes([ + self.fetch_instruction(executable).unwrap() as u8, + self.fetch_instruction(executable).unwrap() as u8, + ]); + bytes as IndexType + } +} diff --git a/nova_vm/src/language/script.rs b/nova_vm/src/language/script.rs new file mode 100644 index 00000000..690a0106 --- /dev/null +++ b/nova_vm/src/language/script.rs @@ -0,0 +1,151 @@ +use crate::{ + execution::{ + Agent, ECMAScriptCode, EnvironmentIndex, ExecutionContext, RealmIdentifier, 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: RealmIdentifier<'ctx, 'host>, + + /// [[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: RealmIdentifier<'ctx, 'host>, + 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, agent: &mut Agent<'ctx, 'host>) -> Value { + let ecmascript_code = self.ecmascript_code.clone(); + let realm_id = self.realm; + let realm = agent.get_realm_mut(realm_id); + + // 1. Let globalEnv be scriptRecord.[[Realm]].[[GlobalEnv]]. + let global_env = realm.global_env; + + // 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: realm_id, + + // 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: EnvironmentIndex::GlobalEnvironment(global_env), + + // 7. Set the LexicalEnvironment of scriptContext to globalEnv. + lexical_environment: EnvironmentIndex::GlobalEnvironment(global_env), + + // 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.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.execution_context_stack.pop(); + + // 15. Assert: The execution context stack is not empty. + debug_assert!(agent.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 9ad9ea63..5b3357cd 100644 --- a/nova_vm/src/lib.rs +++ b/nova_vm/src/lib.rs @@ -1,467 +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}; - -use crate::heap::indexes::StringIndex; - -/// 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(StringIndex::from_u32(*iter.next().unwrap())); - } - 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.into_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..a36c5712 --- /dev/null +++ b/nova_vm/src/small_integer.rs @@ -0,0 +1,114 @@ +/// 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 const fn zero() -> SmallInteger { + Self { + data: [0, 0, 0, 0, 0, 0, 0], + } + } + + pub 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..49b02750 --- /dev/null +++ b/nova_vm/src/small_string.rs @@ -0,0 +1,105 @@ +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +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 const fn new_empty() -> Self { + Self { + bytes: [0, 0, 0, 0, 0, 0, 0], + } + } + + pub 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..3293dc72 --- /dev/null +++ b/nova_vm/src/types/language/bigint.rs @@ -0,0 +1,9 @@ +use super::value::{BIGINT_DISCRIMINANT, SMALL_BIGINT_DISCRIMINANT}; +use crate::{heap::indexes::BigIntIndex, SmallInteger}; + +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum BigInt { + BigInt(BigIntIndex) = BIGINT_DISCRIMINANT, + SmallBigInt(SmallInteger) = SMALL_BIGINT_DISCRIMINANT, +} diff --git a/nova_vm/src/types/language/function.rs b/nova_vm/src/types/language/function.rs new file mode 100644 index 00000000..bd6068fb --- /dev/null +++ b/nova_vm/src/types/language/function.rs @@ -0,0 +1,62 @@ +use super::{Object, Value}; +use crate::heap::indexes::FunctionIndex; + +/// https://tc39.es/ecma262/#function-object +#[derive(Clone, Copy)] +pub struct Function(pub FunctionIndex); + +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: FunctionIndex) -> 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..035df5dc --- /dev/null +++ b/nova_vm/src/types/language/number.rs @@ -0,0 +1,679 @@ +use super::{ + value::{FLOAT_DISCRIMINANT, INTEGER_DISCRIMINANT, NUMBER_DISCRIMINANT}, + Value, +}; +use crate::{ + execution::{Agent, JsResult}, + heap::{indexes::NumberIndex, CreateHeapData, GetHeapData}, + SmallInteger, +}; + +/// 6.1.6.1 The Number Type +/// https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type +#[derive(Clone, Copy)] +#[repr(u8)] +pub enum Number { + Number(NumberIndex) = NUMBER_DISCRIMINANT, + // 56-bit signed integer. + Integer(SmallInteger) = INTEGER_DISCRIMINANT, + Float(f32) = FLOAT_DISCRIMINANT, +} + +impl std::fmt::Debug for Number { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl From for Number { + fn from(value: SmallInteger) -> Self { + Number::Integer(value) + } +} + +impl From for Number { + fn from(value: i32) -> Self { + Number::Integer(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::Integer(SmallInteger::from_i64_unchecked(n)) + } +} + +impl From for Number { + fn from(value: f32) -> Self { + Number::Float(value) + } +} + +impl TryFrom for Number { + type Error = (); + fn try_from(value: Value) -> Result { + if matches!( + value, + Value::Number(_) | Value::Integer(_) | Value::Float(_) + ) { + // SAFETY: Sub-enum. + Ok(unsafe { std::mem::transmute::(value) }) + } else { + Err(()) + } + } +} + +impl Number { + pub fn new(value: Value) -> Self { + debug_assert!(matches!( + value, + Value::Number(_) | Value::Integer(_) | Value::Float(_) + )); + // SAFETY: Sub-enum. + unsafe { std::mem::transmute::(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 { + // SAFETY: Sub-enum. + unsafe { std::mem::transmute::(self) } + } + + pub fn is_nan(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => agent.heap.get(n).is_nan(), + Value::Integer(_) => false, + Value::Float(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.heap.get(n).is_sign_positive(), + Value::Integer(n) => 0i64 == n.into(), + Value::Float(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.heap.get(n).is_sign_negative(), + Value::Integer(_) => false, + Value::Float(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.heap.get(n) == f64::INFINITY, + Value::Integer(_) => false, + Value::Float(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.heap.get(n) == f64::NEG_INFINITY, + Value::Integer(_) => false, + Value::Float(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.heap.get(n).is_finite(), + Value::Integer(_) => true, + Value::Float(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.heap.get(n); + !n.is_sign_negative() && !n.is_sign_positive() + } + Value::Integer(_) => true, + Value::Float(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 n = agent.heap.get(n).trunc(); + agent.heap.create(n) + } + Value::Integer(_) => self, + Value::Float(n) => n.trunc().into(), + _ => unreachable!(), + } + } + + pub fn into_f64(self, agent: &Agent) -> f64 { + let x = self.into_value(); + + match x { + Value::Number(n) => *agent.heap.get(n), + Value::Integer(n) => Into::::into(n) as f64, + Value::Float(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.heap.get(x) == agent.heap.get(y), + (Value::Number(x), Value::Integer(y)) => *agent.heap.get(x) == y.into_i64() as f64, + (Value::Number(x), Value::Float(y)) => *agent.heap.get(x) == y as f64, + (Value::Integer(x), Value::Number(y)) => (x.into_i64() as f64) == *agent.heap.get(y), + (Value::Integer(x), Value::Integer(y)) => x.into_i64() == y.into_i64(), + (Value::Integer(x), Value::Float(y)) => (x.into_i64() as f64) == y as f64, + (Value::Float(x), Value::Number(y)) => (x as f64) == *agent.heap.get(y), + (Value::Float(x), Value::Integer(y)) => (x as f64) == y.into_i64() as f64, + (Value::Float(x), Value::Float(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.heap.get(n); + n % 1.0 == 0.0 && n % 2.0 == 0.0 + } + Value::Integer(n) => Into::::into(n) % 2 == 0, + Value::Float(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.heap.get(n); + if n > 0.0 { + self + } else { + agent.heap.create(-n) + } + } + Value::Integer(n) => { + let n = n.into_i64(); + Number::Integer(SmallInteger::from_i64_unchecked(n.abs())) + } + Value::Float(n) => Number::Float(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 value = *agent.heap.get(n); + agent.heap.create(-value) + } + Value::Integer(n) => SmallInteger::from_i64_unchecked(-n.into_i64()).into(), + Value::Float(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 + .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.heap.get(x) < agent.heap.get(y), + (Value::Number(x), Value::Integer(y)) => *agent.heap.get(x) < y.into_i64() as f64, + (Value::Number(x), Value::Float(y)) => *agent.heap.get(x) < y as f64, + (Value::Integer(x), Value::Number(y)) => (x.into_i64() as f64) < *agent.heap.get(y), + (Value::Integer(x), Value::Integer(y)) => x.into_i64() < y.into_i64(), + (Value::Integer(x), Value::Float(y)) => (x.into_i64() as f64) < y as f64, + (Value::Float(x), Value::Number(y)) => (x as f64) < *agent.heap.get(y), + (Value::Float(x), Value::Integer(y)) => (x as f64) < y.into_i64() as f64, + (Value::Float(x), Value::Float(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..92b985f9 --- /dev/null +++ b/nova_vm/src/types/language/object.rs @@ -0,0 +1,211 @@ +mod data; +mod internal_methods; +mod property_key; +mod property_storage; +use super::{ + value::{ARRAY_DISCRIMINANT, FUNCTION_DISCRIMINANT, OBJECT_DISCRIMINANT}, + Value, +}; +use crate::{ + builtins::ordinary, + execution::{agent::ExceptionType, Agent, JsResult}, + heap::{ + indexes::{ArrayIndex, FunctionIndex, ObjectIndex}, + GetHeapData, + }, + types::PropertyDescriptor, + Heap, +}; + +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 +/// +/// In Nova +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum Object { + Object(ObjectIndex) = OBJECT_DISCRIMINANT, + // Date(DateIndex) = DATE_DISCRIMINANT, + // Error(ErrorIndex) = ERROR_DISCRIMINANT, + Array(ArrayIndex) = ARRAY_DISCRIMINANT, + Function(FunctionIndex) = FUNCTION_DISCRIMINANT, + //RegExp(RegExpIndex) = REGEXP_DISCRIMINANT, +} + +impl From for Object { + fn from(value: ObjectIndex) -> Self { + Object::Object(value) + } +} + +impl From for Object { + fn from(value: ArrayIndex) -> Self { + Object::Array(value) + } +} + +impl From for Object { + fn from(value: FunctionIndex) -> Self { + Object::Function(value) + } +} + +impl From for Value { + fn from(value: Object) -> Self { + // SAFETY: Sub-enum. + unsafe { std::mem::transmute::(value) } + } +} + +impl TryFrom for Object { + type Error = (); + fn try_from(value: Value) -> Result { + match value { + Value::Object(x) => Ok(Object::Object(x)), + Value::Array(x) => Ok(Object::Array(x)), + Value::Function(x) => Ok(Object::Function(x)), + _ => Err(()), + } + } +} + +impl Object { + pub fn into_value(self) -> Value { + self.into() + } + + fn get_object_index(self, heap: &Heap) -> ObjectIndex { + match self { + Object::Object(index) => index, + Object::Array(array_index) => heap + .arrays + .get(array_index.into_index()) + .unwrap() + .unwrap() + .object_index + .unwrap(), + Object::Function(function_index) => heap + .functions + .get(function_index.into_index()) + .unwrap() + .as_ref() + .unwrap() + .object_index + .unwrap(), + } + } + + /// [[Extensible]] + pub fn extensible(self, agent: &mut Agent) -> bool { + let heap = &agent.heap; + let object_index = self.get_object_index(heap); + heap.get(object_index).extensible + } + + /// [[Extensible]] + pub fn set_extensible(self, agent: &mut Agent, value: bool) { + let heap = &mut agent.heap; + let object_index = self.get_object_index(heap); + let object = heap.get_mut(object_index); + object.extensible = value; + } + + /// [[Prototype]] + pub fn prototype(self, agent: &mut Agent) -> Option { + let heap = &agent.heap; + let realm = agent.current_realm(); + + match self { + Object::Object(object) => { + let object = heap.get(object); + object.prototype.map(|v| v.into_value()) + } + Object::Array(array) => { + let array = heap.get(array); + + if let Some(object_index) = array.object_index { + let prototype = heap.get(object_index).prototype; + prototype.map(|v| v.into()) + } else { + Some(realm.intrinsics.array_prototype().into_value()) + } + } + Object::Function(_) => Some(realm.intrinsics.function_prototype().into_value()), + _ => unreachable!(), + } + } + + /// [[Prototype]] + pub fn set_prototype(self, agent: &mut Agent, prototype: Option) { + let heap = &mut agent.heap; + let object_index = self.get_object_index(&heap); + let object = heap.get_mut(object_index); + object.prototype = prototype; + } + + 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). + let define_own_property = self.internal_methods(agent).define_own_property; + 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..ac8dc6e6 --- /dev/null +++ b/nova_vm/src/types/language/object/internal_methods.rs @@ -0,0 +1,93 @@ +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( + agent: &mut Agent, + 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..0748c6b1 --- /dev/null +++ b/nova_vm/src/types/language/object/property_key.rs @@ -0,0 +1,123 @@ +use crate::{ + execution::Agent, + heap::{indexes::StringIndex, GetHeapData}, + types::{String, Value}, + SmallInteger, SmallString, +}; + +#[derive(Debug, Clone, Copy)] +pub enum PropertyKey { + String(StringIndex), + SmallString(SmallString), + SmallInteger(SmallInteger), +} + +impl From for PropertyKey { + fn from(value: StringIndex) -> 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::Integer(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::Integer(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::Integer(_)) + } + + 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 s = agent.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..d1766441 --- /dev/null +++ b/nova_vm/src/types/language/object/property_storage.rs @@ -0,0 +1,99 @@ +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 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 keys = &agent.heap.get(object).keys; + // realm.heap.elements.get(keys).iter().any(|k| { + // if let Some(value) = k { + // value.equals(agent, key) + // } + // false + // }); + true + } + Value::Array(array) => { + if key.equals( + agent, + PropertyKey::from(String::try_from("length").unwrap()), + ) { + return true; + } + + let array = agent.heap.get(array); + + if key.is_array_index() { + return agent.heap.elements.has(array.elements, key.into_value()); + } + + if let Some(object) = array.object_index { + agent + .heap + .elements + .has(object.get(&agent.heap).keys, key.into_value()) + } else { + 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..1319393a --- /dev/null +++ b/nova_vm/src/types/language/string.rs @@ -0,0 +1,110 @@ +use super::Value; +use crate::{ + execution::Agent, + heap::{indexes::StringIndex, GetHeapData}, + SmallString, +}; + +/// 6.1.4 The String Type +/// https://tc39.es/ecma262/#sec-ecmascript-language-types-string-type +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u8)] +pub enum String { + String(StringIndex), + SmallString(SmallString), +} + +impl From for String { + fn from(value: StringIndex) -> 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.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.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..24c40a45 --- /dev/null +++ b/nova_vm/src/types/language/value.rs @@ -0,0 +1,781 @@ +use std::mem::size_of; + +use crate::{ + execution::{Agent, JsResult}, + heap::indexes::{ + ArrayIndex, BigIntIndex, DateIndex, ErrorIndex, FunctionIndex, NumberIndex, ObjectIndex, + RegExpIndex, StringIndex, SymbolIndex, + }, + Heap, SmallInteger, SmallString, +}; + +use super::{BigInt, Number}; + +/// 6.1 ECMAScript Language Types +/// https://tc39.es/ecma262/#sec-ecmascript-language-types +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(u8)] +pub enum Value { + /// 6.1.1 The Undefined Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-undefined-type + Undefined = 1, + + /// 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(StringIndex), + SmallString(SmallString), + + /// 6.1.5 The Symbol Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-symbol-type + Symbol(SymbolIndex), + + /// 6.1.6.1 The Number Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type + Number(NumberIndex), + Integer(SmallInteger), // 56-bit signed integer. + Float(f32), + + /// 6.1.6.2 The BigInt Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-bigint-type + BigInt(BigIntIndex), + SmallBigInt(SmallInteger), + + /// 6.1.7 The Object Type + /// https://tc39.es/ecma262/#sec-object-type + Object(ObjectIndex), + + // Well-known object types + // Roughly corresponding to 6.1.7.4 Well-Known Intrinsic Objects + // https://tc39.es/ecma262/#sec-well-known-intrinsic-objects + Array(ArrayIndex), + Date(DateIndex), + Error(ErrorIndex), + Function(FunctionIndex), + RegExp(RegExpIndex), + // TODO: Implement primitive value objects, those useless things. + // BigIntObject(u32), + // BooleanObject(u32), + // NumberObject(u32), + // StringObject(u32), + // SymbolObject(u32), +} + +/// 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 so that eg. holes in arrays do not start requiring extra bookkeeping. +const _OPTIONAL_VALUE_SIZE_IS_WORD: () = assert!(size_of::>() == size_of::()); + +impl Default for Value { + fn default() -> Self { + Value::Undefined + } +} + +#[derive(Debug, Clone, Copy)] +pub enum PreferredType { + String, + Number, +} +const fn value_discriminant(value: Value) -> u8 { + // SAFETY: Because `Self` is marked `repr(u8)`, its layout is a `repr(C)` `union` + // between `repr(C)` structs, each of which has the `u8` discriminant as its first + // field, so we can read the discriminant without offsetting the pointer. + unsafe { *(&value as *const Value).cast::() } +} + +pub(crate) const UNDEFINED_DISCRIMINANT: u8 = value_discriminant(Value::Undefined); +pub(crate) const NULL_DISCRIMINANT: u8 = value_discriminant(Value::Null); +pub(crate) const BOOLEAN_DISCRIMINANT: u8 = value_discriminant(Value::Boolean(true)); +pub(crate) const STRING_DISCRIMINANT: u8 = + value_discriminant(Value::String(StringIndex::from_u32_index(0))); +pub(crate) const SMALL_STRING_DISCRIMINANT: u8 = + value_discriminant(Value::SmallString(SmallString::new_empty())); +pub(crate) const SYMBOL_DISCRIMINANT: u8 = + value_discriminant(Value::Symbol(SymbolIndex::from_u32_index(0))); +pub(crate) const NUMBER_DISCRIMINANT: u8 = + value_discriminant(Value::Number(NumberIndex::from_u32_index(0))); +pub(crate) const INTEGER_DISCRIMINANT: u8 = + value_discriminant(Value::Integer(SmallInteger::zero())); +pub(crate) const FLOAT_DISCRIMINANT: u8 = value_discriminant(Value::Float(0f32)); +pub(crate) const BIGINT_DISCRIMINANT: u8 = + value_discriminant(Value::BigInt(BigIntIndex::from_u32_index(0))); +pub(crate) const SMALL_BIGINT_DISCRIMINANT: u8 = + value_discriminant(Value::SmallBigInt(SmallInteger::zero())); +pub(crate) const OBJECT_DISCRIMINANT: u8 = + value_discriminant(Value::Object(ObjectIndex::from_u32_index(0))); +pub(crate) const ARRAY_DISCRIMINANT: u8 = + value_discriminant(Value::Array(ArrayIndex::from_u32_index(0))); +pub(crate) const DATE_DISCRIMINANT: u8 = + value_discriminant(Value::Date(DateIndex::from_u32_index(0))); +pub(crate) const ERROR_DISCRIMINANT: u8 = + value_discriminant(Value::Error(ErrorIndex::from_u32_index(0))); +pub(crate) const FUNCTION_DISCRIMINANT: u8 = + value_discriminant(Value::Function(FunctionIndex::from_u32_index(0))); +pub(crate) const REGEXP_DISCRIMINANT: u8 = + value_discriminant(Value::RegExp(RegExpIndex::from_u32_index(0))); + +impl Value { + pub fn from_str(heap: &mut Heap, message: &str) -> Value { + if let Ok(ascii_string) = SmallString::try_from(message) { + Value::SmallString(ascii_string) + } else { + Value::String(heap.alloc_string(message)) + } + } + + pub fn from_f64(heap: &mut Heap, value: f64) -> Value { + let is_int = value.fract() == 0.0; + if is_int { + if let Ok(data) = Value::try_from(value as i64) { + return data; + } + } + if value as f32 as f64 == value { + // TODO: Verify logic + Value::Float(value as f32) + } else { + Value::Number(heap.alloc_number(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::Array(_) + | Value::Date(_) + | Value::Function(_) + | Value::Error(_) + | Value::RegExp(_) + ) + } + + 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::Float(_) | Value::Integer(_)) + } + + 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::Float(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::Float(value) + } +} + +impl TryFrom for Value { + type Error = (); + fn try_from(value: i64) -> Result { + Ok(Value::Integer(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::Integer(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..8382258b --- /dev/null +++ b/nova_vm/src/types/spec/property_descriptor.rs @@ -0,0 +1,111 @@ +use crate::{ + execution::{Agent, JsResult}, + types::{Function, 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(); + + // 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..ebaf8453 --- /dev/null +++ b/nova_vm/src/types/spec/reference.rs @@ -0,0 +1,66 @@ +use crate::{ + execution::EnvironmentIndex, + 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(EnvironmentIndex), + 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 d0f57700..00000000 --- a/nova_vm/src/value.rs +++ /dev/null @@ -1,368 +0,0 @@ -use crate::{ - heap::{ - indexes::{ - ArrayIndex, BigIntIndex, DateIndex, ErrorIndex, FunctionIndex, NumberIndex, - ObjectIndex, RegExpIndex, StringIndex, SymbolIndex, - }, - Heap, - }, - stack_string::StackString, - Type, VM, -}; -use std::{fmt::Debug, mem::size_of}; - -#[derive(Clone, Copy)] -#[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.into_index()]; - let number = &vm.heap.numbers[y.into_index()]; - 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.into_index()]; - let number = &vm.heap.numbers[x.into_index()]; - 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.into_index()].as_ref().unwrap().value() - == vm.heap.numbers[n2.into_index()].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.into_index()].as_ref().unwrap().data - == vm.heap.strings[s2.into_index()].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.into_index()].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" diff --git a/wasm/src/decoder/util.rs b/wasm/src/decoder/util.rs index 97a46718..41246025 100644 --- a/wasm/src/decoder/util.rs +++ b/wasm/src/decoder/util.rs @@ -2,7 +2,7 @@ use super::common; use crate::error::Error; use crate::error::Result; use crate::varint::decode_u32; -pub(crate) fn decode_vec(reader: &mut R, func: F) -> Result> +pub fn decode_vec(reader: &mut R, func: F) -> Result> where R: std::io::Read, F: Fn(&mut R) -> Result,