From 3cc32c04bee6f427f0f1525a8a2c859534cc5a13 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Mon, 24 Jul 2023 10:39:48 -0500 Subject: [PATCH 01/21] feat: engine setup This is greatly inspired by Kiesel and SerenetyOS's LibJS JavaScript engines. --- nova_vm/Cargo.toml | 9 +- nova_vm/src/builtins.rs | 6 + nova_vm/src/builtins/array.rs | 25 + nova_vm/src/builtins/builtin_function.rs | 96 ++++ nova_vm/src/execution.rs | 13 + nova_vm/src/execution/agent.rs | 44 ++ nova_vm/src/execution/default_host_hooks.rs | 15 + nova_vm/src/execution/environments.rs | 25 + .../environments/declarative_environment.rs | 29 ++ .../environments/function_environment.rs | 37 ++ .../environments/global_environment.rs | 23 + .../environments/object_environment.rs | 16 + .../environments/private_environment.rs | 12 + nova_vm/src/execution/execution_context.rs | 42 ++ nova_vm/src/execution/realm.rs | 31 ++ nova_vm/src/execution/realm/intrinsics.rs | 144 ++++++ nova_vm/src/heap.rs | 436 ++++------------ nova_vm/src/heap/array.rs | 177 +------ nova_vm/src/heap/bigint.rs | 139 +---- nova_vm/src/heap/boolean.rs | 56 -- nova_vm/src/heap/date.rs | 169 ------ nova_vm/src/heap/error.rs | 90 ---- nova_vm/src/heap/function.rs | 112 ---- nova_vm/src/heap/math.rs | 107 ---- nova_vm/src/heap/number.rs | 126 +---- nova_vm/src/heap/object.rs | 481 ++---------------- nova_vm/src/heap/regexp.rs | 143 ------ nova_vm/src/heap/string.rs | 48 +- nova_vm/src/heap/symbol.rs | 221 +------- nova_vm/src/language.rs | 4 + nova_vm/src/language/bytecode.rs | 7 + nova_vm/src/language/bytecode/executable.rs | 70 +++ nova_vm/src/language/bytecode/instructions.rs | 198 +++++++ nova_vm/src/language/bytecode/vm.rs | 62 +++ nova_vm/src/language/script.rs | 160 ++++++ nova_vm/src/lib.rs | 473 +---------------- nova_vm/src/small_integer.rs | 52 ++ nova_vm/src/small_string.rs | 43 ++ nova_vm/src/stack_string.rs | 95 ---- nova_vm/src/types.rs | 17 + nova_vm/src/types/language.rs | 3 + nova_vm/src/types/language/value.rs | 122 +++++ nova_vm/src/types/spec.rs | 3 + nova_vm/src/types/spec/bytecode.rs | 7 + nova_vm/src/types/spec/reference.rs | 66 +++ nova_vm/src/value.rs | 370 -------------- 46 files changed, 1557 insertions(+), 3067 deletions(-) create mode 100644 nova_vm/src/builtins.rs create mode 100644 nova_vm/src/builtins/array.rs create mode 100644 nova_vm/src/builtins/builtin_function.rs create mode 100644 nova_vm/src/execution.rs create mode 100644 nova_vm/src/execution/agent.rs create mode 100644 nova_vm/src/execution/default_host_hooks.rs create mode 100644 nova_vm/src/execution/environments.rs create mode 100644 nova_vm/src/execution/environments/declarative_environment.rs create mode 100644 nova_vm/src/execution/environments/function_environment.rs create mode 100644 nova_vm/src/execution/environments/global_environment.rs create mode 100644 nova_vm/src/execution/environments/object_environment.rs create mode 100644 nova_vm/src/execution/environments/private_environment.rs create mode 100644 nova_vm/src/execution/execution_context.rs create mode 100644 nova_vm/src/execution/realm.rs create mode 100644 nova_vm/src/execution/realm/intrinsics.rs delete mode 100644 nova_vm/src/heap/boolean.rs delete mode 100644 nova_vm/src/heap/date.rs delete mode 100644 nova_vm/src/heap/error.rs delete mode 100644 nova_vm/src/heap/function.rs delete mode 100644 nova_vm/src/heap/math.rs delete mode 100644 nova_vm/src/heap/regexp.rs create mode 100644 nova_vm/src/language.rs create mode 100644 nova_vm/src/language/bytecode.rs create mode 100644 nova_vm/src/language/bytecode/executable.rs create mode 100644 nova_vm/src/language/bytecode/instructions.rs create mode 100644 nova_vm/src/language/bytecode/vm.rs create mode 100644 nova_vm/src/language/script.rs create mode 100644 nova_vm/src/small_integer.rs create mode 100644 nova_vm/src/small_string.rs delete mode 100644 nova_vm/src/stack_string.rs create mode 100644 nova_vm/src/types.rs create mode 100644 nova_vm/src/types/language.rs create mode 100644 nova_vm/src/types/language/value.rs create mode 100644 nova_vm/src/types/spec.rs create mode 100644 nova_vm/src/types/spec/bytecode.rs create mode 100644 nova_vm/src/types/spec/reference.rs delete mode 100644 nova_vm/src/value.rs diff --git a/nova_vm/Cargo.toml b/nova_vm/Cargo.toml index bf852d5f..ee98d66c 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.0.7" +oxc_span = "0.0.7" +oxc_ast = "0.0.7" +oxc_allocator = "0.0.7" +oxc_diagnostics = "0.0.7" +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..a999825a --- /dev/null +++ b/nova_vm/src/builtins.rs @@ -0,0 +1,6 @@ +mod array; +mod builtin_function; + +pub use builtin_function::{ + create_builtin_function, ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs, +}; diff --git a/nova_vm/src/builtins/array.rs b/nova_vm/src/builtins/array.rs new file mode 100644 index 00000000..f7336d2a --- /dev/null +++ b/nova_vm/src/builtins/array.rs @@ -0,0 +1,25 @@ +use super::{create_builtin_function, ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs}; +use crate::{ + execution::{Agent, JsResult, Realm}, + types::{Object, Value}, +}; + +struct ArrayConstructor; + +impl Builtin for ArrayConstructor { + fn create(realm: &mut Realm) -> Object { + let object = create_builtin_function( + &mut realm.agent.clone().borrow_mut(), + Behaviour::Regular(Self::behaviour), + BuiltinFunctionArgs::new(1, "Array", realm), + ); + + object + } +} + +impl ArrayConstructor { + fn behaviour(agent: &mut Agent, 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..3e823cc4 --- /dev/null +++ b/nova_vm/src/builtins/builtin_function.rs @@ -0,0 +1,96 @@ +use crate::{ + execution::{Agent, JsResult, Realm}, + types::{Object, Value}, +}; + +#[derive(Debug)] +pub struct ArgumentsList; + +type RegularFn = fn(&mut Agent, Value, ArgumentsList) -> JsResult; +type ConstructorFn = fn(&mut Agent, Value, ArgumentsList, Option) -> JsResult; + +#[derive(Debug)] +pub enum Behaviour { + Regular(RegularFn), + Constructor(ConstructorFn), +} + +pub trait Builtin { + fn create(realm: &mut Realm) -> Object; +} + +#[derive(Debug, Default)] +pub struct BuiltinFunctionArgs<'a, 'ctx, 'host> { + pub length: u32, + pub name: &'static str, + pub realm: Option<&'a mut Realm<'ctx, 'host>>, + pub prototype: Option, + pub prefix: Option, +} + +impl<'a, 'ctx, 'host: 'ctx> BuiltinFunctionArgs<'a, 'ctx, 'host> { + pub fn new(length: u32, name: &'static str, realm: &'a mut Realm<'ctx, 'host>) -> Self { + Self { + length, + name, + realm: Some(realm), + ..Default::default() + } + } +} + +/// 10.3.3 CreateBuiltinFunction ( behaviour, length, name, additionalInternalSlotsList [ , realm [ , prototype [ , prefix ] ] ] ) +/// https://tc39.es/ecma262/#sec-createbuiltinfunction +pub fn create_builtin_function<'ctx, 'host: 'ctx>( + agent: &mut Agent<'ctx, 'host>, + behaviour: Behaviour, + args: BuiltinFunctionArgs<'_, 'ctx, 'host>, +) -> Object { + // 1. If realm is not present, set realm to the current Realm Record. + let realm = args.realm.unwrap(); // TODO: load record + + // 2. If prototype is not present, set prototype to realm.[[Intrinsics]].[[%Function.prototype%]]. + let prototype = args + .prototype + .unwrap_or_else(|| realm.intrinsics.function_prototype()); + + // 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. + + // 10. Perform SetFunctionLength(func, length). + + // 11. If prefix is not present, then + // a. Perform SetFunctionName(func, name). + // 12. Else, + // a. Perform SetFunctionName(func, name, prefix). + + // 13. Return func. + todo!(); +} + +pub fn define_builtin_function<'ctx, 'host: 'ctx>( + object: Object, + name: &'static str, + behaviour: RegularFn, + length: u32, + realm: &'ctx mut Realm<'ctx, 'host>, +) { + let agent_mut = realm.agent.clone(); + let mut agent = agent_mut.borrow_mut(); + + let function = create_builtin_function( + &mut agent, + Behaviour::Regular(behaviour), + BuiltinFunctionArgs::new(length, name, realm), + ); + + define_builtin_property(object, name, Value::from(function)); +} + +pub fn define_builtin_property(object: Object, name: &'static str, value: Value) {} diff --git a/nova_vm/src/execution.rs b/nova_vm/src/execution.rs new file mode 100644 index 00000000..c0362db7 --- /dev/null +++ b/nova_vm/src/execution.rs @@ -0,0 +1,13 @@ +pub mod agent; +mod default_host_hooks; +mod environments; +mod execution_context; +mod realm; + +pub use agent::{Agent, JsResult}; +pub use environments::{ + DeclarativeEnvironment, Environment, FunctionEnvironment, GlobalEnvironment, ObjectEnvironment, + PrivateEnvironment, +}; +pub use execution_context::{ECMAScriptCode, ExecutionContext, ScriptOrModule}; +pub use realm::Realm; diff --git a/nova_vm/src/execution/agent.rs b/nova_vm/src/execution/agent.rs new file mode 100644 index 00000000..e34d42c1 --- /dev/null +++ b/nova_vm/src/execution/agent.rs @@ -0,0 +1,44 @@ +use super::{ExecutionContext, Realm}; +use crate::{ + types::{Object, Symbol, Value}, + Heap, +}; +use std::collections::HashMap; + +#[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 execution_context_stack: Vec>, +} + +impl Agent<'_, '_> { + pub fn current_realm(&self) -> &mut Realm { + todo!() + } +} diff --git a/nova_vm/src/execution/default_host_hooks.rs b/nova_vm/src/execution/default_host_hooks.rs new file mode 100644 index 00000000..33916ab4 --- /dev/null +++ b/nova_vm/src/execution/default_host_hooks.rs @@ -0,0 +1,15 @@ +use super::{JsResult, Realm}; +use crate::types::Object; + +/// 19.2.1.2 HostEnsureCanCompileStrings ( calleeRealm ) +/// https://tc39.es/ecma262/#sec-hostensurecancompilestrings +pub fn host_ensure_can_compile_strings(_: &mut Realm) -> JsResult<()> { + Ok(()) +} + +/// 20.2.5 HostHasSourceTextAvailable ( func ) +/// https://tc39.es/ecma262/#sec-hosthassourcetextavailable +pub fn host_has_source_text_available(_: Object) -> bool { + // The default implementation of HostHasSourceTextAvailable is to return true. + return true; +} diff --git a/nova_vm/src/execution/environments.rs b/nova_vm/src/execution/environments.rs new file mode 100644 index 00000000..c7a2ee5c --- /dev/null +++ b/nova_vm/src/execution/environments.rs @@ -0,0 +1,25 @@ +//! 9.1 Environment Records +//! https://tc39.es/ecma262/#sec-environment-records + +pub mod declarative_environment; +pub mod function_environment; +pub mod global_environment; +pub mod object_environment; +pub mod private_environment; + +pub use declarative_environment::DeclarativeEnvironment; +pub use function_environment::FunctionEnvironment; +pub use global_environment::GlobalEnvironment; +pub use object_environment::ObjectEnvironment; +pub use private_environment::PrivateEnvironment; +use std::{cell::RefCell, rc::Rc}; + +/// 9.1.1 The Environment Record Type Hierarchy +/// https://tc39.es/ecma262/#sec-the-environment-record-type-hierarchy +#[derive(Debug)] +pub enum Environment { + DeclarativeEnvironment(Rc>), + ObjectEnvironment(Rc>), + FunctionEnvironment(Rc>), + GlobalEnvironment(Rc>), +} diff --git a/nova_vm/src/execution/environments/declarative_environment.rs b/nova_vm/src/execution/environments/declarative_environment.rs new file mode 100644 index 00000000..c1e75df0 --- /dev/null +++ b/nova_vm/src/execution/environments/declarative_environment.rs @@ -0,0 +1,29 @@ +use super::Environment; +use crate::types::Value; +use std::collections::HashMap; + +/// 9.1.1.1 Declarative Environment Records +/// https://tc39.es/ecma262/#sec-declarative-environment-records +#[derive(Debug)] +pub struct DeclarativeEnvironment { + pub outer_env: Option, + pub bindings: HashMap<&'static str, Binding>, +} + +#[derive(Debug)] +pub struct Binding { + pub value: Option, + pub strict: bool, + pub mutable: bool, + pub deletable: bool, +} + +impl DeclarativeEnvironment { + /// 9.1.1.1.1 HasBinding ( N ) + /// https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n + pub fn has_binding(self, name: &str) -> bool { + // 1. If envRec has a binding for N, return true. + // 2. Return false. + return self.bindings.contains_key(name); + } +} diff --git a/nova_vm/src/execution/environments/function_environment.rs b/nova_vm/src/execution/environments/function_environment.rs new file mode 100644 index 00000000..2347ffc3 --- /dev/null +++ b/nova_vm/src/execution/environments/function_environment.rs @@ -0,0 +1,37 @@ +use super::{DeclarativeEnvironment, Environment}; +use crate::types::Value; +use std::{cell::RefCell, rc::Rc}; + +#[derive(Debug)] +pub enum ThisBindingStatus { + Lexical, + Initialized, + Uninitialized, +} + +#[derive(Debug)] +struct ECMAScriptFunction; + +/// 9.1.1.3 Function Environment Records +/// https://tc39.es/ecma262/#sec-function-environment-records +#[derive(Debug)] +pub struct FunctionEnvironment { + /// [[ThisValue]] + this_value: Value, + + /// [[ThisBindingStatus]] + this_binding_status: ThisBindingStatus, + + /// [[FunctionObject]] + function_object: ECMAScriptFunction, + + /// [[NewTarget]] + new_target: Option, + + /// [[OuterEnv]] + outer_env: Option, + + // NOTE: This is how we implement the spec's inheritance of function + // environments. + declarative_environment: Rc>, +} diff --git a/nova_vm/src/execution/environments/global_environment.rs b/nova_vm/src/execution/environments/global_environment.rs new file mode 100644 index 00000000..4f1d07da --- /dev/null +++ b/nova_vm/src/execution/environments/global_environment.rs @@ -0,0 +1,23 @@ +use super::{DeclarativeEnvironment, Environment, ObjectEnvironment}; +use crate::types::Object; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +/// 9.1.1.4 Global Environment Records +/// https://tc39.es/ecma262/#sec-global-environment-records +#[derive(Debug)] +pub struct GlobalEnvironment { + // [[ObjectRecord]] + object_record: Rc>, + + /// [[GlobalThisValue]] + global_this_value: Object, + + /// [[DeclarativeRecord]] + declarative_record: Rc>, + + /// [[VarNames]] + var_names: HashMap<&'static str, ()>, + + /// [[OuterEnv]] + outer_env: Option, +} diff --git a/nova_vm/src/execution/environments/object_environment.rs b/nova_vm/src/execution/environments/object_environment.rs new file mode 100644 index 00000000..2200038f --- /dev/null +++ b/nova_vm/src/execution/environments/object_environment.rs @@ -0,0 +1,16 @@ +use super::Environment; +use crate::types::Object; + +/// 9.1.1.2 Object Environment Records +/// https://tc39.es/ecma262/#sec-object-environment-records +#[derive(Debug)] +pub struct ObjectEnvironment { + /// [[BindingObject]] + binding_object: Object, + + /// [[IsWithEnvironment]] + is_with_environment: bool, + + /// [[OuterEnv]] + outer_env: Option, +} diff --git a/nova_vm/src/execution/environments/private_environment.rs b/nova_vm/src/execution/environments/private_environment.rs new file mode 100644 index 00000000..9fd9b48e --- /dev/null +++ b/nova_vm/src/execution/environments/private_environment.rs @@ -0,0 +1,12 @@ +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +/// 9.2 PrivateEnvironment Records +/// https://tc39.es/ecma262/#sec-privateenvironment-records +#[derive(Debug)] +pub struct PrivateEnvironment { + /// [[OuterPrivateEnvironment]] + outer_private_environment: Option>>, + + /// [[Names]] + names: HashMap<&'static str, ()>, // TODO: Implement private names +} diff --git a/nova_vm/src/execution/execution_context.rs b/nova_vm/src/execution/execution_context.rs new file mode 100644 index 00000000..4e7e4e38 --- /dev/null +++ b/nova_vm/src/execution/execution_context.rs @@ -0,0 +1,42 @@ +use super::{Environment, PrivateEnvironment}; +use crate::{execution::Realm, language::Script, types::*}; +use std::{cell::RefCell, rc::Rc}; + +#[derive(Debug)] +pub struct Module; + +#[derive(Debug)] +pub enum ScriptOrModule<'ctx, 'host> { + Script(&'ctx mut Script<'ctx, 'host>), + Module(Rc>), +} + +#[derive(Debug)] +pub struct ECMAScriptCode { + /// LexicalEnvironment + pub lexical_environment: Environment, + + /// VariableEnvironment + pub variable_environment: Environment, + + /// PrivateEnvironment + pub private_environment: Option>>, +} + +/// 9.4 Execution Contexts +/// https://tc39.es/ecma262/#sec-execution-contexts +#[derive(Debug)] +pub struct ExecutionContext<'ctx, 'host> { + /// Function + pub function: Option, + + /// Realm + pub realm: Rc>>, + + /// ScriptOrModule + pub script_or_module: Option>, + + /// ECMAScript code execution contexts have the additional state components listed in Table 26. + /// https://tc39.es/ecma262/#ecmascript-code-execution-context + pub ecmascript_code: Option, +} diff --git a/nova_vm/src/execution/realm.rs b/nova_vm/src/execution/realm.rs new file mode 100644 index 00000000..07e037dd --- /dev/null +++ b/nova_vm/src/execution/realm.rs @@ -0,0 +1,31 @@ +mod intrinsics; + +use super::{Agent, GlobalEnvironment}; +use crate::types::Object; +use intrinsics::Intrinsics; +use std::{ + any::Any, + cell::{RefCell, RefMut}, + rc::Rc, +}; + +/// 9.3 Realms +/// https://tc39.es/ecma262/#sec-code-realms +#[derive(Debug)] +pub struct Realm<'ctx, 'host> { + pub agent: Rc>>, + + // rng: Xoroshiro128, + /// [[Intrinsics]] + pub intrinsics: Intrinsics<'ctx, 'host>, + + /// [[GlobalObject]] + pub global_object: Object, + + /// [[GlobalEnv]] + pub global_env: Rc>, + + /// [[HostDefined]] + pub host_defined: Option>>, + // TODO: [[TemplateMap]], [[LoadedModules]] +} diff --git a/nova_vm/src/execution/realm/intrinsics.rs b/nova_vm/src/execution/realm/intrinsics.rs new file mode 100644 index 00000000..9465cb20 --- /dev/null +++ b/nova_vm/src/execution/realm/intrinsics.rs @@ -0,0 +1,144 @@ +use super::Realm; +use crate::{execution::JsResult, types::Object}; +use std::{cell::RefCell, rc::Rc}; + +#[derive(Debug)] +pub struct Intrinsics<'ctx, 'host> { + pub realm: Rc>>, + + // Not stored as top-level properties so we can have methods of the same names + pub lazy_intrinsics: LazyIntrinsics, +} + +macro_rules! lazy_intrinsic { + ($name: ident $ptr: ty) => { + pub fn $name(&mut self) -> Object { + let intrinsic = &mut self.lazy_intrinsics.$name; + + if let Some(intrinsic) = intrinsic { + intrinsic + } else { + } + } + }; +} + +impl Intrinsics<'_, '_> { + pub fn function_prototype(&mut self) -> Object { + todo!() + } +} + +#[derive(Debug)] +pub struct LazyIntrinsics { + /// %Array% + pub array: Option, + + /// %Array.prototype% + pub array_prototype_prototype: Option, + + /// %BigInt% + pub big_int: Option, + + /// %BigInt.prototype% + pub big_int_prototype: Option, + + /// %Boolean% + pub boolean: Option, + + /// %Boolean.prototype% + pub boolean_prototype: Option, + + /// %Error% + pub error: Option, + + /// %Error.prototype% + pub error_prototype: Option, + + /// %eval% + pub eval: Option, + + /// %EvalError% + pub eval_error: Option, + + /// %EvalError.prototype% + pub eval_error_prototype: Option, + + /// %Function% + pub function: Option, + + /// %Function.prototype% + pub function_prototype: Option, + + /// %isFinite% + pub is_finite: Option, + + /// %isNaN% + pub is_nan: Option, + + /// %Math% + pub math: Option, + + /// %Number% + pub number: Option, + + /// %Number.prototype% + pub number_prototype: Option, + + /// %Object% + pub object: Option, + + /// %Object.prototype% + pub object_prototype: Option, + + /// %Object.prototype.toString% + pub object_prototype_to_string: Option, + + /// %RangeError% + pub range_error: Option, + + /// %RangeError.prototype% + pub range_error_prototype: Option, + + /// %ReferenceError% + pub reference_error: Option, + + /// %ReferenceError.prototype% + pub reference_error_prototype: Option, + + /// %Reflect% + pub reflect: Option, + + /// %String% + pub string: Option, + + /// %String.prototype% + pub string_prototype: Option, + + /// %Symbol% + pub symbol: Option, + + /// %Symbol.prototype% + pub symbol_prototype: Option, + + /// %SyntaxError% + pub syntax_error: Option, + + /// %SyntaxError.prototype% + pub syntax_error_prototype: Option, + + /// %ThrowTypeError% + pub throw_type_error: Option, + + /// %TypeError% + pub type_error: Option, + + /// %TypeError.prototype% + pub type_error_prototype: Option, + + /// %URIError% + pub uri_error: Option, + + /// %URIError.prototype% + pub uri_error_prototype: Option, +} diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index ec9cbd7e..47e68285 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -1,122 +1,115 @@ mod array; mod bigint; -mod boolean; -mod date; -mod error; -mod function; mod heap_constants; mod heap_trace; -mod math; mod number; mod object; -mod regexp; mod string; mod symbol; -use self::{ - array::{initialize_array_heap, ArrayHeapData}, - bigint::{initialize_bigint_heap, BigIntHeapData}, - boolean::initialize_boolean_heap, - date::{initialize_date_heap, DateHeapData}, - error::{initialize_error_heap, ErrorHeapData}, - function::{initialize_function_heap, FunctionHeapData, JsBindingFunction}, - heap_constants::{ - BuiltinObjectIndexes, FIRST_CONSTRUCTOR_INDEX, LAST_BUILTIN_OBJECT_INDEX, - LAST_WELL_KNOWN_SYMBOL_INDEX, - }, - heap_trace::HeapTrace, - math::initialize_math_object, - number::{initialize_number_heap, NumberHeapData}, - object::{ - initialize_object_heap, ObjectEntry, ObjectHeapData, PropertyDescriptor, PropertyKey, - }, - regexp::{initialize_regexp_heap, RegExpHeapData}, - string::{initialize_string_heap, StringHeapData}, - symbol::{initialize_symbol_heap, SymbolHeapData}, -}; -use crate::value::Value; -use std::cell::Cell; +pub use array::ArrayHeapData; +pub use bigint::BigIntHeapData; +pub use number::NumberHeapData; +pub use object::ObjectHeapData; +pub use string::StringHeapData; +pub use symbol::SymbolHeapData; + +use self::heap_trace::HeapTrace; +use crate::types::Value; +use std::{cell::Cell, marker::PhantomData}; use wtf8::Wtf8; +/// A handle to GC-managed memory. +#[derive(Clone)] +pub struct Handle { + id: u32, + _marker: &'static PhantomData, +} + +impl Copy for Handle {} + +impl Handle { + pub fn new(id: u32) -> Self { + Self { + id, + // SAFETY: We hopefully will make sure handles ar esafe. + _marker: unsafe { + std::mem::transmute::<&PhantomData, &'static PhantomData>( + &PhantomData::default(), + ) + }, + } + } +} + +macro_rules! impl_handle_debug { + ($name: ty) => { + impl std::fmt::Debug for Handle<$name> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "$name(0x{:x})", self.id) + } + } + }; +} + +impl_handle_debug!(StringHeapData); +impl_handle_debug!(SymbolHeapData); +impl_handle_debug!(NumberHeapData); +impl_handle_debug!(BigIntHeapData); +impl_handle_debug!(ObjectHeapData); +impl_handle_debug!(ArrayHeapData); + #[derive(Debug)] pub struct Heap { - pub(crate) arrays: Vec>, - pub(crate) bigints: Vec>, - pub(crate) errors: Vec>, - pub(crate) functions: Vec>, - pub(crate) dates: Vec>, - pub(crate) globals: Vec, - pub(crate) numbers: Vec>, - pub(crate) objects: Vec>, - pub(crate) regexps: Vec>, pub(crate) strings: Vec>, pub(crate) symbols: Vec>, + pub(crate) numbers: Vec>, + pub(crate) bigints: Vec>, + pub(crate) objects: Vec>, + pub(crate) arrays: Vec>, } fn stop_the_world() {} fn start_the_world() {} +/// Creates a [`Value`] from the given data. Allocating the data is **not** +/// guaranteed. +pub trait CreateHeapData { + fn create(&mut self, data: T) -> Value; +} + +impl CreateHeapData for Heap { + fn create(&mut self, data: f64) -> Value { + if let Ok(value) = Value::try_from(data) { + value + } else { + let id = self.alloc_number(data); + Value::Number(Handle::new(id)) + } + } +} + +impl CreateHeapData<&str> for Heap { + fn create(&mut self, data: &str) -> Value { + if let Ok(value) = Value::try_from(data) { + value + } else { + let id = self.alloc_string(data); + Value::String(Handle::new(id)) + } + } +} + impl Heap { pub fn new() -> Heap { let mut heap = Heap { - arrays: Vec::with_capacity(1024), - bigints: Vec::with_capacity(1024), - errors: Vec::with_capacity(1024), - functions: Vec::with_capacity(1024), - dates: Vec::with_capacity(1024), - globals: Vec::with_capacity(1024), - numbers: Vec::with_capacity(1024), - objects: Vec::with_capacity(1024), - regexps: Vec::with_capacity(1024), strings: Vec::with_capacity(1024), symbols: Vec::with_capacity(1024), + numbers: Vec::with_capacity(1024), + bigints: Vec::with_capacity(1024), + objects: Vec::with_capacity(1024), + arrays: Vec::with_capacity(1024), }; - for _ in 0..LAST_WELL_KNOWN_SYMBOL_INDEX + 1 { - // Initialize well known symbol slots - heap.symbols.push(None); - } - for i in 0..LAST_BUILTIN_OBJECT_INDEX + 1 { - // Initialize all static slots in heap objects. - heap.objects.push(None); - if i >= FIRST_CONSTRUCTOR_INDEX { - heap.functions.push(None); - } - } - initialize_object_heap(&mut heap); - initialize_function_heap(&mut heap); - initialize_boolean_heap(&mut heap); - initialize_symbol_heap(&mut heap); - initialize_error_heap(&mut heap); - initialize_number_heap(&mut heap); - initialize_bigint_heap(&mut heap); - initialize_math_object(&mut heap); - initialize_date_heap(&mut heap); - initialize_string_heap(&mut heap); - initialize_regexp_heap(&mut heap); - initialize_array_heap(&mut heap); - // initialize_typedarray_heap(&mut heap); - // initialize_map_heap(&mut heap); - // initialize_set_heap(&mut heap); - // initialize_weak_map_heap(&mut heap); - // initialize_weak_set_heap(&mut heap); - // initialize_array_buffer_heap(&mut heap); - // initialize_shared_array_buffer_heap(&mut heap); - // initialize_data_view_heap(&mut heap); - // initialize_json_heap(&mut heap); - // initialize_atomics_heap(&mut heap); - // initialize_weak_ref_heap(&mut heap); - // initialize_finalization_registry_heap(&mut heap); - // initialize_iterator_heap(&mut heap); - // initialize_async_iterator_heap(&mut heap); - // initialize_promise_heap(&mut heap); - // initialize_generator_function_heap(&mut heap); - // initialize_async_generator_function_heap(&mut heap); - // initialize_generator_heap(&mut heap); - // initialize_async_generator_heap(&mut heap); - // initialize_async_function_heap(&mut heap); - // initialize_reflect_heap(&mut heap); - // initialize_proxy_heap(&mut heap); - // initialize_module_heap(&mut heap); heap } @@ -145,214 +138,8 @@ impl Heap { self.numbers.len() as u32 } - pub(crate) fn create_function( - &mut self, - name: Value, - length: u8, - uses_arguments: bool, - binding: JsBindingFunction, - ) -> u32 { - let func_object_data = ObjectHeapData { - _extensible: true, - bits: HeapBits::new(), - entries: vec![ - ObjectEntry::new( - PropertyKey::from_str(self, "length"), - PropertyDescriptor::roxh(Value::SmiU(length as u32)), - ), - ObjectEntry::new( - PropertyKey::from_str(self, "name"), - PropertyDescriptor::roxh(name), - ), - ], - prototype: PropertyDescriptor::roh(Value::Object( - BuiltinObjectIndexes::FunctionPrototypeIndex as u32, - )), - }; - self.objects.push(Some(func_object_data)); - let func_data = FunctionHeapData { - binding, - bits: HeapBits::new(), - bound: None, - length, - object_index: self.objects.len() as u32, - uses_arguments, - visible: None, - }; - self.functions.push(Some(func_data)); - self.functions.len() as u32 - } - - pub(crate) fn create_object(&mut self, entries: Vec) -> u32 { - let object_data = ObjectHeapData { - _extensible: true, - bits: HeapBits::new(), - entries, - prototype: PropertyDescriptor::roh(Value::Object( - BuiltinObjectIndexes::ObjectPrototypeIndex as u32, - )), - }; - self.objects.push(Some(object_data)); - self.objects.len() as u32 - } - - pub(crate) fn create_null_object(&mut self, entries: Vec) -> u32 { - let object_data = ObjectHeapData { - _extensible: true, - bits: HeapBits::new(), - entries, - prototype: PropertyDescriptor::roh(Value::Null), - }; - self.objects.push(Some(object_data)); - self.objects.len() as u32 - } - - fn partial_trace(&mut self) -> () { - // TODO: Consider roots count - for global in self.globals.iter() { - global.trace(self); - } - for error in self.errors.iter() { - let Some(data) = error else { - continue; - }; - let marked = data.bits.marked.take(); - data.bits.marked.set(marked); - if !marked { - continue; - } - let dirty = data.bits.dirty.take(); - data.bits.dirty.set(dirty); - if dirty { - error.trace(self); - } - } - for function in self.functions.iter() { - let Some(data) = function else { - continue; - }; - let marked = data.bits.marked.take(); - data.bits.marked.set(marked); - if !marked { - continue; - } - let dirty = data.bits.dirty.take(); - data.bits.dirty.set(dirty); - if dirty { - function.trace(self); - } - } - for object in self.objects.iter() { - let Some(data) = object else { - continue; - }; - let marked = data.bits.marked.take(); - data.bits.marked.set(marked); - if !marked { - continue; - } - let dirty = data.bits.dirty.take(); - data.bits.dirty.set(dirty); - if dirty { - object.trace(self); - } - } - for symbol in self.symbols.iter() { - let Some(data) = symbol else { - continue; - }; - let marked = data.bits.marked.take(); - data.bits.marked.set(marked); - if !marked { - continue; - } - let dirty = data.bits.dirty.take(); - data.bits.dirty.set(dirty); - if dirty { - symbol.trace(self); - } - } - stop_the_world(); - // Repeat above tracing to check for mutations that happened while we were tracing. - for object in self.objects.iter_mut() { - let Some(data) = object else { - continue; - }; - let marked = data.bits.marked.replace(true); - if !marked { - let _ = object.take(); - } - } - for string in self.strings.iter_mut() { - let Some(data) = string else { - continue; - }; - let marked = data.bits.marked.replace(true); - if !marked { - let _ = string.take(); - } - } - for symbol in self.symbols.iter_mut() { - let Some(data) = symbol else { - continue; - }; - let marked = data.bits.marked.replace(true); - if !marked { - let _ = symbol.take(); - } - } - for number in self.numbers.iter_mut() { - let Some(data) = number else { - continue; - }; - let marked = data.bits.marked.replace(true); - if !marked { - let _ = number.take(); - } - } - for bigint in self.bigints.iter_mut() { - let Some(data) = bigint else { - continue; - }; - let marked = data.bits.marked.replace(true); - if !marked { - let _ = bigint.take(); - } - } - while self.objects.last().is_none() { - self.objects.pop(); - } - while self.strings.last().is_none() { - self.strings.pop(); - } - while self.symbols.last().is_none() { - self.symbols.pop(); - } - while self.numbers.last().is_none() { - self.numbers.pop(); - } - while self.bigints.last().is_none() { - self.bigints.pop(); - } - start_the_world(); - } - fn complete_trace(&mut self) -> () { // TODO: Consider roots count - for error in self.errors.iter() { - let Some(data) = error else { - continue; - }; - data.bits.marked.set(false); - data.bits.dirty.set(false); - } - for function in self.functions.iter() { - let Some(data) = function else { - continue; - }; - data.bits.marked.set(false); - data.bits.dirty.set(false); - } for object in self.objects.iter() { let Some(data) = object else { continue; @@ -388,9 +175,6 @@ impl Heap { data.bits.marked.set(false); data.bits.dirty.set(false); } - for global in self.globals.iter() { - global.trace(self); - } stop_the_world(); // Trace from dirty objects and symbols. for object in self.objects.iter_mut() { @@ -460,39 +244,36 @@ impl Heap { impl HeapTrace for Value { fn trace(&self, heap: &Heap) { match self { - &Value::Error(idx) => heap.errors[idx as usize].trace(heap), - &Value::Function(idx) => heap.functions[idx as usize].trace(heap), - &Value::HeapBigInt(idx) => heap.bigints[idx as usize].trace(heap), - &Value::HeapNumber(idx) => heap.numbers[idx as usize].trace(heap), - &Value::HeapString(idx) => heap.strings[idx as usize].trace(heap), - &Value::Object(idx) => heap.objects[idx as usize].trace(heap), - &Value::Symbol(idx) => heap.symbols[idx as usize].trace(heap), + &Value::String(handle) => heap.strings[handle.id as usize].trace(heap), + &Value::Symbol(handle) => heap.symbols[handle.id as usize].trace(heap), + &Value::Number(handle) => heap.numbers[handle.id as usize].trace(heap), + &Value::BigInt(handle) => heap.bigints[handle.id as usize].trace(heap), + &Value::Object(handle) => heap.objects[handle.id as usize].trace(heap), + &Value::ArrayObject(handle) => heap.arrays[handle.id as usize].trace(heap), _ => {} } } fn root(&self, heap: &Heap) { match self { - &Value::Error(idx) => heap.errors[idx as usize].root(heap), - &Value::Function(idx) => heap.functions[idx as usize].root(heap), - &Value::HeapBigInt(idx) => heap.bigints[idx as usize].root(heap), - &Value::HeapNumber(idx) => heap.numbers[idx as usize].root(heap), - &Value::HeapString(idx) => heap.strings[idx as usize].root(heap), - &Value::Object(idx) => heap.objects[idx as usize].root(heap), - &Value::Symbol(idx) => heap.symbols[idx as usize].root(heap), + &Value::String(handle) => heap.strings[handle.id as usize].root(heap), + &Value::Symbol(handle) => heap.symbols[handle.id as usize].root(heap), + &Value::Number(handle) => heap.numbers[handle.id as usize].root(heap), + &Value::BigInt(handle) => heap.bigints[handle.id as usize].root(heap), + &Value::Object(handle) => heap.objects[handle.id as usize].root(heap), + &Value::ArrayObject(handle) => heap.arrays[handle.id as usize].root(heap), _ => {} } } fn unroot(&self, heap: &Heap) { match self { - &Value::Error(idx) => heap.errors[idx as usize].unroot(heap), - &Value::Function(idx) => heap.functions[idx as usize].unroot(heap), - &Value::HeapBigInt(idx) => heap.bigints[idx as usize].unroot(heap), - &Value::HeapNumber(idx) => heap.numbers[idx as usize].unroot(heap), - &Value::HeapString(idx) => heap.strings[idx as usize].unroot(heap), - &Value::Object(idx) => heap.objects[idx as usize].unroot(heap), - &Value::Symbol(idx) => heap.symbols[idx as usize].unroot(heap), + &Value::String(handle) => heap.strings[handle.id as usize].unroot(heap), + &Value::Symbol(handle) => heap.symbols[handle.id as usize].unroot(heap), + &Value::Number(handle) => heap.numbers[handle.id as usize].unroot(heap), + &Value::BigInt(handle) => heap.bigints[handle.id as usize].unroot(heap), + &Value::Object(handle) => heap.objects[handle.id as usize].unroot(heap), + &Value::ArrayObject(handle) => heap.arrays[handle.id as usize].unroot(heap), _ => {} } } @@ -503,7 +284,7 @@ impl HeapTrace for Value { } // TODO: Change to using vectors of u8 bitfields for mark and dirty bits. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct HeapBits { marked: Cell, _weak_marked: Cell, @@ -539,10 +320,3 @@ impl HeapBits { } unsafe impl Sync for HeapBits {} - -#[test] -fn init_heap() { - let heap = Heap::new(); - assert!(heap.objects.len() >= LAST_BUILTIN_OBJECT_INDEX as usize); - println!("{:#?}", heap); -} diff --git a/nova_vm/src/heap/array.rs b/nova_vm/src/heap/array.rs index aff90c98..8d7366fd 100644 --- a/nova_vm/src/heap/array.rs +++ b/nova_vm/src/heap/array.rs @@ -1,20 +1,11 @@ +use super::heap_trace::HeapTrace; use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - Heap, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, + heap::{Heap, HeapBits}, + types::Value, }; -use super::{ - function::FunctionHeapData, - heap_constants::WellKnownSymbolIndexes, - heap_trace::HeapTrace, - object::{ObjectEntry, PropertyKey}, -}; - -#[derive(Debug)] -pub(crate) struct ArrayHeapData { +#[derive(Debug, Clone)] +pub struct ArrayHeapData { pub(super) bits: HeapBits, pub(super) object_index: u32, // TODO: Use SmallVec<[Value; 4]> @@ -40,161 +31,3 @@ impl HeapTrace for Option { self.take(); } } - -pub fn initialize_array_heap(heap: &mut Heap) { - let species_function_name = Value::new_string(heap, "get [Symbol.species]"); - let at_key = PropertyKey::from_str(heap, "at"); - let copy_within_key = PropertyKey::from_str(heap, "copyWithin"); - let entries_key = PropertyKey::from_str(heap, "entries"); - let fill_key = PropertyKey::from_str(heap, "fill"); - let find_key = PropertyKey::from_str(heap, "find"); - let find_index_key = PropertyKey::from_str(heap, "findIndex"); - let find_last_key = PropertyKey::from_str(heap, "findLast"); - let find_last_index_key = PropertyKey::from_str(heap, "findLastIndex"); - let flat_key = PropertyKey::from_str(heap, "flat"); - let flat_map_key = PropertyKey::from_str(heap, "flatMap"); - let includes_key = PropertyKey::from_str(heap, "includes"); - let keys_key = PropertyKey::from_str(heap, "keys"); - let to_reversed_key = PropertyKey::from_str(heap, "toReversed"); - let to_sorted_key = PropertyKey::from_str(heap, "toSorted"); - let to_spliced_key = PropertyKey::from_str(heap, "toSpliced"); - let values_key = PropertyKey::from_str(heap, "values"); - heap.objects[BuiltinObjectIndexes::ArrayConstructorIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ - ObjectEntry::new_prototype_function_entry(heap, "from", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "isArray", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "of", 0, true, array_todo), - ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::ArrayPrototypeIndex as u32, - ), - ObjectEntry::new( - PropertyKey::Symbol(WellKnownSymbolIndexes::Species as u32), - PropertyDescriptor::ReadOnly { - get: heap.create_function(species_function_name, 0, false, array_species), - enumerable: false, - configurable: true, - }, - ), - ], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::ArrayConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::ArrayConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: array_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::ArrayPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new_prototype_function_entry(heap, "at", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "concat", 1, true, array_todo), - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::ArrayConstructorIndex, - ))), - ), - ObjectEntry::new_prototype_function_entry(heap, "copyWithin", 2, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "entries", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "every", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "fill", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "filter", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "find", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "findIndex", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "findLast", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "findLastIndex", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "flat", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "flatMap", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "forEach", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "includes", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "indexOf", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "join", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "keys", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "lastIndexOf", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "map", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "pop", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "push", 1, true, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "reduce", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "reduceRight", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "reverse", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "shift", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "slice", 2, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "some", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "sort", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "splice", 2, true, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "toLocaleString", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "toReversed", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "toSorted", 1, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "toSpliced", 2, true, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "unshift", 1, true, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "values", 0, false, array_todo), - ObjectEntry::new_prototype_function_entry(heap, "with", 2, false, array_todo), - // TODO: These symbol function properties are actually rwxh, this helper generates roxh instead. - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.iterator]", - WellKnownSymbolIndexes::Iterator as u32, - 0, - false, - array_todo, - ), - ObjectEntry::new( - PropertyKey::Symbol(WellKnownSymbolIndexes::Unscopables as u32), - PropertyDescriptor::roxh(Value::Object(heap.create_object(vec![ - ObjectEntry::new(at_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new( - copy_within_key, - PropertyDescriptor::rwx(Value::Boolean(true)), - ), - ObjectEntry::new(entries_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new(fill_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new(find_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new( - find_index_key, - PropertyDescriptor::rwx(Value::Boolean(true)), - ), - ObjectEntry::new(find_last_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new( - find_last_index_key, - PropertyDescriptor::rwx(Value::Boolean(true)), - ), - ObjectEntry::new(flat_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new(flat_map_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new(includes_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new(keys_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new( - to_reversed_key, - PropertyDescriptor::rwx(Value::Boolean(true)), - ), - ObjectEntry::new(to_sorted_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ObjectEntry::new( - to_spliced_key, - PropertyDescriptor::rwx(Value::Boolean(true)), - ), - ObjectEntry::new(values_key, PropertyDescriptor::rwx(Value::Boolean(true))), - ]))), - ), - ], - )); -} - -fn array_constructor_binding(_heap: &mut Heap, _this: Value, _args: &[Value]) -> JsResult { - Ok(Value::Function(0)) -} - -fn array_species(_heap: &mut Heap, this: Value, _args: &[Value]) -> JsResult { - Ok(this) -} - -fn array_todo(_heap: &mut Heap, _this: Value, _args: &[Value]) -> JsResult { - todo!() -} diff --git a/nova_vm/src/heap/bigint.rs b/nova_vm/src/heap/bigint.rs index 5eddbb70..436c8e8d 100644 --- a/nova_vm/src/heap/bigint.rs +++ b/nova_vm/src/heap/bigint.rs @@ -1,34 +1,11 @@ -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - FunctionHeapData, Heap, HeapBits, ObjectEntry, ObjectHeapData, PropertyDescriptor, - PropertyKey, - }, - value::{JsResult, Value}, -}; - use super::heap_trace::HeapTrace; +use crate::heap::{Heap, HeapBits}; +use num_bigint_dig::BigInt; -#[derive(Debug)] -pub(crate) struct BigIntHeapData { +#[derive(Debug, Clone)] +pub struct BigIntHeapData { pub(super) bits: HeapBits, - pub(super) sign: bool, - pub(super) len: u32, - pub(super) parts: Box<[u64]>, -} - -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(super) data: BigInt, } impl HeapTrace for Option { @@ -48,109 +25,3 @@ impl HeapTrace for Option { self.take(); } } - -pub fn initialize_bigint_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::BigintConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ - ObjectEntry::new_prototype_function_entry( - heap, - "asIntN", - 2, - false, - bigint_as_int_n, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "asUintN", - 2, - false, - bigint_as_uint_n, - ), - ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::BigintPrototypeIndex as u32, - ), - ], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::BigintConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: heap.objects.len() as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: bigint_constructor, - }); - heap.objects[BuiltinObjectIndexes::BigintPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::BigintConstructorIndex, - ))), - ), - ObjectEntry::new_prototype_function_entry( - heap, - "toLocaleString", - 0, - false, - bigint_prototype_to_locale_string, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "toString", - 0, - false, - bigint_prototype_to_string, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "valueOf", - 0, - false, - bigint_prototype_value_of, - ), - // @@ToStringTag - // ObjectEntry { key: PropertyKey::Symbol(), PropertyDescriptor } - ], - )); -} - -fn bigint_constructor(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult { - if !this.is_undefined() { - // TODO: Throw TypeError - return Err(Value::Error(0)); - } else { - return Ok(Value::SmallBigInt(3)); - } -} - -fn bigint_as_int_n(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::SmallBigInt(3)) -} - -fn bigint_as_uint_n(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult { - Ok(Value::SmallBigIntU(3)) -} - -fn bigint_prototype_to_locale_string( - heap: &mut Heap, - this: Value, - args: &[Value], -) -> JsResult { - Ok(Value::new_string(heap, "BigInt(3n)")) -} - -fn bigint_prototype_to_string(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult { - Ok(Value::new_string(heap, "BigInt(3n)")) -} - -fn bigint_prototype_value_of(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult { - Ok(Value::new_string(heap, "BigInt(3n)")) -} diff --git a/nova_vm/src/heap/boolean.rs b/nova_vm/src/heap/boolean.rs deleted file mode 100644 index e4e11d94..00000000 --- a/nova_vm/src/heap/boolean.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - FunctionHeapData, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, -}; - -use super::{ - object::{ObjectEntry, PropertyKey}, - Heap, -}; - -pub fn initialize_boolean_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::BooleanConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::BooleanPrototypeIndex as u32, - )], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::BooleanConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::BooleanConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: boolean_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::BooleanPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::BooleanConstructorIndex, - ))), - ), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, boolean_todo), - ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false, boolean_todo), - ], - )); -} - -fn boolean_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::Boolean(false)) -} - -fn boolean_todo(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - todo!(); -} diff --git a/nova_vm/src/heap/date.rs b/nova_vm/src/heap/date.rs deleted file mode 100644 index e89545f6..00000000 --- a/nova_vm/src/heap/date.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::time::SystemTime; - -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - Heap, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, -}; - -use super::{ - function::FunctionHeapData, - heap_constants::WellKnownSymbolIndexes, - heap_trace::HeapTrace, - object::{ObjectEntry, PropertyKey}, -}; - -#[derive(Debug)] -pub(crate) struct DateHeapData { - pub(super) bits: HeapBits, - pub(super) object_index: u32, - pub(super) _date: SystemTime, -} - -impl HeapTrace for Option { - fn trace(&self, heap: &Heap) { - assert!(self.is_some()); - heap.objects[self.as_ref().unwrap().object_index as usize].trace(heap); - } - fn root(&self, _heap: &Heap) { - assert!(self.is_some()); - self.as_ref().unwrap().bits.root(); - } - - fn unroot(&self, _heap: &Heap) { - assert!(self.is_some()); - self.as_ref().unwrap().bits.unroot(); - } - - fn finalize(&mut self, _heap: &Heap) { - self.take(); - } -} - -pub fn initialize_date_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::DateConstructorIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ - ObjectEntry::new_prototype_function_entry(heap, "now", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "parse", 1, false, date_todo), - ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::DatePrototypeIndex as u32, - ), - ObjectEntry::new_prototype_function_entry(heap, "UTC", 7, false, date_todo), - ], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::DateConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::DateConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: date_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::DatePrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::DateConstructorIndex, - ))), - ), - ObjectEntry::new_prototype_function_entry(heap, "getDate", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getDay", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getFullYear", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getHours", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getMilliseconds", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getMinutes", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getMonth", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getSeconds", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getTime", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "getTimezoneOffset", - 0, - false, - date_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "getUTCDate", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getUTCDay", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getUTCFullYear", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getUTCHours", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "getUTCMilliseconds", - 0, - false, - date_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "getUTCMinutes", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getUTCMonth", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "getUTCSeconds", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setDate", 1, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setFullYear", 3, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setHours", 4, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setMilliseconds", 1, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setMinutes", 3, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setMonth", 2, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setSeconds", 2, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setTime", 1, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setUTCDate", 1, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setUTCFullYear", 3, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setUTCHours", 4, false, date_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "setUTCMilliseconds", - 1, - false, - date_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "setUTCMinutes", 3, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setUTCMonth", 2, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "setUTCSeconds", 2, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "toDateString", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "toJSON", 1, false, date_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "toLocaleDateString", - 0, - false, - date_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "toLocaleString", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "toLocaleTimeString", - 0, - false, - date_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "toTimeString", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "toUTCString", 0, false, date_todo), - ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false, date_todo), - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.toPrimitive]", - WellKnownSymbolIndexes::ToPrimitive as u32, - 1, - false, - date_todo, - ), - ], - )); -} - -fn date_constructor_binding(_heap: &mut Heap, _this: Value, _args: &[Value]) -> JsResult { - Ok(Value::Function(0)) -} - -fn date_todo(_heap: &mut Heap, _this: Value, _args: &[Value]) -> JsResult { - todo!() -} diff --git a/nova_vm/src/heap/error.rs b/nova_vm/src/heap/error.rs deleted file mode 100644 index c4027107..00000000 --- a/nova_vm/src/heap/error.rs +++ /dev/null @@ -1,90 +0,0 @@ -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - Heap, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, -}; - -use super::{ - function::FunctionHeapData, - heap_trace::HeapTrace, - object::{ObjectEntry, PropertyKey}, -}; - -#[derive(Debug)] -pub(crate) struct ErrorHeapData { - pub(super) bits: HeapBits, - pub(super) object_index: u32, - // TODO: stack? name? -} - -impl HeapTrace for Option { - fn trace(&self, heap: &Heap) { - assert!(self.is_some()); - heap.objects[self.as_ref().unwrap().object_index as usize].trace(heap); - } - fn root(&self, _heap: &Heap) { - assert!(self.is_some()); - self.as_ref().unwrap().bits.root(); - } - - fn unroot(&self, _heap: &Heap) { - assert!(self.is_some()); - self.as_ref().unwrap().bits.unroot(); - } - - fn finalize(&mut self, _heap: &Heap) { - self.take(); - } -} - -pub fn initialize_error_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::ErrorConstructorIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::ErrorPrototypeIndex as u32, - )], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::ErrorConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::ErrorConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: error_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::ErrorPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::ErrorConstructorIndex, - ))), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "name"), - PropertyDescriptor::rwx(Value::EmptyString), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "name"), - PropertyDescriptor::rwx(Value::new_string(heap, "Error")), - ), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, error_todo), - ], - )); -} - -fn error_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::Function(0)) -} - -fn error_todo(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - todo!() -} diff --git a/nova_vm/src/heap/function.rs b/nova_vm/src/heap/function.rs deleted file mode 100644 index 851feb3d..00000000 --- a/nova_vm/src/heap/function.rs +++ /dev/null @@ -1,112 +0,0 @@ -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - Heap, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, -}; - -use super::{ - heap_constants::WellKnownSymbolIndexes, - heap_trace::HeapTrace, - object::{ObjectEntry, PropertyKey}, -}; - -pub type JsBindingFunction = fn(heap: &mut Heap, this: Value, args: &[Value]) -> JsResult; - -#[derive(Debug)] -pub(crate) struct FunctionHeapData { - pub(super) bits: HeapBits, - pub(super) object_index: u32, - pub(super) length: u8, - pub(super) uses_arguments: bool, - pub(super) bound: Option>, - pub(super) visible: Option>, - pub(super) binding: JsBindingFunction, - // TODO: Should name be here as an "internal slot" of sorts? -} - -impl HeapTrace for Option { - fn trace(&self, heap: &Heap) { - assert!(self.is_some()); - heap.objects[self.as_ref().unwrap().object_index as usize].trace(heap); - } - fn root(&self, _heap: &Heap) { - assert!(self.is_some()); - self.as_ref().unwrap().bits.root(); - } - - fn unroot(&self, _heap: &Heap) { - assert!(self.is_some()); - self.as_ref().unwrap().bits.unroot(); - } - - fn finalize(&mut self, _heap: &Heap) { - self.take(); - } -} - -pub fn initialize_function_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::FunctionConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::FunctionPrototypeIndex as u32, - )], - )); - heap.functions - [get_constructor_index(BuiltinObjectIndexes::FunctionConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::FunctionConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: function_constructor_binding, - }); - // NOTE: According to ECMAScript spec https://tc39.es/ecma262/#sec-properties-of-the-function-prototype-object - // the %Function.prototype% object should itself be a function that always returns undefined. This is not - // upheld here and we probably do not care. It's seemingly the only prototype that is a function. - heap.objects[BuiltinObjectIndexes::FunctionPrototypeIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new_prototype_function_entry(heap, "apply", 2, false, function_todo), - ObjectEntry::new_prototype_function_entry(heap, "bind", 1, true, function_todo), - ObjectEntry::new_prototype_function_entry(heap, "call", 1, true, function_todo), - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::FunctionConstructorIndex, - ))), - ), - ObjectEntry::new_prototype_function_entry( - heap, - "toString", - 0, - false, - function_todo, - ), - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "hasInstance", - WellKnownSymbolIndexes::HasInstance as u32, - 1, - false, - function_todo, - ), - ], - )); -} - -fn function_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::Function(0)) -} - -fn function_todo(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - todo!() -} diff --git a/nova_vm/src/heap/math.rs b/nova_vm/src/heap/math.rs deleted file mode 100644 index 39e1c750..00000000 --- a/nova_vm/src/heap/math.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::value::{JsResult, Value}; - -use super::{ - heap_constants::WellKnownSymbolIndexes, - object::{ObjectEntry, PropertyDescriptor, PropertyKey}, - Heap, -}; - -pub(super) fn initialize_math_object(heap: &mut Heap) { - let e = Value::from_f64(heap, std::f64::consts::E); - let ln10 = Value::from_f64(heap, std::f64::consts::LN_10); - let ln2 = Value::from_f64(heap, std::f64::consts::LN_2); - let log10e = Value::from_f64(heap, std::f64::consts::LOG10_E); - let log2e = Value::from_f64(heap, std::f64::consts::LOG2_E); - let pi = Value::from_f64(heap, std::f64::consts::PI); - let sqrt1_2 = Value::from_f64(heap, std::f64::consts::FRAC_1_SQRT_2); - let sqrt2 = Value::from_f64(heap, std::f64::consts::SQRT_2); - let abs = ObjectEntry::new_prototype_function_entry(heap, "abs", 1, false, math_todo); - let acos = ObjectEntry::new_prototype_function_entry(heap, "acos", 1, false, math_todo); - let acosh = ObjectEntry::new_prototype_function_entry(heap, "acosh", 1, false, math_todo); - let asin = ObjectEntry::new_prototype_function_entry(heap, "asin", 1, false, math_todo); - let asinh = ObjectEntry::new_prototype_function_entry(heap, "asinh", 1, false, math_todo); - let atan = ObjectEntry::new_prototype_function_entry(heap, "atan", 1, false, math_todo); - let atanh = ObjectEntry::new_prototype_function_entry(heap, "atanh", 1, false, math_todo); - let atan2 = ObjectEntry::new_prototype_function_entry(heap, "atan2", 2, false, math_todo); - let cbrt = ObjectEntry::new_prototype_function_entry(heap, "cbrt", 1, false, math_todo); - let ceil = ObjectEntry::new_prototype_function_entry(heap, "ceil", 1, false, math_todo); - let clz32 = ObjectEntry::new_prototype_function_entry(heap, "clz32", 1, false, math_todo); - let cos = ObjectEntry::new_prototype_function_entry(heap, "cos", 1, false, math_todo); - let cosh = ObjectEntry::new_prototype_function_entry(heap, "cosh", 1, false, math_todo); - let exp = ObjectEntry::new_prototype_function_entry(heap, "exp", 1, false, math_todo); - let expm1 = ObjectEntry::new_prototype_function_entry(heap, "expm1", 1, false, math_todo); - let floor = ObjectEntry::new_prototype_function_entry(heap, "floor", 1, false, math_todo); - let fround = ObjectEntry::new_prototype_function_entry(heap, "fround", 1, false, math_todo); - let hypot = ObjectEntry::new_prototype_function_entry(heap, "hypot", 2, true, math_todo); - let imul = ObjectEntry::new_prototype_function_entry(heap, "imul", 2, false, math_todo); - let log = ObjectEntry::new_prototype_function_entry(heap, "log", 1, false, math_todo); - let log1p = ObjectEntry::new_prototype_function_entry(heap, "log1p", 1, false, math_todo); - let log10 = ObjectEntry::new_prototype_function_entry(heap, "log10", 1, false, math_todo); - let log2 = ObjectEntry::new_prototype_function_entry(heap, "log2", 1, false, math_todo); - let max = ObjectEntry::new_prototype_function_entry(heap, "max", 2, true, math_todo); - let min = ObjectEntry::new_prototype_function_entry(heap, "min", 2, true, math_todo); - let pow = ObjectEntry::new_prototype_function_entry(heap, "pow", 2, false, math_todo); - let random = ObjectEntry::new_prototype_function_entry(heap, "random", 0, false, math_todo); - let round = ObjectEntry::new_prototype_function_entry(heap, "round", 1, false, math_todo); - let sign = ObjectEntry::new_prototype_function_entry(heap, "sign", 1, false, math_todo); - let sin = ObjectEntry::new_prototype_function_entry(heap, "sin", 1, false, math_todo); - let sinh = ObjectEntry::new_prototype_function_entry(heap, "sinh", 1, false, math_todo); - let sqrt = ObjectEntry::new_prototype_function_entry(heap, "sqrt", 1, false, math_todo); - let tan = ObjectEntry::new_prototype_function_entry(heap, "tan", 1, false, math_todo); - let tanh = ObjectEntry::new_prototype_function_entry(heap, "tanh", 1, false, math_todo); - let trunc = ObjectEntry::new_prototype_function_entry(heap, "trunc", 1, false, math_todo); - let entries = vec![ - ObjectEntry::new_frozen_entry(heap, "E", e), - ObjectEntry::new_frozen_entry(heap, "LN10", ln10), - ObjectEntry::new_frozen_entry(heap, "LN2", ln2), - ObjectEntry::new_frozen_entry(heap, "LOG10E", log10e), - ObjectEntry::new_frozen_entry(heap, "LOG2E", log2e), - ObjectEntry::new_frozen_entry(heap, "PI", pi), - ObjectEntry::new_frozen_entry(heap, "SQRT1_2", sqrt1_2), - ObjectEntry::new_frozen_entry(heap, "SQRT2", sqrt2), - ObjectEntry::new( - PropertyKey::Symbol(WellKnownSymbolIndexes::ToStringTag as u32), - PropertyDescriptor::roxh(Value::new_string(heap, "Math")), - ), - abs, - acos, - acosh, - asin, - asinh, - atan, - atanh, - atan2, - cbrt, - ceil, - clz32, - cos, - cosh, - exp, - expm1, - floor, - fround, - hypot, - imul, - log, - log1p, - log10, - log2, - max, - min, - pow, - random, - round, - sign, - sin, - sinh, - sqrt, - tan, - tanh, - trunc, - ]; - let _ = heap.create_object(entries); -} - -fn math_todo(_heap: &mut Heap, _this: Value, _args: &[Value]) -> JsResult { - todo!(); -} diff --git a/nova_vm/src/heap/number.rs b/nova_vm/src/heap/number.rs index 11305c6b..c3290d1d 100644 --- a/nova_vm/src/heap/number.rs +++ b/nova_vm/src/heap/number.rs @@ -1,20 +1,8 @@ -use std::vec; +use super::{heap_trace::HeapTrace, Heap}; +use crate::heap::HeapBits; -use super::{ - heap_trace::HeapTrace, - object::{ObjectEntry, PropertyKey}, - Heap, -}; -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - FunctionHeapData, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, -}; - -#[derive(Debug)] -pub(crate) struct NumberHeapData { +#[derive(Debug, Clone)] +pub struct NumberHeapData { pub(super) bits: HeapBits, pub(super) data: f64, } @@ -49,109 +37,3 @@ impl HeapTrace for Option { self.take(); } } - -pub fn initialize_number_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::NumberConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "EPSILON"), - PropertyDescriptor::roh(Value::from_f64(heap, f64::EPSILON)), - ), - ObjectEntry::new_prototype_function_entry(heap, "isFinite", 1, false, number_todo), - ObjectEntry::new_prototype_function_entry(heap, "isInteger", 1, false, number_todo), - ObjectEntry::new_prototype_function_entry(heap, "isNan", 1, false, number_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "isSafeInteger", - 1, - false, - number_todo, - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "MAX_SAFE_INTEGER"), - PropertyDescriptor::roh(Value::from_f64(heap, 9007199254740991.0)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "MAX_VALUE"), - PropertyDescriptor::roh(Value::from_f64(heap, f64::MAX)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "MIN_SAFE_INTEGER"), - PropertyDescriptor::roh(Value::from_f64(heap, -9007199254740991.0)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "MIN_VALUE"), - PropertyDescriptor::roh(Value::from_f64(heap, f64::MIN)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "NaN"), - PropertyDescriptor::roh(Value::NaN), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "NEGATIVE_INFINITY"), - PropertyDescriptor::roh(Value::NegativeInfinity), - ), - ObjectEntry::new_prototype_function_entry( - heap, - "parseFloat", - 1, - false, - number_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "parseInt", 2, false, number_todo), - ObjectEntry::new( - PropertyKey::from_str(heap, "POSITIVE_INFINITY"), - PropertyDescriptor::roh(Value::Infinity), - ), - ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::NumberPrototypeIndex as u32, - ), - ], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::NumberConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::NumberConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: number_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::NumberPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::NumberConstructorIndex, - ))), - ), - ObjectEntry::new_prototype_function_entry(heap, "toExponential", 1, false, number_todo), - ObjectEntry::new_prototype_function_entry(heap, "toExponential", 1, false, number_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "toLocaleString", - 0, - false, - number_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "toPrecision", 1, false, number_todo), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, number_todo), - ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false, number_todo), - ], - )); -} - -fn number_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::SmiU(0)) -} - -fn number_todo(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - todo!(); -} diff --git a/nova_vm/src/heap/object.rs b/nova_vm/src/heap/object.rs index 6bdbd8d0..afedb544 100644 --- a/nova_vm/src/heap/object.rs +++ b/nova_vm/src/heap/object.rs @@ -1,226 +1,7 @@ -use crate::{ - heap::{ - function::JsBindingFunction, - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - heap_trace::HeapTrace, - FunctionHeapData, Heap, HeapBits, - }, - stack_string::StackString, - value::{FunctionIndex, JsResult, StringIndex, SymbolIndex, Value}, -}; -use std::fmt::Debug; +use crate::heap::{heap_trace::HeapTrace, Heap, HeapBits}; -#[derive(Debug)] -pub struct ObjectEntry { - key: PropertyKey, - value: PropertyDescriptor, -} - -impl ObjectEntry { - pub(crate) fn new(key: PropertyKey, value: PropertyDescriptor) -> Self { - ObjectEntry { key, value } - } - - pub(crate) fn new_prototype_function_entry( - heap: &mut Heap, - name: &str, - length: u8, - uses_arguments: bool, - binding: JsBindingFunction, - ) -> Self { - let key = PropertyKey::from_str(heap, name); - let name = match key { - PropertyKey::SmallAsciiString(data) => Value::StackString(data.clone()), - PropertyKey::Smi(_) => unreachable!("No prototype functions should have SMI names"), - PropertyKey::String(idx) => Value::HeapString(idx), - PropertyKey::Symbol(idx) => Value::Symbol(idx), - }; - let func_index = heap.create_function(name, length, uses_arguments, binding); - let value = PropertyDescriptor::rwxh(Value::Function(func_index)); - ObjectEntry { key, value } - } - - pub(crate) fn new_prototype_symbol_function_entry( - heap: &mut Heap, - name: &str, - symbol_index: u32, - length: u8, - uses_arguments: bool, - binding: JsBindingFunction, - ) -> Self { - let name = Value::new_string(heap, name); - let key = PropertyKey::Symbol(symbol_index); - let func_index = heap.create_function(name, length, uses_arguments, binding); - let value = PropertyDescriptor::roxh(Value::Function(func_index)); - ObjectEntry { key, value } - } - - pub(crate) fn new_constructor_prototype_entry(heap: &mut Heap, idx: u32) -> Self { - ObjectEntry { - key: PropertyKey::from_str(heap, "prototype"), - value: PropertyDescriptor::Data { - value: Value::Object(idx), - writable: false, - enumerable: false, - configurable: false, - }, - } - } - - pub(crate) fn new_frozen_entry(heap: &mut Heap, key: &str, value: Value) -> Self { - ObjectEntry { - key: PropertyKey::from_str(heap, key), - value: PropertyDescriptor::roh(value), - } - } -} - -#[derive(Debug)] -pub enum PropertyKey { - SmallAsciiString(StackString), - Smi(i32), - String(StringIndex), - Symbol(SymbolIndex), -} - -impl PropertyKey { - pub fn from_str(heap: &mut Heap, str: &str) -> Self { - if let Some(ascii_string) = StackString::try_from_str(str) { - PropertyKey::SmallAsciiString(ascii_string) - } else { - PropertyKey::String(heap.alloc_string(str)) - } - } -} - -#[derive(Debug)] -pub enum PropertyDescriptor { - Data { - value: Value, - writable: bool, - enumerable: bool, - configurable: bool, - }, - Blocked { - enumerable: bool, - configurable: bool, - }, - ReadOnly { - get: FunctionIndex, - enumerable: bool, - configurable: bool, - }, - WriteOnly { - set: FunctionIndex, - enumerable: bool, - configurable: bool, - }, - ReadWrite { - get: FunctionIndex, - set: FunctionIndex, - enumerable: bool, - configurable: bool, - }, -} - -impl PropertyDescriptor { - #[inline(always)] - pub const fn prototype_slot(idx: u32) -> Self { - Self::Data { - value: Value::Object(idx), - writable: false, - enumerable: false, - configurable: false, - } - } - - #[inline(always)] - /// Read-only, unconfigurable, enumerable data descriptor - pub const fn ro(value: Value) -> Self { - Self::Data { - value, - writable: false, - enumerable: true, - configurable: false, - } - } - #[inline(always)] - /// Read-only, unconfigurable, unenumerable data descriptor - pub const fn roh(value: Value) -> Self { - Self::Data { - value, - writable: false, - enumerable: false, - configurable: false, - } - } - - #[inline(always)] - /// Read-only, configurable, enumerable data descriptor - pub const fn rox(value: Value) -> Self { - Self::Data { - value, - writable: false, - enumerable: true, - configurable: true, - } - } - #[inline(always)] - /// Read-only, configurable, unenumerable data descriptor - pub const fn roxh(value: Value) -> Self { - Self::Data { - value, - writable: false, - enumerable: false, - configurable: true, - } - } - - #[inline(always)] - /// Writable, unconfigurable, enumerable data descriptor - pub const fn rw(value: Value) -> Self { - Self::Data { - value, - writable: true, - enumerable: false, - configurable: false, - } - } - #[inline(always)] - /// Writable, unconfigurable, unenumerable data descriptor - pub const fn rwh(value: Value) -> Self { - Self::Data { - value, - writable: true, - enumerable: false, - configurable: false, - } - } - - #[inline(always)] - /// Writable, configurable, enumerable data descriptor - pub const fn rwx(value: Value) -> Self { - Self::Data { - value, - writable: true, - enumerable: false, - configurable: true, - } - } - #[inline(always)] - /// Writable, configurable, unenumerable data descriptor - pub const fn rwxh(value: Value) -> Self { - Self::Data { - value, - writable: true, - enumerable: false, - configurable: true, - } - } -} - -#[derive(Debug)] -pub(crate) struct ObjectHeapData { +#[derive(Debug, Clone)] +pub struct ObjectHeapData { pub(crate) bits: HeapBits, pub(crate) _extensible: bool, // TODO: It's probably not necessary to have a whole data descriptor here. @@ -229,27 +10,8 @@ pub(crate) struct ObjectHeapData { // 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: PropertyDescriptor, - pub(crate) entries: Vec, -} - -impl ObjectHeapData { - pub fn new(extensible: bool, prototype: PropertyDescriptor, entries: Vec) -> Self { - Self { - bits: HeapBits::new(), - _extensible: extensible, - // TODO: Number, Boolean, etc. objects exist. These can all be - // modeled with their own heap vector or alternatively by adding - // a [[PrimitiveValue]] field to objects: Normally this field is None - // to signal that the object is its own primitive value. For - // Number objects etc the field is Some(Value). - // TODO: Move prototype and key vector into shapes - prototype, - // TODO: Separate entries into key and value vectors - // TODO: Use SmallVec<[T; 4]> - entries, - } - } + // pub(crate) prototype: PropertyDescriptor, + // pub(crate) entries: Vec, } impl HeapTrace for Option { @@ -262,41 +24,41 @@ impl HeapTrace for Option { // Do not keep recursing into already-marked heap values. return; } - match &data.prototype { - PropertyDescriptor::Data { value, .. } => value.trace(heap), - PropertyDescriptor::Blocked { .. } => {} - PropertyDescriptor::ReadOnly { get, .. } => { - heap.functions[*get as usize].trace(heap); - } - PropertyDescriptor::WriteOnly { set, .. } => { - heap.functions[*set as usize].trace(heap); - } - PropertyDescriptor::ReadWrite { get, set, .. } => { - heap.functions[*get as usize].trace(heap); - heap.functions[*set as usize].trace(heap); - } - } - for reference in data.entries.iter() { - match reference.key { - PropertyKey::SmallAsciiString(_) | PropertyKey::Smi(_) => {} - PropertyKey::String(idx) => heap.strings[idx as usize].trace(heap), - PropertyKey::Symbol(idx) => heap.symbols[idx as usize].trace(heap), - } - match &reference.value { - PropertyDescriptor::Data { value, .. } => value.trace(heap), - PropertyDescriptor::Blocked { .. } => {} - PropertyDescriptor::ReadOnly { get, .. } => { - heap.functions[*get as usize].trace(heap); - } - PropertyDescriptor::WriteOnly { set, .. } => { - heap.functions[*set as usize].trace(heap); - } - PropertyDescriptor::ReadWrite { get, set, .. } => { - heap.functions[*get as usize].trace(heap); - heap.functions[*set as usize].trace(heap); - } - } - } + // match &data.prototype { + // PropertyDescriptor::Data { value, .. } => value.trace(heap), + // PropertyDescriptor::Blocked { .. } => {} + // PropertyDescriptor::ReadOnly { get, .. } => { + // heap.functions[*get as usize].trace(heap); + // } + // PropertyDescriptor::WriteOnly { set, .. } => { + // heap.functions[*set as usize].trace(heap); + // } + // PropertyDescriptor::ReadWrite { get, set, .. } => { + // heap.functions[*get as usize].trace(heap); + // heap.functions[*set as usize].trace(heap); + // } + // } + // for reference in data.entries.iter() { + // match reference.key { + // PropertyKey::SmallAsciiString(_) | PropertyKey::Smi(_) => {} + // PropertyKey::String(idx) => heap.strings[idx as usize].trace(heap), + // PropertyKey::Symbol(idx) => heap.symbols[idx as usize].trace(heap), + // } + // match &reference.value { + // PropertyDescriptor::Data { value, .. } => value.trace(heap), + // PropertyDescriptor::Blocked { .. } => {} + // PropertyDescriptor::ReadOnly { get, .. } => { + // heap.functions[*get as usize].trace(heap); + // } + // PropertyDescriptor::WriteOnly { set, .. } => { + // heap.functions[*set as usize].trace(heap); + // } + // PropertyDescriptor::ReadWrite { get, set, .. } => { + // heap.functions[*get as usize].trace(heap); + // heap.functions[*set as usize].trace(heap); + // } + // } + // } } fn root(&self, _heap: &Heap) { @@ -313,166 +75,3 @@ impl HeapTrace for Option { self.take(); } } - -pub fn initialize_object_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::ObjectConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ - ObjectEntry::new_prototype_function_entry(heap, "assign", 1, true, object_todo), - ObjectEntry::new_prototype_function_entry(heap, "create", 2, false, object_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "defineProperties", - 2, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "defineProperty", - 3, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "entries", 1, false, object_todo), - ObjectEntry::new_prototype_function_entry(heap, "freeze", 1, false, object_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "fromEntries", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "getOwnPropertyDescriptor", - 2, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "getOwnPropertyDescriptors", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "getOwnPropertyNames", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "getOwnPropertySymbols", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "getPrototypeOf", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "hasOwn", 2, false, object_todo), - ObjectEntry::new_prototype_function_entry(heap, "is", 1, false, object_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "isExtensible", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "isFrozen", 1, false, object_todo), - ObjectEntry::new_prototype_function_entry(heap, "isSealed", 1, false, object_todo), - ObjectEntry::new_prototype_function_entry(heap, "keys", 1, false, object_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "preventExtensions", - 1, - false, - object_todo, - ), - ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::ObjectPrototypeIndex as u32, - ), - ObjectEntry::new_prototype_function_entry(heap, "seal", 1, false, object_todo), - ObjectEntry::new_prototype_function_entry( - heap, - "setPrototypeOf", - 2, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "values", 1, false, object_todo), - ], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::ObjectConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::ObjectConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: object_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::ObjectConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::roh(Value::Null), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::ObjectConstructorIndex, - ))), - ), - ObjectEntry::new_prototype_function_entry( - heap, - "hasOwnProperty", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "isPrototypeOf", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "propertyIsEnumerable", - 1, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry( - heap, - "toLocaleString", - 0, - false, - object_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, object_todo), - ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false, object_todo), - ], - )); -} - -fn object_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::Object(0)) -} - -fn object_todo(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - todo!() -} diff --git a/nova_vm/src/heap/regexp.rs b/nova_vm/src/heap/regexp.rs deleted file mode 100644 index 40e3193f..00000000 --- a/nova_vm/src/heap/regexp.rs +++ /dev/null @@ -1,143 +0,0 @@ -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - Heap, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, -}; - -use super::{ - function::FunctionHeapData, - heap_constants::WellKnownSymbolIndexes, - heap_trace::HeapTrace, - object::{ObjectEntry, PropertyKey}, -}; - -#[derive(Debug)] -pub(crate) struct RegExpHeapData { - pub(super) bits: HeapBits, - pub(super) object_index: u32, - // pub(super) _regex: RegExp, -} - -impl HeapTrace for Option { - fn trace(&self, heap: &Heap) { - assert!(self.is_some()); - heap.objects[self.as_ref().unwrap().object_index as usize].trace(heap); - } - fn root(&self, _heap: &Heap) { - assert!(self.is_some()); - self.as_ref().unwrap().bits.root(); - } - - fn unroot(&self, _heap: &Heap) { - assert!(self.is_some()); - self.as_ref().unwrap().bits.unroot(); - } - - fn finalize(&mut self, _heap: &Heap) { - self.take(); - } -} - -pub fn initialize_regexp_heap(heap: &mut Heap) { - let species_function_name = Value::new_string(heap, "get [Symbol.species]"); - heap.objects[BuiltinObjectIndexes::RegExpConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ - ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::RegExpPrototypeIndex as u32, - ), - ObjectEntry::new( - PropertyKey::Symbol(WellKnownSymbolIndexes::Species as u32), - PropertyDescriptor::ReadOnly { - get: heap.create_function(species_function_name, 0, false, regexp_species), - enumerable: false, - configurable: true, - }, - ), - ], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::RegExpConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::RegExpConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: regexp_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::RegExpPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::RegExpConstructorIndex, - ))), - ), - // TODO: Write out all the getters - ObjectEntry::new_prototype_function_entry(heap, "exec", 1, false, regexp_todo), - // TODO: These symbol function properties are actually rwxh, this helper generates roxh instead. - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.match]", - WellKnownSymbolIndexes::Match as u32, - 1, - false, - regexp_todo, - ), - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.matchAll]", - WellKnownSymbolIndexes::MatchAll as u32, - 1, - false, - regexp_todo, - ), - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.replace]", - WellKnownSymbolIndexes::Replace as u32, - 2, - false, - regexp_todo, - ), - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.search]", - WellKnownSymbolIndexes::Search as u32, - 1, - false, - regexp_todo, - ), - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.split]", - WellKnownSymbolIndexes::Split as u32, - 2, - false, - regexp_todo, - ), - ObjectEntry::new_prototype_function_entry(heap, "test", 1, false, regexp_todo), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, regexp_todo), - ], - )); -} - -fn regexp_constructor_binding(_heap: &mut Heap, _this: Value, _args: &[Value]) -> JsResult { - Ok(Value::Function(0)) -} - -fn regexp_species(_heap: &mut Heap, this: Value, _args: &[Value]) -> JsResult { - Ok(this) -} - -fn regexp_todo(_heap: &mut Heap, _this: Value, _args: &[Value]) -> JsResult { - todo!() -} diff --git a/nova_vm/src/heap/string.rs b/nova_vm/src/heap/string.rs index 78b7a330..50d94d5e 100644 --- a/nova_vm/src/heap/string.rs +++ b/nova_vm/src/heap/string.rs @@ -1,15 +1,8 @@ -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes}, - heap_trace::HeapTrace, - FunctionHeapData, Heap, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, Value}, -}; +use crate::heap::{heap_trace::HeapTrace, Heap, HeapBits}; use wtf8::Wtf8Buf; -#[derive(Debug)] -pub(crate) struct StringHeapData { +#[derive(Debug, Clone)] +pub struct StringHeapData { pub(crate) bits: HeapBits, pub(crate) data: Wtf8Buf, } @@ -21,11 +14,6 @@ impl StringHeapData { data: Wtf8Buf::from_str(str), } } - - pub fn len(&self) -> usize { - // TODO: We should return the UTF-16 length. - self.data.len() - } } impl HeapTrace for Option { @@ -45,33 +33,3 @@ impl HeapTrace for Option { self.take(); } } - -pub fn initialize_string_heap(heap: &mut Heap) { - heap.objects[BuiltinObjectIndexes::StringConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - // TODO: Methods and properties - Vec::with_capacity(0), - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::StringConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::StringConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: string_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::StringPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - // TODO: Methods and properties - Vec::with_capacity(0), - )); -} - -fn string_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::EmptyString) -} diff --git a/nova_vm/src/heap/symbol.rs b/nova_vm/src/heap/symbol.rs index bab3daf2..1212c178 100644 --- a/nova_vm/src/heap/symbol.rs +++ b/nova_vm/src/heap/symbol.rs @@ -1,25 +1,17 @@ -use crate::{ - heap::{ - heap_constants::{get_constructor_index, BuiltinObjectIndexes, WellKnownSymbolIndexes}, - heap_trace::HeapTrace, - FunctionHeapData, Heap, HeapBits, ObjectHeapData, PropertyDescriptor, - }, - value::{JsResult, StringIndex, Value}, -}; +use super::{Handle, StringHeapData}; +use crate::heap::{heap_trace::HeapTrace, Heap, HeapBits}; -use super::object::{ObjectEntry, PropertyKey}; - -#[derive(Debug)] -pub(crate) struct SymbolHeapData { +#[derive(Debug, Clone)] +pub struct SymbolHeapData { pub(super) bits: HeapBits, - pub(super) descriptor: Option, + pub(super) descriptor: Option>, } impl HeapTrace for Option { fn trace(&self, heap: &Heap) { assert!(self.is_some()); - if let Some(idx) = self.as_ref().unwrap().descriptor { - heap.strings[idx as usize].trace(heap); + if let Some(handle) = self.as_ref().unwrap().descriptor { + heap.strings[handle.id as usize].trace(heap); } } fn root(&self, _heap: &Heap) { @@ -36,202 +28,3 @@ impl HeapTrace for Option { self.take(); } } - -pub fn initialize_symbol_heap(heap: &mut Heap) { - // AsyncIterator - heap.symbols[WellKnownSymbolIndexes::AsyncIterator as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.asyncIterator")), - }); - // HasInstance - heap.symbols[WellKnownSymbolIndexes::HasInstance as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.hasInstance")), - }); - // IsConcatSpreadable - heap.symbols[WellKnownSymbolIndexes::IsConcatSpreadable as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.isConcatSpreadable")), - }); - // Iterator - heap.symbols[WellKnownSymbolIndexes::Iterator as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.iterator")), - }); - // Match - heap.symbols[WellKnownSymbolIndexes::Match as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.match")), - }); - // MatchAll - heap.symbols[WellKnownSymbolIndexes::MatchAll as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.matchAll")), - }); - // Replace - heap.symbols[WellKnownSymbolIndexes::Replace as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.replace")), - }); - // Search - heap.symbols[WellKnownSymbolIndexes::Search as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.search")), - }); - // Species - heap.symbols[WellKnownSymbolIndexes::Species as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.species")), - }); - // Split - heap.symbols[WellKnownSymbolIndexes::Split as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.split")), - }); - // ToPrimitive - heap.symbols[WellKnownSymbolIndexes::ToPrimitive as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.toPrimitive")), - }); - // ToStringTag - heap.symbols[WellKnownSymbolIndexes::ToStringTag as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.toStringTag")), - }); - // Unscopables - heap.symbols[WellKnownSymbolIndexes::Unscopables as usize] = Some(SymbolHeapData { - bits: HeapBits::new(), - descriptor: Some(heap.alloc_string("Symbol.unscopables")), - }); - - heap.objects[BuiltinObjectIndexes::SymbolConstructorIndex as usize] = - Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::FunctionPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "asyncIterator"), - PropertyDescriptor::roh(Value::Symbol( - WellKnownSymbolIndexes::AsyncIterator as u32, - )), - ), - ObjectEntry::new_prototype_function_entry(heap, "for", 1, false, symbol_todo), - ObjectEntry::new( - PropertyKey::from_str(heap, "hasInstance"), - PropertyDescriptor::roh(Value::Symbol( - WellKnownSymbolIndexes::HasInstance as u32, - )), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "isConcatSpreadable"), - PropertyDescriptor::roh(Value::Symbol( - WellKnownSymbolIndexes::IsConcatSpreadable as u32, - )), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "iterator"), - PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::Iterator as u32)), - ), - ObjectEntry::new_prototype_function_entry(heap, "keyFor", 1, false, symbol_todo), - ObjectEntry::new( - PropertyKey::from_str(heap, "Match"), - PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::Match as u32)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "MatchAll"), - PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::MatchAll as u32)), - ), - ObjectEntry::new_constructor_prototype_entry( - heap, - BuiltinObjectIndexes::SymbolPrototypeIndex as u32, - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "Replace"), - PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::Replace as u32)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "Search"), - PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::Search as u32)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "Species"), - PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::Species as u32)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "Split"), - PropertyDescriptor::roh(Value::Symbol(WellKnownSymbolIndexes::Split as u32)), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "ToPrimitive"), - PropertyDescriptor::roh(Value::Symbol( - WellKnownSymbolIndexes::ToPrimitive as u32, - )), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "ToStringTag"), - PropertyDescriptor::roh(Value::Symbol( - WellKnownSymbolIndexes::ToStringTag as u32, - )), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "Unscopables"), - PropertyDescriptor::roh(Value::Symbol( - WellKnownSymbolIndexes::Unscopables as u32, - )), - ), - ], - )); - heap.functions[get_constructor_index(BuiltinObjectIndexes::SymbolConstructorIndex) as usize] = - Some(FunctionHeapData { - bits: HeapBits::new(), - object_index: BuiltinObjectIndexes::SymbolConstructorIndex as u32, - length: 1, - uses_arguments: false, - bound: None, - visible: None, - binding: symbol_constructor_binding, - }); - heap.objects[BuiltinObjectIndexes::SymbolPrototypeIndex as usize] = Some(ObjectHeapData::new( - true, - PropertyDescriptor::prototype_slot(BuiltinObjectIndexes::ObjectPrototypeIndex as u32), - vec![ - ObjectEntry::new( - PropertyKey::from_str(heap, "constructor"), - PropertyDescriptor::rwx(Value::Function(get_constructor_index( - BuiltinObjectIndexes::SymbolConstructorIndex, - ))), - ), - ObjectEntry::new( - PropertyKey::from_str(heap, "description"), - // TODO: create description getter function - PropertyDescriptor::ReadOnly { - get: 0, - enumerable: false, - configurable: true, - }, - ), - ObjectEntry::new_prototype_function_entry(heap, "toString", 0, false, symbol_todo), - ObjectEntry::new_prototype_function_entry(heap, "valueOf", 0, false, symbol_todo), - ObjectEntry::new_prototype_symbol_function_entry( - heap, - "[Symbol.toPrimitive]", - WellKnownSymbolIndexes::ToPrimitive as u32, - 1, - false, - symbol_todo, - ), - ObjectEntry::new( - PropertyKey::Symbol(WellKnownSymbolIndexes::ToStringTag as u32), - PropertyDescriptor::roxh(Value::new_string(heap, "Symbol")), - ), - ], - )); -} - -fn symbol_constructor_binding(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - Ok(Value::Symbol(0)) -} - -fn symbol_todo(heap: &mut Heap, _this: Value, args: &[Value]) -> JsResult { - todo!(); -} diff --git a/nova_vm/src/language.rs b/nova_vm/src/language.rs new file mode 100644 index 00000000..5dc24586 --- /dev/null +++ b/nova_vm/src/language.rs @@ -0,0 +1,4 @@ +mod bytecode; +mod script; + +pub use script::Script; diff --git a/nova_vm/src/language/bytecode.rs b/nova_vm/src/language/bytecode.rs new file mode 100644 index 00000000..b153f88a --- /dev/null +++ b/nova_vm/src/language/bytecode.rs @@ -0,0 +1,7 @@ +mod executable; +mod instructions; +mod vm; + +pub use executable::{Executable, IndexType, JumpIndex}; +pub use instructions::{Instr, Instruction, InstructionIter}; +pub use vm::Vm; diff --git a/nova_vm/src/language/bytecode/executable.rs b/nova_vm/src/language/bytecode/executable.rs new file mode 100644 index 00000000..ea9e3438 --- /dev/null +++ b/nova_vm/src/language/bytecode/executable.rs @@ -0,0 +1,70 @@ +use super::Instruction; +use crate::{types::Value, Heap}; +use oxc_span::Atom; + +pub type IndexType = u16; + +#[derive(Debug)] +pub struct Executable<'ctx> { + pub heap: &'ctx mut Heap, + pub instructions: Vec, + pub constants: Vec, + pub identifiers: Vec, + // TODO: function_expressions +} + +impl<'ctx> Executable<'ctx> { + pub fn add_instruction(&mut self, instruction: Instruction) { + self.instructions.push(instruction); + } + + pub fn add_constant(&mut self, constant: Value) { + self.constants.push(constant); + } + + pub fn add_identifier(&mut self, identifier: Atom) { + self.identifiers.push(identifier); + } + + pub fn add_instruction_with_constant(&mut self, instruction: Instruction, constant: Value) { + debug_assert!(instruction.has_constant_index()); + self.add_instruction(instruction); + self.add_constant(constant); + } + + pub fn add_instruction_with_identifier(&mut self, instruction: Instruction, identifier: Atom) { + debug_assert!(instruction.has_identifier_index()); + self.add_instruction(instruction); + self.add_identifier(identifier); + } + + pub fn add_index(&mut self, index: usize) { + assert!(index < IndexType::MAX as usize); + let bytes: [u8; 2] = unsafe { std::mem::transmute(index as IndexType) }; + self.instructions[index] = unsafe { std::mem::transmute(bytes[0]) }; + self.instructions[index + 1] = unsafe { std::mem::transmute(bytes[0]) }; + } + + pub fn add_jump_index(&mut self) -> JumpIndex { + self.add_index(0); + JumpIndex { + index: self.instructions.len() - std::mem::size_of::(), + } + } + + pub fn set_jump_target(&mut self, jump: JumpIndex, index: usize) { + assert!(index < IndexType::MAX as usize); + let bytes: [u8; 2] = unsafe { std::mem::transmute(index as IndexType) }; + self.instructions[jump.index] = unsafe { std::mem::transmute(bytes[0]) }; + self.instructions[jump.index + 1] = unsafe { std::mem::transmute(bytes[0]) }; + } + + pub fn set_jump_target_here(&mut self, jump: JumpIndex) { + self.set_jump_target(jump, self.instructions.len()); + } +} + +#[derive(Debug)] +pub struct JumpIndex { + pub index: usize, +} diff --git a/nova_vm/src/language/bytecode/instructions.rs b/nova_vm/src/language/bytecode/instructions.rs new file mode 100644 index 00000000..d81f8168 --- /dev/null +++ b/nova_vm/src/language/bytecode/instructions.rs @@ -0,0 +1,198 @@ +use super::IndexType; + +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub enum Instruction { + /// Store ApplyStringOrNumericBinaryOperator() as the result value. + ApplyStringOrNumericBinaryOperator, + /// Store ArrayCreate(0) as the result value. + ArrayCreate, + /// Set an array's value at the given index. + ArraySetValue, + /// Set the length property of an array to the given index. + ArraySetLength, + /// Apply bitwise NOT to the last value on the stack and store it as the result value. + BitwiseNot, + /// Create a catch binding for the given name and populate it with the stored exception. + CreateCatchBinding, + /// Apply the delete operation to the evaluated expression and set it as the result value. + Delete, + /// Store EvaluateCall() as the result value. + /// This instruction has the number of argument values that need to be popped from the stack + /// (last to first) as an argument, the values on the stack afterwards are the this value and + /// lastly the function to call. + EvaluateCall, + /// Store EvaluateNew() as the result value. + /// This instruction has the number of argument values that need to be popped from the stack + /// (last to first) as an argument, the value on the stack afterwards is the constructor to + /// call. + EvaluateNew, + /// Store EvaluatePropertyAccessWithExpressionKey() as the result value. + EvaluatePropertyAccessWithExpressionKey, + /// Store EvaluatePropertyAccessWithIdentifierKey() as the result value. + EvaluatePropertyAccessWithIdentifierKey, + /// Store GetValue() as the result value. + GetValue, + /// Compare the last two values on the stack using the '>' operator rules. + GreaterThan, + /// Compare the last two values on the stack using the '>=' operator rules. + GreaterThanEquals, + /// Store HasProperty() as the result value. + HasProperty, + /// Store InstanceofOperator() as the result value. + InstanceofOperator, + /// Store InstantiateArrowFunctionExpression() as the result value. + InstantiateArrowFunctionExpression, + /// Store InstantiateOrdinaryFunctionExpression() as the result value. + InstantiateOrdinaryFunctionExpression, + /// Store IsLooselyEqual() as the result value. + IsLooselyEqual, + /// Store IsStrictlyEqual() as the result value. + IsStrictlyEqual, + /// Jump to another instruction by setting the instruction pointer. + Jump, + /// Jump to one of two other instructions depending on whether the last value on the stack is + /// truthy or not. + JumpConditional, + /// Compare the last two values on the stack using the '<' operator rules. + LessThan, + /// Compare the last two values on the stack using the '<=' operator rules. + LessThanEquals, + /// Load the result value and add it to the stack. + Load, + /// Load a constant and add it to the stack. + LoadConstant, + /// Determine the this value for an upcoming evaluate_call instruction and add it to the stack. + LoadThisValue, + /// Apply logical NOT to the last value on the stack and store it as the result value. + LogicalNot, + /// Store OrdinaryObjectCreate(%Object.prototype%) as the result value. + ObjectCreate, + /// Set an object's property to the key/value pair from the last two values on the stack. + ObjectSetProperty, + /// Pop a jump target for uncaught exceptions + PopExceptionJumpTarget, + /// Pop the last stored reference. + PopReference, + /// Push a jump target for uncaught exceptions + PushExceptionJumpTarget, + /// Push the last evaluated reference, if any. + PushReference, + /// Call PutValue() with the last reference on the reference stack and the result value. + PutValue, + /// Store ResolveBinding() as the result value. + ResolveBinding, + /// Store ResolveThisBinding() as the result value. + ResolveThisBinding, + /// Rethrow the stored exception, if any. + RethrowExceptionIfAny, + /// Stop bytecode execution, indicating a return from the current function. + Return, + /// Store the last value from the stack as the result value. + Store, + /// Store a constant as the result value. + StoreConstant, + /// Throw the last value from the stack as an exception. + Throw, + /// Store ToNumber() as the result value. + ToNumber, + /// Store ToNumeric() as the result value. + ToNumeric, + /// Apply the typeof operation to the evaluated expression and set it as the result value. + Typeof, + /// Store Number::unaryMinus() / BigInt::unaryMinus() as the result value. + UnaryMinus, +} + +impl Instruction { + pub fn argument_count(self) -> u8 { + return match self { + Self::EvaluateCall + | Self::EvaluatePropertyAccessWithIdentifierKey + | Self::JumpConditional + | Self::ResolveBinding => 2, + Self::ApplyStringOrNumericBinaryOperator + | Self::ArraySetLength + | Self::ArraySetValue + | Self::CreateCatchBinding + | Self::EvaluateNew + | Self::EvaluatePropertyAccessWithExpressionKey + | Self::InstantiateArrowFunctionExpression + | Self::InstantiateOrdinaryFunctionExpression + | Self::Jump + | Self::LoadConstant + | Self::PushExceptionJumpTarget + | Self::StoreConstant => 1, + _ => 0, + }; + } + + pub fn has_constant_index(self) -> bool { + return match self { + Self::LoadConstant | Self::StoreConstant => true, + _ => false, + }; + } + + pub fn has_identifier_index(self) -> bool { + return match self { + Self::CreateCatchBinding + | Self::EvaluatePropertyAccessWithIdentifierKey + | Self::ResolveBinding => true, + _ => false, + }; + } + + pub fn has_function_expression_index(self) -> bool { + return match self { + Self::InstantiateArrowFunctionExpression + | Self::InstantiateOrdinaryFunctionExpression => true, + _ => false, + }; + } +} + +#[derive(Debug)] +pub struct Instr { + pub kind: Instruction, + pub args: [Option; 2], +} + +#[derive(Debug)] +pub struct InstructionIter<'a> { + instructions: &'a [Instruction], + index: usize, +} + +impl<'a> InstructionIter<'a> { + pub fn new(instructions: &'a [Instruction]) -> Self { + Self { + instructions, + index: 0, + } + } +} + +impl Iterator for InstructionIter<'_> { + type Item = Instr; + + fn next(&mut self) -> Option { + if self.index >= self.instructions.len() { + return None; + } + + let kind = self.instructions[self.index]; + self.index += 1; + + let mut args: [Option; 2] = [None, None]; + + for i in 0..kind.argument_count() as usize { + let bytes: &[IndexType] = + unsafe { std::mem::transmute(&self.instructions[self.index..]) }; + self.index += 2; + args[i] = Some(bytes[0]); + } + + Some(Instr { kind, args }) + } +} diff --git a/nova_vm/src/language/bytecode/vm.rs b/nova_vm/src/language/bytecode/vm.rs new file mode 100644 index 00000000..8cdf481c --- /dev/null +++ b/nova_vm/src/language/bytecode/vm.rs @@ -0,0 +1,62 @@ +use std::{cell::RefCell, rc::Rc}; + +use oxc_span::Atom; + +use crate::{ + execution::Agent, + types::{Reference, Value}, +}; + +use super::{Executable, IndexType, Instruction}; + +#[derive(Debug)] +pub struct Vm<'ctx, 'host> { + agent: &'ctx mut Agent<'ctx, 'host>, + ip: usize, + stack: Vec, + reference_stack: Vec>, + exception_jump_target_stack: Vec, + result: Option, + exception: Option, + reference: Option, +} + +impl<'ctx, 'host> Vm<'ctx, 'host> { + pub fn new(agent: &'ctx mut Agent<'ctx, 'host>) -> Self { + Self { + agent, + ip: 0, + stack: Vec::with_capacity(32), + reference_stack: Vec::new(), + exception_jump_target_stack: Vec::new(), + result: None, + exception: None, + reference: None, + } + } + + fn fetch_instruction(&mut self, executable: &Executable) -> Option { + executable.instructions.get(self.ip).map(|kind| { + self.ip += 1; + *kind + }) + } + + fn fetch_constant(&mut self, executable: &Executable) -> Value { + let index = self.fetch_index(executable); + executable.constants[index as usize].clone() + } + + fn fetch_identifier(&mut self, executable: &Executable) -> Atom { + let index = self.fetch_index(executable); + executable.identifiers[index as usize].clone() + } + + fn fetch_index(&mut self, executable: &Executable) -> IndexType { + let bytes: [u8; std::mem::size_of::()] = [ + unsafe { std::mem::transmute(self.fetch_instruction(executable).unwrap()) }, + unsafe { std::mem::transmute(self.fetch_instruction(executable).unwrap()) }, + ]; + unsafe { std::mem::transmute(bytes) } + } +} diff --git a/nova_vm/src/language/script.rs b/nova_vm/src/language/script.rs new file mode 100644 index 00000000..64d758f3 --- /dev/null +++ b/nova_vm/src/language/script.rs @@ -0,0 +1,160 @@ +use crate::{ + execution::{ECMAScriptCode, Environment, ExecutionContext, Realm, ScriptOrModule}, + types::Value, +}; +use oxc_allocator::Allocator; +use oxc_ast::ast::{ + BindingPattern, BindingPatternKind, Declaration, Program, Statement, VariableDeclarationKind, +}; +use oxc_parser::Parser; +use oxc_span::SourceType; +use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc}; + +pub type HostDefined<'ctx> = Option<&'ctx mut dyn Any>; + +/// 16.1.4 Script Records +/// https://tc39.es/ecma262/#sec-script-records +#[derive(Debug)] +pub struct Script<'ctx, 'host> { + /// [[Realm]] + pub realm: Rc>>, + + /// [[ECMAScriptCode]] + pub ecmascript_code: Rc>, + + // TODO: [[LoadedModules]] + /// [[HostDefined]] + pub host_defined: Option>, +} + +#[derive(Debug)] +pub enum ScriptOrErrors<'ctx, 'host> { + Script(Script<'ctx, 'host>), + Errors(Vec), +} + +impl<'ctx, 'host: 'ctx> Script<'ctx, 'host> { + /// 16.1.5 ParseScript ( sourceText, realm, hostDefined ) + /// https://tc39.es/ecma262/#sec-parse-script + pub fn parse( + allocator: &'host Allocator, + source_text: &'host str, + realm: Rc>>, + host_defined: Option>, + ) -> ScriptOrErrors<'ctx, 'host> { + // 1. Let script be ParseText(sourceText, Script). + // 2. If script is a List of errors, return script. + let parser = Parser::new(&allocator, source_text, SourceType::default()); + let script = parser.parse(); + + if script.errors.len() != 0 { + return ScriptOrErrors::Errors(script.errors); + } + + // 3. Return Script Record { + // [[Realm]]: realm, [[ECMAScriptCode]]: script, [[LoadedModules]]: « », [[HostDefined]]: hostDefined + // }. + ScriptOrErrors::Script(Self { + realm, + ecmascript_code: Rc::new(script.program), + host_defined, + }) + } + + /// 16.1.6 ScriptEvaluation ( scriptRecord ) + /// https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation + pub fn evaluate(&'ctx mut self) -> Value { + let ecmascript_code = self.ecmascript_code.clone(); + let realm = self.realm.clone(); + let agent = { + let realm = self.realm.borrow(); + realm.agent.clone() + }; + + // 1. Let globalEnv be scriptRecord.[[Realm]].[[GlobalEnv]]. + let global_env = { + let realm = self.realm.borrow(); + realm.global_env.clone() + }; + + // 2. Let scriptContext be a new ECMAScript code execution context. + let script_context = ExecutionContext { + // 3. Set the Function of scriptContext to null. + function: None, + + // 4. Set the Realm of scriptContext to scriptRecord.[[Realm]]. + realm, + + // 5. Set the ScriptOrModule of scriptContext to scriptRecord. + script_or_module: Some(ScriptOrModule::Script(self)), + + ecmascript_code: Some(ECMAScriptCode { + // 6. Set the VariableEnvironment of scriptContext to globalEnv. + variable_environment: Environment::GlobalEnvironment(global_env.clone()), + + // 7. Set the LexicalEnvironment of scriptContext to globalEnv. + lexical_environment: Environment::GlobalEnvironment(global_env.clone()), + + // 8. Set the PrivateEnvironment of scriptContext to null. + private_environment: None, + }), + }; + + // TODO: 9. Suspend the running execution context. + + // 10. Push scriptContext onto the execution context stack; scriptContext is now the running execution context. + agent + .borrow_mut() + .execution_context_stack + .push(script_context); + + // 11. Let script be scriptRecord.[[ECMAScriptCode]]. + let script = ecmascript_code.as_ref(); + + // TODO: 12. Let result be Completion(GlobalDeclarationInstantiation(script, globalEnv)). + // NOTE: This is totally ad-hoc for now. + let mut seen = HashMap::new(); + for variable_declaration in script.body.iter() { + match &variable_declaration { + Statement::Declaration(decl) => match decl { + Declaration::VariableDeclaration(decl) => { + if decl.kind.is_var() { + for decl in decl.declarations.iter() { + let var_name = match &decl.id.kind { + BindingPatternKind::BindingIdentifier(name) => { + name.name.as_str() + } + _ => continue, + }; + + if !seen.contains_key(var_name) { + // global_env.create_global_var_binding(agent, var_name, false); + _ = seen.insert(var_name, ()); + } + } + } + } + _ => {} + }, + _ => {} + } + } + + // 13. If result.[[Type]] is normal, then + // a. Set result to Completion(Evaluation of script). + // b. If result.[[Type]] is normal and result.[[Value]] is empty, then + // i. Set result to NormalCompletion(undefined). + + // 14. Suspend scriptContext and remove it from the execution context stack. + _ = agent.borrow_mut().execution_context_stack.pop(); + + // 15. Assert: The execution context stack is not empty. + debug_assert!(agent.borrow().execution_context_stack.len() > 0); + + // TODO: 16. Resume the context that is now on the top of the execution context stack as the + // running execution context. + + // 17. Return ? result. + todo!() + } +} diff --git a/nova_vm/src/lib.rs b/nova_vm/src/lib.rs index 422922e9..5b3357cd 100644 --- a/nova_vm/src/lib.rs +++ b/nova_vm/src/lib.rs @@ -1,465 +1,10 @@ -#![feature(try_trait_v2)] - +pub mod builtins; +pub mod execution; pub mod heap; -mod stack_string; -pub mod value; -use heap::Heap; -use oxc_ast::{ - ast::{ - AssignmentOperator, AssignmentTarget, BinaryOperator, BindingPatternKind, Declaration, - Expression, LogicalOperator, Program, SimpleAssignmentTarget, Statement, - VariableDeclarator, - }, - syntax_directed_operations::PropName, -}; -use std::fmt::Debug; -use value::{JsResult, Value}; - -/// https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-ecmascript-language-types -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum Type { - BigInt, - Boolean, - Function, - Null, - Number, - Object, - String, - Symbol, - Undefined, -} - -#[repr(u32)] -pub enum Instruction { - LoadInteger, - LoadBoolean, - LoadNull, - LoadString, - - // [out] [in] - CopyValue, - - // [out] [left] [right] - Add, - Sub, - Mul, - Div, - Mod, - StrictEquality, - Equality, - StrictInequality, - Inequality, - - /// `[jump_rel]` - /// - /// Jumps to the relative instruction. - Jump, - - /// `[addr] [jump_rel]` - /// - /// If `addr` is a true when converted to a boolean, then the instruction - /// will skip `jump_rel` instructions forward. - Test, - - /// `[addr] [jump_rel]` - /// If `addr` is false when converted to a boolean, then the instruction - /// will skip `jump_rel` instructions forward. - TestNot, -} - -impl Into for Instruction { - fn into(self) -> u32 { - self as u32 - } -} - -pub struct VM<'a> { - pub source: &'a str, - pub instructions: Vec, - /// Program counter. - pub pc: u32, - pub heap: Heap, -} - -#[derive(Debug, Default)] -pub struct Env<'a> { - pub map: std::collections::HashMap<&'a str, u32>, - pub parent: Option>>, -} - -impl<'a> VM<'a> { - pub fn interpret(&mut self) -> JsResult<()> { - let mut memory = Vec::::with_capacity(self.pc as usize); - - for _ in 0..self.pc { - memory.push(Value::Undefined); - } - - let instructions = self.instructions.clone(); - let mut iter = instructions.iter(); - while let Some(leading) = iter.next() { - match unsafe { std::mem::transmute::(*leading) } { - Instruction::LoadInteger => { - let addr = *iter.next().unwrap() as usize; - memory[addr] = Value::from_i32(*iter.next().unwrap() as i32); - } - Instruction::LoadBoolean => { - let addr = *iter.next().unwrap() as usize; - memory[addr] = Value::Boolean(*iter.next().unwrap() == 1); - } - Instruction::LoadNull => { - let addr = *iter.next().unwrap() as usize; - memory[addr] = Value::Null; - } - Instruction::LoadString => { - let addr = *iter.next().unwrap() as usize; - memory[addr] = Value::HeapString(*iter.next().unwrap() as u32); - } - Instruction::CopyValue => { - let addr = *iter.next().unwrap() as usize; - memory[addr] = memory[*iter.next().unwrap() as usize].clone(); - } - Instruction::Add => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr].try_into_f64(self)?; - let right = &memory[*iter.next().unwrap() as usize].try_into_f64(self)?; - memory[addr] = Value::from_f64(&mut self.heap, left + right); - } - Instruction::Sub => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr].try_into_f64(self)?; - let right = &memory[*iter.next().unwrap() as usize].try_into_f64(self)?; - memory[addr] = Value::from_f64(&mut self.heap, left - right); - } - Instruction::Mul => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr].try_into_f64(self)?; - let right = &memory[*iter.next().unwrap() as usize].try_into_f64(self)?; - memory[addr] = Value::from_f64(&mut self.heap, left * right); - } - Instruction::Mod => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr].try_into_f64(self)?; - let right = &memory[*iter.next().unwrap() as usize].try_into_f64(self)?; - memory[addr] = Value::from_f64(&mut self.heap, left % right); - } - Instruction::Div => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr].try_into_f64(self)?; - let right = &memory[*iter.next().unwrap() as usize].try_into_f64(self)?; - memory[addr] = Value::from_f64(&mut self.heap, left / right); - } - Instruction::StrictEquality => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr]; - let right = &memory[*iter.next().unwrap() as usize]; - memory[addr] = Value::Boolean(left.is_strictly_equal(self, right)?); - } - Instruction::Equality => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr]; - let right = &memory[*iter.next().unwrap() as usize]; - memory[addr] = Value::Boolean(left.is_loosely_equal(self, right)?); - } - Instruction::StrictInequality => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr]; - let right = &memory[*iter.next().unwrap() as usize]; - memory[addr] = Value::Boolean(!left.is_strictly_equal(self, right)?); - } - Instruction::Inequality => { - let addr = *iter.next().unwrap() as usize; - let left = &memory[addr]; - let right = &memory[*iter.next().unwrap() as usize]; - memory[addr] = Value::Boolean(!left.is_loosely_equal(self, right)?); - } - Instruction::Test => { - let addr = *iter.next().unwrap() as usize; - let jump_rel = *iter.next().unwrap() as usize; - - if memory[addr].into_bool() { - _ = iter.nth(jump_rel); - }; - } - Instruction::TestNot => { - let addr = *iter.next().unwrap() as usize; - let jump_rel = *iter.next().unwrap() as usize; - - if !memory[addr].into_bool() { - _ = iter.nth(jump_rel); - }; - } - Instruction::Jump => { - let jump_rel = *iter.next().unwrap() as usize; - _ = iter.nth(jump_rel); - } - } - } - - println!("{:?}", memory.as_slice()); - - Ok(()) - } - - /// Builds the bytecode to run an expression. - /// - /// Assumes the memory location is valid to use throughout the evaluation as - /// a scratch. - fn build_expr(&mut self, addr: u32, expr: &Expression, env: &mut Env) { - match expr { - Expression::NullLiteral(_) => { - self.instructions.push(Instruction::LoadNull.into()); - self.instructions.push(addr); - } - Expression::BooleanLiteral(l) => { - self.instructions.push(Instruction::LoadBoolean.into()); - self.instructions.push(addr); - self.instructions.push(l.value.into()); - } - Expression::Identifier(ident) => { - // TODO: figure out how to return the ident's memory addr as - // an optimization - self.instructions.push(Instruction::CopyValue.into()); - self.instructions.push(addr); - // TODO: support recursive ident lookups - self.instructions - .push(*env.map.get(ident.name.as_str()).unwrap()); - } - Expression::NumberLiteral(num) => { - self.instructions.push(Instruction::LoadInteger.into()); - self.instructions.push(addr); - self.instructions - .push(unsafe { std::mem::transmute(*num.value.as_f32()) }); - } - Expression::LogicalExpression(expr) => match expr.operator { - LogicalOperator::And => { - self.build_expr(addr, &expr.left, env); - self.instructions.push(Instruction::Test.into()); - self.instructions.push(addr); - let jump_addr = self.instructions.len(); - self.instructions.push(0); - self.build_expr(addr, &expr.right, env); - self.instructions[jump_addr] = (self.instructions.len() - jump_addr) as u32; - } - LogicalOperator::Or => { - self.build_expr(addr, &expr.left, env); - self.instructions.push(Instruction::TestNot.into()); - self.instructions.push(addr); - let jump_addr = self.instructions.len(); - self.instructions.push(0); - self.build_expr(addr, &expr.right, env); - self.instructions[jump_addr] = (self.instructions.len() - jump_addr) as u32; - } - _ => panic!(), - }, - Expression::ConditionalExpression(cond) => { - self.build_expr(addr, &cond.test, env); - - self.instructions.push(Instruction::Test.into()); - self.instructions.push(addr); - let finish_idx = self.instructions.len(); - self.instructions.push(0); - - self.build_expr(addr, &cond.alternate, env); - - self.instructions.push(Instruction::Jump.into()); - let alternate_idx = self.instructions.len(); - self.instructions.push(0); - - self.instructions[finish_idx] = (self.instructions.len() - finish_idx - 2) as u32; - self.build_expr(addr, &cond.consequent, env); - - self.instructions[alternate_idx] = - (self.instructions.len() - alternate_idx - 2) as u32; - } - Expression::ObjectExpression(obj) => { - for prop in obj.properties.iter() { - prop.prop_name(); - } - } - Expression::BinaryExpression(binary_op) => { - macro_rules! binary_op { - ($name: ident) => {{ - let right = self.pc; - self.pc += 1; - - self.build_expr(addr, &binary_op.left, env); - self.build_expr(right, &binary_op.right, env); - - self.instructions.push(Instruction::$name.into()); - self.instructions.push(addr); - self.instructions.push(right); - }}; - } - - match binary_op.operator { - BinaryOperator::Addition => binary_op!(Add), - BinaryOperator::Subtraction => binary_op!(Sub), - BinaryOperator::Multiplication => binary_op!(Mul), - BinaryOperator::Remainder => binary_op!(Mod), - BinaryOperator::Division => binary_op!(Div), - BinaryOperator::StrictEquality => binary_op!(StrictEquality), - BinaryOperator::Equality => binary_op!(Equality), - BinaryOperator::StrictInequality => binary_op!(StrictInequality), - BinaryOperator::Inequality => binary_op!(Inequality), - _ => todo!(), - } - } - Expression::StringLiteral(s) => { - let string_idx = self.heap.alloc_string(&*s.value.as_str()); - - self.instructions.push(Instruction::LoadString.into()); - self.instructions.push(addr); - self.instructions.push(string_idx as u32); - } - Expression::ParenthesizedExpression(data) => { - return self.build_expr(addr, &data.expression, env); - } - Expression::SequenceExpression(data) => { - for expr in data.expressions.iter() { - self.build_expr(addr, expr, env); - } - } - Expression::AssignmentExpression(s) => match s.operator { - AssignmentOperator::Assign => match &s.left { - AssignmentTarget::SimpleAssignmentTarget(target) => match target { - SimpleAssignmentTarget::AssignmentTargetIdentifier(ident) => { - let Some(addr) = env.map.get(ident.name.as_str()) else { - panic!("Unknown ident."); - }; - self.build_expr(*addr, &s.right, env); - } - _ => todo!(), - }, - _ => todo!(), - }, - _ => todo!(), - }, - Expression::ArrayExpression(_) => todo!(), - Expression::BigintLiteral(_) => todo!(), - Expression::RegExpLiteral(_) => todo!(), - Expression::TemplateLiteral(_) => todo!(), - Expression::MetaProperty(_) => todo!(), - Expression::Super(_) => todo!(), - Expression::ArrowFunctionExpression(_) => todo!(), - Expression::AwaitExpression(_) => todo!(), - Expression::CallExpression(_) => todo!(), - Expression::ChainExpression(_) => todo!(), - Expression::ClassExpression(_) => todo!(), - Expression::FunctionExpression(_) => todo!(), - Expression::ImportExpression(_) => todo!(), - Expression::MemberExpression(_) => todo!(), - Expression::NewExpression(_) => todo!(), - Expression::TaggedTemplateExpression(_) => todo!(), - Expression::ThisExpression(_) => todo!(), - Expression::UnaryExpression(_) => todo!(), - Expression::UpdateExpression(_) => todo!(), - Expression::YieldExpression(_) => todo!(), - Expression::PrivateInExpression(_) => todo!(), - // TypeScript and JSX not supported - Expression::JSXElement(_) - | Expression::JSXFragment(_) - | Expression::TSAsExpression(_) - | Expression::TSSatisfiesExpression(_) - | Expression::TSTypeAssertion(_) - | Expression::TSNonNullExpression(_) - | Expression::TSInstantiationExpression(_) => unreachable!(), - } - } - - pub fn build_stmt<'b>(&mut self, stmt: &'b Statement, env: &mut Env<'b>) { - match stmt { - Statement::Declaration(Declaration::VariableDeclaration(decl)) => { - for member in decl.declarations.as_slice() { - let member: &VariableDeclarator = member; - env.map.insert( - match &member.id.kind { - BindingPatternKind::BindingIdentifier(ident) => ident.name.as_str(), - _ => panic!(), - }, - self.pc, - ); - let addr = self.pc; - self.pc += 1; - - if let Some(expr) = &member.init { - self.build_expr(addr, expr, env); - } else { - todo!("Load undefined."); - } - } - } - Statement::Declaration(Declaration::FunctionDeclaration(_)) => todo!(), - Statement::Declaration(Declaration::ClassDeclaration(_)) => todo!(), - Statement::ExpressionStatement(expr) => { - self.build_expr(self.pc, &expr.expression, env); - } - Statement::BlockStatement(block) => { - for stmt in block.body.iter() { - self.build_stmt(&stmt, env); - } - } - Statement::IfStatement(s) => { - let addr = self.pc; - self.pc += 1; - - self.build_expr(addr, &s.test, env); - self.instructions.push(Instruction::Test.into()); - self.instructions.push(addr); - let consequent_idx = self.instructions.len(); - self.instructions.push(0); - - if let Some(alternate) = &s.alternate { - self.build_stmt(alternate, env); - } - - self.instructions.push(Instruction::Jump.into()); - let finish_idx = self.instructions.len(); - self.instructions.push(0); - - self.instructions[consequent_idx] = - (self.instructions.len() - consequent_idx - 2) as u32; - self.build_stmt(&s.consequent, env); - - self.instructions[finish_idx] = (self.instructions.len() - finish_idx - 2) as u32; - } - Statement::BreakStatement(_) => todo!(), - Statement::ContinueStatement(_) => todo!(), - Statement::DebuggerStatement(_) => todo!(), - Statement::DoWhileStatement(_) => todo!(), - Statement::EmptyStatement(_) => todo!(), - Statement::ForInStatement(_) => todo!(), - Statement::ForOfStatement(_) => todo!(), - Statement::ForStatement(_) => todo!(), - Statement::LabeledStatement(_) => todo!(), - Statement::ReturnStatement(_) => todo!(), - Statement::SwitchStatement(_) => todo!(), - Statement::ThrowStatement(_) => todo!(), - Statement::TryStatement(_) => todo!(), - Statement::WhileStatement(_) => todo!(), - Statement::WithStatement(_) => todo!(), - Statement::ModuleDeclaration(_) => todo!(), - // TypeScript not supported - Statement::Declaration(Declaration::TSTypeAliasDeclaration(_)) - | Statement::Declaration(Declaration::TSInterfaceDeclaration(_)) - | Statement::Declaration(Declaration::TSEnumDeclaration(_)) - | Statement::Declaration(Declaration::TSModuleDeclaration(_)) - | Statement::Declaration(Declaration::TSImportEqualsDeclaration(_)) => unreachable!(), - } - } - - pub fn load_program(&mut self, program: Program) { - let mut env = Env::default(); - for stmt in program.body.iter() { - self.build_stmt(stmt, &mut env); - } - } -} - -#[cfg(test)] -mod tests { - - use super::*; - - fn foo() {} -} +pub mod language; +pub mod types; +pub use heap::Heap; +mod small_integer; +mod small_string; +pub use small_integer::SmallInteger; +pub use small_string::SmallString; diff --git a/nova_vm/src/small_integer.rs b/nova_vm/src/small_integer.rs new file mode 100644 index 00000000..3862950e --- /dev/null +++ b/nova_vm/src/small_integer.rs @@ -0,0 +1,52 @@ +/// 56-bit signed integer. +#[derive(Clone, Copy)] +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: i64 = -(2 as i64).pow(56) / 2 + 1; + pub const MAX: i64 = (2 as i64).pow(56) / 2 - 1; + + pub(crate) fn from_i64_unchecked(value: i64) -> SmallInteger { + debug_assert!(value >= Self::MIN && value <= Self::MAX); + let mut data: [u8; 7] = [0, 0, 0, 0, 0, 0, 0]; + let bytes = i64::to_ne_bytes(value); + if cfg!(target_endian = "little") { + data.copy_from_slice(&bytes[0..7]); + } else { + data.copy_from_slice(&bytes[1..8]); + } + Self { data } + } +} + +impl TryFrom for SmallInteger { + type Error = (); + fn try_from(value: i64) -> Result { + if value >= Self::MIN && value <= Self::MAX { + Ok(Self::from_i64_unchecked(value)) + } else { + Err(()) + } + } +} + +impl Into for SmallInteger { + fn into(self) -> i64 { + let Self { data } = self; + let mut bytes: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; + bytes.copy_from_slice(&data); + if cfg!(target_endian = "big") { + bytes.copy_within(0..7, 1); + } + // SAFETY: The format is guaranteed to match `from_i64_unchecked`. + unsafe { std::mem::transmute(bytes) } + } +} diff --git a/nova_vm/src/small_string.rs b/nova_vm/src/small_string.rs new file mode 100644 index 00000000..8f9f4333 --- /dev/null +++ b/nova_vm/src/small_string.rs @@ -0,0 +1,43 @@ +#[derive(Clone, Copy)] +pub struct SmallString { + data: [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 { + self.data.iter().position(|byte| *byte == 0).unwrap_or(7) + } + + pub fn as_bytes(&self) -> &[u8] { + &self.data[0..self.len()] + } + + pub fn as_str(&self) -> &str { + // SAFETY: Guaranteed to be valid UTF-8. + unsafe { std::str::from_utf8_unchecked(self.as_bytes()) } + } + + pub(crate) fn from_str_unchecked(value: &str) -> Self { + assert!(value.len() < 8); + let mut data: [u8; 7] = [0, 0, 0, 0, 0, 0, 0]; + data.copy_from_slice(value.as_bytes()); + Self { data } + } +} + +impl TryFrom<&str> for SmallString { + type Error = (); + fn try_from(value: &str) -> Result { + if value.len() < 8 { + Ok(Self::from_str_unchecked(value)) + } else { + 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..f7f88309 --- /dev/null +++ b/nova_vm/src/types.rs @@ -0,0 +1,17 @@ +mod language; +mod spec; + +pub use language::Value; +pub use spec::{Base, Reference, ReferencedName}; + +impl From for Value { + fn from(value: Object) -> Self { + todo!() + } +} + +#[derive(Debug)] +pub struct Object; + +#[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..f96f3e0c --- /dev/null +++ b/nova_vm/src/types/language.rs @@ -0,0 +1,3 @@ +mod value; + +pub use value::Value; diff --git a/nova_vm/src/types/language/value.rs b/nova_vm/src/types/language/value.rs new file mode 100644 index 00000000..130c3945 --- /dev/null +++ b/nova_vm/src/types/language/value.rs @@ -0,0 +1,122 @@ +use crate::{ + heap::{ + ArrayHeapData, BigIntHeapData, Handle, NumberHeapData, ObjectHeapData, StringHeapData, + SymbolHeapData, + }, + SmallInteger, SmallString, +}; + +/// 6.1 ECMAScript Language Types +/// https://tc39.es/ecma262/#sec-ecmascript-language-types +#[derive(Debug, Clone, Copy)] +pub enum Value { + /// 6.1.1 The Undefined Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-undefined-type + Undefined, + + /// 6.1.2 The Null Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-null-type + Null, + + /// 6.1.3 The Boolean Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-boolean-type + Boolean(bool), + + /// 6.1.4 The String Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-string-type + String(Handle), + SmallString(SmallString), + + /// 6.1.5 The Symbol Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-symbol-type + Symbol(Handle), + + /// 6.1.6.1 The Number Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type + Number(Handle), + IntegerNumber(SmallInteger), // 56-bit signed integer. + FloatNumber(f32), + + /// 6.1.6.2 The BigInt Type + /// https://tc39.es/ecma262/#sec-ecmascript-language-types-bigint-type + BigInt(Handle), + + /// 6.1.7 The Object Type + /// https://tc39.es/ecma262/#sec-object-type + Object(Handle), + ArrayObject(Handle), +} + +impl Value { + pub const fn nan() -> Self { + Self::FloatNumber(f32::NAN) + } + + pub const fn infinity() -> Self { + Self::FloatNumber(f32::INFINITY) + } + + pub const fn neg_infinity() -> Self { + Self::FloatNumber(f32::NEG_INFINITY) + } +} + +impl From> for Value { + fn from(value: Option) -> Self { + value.unwrap_or(Value::Undefined) + } +} + +impl TryFrom<&str> for Value { + type Error = (); + fn try_from(value: &str) -> Result { + if let Ok(data) = value.try_into() { + Ok(Value::SmallString(data)) + } else { + Err(()) + } + } +} + +impl TryFrom for Value { + type Error = (); + fn try_from(value: f64) -> Result { + // TODO: verify logic + if value as f32 as f64 == value { + Ok(Value::FloatNumber(value as f32)) + } else { + Err(()) + } + } +} + +impl From for Value { + fn from(value: f32) -> Self { + Value::FloatNumber(value) + } +} + +impl TryFrom for Value { + type Error = (); + fn try_from(value: i64) -> Result { + Ok(Value::IntegerNumber(SmallInteger::try_from(value)?)) + } +} + +macro_rules! impl_value_from_n { + ($size: ty) => { + impl From<$size> for Value { + fn from(value: $size) -> Self { + let n: i64 = value.into(); + Value::IntegerNumber(SmallInteger::from_i64_unchecked(n)) + } + } + }; +} + +impl_value_from_n!(u8); +impl_value_from_n!(i8); +impl_value_from_n!(u16); +impl_value_from_n!(i16); +impl_value_from_n!(u32); +impl_value_from_n!(i32); diff --git a/nova_vm/src/types/spec.rs b/nova_vm/src/types/spec.rs new file mode 100644 index 00000000..f87bdbda --- /dev/null +++ b/nova_vm/src/types/spec.rs @@ -0,0 +1,3 @@ +mod reference; + +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/reference.rs b/nova_vm/src/types/spec/reference.rs new file mode 100644 index 00000000..a2e2d434 --- /dev/null +++ b/nova_vm/src/types/spec/reference.rs @@ -0,0 +1,66 @@ +use crate::{ + execution::Environment, + types::{Symbol, Value}, +}; +use oxc_span::Atom; + +/// 6.2.5 The Reference Record Specification Type +/// https://tc39.es/ecma262/#sec-reference-record-specification-type +#[derive(Debug)] +pub struct Reference { + /// [[Base]] + pub base: Base, + + /// [[ReferencedName]] + pub referenced_name: ReferencedName, + + /// [[Strict]] + pub strict: bool, + + /// [[ThisValue]] + pub this_value: Option, +} + +impl Reference { + /// 6.2.5.1 IsPropertyReference ( V ) + /// https://tc39.es/ecma262/#sec-ispropertyreference + pub fn is_property_reference(self) -> bool { + match (self.base) { + // 1. if V.[[Base]] is unresolvable, return false. + Base::Unresolvable => false, + + // 2. If V.[[Base]] is an Environment Record, return false; otherwise return true. + Base::Environment(_) => false, + _ => true, + } + } + + /// 6.2.5.2 IsUnresolvableReference ( V ) + /// https://tc39.es/ecma262/#sec-isunresolvablereference + pub fn is_unresolvable_reference(self) -> bool { + // 1. If V.[[Base]] is unresolvable, return true; otherwise return false. + return matches!(self.base, Base::Unresolvable); + } + + /// 6.2.5.3 IsSuperReference ( V ) + /// https://tc39.es/ecma262/#sec-issuperreference + pub fn is_super_reference(self) -> bool { + // 1. If V.[[ThisValue]] is not empty, return true; otherwise return false. + return !matches!(self.this_value, None); + } +} + +#[derive(Debug)] +pub enum Base { + Value(Value), + Environment(Environment), + Unresolvable, +} + +#[derive(Debug)] +pub enum ReferencedName { + String(Atom), + Symbol(Symbol), + // TODO: implement private names + PrivateName, +} diff --git a/nova_vm/src/value.rs b/nova_vm/src/value.rs deleted file mode 100644 index 9d700094..00000000 --- a/nova_vm/src/value.rs +++ /dev/null @@ -1,370 +0,0 @@ -use crate::{heap::Heap, stack_string::StackString, Type, VM}; -use std::{fmt::Debug, mem::size_of}; - -// TODO(@aapoalas): Use transparent struct (u32)'s to ensure proper indexing. -pub type ArrayIndex = u32; -pub type BigIntIndex = u32; -pub type DateIndex = u32; -pub type ErrorIndex = u32; -pub type FunctionIndex = u32; -pub type NumberIndex = u32; -pub type ObjectIndex = u32; -pub type RegExpIndex = u32; -pub type StringIndex = u32; -pub type SymbolIndex = u32; - -#[derive(Clone)] -#[repr(u8)] -pub enum Value { - Array(ArrayIndex), - BigIntObject(u32), // TODO: Implement primitive value objects :( - BooleanObject(u32), // TODO: Implement primitive value objects :( - Boolean(bool), - Date(DateIndex), - EmptyString, - Error(ErrorIndex), - Function(FunctionIndex), - HeapBigInt(BigIntIndex), - HeapNumber(NumberIndex), - HeapString(StringIndex), - Infinity, - NaN, - NegativeInfinity, - NegativeZero, - Null, - NumberObject(u32), // TODO: Implement primitive value objects :( - Object(ObjectIndex), - RegExp(RegExpIndex), - StackString(StackString), - // TOO: Extend these to i56. - SmallBigInt(i32), - SmallBigIntU(u32), - // TODO: Extend these to i48 or even i56. - // i56 would provide safe integer operations - // superior to f64 but is no longer spec-compliant. - Smi(i32), - SmiU(u32), - StringObject(u32), // TODO: Implement primitive value objects :( - Symbol(SymbolIndex), - SymbolObject(u32), // TODO: Implement primitive value objects :( - Undefined, -} - -/// We want to guarantee that all handles to JS values are register sized. This assert must never be removed or broken. -const _VALUE_SIZE_IS_WORD: () = assert!(size_of::() == size_of::()); -// We may also want to keep Option register sized to allow returning it in some cases. -// This may not be possible in the long run and it may not be necessary as we might want to use Undefined instead. -const _OPTIONAL_VALUE_SIZE_IS_WORD: () = assert!(size_of::>() == size_of::()); - -impl Value { - pub fn new_string(heap: &mut Heap, message: &str) -> Value { - if let Some(ascii_string) = StackString::try_from_str(message) { - Value::StackString(ascii_string) - } else { - Value::HeapString(heap.alloc_string(message)) - } - } - - pub fn create_exception(heap: &mut Heap, message: &str) -> Value { - Value::HeapString(heap.alloc_string(message)) - } - - pub fn get_type(&self) -> Type { - match self { - Value::Boolean(_) => Type::Boolean, - Value::EmptyString | Value::HeapString(_) | Value::StackString(_) => Type::String, - Value::Function(_) => Type::Function, - Value::HeapNumber(_) - | Value::Infinity - | Value::NaN - | Value::NegativeInfinity - | Value::NegativeZero - | Value::Smi(_) - | Value::SmiU(_) => Type::Number, - Value::Null => Type::Null, - Value::Object(_) - | Value::Array(_) - | Value::BigIntObject(_) - | Value::BooleanObject(_) - | Value::Date(_) - | Value::Error(_) - | Value::NumberObject(_) - | Value::RegExp(_) - | Value::StringObject(_) - | Value::SymbolObject(_) => Type::Object, - Value::HeapBigInt(_) | Value::SmallBigInt(_) | Value::SmallBigIntU(_) => Type::BigInt, - Value::Symbol(_) => Type::Symbol, - Value::Undefined => Type::Undefined, - } - } - - /// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-islooselyequal - pub fn is_loosely_equal(&self, vm: &mut VM, other: &Value) -> JsResult { - if self.get_type() == other.get_type() { - return self.is_strictly_equal(vm, other); - } - - Ok(match (self, other) { - (Value::Null | Value::Undefined, Value::Null | Value::Undefined) => true, - ( - Value::SmallBigInt(this) | Value::Smi(this), - Value::SmallBigInt(that) | Value::Smi(that), - ) => this == that, - ( - Value::SmallBigIntU(this) | Value::SmiU(this), - Value::SmallBigIntU(that) | Value::SmiU(that), - ) => this == that, - ( - Value::SmallBigInt(this) | Value::Smi(this), - Value::SmallBigIntU(that) | Value::SmiU(that), - ) => *this as u32 == *that, - ( - Value::SmallBigIntU(this) | Value::SmiU(this), - Value::SmallBigInt(that) | Value::Smi(that), - ) => *this == *that as u32, - (&Value::HeapBigInt(x), &Value::HeapNumber(y)) => { - let big_int = &vm.heap.bigints[x as usize]; - let number = &vm.heap.numbers[y as usize]; - big_int.as_ref().unwrap().try_into_f64() == Some(number.as_ref().unwrap().value()) - } - (&Value::HeapNumber(x), &Value::HeapBigInt(y)) => { - let big_int = &vm.heap.bigints[y as usize]; - let number = &vm.heap.numbers[x as usize]; - big_int.as_ref().unwrap().try_into_f64() == Some(number.as_ref().unwrap().value()) - } - (Value::HeapNumber(_), Value::HeapString(_)) => todo!("use ToNumber() intrinsics"), - (Value::HeapString(_), Value::HeapNumber(_)) => todo!("use ToNumber() intrinsics"), - (Value::HeapBigInt(_), Value::HeapString(_)) => { - todo!("use StringToBigInt() intrinsics") - } - (Value::HeapString(_), Value::HeapBigInt(_)) => other.is_loosely_equal(vm, self)?, - (Value::Boolean(_), _) => { - let self_as_f64 = self.try_into_f64(vm)?; - Value::from_f64(&mut vm.heap, self_as_f64).is_loosely_equal(vm, other)? - } - (_, Value::Boolean(_)) => { - let other_as_f64 = other.try_into_f64(vm)?; - Value::from_f64(&mut vm.heap, other_as_f64).is_loosely_equal(vm, self)? - } - ( - Value::HeapString(_) - | Value::HeapNumber(_) - | Value::HeapBigInt(_) - | Value::Symbol(_), - _, - ) => other.is_loosely_equal(vm, &self.to_primitive()?)?, - ( - Value::Object(_), - Value::HeapString(_) - | Value::HeapNumber(_) - | Value::HeapBigInt(_) - | Value::Symbol(_), - ) => self.to_primitive()?.is_loosely_equal(vm, other)?, - _ => false, - }) - } - - /// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-isstrictlyequal - pub fn is_strictly_equal(&self, vm: &VM, other: &Value) -> JsResult { - if self.get_type() != other.get_type() { - return Ok(false); - } - - Ok(match (self, other) { - (Value::SmiU(n1), Value::NegativeZero) | (Value::NegativeZero, Value::SmiU(n1)) => { - *n1 == 0 - } - (Value::Smi(n1) | Value::SmallBigInt(n1), Value::Smi(n2) | Value::SmallBigInt(n2)) => { - n1 == n2 - } - ( - Value::SmiU(n1) | Value::SmallBigIntU(n1), - Value::SmiU(n2) | Value::SmallBigIntU(n2), - ) => n1 == n2, - - (Value::HeapNumber(n1), Value::HeapNumber(n2)) => { - n1 == n2 - || vm.heap.numbers[*n1 as usize].as_ref().unwrap().value() - == vm.heap.numbers[*n2 as usize].as_ref().unwrap().value() - } - - // https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevaluenonnumber - (Value::Null | Value::Undefined, _) => true, - (Value::HeapBigInt(n1), Value::HeapBigInt(n2)) => n1 == n2, - (Value::HeapString(s1), Value::HeapString(s2)) => { - s1 == s2 - || vm.heap.strings[*s1 as usize].as_ref().unwrap().data - == vm.heap.strings[*s2 as usize].as_ref().unwrap().data - } - (Value::Boolean(b1), Value::Boolean(b2)) => b1 == b2, - // TODO: implement x is y procedures - (Value::Object(obj1), Value::Object(obj2)) => obj1 == obj2, - _ => false, - }) - } - - pub fn to_primitive(&self) -> JsResult { - Ok(Value::Null) - } - - /// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-toboolean - pub fn to_boolean(&self) -> Value { - match self { - &Value::Boolean(b) => Value::Boolean(b), - &Value::SmiU(n) => Value::Boolean(n == 0), - Value::Null | Value::EmptyString | Value::NaN | Value::NegativeZero => { - Value::Boolean(false) - } - _ => Value::Boolean(true), - } - } - - /// https://tc39.es/ecma262/multipage/abstract-operations.html#sec-tonumber - pub fn to_number(&self, _vm: &mut VM) -> JsResult { - Ok(match self { - Value::HeapNumber(_) - | Value::Smi(_) - | Value::SmiU(_) - | Value::Infinity - | Value::NegativeInfinity - | Value::NegativeZero => self.clone(), - Value::Function(_) - | Value::Symbol(_) - | Value::HeapBigInt(_) - | Value::SmallBigInt(_) - | Value::SmallBigIntU(_) => todo!("type error"), - Value::Undefined | Value::NaN => Value::NaN, - Value::Null | Value::Boolean(false) | Value::EmptyString => Value::SmiU(0), - Value::Boolean(true) => Value::SmiU(1), - Value::StackString(_) | Value::HeapString(_) => todo!("parse number from string"), - Value::Object(_) | Value::Error(_) => todo!("call valueOf"), - _ => todo!("implement primitive value objects :("), - }) - } - - pub fn from_f64(heap: &mut Heap, value: f64) -> Value { - let is_int = value.fract() == 0.0; - if value.is_nan() { - Value::NaN - } else if value.is_infinite() { - if value.is_sign_positive() { - Value::Infinity - } else { - Value::NegativeInfinity - } - } else if !is_int || value > u32::MAX as f64 || value < i32::MIN as f64 { - Value::HeapNumber(heap.alloc_number(value)) - } else if value.is_sign_positive() { - Value::SmiU(value as u32) - } else { - Value::Smi(value as i32) - } - } - - pub fn try_into_f64(&self, vm: &mut VM) -> JsResult { - match self { - &Value::HeapNumber(n) => Ok(vm.heap.numbers[n as usize].as_ref().unwrap().value()), - &Value::Smi(n) => Ok(n as f64), - &Value::SmiU(n) => Ok(n as f64), - Value::Infinity => Ok(f64::INFINITY), - Value::NegativeInfinity => Ok(f64::NEG_INFINITY), - Value::NegativeZero => Ok(0.), - Value::Undefined | Value::NaN => Ok(f64::NAN), - Value::Function(_) - | Value::Symbol(_) - | Value::HeapBigInt(_) - | Value::SmallBigInt(_) - | Value::SmallBigIntU(_) => todo!("type error"), - Value::Null | Value::Boolean(false) | Value::EmptyString => Ok(0.), - Value::Boolean(true) => Ok(1.), - Value::StackString(_) | Value::HeapString(_) => todo!("parse number from string"), - Value::Object(_) => todo!("call valueOf"), - _ => todo!("sigh"), - } - } - - pub fn into_bool(&self) -> bool { - match self { - &Value::Boolean(b) => b, - &Value::SmiU(n) => n == 0, - Value::Null | Value::EmptyString | Value::NaN | Value::NegativeZero => false, - _ => true, - } - } - - pub fn from_u32(value: u32) -> Value { - Value::SmiU(value) - } - - pub fn from_i32(value: i32) -> Value { - if value >= 0 { - Value::from_u32(value as u32) - } else { - Value::Smi(value) - } - } - - pub fn is_undefined(&self) -> bool { - match self { - Value::Undefined => true, - _ => false, - } - } - - pub fn is_null(&self) -> bool { - match self { - Value::Null => true, - _ => false, - } - } - - pub fn is_number(&self) -> bool { - match self { - Value::Smi(_) => true, - Value::SmiU(_) => true, - Value::NaN => true, - Value::Infinity => true, - Value::NegativeInfinity => true, - Value::NegativeZero => true, - Value::HeapNumber(_) => true, - _ => false, - } - } -} - -pub type JsResult = std::result::Result; - -impl Debug for Value { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Value::Array(arg0) => f.debug_tuple("Array").field(arg0).finish(), - Value::BigIntObject(arg0) => f.debug_tuple("BigIntObject").field(arg0).finish(), - Value::Boolean(arg0) => f.debug_tuple("Boolean").field(arg0).finish(), - Value::BooleanObject(arg0) => f.debug_tuple("BooleanObject").field(arg0).finish(), - Value::EmptyString => write!(f, "EmptyString"), - Value::Date(arg0) => f.debug_tuple("Date").field(arg0).finish(), - Value::Error(arg0) => f.debug_tuple("Error").field(arg0).finish(), - Value::Function(arg0) => f.debug_tuple("Function").field(arg0).finish(), - Value::HeapBigInt(arg0) => f.debug_tuple("BigInt").field(arg0).finish(), - Value::HeapNumber(arg0) => f.debug_tuple("Number").field(arg0).finish(), - Value::HeapString(arg0) => f.debug_tuple("String").field(arg0).finish(), - Value::Infinity => write!(f, "Infinity"), - Value::NaN => write!(f, "NaN"), - Value::NegativeInfinity => write!(f, "-Infinity"), - Value::NegativeZero => write!(f, "-0"), - Value::Null => write!(f, "Null"), - Value::NumberObject(arg0) => f.debug_tuple("NumberObject").field(arg0).finish(), - Value::Object(arg0) => f.debug_tuple("Object").field(arg0).finish(), - Value::RegExp(arg0) => f.debug_tuple("RegExp").field(arg0).finish(), - Value::StackString(arg0) => f.debug_tuple("SmallAsciiString").field(arg0).finish(), - Value::SmallBigInt(arg0) => f.debug_tuple("SmallBigInt").field(arg0).finish(), - Value::SmallBigIntU(arg0) => f.debug_tuple("SmallBigIntU").field(arg0).finish(), - Value::Smi(arg0) => f.debug_tuple("Smi").field(arg0).finish(), - Value::SmiU(arg0) => f.debug_tuple("SmiU").field(arg0).finish(), - Value::StringObject(arg0) => f.debug_tuple("StringObject").field(arg0).finish(), - Value::Symbol(arg0) => f.debug_tuple("Symbol").field(arg0).finish(), - Value::SymbolObject(arg0) => f.debug_tuple("SymbolObject").field(arg0).finish(), - Value::Undefined => write!(f, "Undefined"), - } - } -} From d1051f66c4150501df406f5eb3a11957e39b1eeb Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Mon, 24 Jul 2023 11:16:24 -0500 Subject: [PATCH 02/21] fix small string --- nova_vm/src/small_string.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova_vm/src/small_string.rs b/nova_vm/src/small_string.rs index 8f9f4333..b9698f60 100644 --- a/nova_vm/src/small_string.rs +++ b/nova_vm/src/small_string.rs @@ -24,7 +24,7 @@ impl SmallString { } pub(crate) fn from_str_unchecked(value: &str) -> Self { - assert!(value.len() < 8); + debug_assert!(value.len() < 8 && !value.as_bytes().contains(&0)); let mut data: [u8; 7] = [0, 0, 0, 0, 0, 0, 0]; data.copy_from_slice(value.as_bytes()); Self { data } @@ -34,7 +34,7 @@ impl SmallString { impl TryFrom<&str> for SmallString { type Error = (); fn try_from(value: &str) -> Result { - if value.len() < 8 { + if value.len() < 8 && !value.as_bytes().contains(&0) { Ok(Self::from_str_unchecked(value)) } else { Err(()) From b60617c3c1b80f3fe1f1aa21e818548dc4259022 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Mon, 24 Jul 2023 11:52:23 -0500 Subject: [PATCH 03/21] fix small string Co-authored-by: Aapo Alasuutari --- nova_vm/src/small_string.rs | 75 +++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 12 deletions(-) diff --git a/nova_vm/src/small_string.rs b/nova_vm/src/small_string.rs index b9698f60..dbfad321 100644 --- a/nova_vm/src/small_string.rs +++ b/nova_vm/src/small_string.rs @@ -1,6 +1,6 @@ #[derive(Clone, Copy)] pub struct SmallString { - data: [u8; 7], + bytes: [u8; 7], } impl std::fmt::Debug for SmallString { @@ -11,33 +11,84 @@ impl std::fmt::Debug for SmallString { impl SmallString { pub fn len(&self) -> usize { - self.data.iter().position(|byte| *byte == 0).unwrap_or(7) + // 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 ASCII, which is a subset of UTF-8. + unsafe { &std::str::from_utf8_unchecked(self.as_bytes()) } + } + + #[inline] pub fn as_bytes(&self) -> &[u8] { - &self.data[0..self.len()] + &self.bytes.as_slice().split_at(self.len()).0 } - pub fn as_str(&self) -> &str { - // SAFETY: Guaranteed to be valid UTF-8. - unsafe { std::str::from_utf8_unchecked(self.as_bytes()) } + #[inline] + pub fn data(&self) -> &[u8; 7] { + return &self.bytes; } - pub(crate) fn from_str_unchecked(value: &str) -> Self { - debug_assert!(value.len() < 8 && !value.as_bytes().contains(&0)); - let mut data: [u8; 7] = [0, 0, 0, 0, 0, 0, 0]; - data.copy_from_slice(value.as_bytes()); - Self { data } + pub(crate) fn from_str_unchecked(string: &str) -> Self { + let string_bytes = string.as_bytes(); + + // We have only 7 bytes to work with, and we cannot tell apart + // UTF-8 strings that end with a null byte from our null + // terminator so we must fail to convert on those. + debug_assert!(string_bytes.len() < 8 && string_bytes.last() != Some(&0)); + + let mut bytes = [0, 0, 0, 0, 0, 0, 0]; + bytes + .as_mut_slice() + .split_at_mut(string_bytes.len()) + .0 + .copy_from_slice(string_bytes); + + Self { bytes } } } impl TryFrom<&str> for SmallString { type Error = (); fn try_from(value: &str) -> Result { - if value.len() < 8 && !value.as_bytes().contains(&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. + 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()); +} From 25fddb80a301a40537c9a7c3a91993a968add277 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Mon, 24 Jul 2023 12:54:40 -0500 Subject: [PATCH 04/21] fix small integer Co-authored-by: Aapo Alasuutari --- nova_vm/src/small_integer.rs | 66 ++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/nova_vm/src/small_integer.rs b/nova_vm/src/small_integer.rs index 3862950e..46df5fdc 100644 --- a/nova_vm/src/small_integer.rs +++ b/nova_vm/src/small_integer.rs @@ -1,5 +1,5 @@ /// 56-bit signed integer. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq)] pub struct SmallInteger { data: [u8; 7], } @@ -11,18 +11,23 @@ impl std::fmt::Debug for SmallInteger { } impl SmallInteger { - pub const MIN: i64 = -(2 as i64).pow(56) / 2 + 1; - pub const MAX: i64 = (2 as i64).pow(56) / 2 - 1; + pub const MIN: i64 = -(2 as i64).pow(53) / 2 + 1; + pub const MAX: i64 = (2 as i64).pow(53) / 2 - 1; pub(crate) fn from_i64_unchecked(value: i64) -> SmallInteger { debug_assert!(value >= Self::MIN && value <= Self::MAX); - let mut data: [u8; 7] = [0, 0, 0, 0, 0, 0, 0]; let bytes = i64::to_ne_bytes(value); - if cfg!(target_endian = "little") { - data.copy_from_slice(&bytes[0..7]); + + let data = if cfg!(target_endian = "little") { + [ + bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], + ] } else { - data.copy_from_slice(&bytes[1..8]); - } + [ + bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], + ] + }; + Self { data } } } @@ -41,12 +46,45 @@ impl TryFrom for SmallInteger { impl Into for SmallInteger { fn into(self) -> i64 { let Self { data } = self; - let mut bytes: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; - bytes.copy_from_slice(&data); - if cfg!(target_endian = "big") { - bytes.copy_within(0..7, 1); + + #[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 } - // SAFETY: The format is guaranteed to match `from_i64_unchecked`. - unsafe { std::mem::transmute(bytes) } } } + +#[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, + SmallInteger::try_from(SmallInteger::MAX).unwrap().into() + ); + + assert_eq!(-5i64, SmallInteger::try_from(-5).unwrap().into()); + assert_eq!(-59i64, SmallInteger::try_from(-59).unwrap().into()); + assert_eq!( + SmallInteger::MIN, + SmallInteger::try_from(SmallInteger::MIN).unwrap().into() + ); +} + +#[test] +fn invalid_small_integers() { + assert_eq!(SmallInteger::try_from(SmallInteger::MAX + 1), Err(())); + assert_eq!(SmallInteger::try_from(i64::MAX), Err(())); + assert_eq!(SmallInteger::try_from(SmallInteger::MIN - 1), Err(())); + assert_eq!(SmallInteger::try_from(i64::MIN), Err(())); +} From b3b81053e3e364908ce71e23aabefbec7eb212f1 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Mon, 24 Jul 2023 22:07:25 -0500 Subject: [PATCH 05/21] more work --- nova_vm/src/execution/agent.rs | 16 + nova_vm/src/execution/realm.rs | 2 +- nova_vm/src/heap.rs | 59 ++- nova_vm/src/language/script.rs | 4 +- nova_vm/src/small_integer.rs | 5 + nova_vm/src/small_string.rs | 4 +- nova_vm/src/types.rs | 2 +- nova_vm/src/types/language.rs | 6 + nova_vm/src/types/language/bigint.rs | 4 + nova_vm/src/types/language/number.rs | 669 +++++++++++++++++++++++++++ nova_vm/src/types/language/string.rs | 77 +++ nova_vm/src/types/language/value.rs | 488 ++++++++++++++++++- 12 files changed, 1309 insertions(+), 27 deletions(-) create mode 100644 nova_vm/src/types/language/bigint.rs create mode 100644 nova_vm/src/types/language/number.rs create mode 100644 nova_vm/src/types/language/string.rs diff --git a/nova_vm/src/execution/agent.rs b/nova_vm/src/execution/agent.rs index e34d42c1..005af431 100644 --- a/nova_vm/src/execution/agent.rs +++ b/nova_vm/src/execution/agent.rs @@ -41,4 +41,20 @@ impl Agent<'_, '_> { pub fn current_realm(&self) -> &mut Realm { todo!() } + + /// 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/realm.rs b/nova_vm/src/execution/realm.rs index 07e037dd..0edbf4bb 100644 --- a/nova_vm/src/execution/realm.rs +++ b/nova_vm/src/execution/realm.rs @@ -1,7 +1,7 @@ mod intrinsics; use super::{Agent, GlobalEnvironment}; -use crate::types::Object; +use crate::{types::Object, Heap}; use intrinsics::Intrinsics; use std::{ any::Any, diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 47e68285..b1b24e06 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -15,9 +15,9 @@ pub use string::StringHeapData; pub use symbol::SymbolHeapData; use self::heap_trace::HeapTrace; -use crate::types::Value; +use crate::types::{Number, String, Value}; use std::{cell::Cell, marker::PhantomData}; -use wtf8::Wtf8; +use wtf8::{Wtf8, Wtf8Buf}; /// A handle to GC-managed memory. #[derive(Clone)] @@ -72,34 +72,65 @@ pub struct Heap { fn stop_the_world() {} fn start_the_world() {} -/// Creates a [`Value`] from the given data. Allocating the data is **not** -/// guaranteed. -pub trait CreateHeapData { - fn create(&mut self, data: T) -> Value; +pub trait CreateHeapData { + /// Creates a [`Value`] from the given data. Allocating the data is **not** + /// guaranteed. + fn create(&mut self, data: T) -> F; } -impl CreateHeapData for Heap { - fn create(&mut self, data: f64) -> Value { +pub trait GetHeapData<'a, T, F: 'a> { + fn get(&'a self, handle: Handle) -> F; +} + +impl CreateHeapData for Heap { + fn create(&mut self, data: f64) -> Number { if let Ok(value) = Value::try_from(data) { - value + Number::new(value) + } else if data as f32 as f64 == data { + Number::new(Value::FloatNumber(data as f32)) } else { let id = self.alloc_number(data); - Value::Number(Handle::new(id)) + Number::new(Value::Number(Handle::new(id))) } } } -impl CreateHeapData<&str> for Heap { - fn create(&mut self, data: &str) -> Value { - if let Ok(value) = Value::try_from(data) { +impl<'a> GetHeapData<'a, NumberHeapData, f64> for Heap { + fn get(&'a self, handle: Handle) -> f64 { + self.numbers + .get(handle.id as usize) + .as_ref() + .unwrap() + .as_ref() + .unwrap() + .data + } +} + +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(Handle::new(id)) + String::new(Value::String(Handle::new(id))) } } } +impl<'a> GetHeapData<'a, StringHeapData, &'a Wtf8> for Heap { + fn get(&'a self, handle: Handle) -> &'a Wtf8 { + let data = self + .strings + .get(handle.id as usize) + .as_ref() + .unwrap() + .as_ref() + .unwrap(); + &data.data.slice(0, data.data.len()) + } +} + impl Heap { pub fn new() -> Heap { let mut heap = Heap { diff --git a/nova_vm/src/language/script.rs b/nova_vm/src/language/script.rs index 64d758f3..5866458c 100644 --- a/nova_vm/src/language/script.rs +++ b/nova_vm/src/language/script.rs @@ -3,9 +3,7 @@ use crate::{ types::Value, }; use oxc_allocator::Allocator; -use oxc_ast::ast::{ - BindingPattern, BindingPatternKind, Declaration, Program, Statement, VariableDeclarationKind, -}; +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}; diff --git a/nova_vm/src/small_integer.rs b/nova_vm/src/small_integer.rs index 46df5fdc..c37a726a 100644 --- a/nova_vm/src/small_integer.rs +++ b/nova_vm/src/small_integer.rs @@ -14,6 +14,11 @@ impl SmallInteger { pub const MIN: i64 = -(2 as i64).pow(53) / 2 + 1; pub const MAX: i64 = (2 as i64).pow(53) / 2 - 1; + #[inline] + pub fn into_i64(self) -> i64 { + self.into() + } + pub(crate) fn from_i64_unchecked(value: i64) -> SmallInteger { debug_assert!(value >= Self::MIN && value <= Self::MAX); let bytes = i64::to_ne_bytes(value); diff --git a/nova_vm/src/small_string.rs b/nova_vm/src/small_string.rs index dbfad321..39a8ee9d 100644 --- a/nova_vm/src/small_string.rs +++ b/nova_vm/src/small_string.rs @@ -17,12 +17,12 @@ impl SmallString { .iter() .rev() .position(|&x| x != 0) - .map_or(0, |i| 7 - i) + .unwrap_or(7) } #[inline] pub fn as_str(&self) -> &str { - // SAFETY: Guaranteed to be ASCII, which is a subset of UTF-8. + // SAFETY: Guaranteed to be UTF-8. unsafe { &std::str::from_utf8_unchecked(self.as_bytes()) } } diff --git a/nova_vm/src/types.rs b/nova_vm/src/types.rs index f7f88309..44eb1d78 100644 --- a/nova_vm/src/types.rs +++ b/nova_vm/src/types.rs @@ -1,7 +1,7 @@ mod language; mod spec; -pub use language::Value; +pub use language::{Number, String, Value}; pub use spec::{Base, Reference, ReferencedName}; impl From for Value { diff --git a/nova_vm/src/types/language.rs b/nova_vm/src/types/language.rs index f96f3e0c..05e805eb 100644 --- a/nova_vm/src/types/language.rs +++ b/nova_vm/src/types/language.rs @@ -1,3 +1,9 @@ +mod bigint; +mod number; +mod string; mod value; +pub use bigint::BigInt; +pub use number::Number; +pub use string::String; pub use value::Value; diff --git a/nova_vm/src/types/language/bigint.rs b/nova_vm/src/types/language/bigint.rs new file mode 100644 index 00000000..9e8efb01 --- /dev/null +++ b/nova_vm/src/types/language/bigint.rs @@ -0,0 +1,4 @@ +use super::Value; + +#[derive(Clone, Copy)] +pub struct BigInt(Value); diff --git a/nova_vm/src/types/language/number.rs b/nova_vm/src/types/language/number.rs new file mode 100644 index 00000000..a3e14d4b --- /dev/null +++ b/nova_vm/src/types/language/number.rs @@ -0,0 +1,669 @@ +use super::Value; +use crate::{ + execution::{Agent, JsResult}, + heap::{CreateHeapData, GetHeapData}, + SmallInteger, +}; + +/// 6.1.6.1 The Number Type +/// https://tc39.es/ecma262/#sec-ecmascript-language-types-number-type +#[derive(Clone, Copy)] +pub struct Number(Value); + +impl std::fmt::Debug for Number { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl From for Number { + fn from(value: SmallInteger) -> Self { + Number(Value::IntegerNumber(value)) + } +} + +impl From for Number { + fn from(value: i32) -> Self { + Number(Value::IntegerNumber(SmallInteger::from_i64_unchecked( + value as i64, + ))) + } +} + +impl From for Number { + fn from(value: i64) -> Self { + let n = value.min(SmallInteger::MAX).max(SmallInteger::MIN); + Number(Value::IntegerNumber(SmallInteger::from_i64_unchecked(n))) + } +} + +impl From for Number { + fn from(value: f32) -> Self { + Number(Value::FloatNumber(value)) + } +} + +impl TryFrom for Number { + type Error = (); + fn try_from(value: Value) -> Result { + if matches!( + value, + Value::Number(_) | Value::IntegerNumber(_) | Value::FloatNumber(_) + ) { + Ok(Number(value)) + } else { + Err(()) + } + } +} + +impl Number { + pub(crate) fn new(value: Value) -> Self { + debug_assert!(matches!( + value, + Value::Number(_) | Value::IntegerNumber(_) | Value::FloatNumber(_) + )); + Self(value) + } + + pub fn nan() -> Self { + Self::from(f32::NAN) + } + + pub fn neg_zero() -> Self { + Self::from(-0.0) + } + + pub fn pos_zero() -> Self { + Self::from(0) + } + + pub fn pos_inf() -> Self { + Self::from(f32::INFINITY) + } + + pub fn neg_inf() -> Self { + Self::from(f32::NEG_INFINITY) + } + + pub fn into_value(self) -> Value { + self.0 + } + + pub fn is_nan(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => agent.heap.get(n).is_nan(), + Value::IntegerNumber(_) => false, + Value::FloatNumber(n) => n.is_nan(), + _ => unreachable!(), + } + } + + pub fn is_pos_zero(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => agent.heap.get(n).is_sign_positive(), + Value::IntegerNumber(n) => 0i64 == n.into(), + Value::FloatNumber(n) => n.is_sign_positive(), + _ => unreachable!(), + } + } + + pub fn is_neg_zero(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => agent.heap.get(n).is_sign_negative(), + Value::IntegerNumber(_) => false, + Value::FloatNumber(n) => n.is_sign_negative(), + _ => unreachable!(), + } + } + + pub fn is_pos_infinity(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => agent.heap.get(n) == f64::INFINITY, + Value::IntegerNumber(_) => false, + Value::FloatNumber(n) => n == f32::INFINITY, + _ => unreachable!(), + } + } + + pub fn is_neg_infinity(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => agent.heap.get(n) == f64::NEG_INFINITY, + Value::IntegerNumber(_) => false, + Value::FloatNumber(n) => n == f32::NEG_INFINITY, + _ => unreachable!(), + } + } + + pub fn is_finite(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => agent.heap.get(n).is_finite(), + Value::IntegerNumber(_) => true, + Value::FloatNumber(n) => n.is_finite(), + _ => unreachable!(), + } + } + + pub fn is_nonzero(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => { + let n = agent.heap.get(n); + !n.is_sign_negative() && !n.is_sign_positive() + } + Value::IntegerNumber(_) => true, + Value::FloatNumber(n) => !n.is_sign_negative() && !n.is_sign_positive(), + _ => unreachable!(), + } + } + + /// https://tc39.es/ecma262/#eqn-truncate + /// + /// Guaranteed to be in the valid [`Value::IntegerNumber`] range. + pub fn truncate(self, agent: &mut Agent) -> i64 { + let x = self.into_value(); + + match x { + Value::Number(n) => agent.heap.get(n).trunc() as i64, + Value::IntegerNumber(n) => n.into_i64(), + Value::FloatNumber(n) => n.trunc() as i64, + _ => unreachable!(), + } + } + + pub fn into_f64(self, agent: &Agent) -> f64 { + let x = self.into_value(); + + match x { + Value::Number(n) => agent.heap.get(n), + Value::IntegerNumber(n) => Into::::into(n) as f64, + Value::FloatNumber(n) => n as f64, + _ => unreachable!(), + } + } + + /// A minimal version of ObjectIs when you know the arguments are numbers. + pub fn is(self, agent: &mut Agent, y: Self) -> bool { + // TODO: Add in spec from Object.is pertaining to numbers. + let x = self.into_value(); + let y = y.into_value(); + + match (x, y) { + (Value::Number(x), Value::Number(y)) => agent.heap.get(x) == agent.heap.get(y), + (Value::Number(x), Value::IntegerNumber(y)) => agent.heap.get(x) == y.into_i64() as f64, + (Value::Number(x), Value::FloatNumber(y)) => agent.heap.get(x) == y as f64, + (Value::IntegerNumber(x), Value::Number(y)) => { + (x.into_i64() as f64) == agent.heap.get(y) + } + (Value::IntegerNumber(x), Value::IntegerNumber(y)) => x.into_i64() == y.into_i64(), + (Value::IntegerNumber(x), Value::FloatNumber(y)) => (x.into_i64() as f64) == y as f64, + (Value::FloatNumber(x), Value::Number(y)) => (x as f64) == agent.heap.get(y), + (Value::FloatNumber(x), Value::IntegerNumber(y)) => (x as f64) == y.into_i64() as f64, + (Value::FloatNumber(x), Value::FloatNumber(y)) => x == y, + _ => unreachable!(), + } + } + + pub fn is_odd_integer(self, agent: &mut Agent) -> bool { + let x = self.into_value(); + + match x { + Value::Number(n) => { + let n = agent.heap.get(n); + n % 1.0 == 0.0 && n % 2.0 == 0.0 + } + Value::IntegerNumber(n) => Into::::into(n) % 2 == 0, + Value::FloatNumber(n) => n % 1.0 == 0.0 && n % 2.0 == 0.0, + _ => unreachable!(), + } + } + + pub fn abs(self, agent: &mut Agent) -> Self { + let x = self.into_value(); + + match x { + Value::Number(n) => { + let n = agent.heap.get(n); + if n > 0.0 { + self + } else { + agent.heap.create(-n) + } + } + Value::IntegerNumber(n) => { + let n = n.into_i64(); + Number(Value::IntegerNumber(SmallInteger::from_i64_unchecked( + n.abs(), + ))) + } + Value::FloatNumber(n) => Number(Value::FloatNumber(n.abs())), + _ => unreachable!(), + } + } + + pub fn greater_than(self, agent: &mut Agent, y: Self) -> Value { + let x = self; + y.less_than(agent, x) + } + + /// 6.1.6.1.1 Number::unaryMinus ( x ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-unaryMinus + pub fn unary_minus(self, agent: &mut Agent) -> Self { + let x = self.into_value(); + + // 1. If x is NaN, return NaN. + // NOTE: Computers do this automatically. + + // 2. Return the result of negating x; that is, compute a Number with the same magnitude but opposite sign. + match x { + Value::Number(n) => agent.heap.create(-agent.heap.get(n)), + Value::IntegerNumber(n) => SmallInteger::from_i64_unchecked(-n.into_i64()).into(), + Value::FloatNumber(n) => (-n).into(), + _ => unreachable!(), + } + } + + /// 6.1.6.1.2 Number::bitwiseNOT ( x ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseNOT + pub fn bitwise_not(self, agent: &mut Agent) -> JsResult { + let x = self.into_value(); + + // 1. Let oldValue be ! ToInt32(x). + let old_value = x.to_int32(agent)?; + + // 2. Return the result of applying bitwise complement to oldValue. The mathematical value of the result is exactly representable as a 32-bit two's complement bit string. + Ok(Number::from(!old_value)) + } + + /// 6.1.6.1.3 Number::exponentiate ( base, exponent ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-exponentiate + pub fn exponentiate(self, agent: &mut Agent, exponent: Self) -> Self { + let base = self; + + // 1. If exponent is NaN, return NaN. + if exponent.is_nan(agent) { + return Number::nan(); + } + + // 2. If exponent is either +0𝔽 or -0𝔽, return 1𝔽. + if exponent.is_pos_zero(agent) || exponent.is_neg_zero(agent) { + return Number::from(1); + } + + // 3. If base is NaN, return NaN. + if base.is_nan(agent) { + return Number::nan(); + } + + // 4. If base is +∞𝔽, then + if base.is_pos_infinity(agent) { + // a. If exponent > +0𝔽, return +∞𝔽. Otherwise, return +0𝔽. + return if exponent.greater_than(agent, Number::from(0)).is_true() { + Number::pos_inf() + } else { + Number::pos_zero() + }; + } + + // 5. If base is -∞𝔽, then + if base.is_neg_infinity(agent) { + // a. If exponent > +0𝔽, then + return if exponent.greater_than(agent, 0.into()).is_true() { + // i. If exponent is an odd integral Number, return -∞𝔽. Otherwise, return +∞𝔽. + if exponent.is_odd_integer(agent) { + Number::neg_inf() + } else { + Number::pos_inf() + } + } + // b. Else, + else { + // i. If exponent is an odd integral Number, return -0𝔽. Otherwise, return +0𝔽. + if exponent.is_odd_integer(agent) { + Number::neg_zero() + } else { + Number::pos_zero() + } + }; + } + + // 6. If base is +0𝔽, then + if base.is_pos_zero(agent) { + // a. If exponent > +0𝔽, return +0𝔽. Otherwise, return +∞𝔽. + return if exponent.greater_than(agent, Number::pos_zero()).is_true() { + Number::pos_zero() + } else { + Number::pos_inf() + }; + } + + // 7. If base is -0𝔽, then + if base.is_neg_zero(agent) { + // a. If exponent > +0𝔽, then + return if exponent.greater_than(agent, Number::pos_zero()).is_true() { + // i. If exponent is an odd integral Number, return -0𝔽. Otherwise, return +0𝔽. + if exponent.is_odd_integer(agent) { + Number::neg_zero() + } else { + Number::pos_zero() + } + } + // b. Else, + else { + // i. If exponent is an odd integral Number, return -∞𝔽. Otherwise, return +∞𝔽. + if exponent.is_odd_integer(agent) { + Number::neg_inf() + } else { + Number::pos_inf() + } + }; + } + + // 8. Assert: base is finite and is neither +0𝔽 nor -0𝔽. + debug_assert!(base.is_finite(agent) && base.is_nonzero(agent)); + + // 9. If exponent is +∞𝔽, then + if exponent.is_pos_infinity(agent) { + let base = base.abs(agent); + + // a. If abs(ℝ(base)) > 1, return +∞𝔽. + return if base.greater_than(agent, Number::from(1)).is_true() { + Number::pos_inf() + } + // b. If abs(ℝ(base)) = 1, return NaN. + else if base.is(agent, Number::from(1)) { + Number::nan() + } + // c. If abs(ℝ(base)) < 1, return +0𝔽. + else { + Number::pos_zero() + }; + } + + // 10. If exponent is -∞𝔽, then + if exponent.is_neg_infinity(agent) { + let base = base.into_f64(agent).abs(); + + // a. If abs(ℝ(base)) > 1, return +0𝔽. + return if base > 1.0 { + Number::pos_inf() + } + // b. If abs(ℝ(base)) = 1, return NaN. + else if base == 1.0 { + Number::nan() + } + // c. If abs(ℝ(base)) < 1, return +∞𝔽. + else { + Number::pos_inf() + }; + } + + // 11. Assert: exponent is finite and is neither +0𝔽 nor -0𝔽. + debug_assert!(exponent.is_finite(agent) && exponent.is_nonzero(agent)); + + // 12. If base < -0𝔽 and exponent is not an integral Number, return NaN. + if base.less_than(agent, Number::neg_zero()).is_true() && !exponent.is_odd_integer(agent) { + return Number::nan(); + } + + // 13. Return an implementation-approximated Number value representing the result of raising ℝ(base) to the ℝ(exponent) power. + agent + .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::IntegerNumber(y)) => agent.heap.get(x) < y.into_i64() as f64, + (Value::Number(x), Value::FloatNumber(y)) => agent.heap.get(x) < y as f64, + (Value::IntegerNumber(x), Value::Number(y)) => { + (x.into_i64() as f64) < agent.heap.get(y) + } + (Value::IntegerNumber(x), Value::IntegerNumber(y)) => x.into_i64() < y.into_i64(), + (Value::IntegerNumber(x), Value::FloatNumber(y)) => (x.into_i64() as f64) < y as f64, + (Value::FloatNumber(x), Value::Number(y)) => (x as f64) < agent.heap.get(y), + (Value::FloatNumber(x), Value::IntegerNumber(y)) => (x as f64) < y.into_i64() as f64, + (Value::FloatNumber(x), Value::FloatNumber(y)) => x < y, + _ => unreachable!(), + }) + } + + /// 6.1.6.1.13 Number::equal ( x, y ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-equal + pub fn equal(self, agent: &mut Agent, y: Self) -> bool { + let x = self; + + // 1. If x is NaN, return false. + if x.is_nan(agent) { + return false; + } + + // 2. If y is NaN, return false. + if y.is_nan(agent) { + return false; + } + + // 3. If x is y, return true. + if x.is(agent, y) { + return true; + } + + // 4. If x is +0𝔽 and y is -0𝔽, return true. + if x.is_pos_zero(agent) && y.is_neg_zero(agent) { + return true; + } + + // 5. If x is -0𝔽 and y is +0𝔽, return true. + if x.is_neg_zero(agent) && y.is_pos_zero(agent) { + return true; + } + + // 6. Return false. + return false; + } + + /// 6.1.6.1.14 Number::sameValue ( x, y ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-sameValue + pub fn same_value(self, agent: &mut Agent, y: Self) -> bool { + let x = self; + + // 1. If x is NaN and y is NaN, return true. + if x.is_nan(agent) && y.is_nan(agent) { + return true; + } + + // 2. If x is +0𝔽 and y is -0𝔽, return false. + if x.is_pos_zero(agent) && y.is_neg_zero(agent) { + return false; + } + + // 3. If x is -0𝔽 and y is +0𝔽, return false. + if x.is_neg_zero(agent) && y.is_pos_zero(agent) { + return false; + } + + // 4. If x is y, return true. + if x.is(agent, y) { + return true; + } + + // 5. Return false. + return false; + } + + /// 6.1.6.1.15 Number::sameValueZero ( x, y ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero + pub fn same_value_zero(self, agent: &mut Agent, y: Self) -> bool { + let x = self; + + // 1. If x is NaN and y is NaN, return true. + if x.is_nan(agent) && y.is_nan(agent) { + return true; + } + + // 2. If x is +0𝔽 and y is -0𝔽, return true. + if x.is_pos_zero(agent) && y.is_neg_zero(agent) { + return true; + } + + // 3. If x is -0𝔽 and y is +0𝔽, return true. + if x.is_neg_zero(agent) && y.is_pos_zero(agent) { + return true; + } + + // 4. If x is y, return true. + if x.is(agent, y) { + return true; + } + + // 5. Return false. + return false; + } + + /// 6.1.6.1.16 NumberBitwiseOp ( op, x, y ) + /// https://tc39.es/ecma262/#sec-numberbitwiseop + pub fn bitwise_op(self, agent: &mut Agent, op: BitwiseOp, y: Self) -> JsResult { + let x = self; + + // 1. Let lnum be ! ToInt32(x). + let lnum = x.into_value().to_int32(agent)?; + + // 2. Let rnum be ! ToInt32(y). + let rnum = y.into_value().to_int32(agent)?; + + // 3. Let lbits be the 32-bit two's complement bit string representing ℝ(lnum). + let lbits = lnum; + + // 4. Let rbits be the 32-bit two's complement bit string representing ℝ(rnum). + let rbits = rnum; + + let result = match op { + // 5. If op is &, then + BitwiseOp::And => { + // a. Let result be the result of applying the bitwise AND operation to lbits and rbits. + lbits & rbits + } + // 6. Else if op is ^, then + BitwiseOp::Xor => { + // a. Let result be the result of applying the bitwise exclusive OR (XOR) operation to lbits and rbits. + lbits ^ rbits + } + // 7. Else, + // a. Assert: op is |. + BitwiseOp::Or => { + // b. Let result be the result of applying the bitwise inclusive OR operation to lbits and rbits. + lbits | rbits + } + }; + + // 8. Return the Number value for the integer represented by the 32-bit two's complement bit string result. + Ok(Number::from(result)) + } + + /// 6.1.6.1.17 Number::bitwiseAND ( x, y ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseAND + pub fn bitwise_and(self, agent: &mut Agent, y: Self) -> JsResult { + let x = self; + + // 1. Return NumberBitwiseOp(&, x, y). + x.bitwise_op(agent, BitwiseOp::And, y) + } + + /// 6.1.6.1.18 Number::bitwiseXOR ( x, y ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseXOR + pub fn bitwise_xor(self, agent: &mut Agent, y: Self) -> JsResult { + let x = self; + + // 1. Return NumberBitwiseOp(^, x, y). + x.bitwise_op(agent, BitwiseOp::Xor, y) + } + + /// 6.1.6.1.19 Number::bitwiseOR ( x, y ) + /// https://tc39.es/ecma262/#sec-numeric-types-number-bitwiseOR + pub fn bitwise_or(self, agent: &mut Agent, y: Self) -> JsResult { + let x = self; + + // 1. Return NumberBitwiseOp(|, x, y). + x.bitwise_op(agent, BitwiseOp::Or, y) + } + + // ... +} + +#[derive(Debug, Clone, Copy)] +pub enum BitwiseOp { + And, + Xor, + Or, +} diff --git a/nova_vm/src/types/language/string.rs b/nova_vm/src/types/language/string.rs new file mode 100644 index 00000000..96cadffe --- /dev/null +++ b/nova_vm/src/types/language/string.rs @@ -0,0 +1,77 @@ +use super::Value; +use crate::{execution::Agent, heap::GetHeapData, SmallString}; + +/// 6.1.4 The String Type +/// https://tc39.es/ecma262/#sec-ecmascript-language-types-string-type +#[derive(Debug)] +pub struct String(Value); + +impl TryFrom<&str> for String { + type Error = (); + fn try_from(value: &str) -> Result { + SmallString::try_from(value).map(|s| String::new(Value::SmallString(s))) + } +} + +impl String { + pub(crate) fn new(value: Value) -> Self { + matches!(value, Value::String(_) | Value::SmallString(_)); + Self(value) + } + + pub fn into_value(self) -> Value { + self.0 + } + + /// Byte length of the string. + pub fn len(self, agent: &Agent) -> usize { + let s = self.into_value(); + + match s { + Value::String(s) => agent.heap.get(s).len(), + Value::SmallString(s) => s.len(), + _ => unreachable!(), + } + } + + pub fn as_str<'a>(&'a self, agent: &'a Agent) -> Option<&'a str> { + match &self.0 { + Value::String(s) => agent.heap.get(*s).as_str(), + Value::SmallString(s) => Some(s.as_str()), + _ => unreachable!(), + } + } + + /// 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 index 130c3945..1c230f89 100644 --- a/nova_vm/src/types/language/value.rs +++ b/nova_vm/src/types/language/value.rs @@ -1,4 +1,5 @@ use crate::{ + execution::{Agent, JsResult}, heap::{ ArrayHeapData, BigIntHeapData, Handle, NumberHeapData, ObjectHeapData, StringHeapData, SymbolHeapData, @@ -6,6 +7,8 @@ use crate::{ SmallInteger, SmallString, }; +use super::{BigInt, Number}; + /// 6.1 ECMAScript Language Types /// https://tc39.es/ecma262/#sec-ecmascript-language-types #[derive(Debug, Clone, Copy)] @@ -47,17 +50,490 @@ pub enum Value { ArrayObject(Handle), } +#[derive(Debug, Clone, Copy)] +pub enum PreferredType { + String, + Number, +} + impl Value { - pub const fn nan() -> Self { - Self::FloatNumber(f32::NAN) + pub fn nan() -> Self { + Number::nan().into_value() + } + + pub fn infinity() -> Self { + Number::pos_inf().into_value() + } + + pub fn neg_infinity() -> Self { + Number::neg_inf().into_value() + } + + pub fn is_true(self) -> bool { + matches!(self, Value::Boolean(true)) + } + + pub fn is_false(self) -> bool { + matches!(self, Value::Boolean(false)) + } + + pub fn is_object(self) -> bool { + matches!(self, Value::Object(_) | Value::ArrayObject(_)) + } + + 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(_)) + } + + /// 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ℤ and empty String + if argument.is_undefined() + || argument.is_null() + || argument.is_pos_zero(agent) + || argument.is_neg_zero(agent) + || argument.is_nan(agent) + { + return Ok(false.into()); + } + + // 3. NOTE: This step is replaced in section B.3.6.1. + + // 4. Return true. + return Ok(true.into()); } - pub const fn infinity() -> Self { - Self::FloatNumber(f32::INFINITY) + /// 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()) } - pub const fn neg_infinity() -> Self { - Self::FloatNumber(f32::NEG_INFINITY) + /// 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 = 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::from(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); + + // 4. Let int32bit be int modulo 2^32. + let int32bit = int % 2i64.pow(32); + + // 5. If int32bit ≥ 2^31, return 𝔽(int32bit - 2^32); otherwise return 𝔽(int32bit). + Ok(if int32bit >= 2i64.pow(32) { + int32bit - 2i64.pow(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); + + // 4. Let int32bit be int modulo 2^32. + let int32bit = int % 2i64.pow(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); + + // 4. Let int16bit be int modulo 2^16. + let int16bit = int % 2i64.pow(16); + + // 5. If int16bit ≥ 2^15, return 𝔽(int16bit - 2^16); otherwise return 𝔽(int16bit). + Ok(if int16bit >= 2i64.pow(15) { + int16bit - 2i64.pow(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); + + // 4. Let int16bit be int modulo 2^16. + let int16bit = int % 2i64.pow(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); + + // 4. Let int8bit be int modulo 2^8. + let int8bit = int % 2i64.pow(8); + + // 5. If int8bit ≥ 2^7, return 𝔽(int8bit - 2^8); otherwise return 𝔽(int8bit). + Ok(if int8bit >= 2i64.pow(7) { + int8bit - 2i64.pow(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); + + // 4. Let int8bit be int modulo 2^8. + let int8bit = int % 2i64.pow(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!() + } +} + +impl From for Value { + fn from(value: bool) -> Self { + Value::Boolean(value) } } From ec23e923c87945a03adac4eb2751299214c300c1 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Mon, 24 Jul 2023 22:11:36 -0500 Subject: [PATCH 06/21] little empty string bonus --- nova_vm/src/types/language/value.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/nova_vm/src/types/language/value.rs b/nova_vm/src/types/language/value.rs index 1c230f89..348782ef 100644 --- a/nova_vm/src/types/language/value.rs +++ b/nova_vm/src/types/language/value.rs @@ -125,6 +125,14 @@ impl Value { matches!(self, Value::Symbol(_)) } + pub fn is_empty_string(self) -> bool { + if let Value::SmallString(s) = self { + s.len() == 0 + } else { + false + } + } + /// 7.1.1 ToPrimitive ( input [ , preferredType ] ) /// https://tc39.es/ecma262/#sec-toprimitive pub fn to_primitive( @@ -197,12 +205,13 @@ impl Value { } // 2. If argument is one of undefined, null, +0𝔽, -0𝔽, NaN, 0ℤ, or the empty String, return false. - // TODO: checks for 0ℤ and empty String + // 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()); } From 3705170339caff73ede6fdbaf410ef8dc4681e22 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Mon, 24 Jul 2023 22:39:57 -0500 Subject: [PATCH 07/21] bank error in your favor. inherit $500 --- nova_vm/src/execution/agent.rs | 9 ++-- nova_vm/src/execution/realm.rs | 2 + nova_vm/src/types.rs | 5 +- nova_vm/src/types/language.rs | 2 + nova_vm/src/types/language/number.rs | 81 ++++++++++++++++++++-------- nova_vm/src/types/language/object.rs | 4 ++ nova_vm/src/types/language/string.rs | 8 ++- 7 files changed, 77 insertions(+), 34 deletions(-) create mode 100644 nova_vm/src/types/language/object.rs diff --git a/nova_vm/src/execution/agent.rs b/nova_vm/src/execution/agent.rs index 005af431..f3550716 100644 --- a/nova_vm/src/execution/agent.rs +++ b/nova_vm/src/execution/agent.rs @@ -3,7 +3,7 @@ use crate::{ types::{Object, Symbol, Value}, Heap, }; -use std::collections::HashMap; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; #[derive(Debug, Default)] pub struct Options { @@ -27,7 +27,6 @@ pub struct HostHooks { /// 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, @@ -37,9 +36,9 @@ pub struct Agent<'ctx, 'host> { pub execution_context_stack: Vec>, } -impl Agent<'_, '_> { - pub fn current_realm(&self) -> &mut Realm { - todo!() +impl<'ctx, 'host> Agent<'ctx, 'host> { + pub fn current_realm(&self) -> Rc>> { + self.execution_context_stack.last().unwrap().realm.clone() } /// 5.2.3.2 Throw an Exception diff --git a/nova_vm/src/execution/realm.rs b/nova_vm/src/execution/realm.rs index 0edbf4bb..11d037b9 100644 --- a/nova_vm/src/execution/realm.rs +++ b/nova_vm/src/execution/realm.rs @@ -13,6 +13,8 @@ use std::{ /// https://tc39.es/ecma262/#sec-code-realms #[derive(Debug)] pub struct Realm<'ctx, 'host> { + pub heap: Heap, + pub agent: Rc>>, // rng: Xoroshiro128, diff --git a/nova_vm/src/types.rs b/nova_vm/src/types.rs index 44eb1d78..0ddfa807 100644 --- a/nova_vm/src/types.rs +++ b/nova_vm/src/types.rs @@ -1,7 +1,7 @@ mod language; mod spec; -pub use language::{Number, String, Value}; +pub use language::{Number, Object, String, Value}; pub use spec::{Base, Reference, ReferencedName}; impl From for Value { @@ -10,8 +10,5 @@ impl From for Value { } } -#[derive(Debug)] -pub struct Object; - #[derive(Debug)] pub struct Symbol; diff --git a/nova_vm/src/types/language.rs b/nova_vm/src/types/language.rs index 05e805eb..f923d449 100644 --- a/nova_vm/src/types/language.rs +++ b/nova_vm/src/types/language.rs @@ -1,9 +1,11 @@ mod bigint; mod number; +mod object; mod string; mod value; pub use bigint::BigInt; pub use number::Number; +pub use object::Object; pub use string::String; pub use value::Value; diff --git a/nova_vm/src/types/language/number.rs b/nova_vm/src/types/language/number.rs index a3e14d4b..d69ca1bd 100644 --- a/nova_vm/src/types/language/number.rs +++ b/nova_vm/src/types/language/number.rs @@ -94,7 +94,7 @@ impl Number { let x = self.into_value(); match x { - Value::Number(n) => agent.heap.get(n).is_nan(), + Value::Number(n) => agent.current_realm().borrow().heap.get(n).is_nan(), Value::IntegerNumber(_) => false, Value::FloatNumber(n) => n.is_nan(), _ => unreachable!(), @@ -105,7 +105,12 @@ impl Number { let x = self.into_value(); match x { - Value::Number(n) => agent.heap.get(n).is_sign_positive(), + Value::Number(n) => agent + .current_realm() + .borrow() + .heap + .get(n) + .is_sign_positive(), Value::IntegerNumber(n) => 0i64 == n.into(), Value::FloatNumber(n) => n.is_sign_positive(), _ => unreachable!(), @@ -116,7 +121,12 @@ impl Number { let x = self.into_value(); match x { - Value::Number(n) => agent.heap.get(n).is_sign_negative(), + Value::Number(n) => agent + .current_realm() + .borrow() + .heap + .get(n) + .is_sign_negative(), Value::IntegerNumber(_) => false, Value::FloatNumber(n) => n.is_sign_negative(), _ => unreachable!(), @@ -127,7 +137,7 @@ impl Number { let x = self.into_value(); match x { - Value::Number(n) => agent.heap.get(n) == f64::INFINITY, + Value::Number(n) => agent.current_realm().borrow().heap.get(n) == f64::INFINITY, Value::IntegerNumber(_) => false, Value::FloatNumber(n) => n == f32::INFINITY, _ => unreachable!(), @@ -138,7 +148,7 @@ impl Number { let x = self.into_value(); match x { - Value::Number(n) => agent.heap.get(n) == f64::NEG_INFINITY, + Value::Number(n) => agent.current_realm().borrow().heap.get(n) == f64::NEG_INFINITY, Value::IntegerNumber(_) => false, Value::FloatNumber(n) => n == f32::NEG_INFINITY, _ => unreachable!(), @@ -149,7 +159,7 @@ impl Number { let x = self.into_value(); match x { - Value::Number(n) => agent.heap.get(n).is_finite(), + Value::Number(n) => agent.current_realm().borrow().heap.get(n).is_finite(), Value::IntegerNumber(_) => true, Value::FloatNumber(n) => n.is_finite(), _ => unreachable!(), @@ -161,7 +171,7 @@ impl Number { match x { Value::Number(n) => { - let n = agent.heap.get(n); + let n = agent.current_realm().borrow().heap.get(n); !n.is_sign_negative() && !n.is_sign_positive() } Value::IntegerNumber(_) => true, @@ -177,7 +187,7 @@ impl Number { let x = self.into_value(); match x { - Value::Number(n) => agent.heap.get(n).trunc() as i64, + Value::Number(n) => agent.current_realm().borrow().heap.get(n).trunc() as i64, Value::IntegerNumber(n) => n.into_i64(), Value::FloatNumber(n) => n.trunc() as i64, _ => unreachable!(), @@ -188,7 +198,7 @@ impl Number { let x = self.into_value(); match x { - Value::Number(n) => agent.heap.get(n), + Value::Number(n) => agent.current_realm().borrow().heap.get(n), Value::IntegerNumber(n) => Into::::into(n) as f64, Value::FloatNumber(n) => n as f64, _ => unreachable!(), @@ -202,15 +212,24 @@ impl Number { 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::IntegerNumber(y)) => agent.heap.get(x) == y.into_i64() as f64, - (Value::Number(x), Value::FloatNumber(y)) => agent.heap.get(x) == y as f64, + (Value::Number(x), Value::Number(y)) => { + agent.current_realm().borrow().heap.get(x) + == agent.current_realm().borrow().heap.get(y) + } + (Value::Number(x), Value::IntegerNumber(y)) => { + agent.current_realm().borrow().heap.get(x) == y.into_i64() as f64 + } + (Value::Number(x), Value::FloatNumber(y)) => { + agent.current_realm().borrow().heap.get(x) == y as f64 + } (Value::IntegerNumber(x), Value::Number(y)) => { - (x.into_i64() as f64) == agent.heap.get(y) + (x.into_i64() as f64) == agent.current_realm().borrow().heap.get(y) } (Value::IntegerNumber(x), Value::IntegerNumber(y)) => x.into_i64() == y.into_i64(), (Value::IntegerNumber(x), Value::FloatNumber(y)) => (x.into_i64() as f64) == y as f64, - (Value::FloatNumber(x), Value::Number(y)) => (x as f64) == agent.heap.get(y), + (Value::FloatNumber(x), Value::Number(y)) => { + (x as f64) == agent.current_realm().borrow().heap.get(y) + } (Value::FloatNumber(x), Value::IntegerNumber(y)) => (x as f64) == y.into_i64() as f64, (Value::FloatNumber(x), Value::FloatNumber(y)) => x == y, _ => unreachable!(), @@ -222,7 +241,7 @@ impl Number { match x { Value::Number(n) => { - let n = agent.heap.get(n); + let n = agent.current_realm().borrow().heap.get(n); n % 1.0 == 0.0 && n % 2.0 == 0.0 } Value::IntegerNumber(n) => Into::::into(n) % 2 == 0, @@ -236,11 +255,11 @@ impl Number { match x { Value::Number(n) => { - let n = agent.heap.get(n); + let n = agent.current_realm().borrow().heap.get(n); if n > 0.0 { self } else { - agent.heap.create(-n) + agent.current_realm().borrow_mut().heap.create(-n) } } Value::IntegerNumber(n) => { @@ -269,7 +288,12 @@ impl Number { // 2. Return the result of negating x; that is, compute a Number with the same magnitude but opposite sign. match x { - Value::Number(n) => agent.heap.create(-agent.heap.get(n)), + Value::Number(n) => { + let realm = agent.current_realm(); + let mut realm = realm.borrow_mut(); + let value = realm.heap.get(n); + realm.heap.create(-value) + } Value::IntegerNumber(n) => SmallInteger::from_i64_unchecked(-n.into_i64()).into(), Value::FloatNumber(n) => (-n).into(), _ => unreachable!(), @@ -421,6 +445,8 @@ impl Number { // 13. Return an implementation-approximated Number value representing the result of raising ℝ(base) to the ℝ(exponent) power. agent + .current_realm() + .borrow_mut() .heap .create(base.into_f64(agent).powf(exponent.into_f64(agent))) } @@ -484,15 +510,24 @@ impl Number { // 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::IntegerNumber(y)) => agent.heap.get(x) < y.into_i64() as f64, - (Value::Number(x), Value::FloatNumber(y)) => agent.heap.get(x) < y as f64, + (Value::Number(x), Value::Number(y)) => { + agent.current_realm().borrow().heap.get(x) + < agent.current_realm().borrow().heap.get(y) + } + (Value::Number(x), Value::IntegerNumber(y)) => { + agent.current_realm().borrow().heap.get(x) < y.into_i64() as f64 + } + (Value::Number(x), Value::FloatNumber(y)) => { + agent.current_realm().borrow().heap.get(x) < y as f64 + } (Value::IntegerNumber(x), Value::Number(y)) => { - (x.into_i64() as f64) < agent.heap.get(y) + (x.into_i64() as f64) < agent.current_realm().borrow().heap.get(y) } (Value::IntegerNumber(x), Value::IntegerNumber(y)) => x.into_i64() < y.into_i64(), (Value::IntegerNumber(x), Value::FloatNumber(y)) => (x.into_i64() as f64) < y as f64, - (Value::FloatNumber(x), Value::Number(y)) => (x as f64) < agent.heap.get(y), + (Value::FloatNumber(x), Value::Number(y)) => { + (x as f64) < agent.current_realm().borrow().heap.get(y) + } (Value::FloatNumber(x), Value::IntegerNumber(y)) => (x as f64) < y.into_i64() as f64, (Value::FloatNumber(x), Value::FloatNumber(y)) => x < y, _ => unreachable!(), diff --git a/nova_vm/src/types/language/object.rs b/nova_vm/src/types/language/object.rs new file mode 100644 index 00000000..7b05d0d7 --- /dev/null +++ b/nova_vm/src/types/language/object.rs @@ -0,0 +1,4 @@ +use super::Value; + +#[derive(Debug, Clone, Copy)] +pub struct Object(Value); diff --git a/nova_vm/src/types/language/string.rs b/nova_vm/src/types/language/string.rs index 96cadffe..42138c7b 100644 --- a/nova_vm/src/types/language/string.rs +++ b/nova_vm/src/types/language/string.rs @@ -28,7 +28,7 @@ impl String { let s = self.into_value(); match s { - Value::String(s) => agent.heap.get(s).len(), + Value::String(s) => agent.current_realm().borrow().heap.get(s).len(), Value::SmallString(s) => s.len(), _ => unreachable!(), } @@ -36,7 +36,11 @@ impl String { pub fn as_str<'a>(&'a self, agent: &'a Agent) -> Option<&'a str> { match &self.0 { - Value::String(s) => agent.heap.get(*s).as_str(), + // SAFETY: The immutable reference to the Agent ensures no mutable + // access to the realm. + Value::String(s) => unsafe { + std::mem::transmute(agent.current_realm().borrow().heap.get(*s).as_str()) + }, Value::SmallString(s) => Some(s.as_str()), _ => unreachable!(), } From 8e0b959301b3938de4f17e0b4db789c148496def Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Tue, 25 Jul 2023 15:44:31 -0500 Subject: [PATCH 08/21] press CTRL+S to save --- nova_vm/src/builtins.rs | 9 +- nova_vm/src/builtins/array.rs | 18 +- nova_vm/src/builtins/builtin_function.rs | 47 ++--- nova_vm/src/builtins/ecmascript_function.rs | 63 +++++++ nova_vm/src/builtins/number.rs | 34 ++++ nova_vm/src/builtins/ordinary.rs | 18 ++ nova_vm/src/execution/environments.rs | 2 +- nova_vm/src/execution/execution_context.rs | 4 +- nova_vm/src/execution/realm.rs | 8 +- nova_vm/src/execution/realm/intrinsics.rs | 178 +++++++++++------- nova_vm/src/heap.rs | 53 +++++- nova_vm/src/heap/function.rs | 34 ++++ nova_vm/src/language/script.rs | 4 +- nova_vm/src/types.rs | 4 +- nova_vm/src/types/language.rs | 4 +- nova_vm/src/types/language/function.rs | 25 +++ nova_vm/src/types/language/object.rs | 41 ++++ nova_vm/src/types/language/object/data.rs | 10 + .../types/language/object/internal_methods.rs | 70 +++++++ .../src/types/language/object/property_key.rs | 4 + nova_vm/src/types/language/value.rs | 5 +- nova_vm/src/types/spec.rs | 2 + nova_vm/src/types/spec/property_descriptor.rs | 95 ++++++++++ 23 files changed, 628 insertions(+), 104 deletions(-) create mode 100644 nova_vm/src/builtins/ecmascript_function.rs create mode 100644 nova_vm/src/builtins/number.rs create mode 100644 nova_vm/src/builtins/ordinary.rs create mode 100644 nova_vm/src/heap/function.rs create mode 100644 nova_vm/src/types/language/function.rs create mode 100644 nova_vm/src/types/language/object/data.rs create mode 100644 nova_vm/src/types/language/object/internal_methods.rs create mode 100644 nova_vm/src/types/language/object/property_key.rs create mode 100644 nova_vm/src/types/spec/property_descriptor.rs diff --git a/nova_vm/src/builtins.rs b/nova_vm/src/builtins.rs index a999825a..860649f9 100644 --- a/nova_vm/src/builtins.rs +++ b/nova_vm/src/builtins.rs @@ -1,6 +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, ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs, + create_builtin_function, todo_builtin, ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs, + RegularFn as JsFunction, }; +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 index f7336d2a..46cba49d 100644 --- a/nova_vm/src/builtins/array.rs +++ b/nova_vm/src/builtins/array.rs @@ -1,25 +1,31 @@ -use super::{create_builtin_function, ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs}; +use super::{ + builtin_function::define_builtin_function, create_builtin_function, ArgumentsList, Behaviour, + Builtin, BuiltinFunctionArgs, +}; use crate::{ execution::{Agent, JsResult, Realm}, types::{Object, Value}, }; -struct ArrayConstructor; +pub struct ArrayConstructor; impl Builtin for ArrayConstructor { - fn create(realm: &mut Realm) -> Object { + fn create<'a>(realm: &'a mut Realm<'a, 'a>) -> JsResult { let object = create_builtin_function( - &mut realm.agent.clone().borrow_mut(), Behaviour::Regular(Self::behaviour), BuiltinFunctionArgs::new(1, "Array", realm), ); - object + Ok(object.into_object()) } } impl ArrayConstructor { - fn behaviour(agent: &mut Agent, value: Value, arguments: ArgumentsList) -> JsResult { + 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 index 3e823cc4..ece6cca4 100644 --- a/nova_vm/src/builtins/builtin_function.rs +++ b/nova_vm/src/builtins/builtin_function.rs @@ -1,13 +1,13 @@ use crate::{ execution::{Agent, JsResult, Realm}, - types::{Object, Value}, + types::{Function, Object, Value}, }; #[derive(Debug)] -pub struct ArgumentsList; +pub struct ArgumentsList<'a>(&'a [Value]); -type RegularFn = fn(&mut Agent, Value, ArgumentsList) -> JsResult; -type ConstructorFn = fn(&mut Agent, Value, ArgumentsList, Option) -> JsResult; +pub type RegularFn = fn(&mut Agent, Value, ArgumentsList<'_>) -> JsResult; +type ConstructorFn = fn(&mut Agent, Value, ArgumentsList<'_>, Option) -> JsResult; #[derive(Debug)] pub enum Behaviour { @@ -16,7 +16,7 @@ pub enum Behaviour { } pub trait Builtin { - fn create(realm: &mut Realm) -> Object; + fn create<'a>(realm: &'a mut Realm<'a, 'a>) -> JsResult; } #[derive(Debug, Default)] @@ -28,7 +28,7 @@ pub struct BuiltinFunctionArgs<'a, 'ctx, 'host> { pub prefix: Option, } -impl<'a, 'ctx, 'host: 'ctx> BuiltinFunctionArgs<'a, 'ctx, 'host> { +impl<'a, 'ctx: 'a, 'host: 'ctx> BuiltinFunctionArgs<'a, 'ctx, 'host> { pub fn new(length: u32, name: &'static str, realm: &'a mut Realm<'ctx, 'host>) -> Self { Self { length, @@ -41,18 +41,19 @@ impl<'a, 'ctx, 'host: 'ctx> BuiltinFunctionArgs<'a, 'ctx, 'host> { /// 10.3.3 CreateBuiltinFunction ( behaviour, length, name, additionalInternalSlotsList [ , realm [ , prototype [ , prefix ] ] ] ) /// https://tc39.es/ecma262/#sec-createbuiltinfunction -pub fn create_builtin_function<'ctx, 'host: 'ctx>( - agent: &mut Agent<'ctx, 'host>, +pub fn create_builtin_function<'a, 'b: 'a>( behaviour: Behaviour, - args: BuiltinFunctionArgs<'_, 'ctx, 'host>, -) -> Object { + args: BuiltinFunctionArgs<'a, 'b, 'b>, +) -> Function { // 1. If realm is not present, set realm to the current Realm Record. let realm = args.realm.unwrap(); // TODO: load record // 2. If prototype is not present, set prototype to realm.[[Intrinsics]].[[%Function.prototype%]]. - let prototype = args - .prototype - .unwrap_or_else(|| realm.intrinsics.function_prototype()); + let prototype = if let Some(prototype) = args.prototype { + prototype + } else { + realm.intrinsics().function() + }; // 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. @@ -74,23 +75,27 @@ pub fn create_builtin_function<'ctx, 'host: 'ctx>( todo!(); } -pub fn define_builtin_function<'ctx, 'host: 'ctx>( +pub fn define_builtin_function<'a>( object: Object, name: &'static str, behaviour: RegularFn, length: u32, - realm: &'ctx mut Realm<'ctx, 'host>, -) { - let agent_mut = realm.agent.clone(); - let mut agent = agent_mut.borrow_mut(); - + realm: &'a mut Realm<'a, 'a>, +) -> JsResult<()> { let function = create_builtin_function( - &mut agent, Behaviour::Regular(behaviour), BuiltinFunctionArgs::new(length, name, realm), ); - define_builtin_property(object, name, Value::from(function)); + Ok(()) } pub fn define_builtin_property(object: Object, name: &'static str, value: Value) {} + +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..2c32aa3b --- /dev/null +++ b/nova_vm/src/builtins/ecmascript_function.rs @@ -0,0 +1,63 @@ +use std::{cell::RefCell, rc::Rc}; + +use oxc_ast::ast::{FormalParameters, FunctionBody}; + +use crate::{ + execution::{Environment, PrivateEnvironment, Realm, ScriptOrModule}, + types::Object, +}; + +#[derive(Debug, Clone, Copy)] +pub enum ConstructorKind { + Base, + Derived, +} + +#[derive(Debug, Clone, Copy)] +pub enum ThisMode { + Lexical, + Strict, + Global, +} + +/// 10.2 ECMAScript Function Objects +/// https://tc39.es/ecma262/#sec-ecmascript-function-objects +#[derive(Debug, Clone)] +pub struct ECMAScriptFunction<'ctx, 'host> { + /// [[Environment]] + pub environment: Environment, + + /// [[PrivateEnvironment]] + pub private_environment: Option>>, + + /// [[FormalParameters]] + pub formal_parameters: &'host FormalParameters<'host>, + + /// [[ECMAScriptCode]] + pub ecmascript_code: &'host FunctionBody<'host>, + + /// [[ConstructorKind]] + pub constructor_kind: ConstructorKind, + + /// [[Realm]] + pub realm: Rc>>, + + /// [[ScriptOrModule]] + pub script_or_module: ScriptOrModule<'ctx, 'host>, + + /// [[ThisMode]] + pub this_mode: ThisMode, + + /// [[Strict]] + pub strict: bool, + + /// [[HomeObject]] + pub home_object: Option, + + /// [[SourceText]] + pub source_text: &'host str, + + // TODO: [[Fields]], [[PrivateMethods]], [[ClassFieldInitializerName]] + /// [[IsClassConstructor]] + pub is_class_constructor: bool, +} diff --git a/nova_vm/src/builtins/number.rs b/nova_vm/src/builtins/number.rs new file mode 100644 index 00000000..bbed2511 --- /dev/null +++ b/nova_vm/src/builtins/number.rs @@ -0,0 +1,34 @@ +use super::{ + builtin_function::define_builtin_function, create_builtin_function, todo_builtin, + ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs, +}; +use crate::{ + execution::{Agent, JsResult, Realm}, + types::{Object, Value}, +}; + +pub struct NumberConstructor; + +impl Builtin for NumberConstructor { + fn create<'a>(realm: &'a mut Realm<'a, 'a>) -> JsResult { + let object = create_builtin_function( + Behaviour::Regular(NumberConstructor::behaviour), + BuiltinFunctionArgs::new(1, "Array", realm), + ) + .into_object(); + + define_builtin_function(object, "isFinite", todo_builtin, 1, realm)?; + + Ok(object) + } +} + +impl NumberConstructor { + fn behaviour( + agent: &mut Agent, + this_value: Value, + arguments: ArgumentsList, + ) -> JsResult { + todo!(); + } +} diff --git a/nova_vm/src/builtins/ordinary.rs b/nova_vm/src/builtins/ordinary.rs new file mode 100644 index 00000000..9e221309 --- /dev/null +++ b/nova_vm/src/builtins/ordinary.rs @@ -0,0 +1,18 @@ +use crate::{ + execution::{Agent, JsResult}, + types::Object, +}; + +/// 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). + return 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]]. + return object.prototype(agent); +} diff --git a/nova_vm/src/execution/environments.rs b/nova_vm/src/execution/environments.rs index c7a2ee5c..dfffc310 100644 --- a/nova_vm/src/execution/environments.rs +++ b/nova_vm/src/execution/environments.rs @@ -16,7 +16,7 @@ use std::{cell::RefCell, rc::Rc}; /// 9.1.1 The Environment Record Type Hierarchy /// https://tc39.es/ecma262/#sec-the-environment-record-type-hierarchy -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Environment { DeclarativeEnvironment(Rc>), ObjectEnvironment(Rc>), diff --git a/nova_vm/src/execution/execution_context.rs b/nova_vm/src/execution/execution_context.rs index 4e7e4e38..9ce50452 100644 --- a/nova_vm/src/execution/execution_context.rs +++ b/nova_vm/src/execution/execution_context.rs @@ -5,9 +5,9 @@ use std::{cell::RefCell, rc::Rc}; #[derive(Debug)] pub struct Module; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum ScriptOrModule<'ctx, 'host> { - Script(&'ctx mut Script<'ctx, 'host>), + Script(Rc>>), Module(Rc>), } diff --git a/nova_vm/src/execution/realm.rs b/nova_vm/src/execution/realm.rs index 11d037b9..f4a24561 100644 --- a/nova_vm/src/execution/realm.rs +++ b/nova_vm/src/execution/realm.rs @@ -19,7 +19,7 @@ pub struct Realm<'ctx, 'host> { // rng: Xoroshiro128, /// [[Intrinsics]] - pub intrinsics: Intrinsics<'ctx, 'host>, + // pub intrinsics: Intrinsics<'ctx, 'host>, /// [[GlobalObject]] pub global_object: Object, @@ -31,3 +31,9 @@ pub struct Realm<'ctx, 'host> { pub host_defined: Option>>, // TODO: [[TemplateMap]], [[LoadedModules]] } + +impl<'ctx, 'host> Realm<'ctx, 'host> { + pub fn intrinsics<'a>(&'a self) -> Intrinsics<'a, 'ctx, 'host> { + Intrinsics { realm: self } + } +} diff --git a/nova_vm/src/execution/realm/intrinsics.rs b/nova_vm/src/execution/realm/intrinsics.rs index 9465cb20..acd8dac9 100644 --- a/nova_vm/src/execution/realm/intrinsics.rs +++ b/nova_vm/src/execution/realm/intrinsics.rs @@ -1,144 +1,194 @@ use super::Realm; -use crate::{execution::JsResult, types::Object}; -use std::{cell::RefCell, rc::Rc}; +use crate::types::Object; #[derive(Debug)] -pub struct Intrinsics<'ctx, 'host> { - pub realm: Rc>>, - - // Not stored as top-level properties so we can have methods of the same names - pub lazy_intrinsics: LazyIntrinsics, -} - -macro_rules! lazy_intrinsic { - ($name: ident $ptr: ty) => { - pub fn $name(&mut self) -> Object { - let intrinsic = &mut self.lazy_intrinsics.$name; - - if let Some(intrinsic) = intrinsic { - intrinsic - } else { - } - } - }; +pub struct Intrinsics<'a, 'ctx, 'host> { + pub realm: &'a Realm<'ctx, 'host>, } -impl Intrinsics<'_, '_> { - pub fn function_prototype(&mut self) -> Object { +impl Intrinsics<'_, '_, '_> { + /// %Array% + pub fn array(&self) -> Object { todo!() } -} - -#[derive(Debug)] -pub struct LazyIntrinsics { - /// %Array% - pub array: Option, /// %Array.prototype% - pub array_prototype_prototype: Option, + pub fn array_prototype_prototype(&self) -> Object { + todo!() + } /// %BigInt% - pub big_int: Option, + pub fn big_int(&self) -> Object { + todo!() + } /// %BigInt.prototype% - pub big_int_prototype: Option, + pub fn big_int_prototype(&self) -> Object { + todo!() + } /// %Boolean% - pub boolean: Option, + pub fn boolean(&self) -> Object { + todo!() + } /// %Boolean.prototype% - pub boolean_prototype: Option, + pub fn boolean_prototype(&self) -> Object { + todo!() + } /// %Error% - pub error: Option, + pub fn error(&self) -> Object { + todo!() + } /// %Error.prototype% - pub error_prototype: Option, + pub fn error_prototype(&self) -> Object { + todo!() + } /// %eval% - pub eval: Option, + pub fn eval(&self) -> Object { + todo!() + } /// %EvalError% - pub eval_error: Option, + pub fn eval_error(&self) -> Object { + todo!() + } /// %EvalError.prototype% - pub eval_error_prototype: Option, + pub fn eval_error_prototype(&self) -> Object { + todo!() + } /// %Function% - pub function: Option, + pub fn function(&self) -> Object { + todo!() + } /// %Function.prototype% - pub function_prototype: Option, + pub fn function_prototype(&self) -> Object { + todo!() + } /// %isFinite% - pub is_finite: Option, + pub fn is_finite(&self) -> Object { + todo!() + } /// %isNaN% - pub is_nan: Option, + pub fn is_nan(&self) -> Object { + todo!() + } /// %Math% - pub math: Option, + pub fn math(&self) -> Object { + todo!() + } /// %Number% - pub number: Option, + pub fn number(&self) -> Object { + todo!() + } /// %Number.prototype% - pub number_prototype: Option, + pub fn number_prototype(&self) -> Object { + todo!() + } /// %Object% - pub object: Option, + pub fn object(&self) -> Object { + todo!() + } /// %Object.prototype% - pub object_prototype: Option, + pub fn object_prototype(&self) -> Object { + todo!() + } /// %Object.prototype.toString% - pub object_prototype_to_string: Option, + pub fn object_prototype_to_string(&self) -> Object { + todo!() + } /// %RangeError% - pub range_error: Option, + pub fn range_error(&self) -> Object { + todo!() + } /// %RangeError.prototype% - pub range_error_prototype: Option, + pub fn range_error_prototype(&self) -> Object { + todo!() + } /// %ReferenceError% - pub reference_error: Option, + pub fn reference_error(&self) -> Object { + todo!() + } /// %ReferenceError.prototype% - pub reference_error_prototype: Option, + pub fn reference_error_prototype(&self) -> Object { + todo!() + } /// %Reflect% - pub reflect: Option, + pub fn reflect(&self) -> Object { + todo!() + } /// %String% - pub string: Option, + pub fn string(&self) -> Object { + todo!() + } /// %String.prototype% - pub string_prototype: Option, + pub fn string_prototype(&self) -> Object { + todo!() + } /// %Symbol% - pub symbol: Option, + pub fn symbol(&self) -> Object { + todo!() + } /// %Symbol.prototype% - pub symbol_prototype: Option, + pub fn symbol_prototype(&self) -> Object { + todo!() + } /// %SyntaxError% - pub syntax_error: Option, + pub fn syntax_error(&self) -> Object { + todo!() + } /// %SyntaxError.prototype% - pub syntax_error_prototype: Option, + pub fn syntax_error_prototype(&self) -> Object { + todo!() + } /// %ThrowTypeError% - pub throw_type_error: Option, + pub fn throw_type_error(&self) -> Object { + todo!() + } /// %TypeError% - pub type_error: Option, + pub fn type_error(&self) -> Object { + todo!() + } /// %TypeError.prototype% - pub type_error_prototype: Option, + pub fn type_error_prototype(&self) -> Object { + todo!() + } /// %URIError% - pub uri_error: Option, + pub fn uri_error(&self) -> Object { + todo!() + } /// %URIError.prototype% - pub uri_error_prototype: Option, + pub fn uri_error_prototype(&self) -> Object { + todo!() + } } diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index b1b24e06..71cc2e15 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -1,5 +1,6 @@ mod array; mod bigint; +mod function; mod heap_constants; mod heap_trace; mod number; @@ -9,13 +10,14 @@ mod symbol; pub use array::ArrayHeapData; pub use bigint::BigIntHeapData; +pub use function::FunctionHeapData; pub use number::NumberHeapData; pub use object::ObjectHeapData; pub use string::StringHeapData; pub use symbol::SymbolHeapData; use self::heap_trace::HeapTrace; -use crate::types::{Number, String, Value}; +use crate::types::{Function, Number, Object, String, Value}; use std::{cell::Cell, marker::PhantomData}; use wtf8::{Wtf8, Wtf8Buf}; @@ -58,6 +60,7 @@ impl_handle_debug!(NumberHeapData); impl_handle_debug!(BigIntHeapData); impl_handle_debug!(ObjectHeapData); impl_handle_debug!(ArrayHeapData); +impl_handle_debug!(FunctionHeapData); #[derive(Debug)] pub struct Heap { @@ -67,6 +70,7 @@ pub struct Heap { pub(crate) bigints: Vec>, pub(crate) objects: Vec>, pub(crate) arrays: Vec>, + pub(crate) functions: Vec>, } fn stop_the_world() {} @@ -131,6 +135,25 @@ impl<'a> GetHeapData<'a, StringHeapData, &'a Wtf8> for Heap { } } +impl CreateHeapData for Heap { + fn create(&mut self, data: FunctionHeapData) -> Function { + let id = self.functions.len(); + self.functions.push(Some(data)); + Function::new(Value::Function(Handle::new(id as u32))) + } +} + +impl<'a> GetHeapData<'a, FunctionHeapData, &'a FunctionHeapData> for Heap { + fn get(&'a self, handle: Handle) -> &'a FunctionHeapData { + self.functions + .get(handle.id as usize) + .as_ref() + .unwrap() + .as_ref() + .unwrap() + } +} + impl Heap { pub fn new() -> Heap { let mut heap = Heap { @@ -140,6 +163,7 @@ impl Heap { bigints: Vec::with_capacity(1024), objects: Vec::with_capacity(1024), arrays: Vec::with_capacity(1024), + functions: Vec::with_capacity(1024), }; heap @@ -253,6 +277,24 @@ impl Heap { let _ = bigint.take(); } } + for object in self.objects.iter_mut() { + let Some(data) = object else { + continue; + }; + let marked = data.bits.marked.replace(true); + if !marked { + let _ = object.take(); + } + } + for function in self.functions.iter_mut() { + let Some(data) = function else { + continue; + }; + let marked = data.bits.marked.replace(true); + if !marked { + let _ = function.take(); + } + } while self.objects.last().is_none() { self.objects.pop(); } @@ -268,6 +310,12 @@ impl Heap { while self.bigints.last().is_none() { self.bigints.pop(); } + while self.objects.last().is_none() { + self.objects.pop(); + } + while self.functions.last().is_none() { + self.functions.pop(); + } start_the_world(); } } @@ -281,6 +329,7 @@ impl HeapTrace for Value { &Value::BigInt(handle) => heap.bigints[handle.id as usize].trace(heap), &Value::Object(handle) => heap.objects[handle.id as usize].trace(heap), &Value::ArrayObject(handle) => heap.arrays[handle.id as usize].trace(heap), + &Value::Function(handle) => heap.functions[handle.id as usize].trace(heap), _ => {} } } @@ -293,6 +342,7 @@ impl HeapTrace for Value { &Value::BigInt(handle) => heap.bigints[handle.id as usize].root(heap), &Value::Object(handle) => heap.objects[handle.id as usize].root(heap), &Value::ArrayObject(handle) => heap.arrays[handle.id as usize].root(heap), + &Value::Function(handle) => heap.functions[handle.id as usize].root(heap), _ => {} } } @@ -305,6 +355,7 @@ impl HeapTrace for Value { &Value::BigInt(handle) => heap.bigints[handle.id as usize].unroot(heap), &Value::Object(handle) => heap.objects[handle.id as usize].unroot(heap), &Value::ArrayObject(handle) => heap.arrays[handle.id as usize].unroot(heap), + &Value::Function(handle) => heap.functions[handle.id as usize].unroot(heap), _ => {} } } diff --git a/nova_vm/src/heap/function.rs b/nova_vm/src/heap/function.rs new file mode 100644 index 00000000..3149e442 --- /dev/null +++ b/nova_vm/src/heap/function.rs @@ -0,0 +1,34 @@ +use super::{heap_trace::HeapTrace, Handle, HeapBits, ObjectHeapData}; +use crate::{builtins::JsFunction, Heap}; + +#[derive(Debug, Clone)] +pub struct FunctionHeapData { + pub(crate) bits: HeapBits, + pub(crate) object: Handle, + pub(crate) length: i64, + pub(crate) binding: JsFunction, + // TODO: Should we create a `BoundFunctionHeapData` for the exotic object? + // pub(super) uses_arguments: bool, + // pub(super) bound: Option>, + // pub(super) visible: Option>, +} + +impl HeapTrace for Option { + fn trace(&self, heap: &Heap) { + assert!(self.is_some()); + heap.objects[self.as_ref().unwrap().object.id as usize].trace(heap); + } + fn root(&self, _heap: &Heap) { + assert!(self.is_some()); + self.as_ref().unwrap().bits.root(); + } + + fn unroot(&self, _heap: &Heap) { + assert!(self.is_some()); + self.as_ref().unwrap().bits.unroot(); + } + + fn finalize(&mut self, _heap: &Heap) { + self.take(); + } +} diff --git a/nova_vm/src/language/script.rs b/nova_vm/src/language/script.rs index 5866458c..65713309 100644 --- a/nova_vm/src/language/script.rs +++ b/nova_vm/src/language/script.rs @@ -61,7 +61,7 @@ impl<'ctx, 'host: 'ctx> Script<'ctx, 'host> { /// 16.1.6 ScriptEvaluation ( scriptRecord ) /// https://tc39.es/ecma262/#sec-runtime-semantics-scriptevaluation - pub fn evaluate(&'ctx mut self) -> Value { + pub fn evaluate(self) -> Value { let ecmascript_code = self.ecmascript_code.clone(); let realm = self.realm.clone(); let agent = { @@ -84,7 +84,7 @@ impl<'ctx, 'host: 'ctx> Script<'ctx, 'host> { realm, // 5. Set the ScriptOrModule of scriptContext to scriptRecord. - script_or_module: Some(ScriptOrModule::Script(self)), + script_or_module: Some(ScriptOrModule::Script(Rc::new(RefCell::new(self)))), ecmascript_code: Some(ECMAScriptCode { // 6. Set the VariableEnvironment of scriptContext to globalEnv. diff --git a/nova_vm/src/types.rs b/nova_vm/src/types.rs index 0ddfa807..ee4945a1 100644 --- a/nova_vm/src/types.rs +++ b/nova_vm/src/types.rs @@ -1,8 +1,8 @@ mod language; mod spec; -pub use language::{Number, Object, String, Value}; -pub use spec::{Base, Reference, ReferencedName}; +pub use language::{Function, Number, Object, String, Value}; +pub use spec::{Base, PropertyDescriptor, Reference, ReferencedName}; impl From for Value { fn from(value: Object) -> Self { diff --git a/nova_vm/src/types/language.rs b/nova_vm/src/types/language.rs index f923d449..de08617d 100644 --- a/nova_vm/src/types/language.rs +++ b/nova_vm/src/types/language.rs @@ -1,11 +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::Object; +pub use object::{Object, ObjectData}; pub use string::String; pub use value::Value; diff --git a/nova_vm/src/types/language/function.rs b/nova_vm/src/types/language/function.rs new file mode 100644 index 00000000..edaa8502 --- /dev/null +++ b/nova_vm/src/types/language/function.rs @@ -0,0 +1,25 @@ +use super::{Object, Value}; + +/// https://tc39.es/ecma262/#function-object +#[derive(Clone, Copy)] +pub struct Function(Value); + +impl std::fmt::Debug for Function { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.0) + } +} + +impl Function { + pub(crate) fn new(value: Value) -> Self { + Self(value) + } + + pub fn into_value(self) -> Value { + self.0 + } + + pub fn into_object(self) -> Object { + Object::new(self.into_value()) + } +} diff --git a/nova_vm/src/types/language/object.rs b/nova_vm/src/types/language/object.rs index 7b05d0d7..74d36e5f 100644 --- a/nova_vm/src/types/language/object.rs +++ b/nova_vm/src/types/language/object.rs @@ -1,4 +1,45 @@ +mod data; +mod internal_methods; +mod property_key; + +use crate::{execution::Agent, heap::GetHeapData}; + use super::Value; +pub use data::ObjectData; +pub use internal_methods::InternalMethods; +pub use property_key::PropertyKey; +/// 6.1.7 The Object Type +/// https://tc39.es/ecma262/#sec-object-type #[derive(Debug, Clone, Copy)] pub struct Object(Value); + +impl Object { + pub(crate) fn new(value: Value) -> Self { + Self(value) + } + + pub fn into_value(self) -> Value { + self.0 + } + + pub fn prototype(self, agent: &mut Agent) -> Option { + let realm = agent.current_realm(); + let object = self.into_value(); + + match object { + // Value::Object(object) => { + // let object = realm.heap.get(object); + // } + // Value::ArrayObject(array) => { + // let array = realm.heap.get(array); + // } + Value::Function(function) => { + // let function = realm.heap.get(function); + // function.binding; + todo!() + } + _ => unreachable!(), + } + } +} 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..bc53cbd9 --- /dev/null +++ b/nova_vm/src/types/language/object/internal_methods.rs @@ -0,0 +1,70 @@ +use super::{Object, PropertyKey}; +use crate::{ + builtins::ArgumentsList, + execution::JsResult, + types::{PropertyDescriptor, Value}, +}; + +pub type GetPrototypeOf = fn(object: Object) -> JsResult; +pub type SetPrototypeOf = fn(object: Object, prototype: Option) -> JsResult; +pub type IsExtensible = fn(object: Object) -> JsResult; +pub type PreventExtensions = fn(object: Object) -> JsResult; +pub type GetOwnProperty = fn(object: Object, property_key: PropertyKey) -> JsResult<()>; +pub type DefineOwnProperty = fn( + object: Object, + property_key: PropertyKey, + property_descriptor: PropertyDescriptor, +) -> JsResult; +pub type HasProperty = fn(object: Object, property_key: PropertyKey) -> JsResult; +pub type Get = fn(object: Object, property_key: PropertyKey, receiver: Value) -> JsResult; +pub type Set = + fn(object: Object, property_key: PropertyKey, value: Value, receiver: Value) -> JsResult; +pub type Delete = fn(object: Object, property_key: PropertyKey) -> JsResult; +pub type OwnPropertyKeys = fn(object: Object) -> JsResult>; +pub type Call = + fn(object: Object, this_value: Value, arguments_list: ArgumentsList) -> JsResult; +pub type Construct = fn(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..e8d7e424 --- /dev/null +++ b/nova_vm/src/types/language/object/property_key.rs @@ -0,0 +1,4 @@ +use crate::types::Value; + +#[derive(Debug, Clone, Copy)] +pub struct PropertyKey(Value); diff --git a/nova_vm/src/types/language/value.rs b/nova_vm/src/types/language/value.rs index 348782ef..779cce93 100644 --- a/nova_vm/src/types/language/value.rs +++ b/nova_vm/src/types/language/value.rs @@ -1,8 +1,8 @@ use crate::{ execution::{Agent, JsResult}, heap::{ - ArrayHeapData, BigIntHeapData, Handle, NumberHeapData, ObjectHeapData, StringHeapData, - SymbolHeapData, + ArrayHeapData, BigIntHeapData, FunctionHeapData, Handle, NumberHeapData, ObjectHeapData, + StringHeapData, SymbolHeapData, }, SmallInteger, SmallString, }; @@ -48,6 +48,7 @@ pub enum Value { /// https://tc39.es/ecma262/#sec-object-type Object(Handle), ArrayObject(Handle), + Function(Handle), } #[derive(Debug, Clone, Copy)] diff --git a/nova_vm/src/types/spec.rs b/nova_vm/src/types/spec.rs index f87bdbda..f3e7960e 100644 --- a/nova_vm/src/types/spec.rs +++ b/nova_vm/src/types/spec.rs @@ -1,3 +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/property_descriptor.rs b/nova_vm/src/types/spec/property_descriptor.rs new file mode 100644 index 00000000..6df57bf8 --- /dev/null +++ b/nova_vm/src/types/spec/property_descriptor.rs @@ -0,0 +1,95 @@ +use crate::{ + execution::{Agent, JsResult}, + types::{Object, Value}, +}; + +/// 6.2.6 The Property Descriptor Specification Type +/// https://tc39.es/ecma262/#sec-property-descriptor-specification-type +#[derive(Debug)] +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. + // 3. If Desc has a [[Set]] field, return true. + (Some(_), 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. + // 3. If Desc has a [[Writable]] field, return true. + (Some(_), Some(_)) => true, + // 4. Return false. + _ => false, + } + } + + /// 6.2.6.3 IsGenericDescriptor ( Desc ) + /// https://tc39.es/ecma262/#sec-isgenericdescriptor + pub fn is_generic_descriptor(&self) -> bool { + // 1. If Desc is undefined, return false. + // 2. If IsAccessorDescriptor(Desc) is true, return false. + // 3. If IsDataDescriptor(Desc) is true, return false. + // 4. Return true. + !self.is_accessor_descriptor() && !self.is_data_descriptor() + } + + /// 6.2.6.4 FromPropertyDescriptor ( Desc ) + /// https://tc39.es/ecma262/#sec-frompropertydescriptor + pub fn from_property_descriptor(&self, agent: &mut Agent) -> JsResult { + let realm = agent.current_realm(); + let realm = realm.borrow_mut(); + + // 1. If Desc is undefined, return undefined. + + // 2. Let obj be OrdinaryObjectCreate(%Object.prototype%). + // 3. Assert: obj is an extensible ordinary object with no own properties. + + // 4. If Desc has a [[Value]] field, then + // a. Perform ! CreateDataPropertyOrThrow(obj, "value", Desc.[[Value]]). + + // 5. If Desc has a [[Writable]] field, then + + // 6. If Desc has a [[Get]] field, then + // a. Perform ! CreateDataPropertyOrThrow(obj, "get", Desc.[[Get]]). + // 7. If Desc has a [[Set]] field, then + // a. Perform ! CreateDataPropertyOrThrow(obj, "set", Desc.[[Set]]). + // 8. If Desc has an [[Enumerable]] field, then + // a. Perform ! CreateDataPropertyOrThrow(obj, "enumerable", Desc.[[Enumerable]]). + + // 9. If Desc has a [[Configurable]] field, then + // a. Perform ! CreateDataPropertyOrThrow(obj, "configurable", Desc.[[Configurable]]). + // 10. Return obj. + todo!() + } +} From 9af15110b3687140e908abec8be6034edf3b892b Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Wed, 26 Jul 2023 13:00:41 -0500 Subject: [PATCH 09/21] get builtin flow working mid --- nova_vm/src/builtins.rs | 2 +- nova_vm/src/builtins/builtin_function.rs | 69 +++++-- nova_vm/src/builtins/ecmascript_function.rs | 30 ++- nova_vm/src/builtins/number.rs | 164 ++++++++++++++++- nova_vm/src/builtins/ordinary.rs | 133 +++++++++++++- nova_vm/src/execution.rs | 2 +- nova_vm/src/execution/realm.rs | 19 +- nova_vm/src/execution/realm/intrinsics.rs | 171 +++++++++++------- nova_vm/src/heap.rs | 20 ++ nova_vm/src/heap/function.rs | 12 +- nova_vm/src/heap/heap_constants.rs | 7 + nova_vm/src/heap/object.rs | 9 +- nova_vm/src/types.rs | 2 +- nova_vm/src/types/language.rs | 2 +- nova_vm/src/types/language/object.rs | 110 +++++++++-- .../types/language/object/internal_methods.rs | 35 ++-- .../src/types/language/object/property_key.rs | 44 ++++- nova_vm/src/types/language/value.rs | 85 ++++++++- nova_vm/src/types/spec/property_descriptor.rs | 2 +- 19 files changed, 771 insertions(+), 147 deletions(-) diff --git a/nova_vm/src/builtins.rs b/nova_vm/src/builtins.rs index 860649f9..a840bcf3 100644 --- a/nova_vm/src/builtins.rs +++ b/nova_vm/src/builtins.rs @@ -7,7 +7,7 @@ pub mod ordinary; pub use array::ArrayConstructor; pub use builtin_function::{ create_builtin_function, todo_builtin, ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs, - RegularFn as JsFunction, + ConstructorFn, RegularFn as JsFunction, RegularFn, }; pub use ecmascript_function::ECMAScriptFunction; pub use number::NumberConstructor; diff --git a/nova_vm/src/builtins/builtin_function.rs b/nova_vm/src/builtins/builtin_function.rs index ece6cca4..e34abdcb 100644 --- a/nova_vm/src/builtins/builtin_function.rs +++ b/nova_vm/src/builtins/builtin_function.rs @@ -1,15 +1,17 @@ use crate::{ - execution::{Agent, JsResult, Realm}, - types::{Function, Object, Value}, + execution::{Agent, Intrinsics, JsResult, Realm}, + heap::{BuiltinObjectIndexes, CreateHeapData, FunctionHeapData, HeapBits, ObjectHeapData}, + types::{Function, Object, PropertyDescriptor, PropertyKey, Value}, }; #[derive(Debug)] pub struct ArgumentsList<'a>(&'a [Value]); pub type RegularFn = fn(&mut Agent, Value, ArgumentsList<'_>) -> JsResult; -type ConstructorFn = fn(&mut Agent, Value, ArgumentsList<'_>, Option) -> JsResult; +pub type ConstructorFn = + fn(&mut Agent, Value, ArgumentsList<'_>, Option) -> JsResult; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum Behaviour { Regular(RegularFn), Constructor(ConstructorFn), @@ -22,14 +24,14 @@ pub trait Builtin { #[derive(Debug, Default)] pub struct BuiltinFunctionArgs<'a, 'ctx, 'host> { pub length: u32, - pub name: &'static str, + pub name: &'a str, pub realm: Option<&'a mut Realm<'ctx, 'host>>, pub prototype: Option, pub prefix: Option, } impl<'a, 'ctx: 'a, 'host: 'ctx> BuiltinFunctionArgs<'a, 'ctx, 'host> { - pub fn new(length: u32, name: &'static str, realm: &'a mut Realm<'ctx, 'host>) -> Self { + pub fn new(length: u32, name: &'a str, realm: &'a mut Realm<'ctx, 'host>) -> Self { Self { length, name, @@ -49,12 +51,11 @@ pub fn create_builtin_function<'a, 'b: 'a>( let realm = args.realm.unwrap(); // TODO: load record // 2. If prototype is not present, set prototype to realm.[[Intrinsics]].[[%Function.prototype%]]. - let prototype = if let Some(prototype) = args.prototype { - prototype - } else { - realm.intrinsics().function() - }; + let prototype = args + .prototype + .unwrap_or_else(Intrinsics::function_prototype); + // TODO: Steps 3-4 // 3. Let internalSlotsList be a List containing the names of all the internal slots that 10.3 // requires for the built-in function object that is about to be created. // 4. Append to internalSlotsList the elements of additionalInternalSlotsList. @@ -63,24 +64,48 @@ pub fn create_builtin_function<'a, 'b: 'a>( // 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. - - // 10. Perform SetFunctionLength(func, length). - + let object = realm.heap.create(ObjectHeapData { + bits: HeapBits::new(), + // 6. Set func.[[Prototype]] to prototype. + prototype: PropertyDescriptor { + value: Some(prototype.into_value()), + ..Default::default() + }, + // 7. Set func.[[Extensible]] to true. + extensible: true, + // 8. Set func.[[Realm]] to realm. + // NOTE: Heap data is implicitly attached to the Realm so I don't think + // this matters. + }); + + let initial_name = realm.heap.create(args.name).into_value(); + let func = realm.heap.create(FunctionHeapData { + bits: HeapBits::new(), + object: object.into_object_handle().unwrap(), + behaviour, + // 9. Set func.[[InitialName]] to null. + // TODO: This is non-standard. + initial_name, + // 10. Perform SetFunctionLength(func, length). + length: args.length as i64, + }); + + // TODO: Steps 11-12 // 11. If prefix is not present, then // a. Perform SetFunctionName(func, name). // 12. Else, // a. Perform SetFunctionName(func, name, prefix). // 13. Return func. - todo!(); + func } -pub fn define_builtin_function<'a>( +pub fn define_builtin_function<'a, 'b>( object: Object, - name: &'static str, + name: &'a str, behaviour: RegularFn, length: u32, - realm: &'a mut Realm<'a, 'a>, + realm: &'a mut Realm<'b, 'b>, ) -> JsResult<()> { let function = create_builtin_function( Behaviour::Regular(behaviour), @@ -90,7 +115,13 @@ pub fn define_builtin_function<'a>( Ok(()) } -pub fn define_builtin_property(object: Object, name: &'static str, value: Value) {} +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( diff --git a/nova_vm/src/builtins/ecmascript_function.rs b/nova_vm/src/builtins/ecmascript_function.rs index 2c32aa3b..433edbe8 100644 --- a/nova_vm/src/builtins/ecmascript_function.rs +++ b/nova_vm/src/builtins/ecmascript_function.rs @@ -3,8 +3,8 @@ use std::{cell::RefCell, rc::Rc}; use oxc_ast::ast::{FormalParameters, FunctionBody}; use crate::{ - execution::{Environment, PrivateEnvironment, Realm, ScriptOrModule}, - types::Object, + execution::{Agent, Environment, JsResult, PrivateEnvironment, Realm, ScriptOrModule}, + types::{Number, Object, PropertyDescriptor, PropertyKey, Value}, }; #[derive(Debug, Clone, Copy)] @@ -61,3 +61,29 @@ pub struct ECMAScriptFunction<'ctx, 'host> { /// [[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 index bbed2511..fffd8bf7 100644 --- a/nova_vm/src/builtins/number.rs +++ b/nova_vm/src/builtins/number.rs @@ -1,23 +1,175 @@ use super::{ - builtin_function::define_builtin_function, create_builtin_function, todo_builtin, - ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs, + builtin_function::{define_builtin_function, define_builtin_property}, + create_builtin_function, todo_builtin, ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs, }; use crate::{ - execution::{Agent, JsResult, Realm}, - types::{Object, Value}, + execution::{Agent, Intrinsics, JsResult, Realm}, + heap::{BuiltinObjectIndexes, CreateHeapData}, + types::{Number, Object, PropertyDescriptor, PropertyKey, Value}, + SmallInteger, }; pub struct NumberConstructor; impl Builtin for NumberConstructor { fn create<'a>(realm: &'a mut Realm<'a, 'a>) -> JsResult { - let object = create_builtin_function( + let object: Object = create_builtin_function( Behaviour::Regular(NumberConstructor::behaviour), - BuiltinFunctionArgs::new(1, "Array", realm), + BuiltinFunctionArgs { + length: 1, + name: "Number", + realm: Some(realm), + prototype: Some(Intrinsics::function_prototype()), + ..Default::default() + }, ) .into_object(); + // 21.1.2.1 Number.EPSILON + // https://tc39.es/ecma262/#sec-number.epsilon + define_builtin_property( + object, + "EPSILON", + PropertyDescriptor { + value: Some(realm.heap.create(f64::EPSILON).into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.2.6 Number.MAX_SAFE_INTEGER + // https://tc39.es/ecma262/#sec-number.max_safe_integer + define_builtin_property( + object, + "MAX_SAFE_INTEGER", + PropertyDescriptor { + value: Some(Number::from(SmallInteger::MAX).into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.2.7 Number.MAX_VALUE + // https://tc39.es/ecma262/#sec-number.max_value + define_builtin_property( + object, + "MAX_VALUE", + PropertyDescriptor { + value: Some(realm.heap.create(f64::MAX).into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.2.8 Number.MIN_SAFE_INTEGER + // https://tc39.es/ecma262/#sec-number.min_safe_integer + define_builtin_property( + object, + "MIN_SAFE_INTEGER", + PropertyDescriptor { + value: Some(Number::from(SmallInteger::MIN).into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.2.8 Number.MIN_VALUE + // https://tc39.es/ecma262/#sec-number.min_value + define_builtin_property( + object, + "MIN_VALUE", + PropertyDescriptor { + value: Some(realm.heap.create(f64::MIN).into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.2.10 Number.NaN + // https://tc39.es/ecma262/#sec-number.nan + define_builtin_property( + object, + "NaN", + PropertyDescriptor { + value: Some(Number::nan().into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.2.11 Number.NEGATIVE_INFINITY + // https://tc39.es/ecma262/#sec-number.negative_infinity + define_builtin_property( + object, + "NEGATIVE_INFINITY", + PropertyDescriptor { + value: Some(Number::neg_inf().into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.2.14 Number.POSITIVE_INFINITY + // https://tc39.es/ecma262/#sec-number.positive_infinity + define_builtin_property( + object, + "POSITIVE_INFINITY", + PropertyDescriptor { + value: Some(Number::pos_inf().into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + define_builtin_function(object, "isFinite", todo_builtin, 1, realm)?; + define_builtin_function(object, "isNaN", todo_builtin, 1, realm)?; + define_builtin_function(object, "isSafeInteger", todo_builtin, 1, realm)?; + define_builtin_function(object, "parseFloat", todo_builtin, 1, realm)?; + define_builtin_function(object, "parseInt", todo_builtin, 2, realm)?; + + // 21.1.2.15 Number.prototype + // https://tc39.es/ecma262/#sec-number.prototype + define_builtin_property( + object, + "prototype", + PropertyDescriptor { + value: Some(Intrinsics::number_prototype().into()), + writable: Some(false), + enumerable: Some(false), + configurable: Some(false), + ..Default::default() + }, + )?; + + // 21.1.3.1 Number.prototype.constructor + // https://tc39.es/ecma262/#sec-number.prototype.constructor + define_builtin_property( + Intrinsics::number_prototype(), + "constructor", + PropertyDescriptor { + value: Some(object.into_value()), + writable: Some(true), + enumerable: Some(false), + configurable: Some(true), + ..Default::default() + }, + )?; Ok(object) } diff --git a/nova_vm/src/builtins/ordinary.rs b/nova_vm/src/builtins/ordinary.rs index 9e221309..93fd3ef0 100644 --- a/nova_vm/src/builtins/ordinary.rs +++ b/nova_vm/src/builtins/ordinary.rs @@ -1,18 +1,145 @@ use crate::{ execution::{Agent, JsResult}, - types::Object, + types::{InternalMethods, Object}, +}; + +/// 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: get_prototype_of, + set_prototype_of: set_prototype_of, + is_extensible: is_extensible, + prevent_extensions: prevent_extensions, + get_own_property: todo!(), + define_own_property: todo!(), + has_property: todo!(), + get: todo!(), + set: todo!(), + delete: todo!(), + own_property_keys: todo!(), + call: todo!(), + construct: todo!(), }; /// 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). - return ordinary_get_prototype_of(agent, object); + 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]]. - return object.prototype(agent); + object.prototype(agent) +} + +/// 10.1.2 [[SetPrototypeOf]] ( V ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-setprototypeof-v +fn set_prototype_of( + agent: &mut Agent, + object: Object, + prototype: Option, +) -> JsResult { + // 1. Return OrdinarySetPrototypeOf(O, V). + return ordinary_set_prototype_of(agent, object, prototype); +} + +/// 10.1.2.1 OrdinarySetPrototypeOf ( O, V ) +/// https://tc39.es/ecma262/#sec-ordinarysetprototypeof +pub fn ordinary_set_prototype_of( + agent: &mut Agent, + object: Object, + prototype: Option, +) -> JsResult { + // 1. Let current be O.[[Prototype]]. + let current = object.prototype(agent); + + // 2. If SameValue(V, current) is true, return true. + match (prototype, current) { + (Some(prototype), Some(current)) + if prototype + .into_value() + .same_value(agent, current.into_value()) => + { + return Ok(true) + } + (None, None) => return Ok(true), + _ => {} + } + + // 3. Let extensible be O.[[Extensible]]. + let extensible = object.extensible(agent); + + // 4. If extensible is false, return false. + if !extensible { + return Ok(false); + } + + // 5. Let p be V. + let mut parent_prototype_outer = prototype; + + // 6. Let done be false. + // 7. Repeat, while done is false, + while let Some(parent_prototype) = parent_prototype_outer { + // a. If p is null, then + // i. Set done to true. + + // b. Else if SameValue(p, O) is true, then + if parent_prototype + .into_value() + .same_value(agent, object.into_value()) + { + // i. Return false. + return Ok(false); + } + + // c. Else, + // i. If p.[[GetPrototypeOf]] is not the ordinary object internal method defined in 10.1.1, + // set done to true. + if parent_prototype.internal_methods(agent).get_prototype_of != get_prototype_of { + break; + } + + // ii. Else, set p to p.[[Prototype]]. + parent_prototype_outer = parent_prototype.prototype(agent); + } + + // 8. Set O.[[Prototype]] to V. + object.set_prototype(agent, parent_prototype_outer); + + // 9. Return true. + Ok(true) +} + +/// 10.1.3 [[IsExtensible]] ( ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-isextensible +fn is_extensible(agent: &mut Agent, object: Object) -> JsResult { + // 1. Return OrdinaryIsExtensible(O). + Ok(ordinary_is_extensible(agent, object)) +} + +/// 10.1.3.1 OrdinaryIsExtensible ( O ) +/// https://tc39.es/ecma262/#sec-ordinaryisextensible +pub fn ordinary_is_extensible(agent: &mut Agent, object: Object) -> bool { + // 1. Return O.[[Extensible]]. + todo!() +} + +/// 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. + todo!(); + + // 2. Return true. + return true; } diff --git a/nova_vm/src/execution.rs b/nova_vm/src/execution.rs index c0362db7..6f33692c 100644 --- a/nova_vm/src/execution.rs +++ b/nova_vm/src/execution.rs @@ -10,4 +10,4 @@ pub use environments::{ PrivateEnvironment, }; pub use execution_context::{ECMAScriptCode, ExecutionContext, ScriptOrModule}; -pub use realm::Realm; +pub use realm::{Intrinsics, Realm}; diff --git a/nova_vm/src/execution/realm.rs b/nova_vm/src/execution/realm.rs index f4a24561..1f1333d5 100644 --- a/nova_vm/src/execution/realm.rs +++ b/nova_vm/src/execution/realm.rs @@ -2,12 +2,8 @@ mod intrinsics; use super::{Agent, GlobalEnvironment}; use crate::{types::Object, Heap}; -use intrinsics::Intrinsics; -use std::{ - any::Any, - cell::{RefCell, RefMut}, - rc::Rc, -}; +pub use intrinsics::Intrinsics; +use std::{any::Any, cell::RefCell, rc::Rc}; /// 9.3 Realms /// https://tc39.es/ecma262/#sec-code-realms @@ -17,10 +13,9 @@ pub struct Realm<'ctx, 'host> { pub agent: Rc>>, - // rng: Xoroshiro128, - /// [[Intrinsics]] - // pub intrinsics: Intrinsics<'ctx, 'host>, + // NOTE: We will need an rng here at some point. + // NOTE: [[Intrinsics]] are statically known via the [`Intrinsics`] struct. /// [[GlobalObject]] pub global_object: Object, @@ -31,9 +26,3 @@ pub struct Realm<'ctx, 'host> { pub host_defined: Option>>, // TODO: [[TemplateMap]], [[LoadedModules]] } - -impl<'ctx, 'host> Realm<'ctx, 'host> { - pub fn intrinsics<'a>(&'a self) -> Intrinsics<'a, 'ctx, 'host> { - Intrinsics { realm: self } - } -} diff --git a/nova_vm/src/execution/realm/intrinsics.rs b/nova_vm/src/execution/realm/intrinsics.rs index acd8dac9..cec6e78f 100644 --- a/nova_vm/src/execution/realm/intrinsics.rs +++ b/nova_vm/src/execution/realm/intrinsics.rs @@ -1,194 +1,237 @@ -use super::Realm; -use crate::types::Object; +use crate::{ + heap::{BuiltinObjectIndexes, Handle}, + types::{Object, Value}, +}; -#[derive(Debug)] -pub struct Intrinsics<'a, 'ctx, 'host> { - pub realm: &'a Realm<'ctx, 'host>, -} +// TODO: We should probably consider lazily loading intrinsics. This would +// contain a mutable reference to [`Realm`] and be created via a +// `Realm::intrinsic()` method to guarantee safety. + +pub struct Intrinsics; -impl Intrinsics<'_, '_, '_> { +impl Intrinsics { /// %Array% - pub fn array(&self) -> Object { - todo!() + pub fn array() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::ArrayConstructorIndex as u32, + ))) } /// %Array.prototype% - pub fn array_prototype_prototype(&self) -> Object { - todo!() + pub fn array_prototype() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::ArrayPrototypeIndex as u32, + ))) } /// %BigInt% - pub fn big_int(&self) -> Object { - todo!() + pub fn big_int() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::BigintConstructorIndex as u32, + ))) } /// %BigInt.prototype% - pub fn big_int_prototype(&self) -> Object { - todo!() + pub fn big_int_prototype() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::BigintPrototypeIndex as u32, + ))) } /// %Boolean% - pub fn boolean(&self) -> Object { - todo!() + pub fn boolean() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::BooleanConstructorIndex as u32, + ))) } /// %Boolean.prototype% - pub fn boolean_prototype(&self) -> Object { - todo!() + pub fn boolean_prototype() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::BooleanPrototypeIndex as u32, + ))) } /// %Error% - pub fn error(&self) -> Object { - todo!() + pub fn error() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::ErrorConstructorIndex as u32, + ))) } /// %Error.prototype% - pub fn error_prototype(&self) -> Object { - todo!() + pub fn error_prototype() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::ErrorPrototypeIndex as u32, + ))) } /// %eval% - pub fn eval(&self) -> Object { + pub fn eval() -> Object { todo!() } /// %EvalError% - pub fn eval_error(&self) -> Object { - todo!() + pub fn eval_error() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::ArrayConstructorIndex as u32, + ))) } /// %EvalError.prototype% - pub fn eval_error_prototype(&self) -> Object { + pub fn eval_error_prototype() -> Object { todo!() } /// %Function% - pub fn function(&self) -> Object { - todo!() + pub fn function() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::FunctionConstructorIndex as u32, + ))) } /// %Function.prototype% - pub fn function_prototype(&self) -> Object { - todo!() + pub fn function_prototype() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::FunctionPrototypeIndex as u32, + ))) } /// %isFinite% - pub fn is_finite(&self) -> Object { + pub fn is_finite() -> Object { todo!() } /// %isNaN% - pub fn is_nan(&self) -> Object { + pub fn is_nan() -> Object { todo!() } /// %Math% - pub fn math(&self) -> Object { - todo!() + pub fn math() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::MathObjectIndex as u32, + ))) } /// %Number% - pub fn number(&self) -> Object { - todo!() + pub fn number() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::NumberConstructorIndex as u32, + ))) } /// %Number.prototype% - pub fn number_prototype(&self) -> Object { - todo!() + pub fn number_prototype() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::NumberPrototypeIndex as u32, + ))) } /// %Object% - pub fn object(&self) -> Object { - todo!() + pub fn object() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::ObjectConstructorIndex as u32, + ))) } /// %Object.prototype% - pub fn object_prototype(&self) -> Object { - todo!() + pub fn object_prototype() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::ObjectPrototypeIndex as u32, + ))) } /// %Object.prototype.toString% - pub fn object_prototype_to_string(&self) -> Object { + pub fn object_prototype_to_string() -> Object { todo!() } /// %RangeError% - pub fn range_error(&self) -> Object { + pub fn range_error() -> Object { todo!() } /// %RangeError.prototype% - pub fn range_error_prototype(&self) -> Object { + pub fn range_error_prototype() -> Object { todo!() } /// %ReferenceError% - pub fn reference_error(&self) -> Object { + pub fn reference_error() -> Object { todo!() } /// %ReferenceError.prototype% - pub fn reference_error_prototype(&self) -> Object { + pub fn reference_error_prototype() -> Object { todo!() } /// %Reflect% - pub fn reflect(&self) -> Object { + pub fn reflect() -> Object { todo!() } /// %String% - pub fn string(&self) -> Object { - todo!() + pub fn string() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::StringConstructorIndex as u32, + ))) } /// %String.prototype% - pub fn string_prototype(&self) -> Object { - todo!() + pub fn string_prototype() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::StringPrototypeIndex as u32, + ))) } /// %Symbol% - pub fn symbol(&self) -> Object { - todo!() + pub fn symbol() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::SymbolConstructorIndex as u32, + ))) } /// %Symbol.prototype% - pub fn symbol_prototype(&self) -> Object { - todo!() + pub fn symbol_prototype() -> Object { + Object::new(Value::Object(Handle::new( + BuiltinObjectIndexes::SymbolPrototypeIndex as u32, + ))) } /// %SyntaxError% - pub fn syntax_error(&self) -> Object { + pub fn syntax_error() -> Object { todo!() } /// %SyntaxError.prototype% - pub fn syntax_error_prototype(&self) -> Object { + pub fn syntax_error_prototype() -> Object { todo!() } /// %ThrowTypeError% - pub fn throw_type_error(&self) -> Object { + pub fn throw_type_error() -> Object { todo!() } /// %TypeError% - pub fn type_error(&self) -> Object { + pub fn type_error() -> Object { todo!() } /// %TypeError.prototype% - pub fn type_error_prototype(&self) -> Object { + pub fn type_error_prototype() -> Object { todo!() } /// %URIError% - pub fn uri_error(&self) -> Object { + pub fn uri_error() -> Object { todo!() } /// %URIError.prototype% - pub fn uri_error_prototype(&self) -> Object { + pub fn uri_error_prototype() -> Object { todo!() } } diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 71cc2e15..22ec0019 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -11,6 +11,7 @@ mod symbol; pub use array::ArrayHeapData; pub use bigint::BigIntHeapData; pub use function::FunctionHeapData; +pub use heap_constants::BuiltinObjectIndexes; pub use number::NumberHeapData; pub use object::ObjectHeapData; pub use string::StringHeapData; @@ -154,6 +155,25 @@ impl<'a> GetHeapData<'a, FunctionHeapData, &'a FunctionHeapData> for Heap { } } +impl CreateHeapData for Heap { + fn create(&mut self, data: ObjectHeapData) -> Object { + let id: usize = self.functions.len(); + self.objects.push(Some(data)); + Object::new(Value::Object(Handle::new(id as u32))) + } +} + +impl<'a> GetHeapData<'a, ObjectHeapData, &'a mut ObjectHeapData> for Heap { + fn get(&'a self, handle: Handle) -> &'a mut ObjectHeapData { + self.objects + .get_mut(handle.id as usize) + .as_ref() + .unwrap() + .as_mut() + .unwrap() + } +} + impl Heap { pub fn new() -> Heap { let mut heap = Heap { diff --git a/nova_vm/src/heap/function.rs b/nova_vm/src/heap/function.rs index 3149e442..29b51df6 100644 --- a/nova_vm/src/heap/function.rs +++ b/nova_vm/src/heap/function.rs @@ -1,13 +1,19 @@ use super::{heap_trace::HeapTrace, Handle, HeapBits, ObjectHeapData}; -use crate::{builtins::JsFunction, Heap}; +use crate::{ + builtins::{Behaviour, JsFunction}, + types::{Object, Value}, + Heap, +}; #[derive(Debug, Clone)] pub struct FunctionHeapData { pub(crate) bits: HeapBits, pub(crate) object: Handle, + pub(crate) initial_name: Value, pub(crate) length: i64, - pub(crate) binding: JsFunction, - // TODO: Should we create a `BoundFunctionHeapData` for the exotic object? + pub(crate) behaviour: Behaviour, + // TODO: Should we create a `BoundFunctionHeapData` for an exotic object + // that allows setting fields and other deoptimizations? // pub(super) uses_arguments: bool, // pub(super) bound: Option>, // pub(super) visible: Option>, diff --git a/nova_vm/src/heap/heap_constants.rs b/nova_vm/src/heap/heap_constants.rs index f4921ce7..3b24c7e3 100644 --- a/nova_vm/src/heap/heap_constants.rs +++ b/nova_vm/src/heap/heap_constants.rs @@ -9,6 +9,7 @@ // +==================================================================+ #[repr(u32)] +#[derive(Debug, Clone, Copy)] pub enum BuiltinObjectIndexes { // Fundamental objects ObjectPrototypeIndex, @@ -131,6 +132,12 @@ pub enum BuiltinObjectIndexes { ProxyConstructorIndex, } +impl Default for BuiltinObjectIndexes { + fn default() -> Self { + Self::ObjectPrototypeIndex + } +} + pub const LAST_BUILTIN_OBJECT_INDEX: u32 = BuiltinObjectIndexes::ProxyConstructorIndex as u32; pub const FIRST_CONSTRUCTOR_INDEX: u32 = BuiltinObjectIndexes::ObjectConstructorIndex as u32; diff --git a/nova_vm/src/heap/object.rs b/nova_vm/src/heap/object.rs index afedb544..0b38e02e 100644 --- a/nova_vm/src/heap/object.rs +++ b/nova_vm/src/heap/object.rs @@ -1,16 +1,19 @@ -use crate::heap::{heap_trace::HeapTrace, Heap, HeapBits}; +use crate::{ + heap::{heap_trace::HeapTrace, Heap, HeapBits}, + types::PropertyDescriptor, +}; #[derive(Debug, Clone)] pub struct ObjectHeapData { pub(crate) bits: HeapBits, - pub(crate) _extensible: bool, + pub(crate) extensible: bool, // TODO: It's probably not necessary to have a whole data descriptor here. // A prototype can only be set to be null or an object, meaning that most of the // possible Value options are impossible. // 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: PropertyDescriptor, + pub(crate) prototype: PropertyDescriptor, // pub(crate) entries: Vec, } diff --git a/nova_vm/src/types.rs b/nova_vm/src/types.rs index ee4945a1..2d392f9e 100644 --- a/nova_vm/src/types.rs +++ b/nova_vm/src/types.rs @@ -1,7 +1,7 @@ mod language; mod spec; -pub use language::{Function, Number, Object, String, Value}; +pub use language::{Function, InternalMethods, Number, Object, PropertyKey, String, Value}; pub use spec::{Base, PropertyDescriptor, Reference, ReferencedName}; impl From for Value { diff --git a/nova_vm/src/types/language.rs b/nova_vm/src/types/language.rs index de08617d..e80f3d79 100644 --- a/nova_vm/src/types/language.rs +++ b/nova_vm/src/types/language.rs @@ -8,6 +8,6 @@ mod value; pub use bigint::BigInt; pub use function::Function; pub use number::Number; -pub use object::{Object, ObjectData}; +pub use object::{InternalMethods, Object, ObjectData, PropertyKey}; pub use string::String; pub use value::Value; diff --git a/nova_vm/src/types/language/object.rs b/nova_vm/src/types/language/object.rs index 74d36e5f..667d7c91 100644 --- a/nova_vm/src/types/language/object.rs +++ b/nova_vm/src/types/language/object.rs @@ -2,9 +2,14 @@ mod data; mod internal_methods; mod property_key; -use crate::{execution::Agent, heap::GetHeapData}; +use crate::{ + builtins::ordinary, + execution::{agent::ExceptionType, Agent, Intrinsics, JsResult}, + heap::{GetHeapData, Handle, ObjectHeapData}, + types::PropertyDescriptor, +}; -use super::Value; +use super::{Function, Value}; pub use data::ObjectData; pub use internal_methods::InternalMethods; pub use property_key::PropertyKey; @@ -14,6 +19,17 @@ pub use property_key::PropertyKey; #[derive(Debug, Clone, Copy)] pub struct Object(Value); +impl TryFrom for Object { + type Error = (); + fn try_from(value: Value) -> Result { + if let Value::Object(_) | Value::ArrayObject(_) | Value::Function(_) = value { + Ok(Self(value)) + } else { + Err(()) + } + } +} + impl Object { pub(crate) fn new(value: Value) -> Self { Self(value) @@ -23,23 +39,91 @@ impl Object { self.0 } + pub fn into_object_handle(self) -> Option> { + let object = self.into_value(); + + match object { + Value::Object(handle) => Some(handle), + Value::Function(_) => None, + Value::ArrayObject(_) => None, + _ => unreachable!(), + } + } + + pub fn extensible(self, agent: &mut Agent) -> bool { + let object = self.into_value(); + + match object { + Value::Object(object) => agent.current_realm().borrow().heap.get(object).extensible, + Value::ArrayObject(_) => true, + Value::Function(_) => true, + _ => unreachable!(), + } + } + pub fn prototype(self, agent: &mut Agent) -> Option { - let realm = agent.current_realm(); let object = self.into_value(); match object { - // Value::Object(object) => { - // let object = realm.heap.get(object); - // } - // Value::ArrayObject(array) => { - // let array = realm.heap.get(array); - // } - Value::Function(function) => { - // let function = realm.heap.get(function); - // function.binding; - todo!() + Value::Object(object) => { + let realm = agent.current_realm(); + let realm = realm.borrow(); + let object = realm.heap.get(object); + object.prototype.value?.try_into().ok() + } + Value::ArrayObject(_) => Some(Intrinsics::array_prototype()), + Value::Function(_) => Some(Intrinsics::function_prototype()), + _ => unreachable!(), + } + } + + // TODO: Is there a spec variant of this? + pub fn set_prototype(self, agent: &mut Agent, prototype: Option) { + let object = self.into_value(); + + match object { + Value::Object(object) => { + let realm = agent.current_realm(); + let realm = realm.borrow_mut(); + let object = realm.heap.get(object); + object.prototype.value = prototype.map(|object| object.into_value()); } + Value::ArrayObject(_) => todo!(), + Value::Function(_) => todo!(), _ => unreachable!(), } } + + pub fn internal_methods<'a>(self, agent: &mut Agent) -> &'a InternalMethods { + // TODO: Logic for fetching methods for objects/anything else. + &ordinary::METHODS + } + + /// /// 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(()) + } } diff --git a/nova_vm/src/types/language/object/internal_methods.rs b/nova_vm/src/types/language/object/internal_methods.rs index bc53cbd9..c06e89c1 100644 --- a/nova_vm/src/types/language/object/internal_methods.rs +++ b/nova_vm/src/types/language/object/internal_methods.rs @@ -1,29 +1,40 @@ use super::{Object, PropertyKey}; use crate::{ builtins::ArgumentsList, - execution::JsResult, + execution::{Agent, JsResult}, types::{PropertyDescriptor, Value}, }; -pub type GetPrototypeOf = fn(object: Object) -> JsResult; -pub type SetPrototypeOf = fn(object: Object, prototype: Option) -> JsResult; -pub type IsExtensible = fn(object: Object) -> JsResult; -pub type PreventExtensions = fn(object: Object) -> JsResult; -pub type GetOwnProperty = fn(object: Object, property_key: PropertyKey) -> JsResult<()>; +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(object: Object, property_key: PropertyKey) -> JsResult; -pub type Get = fn(object: Object, property_key: PropertyKey, receiver: Value) -> JsResult; +) -> bool; +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(object: Object, property_key: PropertyKey, value: Value, receiver: Value) -> JsResult; -pub type Delete = fn(object: Object, property_key: PropertyKey) -> JsResult; -pub type OwnPropertyKeys = fn(object: Object) -> JsResult>; +pub type Delete = + fn(agent: &mut Agent, object: Object, property_key: PropertyKey) -> JsResult; +pub type OwnPropertyKeys = fn(agent: &mut Agent, object: Object) -> JsResult>; pub type Call = fn(object: Object, this_value: Value, arguments_list: ArgumentsList) -> JsResult; -pub type Construct = fn(object: Object, 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 diff --git a/nova_vm/src/types/language/object/property_key.rs b/nova_vm/src/types/language/object/property_key.rs index e8d7e424..ab9db7d6 100644 --- a/nova_vm/src/types/language/object/property_key.rs +++ b/nova_vm/src/types/language/object/property_key.rs @@ -1,4 +1,46 @@ -use crate::types::Value; +use crate::{ + types::{String, Value}, + SmallString, +}; #[derive(Debug, Clone, Copy)] pub struct PropertyKey(Value); + +impl Default for PropertyKey { + fn default() -> Self { + Self(Value::SmallString(SmallString::from_str_unchecked( + "unknown", + ))) + } +} + +impl PropertyKey { + pub(crate) fn new(value: Value) -> Self { + debug_assert!(matches!( + value, + Value::IntegerNumber(_) | Value::String(_) | Value::SmallString(_) + )); + Self(value) + } + + pub fn into_value(self) -> Value { + self.0 + } +} + +impl From for PropertyKey { + fn from(value: String) -> Self { + Self(value.into_value()) + } +} + +impl TryFrom for PropertyKey { + type Error = (); + fn try_from(value: Value) -> Result { + if value.is_string() || value.is_symbol() || value.is_number() { + Ok(Self(value)) + } else { + Err(()) + } + } +} diff --git a/nova_vm/src/types/language/value.rs b/nova_vm/src/types/language/value.rs index 779cce93..46b7d960 100644 --- a/nova_vm/src/types/language/value.rs +++ b/nova_vm/src/types/language/value.rs @@ -79,7 +79,10 @@ impl Value { } pub fn is_object(self) -> bool { - matches!(self, Value::Object(_) | Value::ArrayObject(_)) + matches!( + self, + Value::Object(_) | Value::ArrayObject(_) | Value::Function(_) + ) } pub fn is_string(self) -> bool { @@ -126,6 +129,13 @@ impl Value { matches!(self, Value::Symbol(_)) } + pub fn is_number(self) -> bool { + matches!( + self, + Value::Number(_) | Value::FloatNumber(_) | Value::IntegerNumber(_) + ) + } + pub fn is_empty_string(self) -> bool { if let Value::SmallString(s) = self { s.len() == 0 @@ -539,6 +549,73 @@ impl Value { 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 { @@ -576,6 +653,12 @@ impl TryFrom for Value { } } +impl From for Value { + fn from(value: Number) -> Self { + value.into_value() + } +} + impl From for Value { fn from(value: f32) -> Self { Value::FloatNumber(value) diff --git a/nova_vm/src/types/spec/property_descriptor.rs b/nova_vm/src/types/spec/property_descriptor.rs index 6df57bf8..9bdbf4f1 100644 --- a/nova_vm/src/types/spec/property_descriptor.rs +++ b/nova_vm/src/types/spec/property_descriptor.rs @@ -5,7 +5,7 @@ use crate::{ /// 6.2.6 The Property Descriptor Specification Type /// https://tc39.es/ecma262/#sec-property-descriptor-specification-type -#[derive(Debug)] +#[derive(Debug, Clone, Default)] pub struct PropertyDescriptor { /// [[Value]] pub value: Option, From a88e5289f505d217e81596068a0e508ffc37e87d Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Wed, 26 Jul 2023 14:32:05 -0500 Subject: [PATCH 10/21] pattern-based equality --- nova_vm/src/small_string.rs | 5 +++++ nova_vm/src/types/language/value.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/nova_vm/src/small_string.rs b/nova_vm/src/small_string.rs index 39a8ee9d..458c88ac 100644 --- a/nova_vm/src/small_string.rs +++ b/nova_vm/src/small_string.rs @@ -36,6 +36,11 @@ impl SmallString { return &self.bytes; } + #[inline] + pub fn is_empty(&self) -> bool { + matches!(self.bytes, [0, 0, 0, 0, 0, 0, 0]) + } + pub(crate) fn from_str_unchecked(string: &str) -> Self { let string_bytes = string.as_bytes(); diff --git a/nova_vm/src/types/language/value.rs b/nova_vm/src/types/language/value.rs index 46b7d960..12c2627c 100644 --- a/nova_vm/src/types/language/value.rs +++ b/nova_vm/src/types/language/value.rs @@ -138,7 +138,7 @@ impl Value { pub fn is_empty_string(self) -> bool { if let Value::SmallString(s) = self { - s.len() == 0 + s.is_empty() } else { false } From a7c9c7fca31d91e037b02180fcd7ce7fbd093563 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Wed, 26 Jul 2023 15:05:20 -0500 Subject: [PATCH 11/21] fix invalid truncating stuff --- nova_vm/src/types/language/number.rs | 15 ++++++----- nova_vm/src/types/language/value.rs | 40 ++++++++++++++-------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/nova_vm/src/types/language/number.rs b/nova_vm/src/types/language/number.rs index d69ca1bd..19c3a6c5 100644 --- a/nova_vm/src/types/language/number.rs +++ b/nova_vm/src/types/language/number.rs @@ -181,15 +181,18 @@ impl Number { } /// https://tc39.es/ecma262/#eqn-truncate - /// - /// Guaranteed to be in the valid [`Value::IntegerNumber`] range. - pub fn truncate(self, agent: &mut Agent) -> i64 { + pub fn truncate(self, agent: &mut Agent) -> Number { let x = self.into_value(); match x { - Value::Number(n) => agent.current_realm().borrow().heap.get(n).trunc() as i64, - Value::IntegerNumber(n) => n.into_i64(), - Value::FloatNumber(n) => n.trunc() as i64, + Value::Number(n) => { + let realm = agent.current_realm(); + let mut realm = realm.borrow_mut(); + let n = realm.heap.get(n).trunc(); + realm.heap.create(n) + } + Value::IntegerNumber(_) => self, + Value::FloatNumber(n) => n.trunc().into(), _ => unreachable!(), } } diff --git a/nova_vm/src/types/language/value.rs b/nova_vm/src/types/language/value.rs index 12c2627c..cb780cbe 100644 --- a/nova_vm/src/types/language/value.rs +++ b/nova_vm/src/types/language/value.rs @@ -302,7 +302,7 @@ impl Value { /// 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 = self; + let argument: Value = self; // 1. Let number be ? ToNumber(argument). let number = argument.to_number(agent)?; @@ -323,7 +323,7 @@ impl Value { } // 5. Return truncate(ℝ(number)). - Ok(Number::from(number.truncate(agent))) + Ok(number.truncate(agent)) } /// 7.1.6 ToInt32 ( argument ) @@ -340,14 +340,14 @@ impl Value { } // 3. Let int be truncate(ℝ(number)). - let int = number.truncate(agent); + let int = number.truncate(agent).into_f64(agent); // 4. Let int32bit be int modulo 2^32. - let int32bit = int % 2i64.pow(32); + let int32bit = int % 2f64.powf(32.0); // 5. If int32bit ≥ 2^31, return 𝔽(int32bit - 2^32); otherwise return 𝔽(int32bit). - Ok(if int32bit >= 2i64.pow(32) { - int32bit - 2i64.pow(32) + Ok(if int32bit >= 2f64.powf(32.0) { + int32bit - 2f64.powf(32.0) } else { int32bit } as i32) @@ -367,10 +367,10 @@ impl Value { } // 3. Let int be truncate(ℝ(number)). - let int = number.truncate(agent); + let int = number.truncate(agent).into_f64(agent); // 4. Let int32bit be int modulo 2^32. - let int32bit = int % 2i64.pow(32); + let int32bit = int % 2f64.powf(32.0); // 5. Return 𝔽(int32bit). Ok(int32bit as u32) @@ -390,14 +390,14 @@ impl Value { } // 3. Let int be truncate(ℝ(number)). - let int = number.truncate(agent); + let int = number.truncate(agent).into_f64(agent); // 4. Let int16bit be int modulo 2^16. - let int16bit = int % 2i64.pow(16); + let int16bit = int % 2f64.powf(16.0); // 5. If int16bit ≥ 2^15, return 𝔽(int16bit - 2^16); otherwise return 𝔽(int16bit). - Ok(if int16bit >= 2i64.pow(15) { - int16bit - 2i64.pow(16) + Ok(if int16bit >= 2f64.powf(15.0) { + int16bit - 2f64.powf(16.0) } else { int16bit } as i16) @@ -417,10 +417,10 @@ impl Value { } // 3. Let int be truncate(ℝ(number)). - let int = number.truncate(agent); + let int = number.truncate(agent).into_f64(agent); // 4. Let int16bit be int modulo 2^16. - let int16bit = int % 2i64.pow(16); + let int16bit = int % 2f64.powf(16.0); // Return 𝔽(int16bit). Ok(int16bit as i16) @@ -440,14 +440,14 @@ impl Value { } // 3. Let int be truncate(ℝ(number)). - let int = number.truncate(agent); + let int = number.truncate(agent).into_f64(agent); // 4. Let int8bit be int modulo 2^8. - let int8bit = int % 2i64.pow(8); + let int8bit = int % 2f64.powf(8.0); // 5. If int8bit ≥ 2^7, return 𝔽(int8bit - 2^8); otherwise return 𝔽(int8bit). - Ok(if int8bit >= 2i64.pow(7) { - int8bit - 2i64.pow(8) + Ok(if int8bit >= 2f64.powf(7.0) { + int8bit - 2f64.powf(8.0) } else { int8bit } as i8) @@ -467,10 +467,10 @@ impl Value { } // 3. Let int be truncate(ℝ(number)). - let int = number.truncate(agent); + let int = number.truncate(agent).into_f64(agent); // 4. Let int8bit be int modulo 2^8. - let int8bit = int % 2i64.pow(8); + let int8bit = int % 2f64.powf(8.0); // 5. Return 𝔽(int8bit). Ok(int8bit as u8) From 2bb7b94821261a41685fabdf2718f8ef151a03c8 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Thu, 27 Jul 2023 14:11:59 -0500 Subject: [PATCH 12/21] checkpoint --- nova_vm/src/builtins/builtin_function.rs | 12 +- nova_vm/src/builtins/number.rs | 39 +++- nova_vm/src/builtins/ordinary.rs | 61 +++++- nova_vm/src/heap.rs | 186 ++++++++++-------- nova_vm/src/heap/array.rs | 20 +- nova_vm/src/heap/bigint.rs | 9 + nova_vm/src/heap/function.rs | 18 +- nova_vm/src/heap/object.rs | 24 ++- nova_vm/src/heap/string.rs | 7 + nova_vm/src/heap/symbol.rs | 11 +- nova_vm/src/language/bytecode/vm.rs | 2 - nova_vm/src/types/language.rs | 2 +- nova_vm/src/types/language/number.rs | 30 +-- nova_vm/src/types/language/object.rs | 34 +++- .../src/types/language/object/property_key.rs | 70 +++++++ .../types/language/object/property_storage.rs | 52 +++++ nova_vm/src/types/language/value.rs | 30 +-- nova_vm/src/types/spec/reference.rs | 2 +- 18 files changed, 474 insertions(+), 135 deletions(-) create mode 100644 nova_vm/src/types/language/object/property_storage.rs diff --git a/nova_vm/src/builtins/builtin_function.rs b/nova_vm/src/builtins/builtin_function.rs index e34abdcb..f9d6344f 100644 --- a/nova_vm/src/builtins/builtin_function.rs +++ b/nova_vm/src/builtins/builtin_function.rs @@ -7,6 +7,13 @@ use crate::{ #[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; @@ -76,6 +83,7 @@ pub fn create_builtin_function<'a, 'b: 'a>( // 8. Set func.[[Realm]] to realm. // NOTE: Heap data is implicitly attached to the Realm so I don't think // this matters. + entries: Vec::new(), }); let initial_name = realm.heap.create(args.name).into_value(); @@ -83,8 +91,8 @@ pub fn create_builtin_function<'a, 'b: 'a>( bits: HeapBits::new(), object: object.into_object_handle().unwrap(), behaviour, - // 9. Set func.[[InitialName]] to null. - // TODO: This is non-standard. + // TODO: 9. Set func.[[InitialName]] to null. + // NOTE: This is non-standard. initial_name, // 10. Perform SetFunctionLength(func, length). length: args.length as i64, diff --git a/nova_vm/src/builtins/number.rs b/nova_vm/src/builtins/number.rs index fffd8bf7..f937f8de 100644 --- a/nova_vm/src/builtins/number.rs +++ b/nova_vm/src/builtins/number.rs @@ -1,6 +1,7 @@ use super::{ builtin_function::{define_builtin_function, define_builtin_property}, - create_builtin_function, todo_builtin, ArgumentsList, Behaviour, Builtin, BuiltinFunctionArgs, + create_builtin_function, ordinary, todo_builtin, ArgumentsList, Behaviour, Builtin, + BuiltinFunctionArgs, }; use crate::{ execution::{Agent, Intrinsics, JsResult, Realm}, @@ -14,7 +15,7 @@ pub struct NumberConstructor; impl Builtin for NumberConstructor { fn create<'a>(realm: &'a mut Realm<'a, 'a>) -> JsResult { let object: Object = create_builtin_function( - Behaviour::Regular(NumberConstructor::behaviour), + Behaviour::Constructor(Self::behaviour), BuiltinFunctionArgs { length: 1, name: "Number", @@ -176,11 +177,45 @@ impl Builtin for NumberConstructor { } 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 index 93fd3ef0..f3b248d0 100644 --- a/nova_vm/src/builtins/ordinary.rs +++ b/nova_vm/src/builtins/ordinary.rs @@ -1,6 +1,6 @@ use crate::{ execution::{Agent, JsResult}, - types::{InternalMethods, Object}, + types::{InternalMethods, Object, PropertyDescriptor, PropertyKey, Value}, }; /// 10.1 Ordinary Object Internal Methods and Internal Slots @@ -138,8 +138,63 @@ fn prevent_extensions(agent: &mut Agent, object: Object) -> JsResult { /// https://tc39.es/ecma262/#sec-ordinarypreventextensions pub fn ordinary_prevent_extensions(agent: &mut Agent, object: Object) -> bool { // 1. Set O.[[Extensible]] to false. - todo!(); + object.set_extensible(agent, false); // 2. Return true. - 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) } diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 22ec0019..e48e8cca 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -19,22 +19,28 @@ pub use symbol::SymbolHeapData; use self::heap_trace::HeapTrace; use crate::types::{Function, Number, Object, String, Value}; -use std::{cell::Cell, marker::PhantomData}; +use std::{cell::Cell, marker::PhantomData, num::NonZeroU32}; use wtf8::{Wtf8, Wtf8Buf}; /// A handle to GC-managed memory. #[derive(Clone)] pub struct Handle { - id: u32, + id: NonZeroU32, _marker: &'static PhantomData, } impl Copy for Handle {} +impl PartialEq for Handle { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + impl Handle { pub fn new(id: u32) -> Self { Self { - id, + id: NonZeroU32::try_from(id).unwrap(), // SAFETY: We hopefully will make sure handles ar esafe. _marker: unsafe { std::mem::transmute::<&PhantomData, &'static PhantomData>( @@ -83,8 +89,9 @@ pub trait CreateHeapData { fn create(&mut self, data: T) -> F; } -pub trait GetHeapData<'a, T, F: 'a> { - fn get(&'a self, handle: Handle) -> F; +pub trait GetHeapData<'a, T, F> { + fn get(&'a self, handle: Handle) -> &'a F; + fn get_mut(&'a mut self, handle: Handle) -> &'a mut F; } impl CreateHeapData for Heap { @@ -100,18 +107,58 @@ impl CreateHeapData for Heap { } } -impl<'a> GetHeapData<'a, NumberHeapData, f64> for Heap { - fn get(&'a self, handle: Handle) -> f64 { - self.numbers - .get(handle.id as usize) - .as_ref() - .unwrap() - .as_ref() - .unwrap() - .data - } +macro_rules! impl_heap_data { + ($table: ident, $in: ty, $out: ty) => { + impl<'a> GetHeapData<'a, $in, $out> for Heap { + fn get(&'a self, handle: Handle<$in>) -> &'a $out { + self.$table + .get(handle.id.get() as usize) + .unwrap() + .as_ref() + .unwrap() + } + + fn get_mut(&'a mut self, handle: Handle<$in>) -> &'a mut $out { + self.$table + .get_mut(handle.id.get() as usize) + .unwrap() + .as_mut() + .unwrap() + } + } + }; + ($table: ident, $in: ty, $out: ty, $accessor: ident) => { + impl<'a> GetHeapData<'a, $in, $out> for Heap { + fn get(&'a self, handle: Handle<$in>) -> &'a $out { + &self + .$table + .get(handle.id.get() as usize) + .as_ref() + .unwrap() + .as_ref() + .unwrap() + .$accessor + } + + fn get_mut(&'a mut self, handle: Handle<$in>) -> &'a mut $out { + &mut self + .$table + .get_mut(handle.id.get() as usize) + .unwrap() + .as_mut() + .unwrap() + .$accessor + } + } + }; } +impl_heap_data!(numbers, NumberHeapData, f64, data); +impl_heap_data!(objects, ObjectHeapData, ObjectHeapData); +impl_heap_data!(strings, StringHeapData, Wtf8Buf, data); +impl_heap_data!(functions, FunctionHeapData, FunctionHeapData); +impl_heap_data!(arrays, ArrayHeapData, ArrayHeapData); + impl CreateHeapData<&str, String> for Heap { fn create(&mut self, data: &str) -> String { if let Ok(value) = String::try_from(data) { @@ -123,19 +170,6 @@ impl CreateHeapData<&str, String> for Heap { } } -impl<'a> GetHeapData<'a, StringHeapData, &'a Wtf8> for Heap { - fn get(&'a self, handle: Handle) -> &'a Wtf8 { - let data = self - .strings - .get(handle.id as usize) - .as_ref() - .unwrap() - .as_ref() - .unwrap(); - &data.data.slice(0, data.data.len()) - } -} - impl CreateHeapData for Heap { fn create(&mut self, data: FunctionHeapData) -> Function { let id = self.functions.len(); @@ -144,17 +178,6 @@ impl CreateHeapData for Heap { } } -impl<'a> GetHeapData<'a, FunctionHeapData, &'a FunctionHeapData> for Heap { - fn get(&'a self, handle: Handle) -> &'a FunctionHeapData { - self.functions - .get(handle.id as usize) - .as_ref() - .unwrap() - .as_ref() - .unwrap() - } -} - impl CreateHeapData for Heap { fn create(&mut self, data: ObjectHeapData) -> Object { let id: usize = self.functions.len(); @@ -163,17 +186,6 @@ impl CreateHeapData for Heap { } } -impl<'a> GetHeapData<'a, ObjectHeapData, &'a mut ObjectHeapData> for Heap { - fn get(&'a self, handle: Handle) -> &'a mut ObjectHeapData { - self.objects - .get_mut(handle.id as usize) - .as_ref() - .unwrap() - .as_mut() - .unwrap() - } -} - impl Heap { pub fn new() -> Heap { let mut heap = Heap { @@ -186,6 +198,14 @@ impl Heap { functions: Vec::with_capacity(1024), }; + heap.strings.push(Some(StringHeapData::dummy())); + heap.symbols.push(Some(SymbolHeapData::dummy())); + heap.numbers.push(Some(NumberHeapData::new(0.0))); + heap.bigints.push(Some(BigIntHeapData::dummy())); + heap.objects.push(Some(ObjectHeapData::dummy())); + heap.arrays.push(Some(ArrayHeapData::dummy())); + heap.functions.push(Some(FunctionHeapData::dummy())); + heap } @@ -215,35 +235,35 @@ impl Heap { fn complete_trace(&mut self) -> () { // TODO: Consider roots count - for object in self.objects.iter() { + for object in self.objects.iter().skip(1) { let Some(data) = object else { continue; }; data.bits.marked.set(false); data.bits.dirty.set(false); } - for string in self.strings.iter() { + for string in self.strings.iter().skip(1) { let Some(data) = string else { continue; }; data.bits.marked.set(false); data.bits.dirty.set(false); } - for symbol in self.symbols.iter() { + for symbol in self.symbols.iter().skip(1) { let Some(data) = symbol else { continue; }; data.bits.marked.set(false); data.bits.dirty.set(false); } - for number in self.numbers.iter() { + for number in self.numbers.iter().skip(1) { let Some(data) = number else { continue; }; data.bits.marked.set(false); data.bits.dirty.set(false); } - for bigint in self.bigints.iter() { + for bigint in self.bigints.iter().skip(1) { let Some(data) = bigint else { continue; }; @@ -252,7 +272,7 @@ impl Heap { } stop_the_world(); // Trace from dirty objects and symbols. - for object in self.objects.iter_mut() { + for object in self.objects.iter_mut().skip(1) { let Some(data) = object else { continue; }; @@ -261,7 +281,7 @@ impl Heap { let _ = object.take(); } } - for string in self.strings.iter_mut() { + for string in self.strings.iter_mut().skip(1) { let Some(data) = string else { continue; }; @@ -270,7 +290,7 @@ impl Heap { let _ = string.take(); } } - for symbol in self.symbols.iter_mut() { + for symbol in self.symbols.iter_mut().skip(1) { let Some(data) = symbol else { continue; }; @@ -279,7 +299,7 @@ impl Heap { let _ = symbol.take(); } } - for number in self.numbers.iter_mut() { + for number in self.numbers.iter_mut().skip(1) { let Some(data) = number else { continue; }; @@ -288,7 +308,7 @@ impl Heap { let _ = number.take(); } } - for bigint in self.bigints.iter_mut() { + for bigint in self.bigints.iter_mut().skip(1) { let Some(data) = bigint else { continue; }; @@ -297,7 +317,7 @@ impl Heap { let _ = bigint.take(); } } - for object in self.objects.iter_mut() { + for object in self.objects.iter_mut().skip(1) { let Some(data) = object else { continue; }; @@ -306,7 +326,7 @@ impl Heap { let _ = object.take(); } } - for function in self.functions.iter_mut() { + for function in self.functions.iter_mut().skip(1) { let Some(data) = function else { continue; }; @@ -343,39 +363,39 @@ impl Heap { impl HeapTrace for Value { fn trace(&self, heap: &Heap) { match self { - &Value::String(handle) => heap.strings[handle.id as usize].trace(heap), - &Value::Symbol(handle) => heap.symbols[handle.id as usize].trace(heap), - &Value::Number(handle) => heap.numbers[handle.id as usize].trace(heap), - &Value::BigInt(handle) => heap.bigints[handle.id as usize].trace(heap), - &Value::Object(handle) => heap.objects[handle.id as usize].trace(heap), - &Value::ArrayObject(handle) => heap.arrays[handle.id as usize].trace(heap), - &Value::Function(handle) => heap.functions[handle.id as usize].trace(heap), + &Value::String(handle) => heap.strings[handle.id.get() as usize].trace(heap), + &Value::Symbol(handle) => heap.symbols[handle.id.get() as usize].trace(heap), + &Value::Number(handle) => heap.numbers[handle.id.get() as usize].trace(heap), + &Value::BigInt(handle) => heap.bigints[handle.id.get() as usize].trace(heap), + &Value::Object(handle) => heap.objects[handle.id.get() as usize].trace(heap), + &Value::ArrayObject(handle) => heap.arrays[handle.id.get() as usize].trace(heap), + &Value::Function(handle) => heap.functions[handle.id.get() as usize].trace(heap), _ => {} } } fn root(&self, heap: &Heap) { match self { - &Value::String(handle) => heap.strings[handle.id as usize].root(heap), - &Value::Symbol(handle) => heap.symbols[handle.id as usize].root(heap), - &Value::Number(handle) => heap.numbers[handle.id as usize].root(heap), - &Value::BigInt(handle) => heap.bigints[handle.id as usize].root(heap), - &Value::Object(handle) => heap.objects[handle.id as usize].root(heap), - &Value::ArrayObject(handle) => heap.arrays[handle.id as usize].root(heap), - &Value::Function(handle) => heap.functions[handle.id as usize].root(heap), + &Value::String(handle) => heap.strings[handle.id.get() as usize].root(heap), + &Value::Symbol(handle) => heap.symbols[handle.id.get() as usize].root(heap), + &Value::Number(handle) => heap.numbers[handle.id.get() as usize].root(heap), + &Value::BigInt(handle) => heap.bigints[handle.id.get() as usize].root(heap), + &Value::Object(handle) => heap.objects[handle.id.get() as usize].root(heap), + &Value::ArrayObject(handle) => heap.arrays[handle.id.get() as usize].root(heap), + &Value::Function(handle) => heap.functions[handle.id.get() as usize].root(heap), _ => {} } } fn unroot(&self, heap: &Heap) { match self { - &Value::String(handle) => heap.strings[handle.id as usize].unroot(heap), - &Value::Symbol(handle) => heap.symbols[handle.id as usize].unroot(heap), - &Value::Number(handle) => heap.numbers[handle.id as usize].unroot(heap), - &Value::BigInt(handle) => heap.bigints[handle.id as usize].unroot(heap), - &Value::Object(handle) => heap.objects[handle.id as usize].unroot(heap), - &Value::ArrayObject(handle) => heap.arrays[handle.id as usize].unroot(heap), - &Value::Function(handle) => heap.functions[handle.id as usize].unroot(heap), + &Value::String(handle) => heap.strings[handle.id.get() as usize].unroot(heap), + &Value::Symbol(handle) => heap.symbols[handle.id.get() as usize].unroot(heap), + &Value::Number(handle) => heap.numbers[handle.id.get() as usize].unroot(heap), + &Value::BigInt(handle) => heap.bigints[handle.id.get() as usize].unroot(heap), + &Value::Object(handle) => heap.objects[handle.id.get() as usize].unroot(heap), + &Value::ArrayObject(handle) => heap.arrays[handle.id.get() as usize].unroot(heap), + &Value::Function(handle) => heap.functions[handle.id.get() as usize].unroot(heap), _ => {} } } diff --git a/nova_vm/src/heap/array.rs b/nova_vm/src/heap/array.rs index 8d7366fd..14a20a23 100644 --- a/nova_vm/src/heap/array.rs +++ b/nova_vm/src/heap/array.rs @@ -1,21 +1,33 @@ -use super::heap_trace::HeapTrace; +use super::{heap_trace::HeapTrace, Handle, ObjectHeapData}; use crate::{ heap::{Heap, HeapBits}, - types::Value, + types::{Object, Value}, }; #[derive(Debug, Clone)] pub struct ArrayHeapData { pub(super) bits: HeapBits, - pub(super) object_index: u32, + pub(super) object: Option>, // TODO: Use SmallVec<[Value; 4]> pub(super) elements: Vec>, } +impl ArrayHeapData { + pub fn dummy() -> Self { + Self { + bits: HeapBits::new(), + object: None, + elements: Vec::new(), + } + } +} + impl HeapTrace for Option { fn trace(&self, heap: &Heap) { assert!(self.is_some()); - heap.objects[self.as_ref().unwrap().object_index as usize].trace(heap); + if let Some(object) = self.as_ref().unwrap().object { + heap.objects[object.id.get() as usize].trace(heap); + } } fn root(&self, _heap: &Heap) { assert!(self.is_some()); diff --git a/nova_vm/src/heap/bigint.rs b/nova_vm/src/heap/bigint.rs index 436c8e8d..16310904 100644 --- a/nova_vm/src/heap/bigint.rs +++ b/nova_vm/src/heap/bigint.rs @@ -8,6 +8,15 @@ pub struct BigIntHeapData { pub(super) data: BigInt, } +impl BigIntHeapData { + pub fn dummy() -> Self { + Self { + bits: HeapBits::new(), + data: BigInt::default(), + } + } +} + impl HeapTrace for Option { fn trace(&self, _heap: &Heap) {} diff --git a/nova_vm/src/heap/function.rs b/nova_vm/src/heap/function.rs index 29b51df6..48324a2e 100644 --- a/nova_vm/src/heap/function.rs +++ b/nova_vm/src/heap/function.rs @@ -1,7 +1,7 @@ use super::{heap_trace::HeapTrace, Handle, HeapBits, ObjectHeapData}; use crate::{ - builtins::{Behaviour, JsFunction}, - types::{Object, Value}, + builtins::{todo_builtin, Behaviour}, + types::Value, Heap, }; @@ -19,10 +19,22 @@ pub struct FunctionHeapData { // pub(super) visible: Option>, } +impl FunctionHeapData { + pub fn dummy() -> Self { + Self { + bits: HeapBits::new(), + object: Handle::new(0), + initial_name: Value::Null, + length: 0, + behaviour: Behaviour::Regular(todo_builtin), + } + } +} + impl HeapTrace for Option { fn trace(&self, heap: &Heap) { assert!(self.is_some()); - heap.objects[self.as_ref().unwrap().object.id as usize].trace(heap); + heap.objects[self.as_ref().unwrap().object.id.get() as usize].trace(heap); } fn root(&self, _heap: &Heap) { assert!(self.is_some()); diff --git a/nova_vm/src/heap/object.rs b/nova_vm/src/heap/object.rs index 0b38e02e..fc69e12f 100644 --- a/nova_vm/src/heap/object.rs +++ b/nova_vm/src/heap/object.rs @@ -1,6 +1,8 @@ +use std::collections::HashMap; + use crate::{ heap::{heap_trace::HeapTrace, Heap, HeapBits}, - types::PropertyDescriptor, + types::{PropertyDescriptor, PropertyKey}, }; #[derive(Debug, Clone)] @@ -14,7 +16,25 @@ pub struct ObjectHeapData { // with functions and possible other special object cases we want to track with partially // separate heap fields later down the line. pub(crate) prototype: PropertyDescriptor, - // pub(crate) entries: Vec, + // TODO: Consider using detached vectors for keys/descriptors. + pub(crate) entries: Vec, +} + +#[derive(Debug, Clone)] +pub struct ObjectEntry { + pub(crate) key: PropertyKey, + pub(crate) value: PropertyDescriptor, +} + +impl ObjectHeapData { + pub fn dummy() -> Self { + Self { + bits: HeapBits::new(), + extensible: false, + prototype: PropertyDescriptor::default(), + entries: Vec::new(), + } + } } impl HeapTrace for Option { diff --git a/nova_vm/src/heap/string.rs b/nova_vm/src/heap/string.rs index 50d94d5e..e5239d93 100644 --- a/nova_vm/src/heap/string.rs +++ b/nova_vm/src/heap/string.rs @@ -8,6 +8,13 @@ pub struct StringHeapData { } impl StringHeapData { + pub fn dummy() -> Self { + Self { + bits: HeapBits::new(), + data: Wtf8Buf::new(), + } + } + pub fn from_str(str: &str) -> Self { StringHeapData { bits: HeapBits::new(), diff --git a/nova_vm/src/heap/symbol.rs b/nova_vm/src/heap/symbol.rs index 1212c178..121a2b39 100644 --- a/nova_vm/src/heap/symbol.rs +++ b/nova_vm/src/heap/symbol.rs @@ -7,11 +7,20 @@ pub struct SymbolHeapData { pub(super) descriptor: Option>, } +impl SymbolHeapData { + pub fn dummy() -> Self { + Self { + bits: HeapBits::new(), + descriptor: None, + } + } +} + impl HeapTrace for Option { fn trace(&self, heap: &Heap) { assert!(self.is_some()); if let Some(handle) = self.as_ref().unwrap().descriptor { - heap.strings[handle.id as usize].trace(heap); + heap.strings[handle.id.get() as usize].trace(heap); } } fn root(&self, _heap: &Heap) { diff --git a/nova_vm/src/language/bytecode/vm.rs b/nova_vm/src/language/bytecode/vm.rs index 8cdf481c..27694b40 100644 --- a/nova_vm/src/language/bytecode/vm.rs +++ b/nova_vm/src/language/bytecode/vm.rs @@ -1,5 +1,3 @@ -use std::{cell::RefCell, rc::Rc}; - use oxc_span::Atom; use crate::{ diff --git a/nova_vm/src/types/language.rs b/nova_vm/src/types/language.rs index e80f3d79..fb497550 100644 --- a/nova_vm/src/types/language.rs +++ b/nova_vm/src/types/language.rs @@ -8,6 +8,6 @@ mod value; pub use bigint::BigInt; pub use function::Function; pub use number::Number; -pub use object::{InternalMethods, Object, ObjectData, PropertyKey}; +pub use object::{InternalMethods, Object, ObjectData, PropertyKey, PropertyStorage}; pub use string::String; pub use value::Value; diff --git a/nova_vm/src/types/language/number.rs b/nova_vm/src/types/language/number.rs index 19c3a6c5..1d903763 100644 --- a/nova_vm/src/types/language/number.rs +++ b/nova_vm/src/types/language/number.rs @@ -137,7 +137,7 @@ impl Number { let x = self.into_value(); match x { - Value::Number(n) => agent.current_realm().borrow().heap.get(n) == f64::INFINITY, + Value::Number(n) => *agent.current_realm().borrow().heap.get(n) == f64::INFINITY, Value::IntegerNumber(_) => false, Value::FloatNumber(n) => n == f32::INFINITY, _ => unreachable!(), @@ -148,7 +148,7 @@ impl Number { let x = self.into_value(); match x { - Value::Number(n) => agent.current_realm().borrow().heap.get(n) == f64::NEG_INFINITY, + Value::Number(n) => *agent.current_realm().borrow().heap.get(n) == f64::NEG_INFINITY, Value::IntegerNumber(_) => false, Value::FloatNumber(n) => n == f32::NEG_INFINITY, _ => unreachable!(), @@ -171,7 +171,7 @@ impl Number { match x { Value::Number(n) => { - let n = agent.current_realm().borrow().heap.get(n); + let n = *agent.current_realm().borrow().heap.get(n); !n.is_sign_negative() && !n.is_sign_positive() } Value::IntegerNumber(_) => true, @@ -201,7 +201,7 @@ impl Number { let x = self.into_value(); match x { - Value::Number(n) => agent.current_realm().borrow().heap.get(n), + Value::Number(n) => *agent.current_realm().borrow().heap.get(n), Value::IntegerNumber(n) => Into::::into(n) as f64, Value::FloatNumber(n) => n as f64, _ => unreachable!(), @@ -220,18 +220,18 @@ impl Number { == agent.current_realm().borrow().heap.get(y) } (Value::Number(x), Value::IntegerNumber(y)) => { - agent.current_realm().borrow().heap.get(x) == y.into_i64() as f64 + *agent.current_realm().borrow().heap.get(x) == y.into_i64() as f64 } (Value::Number(x), Value::FloatNumber(y)) => { - agent.current_realm().borrow().heap.get(x) == y as f64 + *agent.current_realm().borrow().heap.get(x) == y as f64 } (Value::IntegerNumber(x), Value::Number(y)) => { - (x.into_i64() as f64) == agent.current_realm().borrow().heap.get(y) + (x.into_i64() as f64) == *agent.current_realm().borrow().heap.get(y) } (Value::IntegerNumber(x), Value::IntegerNumber(y)) => x.into_i64() == y.into_i64(), (Value::IntegerNumber(x), Value::FloatNumber(y)) => (x.into_i64() as f64) == y as f64, (Value::FloatNumber(x), Value::Number(y)) => { - (x as f64) == agent.current_realm().borrow().heap.get(y) + (x as f64) == *agent.current_realm().borrow().heap.get(y) } (Value::FloatNumber(x), Value::IntegerNumber(y)) => (x as f64) == y.into_i64() as f64, (Value::FloatNumber(x), Value::FloatNumber(y)) => x == y, @@ -244,7 +244,7 @@ impl Number { match x { Value::Number(n) => { - let n = agent.current_realm().borrow().heap.get(n); + let n = *agent.current_realm().borrow().heap.get(n); n % 1.0 == 0.0 && n % 2.0 == 0.0 } Value::IntegerNumber(n) => Into::::into(n) % 2 == 0, @@ -258,7 +258,7 @@ impl Number { match x { Value::Number(n) => { - let n = agent.current_realm().borrow().heap.get(n); + let n = *agent.current_realm().borrow().heap.get(n); if n > 0.0 { self } else { @@ -294,7 +294,7 @@ impl Number { Value::Number(n) => { let realm = agent.current_realm(); let mut realm = realm.borrow_mut(); - let value = realm.heap.get(n); + let value = *realm.heap.get(n); realm.heap.create(-value) } Value::IntegerNumber(n) => SmallInteger::from_i64_unchecked(-n.into_i64()).into(), @@ -518,18 +518,18 @@ impl Number { < agent.current_realm().borrow().heap.get(y) } (Value::Number(x), Value::IntegerNumber(y)) => { - agent.current_realm().borrow().heap.get(x) < y.into_i64() as f64 + *agent.current_realm().borrow().heap.get(x) < y.into_i64() as f64 } (Value::Number(x), Value::FloatNumber(y)) => { - agent.current_realm().borrow().heap.get(x) < y as f64 + *agent.current_realm().borrow().heap.get(x) < y as f64 } (Value::IntegerNumber(x), Value::Number(y)) => { - (x.into_i64() as f64) < agent.current_realm().borrow().heap.get(y) + (x.into_i64() as f64) < *agent.current_realm().borrow().heap.get(y) } (Value::IntegerNumber(x), Value::IntegerNumber(y)) => x.into_i64() < y.into_i64(), (Value::IntegerNumber(x), Value::FloatNumber(y)) => (x.into_i64() as f64) < y as f64, (Value::FloatNumber(x), Value::Number(y)) => { - (x as f64) < agent.current_realm().borrow().heap.get(y) + (x as f64) < *agent.current_realm().borrow().heap.get(y) } (Value::FloatNumber(x), Value::IntegerNumber(y)) => (x as f64) < y.into_i64() as f64, (Value::FloatNumber(x), Value::FloatNumber(y)) => x < y, diff --git a/nova_vm/src/types/language/object.rs b/nova_vm/src/types/language/object.rs index 667d7c91..25ff4fad 100644 --- a/nova_vm/src/types/language/object.rs +++ b/nova_vm/src/types/language/object.rs @@ -1,6 +1,7 @@ mod data; mod internal_methods; mod property_key; +mod property_storage; use crate::{ builtins::ordinary, @@ -9,10 +10,11 @@ use crate::{ types::PropertyDescriptor, }; -use super::{Function, Value}; +use super::Value; pub use data::ObjectData; pub use internal_methods::InternalMethods; pub use property_key::PropertyKey; +pub use property_storage::PropertyStorage; /// 6.1.7 The Object Type /// https://tc39.es/ecma262/#sec-object-type @@ -50,6 +52,7 @@ impl Object { } } + /// [[Extensible]] pub fn extensible(self, agent: &mut Agent) -> bool { let object = self.into_value(); @@ -61,6 +64,25 @@ impl Object { } } + /// [[Extensible]] + pub fn set_extensible(self, agent: &mut Agent, value: bool) { + let object = self.into_value(); + + match object { + Value::Object(object) => { + let realm = agent.current_realm(); + let mut realm = realm.borrow_mut(); + let object = realm.heap.get_mut(object); + object.extensible = true; + } + // TODO: Correct object/function impl + Value::ArrayObject(_) => {} + Value::Function(_) => {} + _ => unreachable!(), + } + } + + /// [[Prototype]] pub fn prototype(self, agent: &mut Agent) -> Option { let object = self.into_value(); @@ -77,15 +99,15 @@ impl Object { } } - // TODO: Is there a spec variant of this? + /// [[Prototype]] pub fn set_prototype(self, agent: &mut Agent, prototype: Option) { let object = self.into_value(); match object { Value::Object(object) => { let realm = agent.current_realm(); - let realm = realm.borrow_mut(); - let object = realm.heap.get(object); + let mut realm = realm.borrow_mut(); + let object = realm.heap.get_mut(object); object.prototype.value = prototype.map(|object| object.into_value()); } Value::ArrayObject(_) => todo!(), @@ -99,6 +121,10 @@ impl Object { &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( diff --git a/nova_vm/src/types/language/object/property_key.rs b/nova_vm/src/types/language/object/property_key.rs index ab9db7d6..255172c2 100644 --- a/nova_vm/src/types/language/object/property_key.rs +++ b/nova_vm/src/types/language/object/property_key.rs @@ -1,4 +1,6 @@ use crate::{ + execution::Agent, + heap::GetHeapData, types::{String, Value}, SmallString, }; @@ -26,6 +28,58 @@ impl PropertyKey { pub fn into_value(self) -> Value { self.0 } + + pub(self) fn is_str_eq_num(s: &str, n: i64) -> bool { + let (s, mut n) = if s.starts_with("-") { + if n > 0 { + return false; + } + (&s[1..], -n as usize) + } else { + if n < 0 { + return false; + } + (s, n as usize) + }; + + if Some(s.len()) != n.checked_ilog10().map(|n| n as usize) { + return false; + } + + for c in s.as_bytes().iter().rev() { + let code = (n % 10) as u8 + '0' as u8; + + if *c != code { + return false; + } + + n /= 10; + } + + true + } + + pub fn equals(self, agent: &mut Agent, y: Self) -> bool { + let x = self.into_value(); + let y = y.into_value(); + + match (x, y) { + // Assumes the interner is working correctly. + (Value::String(s1), Value::String(s2)) => s1 == s2, + (Value::String(s), Value::IntegerNumber(n)) => { + let realm = agent.current_realm(); + let realm = realm.borrow(); + let s = realm.heap.get(s); + + let Some(s) = s.as_str() else { + return false; + }; + + Self::is_str_eq_num(s, n.into_i64()) + } + _ => unreachable!(), + } + } } impl From for PropertyKey { @@ -44,3 +98,19 @@ impl TryFrom for PropertyKey { } } } + +#[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..fe58f431 --- /dev/null +++ b/nova_vm/src/types/language/object/property_storage.rs @@ -0,0 +1,52 @@ +use crate::{ + execution::Agent, + heap::GetHeapData, + types::{PropertyDescriptor, Value}, +}; + +use super::{Object, PropertyKey}; + +#[derive(Clone, Copy)] +pub struct PropertyStorage(Object); + +impl PropertyStorage { + pub(crate) fn new(object: Object) -> Self { + Self(object) + } + + fn into_object(self) -> Object { + self.0 + } + + fn into_value(self) -> Value { + self.into_object().into_value() + } + + pub fn has(self, agent: &mut Agent, key: PropertyKey) -> bool { + let object = self.into_value(); + + match object { + Value::Object(object) => { + let realm = agent.current_realm(); + let realm = realm.borrow(); + let object = realm.heap.get(object); + object + .entries + .iter() + .any(|entry| entry.key.equals(agent, key)) + } + Value::ArrayObject(array) => { + let realm = agent.current_realm(); + let realm = realm.borrow(); + let array = agent.current_realm().borrow().heap.get(array); + true + } + Value::Function(_) => todo!(), + _ => unreachable!(), + } + } + + pub fn get(self, agent: &mut Agent, key: PropertyKey) -> Option { + todo!(); + } +} diff --git a/nova_vm/src/types/language/value.rs b/nova_vm/src/types/language/value.rs index cb780cbe..86ed125a 100644 --- a/nova_vm/src/types/language/value.rs +++ b/nova_vm/src/types/language/value.rs @@ -51,6 +51,12 @@ pub enum Value { Function(Handle), } +impl Default for Value { + fn default() -> Self { + Value::Undefined + } +} + #[derive(Debug, Clone, Copy)] pub enum PreferredType { String, @@ -343,11 +349,11 @@ impl Value { let int = number.truncate(agent).into_f64(agent); // 4. Let int32bit be int modulo 2^32. - let int32bit = int % 2f64.powf(32.0); + let int32bit = int % 2f64.powi(32); // 5. If int32bit ≥ 2^31, return 𝔽(int32bit - 2^32); otherwise return 𝔽(int32bit). - Ok(if int32bit >= 2f64.powf(32.0) { - int32bit - 2f64.powf(32.0) + Ok(if int32bit >= 2f64.powi(32) { + int32bit - 2f64.powi(32) } else { int32bit } as i32) @@ -370,7 +376,7 @@ impl Value { let int = number.truncate(agent).into_f64(agent); // 4. Let int32bit be int modulo 2^32. - let int32bit = int % 2f64.powf(32.0); + let int32bit = int % 2f64.powi(32); // 5. Return 𝔽(int32bit). Ok(int32bit as u32) @@ -393,11 +399,11 @@ impl Value { let int = number.truncate(agent).into_f64(agent); // 4. Let int16bit be int modulo 2^16. - let int16bit = int % 2f64.powf(16.0); + let int16bit = int % 2f64.powi(16); // 5. If int16bit ≥ 2^15, return 𝔽(int16bit - 2^16); otherwise return 𝔽(int16bit). - Ok(if int16bit >= 2f64.powf(15.0) { - int16bit - 2f64.powf(16.0) + Ok(if int16bit >= 2f64.powi(15) { + int16bit - 2f64.powi(16) } else { int16bit } as i16) @@ -420,7 +426,7 @@ impl Value { let int = number.truncate(agent).into_f64(agent); // 4. Let int16bit be int modulo 2^16. - let int16bit = int % 2f64.powf(16.0); + let int16bit = int % 2f64.powi(16); // Return 𝔽(int16bit). Ok(int16bit as i16) @@ -443,11 +449,11 @@ impl Value { let int = number.truncate(agent).into_f64(agent); // 4. Let int8bit be int modulo 2^8. - let int8bit = int % 2f64.powf(8.0); + let int8bit = int % 2f64.powi(8); // 5. If int8bit ≥ 2^7, return 𝔽(int8bit - 2^8); otherwise return 𝔽(int8bit). - Ok(if int8bit >= 2f64.powf(7.0) { - int8bit - 2f64.powf(8.0) + Ok(if int8bit >= 2f64.powi(7) { + int8bit - 2f64.powi(8) } else { int8bit } as i8) @@ -470,7 +476,7 @@ impl Value { let int = number.truncate(agent).into_f64(agent); // 4. Let int8bit be int modulo 2^8. - let int8bit = int % 2f64.powf(8.0); + let int8bit = int % 2f64.powi(8); // 5. Return 𝔽(int8bit). Ok(int8bit as u8) diff --git a/nova_vm/src/types/spec/reference.rs b/nova_vm/src/types/spec/reference.rs index a2e2d434..0027e6fb 100644 --- a/nova_vm/src/types/spec/reference.rs +++ b/nova_vm/src/types/spec/reference.rs @@ -25,7 +25,7 @@ impl Reference { /// 6.2.5.1 IsPropertyReference ( V ) /// https://tc39.es/ecma262/#sec-ispropertyreference pub fn is_property_reference(self) -> bool { - match (self.base) { + match self.base { // 1. if V.[[Base]] is unresolvable, return false. Base::Unresolvable => false, From a5bdae9b13a57465e500717a94fe4c317bb921a9 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Thu, 27 Jul 2023 15:02:20 -0500 Subject: [PATCH 13/21] some work Thanks to Phosra for figuring out I messed up descriptor logic. --- nova_vm/src/heap/array.rs | 24 +++++++++++++---- nova_vm/src/types/language/object.rs | 27 +++++++++---------- nova_vm/src/types/spec/property_descriptor.rs | 7 ++--- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/nova_vm/src/heap/array.rs b/nova_vm/src/heap/array.rs index 14a20a23..b129dc49 100644 --- a/nova_vm/src/heap/array.rs +++ b/nova_vm/src/heap/array.rs @@ -1,15 +1,15 @@ use super::{heap_trace::HeapTrace, Handle, ObjectHeapData}; use crate::{ - heap::{Heap, HeapBits}, + heap::{GetHeapData, Heap, HeapBits}, types::{Object, Value}, }; #[derive(Debug, Clone)] pub struct ArrayHeapData { - pub(super) bits: HeapBits, - pub(super) object: Option>, + pub(crate) bits: HeapBits, + pub(crate) object: Option, // TODO: Use SmallVec<[Value; 4]> - pub(super) elements: Vec>, + pub(crate) elements: Vec>, } impl ArrayHeapData { @@ -26,7 +26,21 @@ impl HeapTrace for Option { fn trace(&self, heap: &Heap) { assert!(self.is_some()); if let Some(object) = self.as_ref().unwrap().object { - heap.objects[object.id.get() as usize].trace(heap); + match object.into_value() { + Value::Object(object) => { + let object = heap.get(object); + + if let Some(value) = object.prototype.value {} + } + Value::ArrayObject(array) => { + let array = heap.get(array); + + if let Some(object) = array.object { + object.internal_methods(agent). + } + } + _ => unreachable!(), + } } } fn root(&self, _heap: &Heap) { diff --git a/nova_vm/src/types/language/object.rs b/nova_vm/src/types/language/object.rs index 25ff4fad..4a8322e3 100644 --- a/nova_vm/src/types/language/object.rs +++ b/nova_vm/src/types/language/object.rs @@ -41,17 +41,6 @@ impl Object { self.0 } - pub fn into_object_handle(self) -> Option> { - let object = self.into_value(); - - match object { - Value::Object(handle) => Some(handle), - Value::Function(_) => None, - Value::ArrayObject(_) => None, - _ => unreachable!(), - } - } - /// [[Extensible]] pub fn extensible(self, agent: &mut Agent) -> bool { let object = self.into_value(); @@ -85,15 +74,25 @@ impl Object { /// [[Prototype]] pub fn prototype(self, agent: &mut Agent) -> Option { let object = self.into_value(); + let realm = agent.current_realm(); + let realm = realm.borrow(); match object { Value::Object(object) => { - let realm = agent.current_realm(); - let realm = realm.borrow(); let object = realm.heap.get(object); object.prototype.value?.try_into().ok() } - Value::ArrayObject(_) => Some(Intrinsics::array_prototype()), + Value::ArrayObject(array) => { + let array = realm.heap.get(array); + + if let Some(object) = array.object { + if let Some(prototype) = object.prototype(agent) { + return Some(prototype); + } + } + + Some(Intrinsics::array_prototype()) + } Value::Function(_) => Some(Intrinsics::function_prototype()), _ => unreachable!(), } diff --git a/nova_vm/src/types/spec/property_descriptor.rs b/nova_vm/src/types/spec/property_descriptor.rs index 9bdbf4f1..902fa88c 100644 --- a/nova_vm/src/types/spec/property_descriptor.rs +++ b/nova_vm/src/types/spec/property_descriptor.rs @@ -33,9 +33,9 @@ impl PropertyDescriptor { // 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(_), Some(_)) => true, - + (_, Some(_)) => true, // 4. Return false. _ => false, } @@ -47,8 +47,9 @@ impl PropertyDescriptor { // 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(_), Some(_)) => true, + (_, Some(_)) => true, // 4. Return false. _ => false, } From ed2074a2c1f40ce10bbe9db1dc3fc5867970a944 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Sat, 29 Jul 2023 10:58:27 -0500 Subject: [PATCH 14/21] more work towards builtins --- nova_vm/src/builtins/builtin_function.rs | 4 +- nova_vm/src/builtins/ordinary.rs | 553 +++++++++++++++++- nova_vm/src/heap.rs | 4 +- nova_vm/src/heap/array.rs | 25 +- nova_vm/src/heap/function.rs | 11 +- nova_vm/src/heap/object.rs | 50 +- nova_vm/src/types/language/object.rs | 30 +- .../types/language/object/internal_methods.rs | 18 +- .../src/types/language/object/property_key.rs | 5 + .../types/language/object/property_storage.rs | 53 +- nova_vm/src/types/language/string.rs | 72 ++- nova_vm/src/types/spec/property_descriptor.rs | 16 + 12 files changed, 738 insertions(+), 103 deletions(-) diff --git a/nova_vm/src/builtins/builtin_function.rs b/nova_vm/src/builtins/builtin_function.rs index f9d6344f..0abe4468 100644 --- a/nova_vm/src/builtins/builtin_function.rs +++ b/nova_vm/src/builtins/builtin_function.rs @@ -83,13 +83,13 @@ pub fn create_builtin_function<'a, 'b: 'a>( // 8. Set func.[[Realm]] to realm. // NOTE: Heap data is implicitly attached to the Realm so I don't think // this matters. - entries: Vec::new(), + entries: Vec::new(), }); let initial_name = realm.heap.create(args.name).into_value(); let func = realm.heap.create(FunctionHeapData { bits: HeapBits::new(), - object: object.into_object_handle().unwrap(), + object: None, behaviour, // TODO: 9. Set func.[[InitialName]] to null. // NOTE: This is non-standard. diff --git a/nova_vm/src/builtins/ordinary.rs b/nova_vm/src/builtins/ordinary.rs index f3b248d0..92314626 100644 --- a/nova_vm/src/builtins/ordinary.rs +++ b/nova_vm/src/builtins/ordinary.rs @@ -6,19 +6,21 @@ use crate::{ /// 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: get_prototype_of, - set_prototype_of: set_prototype_of, - is_extensible: is_extensible, - prevent_extensions: prevent_extensions, - get_own_property: todo!(), - define_own_property: todo!(), - has_property: todo!(), - get: todo!(), - set: todo!(), - delete: todo!(), - own_property_keys: todo!(), - call: todo!(), - construct: todo!(), + 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]] ( ) @@ -124,7 +126,7 @@ fn is_extensible(agent: &mut Agent, object: Object) -> JsResult { /// https://tc39.es/ecma262/#sec-ordinaryisextensible pub fn ordinary_is_extensible(agent: &mut Agent, object: Object) -> bool { // 1. Return O.[[Extensible]]. - todo!() + object.extensible(agent) } /// 10.1.4 [[PreventExtensions]] ( ) @@ -198,3 +200,526 @@ pub fn ordinary_get_own_property( // 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. + if !receiver.is_object() {return Ok(false);} + let receiver = Object::new(receiver); + + // c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P). + let existing_descriptor = ( receiver.internal_methods(agent).get_own_property)( + agent, + receiver, + property_key, + )?; + + // d. If existingDescriptor is not undefined, then + if let Some(existing_descriptor) = existing_descriptor { + // i. If IsAccessorDescriptor(existingDescriptor) is true, return false. + if existing_descriptor.is_accessor_descriptor() {return Ok(false);} + + // ii. If existingDescriptor.[[Writable]] is false, return false. + if existing_descriptor.writable == Some(false) {return Ok(false);} + + // iii. Let valueDesc be the PropertyDescriptor { [[Value]]: V }. + let value_descriptor = PropertyDescriptor{ value: Some(value), ..Default::default() }; + + // iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc). + return (receiver.internal_methods(agent).define_own_property)( + agent, + receiver, + property_key, + value_descriptor, + ); + } + // e. Else, + else { + // i. Assert: Receiver does not currently have a property P. + debug_assert!(!receiver.property_storage().has(agent, property_key)); + + // ii. Return ? CreateDataProperty(Receiver, P, V). + return receiver.create_data_property(agent, property_key, value); + } + } + + // 3. Assert: IsAccessorDescriptor(ownDesc) is true. + debug_assert!(own_descriptor.is_accessor_descriptor()); + + // 4. Let setter be ownDesc.[[Set]]. + // 5. If setter is undefined, return false. + let Some(setter) = own_descriptor.set else { + return Ok(false); + }; + + // 6. Perform ? Call(setter, Receiver, « V »). + todo!(); + // 7. Return true. +} + +/// 10.1.10 [[Delete]] ( P ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-delete-p +fn delete(agent: &mut Agent, object: Object, property_key: PropertyKey) -> JsResult { + // 1. Return ? OrdinaryDelete(O, P). + ordinary_delete(agent, object, property_key) +} + +/// 10.1.10.1 OrdinaryDelete ( O, P ) +/// https://tc39.es/ecma262/#sec-ordinarydelete +pub fn ordinary_delete(agent: &mut Agent, object: Object, property_key: PropertyKey) -> JsResult { + // 1. Let desc be ? O.[[GetOwnProperty]](P). + let descriptor = (object.internal_methods(agent).get_own_property)(agent, object, property_key)?; + + // 2. If desc is undefined, return true. + let Some(descriptor) = descriptor else { + return Ok(true); + }; + + // 3. If desc.[[Configurable]] is true, then + if let Some(true) = descriptor.configurable { + // a. Remove the own property with name P from O. + object.property_storage().remove(agent, property_key); + + // b. Return true. + return Ok(true); + } + + // 4. Return false. + Ok(false) +} + +/// 10.1.11 [[OwnPropertyKeys]] ( ) +/// https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys +fn own_property_keys(agent: &mut Agent, object: Object) -> JsResult> { + // 1. Return OrdinaryOwnPropertyKeys(O). + ordinary_own_property_keys(agent, object) +} + +/// 10.1.11.1 OrdinaryOwnPropertyKeys ( O ) +/// https://tc39.es/ecma262/#sec-ordinaryownpropertykeys +pub fn ordinary_own_property_keys(agent: &mut Agent, object: Object) -> JsResult> { + // 1. Let keys be a new empty List. + let mut keys = Vec::new(); + + // 2. For each own property key P of O such that P is an array index, in ascending numeric + // index order, do + // for entry in object.property_storage().entries(agent) { + // if entry.key.is_array_index() { + // // a. Append P to keys. + // keys.push(entry.key); + // } + // } + + // for (object.property_storage().hash_map.keys()) |property_key| { + // if (property_key.is_array_index()) { + // // a. Append P to keys. + // keys.appendAssumeCapacity(property_key); + // } + // } + + // 3. For each own property key P of O such that P is a String and P is not an array index, in + // ascending chronological order of property creation, do + // for (object.propertyStorage().hash_map.keys()) |property_key| { + // if (property_key == .string or (property_key == .integer_index and !property_key.isArrayIndex())) { + // // a. Append P to keys. + // keys.appendAssumeCapacity(property_key); + // } + // } + + // 4. For each own property key P of O such that P is a Symbol, in ascending chronological + // order of property creation, do + // for (object.propertyStorage().hash_map.keys()) |property_key| { + // if (property_key == .symbol) { + // // a. Append P to keys. + // keys.appendAssumeCapacity(property_key); + // } + // } + + // 5. Return keys. + Ok(keys) +} diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index e48e8cca..95493016 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -13,7 +13,7 @@ pub use bigint::BigIntHeapData; pub use function::FunctionHeapData; pub use heap_constants::BuiltinObjectIndexes; pub use number::NumberHeapData; -pub use object::ObjectHeapData; +pub use object::{ObjectEntry, ObjectHeapData}; pub use string::StringHeapData; pub use symbol::SymbolHeapData; @@ -165,7 +165,7 @@ impl CreateHeapData<&str, String> for Heap { value } else { let id = self.alloc_string(data); - String::new(Value::String(Handle::new(id))) + String::from(Handle::new(id)) } } } diff --git a/nova_vm/src/heap/array.rs b/nova_vm/src/heap/array.rs index b129dc49..541bec3d 100644 --- a/nova_vm/src/heap/array.rs +++ b/nova_vm/src/heap/array.rs @@ -25,21 +25,16 @@ impl ArrayHeapData { impl HeapTrace for Option { fn trace(&self, heap: &Heap) { assert!(self.is_some()); - if let Some(object) = self.as_ref().unwrap().object { - match object.into_value() { - Value::Object(object) => { - let object = heap.get(object); - - if let Some(value) = object.prototype.value {} - } - Value::ArrayObject(array) => { - let array = heap.get(array); - - if let Some(object) = array.object { - object.internal_methods(agent). - } - } - _ => unreachable!(), + + let data = self.as_ref().unwrap(); + + if let Some(object) = data.object { + object.into_value().trace(heap); + } + + for value in data.elements.iter() { + if let Some(value) = value { + value.trace(heap); } } } diff --git a/nova_vm/src/heap/function.rs b/nova_vm/src/heap/function.rs index 48324a2e..0c2ef74d 100644 --- a/nova_vm/src/heap/function.rs +++ b/nova_vm/src/heap/function.rs @@ -1,14 +1,14 @@ use super::{heap_trace::HeapTrace, Handle, HeapBits, ObjectHeapData}; use crate::{ builtins::{todo_builtin, Behaviour}, - types::Value, + types::{Object, Value}, Heap, }; #[derive(Debug, Clone)] pub struct FunctionHeapData { pub(crate) bits: HeapBits, - pub(crate) object: Handle, + pub(crate) object: Option, pub(crate) initial_name: Value, pub(crate) length: i64, pub(crate) behaviour: Behaviour, @@ -23,7 +23,7 @@ impl FunctionHeapData { pub fn dummy() -> Self { Self { bits: HeapBits::new(), - object: Handle::new(0), + object: None, initial_name: Value::Null, length: 0, behaviour: Behaviour::Regular(todo_builtin), @@ -34,7 +34,10 @@ impl FunctionHeapData { impl HeapTrace for Option { fn trace(&self, heap: &Heap) { assert!(self.is_some()); - heap.objects[self.as_ref().unwrap().object.id.get() as usize].trace(heap); + + if let Some(object) = self.as_ref().unwrap().object { + object.into_value().trace(heap); + } } fn root(&self, _heap: &Heap) { assert!(self.is_some()); diff --git a/nova_vm/src/heap/object.rs b/nova_vm/src/heap/object.rs index fc69e12f..f9409ae7 100644 --- a/nova_vm/src/heap/object.rs +++ b/nova_vm/src/heap/object.rs @@ -16,7 +16,7 @@ pub struct ObjectHeapData { // with functions and possible other special object cases we want to track with partially // separate heap fields later down the line. pub(crate) prototype: PropertyDescriptor, - // TODO: Consider using detached vectors for keys/descriptors. + // TODO: Consider using detached vectors for keys/descriptors. pub(crate) entries: Vec, } @@ -41,47 +41,25 @@ impl HeapTrace for Option { fn trace(&self, heap: &Heap) { assert!(self.is_some()); let data = self.as_ref().unwrap(); + let dirty = data.bits.dirty.replace(false); let marked = data.bits.marked.replace(true); + if marked && !dirty { // Do not keep recursing into already-marked heap values. return; } - // match &data.prototype { - // PropertyDescriptor::Data { value, .. } => value.trace(heap), - // PropertyDescriptor::Blocked { .. } => {} - // PropertyDescriptor::ReadOnly { get, .. } => { - // heap.functions[*get as usize].trace(heap); - // } - // PropertyDescriptor::WriteOnly { set, .. } => { - // heap.functions[*set as usize].trace(heap); - // } - // PropertyDescriptor::ReadWrite { get, set, .. } => { - // heap.functions[*get as usize].trace(heap); - // heap.functions[*set as usize].trace(heap); - // } - // } - // for reference in data.entries.iter() { - // match reference.key { - // PropertyKey::SmallAsciiString(_) | PropertyKey::Smi(_) => {} - // PropertyKey::String(idx) => heap.strings[idx as usize].trace(heap), - // PropertyKey::Symbol(idx) => heap.symbols[idx as usize].trace(heap), - // } - // match &reference.value { - // PropertyDescriptor::Data { value, .. } => value.trace(heap), - // PropertyDescriptor::Blocked { .. } => {} - // PropertyDescriptor::ReadOnly { get, .. } => { - // heap.functions[*get as usize].trace(heap); - // } - // PropertyDescriptor::WriteOnly { set, .. } => { - // heap.functions[*set as usize].trace(heap); - // } - // PropertyDescriptor::ReadWrite { get, set, .. } => { - // heap.functions[*get as usize].trace(heap); - // heap.functions[*set as usize].trace(heap); - // } - // } - // } + + if let Some(value) = data.prototype.value { + value.trace(heap); + } + + for entry in data.entries.iter() { + entry.key.into_value().trace(heap); + if let Some(value) = entry.value.value { + value.trace(heap); + } + } } fn root(&self, _heap: &Heap) { diff --git a/nova_vm/src/types/language/object.rs b/nova_vm/src/types/language/object.rs index 4a8322e3..65ba8075 100644 --- a/nova_vm/src/types/language/object.rs +++ b/nova_vm/src/types/language/object.rs @@ -138,7 +138,7 @@ impl Object { self, property_key, property_descriptor, - ); + )?; // 2. If success is false, throw a TypeError exception. if !success { @@ -151,4 +151,32 @@ impl Object { // 3. Return unused. Ok(()) } + + /// 7.3.5 CreateDataProperty ( O, P, V ) + /// https://tc39.es/ecma262/#sec-createdataproperty + pub fn create_data_property( + self: Self, + agent: &mut Agent, + property_key: PropertyKey, + value: Value, + ) -> JsResult { + // 1. Let newDesc be the PropertyDescriptor { + // [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true + // }. + let new_descriptor = PropertyDescriptor { + value: Some(value), + writable: Some(true), + enumerable: Some(true), + configurable: Some(true), + ..Default::default() + }; + + // 2. Return ? O.[[DefineOwnProperty]](P, newDesc). + (self.internal_methods(agent).define_own_property)( + agent, + self, + property_key, + new_descriptor, + ) + } } diff --git a/nova_vm/src/types/language/object/internal_methods.rs b/nova_vm/src/types/language/object/internal_methods.rs index c06e89c1..aaa23321 100644 --- a/nova_vm/src/types/language/object/internal_methods.rs +++ b/nova_vm/src/types/language/object/internal_methods.rs @@ -10,14 +10,17 @@ 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 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, -) -> bool; +) -> JsResult; pub type HasProperty = fn(agent: &mut Agent, object: Object, property_key: PropertyKey) -> JsResult; pub type Get = fn( @@ -26,8 +29,13 @@ pub type Get = fn( property_key: PropertyKey, receiver: Value, ) -> JsResult; -pub type Set = - fn(object: Object, property_key: PropertyKey, value: Value, 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>; diff --git a/nova_vm/src/types/language/object/property_key.rs b/nova_vm/src/types/language/object/property_key.rs index 255172c2..e247cc77 100644 --- a/nova_vm/src/types/language/object/property_key.rs +++ b/nova_vm/src/types/language/object/property_key.rs @@ -29,6 +29,11 @@ impl PropertyKey { self.0 } + pub fn is_array_index(self) -> bool { + // TODO: string check + matches!(self.into_value(), Value::IntegerNumber(_)) + } + pub(self) fn is_str_eq_num(s: &str, n: i64) -> bool { let (s, mut n) = if s.starts_with("-") { if n > 0 { diff --git a/nova_vm/src/types/language/object/property_storage.rs b/nova_vm/src/types/language/object/property_storage.rs index fe58f431..41d17686 100644 --- a/nova_vm/src/types/language/object/property_storage.rs +++ b/nova_vm/src/types/language/object/property_storage.rs @@ -1,5 +1,10 @@ +use std::{ + cell::{Ref, RefCell}, + rc::Rc, +}; + use crate::{ - execution::Agent, + execution::{Agent, Realm}, heap::GetHeapData, types::{PropertyDescriptor, Value}, }; @@ -36,10 +41,28 @@ impl PropertyStorage { .any(|entry| entry.key.equals(agent, key)) } Value::ArrayObject(array) => { + if key.equals(agent, PropertyKey::new(Value::try_from("length").unwrap())) { + return true; + } + let realm = agent.current_realm(); let realm = realm.borrow(); - let array = agent.current_realm().borrow().heap.get(array); - true + let array = realm.heap.get(array); + + if let Value::IntegerNumber(number) = key.into_value() { + if let Some(_) = TryInto::::try_into(number.into_i64()) + .map(|idx| array.elements.get(idx)) + .ok() + { + return true; + } + } + + if let Some(object) = array.object { + return object.property_storage().has(agent, key); + } + + false } Value::Function(_) => todo!(), _ => unreachable!(), @@ -49,4 +72,28 @@ impl PropertyStorage { 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 index 42138c7b..06050fa6 100644 --- a/nova_vm/src/types/language/string.rs +++ b/nova_vm/src/types/language/string.rs @@ -1,48 +1,78 @@ use super::Value; -use crate::{execution::Agent, heap::GetHeapData, SmallString}; +use crate::{ + execution::Agent, + heap::{GetHeapData, Handle, StringHeapData}, + SmallString, +}; /// 6.1.4 The String Type /// https://tc39.es/ecma262/#sec-ecmascript-language-types-string-type -#[derive(Debug)] -pub struct String(Value); +#[derive(Debug, Clone, Copy)] +pub enum String { + String(Handle), + SmallString(SmallString), +} + +impl From> for String { + fn from(value: Handle) -> Self { + String::String(value) + } +} + +impl From for String { + fn from(value: SmallString) -> Self { + String::SmallString(value) + } +} impl TryFrom<&str> for String { type Error = (); fn try_from(value: &str) -> Result { - SmallString::try_from(value).map(|s| String::new(Value::SmallString(s))) + SmallString::try_from(value).map(|s| String::SmallString(s)) } } -impl String { - pub(crate) fn new(value: Value) -> Self { - matches!(value, Value::String(_) | Value::SmallString(_)); - Self(value) +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.0 + self.into() } /// Byte length of the string. pub fn len(self, agent: &Agent) -> usize { - let s = self.into_value(); - - match s { - Value::String(s) => agent.current_realm().borrow().heap.get(s).len(), - Value::SmallString(s) => s.len(), - _ => unreachable!(), + match self { + String::String(s) => agent.current_realm().borrow().heap.get(s).len(), + String::SmallString(s) => s.len(), } } - pub fn as_str<'a>(&'a self, agent: &'a Agent) -> Option<&'a str> { - match &self.0 { - // SAFETY: The immutable reference to the Agent ensures no mutable + 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. - Value::String(s) => unsafe { + String::String(s) => unsafe { std::mem::transmute(agent.current_realm().borrow().heap.get(*s).as_str()) }, - Value::SmallString(s) => Some(s.as_str()), - _ => unreachable!(), + String::SmallString(s) => Some(s.as_str()), } } diff --git a/nova_vm/src/types/spec/property_descriptor.rs b/nova_vm/src/types/spec/property_descriptor.rs index 902fa88c..e20f16f8 100644 --- a/nova_vm/src/types/spec/property_descriptor.rs +++ b/nova_vm/src/types/spec/property_descriptor.rs @@ -93,4 +93,20 @@ impl PropertyDescriptor { // 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() + } } From 8af17023247df70307a21a87b96a143f2be788c7 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Sat, 29 Jul 2023 11:14:32 -0500 Subject: [PATCH 15/21] make intrinsics const functions --- nova_vm/src/builtins/ordinary.rs | 5 +- nova_vm/src/execution/realm/intrinsics.rs | 156 +++++++++++----------- nova_vm/src/heap.rs | 16 +-- nova_vm/src/types.rs | 6 - nova_vm/src/types/language/function.rs | 50 ++++++- nova_vm/src/types/language/object.rs | 93 ++++++++----- 6 files changed, 188 insertions(+), 138 deletions(-) diff --git a/nova_vm/src/builtins/ordinary.rs b/nova_vm/src/builtins/ordinary.rs index 92314626..9e7fe14a 100644 --- a/nova_vm/src/builtins/ordinary.rs +++ b/nova_vm/src/builtins/ordinary.rs @@ -589,8 +589,9 @@ else { if own_descriptor.writable == Some(false) {return Ok(false);} // b. If Receiver is not an Object, return false. - if !receiver.is_object() {return Ok(false);} - let receiver = Object::new(receiver); + 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)( diff --git a/nova_vm/src/execution/realm/intrinsics.rs b/nova_vm/src/execution/realm/intrinsics.rs index cec6e78f..d97ace85 100644 --- a/nova_vm/src/execution/realm/intrinsics.rs +++ b/nova_vm/src/execution/realm/intrinsics.rs @@ -1,6 +1,6 @@ use crate::{ heap::{BuiltinObjectIndexes, Handle}, - types::{Object, Value}, + types::Object, }; // TODO: We should probably consider lazily loading intrinsics. This would @@ -11,227 +11,225 @@ pub struct Intrinsics; impl Intrinsics { /// %Array% - pub fn array() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn array() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::ArrayConstructorIndex as u32, - ))) + )) } /// %Array.prototype% - pub fn array_prototype() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn array_prototype() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::ArrayPrototypeIndex as u32, - ))) + )) } /// %BigInt% - pub fn big_int() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn big_int() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::BigintConstructorIndex as u32, - ))) + )) } /// %BigInt.prototype% - pub fn big_int_prototype() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn big_int_prototype() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::BigintPrototypeIndex as u32, - ))) + )) } /// %Boolean% - pub fn boolean() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn boolean() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::BooleanConstructorIndex as u32, - ))) + )) } /// %Boolean.prototype% - pub fn boolean_prototype() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn boolean_prototype() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::BooleanPrototypeIndex as u32, - ))) + )) } /// %Error% - pub fn error() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn error() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::ErrorConstructorIndex as u32, - ))) + )) } /// %Error.prototype% - pub fn error_prototype() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn error_prototype() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::ErrorPrototypeIndex as u32, - ))) + )) } /// %eval% - pub fn eval() -> Object { + pub const fn eval() -> Object { todo!() } /// %EvalError% - pub fn eval_error() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn eval_error() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::ArrayConstructorIndex as u32, - ))) + )) } /// %EvalError.prototype% - pub fn eval_error_prototype() -> Object { + pub const fn eval_error_prototype() -> Object { todo!() } /// %Function% - pub fn function() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn function() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::FunctionConstructorIndex as u32, - ))) + )) } /// %Function.prototype% - pub fn function_prototype() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn function_prototype() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::FunctionPrototypeIndex as u32, - ))) + )) } /// %isFinite% - pub fn is_finite() -> Object { + pub const fn is_finite() -> Object { todo!() } /// %isNaN% - pub fn is_nan() -> Object { + pub const fn is_nan() -> Object { todo!() } /// %Math% - pub fn math() -> Object { - Object::new(Value::Object(Handle::new( - BuiltinObjectIndexes::MathObjectIndex as u32, - ))) + pub const fn math() -> Object { + Object::Object(Handle::new(BuiltinObjectIndexes::MathObjectIndex as u32)) } /// %Number% - pub fn number() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn number() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::NumberConstructorIndex as u32, - ))) + )) } /// %Number.prototype% - pub fn number_prototype() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn number_prototype() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::NumberPrototypeIndex as u32, - ))) + )) } /// %Object% - pub fn object() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn object() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::ObjectConstructorIndex as u32, - ))) + )) } /// %Object.prototype% - pub fn object_prototype() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn object_prototype() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::ObjectPrototypeIndex as u32, - ))) + )) } /// %Object.prototype.toString% - pub fn object_prototype_to_string() -> Object { + pub const fn object_prototype_to_string() -> Object { todo!() } /// %RangeError% - pub fn range_error() -> Object { + pub const fn range_error() -> Object { todo!() } /// %RangeError.prototype% - pub fn range_error_prototype() -> Object { + pub const fn range_error_prototype() -> Object { todo!() } /// %ReferenceError% - pub fn reference_error() -> Object { + pub const fn reference_error() -> Object { todo!() } /// %ReferenceError.prototype% - pub fn reference_error_prototype() -> Object { + pub const fn reference_error_prototype() -> Object { todo!() } /// %Reflect% - pub fn reflect() -> Object { + pub const fn reflect() -> Object { todo!() } /// %String% - pub fn string() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn string() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::StringConstructorIndex as u32, - ))) + )) } /// %String.prototype% - pub fn string_prototype() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn string_prototype() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::StringPrototypeIndex as u32, - ))) + )) } /// %Symbol% - pub fn symbol() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn symbol() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::SymbolConstructorIndex as u32, - ))) + )) } /// %Symbol.prototype% - pub fn symbol_prototype() -> Object { - Object::new(Value::Object(Handle::new( + pub const fn symbol_prototype() -> Object { + Object::Object(Handle::new( BuiltinObjectIndexes::SymbolPrototypeIndex as u32, - ))) + )) } /// %SyntaxError% - pub fn syntax_error() -> Object { + pub const fn syntax_error() -> Object { todo!() } /// %SyntaxError.prototype% - pub fn syntax_error_prototype() -> Object { + pub const fn syntax_error_prototype() -> Object { todo!() } /// %ThrowTypeError% - pub fn throw_type_error() -> Object { + pub const fn throw_type_error() -> Object { todo!() } /// %TypeError% - pub fn type_error() -> Object { + pub const fn type_error() -> Object { todo!() } /// %TypeError.prototype% - pub fn type_error_prototype() -> Object { + pub const fn type_error_prototype() -> Object { todo!() } /// %URIError% - pub fn uri_error() -> Object { + pub const fn uri_error() -> Object { todo!() } /// %URIError.prototype% - pub fn uri_error_prototype() -> Object { + pub const fn uri_error_prototype() -> Object { todo!() } } diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 95493016..7579bcf4 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -38,14 +38,14 @@ impl PartialEq for Handle { } impl Handle { - pub fn new(id: u32) -> Self { + /// Id must not be 0. + pub const fn new(id: u32) -> Self { + debug_assert!(id != 0); Self { - id: NonZeroU32::try_from(id).unwrap(), - // SAFETY: We hopefully will make sure handles ar esafe. + id: unsafe { NonZeroU32::new_unchecked(id) }, + // SAFETY: We hopefully will make sure handles are safe. _marker: unsafe { - std::mem::transmute::<&PhantomData, &'static PhantomData>( - &PhantomData::default(), - ) + std::mem::transmute::<&PhantomData, &'static PhantomData>(&PhantomData) }, } } @@ -174,7 +174,7 @@ impl CreateHeapData for Heap { fn create(&mut self, data: FunctionHeapData) -> Function { let id = self.functions.len(); self.functions.push(Some(data)); - Function::new(Value::Function(Handle::new(id as u32))) + Function(Handle::new(id as u32)) } } @@ -182,7 +182,7 @@ impl CreateHeapData for Heap { fn create(&mut self, data: ObjectHeapData) -> Object { let id: usize = self.functions.len(); self.objects.push(Some(data)); - Object::new(Value::Object(Handle::new(id as u32))) + Object::Object(Handle::new(id as u32)) } } diff --git a/nova_vm/src/types.rs b/nova_vm/src/types.rs index 2d392f9e..19b28f2b 100644 --- a/nova_vm/src/types.rs +++ b/nova_vm/src/types.rs @@ -4,11 +4,5 @@ mod spec; pub use language::{Function, InternalMethods, Number, Object, PropertyKey, String, Value}; pub use spec::{Base, PropertyDescriptor, Reference, ReferencedName}; -impl From for Value { - fn from(value: Object) -> Self { - todo!() - } -} - #[derive(Debug)] pub struct Symbol; diff --git a/nova_vm/src/types/language/function.rs b/nova_vm/src/types/language/function.rs index edaa8502..efc2dad0 100644 --- a/nova_vm/src/types/language/function.rs +++ b/nova_vm/src/types/language/function.rs @@ -1,8 +1,10 @@ +use crate::heap::{FunctionHeapData, Handle}; + use super::{Object, Value}; /// https://tc39.es/ecma262/#function-object #[derive(Clone, Copy)] -pub struct Function(Value); +pub struct Function(pub Handle); impl std::fmt::Debug for Function { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -10,16 +12,52 @@ impl std::fmt::Debug for Function { } } -impl Function { - pub(crate) fn new(value: Value) -> Self { - Self(value) +impl From> for Function { + fn from(value: Handle) -> Self { + Function(value) + } +} + +impl TryFrom for Function { + type Error = (); + fn try_from(value: Object) -> Result { + if let Object::Function(value) = value { + Ok(Function(value)) + } else { + Err(()) + } + } +} + +impl TryFrom for Function { + type Error = (); + fn try_from(value: Value) -> Result { + if let Value::Function(value) = value { + Ok(Function(value)) + } else { + Err(()) + } } +} + +impl From for Object { + fn from(value: Function) -> Self { + Object::Function(value.0) + } +} +impl From for Value { + fn from(value: Function) -> Self { + Value::Function(value.0) + } +} + +impl Function { pub fn into_value(self) -> Value { - self.0 + self.into() } pub fn into_object(self) -> Object { - Object::new(self.into_value()) + Object::Function(self.0) } } diff --git a/nova_vm/src/types/language/object.rs b/nova_vm/src/types/language/object.rs index 65ba8075..b8dfb16e 100644 --- a/nova_vm/src/types/language/object.rs +++ b/nova_vm/src/types/language/object.rs @@ -6,7 +6,7 @@ mod property_storage; use crate::{ builtins::ordinary, execution::{agent::ExceptionType, Agent, Intrinsics, JsResult}, - heap::{GetHeapData, Handle, ObjectHeapData}, + heap::{ArrayHeapData, FunctionHeapData, GetHeapData, Handle, ObjectHeapData}, types::PropertyDescriptor, }; @@ -19,70 +19,93 @@ pub use property_storage::PropertyStorage; /// 6.1.7 The Object Type /// https://tc39.es/ecma262/#sec-object-type #[derive(Debug, Clone, Copy)] -pub struct Object(Value); +pub enum Object { + Object(Handle), + Array(Handle), + Function(Handle), +} + +impl From> for Object { + fn from(value: Handle) -> Self { + Object::Object(value) + } +} + +impl From> for Object { + fn from(value: Handle) -> Self { + Object::Array(value) + } +} + +impl From> for Object { + fn from(value: Handle) -> Self { + Object::Function(value) + } +} + +impl From for Value { + fn from(value: Object) -> Self { + match value { + Object::Object(x) => Value::Object(x), + Object::Array(x) => Value::ArrayObject(x), + Object::Function(x) => Value::Function(x), + } + } +} impl TryFrom for Object { type Error = (); fn try_from(value: Value) -> Result { - if let Value::Object(_) | Value::ArrayObject(_) | Value::Function(_) = value { - Ok(Self(value)) - } else { - Err(()) + match value { + Value::Object(x) => Ok(Object::Object(x)), + Value::ArrayObject(x) => Ok(Object::Array(x)), + Value::Function(x) => Ok(Object::Function(x)), + _ => Err(()), } } } impl Object { - pub(crate) fn new(value: Value) -> Self { - Self(value) - } - pub fn into_value(self) -> Value { - self.0 + self.into() } /// [[Extensible]] pub fn extensible(self, agent: &mut Agent) -> bool { - let object = self.into_value(); - - match object { - Value::Object(object) => agent.current_realm().borrow().heap.get(object).extensible, - Value::ArrayObject(_) => true, - Value::Function(_) => true, - _ => unreachable!(), + match self { + Object::Object(object) => agent.current_realm().borrow().heap.get(object).extensible, + Object::Array(_) => true, + Object::Function(_) => true, } } /// [[Extensible]] pub fn set_extensible(self, agent: &mut Agent, value: bool) { - let object = self.into_value(); - - match object { - Value::Object(object) => { + match self { + Object::Object(object) => { let realm = agent.current_realm(); let mut realm = realm.borrow_mut(); let object = realm.heap.get_mut(object); object.extensible = true; } // TODO: Correct object/function impl - Value::ArrayObject(_) => {} - Value::Function(_) => {} + Object::Array(_) => {} + Object::Function(_) => {} _ => unreachable!(), } } /// [[Prototype]] pub fn prototype(self, agent: &mut Agent) -> Option { - let object = self.into_value(); let realm = agent.current_realm(); let realm = realm.borrow(); - match object { - Value::Object(object) => { + match self { + Object::Object(object) => { let object = realm.heap.get(object); object.prototype.value?.try_into().ok() } - Value::ArrayObject(array) => { + Object::Array(array) => { let array = realm.heap.get(array); if let Some(object) = array.object { @@ -93,25 +116,21 @@ impl Object { Some(Intrinsics::array_prototype()) } - Value::Function(_) => Some(Intrinsics::function_prototype()), - _ => unreachable!(), + Object::Function(_) => Some(Intrinsics::function_prototype()), } } /// [[Prototype]] pub fn set_prototype(self, agent: &mut Agent, prototype: Option) { - let object = self.into_value(); - - match object { - Value::Object(object) => { + match self { + Object::Object(object) => { let realm = agent.current_realm(); let mut realm = realm.borrow_mut(); let object = realm.heap.get_mut(object); object.prototype.value = prototype.map(|object| object.into_value()); } - Value::ArrayObject(_) => todo!(), - Value::Function(_) => todo!(), - _ => unreachable!(), + Object::Array(_) => todo!(), + Object::Function(_) => todo!(), } } From a50451b5e302d2062f2f88a1e5ce454612cef33f Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Sat, 29 Jul 2023 11:15:31 -0500 Subject: [PATCH 16/21] remove double Option --- nova_vm/src/language/script.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova_vm/src/language/script.rs b/nova_vm/src/language/script.rs index 65713309..7cd334cf 100644 --- a/nova_vm/src/language/script.rs +++ b/nova_vm/src/language/script.rs @@ -8,7 +8,7 @@ use oxc_parser::Parser; use oxc_span::SourceType; use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc}; -pub type HostDefined<'ctx> = Option<&'ctx mut dyn Any>; +pub type HostDefined<'ctx> = &'ctx mut dyn Any; /// 16.1.4 Script Records /// https://tc39.es/ecma262/#sec-script-records From 61779ab1637d2d94a7077567057a52ba3fba38e3 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Sat, 29 Jul 2023 11:31:46 -0500 Subject: [PATCH 17/21] adjust for larger small bigint range --- nova_vm/src/builtins/number.rs | 4 +- nova_vm/src/small_integer.rs | 33 +++-- nova_vm/src/types/language/number.rs | 4 +- .../src/types/language/object/property_key.rs | 134 +++++++++--------- .../types/language/object/property_storage.rs | 7 +- 5 files changed, 102 insertions(+), 80 deletions(-) diff --git a/nova_vm/src/builtins/number.rs b/nova_vm/src/builtins/number.rs index f937f8de..ea40666e 100644 --- a/nova_vm/src/builtins/number.rs +++ b/nova_vm/src/builtins/number.rs @@ -46,7 +46,7 @@ impl Builtin for NumberConstructor { object, "MAX_SAFE_INTEGER", PropertyDescriptor { - value: Some(Number::from(SmallInteger::MAX).into()), + value: Some(Number::from(SmallInteger::MAX_NUMBER).into()), writable: Some(false), enumerable: Some(false), configurable: Some(false), @@ -74,7 +74,7 @@ impl Builtin for NumberConstructor { object, "MIN_SAFE_INTEGER", PropertyDescriptor { - value: Some(Number::from(SmallInteger::MIN).into()), + value: Some(Number::from(SmallInteger::MIN_NUMBER).into()), writable: Some(false), enumerable: Some(false), configurable: Some(false), diff --git a/nova_vm/src/small_integer.rs b/nova_vm/src/small_integer.rs index c37a726a..cd2f9063 100644 --- a/nova_vm/src/small_integer.rs +++ b/nova_vm/src/small_integer.rs @@ -11,8 +11,11 @@ impl std::fmt::Debug for SmallInteger { } impl SmallInteger { - pub const MIN: i64 = -(2 as i64).pow(53) / 2 + 1; - pub const MAX: i64 = (2 as i64).pow(53) / 2 - 1; + 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 { @@ -20,7 +23,7 @@ impl SmallInteger { } pub(crate) fn from_i64_unchecked(value: i64) -> SmallInteger { - debug_assert!(value >= Self::MIN && value <= Self::MAX); + debug_assert!(value >= Self::MIN_NUMBER && value <= Self::MAX_NUMBER); let bytes = i64::to_ne_bytes(value); let data = if cfg!(target_endian = "little") { @@ -40,7 +43,7 @@ impl SmallInteger { impl TryFrom for SmallInteger { type Error = (); fn try_from(value: i64) -> Result { - if value >= Self::MIN && value <= Self::MAX { + if value >= Self::MIN_NUMBER && value <= Self::MAX_NUMBER { Ok(Self::from_i64_unchecked(value)) } else { Err(()) @@ -74,22 +77,32 @@ fn valid_small_integers() { assert_eq!(5i64, SmallInteger::try_from(5).unwrap().into()); assert_eq!(23i64, SmallInteger::try_from(23).unwrap().into()); assert_eq!( - SmallInteger::MAX, - SmallInteger::try_from(SmallInteger::MAX).unwrap().into() + 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, - SmallInteger::try_from(SmallInteger::MIN).unwrap().into() + SmallInteger::MIN_NUMBER, + SmallInteger::try_from(SmallInteger::MIN_NUMBER) + .unwrap() + .into() ); } #[test] fn invalid_small_integers() { - assert_eq!(SmallInteger::try_from(SmallInteger::MAX + 1), Err(())); + 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 - 1), 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/types/language/number.rs b/nova_vm/src/types/language/number.rs index 1d903763..bc496a0e 100644 --- a/nova_vm/src/types/language/number.rs +++ b/nova_vm/src/types/language/number.rs @@ -32,7 +32,9 @@ impl From for Number { impl From for Number { fn from(value: i64) -> Self { - let n = value.min(SmallInteger::MAX).max(SmallInteger::MIN); + let n = value + .min(SmallInteger::MAX_NUMBER) + .max(SmallInteger::MIN_NUMBER); Number(Value::IntegerNumber(SmallInteger::from_i64_unchecked(n))) } } diff --git a/nova_vm/src/types/language/object/property_key.rs b/nova_vm/src/types/language/object/property_key.rs index e247cc77..8ae87d83 100644 --- a/nova_vm/src/types/language/object/property_key.rs +++ b/nova_vm/src/types/language/object/property_key.rs @@ -1,77 +1,91 @@ use crate::{ execution::Agent, - heap::GetHeapData, + heap::{GetHeapData, Handle, StringHeapData}, types::{String, Value}, - SmallString, + SmallInteger, SmallString, }; #[derive(Debug, Clone, Copy)] -pub struct PropertyKey(Value); +pub enum PropertyKey { + String(Handle), + SmallString(SmallString), + SmallInteger(SmallInteger), +} -impl Default for PropertyKey { - fn default() -> Self { - Self(Value::SmallString(SmallString::from_str_unchecked( - "unknown", - ))) +impl From> for PropertyKey { + fn from(value: Handle) -> Self { + PropertyKey::String(value) } } -impl PropertyKey { - pub(crate) fn new(value: Value) -> Self { - debug_assert!(matches!( - value, - Value::IntegerNumber(_) | Value::String(_) | Value::SmallString(_) - )); - Self(value) +impl From for PropertyKey { + fn from(value: SmallString) -> Self { + PropertyKey::SmallString(value) } +} - pub fn into_value(self) -> Value { - self.0 +impl From for PropertyKey { + fn from(value: SmallInteger) -> Self { + PropertyKey::SmallInteger(value) } +} - pub fn is_array_index(self) -> bool { - // TODO: string check - matches!(self.into_value(), Value::IntegerNumber(_)) +impl From for PropertyKey { + fn from(value: String) -> Self { + match value { + String::String(x) => PropertyKey::String(x), + String::SmallString(x) => PropertyKey::SmallString(x), + } } +} - pub(self) fn is_str_eq_num(s: &str, n: i64) -> bool { - let (s, mut n) = if s.starts_with("-") { - if n > 0 { - return false; - } - (&s[1..], -n as usize) - } else { - if n < 0 { - return false; - } - (s, n as usize) - }; - - if Some(s.len()) != n.checked_ilog10().map(|n| n as usize) { - return false; +impl TryFrom for PropertyKey { + type Error = (); + fn try_from(value: Value) -> Result { + match value { + Value::String(x) => Ok(PropertyKey::String(x)), + Value::SmallString(x) => Ok(PropertyKey::SmallString(x)), + Value::IntegerNumber(x) => Ok(PropertyKey::SmallInteger(x)), + _ => Err(()), } + } +} - for c in s.as_bytes().iter().rev() { - let code = (n % 10) as u8 + '0' as u8; +impl From for Value { + fn from(value: PropertyKey) -> Self { + match value { + PropertyKey::String(x) => Value::String(x), + PropertyKey::SmallString(x) => Value::SmallString(x), + PropertyKey::SmallInteger(x) => Value::IntegerNumber(x), + } + } +} - if *c != code { - return false; - } +impl PropertyKey { + pub fn into_value(self) -> Value { + self.into() + } - n /= 10; - } + pub fn is_array_index(self) -> bool { + // TODO: string check + matches!(self.into_value(), Value::IntegerNumber(_)) + } - true + 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.into_value(); - let y = y.into_value(); + let x = self; match (x, y) { // Assumes the interner is working correctly. - (Value::String(s1), Value::String(s2)) => s1 == s2, - (Value::String(s), Value::IntegerNumber(n)) => { + (PropertyKey::String(s1), PropertyKey::String(s2)) => s1 == s2, + (PropertyKey::SmallString(s1), PropertyKey::SmallString(s2)) => { + s1.as_str() == s2.as_str() + } + (PropertyKey::String(s), PropertyKey::SmallInteger(n)) => { let realm = agent.current_realm(); let realm = realm.borrow(); let s = realm.heap.get(s); @@ -82,24 +96,14 @@ impl PropertyKey { Self::is_str_eq_num(s, n.into_i64()) } - _ => unreachable!(), - } - } -} - -impl From for PropertyKey { - fn from(value: String) -> Self { - Self(value.into_value()) - } -} - -impl TryFrom for PropertyKey { - type Error = (); - fn try_from(value: Value) -> Result { - if value.is_string() || value.is_symbol() || value.is_number() { - Ok(Self(value)) - } else { - Err(()) + (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, } } } diff --git a/nova_vm/src/types/language/object/property_storage.rs b/nova_vm/src/types/language/object/property_storage.rs index 41d17686..5074e954 100644 --- a/nova_vm/src/types/language/object/property_storage.rs +++ b/nova_vm/src/types/language/object/property_storage.rs @@ -6,7 +6,7 @@ use std::{ use crate::{ execution::{Agent, Realm}, heap::GetHeapData, - types::{PropertyDescriptor, Value}, + types::{PropertyDescriptor, String, Value}, }; use super::{Object, PropertyKey}; @@ -41,7 +41,10 @@ impl PropertyStorage { .any(|entry| entry.key.equals(agent, key)) } Value::ArrayObject(array) => { - if key.equals(agent, PropertyKey::new(Value::try_from("length").unwrap())) { + if key.equals( + agent, + PropertyKey::from(String::try_from("length").unwrap()), + ) { return true; } From c03675a07e3ffbe6b1db0002c435aa3b0e21ce44 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Sat, 29 Jul 2023 11:39:17 -0500 Subject: [PATCH 18/21] update oxc deps --- nova_vm/Cargo.toml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nova_vm/Cargo.toml b/nova_vm/Cargo.toml index ee98d66c..e66e2a7a 100644 --- a/nova_vm/Cargo.toml +++ b/nova_vm/Cargo.toml @@ -8,10 +8,10 @@ edition = "2021" [dependencies] gc = { version = "0.4", features = ["derive"] } wtf8 = "0.1" -oxc_parser = "0.0.7" -oxc_span = "0.0.7" -oxc_ast = "0.0.7" -oxc_allocator = "0.0.7" -oxc_diagnostics = "0.0.7" +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" From 3ff1a7359937bcd1cc25db9470f9f2fbfa5bba8c Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Sat, 29 Jul 2023 11:44:37 -0500 Subject: [PATCH 19/21] update to stable and fix cli --- nova_cli/Cargo.toml | 5 +++-- nova_cli/src/main.rs | 21 ++++++++++----------- nova_vm/src/small_string.rs | 2 +- rust-toolchain.toml | 2 -- 4 files changed, 14 insertions(+), 16 deletions(-) delete mode 100644 rust-toolchain.toml 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/src/small_string.rs b/nova_vm/src/small_string.rs index 458c88ac..d82e5991 100644 --- a/nova_vm/src/small_string.rs +++ b/nova_vm/src/small_string.rs @@ -17,7 +17,7 @@ impl SmallString { .iter() .rev() .position(|&x| x != 0) - .unwrap_or(7) + .map_or(0, |i| 7 - i) } #[inline] 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" From a9b63f321f604a82f47d347668ac7d0ff12da35f Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Sat, 29 Jul 2023 11:46:21 -0500 Subject: [PATCH 20/21] fmt --- nova_vm/src/builtins/ordinary.rs | 241 ++++++++++++++++++++----------- 1 file changed, 155 insertions(+), 86 deletions(-) diff --git a/nova_vm/src/builtins/ordinary.rs b/nova_vm/src/builtins/ordinary.rs index 9e7fe14a..9b107656 100644 --- a/nova_vm/src/builtins/ordinary.rs +++ b/nova_vm/src/builtins/ordinary.rs @@ -16,7 +16,7 @@ pub static METHODS: InternalMethods = InternalMethods { get, set, delete, - own_property_keys, + own_property_keys, // call: todo!(), // construct: todo!(), call: None, @@ -227,7 +227,14 @@ pub fn ordinary_define_own_property( 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) + validate_and_apply_property_descriptor( + agent, + Some(object), + property_key, + extensible, + descriptor, + current, + ) } /// 10.1.6.3 ValidateAndApplyPropertyDescriptor ( O, P, extensible, Desc, current ) @@ -292,7 +299,6 @@ fn validate_and_apply_property_descriptor( return Ok(true); }; - // 3. Assert: current is a fully populated Property Descriptor. debug_assert!(current.is_fully_populated()); @@ -314,11 +320,13 @@ fn validate_and_apply_property_descriptor( 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() { + if !descriptor.is_generic_descriptor() + && descriptor.is_accessor_descriptor() != current.is_accessor_descriptor() + { return Ok(false); } @@ -328,8 +336,11 @@ fn validate_and_apply_property_descriptor( // 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); + if !desc_get + .into_value() + .same_value(agent, cur_get.into_value()) + { + return Ok(false); } } else { return Ok(false); @@ -340,7 +351,10 @@ fn validate_and_apply_property_descriptor( // 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()) { + if !desc_set + .into_value() + .same_value(agent, cur_set.into_value()) + { return Ok(false); } } else { @@ -375,34 +389,46 @@ fn validate_and_apply_property_descriptor( 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()); + 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()); + 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() - }); + 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()); + 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()); + 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 @@ -415,27 +441,35 @@ fn validate_and_apply_property_descriptor( // .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() - }); + 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() - }); + 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() + }, + ); } } @@ -445,14 +479,22 @@ fn validate_and_apply_property_descriptor( /// 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 { +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 { +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)?; @@ -476,14 +518,24 @@ pub fn ordinary_has_property(agent: &mut Agent, object: Object, property_key: Pr /// 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 { +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 { +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 @@ -516,10 +568,15 @@ pub fn ordinary_get(agent: &mut Agent, object: Object, property_key: PropertyKey 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 { +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) } @@ -534,7 +591,8 @@ pub fn ordinary_set( receiver: Value, ) -> JsResult { // 1. Let ownDesc be ? O.[[GetOwnProperty]](P). - let own_descriptor = (object.internal_methods(agent).get_own_property)(agent, object, property_key)?; + 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) @@ -549,44 +607,46 @@ pub fn ordinary_set_with_own_descriptor( value: Value, receiver: Value, own_descriptor: Option, -) ->JsResult { +) -> 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); + // 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() - } -} + // 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);} + 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 { @@ -594,22 +654,26 @@ else { }; // c. Let existingDescriptor be ? Receiver.[[GetOwnProperty]](P). - let existing_descriptor = ( receiver.internal_methods(agent).get_own_property)( - agent, - receiver, - property_key, - )?; + 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);} + 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);} + 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() }; + let value_descriptor = PropertyDescriptor { + value: Some(value), + ..Default::default() + }; // iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc). return (receiver.internal_methods(agent).define_own_property)( @@ -652,9 +716,14 @@ fn delete(agent: &mut Agent, object: Object, property_key: PropertyKey) -> JsRes /// 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 { +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)?; + 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 { @@ -689,12 +758,12 @@ pub fn ordinary_own_property_keys(agent: &mut Agent, object: Object) -> JsResult // 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 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()) { From 7a32683ed20cd197b7c0d8f9bb2409a89950e1f9 Mon Sep 17 00:00:00 2001 From: Carter Snook Date: Sat, 29 Jul 2023 11:48:00 -0500 Subject: [PATCH 21/21] i guess cargo fmt doesnt work --- nova_vm/src/builtins/ordinary.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova_vm/src/builtins/ordinary.rs b/nova_vm/src/builtins/ordinary.rs index 9b107656..38bf1b9a 100644 --- a/nova_vm/src/builtins/ordinary.rs +++ b/nova_vm/src/builtins/ordinary.rs @@ -260,7 +260,7 @@ fn validate_and_apply_property_descriptor( 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]], @@ -544,7 +544,7 @@ pub fn ordinary_get( 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); };