diff --git a/nova_vm/Cargo.toml b/nova_vm/Cargo.toml index 582fbebb2..a1481ac32 100644 --- a/nova_vm/Cargo.toml +++ b/nova_vm/Cargo.toml @@ -25,14 +25,15 @@ wtf8 = { workspace = true } [features] default = ["math", "json", "date", "array-buffer", "shared-array-buffer", "weak-refs", "atomics"] -math = [] -json = ["sonic-rs"] -date = [] array-buffer = [] -shared-array-buffer = [] -weak-refs = [] atomics = ["array-buffer", "shared-array-buffer"] +date = [] +interleaved-gc = [] +json = ["sonic-rs"] +math = [] +shared-array-buffer = [] typescript = [] +weak-refs = [] [build-dependencies] small_string = { path = "../small_string" } diff --git a/nova_vm/src/ecmascript/abstract_operations/operations_on_iterator_objects.rs b/nova_vm/src/ecmascript/abstract_operations/operations_on_iterator_objects.rs index 5bccb47b7..1722b005f 100644 --- a/nova_vm/src/ecmascript/abstract_operations/operations_on_iterator_objects.rs +++ b/nova_vm/src/ecmascript/abstract_operations/operations_on_iterator_objects.rs @@ -451,12 +451,22 @@ pub(crate) fn iterator_to_list( impl HeapMarkAndSweep for IteratorRecord { fn mark_values(&self, queues: &mut WorkQueues) { - self.iterator.mark_values(queues); - self.next_method.mark_values(queues); + let Self { + iterator, + next_method, + done: _, + } = self; + iterator.mark_values(queues); + next_method.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.iterator.sweep_values(compactions); - self.next_method.sweep_values(compactions); + let Self { + iterator, + next_method, + done: _, + } = self; + iterator.sweep_values(compactions); + next_method.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs b/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs index bedb6a7d7..b189bcbba 100644 --- a/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs +++ b/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs @@ -907,7 +907,6 @@ pub(crate) fn initialize_instance_elements( // To do this, we need a new execution context that points to a new // Function environment. The function environment should be lexically a // child of the class constructor's creating environment. - let bytecode = unsafe { bytecode.as_ref() }; let f = constructor.into_function(); let outer_env = constructor_data.environment; let outer_priv_env = constructor_data.private_environment; diff --git a/nova_vm/src/ecmascript/builtins/array/data.rs b/nova_vm/src/ecmascript/builtins/array/data.rs index 15305416b..35b16aa84 100644 --- a/nova_vm/src/ecmascript/builtins/array/data.rs +++ b/nova_vm/src/ecmascript/builtins/array/data.rs @@ -127,14 +127,12 @@ pub struct ArrayHeapData { impl HeapMarkAndSweep for SealableElementsVector { fn mark_values(&self, queues: &mut WorkQueues) { - let item = *self; - let elements: ElementsVector = item.into(); + let elements: ElementsVector = (*self).into(); elements.mark_values(queues) } fn sweep_values(&mut self, compactions: &CompactionLists) { - let item = *self; - let mut elements: ElementsVector = item.into(); + let mut elements: ElementsVector = (*self).into(); elements.sweep_values(compactions); self.elements_index = elements.elements_index; } @@ -142,12 +140,20 @@ impl HeapMarkAndSweep for SealableElementsVector { impl HeapMarkAndSweep for ArrayHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); - self.elements.mark_values(queues); + let Self { + object_index, + elements, + } = self; + object_index.mark_values(queues); + elements.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); - self.elements.sweep_values(compactions); + let Self { + object_index, + elements, + } = self; + object_index.sweep_values(compactions); + elements.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/array_buffer/data.rs b/nova_vm/src/ecmascript/builtins/array_buffer/data.rs index 86cc47efc..68a857683 100644 --- a/nova_vm/src/ecmascript/builtins/array_buffer/data.rs +++ b/nova_vm/src/ecmascript/builtins/array_buffer/data.rs @@ -149,10 +149,18 @@ impl ArrayBufferHeapData { impl HeapMarkAndSweep for ArrayBufferHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); + let Self { + object_index, + buffer: _, + } = self; + object_index.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); + let Self { + object_index, + buffer: _, + } = self; + object_index.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/bound_function.rs b/nova_vm/src/ecmascript/builtins/bound_function.rs index 234852d97..c2b5478dd 100644 --- a/nova_vm/src/ecmascript/builtins/bound_function.rs +++ b/nova_vm/src/ecmascript/builtins/bound_function.rs @@ -321,18 +321,34 @@ impl HeapMarkAndSweep for BoundFunction { impl HeapMarkAndSweep for BoundFunctionHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.name.mark_values(queues); - self.bound_target_function.mark_values(queues); - self.object_index.mark_values(queues); - self.bound_this.mark_values(queues); - self.bound_arguments.mark_values(queues); + let Self { + object_index, + length: _, + bound_target_function, + bound_this, + bound_arguments, + name, + } = self; + name.mark_values(queues); + bound_target_function.mark_values(queues); + object_index.mark_values(queues); + bound_this.mark_values(queues); + bound_arguments.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.name.sweep_values(compactions); - self.bound_target_function.sweep_values(compactions); - self.object_index.sweep_values(compactions); - self.bound_this.sweep_values(compactions); - self.bound_arguments.sweep_values(compactions); + let Self { + object_index, + length: _, + bound_target_function, + bound_this, + bound_arguments, + name, + } = self; + name.sweep_values(compactions); + bound_target_function.sweep_values(compactions); + object_index.sweep_values(compactions); + bound_this.sweep_values(compactions); + bound_arguments.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/builtin_constructor.rs b/nova_vm/src/ecmascript/builtins/builtin_constructor.rs index 4a68baaed..9f72f2229 100644 --- a/nova_vm/src/ecmascript/builtins/builtin_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/builtin_constructor.rs @@ -2,10 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use std::{ - ops::{Index, IndexMut}, - ptr::NonNull, -}; +use std::ops::{Index, IndexMut}; use oxc_span::Span; @@ -339,7 +336,7 @@ pub(crate) struct BuiltinConstructorArgs { pub(crate) class_name: String, pub(crate) prototype: Option, pub(crate) prototype_property: Object, - pub(crate) compiled_initializer_bytecode: Option>, + pub(crate) compiled_initializer_bytecode: Option, pub(crate) env: EnvironmentIndex, pub(crate) private_env: Option, pub(crate) source_code: SourceCode, @@ -414,17 +411,13 @@ pub(crate) fn create_builtin_constructor( agent.heap.create_null_object(&entries) }; - let compiled_initializer_bytecode = args - .compiled_initializer_bytecode - .map(|bytecode| NonNull::from(Box::leak(bytecode))); - // 13. Return func. agent.heap.create(BuiltinConstructorHeapData { // 10. Perform SetFunctionLength(func, length). // Skipped as length of builtin constructors is always 0. // 8. Set func.[[Realm]] to realm. realm, - compiled_initializer_bytecode, + compiled_initializer_bytecode: args.compiled_initializer_bytecode, is_derived: args.is_derived, object_index: Some(backing_object), environment: args.env, @@ -453,34 +446,40 @@ impl HeapMarkAndSweep for BuiltinConstructorFunction { impl HeapMarkAndSweep for BuiltinConstructorHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.realm.mark_values(queues); - self.object_index.mark_values(queues); - self.environment.mark_values(queues); - self.private_environment.mark_values(queues); - self.source_code.mark_values(queues); - if let Some(exe) = &self.compiled_initializer_bytecode { - // SAFETY: This is a valid, non-null pointer to an owned Executable - // that cannot have any live mutable references to it. - unsafe { exe.as_ref() }.mark_values(queues); - } + let Self { + object_index, + realm, + is_derived: _, + compiled_initializer_bytecode, + environment, + private_environment, + source_text: _, + source_code, + } = self; + realm.mark_values(queues); + object_index.mark_values(queues); + environment.mark_values(queues); + private_environment.mark_values(queues); + source_code.mark_values(queues); + compiled_initializer_bytecode.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.realm.sweep_values(compactions); - self.object_index.sweep_values(compactions); - self.environment.sweep_values(compactions); - self.private_environment.sweep_values(compactions); - self.source_code.sweep_values(compactions); - if let Some(exe) = &mut self.compiled_initializer_bytecode { - // SAFETY: This is a valid, non-null pointer to an owned Executable - // that cannot have any live references to it. - // References to this Executable are only created above for marking - // and in function_definition for running the function. Both of the - // references only live for the duration of a synchronous call and - // no longer. Sweeping cannot run concurrently with marking or with - // ECMAScript code execution. Hence we can be sure that this is not - // an aliasing violation. - unsafe { exe.as_mut() }.sweep_values(compactions); - } + let Self { + object_index, + realm, + is_derived: _, + compiled_initializer_bytecode, + environment, + private_environment, + source_text: _, + source_code, + } = self; + realm.sweep_values(compactions); + object_index.sweep_values(compactions); + environment.sweep_values(compactions); + private_environment.sweep_values(compactions); + source_code.sweep_values(compactions); + compiled_initializer_bytecode.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/builtin_function.rs b/nova_vm/src/ecmascript/builtins/builtin_function.rs index 95fb99b4c..8f3bdc089 100644 --- a/nova_vm/src/ecmascript/builtins/builtin_function.rs +++ b/nova_vm/src/ecmascript/builtins/builtin_function.rs @@ -540,14 +540,28 @@ impl HeapMarkAndSweep for BuiltinFunction { impl HeapMarkAndSweep for BuiltinFunctionHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.realm.mark_values(queues); - self.initial_name.mark_values(queues); - self.object_index.mark_values(queues); + let Self { + object_index, + length: _, + realm, + initial_name, + behaviour: _, + } = self; + realm.mark_values(queues); + initial_name.mark_values(queues); + object_index.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.realm.sweep_values(compactions); - self.initial_name.sweep_values(compactions); - self.object_index.sweep_values(compactions); + let Self { + object_index, + length: _, + realm, + initial_name, + behaviour: _, + } = self; + realm.sweep_values(compactions); + initial_name.sweep_values(compactions); + object_index.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_function_objects/await_reaction.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_function_objects/await_reaction.rs index b267c19dd..3830e844d 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_function_objects/await_reaction.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_function_objects/await_reaction.rs @@ -68,8 +68,7 @@ impl AwaitReactionIdentifier { // 5. d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it. let vm = agent[self].vm.take().unwrap(); let async_function = agent[self].async_function.unwrap(); - // SAFETY: We keep the async function alive. - let executable = unsafe { agent[async_function].compiled_bytecode.unwrap().as_ref() }; + let executable = agent[async_function].compiled_bytecode.unwrap(); let execution_result = match reaction_type { PromiseReactionType::Fulfill => vm.resume(agent, executable, value), PromiseReactionType::Reject => vm.resume_throw(agent, executable, value), @@ -179,14 +178,28 @@ impl CreateHeapData for Heap { impl HeapMarkAndSweep for AwaitReaction { fn mark_values(&self, queues: &mut WorkQueues) { - self.vm.mark_values(queues); - self.async_function.mark_values(queues); - self.return_promise_capability.mark_values(queues); + let Self { + vm, + async_function, + execution_context, + return_promise_capability, + } = self; + vm.mark_values(queues); + async_function.mark_values(queues); + execution_context.mark_values(queues); + return_promise_capability.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.vm.sweep_values(compactions); - self.async_function.sweep_values(compactions); - self.return_promise_capability.sweep_values(compactions); + let Self { + vm, + async_function, + execution_context, + return_promise_capability, + } = self; + vm.sweep_values(compactions); + async_function.sweep_values(compactions); + execution_context.sweep_values(compactions); + return_promise_capability.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/generator_objects.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/generator_objects.rs index d222003f3..f87c72f85 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/generator_objects.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/generator_objects.rs @@ -15,7 +15,7 @@ use crate::{ InternalMethods, InternalSlots, IntoObject, IntoValue, Object, OrdinaryObject, Value, }, }, - engine::{Executable, ExecutionResult, SuspendedVm, Vm}, + engine::{local_value::ObjectScopeRoot, Executable, ExecutionResult, SuspendedVm, Vm}, heap::{ indexes::{BaseIndex, GeneratorIndex}, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, WorkQueues, @@ -35,7 +35,7 @@ impl Generator { } /// [27.5.3.3 GeneratorResume ( generator, value, generatorBrand )](https://tc39.es/ecma262/#sec-generatorresume) - pub(crate) fn resume(self, agent: &mut Agent, value: Value) -> JsResult { + pub(crate) fn resume(mut self, agent: &mut Agent, value: Value) -> JsResult { // 1. Let state be ? GeneratorValidate(generator, generatorBrand). match agent[self].generator_state.as_ref().unwrap() { GeneratorState::Suspended { .. } => { @@ -72,14 +72,18 @@ impl Generator { // execution context. agent.execution_context_stack.push(execution_context); + let saved = ObjectScopeRoot::root(self, agent); + // 9. Resume the suspended evaluation of genContext using NormalCompletion(value) as the // result of the operation that suspended it. Let result be the value returned by the // resumed computation. let execution_result = match vm_or_args { - VmOrArguments::Arguments(args) => Vm::execute(agent, &executable, Some(&args)), - VmOrArguments::Vm(vm) => vm.resume(agent, &executable, value), + VmOrArguments::Arguments(args) => Vm::execute(agent, executable, Some(&args)), + VmOrArguments::Vm(vm) => vm.resume(agent, executable, value), }; + self = saved.get(agent); + // GeneratorStart: 4.f. Remove acGenContext from the execution context stack and restore the // execution context that is at the top of the execution context stack as the running // execution context. @@ -194,7 +198,7 @@ impl Generator { // 10. Resume the suspended evaluation of genContext using NormalCompletion(value) as the // result of the operation that suspended it. Let result be the value returned by the // resumed computation. - let execution_result = vm.resume_throw(agent, &executable, value); + let execution_result = vm.resume_throw(agent, executable, value); // GeneratorStart: 4.f. Remove acGenContext from the execution context stack and restore the // execution context that is at the top of the execution context stack as the running @@ -264,6 +268,18 @@ impl From for Object { } } +impl TryFrom for Generator { + type Error = (); + + fn try_from(value: Value) -> Result { + if let Value::Generator(value) = value { + Ok(value) + } else { + Err(()) + } + } +} + impl InternalSlots for Generator { const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::Generator; @@ -355,12 +371,16 @@ pub(crate) enum GeneratorState { impl HeapMarkAndSweep for GeneratorHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); + let Self { + object_index, + generator_state, + } = self; + object_index.mark_values(queues); if let Some(GeneratorState::Suspended { vm_or_args, executable, execution_context, - }) = &self.generator_state + }) = generator_state { match vm_or_args { VmOrArguments::Vm(vm) => vm.mark_values(queues), @@ -372,12 +392,16 @@ impl HeapMarkAndSweep for GeneratorHeapData { } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); + let Self { + object_index, + generator_state, + } = self; + object_index.sweep_values(compactions); if let Some(GeneratorState::Suspended { vm_or_args, executable, execution_context, - }) = &mut self.generator_state + }) = generator_state { match vm_or_args { VmOrArguments::Vm(vm) => vm.sweep_values(compactions), diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs index 682818751..a9b96e73f 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs @@ -228,11 +228,19 @@ impl PromiseCapability { impl HeapMarkAndSweep for PromiseCapability { fn mark_values(&self, queues: &mut WorkQueues) { - self.promise.mark_values(queues); + let Self { + promise, + must_be_unresolved: _, + } = self; + promise.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.promise.sweep_values(compactions); + let Self { + promise, + must_be_unresolved: _, + } = self; + promise.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/data_view/data.rs b/nova_vm/src/ecmascript/builtins/data_view/data.rs index becc10751..453d04e3a 100644 --- a/nova_vm/src/ecmascript/builtins/data_view/data.rs +++ b/nova_vm/src/ecmascript/builtins/data_view/data.rs @@ -14,10 +14,12 @@ pub struct DataViewHeapData { impl HeapMarkAndSweep for DataViewHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); + let Self { object_index } = self; + object_index.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); + let Self { object_index } = self; + object_index.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/date/data.rs b/nova_vm/src/ecmascript/builtins/date/data.rs index 96423802a..3285cb694 100644 --- a/nova_vm/src/ecmascript/builtins/date/data.rs +++ b/nova_vm/src/ecmascript/builtins/date/data.rs @@ -25,10 +25,18 @@ impl DateHeapData { impl HeapMarkAndSweep for DateHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); + let Self { + object_index, + date: _, + } = self; + object_index.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); + let Self { + object_index, + date: _, + } = self; + object_index.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/ecmascript_function.rs b/nova_vm/src/ecmascript/builtins/ecmascript_function.rs index 26bb4e4b2..32b9d4090 100644 --- a/nova_vm/src/ecmascript/builtins/ecmascript_function.rs +++ b/nova_vm/src/ecmascript/builtins/ecmascript_function.rs @@ -1038,51 +1038,74 @@ impl CreateHeapData for Heap { impl HeapMarkAndSweep for ECMAScriptFunctionHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.name.mark_values(queues); - self.object_index.mark_values(queues); - - self.ecmascript_function.environment.mark_values(queues); - self.ecmascript_function - .private_environment - .mark_values(queues); - self.ecmascript_function.realm.mark_values(queues); - self.ecmascript_function - .script_or_module - .mark_values(queues); - self.ecmascript_function.home_object.mark_values(queues); - if let Some(exe) = &self.compiled_bytecode { - // SAFETY: This is a valid, non-null pointer to an owned Executable - // that cannot have any live mutable references to it. - unsafe { exe.as_ref() }.mark_values(queues); - } + let Self { + object_index, + length: _, + ecmascript_function, + compiled_bytecode, + name, + } = self; + let ECMAScriptFunctionObjectHeapData { + environment, + private_environment, + formal_parameters: _, + ecmascript_code: _, + is_concise_arrow_function: _, + is_async: _, + is_generator: _, + constructor_status: _, + realm, + script_or_module, + this_mode: _, + strict: _, + home_object, + source_text: _, + source_code, + } = ecmascript_function; + object_index.mark_values(queues); + compiled_bytecode.mark_values(queues); + name.mark_values(queues); + environment.mark_values(queues); + private_environment.mark_values(queues); + realm.mark_values(queues); + script_or_module.mark_values(queues); + home_object.mark_values(queues); + source_code.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.name.sweep_values(compactions); - self.object_index.sweep_values(compactions); - self.ecmascript_function - .environment - .sweep_values(compactions); - self.ecmascript_function - .private_environment - .sweep_values(compactions); - self.ecmascript_function.realm.sweep_values(compactions); - self.ecmascript_function - .script_or_module - .sweep_values(compactions); - self.ecmascript_function - .home_object - .sweep_values(compactions); - if let Some(exe) = &mut self.compiled_bytecode { - // SAFETY: This is a valid, non-null pointer to an owned Executable - // that cannot have any live references to it. - // References to this Executable are only created above for marking - // and in function_definition for running the function. Both of the - // references only live for the duration of a synchronous call and - // no longer. Sweeping cannot run concurrently with marking or with - // ECMAScript code execution. Hence we can be sure that this is not - // an aliasing violation. - unsafe { exe.as_mut() }.sweep_values(compactions); - } + let Self { + object_index, + length: _, + ecmascript_function, + compiled_bytecode, + name, + } = self; + let ECMAScriptFunctionObjectHeapData { + environment, + private_environment, + formal_parameters: _, + ecmascript_code: _, + is_concise_arrow_function: _, + is_async: _, + is_generator: _, + constructor_status: _, + realm, + script_or_module, + this_mode: _, + strict: _, + home_object, + source_text: _, + source_code, + } = ecmascript_function; + object_index.sweep_values(compactions); + compiled_bytecode.sweep_values(compactions); + name.sweep_values(compactions); + environment.sweep_values(compactions); + private_environment.sweep_values(compactions); + realm.sweep_values(compactions); + script_or_module.sweep_values(compactions); + home_object.sweep_values(compactions); + source_code.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/embedder_object/data.rs b/nova_vm/src/ecmascript/builtins/embedder_object/data.rs index 402e36959..c495c1423 100644 --- a/nova_vm/src/ecmascript/builtins/embedder_object/data.rs +++ b/nova_vm/src/ecmascript/builtins/embedder_object/data.rs @@ -8,7 +8,11 @@ use crate::heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}; pub struct EmbedderObjectHeapData {} impl HeapMarkAndSweep for EmbedderObjectHeapData { - fn mark_values(&self, _queues: &mut WorkQueues) {} + fn mark_values(&self, _queues: &mut WorkQueues) { + let Self {} = self; + } - fn sweep_values(&mut self, _compactions: &CompactionLists) {} + fn sweep_values(&mut self, _compactions: &CompactionLists) { + let Self {} = self; + } } diff --git a/nova_vm/src/ecmascript/builtins/error/data.rs b/nova_vm/src/ecmascript/builtins/error/data.rs index 5498e1014..544e1ef88 100644 --- a/nova_vm/src/ecmascript/builtins/error/data.rs +++ b/nova_vm/src/ecmascript/builtins/error/data.rs @@ -32,14 +32,27 @@ impl ErrorHeapData { impl HeapMarkAndSweep for ErrorHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); - self.message.mark_values(queues); - self.cause.mark_values(queues); + let Self { + object_index, + kind: _, + message, + cause, + } = self; + + object_index.mark_values(queues); + message.mark_values(queues); + cause.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); - self.message.sweep_values(compactions); - self.cause.sweep_values(compactions); + let Self { + object_index, + kind: _, + message, + cause, + } = self; + object_index.sweep_values(compactions); + message.sweep_values(compactions); + cause.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/finalization_registry/data.rs b/nova_vm/src/ecmascript/builtins/finalization_registry/data.rs index c60b28747..7ca108985 100644 --- a/nova_vm/src/ecmascript/builtins/finalization_registry/data.rs +++ b/nova_vm/src/ecmascript/builtins/finalization_registry/data.rs @@ -14,10 +14,12 @@ pub struct FinalizationRegistryHeapData { impl HeapMarkAndSweep for FinalizationRegistryHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); + let Self { object_index } = self; + object_index.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); + let Self { object_index } = self; + object_index.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/global_object.rs b/nova_vm/src/ecmascript/builtins/global_object.rs index e67d0351e..4c63b452b 100644 --- a/nova_vm/src/ecmascript/builtins/global_object.rs +++ b/nova_vm/src/ecmascript/builtins/global_object.rs @@ -335,7 +335,10 @@ pub fn perform_eval( // a. Set result to Completion(Evaluation of body). // 30. If result is a normal completion and result.[[Value]] is empty, then // a. Set result to NormalCompletion(undefined). - Vm::execute(agent, &exe, None).into_js_result() + let result = Vm::execute(agent, exe, None).into_js_result(); + // SAFETY: No one can access the bytecode anymore. + unsafe { exe.try_drop(agent) }; + result } else { Err(result.err().unwrap()) }; diff --git a/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_iterator_objects/array_iterator.rs b/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_iterator_objects/array_iterator.rs index fea456c46..503b84f5d 100644 --- a/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_iterator_objects/array_iterator.rs +++ b/nova_vm/src/ecmascript/builtins/indexed_collections/array_objects/array_iterator_objects/array_iterator.rs @@ -180,12 +180,24 @@ pub struct ArrayIteratorHeapData { impl HeapMarkAndSweep for ArrayIteratorHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); - self.array.mark_values(queues); + let Self { + object_index, + array, + next_index: _, + kind: _, + } = self; + object_index.mark_values(queues); + array.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); - self.array.sweep_values(compactions); + let Self { + object_index, + array, + next_index: _, + kind: _, + } = self; + object_index.sweep_values(compactions); + array.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/keyed_collections/map_objects/map_iterator_objects/map_iterator.rs b/nova_vm/src/ecmascript/builtins/keyed_collections/map_objects/map_iterator_objects/map_iterator.rs index 9e8c2c54e..34b2cfb79 100644 --- a/nova_vm/src/ecmascript/builtins/keyed_collections/map_objects/map_iterator_objects/map_iterator.rs +++ b/nova_vm/src/ecmascript/builtins/keyed_collections/map_objects/map_iterator_objects/map_iterator.rs @@ -172,12 +172,24 @@ pub struct MapIteratorHeapData { impl HeapMarkAndSweep for MapIteratorHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); - self.map.mark_values(queues); + let Self { + object_index, + map, + next_index: _, + kind: _, + } = self; + object_index.mark_values(queues); + map.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); - self.map.sweep_values(compactions); + let Self { + object_index, + map, + next_index: _, + kind: _, + } = self; + object_index.sweep_values(compactions); + map.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_iterator_objects/set_iterator.rs b/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_iterator_objects/set_iterator.rs index 970e468fe..b5f1985b5 100644 --- a/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_iterator_objects/set_iterator.rs +++ b/nova_vm/src/ecmascript/builtins/keyed_collections/set_objects/set_iterator_objects/set_iterator.rs @@ -172,12 +172,24 @@ pub struct SetIteratorHeapData { impl HeapMarkAndSweep for SetIteratorHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); - self.set.mark_values(queues); + let Self { + object_index, + set, + next_index: _, + kind: _, + } = self; + object_index.mark_values(queues); + set.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); - self.set.sweep_values(compactions); + let Self { + object_index, + set, + next_index: _, + kind: _, + } = self; + object_index.sweep_values(compactions); + set.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/map/data.rs b/nova_vm/src/ecmascript/builtins/map/data.rs index 0704bfd48..a9049255d 100644 --- a/nova_vm/src/ecmascript/builtins/map/data.rs +++ b/nova_vm/src/ecmascript/builtins/map/data.rs @@ -169,12 +169,16 @@ fn rehash_map_data( impl HeapMarkAndSweep for MapHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); - self.map_data + let Self { + object_index, + map_data, + } = self; + object_index.mark_values(queues); + map_data .keys .iter() .for_each(|value| value.mark_values(queues)); - self.map_data + map_data .values .iter() .for_each(|value| value.mark_values(queues)); diff --git a/nova_vm/src/ecmascript/builtins/module/data.rs b/nova_vm/src/ecmascript/builtins/module/data.rs index 9f376b883..d60d7cb83 100644 --- a/nova_vm/src/ecmascript/builtins/module/data.rs +++ b/nova_vm/src/ecmascript/builtins/module/data.rs @@ -86,18 +86,44 @@ impl ModuleRecord { impl HeapMarkAndSweep for ModuleHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - for ele in self.exports.iter() { + let Self { + object_index, + module, + exports, + } = self; + let ModuleRecord { + realm, + environment: _, + namespace, + host_defined: _, + } = module; + for ele in exports.iter() { ele.mark_values(queues); } - self.module.namespace.mark_values(queues); - self.object_index.mark_values(queues); + realm.mark_values(queues); + // environment.mark_values(queues); + namespace.mark_values(queues); + object_index.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - for ele in self.exports.iter_mut() { + let Self { + object_index, + module, + exports, + } = self; + let ModuleRecord { + realm, + environment: _, + namespace, + host_defined: _, + } = module; + for ele in exports.iter_mut() { ele.sweep_values(compactions); } - self.module.namespace.sweep_values(compactions); - self.object_index.sweep_values(compactions); + realm.sweep_values(compactions); + // environment.sweep_values(compactions); + namespace.sweep_values(compactions); + object_index.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/primitive_objects.rs b/nova_vm/src/ecmascript/builtins/primitive_objects.rs index c57dea555..02409b08a 100644 --- a/nova_vm/src/ecmascript/builtins/primitive_objects.rs +++ b/nova_vm/src/ecmascript/builtins/primitive_objects.rs @@ -528,8 +528,9 @@ impl PrimitiveObjectHeapData { impl HeapMarkAndSweep for PrimitiveObjectHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); - match self.data { + let Self { object_index, data } = self; + object_index.mark_values(queues); + match data { PrimitiveObjectData::String(data) => data.mark_values(queues), PrimitiveObjectData::Symbol(data) => data.mark_values(queues), PrimitiveObjectData::Number(data) => data.mark_values(queues), @@ -539,8 +540,9 @@ impl HeapMarkAndSweep for PrimitiveObjectHeapData { } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); - match &mut self.data { + let Self { object_index, data } = self; + object_index.sweep_values(compactions); + match data { PrimitiveObjectData::String(data) => data.sweep_values(compactions), PrimitiveObjectData::Symbol(data) => data.sweep_values(compactions), PrimitiveObjectData::Number(data) => data.sweep_values(compactions), diff --git a/nova_vm/src/ecmascript/builtins/promise/data.rs b/nova_vm/src/ecmascript/builtins/promise/data.rs index 6ab64aa4c..bf822b4f8 100644 --- a/nova_vm/src/ecmascript/builtins/promise/data.rs +++ b/nova_vm/src/ecmascript/builtins/promise/data.rs @@ -93,13 +93,21 @@ impl HeapMarkAndSweep for PromiseReactions { impl HeapMarkAndSweep for PromiseHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); - self.promise_state.mark_values(queues); + let Self { + object_index, + promise_state, + } = self; + object_index.mark_values(queues); + promise_state.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); - self.promise_state.sweep_values(compactions); + let Self { + object_index, + promise_state, + } = self; + object_index.sweep_values(compactions); + promise_state.sweep_values(compactions); } } @@ -109,13 +117,16 @@ impl HeapMarkAndSweep for PromiseState { PromiseState::Pending { fulfill_reactions, reject_reactions, - .. + is_resolved: _, } => { fulfill_reactions.mark_values(queues); reject_reactions.mark_values(queues); } PromiseState::Fulfilled { promise_result } - | PromiseState::Rejected { promise_result, .. } => { + | PromiseState::Rejected { + promise_result, + is_handled: _, + } => { promise_result.mark_values(queues); } } @@ -126,13 +137,16 @@ impl HeapMarkAndSweep for PromiseState { PromiseState::Pending { fulfill_reactions, reject_reactions, - .. + is_resolved: _, } => { fulfill_reactions.sweep_values(compactions); reject_reactions.sweep_values(compactions); } PromiseState::Fulfilled { promise_result } - | PromiseState::Rejected { promise_result, .. } => { + | PromiseState::Rejected { + promise_result, + is_handled: _, + } => { promise_result.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/regexp/data.rs b/nova_vm/src/ecmascript/builtins/regexp/data.rs index 7e32ff116..0de69de9a 100644 --- a/nova_vm/src/ecmascript/builtins/regexp/data.rs +++ b/nova_vm/src/ecmascript/builtins/regexp/data.rs @@ -29,12 +29,22 @@ impl Default for RegExpHeapData { impl HeapMarkAndSweep for RegExpHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); - self.original_source.mark_values(queues); + let Self { + object_index, + original_source, + original_flags: _, + } = self; + object_index.mark_values(queues); + original_source.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); - self.original_source.sweep_values(compactions); + let Self { + object_index, + original_source, + original_flags: _, + } = self; + object_index.sweep_values(compactions); + original_source.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/set/data.rs b/nova_vm/src/ecmascript/builtins/set/data.rs index 8234a0255..790d4306d 100644 --- a/nova_vm/src/ecmascript/builtins/set/data.rs +++ b/nova_vm/src/ecmascript/builtins/set/data.rs @@ -168,8 +168,12 @@ fn rehash_set_data( impl HeapMarkAndSweep for SetHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); - self.set_data + let Self { + object_index, + set_data, + } = self; + object_index.mark_values(queues); + set_data .values .iter() .for_each(|value| value.mark_values(queues)); diff --git a/nova_vm/src/ecmascript/builtins/weak_map/data.rs b/nova_vm/src/ecmascript/builtins/weak_map/data.rs index 9e2ff540b..17162585e 100644 --- a/nova_vm/src/ecmascript/builtins/weak_map/data.rs +++ b/nova_vm/src/ecmascript/builtins/weak_map/data.rs @@ -27,21 +27,31 @@ pub struct WeakMapHeapData { impl HeapMarkAndSweep for WeakMapHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); - for ele in &self.keys { + let Self { + object_index, + keys, + values, + } = self; + object_index.mark_values(queues); + for ele in keys { ele.mark_values(queues); } - for ele in &self.values { + for ele in values { ele.mark_values(queues); } } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); - for ele in &mut self.keys { + let Self { + object_index, + keys, + values, + } = self; + object_index.sweep_values(compactions); + for ele in keys { ele.sweep_values(compactions); } - for ele in &mut self.values { + for ele in values { ele.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/weak_ref/data.rs b/nova_vm/src/ecmascript/builtins/weak_ref/data.rs index e5ba45b03..00bceba9f 100644 --- a/nova_vm/src/ecmascript/builtins/weak_ref/data.rs +++ b/nova_vm/src/ecmascript/builtins/weak_ref/data.rs @@ -26,15 +26,25 @@ impl Default for WeakRefHeapData { impl HeapMarkAndSweep for WeakRefHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.object_index.mark_values(queues); - if self.is_strong { - self.value.mark_values(queues); + let Self { + object_index, + value, + is_strong, + } = self; + object_index.mark_values(queues); + if *is_strong { + value.mark_values(queues); } } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object_index.sweep_values(compactions); - self.value.sweep_values(compactions); - self.is_strong = false; + let Self { + object_index, + value, + is_strong, + } = self; + object_index.sweep_values(compactions); + value.sweep_values(compactions); + *is_strong = false; } } diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index c504a2511..251a7c922 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -18,11 +18,9 @@ use crate::{ builtins::{control_abstraction_objects::promise_objects::promise_abstract_operations::promise_jobs::{PromiseReactionJob, PromiseResolveThenableJob}, error::ErrorHeapData, promise::Promise}, scripts_and_modules::ScriptOrModule, types::{Function, IntoValue, Object, Reference, String, Symbol, Value}, - }, - heap::{heap_gc::heap_gc, CreateHeapData, PrimitiveHeapIndexable}, - Heap, + }, engine::Vm, heap::{heap_gc::heap_gc, CreateHeapData, PrimitiveHeapIndexable}, Heap }; -use std::{any::Any, cell::RefCell}; +use std::{any::Any, cell::RefCell, ptr::NonNull}; #[derive(Debug, Default)] pub struct Options { @@ -236,6 +234,8 @@ impl GcAgent { assert!(self.agent.execution_context_stack.is_empty()); let result = self.agent.run_in_realm(realm, func); assert!(self.agent.execution_context_stack.is_empty()); + assert!(self.agent.vm_stack.is_empty()); + self.agent.stack_values.borrow_mut().clear(); result } @@ -244,7 +244,7 @@ impl GcAgent { // GC is disabled; no-op return; } - heap_gc(&mut self.agent.heap, &mut self.realm_roots); + heap_gc(&mut self.agent, &mut self.realm_roots); } } @@ -253,8 +253,6 @@ impl GcAgent { pub struct Agent { pub(crate) heap: Heap, pub(crate) options: Options, - // pre_allocated: PreAllocated, - pub(crate) exception: Option, pub(crate) symbol_id: usize, pub(crate) global_symbol_registry: AHashMap<&'static str, Symbol>, pub(crate) host_hooks: &'static dyn HostHooks, @@ -264,6 +262,8 @@ pub struct Agent { /// TODO: With Realm-specific heaps we'll need a side-table to define which /// Realm a particular stack value points to. pub(crate) stack_values: RefCell>, + /// Temporary storage for on-stack VMs. + pub(crate) vm_stack: Vec>, } impl Agent { @@ -271,12 +271,12 @@ impl Agent { Self { heap: Heap::new(), options, - exception: None, symbol_id: 0, global_symbol_registry: AHashMap::default(), host_hooks, execution_context_stack: Vec::new(), stack_values: RefCell::new(Vec::with_capacity(64)), + vm_stack: Vec::with_capacity(16), } } diff --git a/nova_vm/src/ecmascript/execution/environments/declarative_environment.rs b/nova_vm/src/ecmascript/execution/environments/declarative_environment.rs index 8a90a9095..7eda1a23b 100644 --- a/nova_vm/src/ecmascript/execution/environments/declarative_environment.rs +++ b/nova_vm/src/ecmascript/execution/environments/declarative_environment.rs @@ -152,25 +152,33 @@ impl DeclarativeEnvironment { impl HeapMarkAndSweep for DeclarativeEnvironment { fn mark_values(&self, queues: &mut WorkQueues) { - self.outer_env.mark_values(queues); - for binding in self.bindings.values() { + let Self { + outer_env, + bindings, + } = self; + outer_env.mark_values(queues); + for binding in bindings.values() { binding.value.mark_values(queues); } } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.outer_env.sweep_values(compactions); - for binding in self.bindings.values_mut() { + let Self { + outer_env, + bindings, + } = self; + outer_env.sweep_values(compactions); + for binding in bindings.values_mut() { binding.value.sweep_values(compactions); } - let keys = self.bindings.keys().copied().collect::>(); + let keys = bindings.keys().copied().collect::>(); for key in keys.iter() { let mut new_key = *key; new_key.sweep_values(compactions); if *key != new_key { - let mut binding = self.bindings.remove(key).unwrap(); + let mut binding = bindings.remove(key).unwrap(); binding.value.sweep_values(compactions); - self.bindings.insert(new_key, binding); + bindings.insert(new_key, binding); } } } diff --git a/nova_vm/src/ecmascript/execution/environments/function_environment.rs b/nova_vm/src/ecmascript/execution/environments/function_environment.rs index 47b426805..290ab3003 100644 --- a/nova_vm/src/ecmascript/execution/environments/function_environment.rs +++ b/nova_vm/src/ecmascript/execution/environments/function_environment.rs @@ -70,17 +70,31 @@ pub(crate) struct FunctionEnvironment { impl HeapMarkAndSweep for FunctionEnvironment { fn mark_values(&self, queues: &mut WorkQueues) { - self.declarative_environment.mark_values(queues); - self.function_object.mark_values(queues); - self.new_target.mark_values(queues); - self.this_value.mark_values(queues); + let Self { + this_value, + this_binding_status: _, + function_object, + new_target, + declarative_environment, + } = self; + declarative_environment.mark_values(queues); + function_object.mark_values(queues); + new_target.mark_values(queues); + this_value.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.declarative_environment.sweep_values(compactions); - self.function_object.sweep_values(compactions); - self.new_target.sweep_values(compactions); - self.this_value.sweep_values(compactions); + let Self { + this_value, + this_binding_status: _, + function_object, + new_target, + declarative_environment, + } = self; + declarative_environment.sweep_values(compactions); + function_object.sweep_values(compactions); + new_target.sweep_values(compactions); + this_value.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/execution/environments/global_environment.rs b/nova_vm/src/ecmascript/execution/environments/global_environment.rs index 936efefd9..f803a8295 100644 --- a/nova_vm/src/ecmascript/execution/environments/global_environment.rs +++ b/nova_vm/src/ecmascript/execution/environments/global_environment.rs @@ -100,19 +100,31 @@ impl GlobalEnvironment { impl HeapMarkAndSweep for GlobalEnvironment { fn mark_values(&self, queues: &mut WorkQueues) { - self.declarative_record.mark_values(queues); - self.global_this_value.mark_values(queues); - self.object_record.mark_values(queues); - for ele in &self.var_names { + let Self { + object_record, + global_this_value, + declarative_record, + var_names, + } = self; + declarative_record.mark_values(queues); + global_this_value.mark_values(queues); + object_record.mark_values(queues); + for ele in var_names { ele.mark_values(queues); } } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.declarative_record.sweep_values(compactions); - self.global_this_value.sweep_values(compactions); - self.object_record.sweep_values(compactions); - for key in self.var_names.clone() { + let Self { + object_record, + global_this_value, + declarative_record, + var_names, + } = self; + declarative_record.sweep_values(compactions); + global_this_value.sweep_values(compactions); + object_record.sweep_values(compactions); + for key in var_names.clone() { let mut new_key = key; new_key.sweep_values(compactions); if key != new_key { diff --git a/nova_vm/src/ecmascript/execution/environments/object_environment.rs b/nova_vm/src/ecmascript/execution/environments/object_environment.rs index 670589f7f..ba6451c17 100644 --- a/nova_vm/src/ecmascript/execution/environments/object_environment.rs +++ b/nova_vm/src/ecmascript/execution/environments/object_environment.rs @@ -75,13 +75,23 @@ impl ObjectEnvironment { impl HeapMarkAndSweep for ObjectEnvironment { fn mark_values(&self, queues: &mut WorkQueues) { - self.outer_env.mark_values(queues); - self.binding_object.mark_values(queues); + let Self { + binding_object, + is_with_environment: _, + outer_env, + } = self; + outer_env.mark_values(queues); + binding_object.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.outer_env.sweep_values(compactions); - self.binding_object.sweep_values(compactions); + let Self { + binding_object, + is_with_environment: _, + outer_env, + } = self; + outer_env.sweep_values(compactions); + binding_object.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/execution/execution_context.rs b/nova_vm/src/ecmascript/execution/execution_context.rs index 7ea4fe9e8..e2faa65cf 100644 --- a/nova_vm/src/ecmascript/execution/execution_context.rs +++ b/nova_vm/src/ecmascript/execution/execution_context.rs @@ -98,33 +98,59 @@ impl ExecutionContext { impl HeapMarkAndSweep for ECMAScriptCodeEvaluationState { fn mark_values(&self, queues: &mut WorkQueues) { - self.lexical_environment.mark_values(queues); - self.variable_environment.mark_values(queues); - self.private_environment.mark_values(queues); - self.source_code.mark_values(queues); + let Self { + lexical_environment, + variable_environment, + private_environment, + is_strict_mode: _, + source_code, + } = self; + lexical_environment.mark_values(queues); + variable_environment.mark_values(queues); + private_environment.mark_values(queues); + source_code.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.lexical_environment.sweep_values(compactions); - self.variable_environment.sweep_values(compactions); - self.private_environment.sweep_values(compactions); - self.source_code.sweep_values(compactions); + let Self { + lexical_environment, + variable_environment, + private_environment, + is_strict_mode: _, + source_code, + } = self; + lexical_environment.sweep_values(compactions); + variable_environment.sweep_values(compactions); + private_environment.sweep_values(compactions); + source_code.sweep_values(compactions); } } impl HeapMarkAndSweep for ExecutionContext { fn mark_values(&self, queues: &mut WorkQueues) { - self.ecmascript_code.mark_values(queues); - self.function.mark_values(queues); - self.realm.mark_values(queues); - self.script_or_module.mark_values(queues); + let Self { + ecmascript_code, + function, + realm, + script_or_module, + } = self; + ecmascript_code.mark_values(queues); + function.mark_values(queues); + realm.mark_values(queues); + script_or_module.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.ecmascript_code.sweep_values(compactions); - self.function.sweep_values(compactions); - self.realm.sweep_values(compactions); - self.script_or_module.sweep_values(compactions); + let Self { + ecmascript_code, + function, + realm, + script_or_module, + } = self; + ecmascript_code.sweep_values(compactions); + function.sweep_values(compactions); + realm.sweep_values(compactions); + script_or_module.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/execution/realm.rs b/nova_vm/src/ecmascript/execution/realm.rs index 5fef330bf..d5abf644b 100644 --- a/nova_vm/src/ecmascript/execution/realm.rs +++ b/nova_vm/src/ecmascript/execution/realm.rs @@ -175,15 +175,33 @@ impl Realm { impl HeapMarkAndSweep for Realm { fn mark_values(&self, queues: &mut WorkQueues) { - self.intrinsics().mark_values(queues); - self.global_env.mark_values(queues); - self.global_object.mark_values(queues); + let Self { + agent_signifier: _, + intrinsics, + global_object, + global_env, + template_map: _, + loaded_modules: _, + host_defined: _, + } = self; + intrinsics.mark_values(queues); + global_env.mark_values(queues); + global_object.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.intrinsics_mut().sweep_values(compactions); - self.global_env.sweep_values(compactions); - self.global_object.sweep_values(compactions); + let Self { + agent_signifier: _, + intrinsics, + global_object, + global_env, + template_map: _, + loaded_modules: _, + host_defined: _, + } = self; + intrinsics.sweep_values(compactions); + global_env.sweep_values(compactions); + global_object.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs index 39b3cf52a..e9b9c208d 100644 --- a/nova_vm/src/ecmascript/execution/realm/intrinsics.rs +++ b/nova_vm/src/ecmascript/execution/realm/intrinsics.rs @@ -1776,11 +1776,19 @@ impl HeapMarkAndSweep for Intrinsics { } fn sweep_values(&mut self, compactions: &CompactionLists) { - OrdinaryObject(self.object_index_base).sweep_values(compactions); - BuiltinFunction(self.builtin_function_index_base).sweep_values(compactions); + let Self { + object_index_base, + primitive_object_index_base, + array_prototype, + builtin_function_index_base, + } = self; + compactions.objects.shift_index(object_index_base); compactions .primitive_objects - .shift_index(&mut self.primitive_object_index_base); - self.array_prototype.sweep_values(compactions); + .shift_index(primitive_object_index_base); + array_prototype.sweep_values(compactions); + compactions + .builtin_functions + .shift_index(builtin_function_index_base); } } diff --git a/nova_vm/src/ecmascript/scripts_and_modules/script.rs b/nova_vm/src/ecmascript/scripts_and_modules/script.rs index e3938229e..4d7cd1e76 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules/script.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules/script.rs @@ -166,13 +166,27 @@ pub type ScriptOrErrors = Result>; impl HeapMarkAndSweep for Script { fn mark_values(&self, queues: &mut WorkQueues) { - self.realm.mark_values(queues); - self.source_code.mark_values(queues); + let Self { + realm, + ecmascript_code: _, + loaded_modules: _, + host_defined: _, + source_code, + } = self; + realm.mark_values(queues); + source_code.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.realm.sweep_values(compactions); - self.source_code.sweep_values(compactions); + let Self { + realm, + ecmascript_code: _, + loaded_modules: _, + host_defined: _, + source_code, + } = self; + realm.sweep_values(compactions); + source_code.sweep_values(compactions); } } @@ -284,11 +298,15 @@ pub fn script_evaluation(agent: &mut Agent, script: Script) -> JsResult { // 13. If result.[[Type]] is normal, then let result: JsResult = if result.is_ok() { - let exe = Executable::compile_script(agent, script); + let bytecode = Executable::compile_script(agent, script); // 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). - Vm::execute(agent, &exe, None).into_js_result() + let result = Vm::execute(agent, bytecode, None).into_js_result(); + // SAFETY: The bytecode is not accessible by anyone and no one will try + // to re-run it. + unsafe { bytecode.try_drop(agent) }; + result } else { Err(result.err().unwrap()) }; diff --git a/nova_vm/src/ecmascript/scripts_and_modules/source_code.rs b/nova_vm/src/ecmascript/scripts_and_modules/source_code.rs index b2625a314..bd00c8755 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules/source_code.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules/source_code.rs @@ -179,11 +179,19 @@ impl CreateHeapData for Heap { impl HeapMarkAndSweep for SourceCodeHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.source.mark_values(queues); + let Self { + source, + allocator: _, + } = self; + source.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.source.sweep_values(compactions); + let Self { + source, + allocator: _, + } = self; + source.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/syntax_directed_operations/function_definitions.rs b/nova_vm/src/ecmascript/syntax_directed_operations/function_definitions.rs index cb5b30528..4855bf631 100644 --- a/nova_vm/src/ecmascript/syntax_directed_operations/function_definitions.rs +++ b/nova_vm/src/ecmascript/syntax_directed_operations/function_definitions.rs @@ -2,8 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use std::ptr::NonNull; - use crate::{ ecmascript::{ abstract_operations::operations_on_objects::define_property_or_throw, @@ -270,16 +268,12 @@ pub(crate) fn evaluate_function_body( //function_declaration_instantiation(agent, function_object, arguments_list)?; // 2. Return ? Evaluation of FunctionStatementList. let exe = if let Some(exe) = agent[function_object].compiled_bytecode { - // SAFETY: exe is a non-null pointer pointing to an initialized - // Executable that cannot have a live mutable reference to it. - unsafe { exe.as_ref() } + exe } else { let data = CompileFunctionBodyData::new(agent, function_object); - let exe = Box::new(Executable::compile_function_body(agent, data)); - let exe = NonNull::from(Box::leak(exe)); + let exe = Executable::compile_function_body(agent, data); agent[function_object].compiled_bytecode = Some(exe); - // SAFETY: Same as above, only more. - unsafe { exe.as_ref() } + exe }; Vm::execute(agent, exe, Some(arguments_list.0)).into_js_result() } @@ -301,16 +295,12 @@ pub(crate) fn evaluate_async_function_body( // 4. Else, // a. Perform AsyncFunctionStart(promiseCapability, FunctionBody). let exe = if let Some(exe) = agent[function_object].compiled_bytecode { - // SAFETY: exe is a non-null pointer pointing to an initialized - // Executable that cannot have a live mutable reference to it. - unsafe { exe.as_ref() } + exe } else { let data = CompileFunctionBodyData::new(agent, function_object); - let exe = Box::new(Executable::compile_function_body(agent, data)); - let exe = NonNull::from(Box::leak(exe)); + let exe = Executable::compile_function_body(agent, data); agent[function_object].compiled_bytecode = Some(exe); - // SAFETY: Same as above, only more. - unsafe { exe.as_ref() } + exe }; // AsyncFunctionStart will run the function until it returns, throws or gets suspended with diff --git a/nova_vm/src/ecmascript/types/language/bigint/data.rs b/nova_vm/src/ecmascript/types/language/bigint/data.rs index 1cc2d60db..68d040ab6 100644 --- a/nova_vm/src/ecmascript/types/language/bigint/data.rs +++ b/nova_vm/src/ecmascript/types/language/bigint/data.rs @@ -12,8 +12,12 @@ pub struct BigIntHeapData { impl HeapMarkAndSweep for BigIntHeapData { #[inline(always)] - fn mark_values(&self, _queues: &mut WorkQueues) {} + fn mark_values(&self, _queues: &mut WorkQueues) { + let Self { data: _ } = self; + } #[inline(always)] - fn sweep_values(&mut self, _compactions: &CompactionLists) {} + fn sweep_values(&mut self, _compactions: &CompactionLists) { + let Self { data: _ } = self; + } } diff --git a/nova_vm/src/ecmascript/types/language/function/data.rs b/nova_vm/src/ecmascript/types/language/function/data.rs index 2cf8af264..7b482b0e2 100644 --- a/nova_vm/src/ecmascript/types/language/function/data.rs +++ b/nova_vm/src/ecmascript/types/language/function/data.rs @@ -2,8 +2,6 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -use std::ptr::NonNull; - use oxc_span::Span; use crate::{ @@ -68,7 +66,7 @@ pub struct BuiltinConstructorHeapData { /// Base. pub(crate) is_derived: bool, /// Stores the compiled bytecode of class field initializers. - pub(crate) compiled_initializer_bytecode: Option>, + pub(crate) compiled_initializer_bytecode: Option, /// ### \[\[Environment]] /// /// This is required for class field initializers. @@ -87,38 +85,14 @@ pub struct BuiltinConstructorHeapData { pub(crate) source_code: SourceCode, } -// SAFETY: We promise not to ever mutate the Executable, especially not from -// foreign threads. -unsafe impl Send for BuiltinConstructorHeapData {} - -impl Drop for BuiltinConstructorHeapData { - fn drop(&mut self) { - if let Some(exe) = self.compiled_initializer_bytecode.take() { - // SAFETY: No references to this compiled bytecode should exist as - // otherwise we should not have been garbage collected. - drop(unsafe { Box::from_raw(exe.as_ptr()) }); - } - } -} - #[derive(Debug)] pub struct ECMAScriptFunctionHeapData { pub(crate) object_index: Option, pub(crate) length: u8, pub(crate) ecmascript_function: ECMAScriptFunctionObjectHeapData, /// Stores the compiled bytecode of an ECMAScript function. - pub(crate) compiled_bytecode: Option>, + pub(crate) compiled_bytecode: Option, pub(crate) name: Option, } unsafe impl Send for ECMAScriptFunctionHeapData {} - -impl Drop for ECMAScriptFunctionHeapData { - fn drop(&mut self) { - if let Some(exe) = self.compiled_bytecode.take() { - // SAFETY: No references to this compiled bytecode should exist as - // otherwise we should not have been garbage collected. - drop(unsafe { Box::from_raw(exe.as_ptr()) }); - } - } -} diff --git a/nova_vm/src/ecmascript/types/language/number/data.rs b/nova_vm/src/ecmascript/types/language/number/data.rs index 87540d509..065ed5e25 100644 --- a/nova_vm/src/ecmascript/types/language/number/data.rs +++ b/nova_vm/src/ecmascript/types/language/number/data.rs @@ -23,7 +23,11 @@ impl From for f64 { } impl HeapMarkAndSweep for NumberHeapData { - fn mark_values(&self, _queues: &mut WorkQueues) {} + fn mark_values(&self, _queues: &mut WorkQueues) { + let Self { data: _ } = self; + } - fn sweep_values(&mut self, _compactions: &CompactionLists) {} + fn sweep_values(&mut self, _compactions: &CompactionLists) { + let Self { data: _ } = self; + } } diff --git a/nova_vm/src/ecmascript/types/language/object/data.rs b/nova_vm/src/ecmascript/types/language/object/data.rs index 39115b0a7..9cb2a06d9 100644 --- a/nova_vm/src/ecmascript/types/language/object/data.rs +++ b/nova_vm/src/ecmascript/types/language/object/data.rs @@ -51,14 +51,27 @@ impl ObjectHeapData { impl HeapMarkAndSweep for ObjectHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.keys.mark_values(queues); - self.values.mark_values(queues); - self.prototype.mark_values(queues); + let Self { + extensible: _, + prototype, + keys, + values, + } = self; + + keys.mark_values(queues); + values.mark_values(queues); + prototype.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.keys.sweep_values(compactions); - self.values.sweep_values(compactions); - self.prototype.sweep_values(compactions); + let Self { + extensible: _, + prototype, + keys, + values, + } = self; + keys.sweep_values(compactions); + values.sweep_values(compactions); + prototype.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/types/language/string/data.rs b/nova_vm/src/ecmascript/types/language/string/data.rs index e12796c4a..d393783ca 100644 --- a/nova_vm/src/ecmascript/types/language/string/data.rs +++ b/nova_vm/src/ecmascript/types/language/string/data.rs @@ -242,7 +242,17 @@ impl StringHeapData { } impl HeapMarkAndSweep for StringHeapData { - fn mark_values(&self, _queues: &mut WorkQueues) {} + fn mark_values(&self, _queues: &mut WorkQueues) { + let Self { + data: _, + mapping: _, + } = self; + } - fn sweep_values(&mut self, _compactions: &CompactionLists) {} + fn sweep_values(&mut self, _compactions: &CompactionLists) { + let Self { + data: _, + mapping: _, + } = self; + } } diff --git a/nova_vm/src/ecmascript/types/language/symbol/data.rs b/nova_vm/src/ecmascript/types/language/symbol/data.rs index b7d965151..455d51d6a 100644 --- a/nova_vm/src/ecmascript/types/language/symbol/data.rs +++ b/nova_vm/src/ecmascript/types/language/symbol/data.rs @@ -14,10 +14,12 @@ pub struct SymbolHeapData { impl HeapMarkAndSweep for SymbolHeapData { fn mark_values(&self, queues: &mut WorkQueues) { - self.descriptor.mark_values(queues); + let Self { descriptor } = self; + descriptor.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.descriptor.sweep_values(compactions); + let Self { descriptor } = self; + descriptor.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/types/spec/reference.rs b/nova_vm/src/ecmascript/types/spec/reference.rs index 584079b6b..50cf11383 100644 --- a/nova_vm/src/ecmascript/types/spec/reference.rs +++ b/nova_vm/src/ecmascript/types/spec/reference.rs @@ -315,15 +315,27 @@ pub(crate) enum Base { impl HeapMarkAndSweep for Reference { fn mark_values(&self, queues: &mut WorkQueues) { - self.base.mark_values(queues); - self.referenced_name.mark_values(queues); - self.this_value.mark_values(queues); + let Self { + base, + referenced_name, + strict: _, + this_value, + } = self; + base.mark_values(queues); + referenced_name.mark_values(queues); + this_value.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.base.sweep_values(compactions); - self.referenced_name.sweep_values(compactions); - self.this_value.sweep_values(compactions); + let Self { + base, + referenced_name, + strict: _, + this_value, + } = self; + base.sweep_values(compactions); + referenced_name.sweep_values(compactions); + this_value.sweep_values(compactions); } } diff --git a/nova_vm/src/engine/bytecode.rs b/nova_vm/src/engine/bytecode.rs index 2d7f96317..64bf889c7 100644 --- a/nova_vm/src/engine/bytecode.rs +++ b/nova_vm/src/engine/bytecode.rs @@ -2,14 +2,17 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +mod bytecode_compiler; mod executable; mod instructions; pub(super) mod iterator; mod vm; +pub(crate) use bytecode_compiler::{ + is_reference, CompileContext, CompileEvaluation, NamedEvaluationParameter, +}; pub(crate) use executable::{ - is_reference, CompileContext, CompileEvaluation, Executable, FunctionExpression, IndexType, - NamedEvaluationParameter, SendableRef, + Executable, ExecutableHeapData, FunctionExpression, IndexType, SendableRef, }; pub(crate) use instructions::{Instruction, InstructionIter}; pub(crate) use vm::{instanceof_operator, ExecutionResult, SuspendedVm, Vm}; diff --git a/nova_vm/src/engine/bytecode/bytecode_compiler.rs b/nova_vm/src/engine/bytecode/bytecode_compiler.rs new file mode 100644 index 000000000..a4fe144aa --- /dev/null +++ b/nova_vm/src/engine/bytecode/bytecode_compiler.rs @@ -0,0 +1,2922 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +mod class_definition_evaluation; +mod for_in_of_statement; +mod function_declaration_instantiation; + +use super::{ + executable::ArrowFunctionExpression, Executable, ExecutableHeapData, FunctionExpression, + Instruction, SendableRef, +}; +use crate::{ + ecmascript::{ + builtins::regexp::reg_exp_create, + execution::Agent, + syntax_directed_operations::{ + function_definitions::{CompileFunctionBodyData, ContainsExpression}, + scope_analysis::{LexicallyScopedDeclaration, LexicallyScopedDeclarations}, + }, + types::{BigInt, IntoValue, Number, PropertyKey, String, Value, BUILTIN_STRING_MEMORY}, + }, + heap::CreateHeapData, +}; +use num_traits::Num; +use oxc_ast::{ + ast::{self, BindingPattern, BindingRestElement, CallExpression, NewExpression, Statement}, + syntax_directed_operations::BoundNames, +}; +use oxc_span::Atom; +use oxc_syntax::operator::{BinaryOperator, UnaryOperator}; + +pub type IndexType = u16; + +#[derive(Debug, Clone, Copy)] +pub(crate) enum NamedEvaluationParameter { + /// Name is in the result register + Result, + /// Name is at the top of the stack + Stack, + /// Name is in the reference register + Reference, + /// Name is at the top of the reference stack + ReferenceStack, +} + +pub(crate) struct CompileContext<'agent> { + pub(crate) agent: &'agent mut Agent, + /// Instructions being built + instructions: Vec, + /// Constants being built + constants: Vec, + /// Function expressions being built + function_expressions: Vec, + /// Arrow function expressions being built + arrow_function_expressions: Vec, + class_initializer_bytecodes: Vec<(Option, bool)>, + /// NamedEvaluation name parameter + name_identifier: Option, + /// If true, indicates that all bindings being created are lexical. + /// + /// Otherwise, all bindings being created are variable scoped. + lexical_binding_state: bool, + /// `continue;` statement jumps that were present in the current loop. + current_continue: Option>, + /// `break;` statement jumps that were present in the current loop. + current_break: Option>, + /// `?.` chain jumps that were present in a chain expression. + optional_chains: Option>, + /// In a `(a?.b)?.()` chain the evaluation of `(a?.b)` must be considered a + /// reference. + is_call_optional_chain_this: bool, +} + +impl CompileContext<'_> { + pub(super) fn new(agent: &'_ mut Agent) -> CompileContext<'_> { + CompileContext { + agent, + instructions: Vec::new(), + constants: Vec::new(), + function_expressions: Vec::new(), + arrow_function_expressions: Vec::new(), + class_initializer_bytecodes: Vec::new(), + name_identifier: None, + lexical_binding_state: false, + current_continue: None, + current_break: None, + optional_chains: None, + is_call_optional_chain_this: false, + } + } + + /// Compile a class static field with an optional initializer into the + /// current context. + pub(crate) fn compile_class_static_field( + &mut self, + property_key: &ast::PropertyKey<'_>, + value: &Option>, + ) { + let identifier = match property_key { + ast::PropertyKey::StaticIdentifier(identifier_name) => { + String::from_str(self.agent, identifier_name.name.as_str()) + } + ast::PropertyKey::PrivateIdentifier(_private_identifier) => todo!(), + ast::PropertyKey::BooleanLiteral(_boolean_literal) => todo!(), + ast::PropertyKey::NullLiteral(_null_literal) => todo!(), + ast::PropertyKey::NumericLiteral(_numeric_literal) => todo!(), + ast::PropertyKey::BigIntLiteral(_big_int_literal) => todo!(), + ast::PropertyKey::RegExpLiteral(_reg_exp_literal) => todo!(), + ast::PropertyKey::StringLiteral(_string_literal) => todo!(), + ast::PropertyKey::TemplateLiteral(_template_literal) => todo!(), + _ => unreachable!(), + }; + // Turn the static name to a 'this' property access. + self.add_instruction(Instruction::ResolveThisBinding); + self.add_instruction_with_identifier( + Instruction::EvaluatePropertyAccessWithIdentifierKey, + identifier, + ); + if let Some(value) = value { + // Minor optimisation: We do not need to push and pop the + // reference if we know we're not using the reference stack. + let is_literal = value.is_literal(); + if !is_literal { + self.add_instruction(Instruction::PushReference); + } + value.compile(self); + if is_reference(value) { + self.add_instruction(Instruction::GetValue); + } + if !is_literal { + self.add_instruction(Instruction::PopReference); + } + } else { + // Same optimisation is unconditionally valid here. + self.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); + } + self.add_instruction(Instruction::PutValue); + } + + /// Compile a class computed field with an optional initializer into the + /// current context. + pub(crate) fn compile_class_computed_field( + &mut self, + property_key_id: String, + value: &Option>, + ) { + // Resolve 'this' into the stack. + self.add_instruction(Instruction::ResolveThisBinding); + self.add_instruction(Instruction::Load); + // Resolve the static computed key ID to the actual computed key value. + self.add_instruction_with_identifier(Instruction::ResolveBinding, property_key_id); + // Store the computed key value as the result. + self.add_instruction(Instruction::GetValue); + // Evaluate access to 'this' with the computed key. + self.add_instruction(Instruction::EvaluatePropertyAccessWithExpressionKey); + if let Some(value) = value { + // Minor optimisation: We do not need to push and pop the + // reference if we know we're not using the reference stack. + let is_literal = value.is_literal(); + if !is_literal { + self.add_instruction(Instruction::PushReference); + } + value.compile(self); + if is_reference(value) { + self.add_instruction(Instruction::GetValue); + } + if !is_literal { + self.add_instruction(Instruction::PopReference); + } + } else { + // Same optimisation is unconditionally valid here. + self.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); + } + self.add_instruction(Instruction::PutValue); + } + + /// Compile a function body into the current context. + /// + /// This is useful when the function body is part of a larger whole, namely + /// with class constructors. + pub(crate) fn compile_function_body(&mut self, data: CompileFunctionBodyData<'_>) { + if self.agent.options.print_internals { + eprintln!(); + eprintln!("=== Compiling Function ==="); + eprintln!(); + } + + function_declaration_instantiation::instantiation( + self, + data.params, + data.body, + data.is_strict, + data.is_lexical, + ); + + // SAFETY: Script referred by the Function uniquely owns the Program + // and the body buffer does not move under any circumstances during + // heap operations. + let body: &[Statement] = unsafe { std::mem::transmute(data.body.statements.as_slice()) }; + + self.compile_statements(body); + } + + pub(super) fn compile_statements(&mut self, body: &[Statement]) { + let iter = body.iter(); + + for stmt in iter { + stmt.compile(self); + } + } + + pub(super) fn do_implicit_return(&mut self) { + if self.instructions.last() != Some(&Instruction::Return.as_u8()) { + // If code did not end with a return statement, add it manually + self.add_instruction(Instruction::Return); + } + } + + pub(super) fn finish(self) -> Executable { + self.agent.heap.create(ExecutableHeapData { + instructions: self.instructions.into_boxed_slice(), + constants: self.constants.into_boxed_slice(), + function_expressions: self.function_expressions.into_boxed_slice(), + arrow_function_expressions: self.arrow_function_expressions.into_boxed_slice(), + class_initializer_bytecodes: self.class_initializer_bytecodes.into_boxed_slice(), + }) + } + + pub(crate) fn create_identifier(&mut self, atom: &Atom<'_>) -> String { + let existing = self.constants.iter().find_map(|constant| { + if let Ok(existing_identifier) = String::try_from(*constant) { + if existing_identifier.as_str(self.agent) == atom.as_str() { + Some(existing_identifier) + } else { + None + } + } else { + None + } + }); + if let Some(existing) = existing { + existing + } else { + String::from_str(self.agent, atom.as_str()) + } + } + + fn peek_last_instruction(&self) -> Option { + for ele in self.instructions.iter().rev() { + if *ele == Instruction::ExitDeclarativeEnvironment.as_u8() { + // Not a "real" instruction + continue; + } + return Some(*ele); + } + None + } + + fn _push_instruction(&mut self, instruction: Instruction) { + self.instructions + .push(unsafe { std::mem::transmute::(instruction) }); + } + + fn add_instruction(&mut self, instruction: Instruction) { + debug_assert_eq!(instruction.argument_count(), 0); + debug_assert!( + !instruction.has_constant_index() + && !instruction.has_function_expression_index() + && !instruction.has_identifier_index() + ); + self._push_instruction(instruction); + } + + fn add_instruction_with_jump_slot(&mut self, instruction: Instruction) -> JumpIndex { + debug_assert_eq!(instruction.argument_count(), 1); + debug_assert!(instruction.has_jump_slot()); + self._push_instruction(instruction); + self.add_jump_index() + } + + fn add_jump_instruction_to_index(&mut self, instruction: Instruction, jump_index: JumpIndex) { + debug_assert_eq!(instruction.argument_count(), 1); + debug_assert!(instruction.has_jump_slot()); + self._push_instruction(instruction); + self.add_index(jump_index.index); + } + + fn get_jump_index_to_here(&self) -> JumpIndex { + JumpIndex { + index: self.instructions.len(), + } + } + + fn add_constant(&mut self, constant: Value) -> usize { + let duplicate = self + .constants + .iter() + .enumerate() + .find(|item| item.1.eq(&constant)) + .map(|(idx, _)| idx); + + duplicate.unwrap_or_else(|| { + let index = self.constants.len(); + self.constants.push(constant); + index + }) + } + + fn add_identifier(&mut self, identifier: String) -> usize { + let duplicate = self + .constants + .iter() + .enumerate() + .find(|item| String::try_from(*item.1) == Ok(identifier)) + .map(|(idx, _)| idx); + + duplicate.unwrap_or_else(|| { + let index = self.constants.len(); + self.constants.push(identifier.into_value()); + index + }) + } + + fn add_instruction_with_immediate(&mut self, instruction: Instruction, immediate: usize) { + debug_assert_eq!(instruction.argument_count(), 1); + self._push_instruction(instruction); + self.add_index(immediate); + } + + fn add_instruction_with_constant( + &mut self, + instruction: Instruction, + constant: impl Into, + ) { + debug_assert_eq!(instruction.argument_count(), 1); + debug_assert!(instruction.has_constant_index()); + self._push_instruction(instruction); + let constant = self.add_constant(constant.into()); + self.add_index(constant); + } + + fn add_instruction_with_identifier(&mut self, instruction: Instruction, identifier: String) { + debug_assert_eq!(instruction.argument_count(), 1); + debug_assert!(instruction.has_identifier_index()); + self._push_instruction(instruction); + let identifier = self.add_identifier(identifier); + self.add_index(identifier); + } + + fn add_instruction_with_identifier_and_constant( + &mut self, + instruction: Instruction, + identifier: String, + constant: impl Into, + ) { + debug_assert_eq!(instruction.argument_count(), 2); + debug_assert!(instruction.has_identifier_index() && instruction.has_constant_index()); + self._push_instruction(instruction); + let identifier = self.add_identifier(identifier); + self.add_index(identifier); + let constant = self.add_constant(constant.into()); + self.add_index(constant); + } + + fn add_instruction_with_immediate_and_immediate( + &mut self, + instruction: Instruction, + immediate1: usize, + immediate2: usize, + ) { + debug_assert_eq!(instruction.argument_count(), 2); + self._push_instruction(instruction); + self.add_index(immediate1); + self.add_index(immediate2) + } + + fn add_index(&mut self, index: usize) { + let index = IndexType::try_from(index).expect("Immediate value is too large"); + let bytes: [u8; 2] = index.to_ne_bytes(); + self.instructions.extend_from_slice(&bytes); + } + + fn add_instruction_with_function_expression( + &mut self, + instruction: Instruction, + function_expression: FunctionExpression, + ) { + debug_assert_eq!(instruction.argument_count(), 1); + debug_assert!(instruction.has_function_expression_index()); + self._push_instruction(instruction); + self.function_expressions.push(function_expression); + let index = self.function_expressions.len() - 1; + self.add_index(index); + } + + /// Add an Instruction that takes a function expression and an immediate + /// as its bytecode parameters. + /// + /// Returns the function expression's index. + fn add_instruction_with_function_expression_and_immediate( + &mut self, + instruction: Instruction, + function_expression: FunctionExpression, + immediate: usize, + ) -> IndexType { + debug_assert_eq!(instruction.argument_count(), 2); + debug_assert!(instruction.has_function_expression_index()); + self._push_instruction(instruction); + self.function_expressions.push(function_expression); + let index = self.function_expressions.len() - 1; + self.add_index(index); + self.add_index(immediate); + // Note: add_index would have panicked if this was not a lossless + // conversion. + index as IndexType + } + + fn add_arrow_function_expression( + &mut self, + arrow_function_expression: ArrowFunctionExpression, + ) { + let instruction = Instruction::InstantiateArrowFunctionExpression; + debug_assert_eq!(instruction.argument_count(), 1); + debug_assert!(instruction.has_function_expression_index()); + self._push_instruction(instruction); + self.arrow_function_expressions + .push(arrow_function_expression); + let index = self.arrow_function_expressions.len() - 1; + self.add_index(index); + } + + fn add_jump_index(&mut self) -> JumpIndex { + self.add_index(0); + JumpIndex { + index: self.instructions.len() - std::mem::size_of::(), + } + } + + fn set_jump_target(&mut self, source: JumpIndex, target: JumpIndex) { + assert!(target.index < IndexType::MAX as usize); + let bytes: [u8; 2] = (target.index as IndexType).to_ne_bytes(); + self.instructions[source.index] = bytes[0]; + self.instructions[source.index + 1] = bytes[1]; + } + + fn set_jump_target_here(&mut self, jump: JumpIndex) { + self.set_jump_target( + jump, + JumpIndex { + index: self.instructions.len(), + }, + ); + } +} + +#[derive(Debug, Clone)] +#[repr(transparent)] +pub(crate) struct JumpIndex { + pub(crate) index: usize, +} + +pub(crate) trait CompileEvaluation { + fn compile(&self, ctx: &mut CompileContext); +} + +pub(crate) fn is_reference(expression: &ast::Expression) -> bool { + match expression { + ast::Expression::Identifier(_) + | ast::Expression::ComputedMemberExpression(_) + | ast::Expression::StaticMemberExpression(_) + | ast::Expression::PrivateFieldExpression(_) + | ast::Expression::Super(_) => true, + ast::Expression::ParenthesizedExpression(parenthesized) => { + is_reference(&parenthesized.expression) + } + _ => false, + } +} + +fn is_chain_expression(expression: &ast::Expression) -> bool { + match expression { + ast::Expression::ChainExpression(_) => true, + ast::Expression::ParenthesizedExpression(parenthesized) => { + is_chain_expression(&parenthesized.expression) + } + _ => false, + } +} + +impl CompileEvaluation for ast::NumericLiteral<'_> { + fn compile(&self, ctx: &mut CompileContext) { + let constant = ctx.agent.heap.create(self.value); + ctx.add_instruction_with_constant(Instruction::StoreConstant, constant); + } +} + +impl CompileEvaluation for ast::BooleanLiteral { + fn compile(&self, ctx: &mut CompileContext) { + ctx.add_instruction_with_constant(Instruction::StoreConstant, self.value); + } +} + +impl CompileEvaluation for ast::BigIntLiteral<'_> { + fn compile(&self, ctx: &mut CompileContext) { + // Drop out the trailing 'n' from BigInt literals. + let last_index = self.raw.len() - 1; + let (big_int_str, radix) = match self.base { + oxc_syntax::number::BigintBase::Decimal => (&self.raw.as_str()[..last_index], 10), + oxc_syntax::number::BigintBase::Binary => (&self.raw.as_str()[2..last_index], 2), + oxc_syntax::number::BigintBase::Octal => (&self.raw.as_str()[2..last_index], 8), + oxc_syntax::number::BigintBase::Hex => (&self.raw.as_str()[2..last_index], 16), + }; + let constant = BigInt::from_num_bigint( + ctx.agent, + num_bigint::BigInt::from_str_radix(big_int_str, radix).unwrap(), + ); + ctx.add_instruction_with_constant(Instruction::StoreConstant, constant); + } +} + +impl CompileEvaluation for ast::NullLiteral { + fn compile(&self, ctx: &mut CompileContext) { + ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Null); + } +} + +impl CompileEvaluation for ast::StringLiteral<'_> { + fn compile(&self, ctx: &mut CompileContext) { + let constant = String::from_str(ctx.agent, self.value.as_str()); + ctx.add_instruction_with_constant(Instruction::StoreConstant, constant); + } +} + +impl CompileEvaluation for ast::IdentifierReference<'_> { + fn compile(&self, ctx: &mut CompileContext) { + let identifier = String::from_str(ctx.agent, self.name.as_str()); + ctx.add_instruction_with_identifier(Instruction::ResolveBinding, identifier); + } +} + +impl CompileEvaluation for ast::BindingIdentifier<'_> { + fn compile(&self, ctx: &mut CompileContext) { + let identifier = String::from_str(ctx.agent, self.name.as_str()); + ctx.add_instruction_with_identifier(Instruction::ResolveBinding, identifier); + } +} + +impl CompileEvaluation for ast::UnaryExpression<'_> { + /// ### [13.5 Unary Operators](https://tc39.es/ecma262/#sec-unary-operators) + fn compile(&self, ctx: &mut CompileContext) { + match self.operator { + // 13.5.5 Unary - Operator + // https://tc39.es/ecma262/#sec-unary-minus-operator-runtime-semantics-evaluation + // UnaryExpression : - UnaryExpression + UnaryOperator::UnaryNegation => { + // 1. Let expr be ? Evaluation of UnaryExpression. + self.argument.compile(ctx); + + // 2. Let oldValue be ? ToNumeric(? GetValue(expr)). + if is_reference(&self.argument) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::ToNumeric); + + // 3. If oldValue is a Number, then + // a. Return Number::unaryMinus(oldValue). + // 4. Else, + // a. Assert: oldValue is a BigInt. + // b. Return BigInt::unaryMinus(oldValue). + ctx.add_instruction(Instruction::UnaryMinus); + } + // 13.5.4 Unary + Operator + // https://tc39.es/ecma262/#sec-unary-plus-operator + // UnaryExpression : + UnaryExpression + UnaryOperator::UnaryPlus => { + // 1. Let expr be ? Evaluation of UnaryExpression. + self.argument.compile(ctx); + + // 2. Return ? ToNumber(? GetValue(expr)). + if is_reference(&self.argument) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::ToNumber); + } + // 13.5.6 Unary ! Operator + // https://tc39.es/ecma262/#sec-logical-not-operator-runtime-semantics-evaluation + // UnaryExpression : ! UnaryExpression + UnaryOperator::LogicalNot => { + // 1. Let expr be ? Evaluation of UnaryExpression. + self.argument.compile(ctx); + + // 2. Let oldValue be ToBoolean(? GetValue(expr)). + // 3. If oldValue is true, return false. + // 4. Return true. + if is_reference(&self.argument) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::LogicalNot); + } + // 13.5.7 Unary ~ Operator + // https://tc39.es/ecma262/#sec-bitwise-not-operator-runtime-semantics-evaluation + // UnaryExpression : ~ UnaryExpression + UnaryOperator::BitwiseNot => { + // 1. Let expr be ? Evaluation of UnaryExpression. + self.argument.compile(ctx); + + // 2. Let oldValue be ? ToNumeric(? GetValue(expr)). + // 3. If oldValue is a Number, then + // a. Return Number::bitwiseNOT(oldValue). + // 4. Else, + // a. Assert: oldValue is a BigInt. + // b. Return BigInt::bitwiseNOT(oldValue). + if is_reference(&self.argument) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::BitwiseNot); + } + // 13.5.3 The typeof Operator + // UnaryExpression : typeof UnaryExpression + UnaryOperator::Typeof => { + // 1. Let val be ? Evaluation of UnaryExpression. + self.argument.compile(ctx); + // 3. Set val to ? GetValue(val). + ctx.add_instruction(Instruction::Typeof); + } + // 13.5.2 The void operator + // UnaryExpression : void UnaryExpression + UnaryOperator::Void => { + // 1. Let expr be ? Evaluation of UnaryExpression. + self.argument.compile(ctx); + // NOTE: GetValue must be called even though its value is not used because it may have observable side-effects. + // 2. Perform ? GetValue(expr). + if is_reference(&self.argument) { + ctx.add_instruction(Instruction::GetValue); + } + // 3. Return undefined. + ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); + } + // 13.5.1 The delete operator + // https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation + // UnaryExpression : delete UnaryExpression + UnaryOperator::Delete => { + // Let ref be ? Evaluation of UnaryExpression. + self.argument.compile(ctx); + // 2. If ref is not a Reference Record, return true. + if !is_reference(&self.argument) { + ctx.add_instruction_with_constant(Instruction::StoreConstant, true); + return; + } + ctx.add_instruction(Instruction::Delete); + } + } + } +} + +impl CompileEvaluation for ast::BinaryExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + // 1. Let lref be ? Evaluation of leftOperand. + self.left.compile(ctx); + + // 2. Let lval be ? GetValue(lref). + if is_reference(&self.left) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::Load); + + // 3. Let rref be ? Evaluation of rightOperand. + self.right.compile(ctx); + + // 4. Let rval be ? GetValue(rref). + if is_reference(&self.right) { + ctx.add_instruction(Instruction::GetValue); + } + + match self.operator { + BinaryOperator::LessThan => { + ctx.add_instruction(Instruction::LessThan); + } + BinaryOperator::LessEqualThan => { + ctx.add_instruction(Instruction::LessThanEquals); + } + BinaryOperator::GreaterThan => { + ctx.add_instruction(Instruction::GreaterThan); + } + BinaryOperator::GreaterEqualThan => { + ctx.add_instruction(Instruction::GreaterThanEquals); + } + BinaryOperator::StrictEquality => { + ctx.add_instruction(Instruction::IsStrictlyEqual); + } + BinaryOperator::StrictInequality => { + ctx.add_instruction(Instruction::IsStrictlyEqual); + ctx.add_instruction(Instruction::LogicalNot); + } + BinaryOperator::Equality => { + ctx.add_instruction(Instruction::IsLooselyEqual); + } + BinaryOperator::Inequality => { + ctx.add_instruction(Instruction::IsLooselyEqual); + ctx.add_instruction(Instruction::LogicalNot); + } + BinaryOperator::In => { + ctx.add_instruction(Instruction::HasProperty); + } + BinaryOperator::Instanceof => { + ctx.add_instruction(Instruction::InstanceofOperator); + } + _ => { + // 5. Return ? ApplyStringOrNumericBinaryOperator(lval, opText, rval). + ctx.add_instruction(Instruction::ApplyStringOrNumericBinaryOperator( + self.operator, + )); + } + } + } +} + +impl CompileEvaluation for ast::LogicalExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + self.left.compile(ctx); + if is_reference(&self.left) { + ctx.add_instruction(Instruction::GetValue); + } + // We store the left value on the stack, because we'll need to restore + // it later. + ctx.add_instruction(Instruction::LoadCopy); + + match self.operator { + oxc_syntax::operator::LogicalOperator::Or => { + ctx.add_instruction(Instruction::LogicalNot); + } + oxc_syntax::operator::LogicalOperator::And => {} + oxc_syntax::operator::LogicalOperator::Coalesce => { + ctx.add_instruction(Instruction::IsNullOrUndefined); + } + } + let jump_to_return_left = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); + + // We're returning the right expression, so we discard the left value + // at the top of the stack. + ctx.add_instruction(Instruction::Store); + + self.right.compile(ctx); + if is_reference(&self.right) { + ctx.add_instruction(Instruction::GetValue); + } + let jump_to_end = ctx.add_instruction_with_jump_slot(Instruction::Jump); + + ctx.set_jump_target_here(jump_to_return_left); + // Return the result of the left expression. + ctx.add_instruction(Instruction::Store); + ctx.set_jump_target_here(jump_to_end); + } +} + +impl CompileEvaluation for ast::AssignmentExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + // 1. Let lref be ? Evaluation of LeftHandSideExpression. + let is_identifier_ref = match &self.left { + ast::AssignmentTarget::ArrayAssignmentTarget(_) => todo!(), + ast::AssignmentTarget::AssignmentTargetIdentifier(identifier) => { + identifier.compile(ctx); + true + } + ast::AssignmentTarget::ComputedMemberExpression(expression) => { + expression.compile(ctx); + false + } + ast::AssignmentTarget::ObjectAssignmentTarget(_) => todo!(), + ast::AssignmentTarget::PrivateFieldExpression(_) => todo!(), + ast::AssignmentTarget::StaticMemberExpression(expression) => { + expression.compile(ctx); + false + } + ast::AssignmentTarget::TSAsExpression(_) + | ast::AssignmentTarget::TSSatisfiesExpression(_) + | ast::AssignmentTarget::TSNonNullExpression(_) + | ast::AssignmentTarget::TSTypeAssertion(_) + | ast::AssignmentTarget::TSInstantiationExpression(_) => unreachable!(), + }; + + if self.operator == oxc_syntax::operator::AssignmentOperator::Assign { + ctx.add_instruction(Instruction::PushReference); + self.right.compile(ctx); + + if is_reference(&self.right) { + ctx.add_instruction(Instruction::GetValue); + } + + ctx.add_instruction(Instruction::LoadCopy); + ctx.add_instruction(Instruction::PopReference); + ctx.add_instruction(Instruction::PutValue); + + // ... Return rval. + ctx.add_instruction(Instruction::Store); + } else if matches!( + self.operator, + oxc_syntax::operator::AssignmentOperator::LogicalAnd + | oxc_syntax::operator::AssignmentOperator::LogicalNullish + | oxc_syntax::operator::AssignmentOperator::LogicalOr + ) { + // 2. Let lval be ? GetValue(lref). + ctx.add_instruction(Instruction::GetValueKeepReference); + ctx.add_instruction(Instruction::PushReference); + // We store the left value on the stack, because we'll need to + // restore it later. + ctx.add_instruction(Instruction::LoadCopy); + + match self.operator { + oxc_syntax::operator::AssignmentOperator::LogicalAnd => { + // 3. Let lbool be ToBoolean(lval). + // Note: We do not directly call ToBoolean: JumpIfNot does. + // 4. If lbool is false, return lval. + } + oxc_syntax::operator::AssignmentOperator::LogicalOr => { + // 3. Let lbool be ToBoolean(lval). + // Note: We do not directly call ToBoolean: JumpIfNot does. + // 4. If lbool is true, return lval. + ctx.add_instruction(Instruction::LogicalNot); + } + oxc_syntax::operator::AssignmentOperator::LogicalNullish => { + // 3. If lval is neither undefined nor null, return lval. + ctx.add_instruction(Instruction::IsNullOrUndefined); + } + _ => unreachable!(), + } + + let jump_to_end = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); + + // We're returning the right expression, so we discard the left + // value at the top of the stack. + ctx.add_instruction(Instruction::Store); + + // 5. If IsAnonymousFunctionDefinition(AssignmentExpression) + // is true and IsIdentifierRef of LeftHandSideExpression is true, + // then + if is_identifier_ref && is_anonymous_function_definition(&self.right) { + // a. Let lhs be the StringValue of LeftHandSideExpression. + // b. Let rval be ? NamedEvaluation of AssignmentExpression with argument lhs. + ctx.name_identifier = Some(NamedEvaluationParameter::ReferenceStack); + self.right.compile(ctx); + } else { + // 6. Else + // a. Let rref be ? Evaluation of AssignmentExpression. + self.right.compile(ctx); + // b. Let rval be ? GetValue(rref). + if is_reference(&self.right) { + ctx.add_instruction(Instruction::GetValue); + } + } + + // 7. Perform ? PutValue(lref, rval). + ctx.add_instruction(Instruction::LoadCopy); + ctx.add_instruction(Instruction::PopReference); + ctx.add_instruction(Instruction::PutValue); + + // 4. ... return lval. + ctx.set_jump_target_here(jump_to_end); + ctx.add_instruction(Instruction::Store); + } else { + // 2. let lval be ? GetValue(lref). + ctx.add_instruction(Instruction::GetValueKeepReference); + ctx.add_instruction(Instruction::Load); + ctx.add_instruction(Instruction::PushReference); + // 3. Let rref be ? Evaluation of AssignmentExpression. + self.right.compile(ctx); + + // 4. Let rval be ? GetValue(rref). + if is_reference(&self.right) { + ctx.add_instruction(Instruction::GetValue); + } + + // 5. Let assignmentOpText be the source text matched by AssignmentOperator. + // 6. Let opText be the sequence of Unicode code points associated with assignmentOpText in the following table: + let op_text = match self.operator { + oxc_syntax::operator::AssignmentOperator::Addition => BinaryOperator::Addition, + oxc_syntax::operator::AssignmentOperator::Subtraction => { + BinaryOperator::Subtraction + } + oxc_syntax::operator::AssignmentOperator::Multiplication => { + BinaryOperator::Multiplication + } + oxc_syntax::operator::AssignmentOperator::Division => BinaryOperator::Division, + oxc_syntax::operator::AssignmentOperator::Remainder => BinaryOperator::Remainder, + oxc_syntax::operator::AssignmentOperator::ShiftLeft => BinaryOperator::ShiftLeft, + oxc_syntax::operator::AssignmentOperator::ShiftRight => BinaryOperator::ShiftRight, + oxc_syntax::operator::AssignmentOperator::ShiftRightZeroFill => { + BinaryOperator::ShiftRightZeroFill + } + oxc_syntax::operator::AssignmentOperator::BitwiseOR => BinaryOperator::BitwiseOR, + oxc_syntax::operator::AssignmentOperator::BitwiseXOR => BinaryOperator::BitwiseXOR, + oxc_syntax::operator::AssignmentOperator::BitwiseAnd => BinaryOperator::BitwiseAnd, + oxc_syntax::operator::AssignmentOperator::Exponential => { + BinaryOperator::Exponential + } + _ => unreachable!(), + }; + // 7. Let r be ? ApplyStringOrNumericBinaryOperator(lval, opText, rval). + ctx.add_instruction(Instruction::ApplyStringOrNumericBinaryOperator(op_text)); + ctx.add_instruction(Instruction::LoadCopy); + // 8. Perform ? PutValue(lref, r). + ctx.add_instruction(Instruction::PopReference); + ctx.add_instruction(Instruction::PutValue); + // 9. Return r. + ctx.add_instruction(Instruction::Store); + } + } +} + +impl CompileEvaluation for ast::ParenthesizedExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + self.expression.compile(ctx); + } +} + +impl CompileEvaluation for ast::ArrowFunctionExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + // CompileContext holds a name identifier for us if this is NamedEvaluation. + let identifier = ctx.name_identifier.take(); + ctx.add_arrow_function_expression(ArrowFunctionExpression { + expression: SendableRef::new(unsafe { + std::mem::transmute::< + &ast::ArrowFunctionExpression<'_>, + &'static ast::ArrowFunctionExpression<'static>, + >(self) + }), + identifier, + }); + } +} + +impl CompileEvaluation for ast::Function<'_> { + fn compile(&self, ctx: &mut CompileContext) { + // CompileContext holds a name identifier for us if this is NamedEvaluation. + let identifier = ctx.name_identifier.take(); + ctx.add_instruction_with_function_expression( + Instruction::InstantiateOrdinaryFunctionExpression, + FunctionExpression { + expression: SendableRef::new(unsafe { + std::mem::transmute::<&ast::Function<'_>, &'static ast::Function<'static>>(self) + }), + identifier, + compiled_bytecode: None, + }, + ); + } +} + +impl CompileEvaluation for ast::ObjectExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + // TODO: Consider preparing the properties onto the stack and creating + // the object with a known size. + ctx.add_instruction(Instruction::ObjectCreate); + for property in self.properties.iter() { + match property { + ast::ObjectPropertyKind::ObjectProperty(prop) => { + let mut is_proto_setter = false; + match &prop.key { + ast::PropertyKey::ArrayExpression(init) => init.compile(ctx), + ast::PropertyKey::ArrowFunctionExpression(init) => init.compile(ctx), + ast::PropertyKey::AssignmentExpression(init) => init.compile(ctx), + ast::PropertyKey::AwaitExpression(init) => init.compile(ctx), + ast::PropertyKey::BigIntLiteral(init) => init.compile(ctx), + ast::PropertyKey::BinaryExpression(init) => init.compile(ctx), + ast::PropertyKey::BooleanLiteral(init) => init.compile(ctx), + ast::PropertyKey::CallExpression(init) => init.compile(ctx), + ast::PropertyKey::ChainExpression(init) => init.compile(ctx), + ast::PropertyKey::ClassExpression(init) => init.compile(ctx), + ast::PropertyKey::ComputedMemberExpression(init) => init.compile(ctx), + ast::PropertyKey::ConditionalExpression(init) => init.compile(ctx), + ast::PropertyKey::FunctionExpression(init) => init.compile(ctx), + ast::PropertyKey::Identifier(init) => init.compile(ctx), + ast::PropertyKey::ImportExpression(init) => init.compile(ctx), + ast::PropertyKey::LogicalExpression(init) => init.compile(ctx), + ast::PropertyKey::MetaProperty(init) => init.compile(ctx), + ast::PropertyKey::NewExpression(init) => init.compile(ctx), + ast::PropertyKey::NullLiteral(init) => init.compile(ctx), + ast::PropertyKey::NumericLiteral(init) => init.compile(ctx), + ast::PropertyKey::ObjectExpression(init) => init.compile(ctx), + ast::PropertyKey::ParenthesizedExpression(init) => init.compile(ctx), + ast::PropertyKey::PrivateFieldExpression(init) => init.compile(ctx), + ast::PropertyKey::PrivateIdentifier(_init) => todo!(), + ast::PropertyKey::PrivateInExpression(init) => init.compile(ctx), + ast::PropertyKey::RegExpLiteral(init) => init.compile(ctx), + ast::PropertyKey::SequenceExpression(init) => init.compile(ctx), + ast::PropertyKey::StaticIdentifier(id) => { + if id.name == "__proto__" { + if prop.kind == ast::PropertyKind::Init && !prop.shorthand { + // If property key is "__proto__" then we + // should dispatch a SetPrototype instruction. + is_proto_setter = true; + } else { + ctx.add_instruction_with_constant( + Instruction::StoreConstant, + BUILTIN_STRING_MEMORY.__proto__, + ); + } + } else { + let identifier = PropertyKey::from_str(ctx.agent, &id.name); + ctx.add_instruction_with_constant( + Instruction::StoreConstant, + identifier, + ); + } + } + ast::PropertyKey::StaticMemberExpression(init) => init.compile(ctx), + ast::PropertyKey::StringLiteral(init) => { + let identifier = PropertyKey::from_str(ctx.agent, &init.value); + ctx.add_instruction_with_constant( + Instruction::StoreConstant, + identifier, + ); + } + ast::PropertyKey::Super(_) => unreachable!(), + ast::PropertyKey::TaggedTemplateExpression(init) => init.compile(ctx), + ast::PropertyKey::TemplateLiteral(init) => init.compile(ctx), + ast::PropertyKey::ThisExpression(init) => init.compile(ctx), + ast::PropertyKey::UnaryExpression(init) => init.compile(ctx), + ast::PropertyKey::UpdateExpression(init) => init.compile(ctx), + ast::PropertyKey::YieldExpression(init) => init.compile(ctx), + ast::PropertyKey::JSXElement(_) + | ast::PropertyKey::JSXFragment(_) + | ast::PropertyKey::TSAsExpression(_) + | ast::PropertyKey::TSSatisfiesExpression(_) + | ast::PropertyKey::TSTypeAssertion(_) + | ast::PropertyKey::TSNonNullExpression(_) + | ast::PropertyKey::TSInstantiationExpression(_) => unreachable!(), + } + if let Some(prop_key_expression) = prop.key.as_expression() { + if is_reference(prop_key_expression) { + assert!(!is_proto_setter); + ctx.add_instruction(Instruction::GetValue); + } + } + if !is_proto_setter { + // Prototype setter doesn't need the key. + ctx.add_instruction(Instruction::Load); + } + match prop.kind { + ast::PropertyKind::Init => { + if !is_proto_setter && is_anonymous_function_definition(&prop.value) { + ctx.name_identifier = Some(NamedEvaluationParameter::Stack); + } + prop.value.compile(ctx); + if is_reference(&prop.value) { + ctx.add_instruction(Instruction::GetValue); + } + // 7. If isProtoSetter is true, then + if is_proto_setter { + // a. If propValue is an Object or propValue is null, then + // i. Perform ! object.[[SetPrototypeOf]](propValue). + // b. Return unused. + ctx.add_instruction(Instruction::ObjectSetPrototype); + } else { + ctx.add_instruction(Instruction::ObjectDefineProperty); + } + } + ast::PropertyKind::Get | ast::PropertyKind::Set => { + let is_get = prop.kind == ast::PropertyKind::Get; + let ast::Expression::FunctionExpression(function_expression) = + &prop.value + else { + unreachable!() + }; + ctx.add_instruction_with_function_expression_and_immediate( + if is_get { + Instruction::ObjectDefineGetter + } else { + Instruction::ObjectDefineSetter + }, + FunctionExpression { + expression: SendableRef::new(unsafe { + std::mem::transmute::< + &ast::Function<'_>, + &'static ast::Function<'static>, + >( + function_expression + ) + }), + identifier: None, + compiled_bytecode: None, + }, + // enumerable: true, + true.into(), + ); + } + } + } + ast::ObjectPropertyKind::SpreadProperty(spread) => { + spread.argument.compile(ctx); + if is_reference(&spread.argument) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::CopyDataProperties); + } + } + } + // 3. Return obj + ctx.add_instruction(Instruction::Store); + } +} + +impl CompileEvaluation for ast::ArrayExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + let elements_min_count = self.elements.len(); + ctx.add_instruction_with_immediate(Instruction::ArrayCreate, elements_min_count); + for ele in &self.elements { + match ele { + ast::ArrayExpressionElement::SpreadElement(spread) => { + spread.argument.compile(ctx); + if is_reference(&spread.argument) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::GetIteratorSync); + + let iteration_start = ctx.get_jump_index_to_here(); + let iteration_end = + ctx.add_instruction_with_jump_slot(Instruction::IteratorStepValue); + ctx.add_instruction(Instruction::ArrayPush); + ctx.add_jump_instruction_to_index(Instruction::Jump, iteration_start); + ctx.set_jump_target_here(iteration_end); + } + ast::ArrayExpressionElement::Elision(_) => { + ctx.add_instruction(Instruction::ArrayElision); + } + _ => { + let expression = ele.to_expression(); + expression.compile(ctx); + if is_reference(expression) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::ArrayPush); + } + } + } + ctx.add_instruction(Instruction::Store); + } +} + +fn compile_arguments(arguments: &[ast::Argument], ctx: &mut CompileContext) -> usize { + // If the arguments don't contain the spread operator, then we can know the + // number of arguments at compile-time and we can pass it as an argument to + // the call instruction. + // Otherwise, the first time we find a spread operator, we need to start + // tracking the number of arguments in the compiled bytecode. We store this + // number in the result value, and we pass u16::MAX to the call instruction. + let mut known_num_arguments = Some(0 as IndexType); + + for argument in arguments { + // If known_num_arguments is None, the stack contains the number of + // arguments, followed by the arguments. + if let ast::Argument::SpreadElement(spread) = argument { + if let Some(num_arguments) = known_num_arguments.take() { + ctx.add_instruction_with_constant(Instruction::LoadConstant, num_arguments); + } + + spread.argument.compile(ctx); + if is_reference(&spread.argument) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::GetIteratorSync); + + let iteration_start = ctx.get_jump_index_to_here(); + let iteration_end = ctx.add_instruction_with_jump_slot(Instruction::IteratorStepValue); + // result: value; stack: [num, ...args] + ctx.add_instruction(Instruction::LoadStoreSwap); + // result: num; stack: [value, ...args] + ctx.add_instruction(Instruction::Increment); + // result: num + 1; stack: [value, ...args] + ctx.add_instruction(Instruction::Load); + // stack: [num + 1, value, ...args] + ctx.add_jump_instruction_to_index(Instruction::Jump, iteration_start); + ctx.set_jump_target_here(iteration_end); + } else { + let expression = argument.to_expression(); + expression.compile(ctx); + if is_reference(expression) { + ctx.add_instruction(Instruction::GetValue); + } + if let Some(num_arguments) = known_num_arguments.as_mut() { + ctx.add_instruction(Instruction::Load); + // stack: [value, ...args] + + if *num_arguments < IndexType::MAX - 1 { + *num_arguments += 1; + } else { + // If we overflow, we switch to tracking the number on the + // result value. + debug_assert_eq!(*num_arguments, IndexType::MAX - 1); + known_num_arguments = None; + ctx.add_instruction_with_constant( + Instruction::LoadConstant, + Value::from(IndexType::MAX), + ); + // stack: [num + 1, value, ...args] + } + } else { + // result: value; stack: [num, ...args] + ctx.add_instruction(Instruction::LoadStoreSwap); + // result: num; stack: [value, ...args] + ctx.add_instruction(Instruction::Increment); + // result: num + 1; stack: [value, ...args] + ctx.add_instruction(Instruction::Load); + // stack: [num + 1, value, ...args] + } + } + } + + if let Some(num_arguments) = known_num_arguments { + assert_ne!(num_arguments, IndexType::MAX); + num_arguments as usize + } else { + // stack: [num, ...args] + ctx.add_instruction(Instruction::Store); + // result: num; stack: [...args] + IndexType::MAX as usize + } +} + +impl CompileEvaluation for CallExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + // Direct eval + if !self.optional { + if let ast::Expression::Identifier(ident) = &self.callee { + if ident.name == "eval" { + let num_arguments = compile_arguments(&self.arguments, ctx); + ctx.add_instruction_with_immediate(Instruction::DirectEvalCall, num_arguments); + return; + } + } + } + + // 1. Let ref be ? Evaluation of CallExpression. + ctx.is_call_optional_chain_this = is_chain_expression(&self.callee); + let is_super_call = matches!(self.callee, ast::Expression::Super(_)); + let need_pop_reference = if is_super_call { + // Note: There is nothing to do with super calls here. + false + } else { + self.callee.compile(ctx); + if is_reference(&self.callee) { + // 2. Let func be ? GetValue(ref). + ctx.add_instruction(Instruction::GetValueKeepReference); + // Optimization: If we know arguments is empty, we don't need to + // worry about arguments evaluation clobbering our function's this + // reference. + if !self.arguments.is_empty() { + ctx.add_instruction(Instruction::PushReference); + true + } else { + false + } + } else { + false + } + }; + + if self.optional { + // Optional Chains + + // Load copy of func to stack. + ctx.add_instruction(Instruction::LoadCopy); + // 3. If func is either undefined or null, then + ctx.add_instruction(Instruction::IsNullOrUndefined); + // a. Return undefined + + // To return undefined we jump over the rest of the call handling. + let jump_over_call = if need_pop_reference { + // If we need to pop the reference stack, then we must do it + // here before we go to the nullish case handling. + // Note the inverted jump condition here! + let jump_to_call = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); + // Now we're in our local nullish case handling. + // First we pop our reference. + ctx.add_instruction(Instruction::PopReference); + // And now we're ready to jump over the call. + let jump_over_call = ctx.add_instruction_with_jump_slot(Instruction::Jump); + // But if we're jumping to call then we need to land here. + ctx.set_jump_target_here(jump_to_call); + jump_over_call + } else { + ctx.add_instruction_with_jump_slot(Instruction::JumpIfTrue) + }; + // Register our jump slot to the chain nullish case handling. + ctx.optional_chains.as_mut().unwrap().push(jump_over_call); + } else if !is_super_call { + ctx.add_instruction(Instruction::Load); + } + // If we're in an optional chain, we need to pluck it out while we're + // compiling the parameters: They do not join our chain. + let optional_chain = ctx.optional_chains.take(); + let num_arguments = compile_arguments(&self.arguments, ctx); + // After we're done with compiling parameters we go back into the chain. + if let Some(optional_chain) = optional_chain { + ctx.optional_chains.replace(optional_chain); + } + + if is_super_call { + ctx.add_instruction_with_immediate(Instruction::EvaluateSuper, num_arguments); + } else { + if need_pop_reference { + ctx.add_instruction(Instruction::PopReference); + } + ctx.add_instruction_with_immediate(Instruction::EvaluateCall, num_arguments); + } + } +} + +impl CompileEvaluation for NewExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + self.callee.compile(ctx); + if is_reference(&self.callee) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::Load); + + let num_arguments = compile_arguments(&self.arguments, ctx); + ctx.add_instruction_with_immediate(Instruction::EvaluateNew, num_arguments); + } +} + +impl CompileEvaluation for ast::MemberExpression<'_> { + /// ### [13.3.2 Property Accessors](https://tc39.es/ecma262/#sec-property-accessors) + fn compile(&self, ctx: &mut CompileContext) { + match self { + ast::MemberExpression::ComputedMemberExpression(x) => x.compile(ctx), + ast::MemberExpression::StaticMemberExpression(x) => x.compile(ctx), + ast::MemberExpression::PrivateFieldExpression(x) => x.compile(ctx), + } + } +} + +impl CompileEvaluation for ast::ComputedMemberExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + // 1. Let baseReference be ? Evaluation of MemberExpression. + self.object.compile(ctx); + + // 2. Let baseValue be ? GetValue(baseReference). + if is_reference(&self.object) { + ctx.add_instruction(Instruction::GetValue); + } + + if self.optional { + // Optional Chains + + // Load copy of baseValue to stack. + ctx.add_instruction(Instruction::LoadCopy); + // 3. If baseValue is either undefined or null, then + ctx.add_instruction(Instruction::IsNullOrUndefined); + // a. Return undefined + + // To return undefined we jump over the property access. + let jump_over_property_access = + ctx.add_instruction_with_jump_slot(Instruction::JumpIfTrue); + + // Register our jump slot to the chain nullish case handling. + ctx.optional_chains + .as_mut() + .unwrap() + .push(jump_over_property_access); + } else { + ctx.add_instruction(Instruction::Load); + } + + // If we're in an optional chain, we need to pluck it out while we're + // compiling the member expression: They do not join our chain. + let optional_chain = ctx.optional_chains.take(); + // 4. Return ? EvaluatePropertyAccessWithExpressionKey(baseValue, Expression, strict). + self.expression.compile(ctx); + if is_reference(&self.expression) { + ctx.add_instruction(Instruction::GetValue); + } + // After we're done with compiling the member expression we go back + // into the chain. + if let Some(optional_chain) = optional_chain { + ctx.optional_chains.replace(optional_chain); + } + + ctx.add_instruction(Instruction::EvaluatePropertyAccessWithExpressionKey); + } +} + +impl CompileEvaluation for ast::StaticMemberExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + // 1. Let baseReference be ? Evaluation of MemberExpression. + self.object.compile(ctx); + + // 2. Let baseValue be ? GetValue(baseReference). + if is_reference(&self.object) { + ctx.add_instruction(Instruction::GetValue); + } + + if self.optional { + // Optional Chains + + // Load copy of baseValue to stack. + ctx.add_instruction(Instruction::LoadCopy); + // 3. If baseValue is either undefined or null, then + ctx.add_instruction(Instruction::IsNullOrUndefined); + // a. Return undefined + + // To return undefined we jump over the property access. + let jump_over_property_access = + ctx.add_instruction_with_jump_slot(Instruction::JumpIfTrue); + + // Register our jump slot to the chain nullish case handling. + ctx.optional_chains + .as_mut() + .unwrap() + .push(jump_over_property_access); + + // Return copy of baseValue from stack if it is not. + ctx.add_instruction(Instruction::Store); + } + + // 4. Return EvaluatePropertyAccessWithIdentifierKey(baseValue, IdentifierName, strict). + let identifier = String::from_str(ctx.agent, self.property.name.as_str()); + ctx.add_instruction_with_identifier( + Instruction::EvaluatePropertyAccessWithIdentifierKey, + identifier, + ); + } +} + +impl CompileEvaluation for ast::PrivateFieldExpression<'_> { + fn compile(&self, _ctx: &mut CompileContext) { + todo!() + } +} + +impl CompileEvaluation for ast::AwaitExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + // 1. Let exprRef be ? Evaluation of UnaryExpression. + self.argument.compile(ctx); + // 2. Let value be ? GetValue(exprRef). + if is_reference(&self.argument) { + ctx.add_instruction(Instruction::GetValue); + } + // 3. Return ? Await(value). + ctx.add_instruction(Instruction::Await); + } +} + +impl CompileEvaluation for ast::ChainExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + // It's possible that we're compiling a ChainExpression inside a call + // that is itself in a ChainExpression. We will drop into the previous + // chain in this case. + let installed_own_chains = if ctx.optional_chains.is_none() { + // We prepare for at least two chains to exist. One chain is often + // enough but two is a bit safer. Three is rare. + ctx.optional_chains.replace(Vec::with_capacity(2)); + true + } else { + false + }; + let need_get_value = match self.expression { + ast::ChainElement::CallExpression(ref call) => { + call.compile(ctx); + false + } + ast::ChainElement::ComputedMemberExpression(ref call) => { + call.compile(ctx); + true + } + ast::ChainElement::StaticMemberExpression(ref call) => { + call.compile(ctx); + true + } + ast::ChainElement::PrivateFieldExpression(ref call) => { + call.compile(ctx); + true + } + }; + // If chain succeeded, we come here and should jump over the nullish + // case handling. + if need_get_value { + // If we handled a member or field expression, we need to get its + // value. However, there's a chance that we cannot just throw away + // the reference. If the result of the chain expression is going to + // be used in a (potentially optional) call expression then we need + // both its value and its reference. + if ctx.is_call_optional_chain_this { + ctx.is_call_optional_chain_this = false; + ctx.add_instruction(Instruction::GetValueKeepReference); + } else { + ctx.add_instruction(Instruction::GetValue); + } + } + if installed_own_chains { + let jump_over_return_undefined = ctx.add_instruction_with_jump_slot(Instruction::Jump); + let own_chains = ctx.optional_chains.take().unwrap(); + for jump_to_return_undefined in own_chains { + ctx.set_jump_target_here(jump_to_return_undefined); + } + // All optional chains come here with a copy of their null or + // undefined baseValue on the stack. Pop it off. + ctx.add_instruction(Instruction::Store); + // Replace any possible null with undefined. + ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); + ctx.set_jump_target_here(jump_over_return_undefined); + } + } +} + +impl CompileEvaluation for ast::ConditionalExpression<'_> { + /// ## [13.14 Conditional Operator ( ? : )](https://tc39.es/ecma262/#sec-conditional-operator) + /// ### [13.14.1 Runtime Semantics: Evaluation](https://tc39.es/ecma262/#sec-conditional-operator-runtime-semantics-evaluation) + fn compile(&self, ctx: &mut CompileContext) { + // 1. Let lref be ? Evaluation of ShortCircuitExpression. + self.test.compile(ctx); + // 2. Let lval be ToBoolean(? GetValue(lref)). + if is_reference(&self.test) { + ctx.add_instruction(Instruction::GetValue); + } + // Jump over first AssignmentExpression (consequent) if test fails. + // Note: JumpIfNot performs ToBoolean from above step. + let jump_to_second = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); + // 3. If lval is true, then + // a. Let trueRef be ? Evaluation of the first AssignmentExpression. + self.consequent.compile(ctx); + // b. Return ? GetValue(trueRef). + if is_reference(&self.consequent) { + ctx.add_instruction(Instruction::GetValue); + } + // Jump over second AssignmentExpression (alternate). + let jump_over_second = ctx.add_instruction_with_jump_slot(Instruction::Jump); + // 4. Else, + ctx.set_jump_target_here(jump_to_second); + // a. Let falseRef be ? Evaluation of the second AssignmentExpression. + self.alternate.compile(ctx); + // b. Return ? GetValue(falseRef). + if is_reference(&self.alternate) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.set_jump_target_here(jump_over_second); + } +} + +impl CompileEvaluation for ast::ImportExpression<'_> { + fn compile(&self, _ctx: &mut CompileContext) { + todo!() + } +} + +impl CompileEvaluation for ast::MetaProperty<'_> { + fn compile(&self, _ctx: &mut CompileContext) { + todo!() + } +} + +impl CompileEvaluation for ast::PrivateInExpression<'_> { + fn compile(&self, _ctx: &mut CompileContext) { + todo!() + } +} + +impl CompileEvaluation for ast::RegExpLiteral<'_> { + fn compile(&self, ctx: &mut CompileContext) { + let pattern = match self.regex.pattern { + ast::RegExpPattern::Raw(pattern) => pattern, + ast::RegExpPattern::Invalid(pattern) => pattern, + // We probably shouldn't be getting parsed RegExps? + ast::RegExpPattern::Pattern(_) => unreachable!(), + }; + let pattern = String::from_str(ctx.agent, pattern); + let regexp = + reg_exp_create(ctx.agent, pattern.into_value(), Some(self.regex.flags)).unwrap(); + ctx.add_instruction_with_constant(Instruction::StoreConstant, regexp); + } +} + +impl CompileEvaluation for ast::SequenceExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + for expr in &self.expressions { + expr.compile(ctx); + } + } +} + +impl CompileEvaluation for ast::Super { + fn compile(&self, _ctx: &mut CompileContext) { + todo!() + } +} + +impl CompileEvaluation for ast::TaggedTemplateExpression<'_> { + fn compile(&self, _ctx: &mut CompileContext) { + todo!() + } +} + +impl CompileEvaluation for ast::TemplateLiteral<'_> { + fn compile(&self, ctx: &mut CompileContext) { + if self.is_no_substitution_template() { + let constant = String::from_str( + ctx.agent, + self.quasi() + .as_ref() + .expect("Invalid escape sequence in template literal") + .as_str(), + ); + ctx.add_instruction_with_constant(Instruction::StoreConstant, constant); + } else { + let mut count = 0; + let mut quasis = self.quasis.as_slice(); + let mut expressions = self.expressions.as_slice(); + while let Some((head, rest)) = quasis.split_first() { + quasis = rest; + // 1. Let head be the TV of TemplateHead as defined in 12.9.6. + let head = + String::from_str(ctx.agent, head.value.cooked.as_ref().unwrap().as_str()); + ctx.add_instruction_with_constant(Instruction::LoadConstant, head); + count += 1; + if let Some((expression, rest)) = expressions.split_first() { + expressions = rest; + // 2. Let subRef be ? Evaluation of Expression. + expression.compile(ctx); + if is_reference(expression) { + // 3. Let sub be ? GetValue(subRef). + ctx.add_instruction(Instruction::GetValue); + } + // 4. Let middle be ? ToString(sub). + // Note: This is done by StringConcat. + ctx.add_instruction(Instruction::Load); + count += 1; + } + // 5. Let tail be ? Evaluation of TemplateSpans. + } + // 6. Return the string-concatenation of head, middle, and tail. + ctx.add_instruction_with_immediate(Instruction::StringConcat, count); + } + } +} + +impl CompileEvaluation for ast::ThisExpression { + fn compile(&self, ctx: &mut CompileContext) { + ctx.add_instruction(Instruction::ResolveThisBinding); + } +} + +impl CompileEvaluation for ast::YieldExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + if self.delegate { + todo!("`yield*` is not yet supported"); + } + if let Some(arg) = &self.argument { + // YieldExpression : yield AssignmentExpression + // 1. Let exprRef be ? Evaluation of AssignmentExpression. + arg.compile(ctx); + // 2. Let value be ? GetValue(exprRef). + if is_reference(arg) { + ctx.add_instruction(Instruction::GetValue); + } + } else { + // YieldExpression : yield + // 1. Return ? Yield(undefined). + ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); + } + // 3. Return ? Yield(value). + ctx.add_instruction(Instruction::Yield); + } +} + +impl CompileEvaluation for ast::Expression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + match self { + ast::Expression::ArrayExpression(x) => x.compile(ctx), + ast::Expression::ArrowFunctionExpression(x) => x.compile(ctx), + ast::Expression::AssignmentExpression(x) => x.compile(ctx), + ast::Expression::AwaitExpression(x) => x.compile(ctx), + ast::Expression::BigIntLiteral(x) => x.compile(ctx), + ast::Expression::BinaryExpression(x) => x.compile(ctx), + ast::Expression::BooleanLiteral(x) => x.compile(ctx), + ast::Expression::CallExpression(x) => x.compile(ctx), + ast::Expression::ChainExpression(x) => x.compile(ctx), + ast::Expression::ClassExpression(x) => x.compile(ctx), + ast::Expression::ComputedMemberExpression(x) => x.compile(ctx), + ast::Expression::ConditionalExpression(x) => x.compile(ctx), + ast::Expression::FunctionExpression(x) => x.compile(ctx), + ast::Expression::Identifier(x) => x.compile(ctx), + ast::Expression::ImportExpression(x) => x.compile(ctx), + ast::Expression::LogicalExpression(x) => x.compile(ctx), + ast::Expression::MetaProperty(x) => x.compile(ctx), + ast::Expression::NewExpression(x) => x.compile(ctx), + ast::Expression::NullLiteral(x) => x.compile(ctx), + ast::Expression::NumericLiteral(x) => x.compile(ctx), + ast::Expression::ObjectExpression(x) => x.compile(ctx), + ast::Expression::ParenthesizedExpression(x) => x.compile(ctx), + ast::Expression::PrivateFieldExpression(x) => x.compile(ctx), + ast::Expression::PrivateInExpression(x) => x.compile(ctx), + ast::Expression::RegExpLiteral(x) => x.compile(ctx), + ast::Expression::SequenceExpression(x) => x.compile(ctx), + ast::Expression::StaticMemberExpression(x) => x.compile(ctx), + ast::Expression::StringLiteral(x) => x.compile(ctx), + ast::Expression::Super(x) => x.compile(ctx), + ast::Expression::TaggedTemplateExpression(x) => x.compile(ctx), + ast::Expression::TemplateLiteral(x) => x.compile(ctx), + ast::Expression::ThisExpression(x) => x.compile(ctx), + ast::Expression::UnaryExpression(x) => x.compile(ctx), + ast::Expression::UpdateExpression(x) => x.compile(ctx), + ast::Expression::YieldExpression(x) => x.compile(ctx), + ast::Expression::JSXElement(_) + | ast::Expression::JSXFragment(_) + | ast::Expression::TSAsExpression(_) + | ast::Expression::TSSatisfiesExpression(_) + | ast::Expression::TSTypeAssertion(_) + | ast::Expression::TSNonNullExpression(_) + | ast::Expression::TSInstantiationExpression(_) => unreachable!(), + } + } +} + +impl CompileEvaluation for ast::UpdateExpression<'_> { + fn compile(&self, ctx: &mut CompileContext) { + match &self.argument { + ast::SimpleAssignmentTarget::AssignmentTargetIdentifier(x) => x.compile(ctx), + ast::SimpleAssignmentTarget::ComputedMemberExpression(x) => x.compile(ctx), + ast::SimpleAssignmentTarget::PrivateFieldExpression(_) => todo!(), + ast::SimpleAssignmentTarget::StaticMemberExpression(x) => x.compile(ctx), + ast::SimpleAssignmentTarget::TSAsExpression(_) + | ast::SimpleAssignmentTarget::TSInstantiationExpression(_) + | ast::SimpleAssignmentTarget::TSNonNullExpression(_) + | ast::SimpleAssignmentTarget::TSSatisfiesExpression(_) + | ast::SimpleAssignmentTarget::TSTypeAssertion(_) => unreachable!(), + } + ctx.add_instruction(Instruction::GetValueKeepReference); + if !self.prefix { + // The return value of postfix increment/decrement is the value + // after ToNumeric. + ctx.add_instruction(Instruction::ToNumeric); + ctx.add_instruction(Instruction::LoadCopy); + } + match self.operator { + oxc_syntax::operator::UpdateOperator::Increment => { + ctx.add_instruction(Instruction::Increment); + } + oxc_syntax::operator::UpdateOperator::Decrement => { + ctx.add_instruction(Instruction::Decrement); + } + } + if self.prefix { + ctx.add_instruction(Instruction::LoadCopy); + } + ctx.add_instruction(Instruction::PutValue); + ctx.add_instruction(Instruction::Store); + } +} + +impl CompileEvaluation for ast::ExpressionStatement<'_> { + /// ### [14.5.1 Runtime Semantics: Evaluation](https://tc39.es/ecma262/#sec-expression-statement-runtime-semantics-evaluation) + /// `ExpressionStatement : Expression ;` + fn compile(&self, ctx: &mut CompileContext) { + // 1. Let exprRef be ? Evaluation of Expression. + self.expression.compile(ctx); + if is_reference(&self.expression) { + // 2. Return ? GetValue(exprRef). + ctx.add_instruction(Instruction::GetValue); + } + } +} + +impl CompileEvaluation for ast::ReturnStatement<'_> { + fn compile(&self, ctx: &mut CompileContext) { + if let Some(expr) = &self.argument { + expr.compile(ctx); + if is_reference(expr) { + ctx.add_instruction(Instruction::GetValue); + } + } else { + ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); + } + ctx.add_instruction(Instruction::Return); + } +} + +impl CompileEvaluation for ast::IfStatement<'_> { + fn compile(&self, ctx: &mut CompileContext) { + // if (test) consequent + self.test.compile(ctx); + if is_reference(&self.test) { + ctx.add_instruction(Instruction::GetValue); + } + // jump over consequent if test fails + let jump_to_else = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); + self.consequent.compile(ctx); + let mut jump_over_else: Option = None; + if let Some(alternate) = &self.alternate { + // Optimisation: If the an else branch exists, the consequent + // branch needs to end in a jump over it. But if the consequent + // branch ends in a return statement that jump becomes unnecessary. + if ctx.peek_last_instruction() != Some(Instruction::Return.as_u8()) { + jump_over_else = Some(ctx.add_instruction_with_jump_slot(Instruction::Jump)); + } + + // Jump to else-branch when if test fails. + ctx.set_jump_target_here(jump_to_else); + alternate.compile(ctx); + } else { + // Jump over if-branch when if test fails. + ctx.set_jump_target_here(jump_to_else); + } + + // Jump over else-branch at the end of if-branch if necessary. + // (See optimisation above for when it is not needed.) + if let Some(jump_over_else) = jump_over_else { + ctx.set_jump_target_here(jump_over_else); + } + } +} + +impl CompileEvaluation for ast::ArrayPattern<'_> { + fn compile(&self, ctx: &mut CompileContext) { + if self.elements.is_empty() && self.rest.is_none() { + return; + } + + ctx.add_instruction(Instruction::Store); + ctx.add_instruction(Instruction::GetIteratorSync); + + if !self.contains_expression() { + simple_array_pattern( + ctx, + self.elements.iter().map(Option::as_ref), + self.rest.as_deref(), + self.elements.len(), + ctx.lexical_binding_state, + ); + } else { + complex_array_pattern( + ctx, + self.elements.iter().map(Option::as_ref), + self.rest.as_deref(), + ctx.lexical_binding_state, + ); + } + } +} + +fn simple_array_pattern<'a, 'b, I>( + ctx: &mut CompileContext, + elements: I, + rest: Option<&BindingRestElement>, + num_elements: usize, + has_environment: bool, +) where + 'b: 'a, + I: Iterator>>, +{ + ctx.add_instruction_with_immediate_and_immediate( + Instruction::BeginSimpleArrayBindingPattern, + num_elements, + has_environment.into(), + ); + + for ele in elements { + let Some(ele) = ele else { + ctx.add_instruction(Instruction::BindingPatternSkip); + continue; + }; + match &ele.kind { + ast::BindingPatternKind::BindingIdentifier(identifier) => { + let identifier_string = ctx.create_identifier(&identifier.name); + ctx.add_instruction_with_identifier( + Instruction::BindingPatternBind, + identifier_string, + ) + } + ast::BindingPatternKind::ObjectPattern(pattern) => { + ctx.add_instruction(Instruction::BindingPatternGetValue); + simple_object_pattern(pattern, ctx, has_environment); + } + ast::BindingPatternKind::ArrayPattern(pattern) => { + ctx.add_instruction(Instruction::BindingPatternGetValue); + simple_array_pattern( + ctx, + pattern.elements.iter().map(Option::as_ref), + pattern.rest.as_deref(), + pattern.elements.len(), + has_environment, + ); + } + ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), + } + } + + if let Some(rest) = rest { + match &rest.argument.kind { + ast::BindingPatternKind::BindingIdentifier(identifier) => { + let identifier_string = ctx.create_identifier(&identifier.name); + ctx.add_instruction_with_identifier( + Instruction::BindingPatternBindRest, + identifier_string, + ); + } + ast::BindingPatternKind::ObjectPattern(pattern) => { + ctx.add_instruction(Instruction::BindingPatternGetRestValue); + simple_object_pattern(pattern, ctx, has_environment); + } + ast::BindingPatternKind::ArrayPattern(pattern) => { + ctx.add_instruction(Instruction::BindingPatternGetRestValue); + simple_array_pattern( + ctx, + pattern.elements.iter().map(Option::as_ref), + pattern.rest.as_deref(), + pattern.elements.len(), + has_environment, + ); + } + ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), + } + } else { + ctx.add_instruction(Instruction::FinishBindingPattern); + } +} + +fn complex_array_pattern<'a, 'b, I>( + ctx: &mut CompileContext, + elements: I, + rest: Option<&BindingRestElement>, + has_environment: bool, +) where + 'b: 'a, + I: Iterator>>, +{ + for ele in elements { + ctx.add_instruction(Instruction::IteratorStepValueOrUndefined); + + let Some(ele) = ele else { + continue; + }; + + let binding_pattern = match &ele.kind { + ast::BindingPatternKind::AssignmentPattern(pattern) => { + // Run the initializer if the result value is undefined. + ctx.add_instruction(Instruction::LoadCopy); + ctx.add_instruction(Instruction::IsUndefined); + let jump_slot = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); + ctx.add_instruction(Instruction::Store); + if is_anonymous_function_definition(&pattern.right) { + if let ast::BindingPatternKind::BindingIdentifier(identifier) = + &pattern.left.kind + { + let identifier_string = ctx.create_identifier(&identifier.name); + ctx.add_instruction_with_constant( + Instruction::StoreConstant, + identifier_string, + ); + ctx.name_identifier = Some(NamedEvaluationParameter::Result); + } + } + pattern.right.compile(ctx); + ctx.name_identifier = None; + if is_reference(&pattern.right) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::Load); + ctx.set_jump_target_here(jump_slot); + ctx.add_instruction(Instruction::Store); + + &pattern.left.kind + } + _ => &ele.kind, + }; + + match binding_pattern { + ast::BindingPatternKind::BindingIdentifier(identifier) => { + let identifier_string = ctx.create_identifier(&identifier.name); + ctx.add_instruction_with_identifier(Instruction::ResolveBinding, identifier_string); + if !has_environment { + ctx.add_instruction(Instruction::PutValue); + } else { + ctx.add_instruction(Instruction::InitializeReferencedBinding); + } + } + ast::BindingPatternKind::ObjectPattern(pattern) => { + ctx.add_instruction(Instruction::Load); + pattern.compile(ctx); + } + ast::BindingPatternKind::ArrayPattern(pattern) => { + ctx.add_instruction(Instruction::Load); + pattern.compile(ctx); + } + ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), + } + } + + if let Some(rest) = rest { + ctx.add_instruction(Instruction::IteratorRestIntoArray); + match &rest.argument.kind { + ast::BindingPatternKind::BindingIdentifier(identifier) => { + let identifier_string = ctx.create_identifier(&identifier.name); + ctx.add_instruction_with_identifier(Instruction::ResolveBinding, identifier_string); + if !has_environment { + ctx.add_instruction(Instruction::PutValue); + } else { + ctx.add_instruction(Instruction::InitializeReferencedBinding); + } + } + ast::BindingPatternKind::ObjectPattern(pattern) => { + ctx.add_instruction(Instruction::Load); + pattern.compile(ctx); + } + ast::BindingPatternKind::ArrayPattern(pattern) => { + ctx.add_instruction(Instruction::Load); + pattern.compile(ctx); + } + ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), + } + } else { + ctx.add_instruction(Instruction::IteratorClose); + } +} + +impl CompileEvaluation for ast::ObjectPattern<'_> { + fn compile(&self, ctx: &mut CompileContext) { + if !self.contains_expression() { + simple_object_pattern(self, ctx, ctx.lexical_binding_state); + } else { + complex_object_pattern(self, ctx, ctx.lexical_binding_state); + } + } +} + +fn simple_object_pattern( + pattern: &ast::ObjectPattern<'_>, + ctx: &mut CompileContext, + has_environment: bool, +) { + ctx.add_instruction_with_immediate( + Instruction::BeginSimpleObjectBindingPattern, + has_environment.into(), + ); + + for ele in &pattern.properties { + if ele.shorthand { + let ast::PropertyKey::StaticIdentifier(identifier) = &ele.key else { + unreachable!() + }; + assert!(matches!( + &ele.value.kind, + ast::BindingPatternKind::BindingIdentifier(_) + )); + let identifier_string = ctx.create_identifier(&identifier.name); + ctx.add_instruction_with_identifier(Instruction::BindingPatternBind, identifier_string); + } else { + let key_string = match &ele.key { + ast::PropertyKey::StaticIdentifier(identifier) => { + PropertyKey::from_str(ctx.agent, &identifier.name).into_value() + } + ast::PropertyKey::NumericLiteral(literal) => { + let numeric_value = Number::from_f64(ctx.agent, literal.value); + if let Number::Integer(_) = numeric_value { + numeric_value.into_value() + } else { + Number::to_string_radix_10(ctx.agent, numeric_value).into_value() + } + } + ast::PropertyKey::StringLiteral(literal) => { + PropertyKey::from_str(ctx.agent, &literal.value).into_value() + } + _ => unreachable!(), + }; + + match &ele.value.kind { + ast::BindingPatternKind::BindingIdentifier(identifier) => { + let value_identifier_string = ctx.create_identifier(&identifier.name); + ctx.add_instruction_with_identifier_and_constant( + Instruction::BindingPatternBindNamed, + value_identifier_string, + key_string, + ) + } + ast::BindingPatternKind::ObjectPattern(pattern) => { + ctx.add_instruction_with_constant( + Instruction::BindingPatternGetValueNamed, + key_string, + ); + simple_object_pattern(pattern, ctx, has_environment); + } + ast::BindingPatternKind::ArrayPattern(pattern) => { + ctx.add_instruction_with_constant( + Instruction::BindingPatternGetValueNamed, + key_string, + ); + simple_array_pattern( + ctx, + pattern.elements.iter().map(Option::as_ref), + pattern.rest.as_deref(), + pattern.elements.len(), + has_environment, + ); + } + ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), + } + } + } + + if let Some(rest) = &pattern.rest { + match &rest.argument.kind { + ast::BindingPatternKind::BindingIdentifier(identifier) => { + let identifier_string = ctx.create_identifier(&identifier.name); + ctx.add_instruction_with_identifier( + Instruction::BindingPatternBindRest, + identifier_string, + ); + } + _ => unreachable!(), + } + } else { + ctx.add_instruction(Instruction::FinishBindingPattern); + } +} + +fn complex_object_pattern( + object_pattern: &ast::ObjectPattern<'_>, + ctx: &mut CompileContext, + has_environment: bool, +) { + // 8.6.2 Runtime Semantics: BindingInitialization + // BindingPattern : ObjectBindingPattern + // 1. Perform ? RequireObjectCoercible(value). + // NOTE: RequireObjectCoercible throws in the same cases as ToObject, and other operations + // later on (such as GetV) also perform ToObject, so we convert to an object early. + ctx.add_instruction(Instruction::Store); + ctx.add_instruction(Instruction::ToObject); + ctx.add_instruction(Instruction::Load); + + for property in &object_pattern.properties { + match &property.key { + ast::PropertyKey::StaticIdentifier(identifier) => { + ctx.add_instruction(Instruction::Store); + ctx.add_instruction(Instruction::LoadCopy); + let identifier_string = ctx.create_identifier(&identifier.name); + ctx.add_instruction_with_identifier( + Instruction::EvaluatePropertyAccessWithIdentifierKey, + identifier_string, + ); + } + ast::PropertyKey::PrivateIdentifier(_) => todo!(), + _ => { + property.key.to_expression().compile(ctx); + ctx.add_instruction(Instruction::EvaluatePropertyAccessWithExpressionKey); + } + } + if object_pattern.rest.is_some() { + ctx.add_instruction(Instruction::GetValueKeepReference); + ctx.add_instruction(Instruction::PushReference); + } else { + ctx.add_instruction(Instruction::GetValue); + } + + let binding_pattern = match &property.value.kind { + ast::BindingPatternKind::AssignmentPattern(pattern) => { + // Run the initializer if the result value is undefined. + ctx.add_instruction(Instruction::LoadCopy); + ctx.add_instruction(Instruction::IsUndefined); + let jump_slot = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); + ctx.add_instruction(Instruction::Store); + if is_anonymous_function_definition(&pattern.right) { + if let ast::BindingPatternKind::BindingIdentifier(identifier) = + &pattern.left.kind + { + let identifier_string = ctx.create_identifier(&identifier.name); + ctx.add_instruction_with_constant( + Instruction::StoreConstant, + identifier_string, + ); + ctx.name_identifier = Some(NamedEvaluationParameter::Result); + } + } + pattern.right.compile(ctx); + ctx.name_identifier = None; + if is_reference(&pattern.right) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::Load); + ctx.set_jump_target_here(jump_slot); + ctx.add_instruction(Instruction::Store); + + &pattern.left.kind + } + _ => &property.value.kind, + }; + + match binding_pattern { + ast::BindingPatternKind::BindingIdentifier(identifier) => { + let identifier_string = ctx.create_identifier(&identifier.name); + ctx.add_instruction_with_identifier(Instruction::ResolveBinding, identifier_string); + if !has_environment { + ctx.add_instruction(Instruction::PutValue); + } else { + ctx.add_instruction(Instruction::InitializeReferencedBinding); + } + } + ast::BindingPatternKind::ObjectPattern(pattern) => { + ctx.add_instruction(Instruction::Load); + pattern.compile(ctx); + } + ast::BindingPatternKind::ArrayPattern(pattern) => { + ctx.add_instruction(Instruction::Load); + pattern.compile(ctx); + } + ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), + } + } + + if let Some(rest) = &object_pattern.rest { + let ast::BindingPatternKind::BindingIdentifier(identifier) = &rest.argument.kind else { + unreachable!() + }; + + // We have kept the references for all of the properties read in the reference stack, so we + // can now use them to exclude those properties from the rest object. + ctx.add_instruction_with_immediate( + Instruction::CopyDataPropertiesIntoObject, + object_pattern.properties.len(), + ); + + let identifier_string = ctx.create_identifier(&identifier.name); + ctx.add_instruction_with_identifier(Instruction::ResolveBinding, identifier_string); + if !has_environment { + ctx.add_instruction(Instruction::PutValue); + } else { + ctx.add_instruction(Instruction::InitializeReferencedBinding); + } + } else { + // Don't keep the object on the stack. + ctx.add_instruction(Instruction::Store); + } +} + +impl CompileEvaluation for ast::VariableDeclaration<'_> { + fn compile(&self, ctx: &mut CompileContext) { + match self.kind { + // VariableStatement : var VariableDeclarationList ; + ast::VariableDeclarationKind::Var => { + for decl in &self.declarations { + // VariableDeclaration : BindingIdentifier + let Some(init) = &decl.init else { + // 1. Return EMPTY. + continue; + }; + // VariableDeclaration : BindingIdentifier Initializer + + let ast::BindingPatternKind::BindingIdentifier(identifier) = &decl.id.kind + else { + // VariableDeclaration : BindingPattern Initializer + ctx.lexical_binding_state = false; + // 1. Let rhs be ? Evaluation of Initializer. + init.compile(ctx); + // 2. Let rval be ? GetValue(rhs). + if is_reference(init) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::Load); + // 3. Return ? BindingInitialization of BidingPattern with arguments rval and undefined. + match &decl.id.kind { + ast::BindingPatternKind::BindingIdentifier(_) => unreachable!(), + ast::BindingPatternKind::ObjectPattern(pattern) => pattern.compile(ctx), + ast::BindingPatternKind::ArrayPattern(pattern) => pattern.compile(ctx), + ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), + } + return; + }; + + // 1. Let bindingId be StringValue of BindingIdentifier. + // 2. Let lhs be ? ResolveBinding(bindingId). + let identifier_string = String::from_str(ctx.agent, identifier.name.as_str()); + ctx.add_instruction_with_identifier( + Instruction::ResolveBinding, + identifier_string, + ); + ctx.add_instruction(Instruction::PushReference); + + // 3. If IsAnonymousFunctionDefinition(Initializer) is true, then + if is_anonymous_function_definition(init) { + // a. Let value be ? NamedEvaluation of Initializer with argument bindingId. + ctx.name_identifier = Some(NamedEvaluationParameter::ReferenceStack); + init.compile(ctx); + } else { + // 4. Else, + // a. Let rhs be ? Evaluation of Initializer. + init.compile(ctx); + // b. Let value be ? GetValue(rhs). + if is_reference(init) { + ctx.add_instruction(Instruction::GetValue); + } + } + // 5. Perform ? PutValue(lhs, value). + ctx.add_instruction(Instruction::PopReference); + ctx.add_instruction(Instruction::PutValue); + + // 6. Return EMPTY. + // Store Undefined as the result value. + ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); + } + } + ast::VariableDeclarationKind::Let | ast::VariableDeclarationKind::Const => { + for decl in &self.declarations { + let ast::BindingPatternKind::BindingIdentifier(identifier) = &decl.id.kind + else { + ctx.lexical_binding_state = true; + let init = decl.init.as_ref().unwrap(); + + // LexicalBinding : BindingPattern Initializer + // 1. Let rhs be ? Evaluation of Initializer. + init.compile(ctx); + // 2. Let value be ? GetValue(rhs). + if is_reference(init) { + ctx.add_instruction(Instruction::GetValue); + } + // 3. Let env be the running execution context's LexicalEnvironment. + // 4. Return ? BindingInitialization of BindingPattern with arguments value and env. + ctx.add_instruction(Instruction::Load); + match &decl.id.kind { + ast::BindingPatternKind::BindingIdentifier(_) => unreachable!(), + ast::BindingPatternKind::ObjectPattern(pattern) => pattern.compile(ctx), + ast::BindingPatternKind::ArrayPattern(pattern) => pattern.compile(ctx), + ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), + } + return; + }; + + // 1. Let lhs be ! ResolveBinding(StringValue of BindingIdentifier). + let identifier_string = String::from_str(ctx.agent, identifier.name.as_str()); + ctx.add_instruction_with_identifier( + Instruction::ResolveBinding, + identifier_string, + ); + + let Some(init) = &decl.init else { + // LexicalBinding : BindingIdentifier + // 2. Perform ! InitializeReferencedBinding(lhs, undefined). + ctx.add_instruction_with_constant( + Instruction::StoreConstant, + Value::Undefined, + ); + ctx.add_instruction(Instruction::InitializeReferencedBinding); + // 3. Return empty. + ctx.add_instruction_with_constant( + Instruction::StoreConstant, + Value::Undefined, + ); + return; + }; + + // LexicalBinding : BindingIdentifier Initializer + ctx.add_instruction(Instruction::PushReference); + // 3. If IsAnonymousFunctionDefinition(Initializer) is true, then + if is_anonymous_function_definition(init) { + // a. Let value be ? NamedEvaluation of Initializer with argument bindingId. + ctx.name_identifier = Some(NamedEvaluationParameter::ReferenceStack); + init.compile(ctx); + } else { + // 4. Else, + // a. Let rhs be ? Evaluation of Initializer. + init.compile(ctx); + // b. Let value be ? GetValue(rhs). + if is_reference(init) { + ctx.add_instruction(Instruction::GetValue); + } + } + + // 5. Perform ! InitializeReferencedBinding(lhs, value). + ctx.add_instruction(Instruction::PopReference); + ctx.add_instruction(Instruction::InitializeReferencedBinding); + // 6. Return empty. + ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); + } + } + ast::VariableDeclarationKind::Using => todo!(), + ast::VariableDeclarationKind::AwaitUsing => todo!(), + } + } +} + +impl CompileEvaluation for ast::Declaration<'_> { + fn compile(&self, ctx: &mut CompileContext) { + match self { + ast::Declaration::VariableDeclaration(x) => x.compile(ctx), + ast::Declaration::FunctionDeclaration(x) => x.compile(ctx), + other => todo!("{other:?}"), + } + } +} + +impl CompileEvaluation for ast::BlockStatement<'_> { + fn compile(&self, ctx: &mut CompileContext) { + if self.body.is_empty() { + // Block : {} + // 1. Return EMPTY. + return; + } + ctx.add_instruction(Instruction::EnterDeclarativeEnvironment); + // SAFETY: Stupid lifetime transmute. + let body = unsafe { + std::mem::transmute::< + &oxc_allocator::Vec<'_, Statement<'_>>, + &'static oxc_allocator::Vec<'static, Statement<'static>>, + >(&self.body) + }; + body.lexically_scoped_declarations(&mut |decl| { + match decl { + LexicallyScopedDeclaration::Variable(decl) => { + if decl.kind.is_const() { + decl.id.bound_names(&mut |name| { + let identifier = String::from_str(ctx.agent, name.name.as_str()); + ctx.add_instruction_with_identifier( + Instruction::CreateImmutableBinding, + identifier, + ); + }); + } else if decl.kind.is_lexical() { + decl.id.bound_names(&mut |name| { + let identifier = String::from_str(ctx.agent, name.name.as_str()); + ctx.add_instruction_with_identifier( + Instruction::CreateMutableBinding, + identifier, + ); + }); + } + } + LexicallyScopedDeclaration::Function(decl) => { + // TODO: InstantiateFunctionObject and InitializeBinding + decl.bound_names(&mut |name| { + let identifier = String::from_str(ctx.agent, name.name.as_str()); + ctx.add_instruction_with_identifier( + Instruction::CreateMutableBinding, + identifier, + ); + }); + } + LexicallyScopedDeclaration::Class(decl) => { + decl.bound_names(&mut |name| { + let identifier = String::from_str(ctx.agent, name.name.as_str()); + ctx.add_instruction_with_identifier( + Instruction::CreateMutableBinding, + identifier, + ); + }); + } + LexicallyScopedDeclaration::DefaultExport => unreachable!(), + } + }); + for ele in &self.body { + ele.compile(ctx); + } + if ctx.peek_last_instruction() != Some(Instruction::Return.as_u8()) { + // Block did not end in a return so we overwrite the result with undefined. + ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); + } + ctx.add_instruction(Instruction::ExitDeclarativeEnvironment); + } +} + +impl CompileEvaluation for ast::ForStatement<'_> { + fn compile(&self, ctx: &mut CompileContext) { + let previous_continue = ctx.current_continue.replace(vec![]); + let previous_break = ctx.current_break.replace(vec![]); + + let mut per_iteration_lets = vec![]; + let mut is_lexical = false; + + if let Some(init) = &self.init { + match init { + ast::ForStatementInit::ArrayExpression(init) => init.compile(ctx), + ast::ForStatementInit::ArrowFunctionExpression(init) => init.compile(ctx), + ast::ForStatementInit::AssignmentExpression(init) => init.compile(ctx), + ast::ForStatementInit::AwaitExpression(init) => init.compile(ctx), + ast::ForStatementInit::BigIntLiteral(init) => init.compile(ctx), + ast::ForStatementInit::BinaryExpression(init) => init.compile(ctx), + ast::ForStatementInit::BooleanLiteral(init) => init.compile(ctx), + ast::ForStatementInit::CallExpression(init) => init.compile(ctx), + ast::ForStatementInit::ChainExpression(init) => init.compile(ctx), + ast::ForStatementInit::ClassExpression(init) => init.compile(ctx), + ast::ForStatementInit::ComputedMemberExpression(init) => init.compile(ctx), + ast::ForStatementInit::ConditionalExpression(init) => init.compile(ctx), + ast::ForStatementInit::FunctionExpression(init) => init.compile(ctx), + ast::ForStatementInit::Identifier(init) => init.compile(ctx), + ast::ForStatementInit::ImportExpression(init) => init.compile(ctx), + ast::ForStatementInit::LogicalExpression(init) => init.compile(ctx), + ast::ForStatementInit::MetaProperty(init) => init.compile(ctx), + ast::ForStatementInit::NewExpression(init) => init.compile(ctx), + ast::ForStatementInit::NullLiteral(init) => init.compile(ctx), + ast::ForStatementInit::NumericLiteral(init) => init.compile(ctx), + ast::ForStatementInit::ObjectExpression(init) => init.compile(ctx), + ast::ForStatementInit::ParenthesizedExpression(init) => init.compile(ctx), + ast::ForStatementInit::PrivateFieldExpression(init) => init.compile(ctx), + ast::ForStatementInit::PrivateInExpression(init) => init.compile(ctx), + ast::ForStatementInit::RegExpLiteral(init) => init.compile(ctx), + ast::ForStatementInit::SequenceExpression(init) => init.compile(ctx), + ast::ForStatementInit::StaticMemberExpression(init) => init.compile(ctx), + ast::ForStatementInit::StringLiteral(init) => init.compile(ctx), + ast::ForStatementInit::Super(init) => init.compile(ctx), + ast::ForStatementInit::TaggedTemplateExpression(init) => init.compile(ctx), + ast::ForStatementInit::TemplateLiteral(init) => init.compile(ctx), + ast::ForStatementInit::ThisExpression(init) => init.compile(ctx), + ast::ForStatementInit::UnaryExpression(init) => init.compile(ctx), + ast::ForStatementInit::UpdateExpression(init) => init.compile(ctx), + ast::ForStatementInit::VariableDeclaration(init) => { + is_lexical = init.kind.is_lexical(); + if is_lexical { + // 1. Let oldEnv be the running execution context's LexicalEnvironment. + // 2. Let loopEnv be NewDeclarativeEnvironment(oldEnv). + ctx.add_instruction(Instruction::EnterDeclarativeEnvironment); + // 3. Let isConst be IsConstantDeclaration of LexicalDeclaration. + let is_const = init.kind.is_const(); + // 4. Let boundNames be the BoundNames of LexicalDeclaration. + // 5. For each element dn of boundNames, do + // a. If isConst is true, then + if is_const { + init.bound_names(&mut |dn| { + // i. Perform ! loopEnv.CreateImmutableBinding(dn, true). + let identifier = String::from_str(ctx.agent, dn.name.as_str()); + ctx.add_instruction_with_identifier( + Instruction::CreateImmutableBinding, + identifier, + ) + }); + } else { + // b. Else, + // i. Perform ! loopEnv.CreateMutableBinding(dn, false). + init.bound_names(&mut |dn| { + let identifier = String::from_str(ctx.agent, dn.name.as_str()); + // 9. If isConst is false, let perIterationLets + // be boundNames; otherwise let perIterationLets + // be a new empty List. + per_iteration_lets.push(identifier); + ctx.add_instruction_with_identifier( + Instruction::CreateMutableBinding, + identifier, + ) + }); + } + // 6. Set the running execution context's LexicalEnvironment to loopEnv. + } + init.compile(ctx); + } + ast::ForStatementInit::YieldExpression(init) => init.compile(ctx), + ast::ForStatementInit::JSXElement(_) + | ast::ForStatementInit::JSXFragment(_) + | ast::ForStatementInit::TSAsExpression(_) + | ast::ForStatementInit::TSSatisfiesExpression(_) + | ast::ForStatementInit::TSTypeAssertion(_) + | ast::ForStatementInit::TSNonNullExpression(_) + | ast::ForStatementInit::TSInstantiationExpression(_) => unreachable!(), + } + } + // 2. Perform ? CreatePerIterationEnvironment(perIterationBindings). + let create_per_iteration_env = if !per_iteration_lets.is_empty() { + Some(|ctx: &mut CompileContext| { + if per_iteration_lets.len() == 1 { + // NOTE: Optimization for the usual case of a single let + // binding. We do not need to push and pop from the stack + // in this case but can use the result register directly. + // There are rather easy further optimizations available as + // well around creating a sibling environment directly, + // creating an initialized mutable binding directly, and + // importantly: The whole loop environment is unnecessary + // if the loop contains no closures (that capture the + // per-iteration lets). + + let binding = *per_iteration_lets.first().unwrap(); + // Get value of binding from lastIterationEnv. + ctx.add_instruction_with_identifier(Instruction::ResolveBinding, binding); + ctx.add_instruction(Instruction::GetValue); + // Current declarative environment is now "outer" + ctx.add_instruction(Instruction::ExitDeclarativeEnvironment); + // NewDeclarativeEnvironment(outer) + ctx.add_instruction(Instruction::EnterDeclarativeEnvironment); + ctx.add_instruction_with_identifier(Instruction::CreateMutableBinding, binding); + ctx.add_instruction_with_identifier(Instruction::ResolveBinding, binding); + ctx.add_instruction(Instruction::InitializeReferencedBinding); + } else { + for bn in &per_iteration_lets { + ctx.add_instruction_with_identifier(Instruction::ResolveBinding, *bn); + ctx.add_instruction(Instruction::GetValue); + ctx.add_instruction(Instruction::Load); + } + ctx.add_instruction(Instruction::ExitDeclarativeEnvironment); + ctx.add_instruction(Instruction::EnterDeclarativeEnvironment); + for bn in per_iteration_lets.iter().rev() { + ctx.add_instruction_with_identifier(Instruction::CreateMutableBinding, *bn); + ctx.add_instruction_with_identifier(Instruction::ResolveBinding, *bn); + ctx.add_instruction(Instruction::Store); + ctx.add_instruction(Instruction::InitializeReferencedBinding); + } + } + }) + } else { + None + }; + + if let Some(create_per_iteration_env) = create_per_iteration_env { + create_per_iteration_env(ctx); + } + + let loop_jump = ctx.get_jump_index_to_here(); + if let Some(test) = &self.test { + test.compile(ctx); + if is_reference(test) { + ctx.add_instruction(Instruction::GetValue); + } + } + // jump over consequent if test fails + let end_jump = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); + + self.body.compile(ctx); + + let own_continues = ctx.current_continue.take().unwrap(); + for continue_entry in own_continues { + ctx.set_jump_target_here(continue_entry); + } + + if let Some(create_per_iteration_env) = create_per_iteration_env { + create_per_iteration_env(ctx); + } + + if let Some(update) = &self.update { + update.compile(ctx); + } + ctx.add_jump_instruction_to_index(Instruction::Jump, loop_jump); + ctx.set_jump_target_here(end_jump); + + let own_breaks = ctx.current_break.take().unwrap(); + for break_entry in own_breaks { + ctx.set_jump_target_here(break_entry); + } + if is_lexical { + // Lexical binding loops have an extra declarative environment that + // we need to exit from once we exit the loop. + ctx.add_instruction(Instruction::ExitDeclarativeEnvironment); + } + ctx.current_break = previous_break; + ctx.current_continue = previous_continue; + } +} + +impl CompileEvaluation for ast::SwitchStatement<'_> { + fn compile(&self, ctx: &mut CompileContext) { + let previous_break = ctx.current_break.replace(vec![]); + // 1. Let exprRef be ? Evaluation of Expression. + self.discriminant.compile(ctx); + if is_reference(&self.discriminant) { + // 2. Let switchValue be ? GetValue(exprRef). + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::Load); + // 3. Let oldEnv be the running execution context's LexicalEnvironment. + // 4. Let blockEnv be NewDeclarativeEnvironment(oldEnv). + // 6. Set the running execution context's LexicalEnvironment to blockEnv. + ctx.add_instruction(Instruction::EnterDeclarativeEnvironment); + // 5. Perform BlockDeclarationInstantiation(CaseBlock, blockEnv). + // TODO: Analyze switch env instantiation. + + // 7. Let R be Completion(CaseBlockEvaluation of CaseBlock with argument switchValue). + let mut has_default = false; + let mut jump_indexes = Vec::with_capacity(self.cases.len()); + for case in &self.cases { + let Some(test) = &case.test else { + // Default case test does not care about the write order: After + // all other cases have been tested, default will be entered if + // no other was entered previously. The placement of the + // default case only matters for fall-through behaviour. + has_default = true; + continue; + }; + // Duplicate the switchValue on the stack. One will remain, one is + // used by the IsStrictlyEqual + ctx.add_instruction(Instruction::Store); + ctx.add_instruction(Instruction::LoadCopy); + ctx.add_instruction(Instruction::Load); + // 2. Let exprRef be ? Evaluation of the Expression of C. + test.compile(ctx); + // 3. Let clauseSelector be ? GetValue(exprRef). + if is_reference(test) { + ctx.add_instruction(Instruction::GetValue); + } + // 4. Return IsStrictlyEqual(input, clauseSelector). + ctx.add_instruction(Instruction::IsStrictlyEqual); + // b. If found is true then [evaluate case] + jump_indexes.push(ctx.add_instruction_with_jump_slot(Instruction::JumpIfTrue)); + } + + if has_default { + // 10. If foundInB is true, return V. + // 11. Let defaultR be Completion(Evaluation of DefaultClause). + jump_indexes.push(ctx.add_instruction_with_jump_slot(Instruction::Jump)); + } + + let mut index = 0; + for (i, case) in self.cases.iter().enumerate() { + let fallthrough_jump = if i != 0 { + Some(ctx.add_instruction_with_jump_slot(Instruction::Jump)) + } else { + None + }; + // Jump from IsStrictlyEqual comparison to here. + let jump_index = if case.test.is_some() { + let jump_index = jump_indexes.get(index).unwrap(); + index += 1; + jump_index + } else { + // Default case! The jump index is last in the Vec. + jump_indexes.last().unwrap() + }; + ctx.set_jump_target_here(jump_index.clone()); + + // Pop the switchValue from the stack. + ctx.add_instruction(Instruction::Store); + // And override it with undefined + ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); + + if let Some(fallthrough_jump) = fallthrough_jump { + ctx.set_jump_target_here(fallthrough_jump); + } + + for ele in &case.consequent { + ele.compile(ctx); + } + } + + let own_breaks = ctx.current_break.take().unwrap(); + for break_entry in own_breaks { + ctx.set_jump_target_here(break_entry); + } + ctx.current_break = previous_break; + + // 8. Set the running execution context's LexicalEnvironment to oldEnv. + ctx.add_instruction(Instruction::ExitDeclarativeEnvironment); + // 9. Return R. + } +} + +impl CompileEvaluation for ast::ThrowStatement<'_> { + fn compile(&self, ctx: &mut CompileContext) { + self.argument.compile(ctx); + if is_reference(&self.argument) { + ctx.add_instruction(Instruction::GetValue); + } + ctx.add_instruction(Instruction::Throw) + } +} + +impl CompileEvaluation for ast::TryStatement<'_> { + fn compile(&self, ctx: &mut CompileContext) { + if self.finalizer.is_some() { + todo!(); + } + + let jump_to_catch = + ctx.add_instruction_with_jump_slot(Instruction::PushExceptionJumpTarget); + self.block.compile(ctx); + ctx.add_instruction(Instruction::PopExceptionJumpTarget); + let jump_to_end = ctx.add_instruction_with_jump_slot(Instruction::Jump); + + let catch_clause = self.handler.as_ref().unwrap(); + ctx.set_jump_target_here(jump_to_catch); + if let Some(exception_param) = &catch_clause.param { + let ast::BindingPatternKind::BindingIdentifier(identifier) = + &exception_param.pattern.kind + else { + todo!("{:?}", exception_param.pattern.kind); + }; + ctx.add_instruction(Instruction::EnterDeclarativeEnvironment); + let identifier_string = String::from_str(ctx.agent, identifier.name.as_str()); + ctx.add_instruction_with_identifier(Instruction::CreateCatchBinding, identifier_string); + } + catch_clause.body.compile(ctx); + if catch_clause.param.is_some() { + ctx.add_instruction(Instruction::ExitDeclarativeEnvironment); + } + ctx.set_jump_target_here(jump_to_end); + } +} + +impl CompileEvaluation for ast::WhileStatement<'_> { + fn compile(&self, ctx: &mut CompileContext) { + let previous_continue = ctx.current_continue.replace(vec![]); + let previous_break = ctx.current_break.replace(vec![]); + + // 2. Repeat + let start_jump = ctx.get_jump_index_to_here(); + + // a. Let exprRef be ? Evaluation of Expression. + + self.test.compile(ctx); + if is_reference(&self.test) { + // b. Let exprValue be ? GetValue(exprRef). + ctx.add_instruction(Instruction::GetValue); + } + + // c. If ToBoolean(exprValue) is false, return V. + // jump over loop jump if test fails + let end_jump = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); + // d. Let stmtResult be Completion(Evaluation of Statement). + self.body.compile(ctx); + + // e. If LoopContinues(stmtResult, labelSet) is false, return ? UpdateEmpty(stmtResult, V). + // f. If stmtResult.[[Value]] is not EMPTY, set V to stmtResult.[[Value]]. + ctx.add_jump_instruction_to_index(Instruction::Jump, start_jump.clone()); + let own_continues = ctx.current_continue.take().unwrap(); + for continue_entry in own_continues { + ctx.set_jump_target(continue_entry, start_jump.clone()); + } + + ctx.set_jump_target_here(end_jump); + + let own_breaks = ctx.current_break.take().unwrap(); + for break_entry in own_breaks { + ctx.set_jump_target_here(break_entry); + } + ctx.current_break = previous_break; + ctx.current_continue = previous_continue; + } +} + +impl CompileEvaluation for ast::DoWhileStatement<'_> { + fn compile(&self, ctx: &mut CompileContext) { + let previous_continue = ctx.current_continue.replace(vec![]); + let previous_break = ctx.current_break.replace(vec![]); + + let start_jump = ctx.get_jump_index_to_here(); + self.body.compile(ctx); + + let own_continues = ctx.current_continue.take().unwrap(); + for continue_entry in own_continues { + ctx.set_jump_target_here(continue_entry); + } + + self.test.compile(ctx); + if is_reference(&self.test) { + ctx.add_instruction(Instruction::GetValue); + } + // jump over loop jump if test fails + let end_jump = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); + ctx.add_jump_instruction_to_index(Instruction::Jump, start_jump); + ctx.set_jump_target_here(end_jump); + + let own_breaks = ctx.current_break.take().unwrap(); + for break_entry in own_breaks { + ctx.set_jump_target_here(break_entry); + } + ctx.current_break = previous_break; + ctx.current_continue = previous_continue; + } +} + +impl CompileEvaluation for ast::BreakStatement<'_> { + fn compile(&self, ctx: &mut CompileContext) { + if let Some(label) = &self.label { + let label = label.name.as_str(); + todo!("break {};", label); + } + let break_jump = ctx.add_instruction_with_jump_slot(Instruction::Jump); + ctx.current_break.as_mut().unwrap().push(break_jump); + } +} + +impl CompileEvaluation for ast::ContinueStatement<'_> { + fn compile(&self, ctx: &mut CompileContext) { + if let Some(label) = &self.label { + let label = label.name.as_str(); + todo!("continue {};", label); + } + let continue_jump = ctx.add_instruction_with_jump_slot(Instruction::Jump); + ctx.current_continue.as_mut().unwrap().push(continue_jump); + } +} + +impl CompileEvaluation for ast::Statement<'_> { + fn compile(&self, ctx: &mut CompileContext) { + match self { + ast::Statement::ExpressionStatement(x) => x.compile(ctx), + ast::Statement::ReturnStatement(x) => x.compile(ctx), + ast::Statement::IfStatement(x) => x.compile(ctx), + ast::Statement::VariableDeclaration(x) => x.compile(ctx), + ast::Statement::FunctionDeclaration(x) => x.compile(ctx), + ast::Statement::BlockStatement(x) => x.compile(ctx), + ast::Statement::EmptyStatement(_) => {} + ast::Statement::ForStatement(x) => x.compile(ctx), + ast::Statement::ThrowStatement(x) => x.compile(ctx), + ast::Statement::TryStatement(x) => x.compile(ctx), + Statement::BreakStatement(statement) => statement.compile(ctx), + Statement::ContinueStatement(statement) => statement.compile(ctx), + Statement::DebuggerStatement(_) => todo!(), + Statement::DoWhileStatement(statement) => statement.compile(ctx), + Statement::ForInStatement(statement) => statement.compile(ctx), + Statement::ForOfStatement(statement) => statement.compile(ctx), + Statement::LabeledStatement(_) => todo!(), + Statement::SwitchStatement(statement) => statement.compile(ctx), + Statement::WhileStatement(statement) => statement.compile(ctx), + Statement::WithStatement(_) => todo!(), + Statement::ClassDeclaration(x) => x.compile(ctx), + Statement::ImportDeclaration(_) => todo!(), + Statement::ExportAllDeclaration(_) => todo!(), + Statement::ExportDefaultDeclaration(_) => todo!(), + Statement::ExportNamedDeclaration(_) => todo!(), + #[cfg(feature = "typescript")] + Statement::TSTypeAliasDeclaration(_) | Statement::TSInterfaceDeclaration(_) => {} + #[cfg(not(feature = "typescript"))] + Statement::TSTypeAliasDeclaration(_) | Statement::TSInterfaceDeclaration(_) => { + unreachable!() + } + Statement::TSEnumDeclaration(_) + | Statement::TSExportAssignment(_) + | Statement::TSImportEqualsDeclaration(_) + | Statement::TSModuleDeclaration(_) + | Statement::TSNamespaceExportDeclaration(_) => unreachable!(), + } + } +} + +fn is_anonymous_function_definition(expression: &ast::Expression) -> bool { + match expression { + ast::Expression::ArrowFunctionExpression(_) => true, + ast::Expression::FunctionExpression(f) => f.id.is_none(), + _ => false, + } +} diff --git a/nova_vm/src/engine/bytecode/executable/class_definition_evaluation.rs b/nova_vm/src/engine/bytecode/bytecode_compiler/class_definition_evaluation.rs similarity index 100% rename from nova_vm/src/engine/bytecode/executable/class_definition_evaluation.rs rename to nova_vm/src/engine/bytecode/bytecode_compiler/class_definition_evaluation.rs diff --git a/nova_vm/src/engine/bytecode/executable/for_in_of_statement.rs b/nova_vm/src/engine/bytecode/bytecode_compiler/for_in_of_statement.rs similarity index 100% rename from nova_vm/src/engine/bytecode/executable/for_in_of_statement.rs rename to nova_vm/src/engine/bytecode/bytecode_compiler/for_in_of_statement.rs diff --git a/nova_vm/src/engine/bytecode/executable/function_declaration_instantiation.rs b/nova_vm/src/engine/bytecode/bytecode_compiler/function_declaration_instantiation.rs similarity index 99% rename from nova_vm/src/engine/bytecode/executable/function_declaration_instantiation.rs rename to nova_vm/src/engine/bytecode/bytecode_compiler/function_declaration_instantiation.rs index a5970ec73..07d21a987 100644 --- a/nova_vm/src/engine/bytecode/executable/function_declaration_instantiation.rs +++ b/nova_vm/src/engine/bytecode/bytecode_compiler/function_declaration_instantiation.rs @@ -21,7 +21,7 @@ use crate::{ }, types::{String, Value, BUILTIN_STRING_MEMORY}, }, - engine::{bytecode::executable::CompileContext, Instruction}, + engine::{bytecode::bytecode_compiler::CompileContext, Instruction}, }; use super::{complex_array_pattern, simple_array_pattern, CompileEvaluation}; diff --git a/nova_vm/src/engine/bytecode/executable.rs b/nova_vm/src/engine/bytecode/executable.rs index de5acd35e..1f296e078 100644 --- a/nova_vm/src/engine/bytecode/executable.rs +++ b/nova_vm/src/engine/bytecode/executable.rs @@ -2,455 +2,22 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -mod class_definition_evaluation; -mod for_in_of_statement; -mod function_declaration_instantiation; +use std::{ + num::NonZeroU32, + ops::{Index, IndexMut}, +}; -use super::{instructions::Instr, Instruction}; +use super::{instructions::Instr, CompileContext, Instruction, NamedEvaluationParameter}; use crate::{ ecmascript::{ - builtins::regexp::reg_exp_create, execution::Agent, scripts_and_modules::script::ScriptIdentifier, - syntax_directed_operations::{ - function_definitions::{CompileFunctionBodyData, ContainsExpression}, - scope_analysis::{LexicallyScopedDeclaration, LexicallyScopedDeclarations}, - }, - types::{BigInt, IntoValue, Number, PropertyKey, String, Value, BUILTIN_STRING_MEMORY}, + syntax_directed_operations::function_definitions::CompileFunctionBodyData, + types::{String, Value}, }, - heap::{CompactionLists, CreateHeapData, HeapMarkAndSweep, WorkQueues}, -}; -use num_traits::Num; -use oxc_ast::{ - ast::{self, BindingPattern, BindingRestElement, CallExpression, NewExpression, Statement}, - syntax_directed_operations::BoundNames, + heap::{CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, WorkQueues}, }; -use oxc_span::Atom; -use oxc_syntax::operator::{BinaryOperator, UnaryOperator}; - -pub type IndexType = u16; - -#[derive(Debug, Clone, Copy)] -pub(crate) enum NamedEvaluationParameter { - /// Name is in the result register - Result, - /// Name is at the top of the stack - Stack, - /// Name is in the reference register - Reference, - /// Name is at the top of the reference stack - ReferenceStack, -} - -pub(crate) struct CompileContext<'agent> { - pub(crate) agent: &'agent mut Agent, - /// Instructions being built - instructions: Vec, - /// Constants being built - constants: Vec, - /// Function expressions being built - function_expressions: Vec, - /// Arrow function expressions being built - arrow_function_expressions: Vec, - class_initializer_bytecodes: Vec<(Option, bool)>, - /// NamedEvaluation name parameter - name_identifier: Option, - /// If true, indicates that all bindings being created are lexical. - /// - /// Otherwise, all bindings being created are variable scoped. - lexical_binding_state: bool, - /// `continue;` statement jumps that were present in the current loop. - current_continue: Option>, - /// `break;` statement jumps that were present in the current loop. - current_break: Option>, - /// `?.` chain jumps that were present in a chain expression. - optional_chains: Option>, - /// In a `(a?.b)?.()` chain the evaluation of `(a?.b)` must be considered a - /// reference. - is_call_optional_chain_this: bool, -} - -impl CompileContext<'_> { - fn new(agent: &'_ mut Agent) -> CompileContext<'_> { - CompileContext { - agent, - instructions: Vec::new(), - constants: Vec::new(), - function_expressions: Vec::new(), - arrow_function_expressions: Vec::new(), - class_initializer_bytecodes: Vec::new(), - name_identifier: None, - lexical_binding_state: false, - current_continue: None, - current_break: None, - optional_chains: None, - is_call_optional_chain_this: false, - } - } - - /// Compile a class static field with an optional initializer into the - /// current context. - pub(crate) fn compile_class_static_field( - &mut self, - property_key: &ast::PropertyKey<'_>, - value: &Option>, - ) { - let identifier = match property_key { - ast::PropertyKey::StaticIdentifier(identifier_name) => { - String::from_str(self.agent, identifier_name.name.as_str()) - } - ast::PropertyKey::PrivateIdentifier(_private_identifier) => todo!(), - ast::PropertyKey::BooleanLiteral(_boolean_literal) => todo!(), - ast::PropertyKey::NullLiteral(_null_literal) => todo!(), - ast::PropertyKey::NumericLiteral(_numeric_literal) => todo!(), - ast::PropertyKey::BigIntLiteral(_big_int_literal) => todo!(), - ast::PropertyKey::RegExpLiteral(_reg_exp_literal) => todo!(), - ast::PropertyKey::StringLiteral(_string_literal) => todo!(), - ast::PropertyKey::TemplateLiteral(_template_literal) => todo!(), - _ => unreachable!(), - }; - // Turn the static name to a 'this' property access. - self.add_instruction(Instruction::ResolveThisBinding); - self.add_instruction_with_identifier( - Instruction::EvaluatePropertyAccessWithIdentifierKey, - identifier, - ); - if let Some(value) = value { - // Minor optimisation: We do not need to push and pop the - // reference if we know we're not using the reference stack. - let is_literal = value.is_literal(); - if !is_literal { - self.add_instruction(Instruction::PushReference); - } - value.compile(self); - if is_reference(value) { - self.add_instruction(Instruction::GetValue); - } - if !is_literal { - self.add_instruction(Instruction::PopReference); - } - } else { - // Same optimisation is unconditionally valid here. - self.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); - } - self.add_instruction(Instruction::PutValue); - } - - /// Compile a class computed field with an optional initializer into the - /// current context. - pub(crate) fn compile_class_computed_field( - &mut self, - property_key_id: String, - value: &Option>, - ) { - // Resolve 'this' into the stack. - self.add_instruction(Instruction::ResolveThisBinding); - self.add_instruction(Instruction::Load); - // Resolve the static computed key ID to the actual computed key value. - self.add_instruction_with_identifier(Instruction::ResolveBinding, property_key_id); - // Store the computed key value as the result. - self.add_instruction(Instruction::GetValue); - // Evaluate access to 'this' with the computed key. - self.add_instruction(Instruction::EvaluatePropertyAccessWithExpressionKey); - if let Some(value) = value { - // Minor optimisation: We do not need to push and pop the - // reference if we know we're not using the reference stack. - let is_literal = value.is_literal(); - if !is_literal { - self.add_instruction(Instruction::PushReference); - } - value.compile(self); - if is_reference(value) { - self.add_instruction(Instruction::GetValue); - } - if !is_literal { - self.add_instruction(Instruction::PopReference); - } - } else { - // Same optimisation is unconditionally valid here. - self.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); - } - self.add_instruction(Instruction::PutValue); - } - - /// Compile a function body into the current context. - /// - /// This is useful when the function body is part of a larger whole, namely - /// with class constructors. - pub(crate) fn compile_function_body(&mut self, data: CompileFunctionBodyData<'_>) { - if self.agent.options.print_internals { - eprintln!(); - eprintln!("=== Compiling Function ==="); - eprintln!(); - } - - function_declaration_instantiation::instantiation( - self, - data.params, - data.body, - data.is_strict, - data.is_lexical, - ); - - // SAFETY: Script referred by the Function uniquely owns the Program - // and the body buffer does not move under any circumstances during - // heap operations. - let body: &[Statement] = unsafe { std::mem::transmute(data.body.statements.as_slice()) }; - - self.compile_statements(body); - } - - fn compile_statements(&mut self, body: &[Statement]) { - let iter = body.iter(); - - for stmt in iter { - stmt.compile(self); - } - } - - fn do_implicit_return(&mut self) { - if self.instructions.last() != Some(&Instruction::Return.as_u8()) { - // If code did not end with a return statement, add it manually - self.add_instruction(Instruction::Return); - } - } - - fn finish(self) -> Executable { - Executable { - instructions: self.instructions.into_boxed_slice(), - constants: self.constants.into_boxed_slice(), - function_expressions: self.function_expressions.into_boxed_slice(), - arrow_function_expressions: self.arrow_function_expressions.into_boxed_slice(), - class_initializer_bytecodes: self.class_initializer_bytecodes.into_boxed_slice(), - } - } - - pub(crate) fn create_identifier(&mut self, atom: &Atom<'_>) -> String { - let existing = self.constants.iter().find_map(|constant| { - if let Ok(existing_identifier) = String::try_from(*constant) { - if existing_identifier.as_str(self.agent) == atom.as_str() { - Some(existing_identifier) - } else { - None - } - } else { - None - } - }); - if let Some(existing) = existing { - existing - } else { - String::from_str(self.agent, atom.as_str()) - } - } - - fn peek_last_instruction(&self) -> Option { - for ele in self.instructions.iter().rev() { - if *ele == Instruction::ExitDeclarativeEnvironment.as_u8() { - // Not a "real" instruction - continue; - } - return Some(*ele); - } - None - } - - fn _push_instruction(&mut self, instruction: Instruction) { - self.instructions - .push(unsafe { std::mem::transmute::(instruction) }); - } - - fn add_instruction(&mut self, instruction: Instruction) { - debug_assert_eq!(instruction.argument_count(), 0); - debug_assert!( - !instruction.has_constant_index() - && !instruction.has_function_expression_index() - && !instruction.has_identifier_index() - ); - self._push_instruction(instruction); - } - - fn add_instruction_with_jump_slot(&mut self, instruction: Instruction) -> JumpIndex { - debug_assert_eq!(instruction.argument_count(), 1); - debug_assert!(instruction.has_jump_slot()); - self._push_instruction(instruction); - self.add_jump_index() - } - - fn add_jump_instruction_to_index(&mut self, instruction: Instruction, jump_index: JumpIndex) { - debug_assert_eq!(instruction.argument_count(), 1); - debug_assert!(instruction.has_jump_slot()); - self._push_instruction(instruction); - self.add_index(jump_index.index); - } - - fn get_jump_index_to_here(&self) -> JumpIndex { - JumpIndex { - index: self.instructions.len(), - } - } - - fn add_constant(&mut self, constant: Value) -> usize { - let duplicate = self - .constants - .iter() - .enumerate() - .find(|item| item.1.eq(&constant)) - .map(|(idx, _)| idx); - - duplicate.unwrap_or_else(|| { - let index = self.constants.len(); - self.constants.push(constant); - index - }) - } - - fn add_identifier(&mut self, identifier: String) -> usize { - let duplicate = self - .constants - .iter() - .enumerate() - .find(|item| String::try_from(*item.1) == Ok(identifier)) - .map(|(idx, _)| idx); - - duplicate.unwrap_or_else(|| { - let index = self.constants.len(); - self.constants.push(identifier.into_value()); - index - }) - } - - fn add_instruction_with_immediate(&mut self, instruction: Instruction, immediate: usize) { - debug_assert_eq!(instruction.argument_count(), 1); - self._push_instruction(instruction); - self.add_index(immediate); - } - - fn add_instruction_with_constant( - &mut self, - instruction: Instruction, - constant: impl Into, - ) { - debug_assert_eq!(instruction.argument_count(), 1); - debug_assert!(instruction.has_constant_index()); - self._push_instruction(instruction); - let constant = self.add_constant(constant.into()); - self.add_index(constant); - } - - fn add_instruction_with_identifier(&mut self, instruction: Instruction, identifier: String) { - debug_assert_eq!(instruction.argument_count(), 1); - debug_assert!(instruction.has_identifier_index()); - self._push_instruction(instruction); - let identifier = self.add_identifier(identifier); - self.add_index(identifier); - } - - fn add_instruction_with_identifier_and_constant( - &mut self, - instruction: Instruction, - identifier: String, - constant: impl Into, - ) { - debug_assert_eq!(instruction.argument_count(), 2); - debug_assert!(instruction.has_identifier_index() && instruction.has_constant_index()); - self._push_instruction(instruction); - let identifier = self.add_identifier(identifier); - self.add_index(identifier); - let constant = self.add_constant(constant.into()); - self.add_index(constant); - } - - fn add_instruction_with_immediate_and_immediate( - &mut self, - instruction: Instruction, - immediate1: usize, - immediate2: usize, - ) { - debug_assert_eq!(instruction.argument_count(), 2); - self._push_instruction(instruction); - self.add_index(immediate1); - self.add_index(immediate2) - } - - fn add_index(&mut self, index: usize) { - let index = IndexType::try_from(index).expect("Immediate value is too large"); - let bytes: [u8; 2] = index.to_ne_bytes(); - self.instructions.extend_from_slice(&bytes); - } - - fn add_instruction_with_function_expression( - &mut self, - instruction: Instruction, - function_expression: FunctionExpression, - ) { - debug_assert_eq!(instruction.argument_count(), 1); - debug_assert!(instruction.has_function_expression_index()); - self._push_instruction(instruction); - self.function_expressions.push(function_expression); - let index = self.function_expressions.len() - 1; - self.add_index(index); - } - - /// Add an Instruction that takes a function expression and an immediate - /// as its bytecode parameters. - /// - /// Returns the function expression's index. - fn add_instruction_with_function_expression_and_immediate( - &mut self, - instruction: Instruction, - function_expression: FunctionExpression, - immediate: usize, - ) -> IndexType { - debug_assert_eq!(instruction.argument_count(), 2); - debug_assert!(instruction.has_function_expression_index()); - self._push_instruction(instruction); - self.function_expressions.push(function_expression); - let index = self.function_expressions.len() - 1; - self.add_index(index); - self.add_index(immediate); - // Note: add_index would have panicked if this was not a lossless - // conversion. - index as IndexType - } - - fn add_arrow_function_expression( - &mut self, - arrow_function_expression: ArrowFunctionExpression, - ) { - let instruction = Instruction::InstantiateArrowFunctionExpression; - debug_assert_eq!(instruction.argument_count(), 1); - debug_assert!(instruction.has_function_expression_index()); - self._push_instruction(instruction); - self.arrow_function_expressions - .push(arrow_function_expression); - let index = self.arrow_function_expressions.len() - 1; - self.add_index(index); - } - - fn add_jump_index(&mut self) -> JumpIndex { - self.add_index(0); - JumpIndex { - index: self.instructions.len() - std::mem::size_of::(), - } - } - - fn set_jump_target(&mut self, source: JumpIndex, target: JumpIndex) { - assert!(target.index < IndexType::MAX as usize); - let bytes: [u8; 2] = (target.index as IndexType).to_ne_bytes(); - self.instructions[source.index] = bytes[0]; - self.instructions[source.index + 1] = bytes[1]; - } - - fn set_jump_target_here(&mut self, jump: JumpIndex) { - self.set_jump_target( - jump, - JumpIndex { - index: self.instructions.len(), - }, - ); - } -} +use oxc_ast::ast::{self, Statement}; #[derive(Debug)] /// A `Send` and `Sync` wrapper over a `&'static T` where `T` might not itself @@ -500,6 +67,8 @@ impl SendableRef { unsafe impl Send for SendableRef {} unsafe impl Sync for SendableRef {} +pub type IndexType = u16; + #[derive(Debug, Clone)] pub(crate) struct FunctionExpression { pub(crate) expression: SendableRef>, @@ -514,12 +83,20 @@ pub(crate) struct ArrowFunctionExpression { pub(crate) identifier: Option, } +/// Reference to a heap-allocated executable VM bytecode. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub(crate) struct Executable(NonZeroU32); + +const EXECUTABLE_OPTION_SIZE_IS_U32: () = + assert!(size_of::() == size_of::>()); + /// ## Notes /// /// - This is inspired by and/or copied from Kiesel engine: /// Copyright (c) 2023-2024 Linus Groh #[derive(Debug, Clone)] -pub(crate) struct Executable { +pub(crate) struct ExecutableHeapData { pub instructions: Box<[u8]>, pub(crate) constants: Box<[Value]>, pub(crate) function_expressions: Box<[FunctionExpression]>, @@ -528,37 +105,7 @@ pub(crate) struct Executable { } impl Executable { - pub(super) fn get_instruction(&self, ip: &mut usize) -> Option { - if *ip >= self.instructions.len() { - return None; - } - - let kind: Instruction = - unsafe { std::mem::transmute::(self.instructions[*ip]) }; - *ip += 1; - - let mut args: [Option; 2] = [None, None]; - - for item in args.iter_mut().take(kind.argument_count() as usize) { - let length = self.instructions[*ip..].len(); - if length >= 2 { - let bytes = IndexType::from_ne_bytes(unsafe { - *std::mem::transmute::<*const u8, *const [u8; 2]>( - self.instructions[*ip..].as_ptr(), - ) - }); - *ip += 2; - *item = Some(bytes); - } else { - *ip += 1; - *item = None; - } - } - - Some(Instr { kind, args }) - } - - pub(crate) fn compile_script(agent: &mut Agent, script: ScriptIdentifier) -> Executable { + pub(crate) fn compile_script(agent: &mut Agent, script: ScriptIdentifier) -> Self { if agent.options.print_internals { eprintln!(); eprintln!("=== Compiling Script ==="); @@ -578,7 +125,7 @@ impl Executable { pub(crate) fn compile_function_body( agent: &mut Agent, data: CompileFunctionBodyData<'_>, - ) -> Executable { + ) -> Self { let mut ctx = CompileContext::new(agent); let is_concise = data.is_concise_body; @@ -592,7 +139,7 @@ impl Executable { ctx.finish() } - pub(crate) fn compile_eval_body(agent: &mut Agent, body: &[Statement]) -> Executable { + pub(crate) fn compile_eval_body(agent: &mut Agent, body: &[Statement]) -> Self { if agent.options.print_internals { eprintln!(); eprintln!("=== Compiling Eval Body ==="); @@ -604,2481 +151,250 @@ impl Executable { ctx.do_implicit_return(); ctx.finish() } -} - -#[derive(Debug, Clone)] -#[repr(transparent)] -pub(crate) struct JumpIndex { - pub(crate) index: usize, -} - -pub(crate) trait CompileEvaluation { - fn compile(&self, ctx: &mut CompileContext); -} - -pub(crate) fn is_reference(expression: &ast::Expression) -> bool { - match expression { - ast::Expression::Identifier(_) - | ast::Expression::ComputedMemberExpression(_) - | ast::Expression::StaticMemberExpression(_) - | ast::Expression::PrivateFieldExpression(_) - | ast::Expression::Super(_) => true, - ast::Expression::ParenthesizedExpression(parenthesized) => { - is_reference(&parenthesized.expression) - } - _ => false, - } -} - -fn is_chain_expression(expression: &ast::Expression) -> bool { - match expression { - ast::Expression::ChainExpression(_) => true, - ast::Expression::ParenthesizedExpression(parenthesized) => { - is_chain_expression(&parenthesized.expression) - } - _ => false, - } -} - -impl CompileEvaluation for ast::NumericLiteral<'_> { - fn compile(&self, ctx: &mut CompileContext) { - let constant = ctx.agent.heap.create(self.value); - ctx.add_instruction_with_constant(Instruction::StoreConstant, constant); - } -} - -impl CompileEvaluation for ast::BooleanLiteral { - fn compile(&self, ctx: &mut CompileContext) { - ctx.add_instruction_with_constant(Instruction::StoreConstant, self.value); - } -} -impl CompileEvaluation for ast::BigIntLiteral<'_> { - fn compile(&self, ctx: &mut CompileContext) { - // Drop out the trailing 'n' from BigInt literals. - let last_index = self.raw.len() - 1; - let (big_int_str, radix) = match self.base { - oxc_syntax::number::BigintBase::Decimal => (&self.raw.as_str()[..last_index], 10), - oxc_syntax::number::BigintBase::Binary => (&self.raw.as_str()[2..last_index], 2), - oxc_syntax::number::BigintBase::Octal => (&self.raw.as_str()[2..last_index], 8), - oxc_syntax::number::BigintBase::Hex => (&self.raw.as_str()[2..last_index], 16), + /// Drops the Executable's heap-allocated data if possible. + /// + /// ## Safety + /// + /// Any attempt to use the Executable after this call will lead to a crash + /// if the drop was performed. + pub(crate) unsafe fn try_drop(self, agent: &mut Agent) { + debug_assert!(!agent.heap.executables.is_empty()); + let index = self.get_index(); + let last_index = agent.heap.executables.len() - 1; + if last_index == index { + // This bytecode was the last-allocated bytecode, and we can drop + // it from the Heap without affecting any other indexes. The caller + // guarantees that the Executable will not be used anymore. + let _ = agent.heap.executables.pop().unwrap(); + } + } + + pub(crate) fn get_index(self) -> usize { + (self.0.get() - 1) as usize + } + + /// SAFETY: The returned reference is valid until the Executable is garbage + /// collected. + #[inline] + pub(super) fn get_instructions(self, agent: &Agent) -> &'static [u8] { + // SAFETY: As long as we're alive the instructions Box lives, and it is + // never accessed mutably. + unsafe { std::mem::transmute(&agent[self].instructions[..]) } + } + + #[inline] + pub(super) fn get_instruction(self, agent: &Agent, ip: &mut usize) -> Option { + // SAFETY: As long as we're alive the instructions Box lives, and it is + // never accessed mutably. + get_instruction(&agent[self].instructions[..], ip) + } + + #[inline] + pub(super) fn get_constants(self, agent: &Agent) -> &[Value] { + &agent[self].constants[..] + } + + #[inline] + pub(super) fn fetch_identifier(self, agent: &Agent, index: usize) -> String { + // SAFETY: As long as we're alive the constants Box lives. It is + // accessed mutably only during GC, during which this function is never + // called. As we do not hand out a reference here, the mutable + // reference during GC and fetching references here never overlap. + let value = agent[self].constants[index]; + let Ok(value) = String::try_from(value) else { + handle_identifier_failure() }; - let constant = BigInt::from_num_bigint( - ctx.agent, - num_bigint::BigInt::from_str_radix(big_int_str, radix).unwrap(), - ); - ctx.add_instruction_with_constant(Instruction::StoreConstant, constant); + value } -} -impl CompileEvaluation for ast::NullLiteral { - fn compile(&self, ctx: &mut CompileContext) { - ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Null); + #[inline] + pub(super) fn fetch_constant(self, agent: &Agent, index: usize) -> Value { + // SAFETY: As long as we're alive the constants Box lives. It is + // accessed mutably only during GC, during which this function is never + // called. As we do not hand out a reference here, the mutable + // reference during GC and fetching references here never overlap. + agent[self].constants[index] } -} -impl CompileEvaluation for ast::StringLiteral<'_> { - fn compile(&self, ctx: &mut CompileContext) { - let constant = String::from_str(ctx.agent, self.value.as_str()); - ctx.add_instruction_with_constant(Instruction::StoreConstant, constant); + pub(super) fn fetch_function_expression( + self, + agent: &Agent, + index: usize, + ) -> &FunctionExpression { + &agent[self].function_expressions[index] } -} -impl CompileEvaluation for ast::IdentifierReference<'_> { - fn compile(&self, ctx: &mut CompileContext) { - let identifier = String::from_str(ctx.agent, self.name.as_str()); - ctx.add_instruction_with_identifier(Instruction::ResolveBinding, identifier); + pub(super) fn fetch_arrow_function_expression( + self, + agent: &Agent, + index: usize, + ) -> &ArrowFunctionExpression { + &agent[self].arrow_function_expressions[index] } -} -impl CompileEvaluation for ast::BindingIdentifier<'_> { - fn compile(&self, ctx: &mut CompileContext) { - let identifier = String::from_str(ctx.agent, self.name.as_str()); - ctx.add_instruction_with_identifier(Instruction::ResolveBinding, identifier); + pub(super) fn fetch_class_initializer_bytecode( + self, + agent: &Agent, + index: usize, + ) -> (Option, bool) { + agent[self].class_initializer_bytecodes[index] } } -impl CompileEvaluation for ast::UnaryExpression<'_> { - /// ### [13.5 Unary Operators](https://tc39.es/ecma262/#sec-unary-operators) - fn compile(&self, ctx: &mut CompileContext) { - match self.operator { - // 13.5.5 Unary - Operator - // https://tc39.es/ecma262/#sec-unary-minus-operator-runtime-semantics-evaluation - // UnaryExpression : - UnaryExpression - UnaryOperator::UnaryNegation => { - // 1. Let expr be ? Evaluation of UnaryExpression. - self.argument.compile(ctx); - - // 2. Let oldValue be ? ToNumeric(? GetValue(expr)). - if is_reference(&self.argument) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::ToNumeric); - - // 3. If oldValue is a Number, then - // a. Return Number::unaryMinus(oldValue). - // 4. Else, - // a. Assert: oldValue is a BigInt. - // b. Return BigInt::unaryMinus(oldValue). - ctx.add_instruction(Instruction::UnaryMinus); - } - // 13.5.4 Unary + Operator - // https://tc39.es/ecma262/#sec-unary-plus-operator - // UnaryExpression : + UnaryExpression - UnaryOperator::UnaryPlus => { - // 1. Let expr be ? Evaluation of UnaryExpression. - self.argument.compile(ctx); +pub(super) fn get_instruction(instructions: &[u8], ip: &mut usize) -> Option { + if *ip >= instructions.len() { + return None; + } - // 2. Return ? ToNumber(? GetValue(expr)). - if is_reference(&self.argument) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::ToNumber); - } - // 13.5.6 Unary ! Operator - // https://tc39.es/ecma262/#sec-logical-not-operator-runtime-semantics-evaluation - // UnaryExpression : ! UnaryExpression - UnaryOperator::LogicalNot => { - // 1. Let expr be ? Evaluation of UnaryExpression. - self.argument.compile(ctx); + let kind: Instruction = unsafe { std::mem::transmute::(instructions[*ip]) }; + *ip += 1; - // 2. Let oldValue be ToBoolean(? GetValue(expr)). - // 3. If oldValue is true, return false. - // 4. Return true. - if is_reference(&self.argument) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::LogicalNot); - } - // 13.5.7 Unary ~ Operator - // https://tc39.es/ecma262/#sec-bitwise-not-operator-runtime-semantics-evaluation - // UnaryExpression : ~ UnaryExpression - UnaryOperator::BitwiseNot => { - // 1. Let expr be ? Evaluation of UnaryExpression. - self.argument.compile(ctx); + let mut args: [Option; 2] = [None, None]; - // 2. Let oldValue be ? ToNumeric(? GetValue(expr)). - // 3. If oldValue is a Number, then - // a. Return Number::bitwiseNOT(oldValue). - // 4. Else, - // a. Assert: oldValue is a BigInt. - // b. Return BigInt::bitwiseNOT(oldValue). - if is_reference(&self.argument) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::BitwiseNot); - } - // 13.5.3 The typeof Operator - // UnaryExpression : typeof UnaryExpression - UnaryOperator::Typeof => { - // 1. Let val be ? Evaluation of UnaryExpression. - self.argument.compile(ctx); - // 3. Set val to ? GetValue(val). - ctx.add_instruction(Instruction::Typeof); - } - // 13.5.2 The void operator - // UnaryExpression : void UnaryExpression - UnaryOperator::Void => { - // 1. Let expr be ? Evaluation of UnaryExpression. - self.argument.compile(ctx); - // NOTE: GetValue must be called even though its value is not used because it may have observable side-effects. - // 2. Perform ? GetValue(expr). - if is_reference(&self.argument) { - ctx.add_instruction(Instruction::GetValue); - } - // 3. Return undefined. - ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); - } - // 13.5.1 The delete operator - // https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation - // UnaryExpression : delete UnaryExpression - UnaryOperator::Delete => { - // Let ref be ? Evaluation of UnaryExpression. - self.argument.compile(ctx); - // 2. If ref is not a Reference Record, return true. - if !is_reference(&self.argument) { - ctx.add_instruction_with_constant(Instruction::StoreConstant, true); - return; - } - ctx.add_instruction(Instruction::Delete); - } + for item in args.iter_mut().take(kind.argument_count() as usize) { + let length = instructions[*ip..].len(); + if length >= 2 { + let bytes = IndexType::from_ne_bytes(unsafe { + *std::mem::transmute::<*const u8, *const [u8; 2]>(instructions[*ip..].as_ptr()) + }); + *ip += 2; + *item = Some(bytes); + } else { + *ip += 1; + *item = None; } } -} -impl CompileEvaluation for ast::BinaryExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - // 1. Let lref be ? Evaluation of leftOperand. - self.left.compile(ctx); - - // 2. Let lval be ? GetValue(lref). - if is_reference(&self.left) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::Load); + Some(Instr { kind, args }) +} - // 3. Let rref be ? Evaluation of rightOperand. - self.right.compile(ctx); +impl ExecutableHeapData { + #[inline] + pub(super) fn get_instruction(&self, ip: &mut usize) -> Option { + get_instruction(&self.instructions, ip) + } - // 4. Let rval be ? GetValue(rref). - if is_reference(&self.right) { - ctx.add_instruction(Instruction::GetValue); + pub(crate) fn compile_script(agent: &mut Agent, script: ScriptIdentifier) -> Executable { + if agent.options.print_internals { + eprintln!(); + eprintln!("=== Compiling Script ==="); + eprintln!(); } + // SAFETY: Script uniquely owns the Program and the body buffer does + // not move under any circumstances during heap operations. + let body: &[Statement] = + unsafe { std::mem::transmute(agent[script].ecmascript_code.body.as_slice()) }; + let mut ctx = CompileContext::new(agent); - match self.operator { - BinaryOperator::LessThan => { - ctx.add_instruction(Instruction::LessThan); - } - BinaryOperator::LessEqualThan => { - ctx.add_instruction(Instruction::LessThanEquals); - } - BinaryOperator::GreaterThan => { - ctx.add_instruction(Instruction::GreaterThan); - } - BinaryOperator::GreaterEqualThan => { - ctx.add_instruction(Instruction::GreaterThanEquals); - } - BinaryOperator::StrictEquality => { - ctx.add_instruction(Instruction::IsStrictlyEqual); - } - BinaryOperator::StrictInequality => { - ctx.add_instruction(Instruction::IsStrictlyEqual); - ctx.add_instruction(Instruction::LogicalNot); - } - BinaryOperator::Equality => { - ctx.add_instruction(Instruction::IsLooselyEqual); - } - BinaryOperator::Inequality => { - ctx.add_instruction(Instruction::IsLooselyEqual); - ctx.add_instruction(Instruction::LogicalNot); - } - BinaryOperator::In => { - ctx.add_instruction(Instruction::HasProperty); - } - BinaryOperator::Instanceof => { - ctx.add_instruction(Instruction::InstanceofOperator); - } - _ => { - // 5. Return ? ApplyStringOrNumericBinaryOperator(lval, opText, rval). - ctx.add_instruction(Instruction::ApplyStringOrNumericBinaryOperator( - self.operator, - )); - } - } + ctx.compile_statements(body); + ctx.do_implicit_return(); + ctx.finish() } -} -impl CompileEvaluation for ast::LogicalExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - self.left.compile(ctx); - if is_reference(&self.left) { - ctx.add_instruction(Instruction::GetValue); - } - // We store the left value on the stack, because we'll need to restore - // it later. - ctx.add_instruction(Instruction::LoadCopy); + pub(crate) fn compile_function_body( + agent: &mut Agent, + data: CompileFunctionBodyData<'_>, + ) -> Executable { + let mut ctx = CompileContext::new(agent); - match self.operator { - oxc_syntax::operator::LogicalOperator::Or => { - ctx.add_instruction(Instruction::LogicalNot); - } - oxc_syntax::operator::LogicalOperator::And => {} - oxc_syntax::operator::LogicalOperator::Coalesce => { - ctx.add_instruction(Instruction::IsNullOrUndefined); - } - } - let jump_to_return_left = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); + let is_concise = data.is_concise_body; - // We're returning the right expression, so we discard the left value - // at the top of the stack. - ctx.add_instruction(Instruction::Store); + ctx.compile_function_body(data); - self.right.compile(ctx); - if is_reference(&self.right) { - ctx.add_instruction(Instruction::GetValue); + if is_concise { + ctx.do_implicit_return(); } - let jump_to_end = ctx.add_instruction_with_jump_slot(Instruction::Jump); - ctx.set_jump_target_here(jump_to_return_left); - // Return the result of the left expression. - ctx.add_instruction(Instruction::Store); - ctx.set_jump_target_here(jump_to_end); + ctx.finish() } -} - -impl CompileEvaluation for ast::AssignmentExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - // 1. Let lref be ? Evaluation of LeftHandSideExpression. - let is_identifier_ref = match &self.left { - ast::AssignmentTarget::ArrayAssignmentTarget(_) => todo!(), - ast::AssignmentTarget::AssignmentTargetIdentifier(identifier) => { - identifier.compile(ctx); - true - } - ast::AssignmentTarget::ComputedMemberExpression(expression) => { - expression.compile(ctx); - false - } - ast::AssignmentTarget::ObjectAssignmentTarget(_) => todo!(), - ast::AssignmentTarget::PrivateFieldExpression(_) => todo!(), - ast::AssignmentTarget::StaticMemberExpression(expression) => { - expression.compile(ctx); - false - } - ast::AssignmentTarget::TSAsExpression(_) - | ast::AssignmentTarget::TSSatisfiesExpression(_) - | ast::AssignmentTarget::TSNonNullExpression(_) - | ast::AssignmentTarget::TSTypeAssertion(_) - | ast::AssignmentTarget::TSInstantiationExpression(_) => unreachable!(), - }; - - if self.operator == oxc_syntax::operator::AssignmentOperator::Assign { - ctx.add_instruction(Instruction::PushReference); - self.right.compile(ctx); - if is_reference(&self.right) { - ctx.add_instruction(Instruction::GetValue); - } - - ctx.add_instruction(Instruction::LoadCopy); - ctx.add_instruction(Instruction::PopReference); - ctx.add_instruction(Instruction::PutValue); - - // ... Return rval. - ctx.add_instruction(Instruction::Store); - } else if matches!( - self.operator, - oxc_syntax::operator::AssignmentOperator::LogicalAnd - | oxc_syntax::operator::AssignmentOperator::LogicalNullish - | oxc_syntax::operator::AssignmentOperator::LogicalOr - ) { - // 2. Let lval be ? GetValue(lref). - ctx.add_instruction(Instruction::GetValueKeepReference); - ctx.add_instruction(Instruction::PushReference); - // We store the left value on the stack, because we'll need to - // restore it later. - ctx.add_instruction(Instruction::LoadCopy); - - match self.operator { - oxc_syntax::operator::AssignmentOperator::LogicalAnd => { - // 3. Let lbool be ToBoolean(lval). - // Note: We do not directly call ToBoolean: JumpIfNot does. - // 4. If lbool is false, return lval. - } - oxc_syntax::operator::AssignmentOperator::LogicalOr => { - // 3. Let lbool be ToBoolean(lval). - // Note: We do not directly call ToBoolean: JumpIfNot does. - // 4. If lbool is true, return lval. - ctx.add_instruction(Instruction::LogicalNot); - } - oxc_syntax::operator::AssignmentOperator::LogicalNullish => { - // 3. If lval is neither undefined nor null, return lval. - ctx.add_instruction(Instruction::IsNullOrUndefined); - } - _ => unreachable!(), - } - - let jump_to_end = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); - - // We're returning the right expression, so we discard the left - // value at the top of the stack. - ctx.add_instruction(Instruction::Store); - - // 5. If IsAnonymousFunctionDefinition(AssignmentExpression) - // is true and IsIdentifierRef of LeftHandSideExpression is true, - // then - if is_identifier_ref && is_anonymous_function_definition(&self.right) { - // a. Let lhs be the StringValue of LeftHandSideExpression. - // b. Let rval be ? NamedEvaluation of AssignmentExpression with argument lhs. - ctx.name_identifier = Some(NamedEvaluationParameter::ReferenceStack); - self.right.compile(ctx); - } else { - // 6. Else - // a. Let rref be ? Evaluation of AssignmentExpression. - self.right.compile(ctx); - // b. Let rval be ? GetValue(rref). - if is_reference(&self.right) { - ctx.add_instruction(Instruction::GetValue); - } - } - - // 7. Perform ? PutValue(lref, rval). - ctx.add_instruction(Instruction::LoadCopy); - ctx.add_instruction(Instruction::PopReference); - ctx.add_instruction(Instruction::PutValue); - - // 4. ... return lval. - ctx.set_jump_target_here(jump_to_end); - ctx.add_instruction(Instruction::Store); - } else { - // 2. let lval be ? GetValue(lref). - ctx.add_instruction(Instruction::GetValueKeepReference); - ctx.add_instruction(Instruction::Load); - ctx.add_instruction(Instruction::PushReference); - // 3. Let rref be ? Evaluation of AssignmentExpression. - self.right.compile(ctx); - - // 4. Let rval be ? GetValue(rref). - if is_reference(&self.right) { - ctx.add_instruction(Instruction::GetValue); - } - - // 5. Let assignmentOpText be the source text matched by AssignmentOperator. - // 6. Let opText be the sequence of Unicode code points associated with assignmentOpText in the following table: - let op_text = match self.operator { - oxc_syntax::operator::AssignmentOperator::Addition => BinaryOperator::Addition, - oxc_syntax::operator::AssignmentOperator::Subtraction => { - BinaryOperator::Subtraction - } - oxc_syntax::operator::AssignmentOperator::Multiplication => { - BinaryOperator::Multiplication - } - oxc_syntax::operator::AssignmentOperator::Division => BinaryOperator::Division, - oxc_syntax::operator::AssignmentOperator::Remainder => BinaryOperator::Remainder, - oxc_syntax::operator::AssignmentOperator::ShiftLeft => BinaryOperator::ShiftLeft, - oxc_syntax::operator::AssignmentOperator::ShiftRight => BinaryOperator::ShiftRight, - oxc_syntax::operator::AssignmentOperator::ShiftRightZeroFill => { - BinaryOperator::ShiftRightZeroFill - } - oxc_syntax::operator::AssignmentOperator::BitwiseOR => BinaryOperator::BitwiseOR, - oxc_syntax::operator::AssignmentOperator::BitwiseXOR => BinaryOperator::BitwiseXOR, - oxc_syntax::operator::AssignmentOperator::BitwiseAnd => BinaryOperator::BitwiseAnd, - oxc_syntax::operator::AssignmentOperator::Exponential => { - BinaryOperator::Exponential - } - _ => unreachable!(), - }; - // 7. Let r be ? ApplyStringOrNumericBinaryOperator(lval, opText, rval). - ctx.add_instruction(Instruction::ApplyStringOrNumericBinaryOperator(op_text)); - ctx.add_instruction(Instruction::LoadCopy); - // 8. Perform ? PutValue(lref, r). - ctx.add_instruction(Instruction::PopReference); - ctx.add_instruction(Instruction::PutValue); - // 9. Return r. - ctx.add_instruction(Instruction::Store); + pub(crate) fn compile_eval_body(agent: &mut Agent, body: &[Statement]) -> Executable { + if agent.options.print_internals { + eprintln!(); + eprintln!("=== Compiling Eval Body ==="); + eprintln!(); } - } -} - -impl CompileEvaluation for ast::ParenthesizedExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - self.expression.compile(ctx); - } -} + let mut ctx = CompileContext::new(agent); -impl CompileEvaluation for ast::ArrowFunctionExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - // CompileContext holds a name identifier for us if this is NamedEvaluation. - let identifier = ctx.name_identifier.take(); - ctx.add_arrow_function_expression(ArrowFunctionExpression { - expression: SendableRef::new(unsafe { - std::mem::transmute::< - &ast::ArrowFunctionExpression<'_>, - &'static ast::ArrowFunctionExpression<'static>, - >(self) - }), - identifier, - }); + ctx.compile_statements(body); + ctx.do_implicit_return(); + ctx.finish() } } -impl CompileEvaluation for ast::Function<'_> { - fn compile(&self, ctx: &mut CompileContext) { - // CompileContext holds a name identifier for us if this is NamedEvaluation. - let identifier = ctx.name_identifier.take(); - ctx.add_instruction_with_function_expression( - Instruction::InstantiateOrdinaryFunctionExpression, - FunctionExpression { - expression: SendableRef::new(unsafe { - std::mem::transmute::<&ast::Function<'_>, &'static ast::Function<'static>>(self) - }), - identifier, - compiled_bytecode: None, - }, - ); - } -} +impl Index for Agent { + type Output = ExecutableHeapData; -impl CompileEvaluation for ast::ObjectExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - // TODO: Consider preparing the properties onto the stack and creating - // the object with a known size. - ctx.add_instruction(Instruction::ObjectCreate); - for property in self.properties.iter() { - match property { - ast::ObjectPropertyKind::ObjectProperty(prop) => { - let mut is_proto_setter = false; - match &prop.key { - ast::PropertyKey::ArrayExpression(init) => init.compile(ctx), - ast::PropertyKey::ArrowFunctionExpression(init) => init.compile(ctx), - ast::PropertyKey::AssignmentExpression(init) => init.compile(ctx), - ast::PropertyKey::AwaitExpression(init) => init.compile(ctx), - ast::PropertyKey::BigIntLiteral(init) => init.compile(ctx), - ast::PropertyKey::BinaryExpression(init) => init.compile(ctx), - ast::PropertyKey::BooleanLiteral(init) => init.compile(ctx), - ast::PropertyKey::CallExpression(init) => init.compile(ctx), - ast::PropertyKey::ChainExpression(init) => init.compile(ctx), - ast::PropertyKey::ClassExpression(init) => init.compile(ctx), - ast::PropertyKey::ComputedMemberExpression(init) => init.compile(ctx), - ast::PropertyKey::ConditionalExpression(init) => init.compile(ctx), - ast::PropertyKey::FunctionExpression(init) => init.compile(ctx), - ast::PropertyKey::Identifier(init) => init.compile(ctx), - ast::PropertyKey::ImportExpression(init) => init.compile(ctx), - ast::PropertyKey::LogicalExpression(init) => init.compile(ctx), - ast::PropertyKey::MetaProperty(init) => init.compile(ctx), - ast::PropertyKey::NewExpression(init) => init.compile(ctx), - ast::PropertyKey::NullLiteral(init) => init.compile(ctx), - ast::PropertyKey::NumericLiteral(init) => init.compile(ctx), - ast::PropertyKey::ObjectExpression(init) => init.compile(ctx), - ast::PropertyKey::ParenthesizedExpression(init) => init.compile(ctx), - ast::PropertyKey::PrivateFieldExpression(init) => init.compile(ctx), - ast::PropertyKey::PrivateIdentifier(_init) => todo!(), - ast::PropertyKey::PrivateInExpression(init) => init.compile(ctx), - ast::PropertyKey::RegExpLiteral(init) => init.compile(ctx), - ast::PropertyKey::SequenceExpression(init) => init.compile(ctx), - ast::PropertyKey::StaticIdentifier(id) => { - if id.name == "__proto__" { - if prop.kind == ast::PropertyKind::Init && !prop.shorthand { - // If property key is "__proto__" then we - // should dispatch a SetPrototype instruction. - is_proto_setter = true; - } else { - ctx.add_instruction_with_constant( - Instruction::StoreConstant, - BUILTIN_STRING_MEMORY.__proto__, - ); - } - } else { - let identifier = PropertyKey::from_str(ctx.agent, &id.name); - ctx.add_instruction_with_constant( - Instruction::StoreConstant, - identifier, - ); - } - } - ast::PropertyKey::StaticMemberExpression(init) => init.compile(ctx), - ast::PropertyKey::StringLiteral(init) => { - let identifier = PropertyKey::from_str(ctx.agent, &init.value); - ctx.add_instruction_with_constant( - Instruction::StoreConstant, - identifier, - ); - } - ast::PropertyKey::Super(_) => unreachable!(), - ast::PropertyKey::TaggedTemplateExpression(init) => init.compile(ctx), - ast::PropertyKey::TemplateLiteral(init) => init.compile(ctx), - ast::PropertyKey::ThisExpression(init) => init.compile(ctx), - ast::PropertyKey::UnaryExpression(init) => init.compile(ctx), - ast::PropertyKey::UpdateExpression(init) => init.compile(ctx), - ast::PropertyKey::YieldExpression(init) => init.compile(ctx), - ast::PropertyKey::JSXElement(_) - | ast::PropertyKey::JSXFragment(_) - | ast::PropertyKey::TSAsExpression(_) - | ast::PropertyKey::TSSatisfiesExpression(_) - | ast::PropertyKey::TSTypeAssertion(_) - | ast::PropertyKey::TSNonNullExpression(_) - | ast::PropertyKey::TSInstantiationExpression(_) => unreachable!(), - } - if let Some(prop_key_expression) = prop.key.as_expression() { - if is_reference(prop_key_expression) { - assert!(!is_proto_setter); - ctx.add_instruction(Instruction::GetValue); - } - } - if !is_proto_setter { - // Prototype setter doesn't need the key. - ctx.add_instruction(Instruction::Load); - } - match prop.kind { - ast::PropertyKind::Init => { - if !is_proto_setter && is_anonymous_function_definition(&prop.value) { - ctx.name_identifier = Some(NamedEvaluationParameter::Stack); - } - prop.value.compile(ctx); - if is_reference(&prop.value) { - ctx.add_instruction(Instruction::GetValue); - } - // 7. If isProtoSetter is true, then - if is_proto_setter { - // a. If propValue is an Object or propValue is null, then - // i. Perform ! object.[[SetPrototypeOf]](propValue). - // b. Return unused. - ctx.add_instruction(Instruction::ObjectSetPrototype); - } else { - ctx.add_instruction(Instruction::ObjectDefineProperty); - } - } - ast::PropertyKind::Get | ast::PropertyKind::Set => { - let is_get = prop.kind == ast::PropertyKind::Get; - let ast::Expression::FunctionExpression(function_expression) = - &prop.value - else { - unreachable!() - }; - ctx.add_instruction_with_function_expression_and_immediate( - if is_get { - Instruction::ObjectDefineGetter - } else { - Instruction::ObjectDefineSetter - }, - FunctionExpression { - expression: SendableRef::new(unsafe { - std::mem::transmute::< - &ast::Function<'_>, - &'static ast::Function<'static>, - >( - function_expression - ) - }), - identifier: None, - compiled_bytecode: None, - }, - // enumerable: true, - true.into(), - ); - } - } - } - ast::ObjectPropertyKind::SpreadProperty(spread) => { - spread.argument.compile(ctx); - if is_reference(&spread.argument) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::CopyDataProperties); - } - } - } - // 3. Return obj - ctx.add_instruction(Instruction::Store); + fn index(&self, index: Executable) -> &Self::Output { + self.heap + .executables + .get(index.get_index()) + .expect("Executable out of bounds") } } -impl CompileEvaluation for ast::ArrayExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - let elements_min_count = self.elements.len(); - ctx.add_instruction_with_immediate(Instruction::ArrayCreate, elements_min_count); - for ele in &self.elements { - match ele { - ast::ArrayExpressionElement::SpreadElement(spread) => { - spread.argument.compile(ctx); - if is_reference(&spread.argument) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::GetIteratorSync); - - let iteration_start = ctx.get_jump_index_to_here(); - let iteration_end = - ctx.add_instruction_with_jump_slot(Instruction::IteratorStepValue); - ctx.add_instruction(Instruction::ArrayPush); - ctx.add_jump_instruction_to_index(Instruction::Jump, iteration_start); - ctx.set_jump_target_here(iteration_end); - } - ast::ArrayExpressionElement::Elision(_) => { - ctx.add_instruction(Instruction::ArrayElision); - } - _ => { - let expression = ele.to_expression(); - expression.compile(ctx); - if is_reference(expression) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::ArrayPush); - } - } - } - ctx.add_instruction(Instruction::Store); +impl IndexMut for Agent { + fn index_mut(&mut self, index: Executable) -> &mut Self::Output { + self.heap + .executables + .get_mut(index.get_index()) + .expect("Executable out of bounds") } } -fn compile_arguments(arguments: &[ast::Argument], ctx: &mut CompileContext) -> usize { - // If the arguments don't contain the spread operator, then we can know the - // number of arguments at compile-time and we can pass it as an argument to - // the call instruction. - // Otherwise, the first time we find a spread operator, we need to start - // tracking the number of arguments in the compiled bytecode. We store this - // number in the result value, and we pass u16::MAX to the call instruction. - let mut known_num_arguments = Some(0 as IndexType); - - for argument in arguments { - // If known_num_arguments is None, the stack contains the number of - // arguments, followed by the arguments. - if let ast::Argument::SpreadElement(spread) = argument { - if let Some(num_arguments) = known_num_arguments.take() { - ctx.add_instruction_with_constant(Instruction::LoadConstant, num_arguments); - } - - spread.argument.compile(ctx); - if is_reference(&spread.argument) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::GetIteratorSync); - - let iteration_start = ctx.get_jump_index_to_here(); - let iteration_end = ctx.add_instruction_with_jump_slot(Instruction::IteratorStepValue); - // result: value; stack: [num, ...args] - ctx.add_instruction(Instruction::LoadStoreSwap); - // result: num; stack: [value, ...args] - ctx.add_instruction(Instruction::Increment); - // result: num + 1; stack: [value, ...args] - ctx.add_instruction(Instruction::Load); - // stack: [num + 1, value, ...args] - ctx.add_jump_instruction_to_index(Instruction::Jump, iteration_start); - ctx.set_jump_target_here(iteration_end); - } else { - let expression = argument.to_expression(); - expression.compile(ctx); - if is_reference(expression) { - ctx.add_instruction(Instruction::GetValue); - } - if let Some(num_arguments) = known_num_arguments.as_mut() { - ctx.add_instruction(Instruction::Load); - // stack: [value, ...args] - - if *num_arguments < IndexType::MAX - 1 { - *num_arguments += 1; - } else { - // If we overflow, we switch to tracking the number on the - // result value. - debug_assert_eq!(*num_arguments, IndexType::MAX - 1); - known_num_arguments = None; - ctx.add_instruction_with_constant( - Instruction::LoadConstant, - Value::from(IndexType::MAX), - ); - // stack: [num + 1, value, ...args] - } - } else { - // result: value; stack: [num, ...args] - ctx.add_instruction(Instruction::LoadStoreSwap); - // result: num; stack: [value, ...args] - ctx.add_instruction(Instruction::Increment); - // result: num + 1; stack: [value, ...args] - ctx.add_instruction(Instruction::Load); - // stack: [num + 1, value, ...args] - } - } - } - - if let Some(num_arguments) = known_num_arguments { - assert_ne!(num_arguments, IndexType::MAX); - num_arguments as usize - } else { - // stack: [num, ...args] - ctx.add_instruction(Instruction::Store); - // result: num; stack: [...args] - IndexType::MAX as usize +impl CreateHeapData for Heap { + fn create(&mut self, data: ExecutableHeapData) -> Executable { + self.executables.push(data); + let index = u32::try_from(self.executables.len()).expect("Executables overflowed"); + // SAFETY: After pushing to executables, the vector cannot be empty. + Executable(unsafe { NonZeroU32::new_unchecked(index) }) } } -impl CompileEvaluation for CallExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - // Direct eval - if !self.optional { - if let ast::Expression::Identifier(ident) = &self.callee { - if ident.name == "eval" { - let num_arguments = compile_arguments(&self.arguments, ctx); - ctx.add_instruction_with_immediate(Instruction::DirectEvalCall, num_arguments); - return; - } - } - } - - // 1. Let ref be ? Evaluation of CallExpression. - ctx.is_call_optional_chain_this = is_chain_expression(&self.callee); - let is_super_call = matches!(self.callee, ast::Expression::Super(_)); - let need_pop_reference = if is_super_call { - // Note: There is nothing to do with super calls here. - false - } else { - self.callee.compile(ctx); - if is_reference(&self.callee) { - // 2. Let func be ? GetValue(ref). - ctx.add_instruction(Instruction::GetValueKeepReference); - // Optimization: If we know arguments is empty, we don't need to - // worry about arguments evaluation clobbering our function's this - // reference. - if !self.arguments.is_empty() { - ctx.add_instruction(Instruction::PushReference); - true - } else { - false - } - } else { - false - } - }; - - if self.optional { - // Optional Chains - - // Load copy of func to stack. - ctx.add_instruction(Instruction::LoadCopy); - // 3. If func is either undefined or null, then - ctx.add_instruction(Instruction::IsNullOrUndefined); - // a. Return undefined - - // To return undefined we jump over the rest of the call handling. - let jump_over_call = if need_pop_reference { - // If we need to pop the reference stack, then we must do it - // here before we go to the nullish case handling. - // Note the inverted jump condition here! - let jump_to_call = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); - // Now we're in our local nullish case handling. - // First we pop our reference. - ctx.add_instruction(Instruction::PopReference); - // And now we're ready to jump over the call. - let jump_over_call = ctx.add_instruction_with_jump_slot(Instruction::Jump); - // But if we're jumping to call then we need to land here. - ctx.set_jump_target_here(jump_to_call); - jump_over_call - } else { - ctx.add_instruction_with_jump_slot(Instruction::JumpIfTrue) - }; - // Register our jump slot to the chain nullish case handling. - ctx.optional_chains.as_mut().unwrap().push(jump_over_call); - } else if !is_super_call { - ctx.add_instruction(Instruction::Load); - } - // If we're in an optional chain, we need to pluck it out while we're - // compiling the parameters: They do not join our chain. - let optional_chain = ctx.optional_chains.take(); - let num_arguments = compile_arguments(&self.arguments, ctx); - // After we're done with compiling parameters we go back into the chain. - if let Some(optional_chain) = optional_chain { - ctx.optional_chains.replace(optional_chain); - } - - if is_super_call { - ctx.add_instruction_with_immediate(Instruction::EvaluateSuper, num_arguments); - } else { - if need_pop_reference { - ctx.add_instruction(Instruction::PopReference); - } - ctx.add_instruction_with_immediate(Instruction::EvaluateCall, num_arguments); - } +impl HeapMarkAndSweep for Executable { + fn mark_values(&self, queues: &mut WorkQueues) { + queues.executables.push(*self); } -} - -impl CompileEvaluation for NewExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - self.callee.compile(ctx); - if is_reference(&self.callee) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::Load); - let num_arguments = compile_arguments(&self.arguments, ctx); - ctx.add_instruction_with_immediate(Instruction::EvaluateNew, num_arguments); + fn sweep_values(&mut self, compactions: &CompactionLists) { + compactions + .executables + .shift_non_zero_u32_index(&mut self.0); } } -impl CompileEvaluation for ast::MemberExpression<'_> { - /// ### [13.3.2 Property Accessors](https://tc39.es/ecma262/#sec-property-accessors) - fn compile(&self, ctx: &mut CompileContext) { - match self { - ast::MemberExpression::ComputedMemberExpression(x) => x.compile(ctx), - ast::MemberExpression::StaticMemberExpression(x) => x.compile(ctx), - ast::MemberExpression::PrivateFieldExpression(x) => x.compile(ctx), +impl HeapMarkAndSweep for ExecutableHeapData { + fn mark_values(&self, queues: &mut WorkQueues) { + let Self { + instructions: _, + constants, + function_expressions: _, + arrow_function_expressions: _, + class_initializer_bytecodes, + } = self; + constants.mark_values(queues); + for ele in class_initializer_bytecodes { + ele.0.mark_values(queues); } } -} - -impl CompileEvaluation for ast::ComputedMemberExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - // 1. Let baseReference be ? Evaluation of MemberExpression. - self.object.compile(ctx); - - // 2. Let baseValue be ? GetValue(baseReference). - if is_reference(&self.object) { - ctx.add_instruction(Instruction::GetValue); - } - - if self.optional { - // Optional Chains - - // Load copy of baseValue to stack. - ctx.add_instruction(Instruction::LoadCopy); - // 3. If baseValue is either undefined or null, then - ctx.add_instruction(Instruction::IsNullOrUndefined); - // a. Return undefined - - // To return undefined we jump over the property access. - let jump_over_property_access = - ctx.add_instruction_with_jump_slot(Instruction::JumpIfTrue); - // Register our jump slot to the chain nullish case handling. - ctx.optional_chains - .as_mut() - .unwrap() - .push(jump_over_property_access); - } else { - ctx.add_instruction(Instruction::Load); - } - - // If we're in an optional chain, we need to pluck it out while we're - // compiling the member expression: They do not join our chain. - let optional_chain = ctx.optional_chains.take(); - // 4. Return ? EvaluatePropertyAccessWithExpressionKey(baseValue, Expression, strict). - self.expression.compile(ctx); - if is_reference(&self.expression) { - ctx.add_instruction(Instruction::GetValue); - } - // After we're done with compiling the member expression we go back - // into the chain. - if let Some(optional_chain) = optional_chain { - ctx.optional_chains.replace(optional_chain); + fn sweep_values(&mut self, compactions: &CompactionLists) { + let Self { + instructions: _, + constants, + function_expressions: _, + arrow_function_expressions: _, + class_initializer_bytecodes, + } = self; + constants.sweep_values(compactions); + for ele in class_initializer_bytecodes { + ele.0.sweep_values(compactions); } - - ctx.add_instruction(Instruction::EvaluatePropertyAccessWithExpressionKey); } } -impl CompileEvaluation for ast::StaticMemberExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - // 1. Let baseReference be ? Evaluation of MemberExpression. - self.object.compile(ctx); - - // 2. Let baseValue be ? GetValue(baseReference). - if is_reference(&self.object) { - ctx.add_instruction(Instruction::GetValue); - } - - if self.optional { - // Optional Chains - - // Load copy of baseValue to stack. - ctx.add_instruction(Instruction::LoadCopy); - // 3. If baseValue is either undefined or null, then - ctx.add_instruction(Instruction::IsNullOrUndefined); - // a. Return undefined - - // To return undefined we jump over the property access. - let jump_over_property_access = - ctx.add_instruction_with_jump_slot(Instruction::JumpIfTrue); - - // Register our jump slot to the chain nullish case handling. - ctx.optional_chains - .as_mut() - .unwrap() - .push(jump_over_property_access); - - // Return copy of baseValue from stack if it is not. - ctx.add_instruction(Instruction::Store); - } - - // 4. Return EvaluatePropertyAccessWithIdentifierKey(baseValue, IdentifierName, strict). - let identifier = String::from_str(ctx.agent, self.property.name.as_str()); - ctx.add_instruction_with_identifier( - Instruction::EvaluatePropertyAccessWithIdentifierKey, - identifier, - ); - } -} - -impl CompileEvaluation for ast::PrivateFieldExpression<'_> { - fn compile(&self, _ctx: &mut CompileContext) { - todo!() - } -} - -impl CompileEvaluation for ast::AwaitExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - // 1. Let exprRef be ? Evaluation of UnaryExpression. - self.argument.compile(ctx); - // 2. Let value be ? GetValue(exprRef). - if is_reference(&self.argument) { - ctx.add_instruction(Instruction::GetValue); - } - // 3. Return ? Await(value). - ctx.add_instruction(Instruction::Await); - } -} - -impl CompileEvaluation for ast::ChainExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - // It's possible that we're compiling a ChainExpression inside a call - // that is itself in a ChainExpression. We will drop into the previous - // chain in this case. - let installed_own_chains = if ctx.optional_chains.is_none() { - // We prepare for at least two chains to exist. One chain is often - // enough but two is a bit safer. Three is rare. - ctx.optional_chains.replace(Vec::with_capacity(2)); - true - } else { - false - }; - let need_get_value = match self.expression { - ast::ChainElement::CallExpression(ref call) => { - call.compile(ctx); - false - } - ast::ChainElement::ComputedMemberExpression(ref call) => { - call.compile(ctx); - true - } - ast::ChainElement::StaticMemberExpression(ref call) => { - call.compile(ctx); - true - } - ast::ChainElement::PrivateFieldExpression(ref call) => { - call.compile(ctx); - true - } - }; - // If chain succeeded, we come here and should jump over the nullish - // case handling. - if need_get_value { - // If we handled a member or field expression, we need to get its - // value. However, there's a chance that we cannot just throw away - // the reference. If the result of the chain expression is going to - // be used in a (potentially optional) call expression then we need - // both its value and its reference. - if ctx.is_call_optional_chain_this { - ctx.is_call_optional_chain_this = false; - ctx.add_instruction(Instruction::GetValueKeepReference); - } else { - ctx.add_instruction(Instruction::GetValue); - } - } - if installed_own_chains { - let jump_over_return_undefined = ctx.add_instruction_with_jump_slot(Instruction::Jump); - let own_chains = ctx.optional_chains.take().unwrap(); - for jump_to_return_undefined in own_chains { - ctx.set_jump_target_here(jump_to_return_undefined); - } - // All optional chains come here with a copy of their null or - // undefined baseValue on the stack. Pop it off. - ctx.add_instruction(Instruction::Store); - // Replace any possible null with undefined. - ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); - ctx.set_jump_target_here(jump_over_return_undefined); - } - } -} - -impl CompileEvaluation for ast::ConditionalExpression<'_> { - /// ## [13.14 Conditional Operator ( ? : )](https://tc39.es/ecma262/#sec-conditional-operator) - /// ### [13.14.1 Runtime Semantics: Evaluation](https://tc39.es/ecma262/#sec-conditional-operator-runtime-semantics-evaluation) - fn compile(&self, ctx: &mut CompileContext) { - // 1. Let lref be ? Evaluation of ShortCircuitExpression. - self.test.compile(ctx); - // 2. Let lval be ToBoolean(? GetValue(lref)). - if is_reference(&self.test) { - ctx.add_instruction(Instruction::GetValue); - } - // Jump over first AssignmentExpression (consequent) if test fails. - // Note: JumpIfNot performs ToBoolean from above step. - let jump_to_second = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); - // 3. If lval is true, then - // a. Let trueRef be ? Evaluation of the first AssignmentExpression. - self.consequent.compile(ctx); - // b. Return ? GetValue(trueRef). - if is_reference(&self.consequent) { - ctx.add_instruction(Instruction::GetValue); - } - // Jump over second AssignmentExpression (alternate). - let jump_over_second = ctx.add_instruction_with_jump_slot(Instruction::Jump); - // 4. Else, - ctx.set_jump_target_here(jump_to_second); - // a. Let falseRef be ? Evaluation of the second AssignmentExpression. - self.alternate.compile(ctx); - // b. Return ? GetValue(falseRef). - if is_reference(&self.alternate) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.set_jump_target_here(jump_over_second); - } -} - -impl CompileEvaluation for ast::ImportExpression<'_> { - fn compile(&self, _ctx: &mut CompileContext) { - todo!() - } -} - -impl CompileEvaluation for ast::MetaProperty<'_> { - fn compile(&self, _ctx: &mut CompileContext) { - todo!() - } -} - -impl CompileEvaluation for ast::PrivateInExpression<'_> { - fn compile(&self, _ctx: &mut CompileContext) { - todo!() - } -} - -impl CompileEvaluation for ast::RegExpLiteral<'_> { - fn compile(&self, ctx: &mut CompileContext) { - let pattern = match self.regex.pattern { - ast::RegExpPattern::Raw(pattern) => pattern, - ast::RegExpPattern::Invalid(pattern) => pattern, - // We probably shouldn't be getting parsed RegExps? - ast::RegExpPattern::Pattern(_) => unreachable!(), - }; - let pattern = String::from_str(ctx.agent, pattern); - let regexp = - reg_exp_create(ctx.agent, pattern.into_value(), Some(self.regex.flags)).unwrap(); - ctx.add_instruction_with_constant(Instruction::StoreConstant, regexp); - } -} - -impl CompileEvaluation for ast::SequenceExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - for expr in &self.expressions { - expr.compile(ctx); - } - } -} - -impl CompileEvaluation for ast::Super { - fn compile(&self, _ctx: &mut CompileContext) { - todo!() - } -} - -impl CompileEvaluation for ast::TaggedTemplateExpression<'_> { - fn compile(&self, _ctx: &mut CompileContext) { - todo!() - } -} - -impl CompileEvaluation for ast::TemplateLiteral<'_> { - fn compile(&self, ctx: &mut CompileContext) { - if self.is_no_substitution_template() { - let constant = String::from_str( - ctx.agent, - self.quasi() - .as_ref() - .expect("Invalid escape sequence in template literal") - .as_str(), - ); - ctx.add_instruction_with_constant(Instruction::StoreConstant, constant); - } else { - let mut count = 0; - let mut quasis = self.quasis.as_slice(); - let mut expressions = self.expressions.as_slice(); - while let Some((head, rest)) = quasis.split_first() { - quasis = rest; - // 1. Let head be the TV of TemplateHead as defined in 12.9.6. - let head = - String::from_str(ctx.agent, head.value.cooked.as_ref().unwrap().as_str()); - ctx.add_instruction_with_constant(Instruction::LoadConstant, head); - count += 1; - if let Some((expression, rest)) = expressions.split_first() { - expressions = rest; - // 2. Let subRef be ? Evaluation of Expression. - expression.compile(ctx); - if is_reference(expression) { - // 3. Let sub be ? GetValue(subRef). - ctx.add_instruction(Instruction::GetValue); - } - // 4. Let middle be ? ToString(sub). - // Note: This is done by StringConcat. - ctx.add_instruction(Instruction::Load); - count += 1; - } - // 5. Let tail be ? Evaluation of TemplateSpans. - } - // 6. Return the string-concatenation of head, middle, and tail. - ctx.add_instruction_with_immediate(Instruction::StringConcat, count); - } - } -} - -impl CompileEvaluation for ast::ThisExpression { - fn compile(&self, ctx: &mut CompileContext) { - ctx.add_instruction(Instruction::ResolveThisBinding); - } -} - -impl CompileEvaluation for ast::YieldExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - if self.delegate { - todo!("`yield*` is not yet supported"); - } - if let Some(arg) = &self.argument { - // YieldExpression : yield AssignmentExpression - // 1. Let exprRef be ? Evaluation of AssignmentExpression. - arg.compile(ctx); - // 2. Let value be ? GetValue(exprRef). - if is_reference(arg) { - ctx.add_instruction(Instruction::GetValue); - } - } else { - // YieldExpression : yield - // 1. Return ? Yield(undefined). - ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); - } - // 3. Return ? Yield(value). - ctx.add_instruction(Instruction::Yield); - } -} - -impl CompileEvaluation for ast::Expression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - match self { - ast::Expression::ArrayExpression(x) => x.compile(ctx), - ast::Expression::ArrowFunctionExpression(x) => x.compile(ctx), - ast::Expression::AssignmentExpression(x) => x.compile(ctx), - ast::Expression::AwaitExpression(x) => x.compile(ctx), - ast::Expression::BigIntLiteral(x) => x.compile(ctx), - ast::Expression::BinaryExpression(x) => x.compile(ctx), - ast::Expression::BooleanLiteral(x) => x.compile(ctx), - ast::Expression::CallExpression(x) => x.compile(ctx), - ast::Expression::ChainExpression(x) => x.compile(ctx), - ast::Expression::ClassExpression(x) => x.compile(ctx), - ast::Expression::ComputedMemberExpression(x) => x.compile(ctx), - ast::Expression::ConditionalExpression(x) => x.compile(ctx), - ast::Expression::FunctionExpression(x) => x.compile(ctx), - ast::Expression::Identifier(x) => x.compile(ctx), - ast::Expression::ImportExpression(x) => x.compile(ctx), - ast::Expression::LogicalExpression(x) => x.compile(ctx), - ast::Expression::MetaProperty(x) => x.compile(ctx), - ast::Expression::NewExpression(x) => x.compile(ctx), - ast::Expression::NullLiteral(x) => x.compile(ctx), - ast::Expression::NumericLiteral(x) => x.compile(ctx), - ast::Expression::ObjectExpression(x) => x.compile(ctx), - ast::Expression::ParenthesizedExpression(x) => x.compile(ctx), - ast::Expression::PrivateFieldExpression(x) => x.compile(ctx), - ast::Expression::PrivateInExpression(x) => x.compile(ctx), - ast::Expression::RegExpLiteral(x) => x.compile(ctx), - ast::Expression::SequenceExpression(x) => x.compile(ctx), - ast::Expression::StaticMemberExpression(x) => x.compile(ctx), - ast::Expression::StringLiteral(x) => x.compile(ctx), - ast::Expression::Super(x) => x.compile(ctx), - ast::Expression::TaggedTemplateExpression(x) => x.compile(ctx), - ast::Expression::TemplateLiteral(x) => x.compile(ctx), - ast::Expression::ThisExpression(x) => x.compile(ctx), - ast::Expression::UnaryExpression(x) => x.compile(ctx), - ast::Expression::UpdateExpression(x) => x.compile(ctx), - ast::Expression::YieldExpression(x) => x.compile(ctx), - ast::Expression::JSXElement(_) - | ast::Expression::JSXFragment(_) - | ast::Expression::TSAsExpression(_) - | ast::Expression::TSSatisfiesExpression(_) - | ast::Expression::TSTypeAssertion(_) - | ast::Expression::TSNonNullExpression(_) - | ast::Expression::TSInstantiationExpression(_) => unreachable!(), - } - } -} - -impl CompileEvaluation for ast::UpdateExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - match &self.argument { - ast::SimpleAssignmentTarget::AssignmentTargetIdentifier(x) => x.compile(ctx), - ast::SimpleAssignmentTarget::ComputedMemberExpression(x) => x.compile(ctx), - ast::SimpleAssignmentTarget::PrivateFieldExpression(_) => todo!(), - ast::SimpleAssignmentTarget::StaticMemberExpression(x) => x.compile(ctx), - ast::SimpleAssignmentTarget::TSAsExpression(_) - | ast::SimpleAssignmentTarget::TSInstantiationExpression(_) - | ast::SimpleAssignmentTarget::TSNonNullExpression(_) - | ast::SimpleAssignmentTarget::TSSatisfiesExpression(_) - | ast::SimpleAssignmentTarget::TSTypeAssertion(_) => unreachable!(), - } - ctx.add_instruction(Instruction::GetValueKeepReference); - if !self.prefix { - // The return value of postfix increment/decrement is the value - // after ToNumeric. - ctx.add_instruction(Instruction::ToNumeric); - ctx.add_instruction(Instruction::LoadCopy); - } - match self.operator { - oxc_syntax::operator::UpdateOperator::Increment => { - ctx.add_instruction(Instruction::Increment); - } - oxc_syntax::operator::UpdateOperator::Decrement => { - ctx.add_instruction(Instruction::Decrement); - } - } - if self.prefix { - ctx.add_instruction(Instruction::LoadCopy); - } - ctx.add_instruction(Instruction::PutValue); - ctx.add_instruction(Instruction::Store); - } -} - -impl CompileEvaluation for ast::ExpressionStatement<'_> { - /// ### [14.5.1 Runtime Semantics: Evaluation](https://tc39.es/ecma262/#sec-expression-statement-runtime-semantics-evaluation) - /// `ExpressionStatement : Expression ;` - fn compile(&self, ctx: &mut CompileContext) { - // 1. Let exprRef be ? Evaluation of Expression. - self.expression.compile(ctx); - if is_reference(&self.expression) { - // 2. Return ? GetValue(exprRef). - ctx.add_instruction(Instruction::GetValue); - } - } -} - -impl CompileEvaluation for ast::ReturnStatement<'_> { - fn compile(&self, ctx: &mut CompileContext) { - if let Some(expr) = &self.argument { - expr.compile(ctx); - if is_reference(expr) { - ctx.add_instruction(Instruction::GetValue); - } - } else { - ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); - } - ctx.add_instruction(Instruction::Return); - } -} - -impl CompileEvaluation for ast::IfStatement<'_> { - fn compile(&self, ctx: &mut CompileContext) { - // if (test) consequent - self.test.compile(ctx); - if is_reference(&self.test) { - ctx.add_instruction(Instruction::GetValue); - } - // jump over consequent if test fails - let jump_to_else = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); - self.consequent.compile(ctx); - let mut jump_over_else: Option = None; - if let Some(alternate) = &self.alternate { - // Optimisation: If the an else branch exists, the consequent - // branch needs to end in a jump over it. But if the consequent - // branch ends in a return statement that jump becomes unnecessary. - if ctx.peek_last_instruction() != Some(Instruction::Return.as_u8()) { - jump_over_else = Some(ctx.add_instruction_with_jump_slot(Instruction::Jump)); - } - - // Jump to else-branch when if test fails. - ctx.set_jump_target_here(jump_to_else); - alternate.compile(ctx); - } else { - // Jump over if-branch when if test fails. - ctx.set_jump_target_here(jump_to_else); - } - - // Jump over else-branch at the end of if-branch if necessary. - // (See optimisation above for when it is not needed.) - if let Some(jump_over_else) = jump_over_else { - ctx.set_jump_target_here(jump_over_else); - } - } -} - -impl CompileEvaluation for ast::ArrayPattern<'_> { - fn compile(&self, ctx: &mut CompileContext) { - if self.elements.is_empty() && self.rest.is_none() { - return; - } - - ctx.add_instruction(Instruction::Store); - ctx.add_instruction(Instruction::GetIteratorSync); - - if !self.contains_expression() { - simple_array_pattern( - ctx, - self.elements.iter().map(Option::as_ref), - self.rest.as_deref(), - self.elements.len(), - ctx.lexical_binding_state, - ); - } else { - complex_array_pattern( - ctx, - self.elements.iter().map(Option::as_ref), - self.rest.as_deref(), - ctx.lexical_binding_state, - ); - } - } -} - -fn simple_array_pattern<'a, 'b, I>( - ctx: &mut CompileContext, - elements: I, - rest: Option<&BindingRestElement>, - num_elements: usize, - has_environment: bool, -) where - 'b: 'a, - I: Iterator>>, -{ - ctx.add_instruction_with_immediate_and_immediate( - Instruction::BeginSimpleArrayBindingPattern, - num_elements, - has_environment.into(), - ); - - for ele in elements { - let Some(ele) = ele else { - ctx.add_instruction(Instruction::BindingPatternSkip); - continue; - }; - match &ele.kind { - ast::BindingPatternKind::BindingIdentifier(identifier) => { - let identifier_string = ctx.create_identifier(&identifier.name); - ctx.add_instruction_with_identifier( - Instruction::BindingPatternBind, - identifier_string, - ) - } - ast::BindingPatternKind::ObjectPattern(pattern) => { - ctx.add_instruction(Instruction::BindingPatternGetValue); - simple_object_pattern(pattern, ctx, has_environment); - } - ast::BindingPatternKind::ArrayPattern(pattern) => { - ctx.add_instruction(Instruction::BindingPatternGetValue); - simple_array_pattern( - ctx, - pattern.elements.iter().map(Option::as_ref), - pattern.rest.as_deref(), - pattern.elements.len(), - has_environment, - ); - } - ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), - } - } - - if let Some(rest) = rest { - match &rest.argument.kind { - ast::BindingPatternKind::BindingIdentifier(identifier) => { - let identifier_string = ctx.create_identifier(&identifier.name); - ctx.add_instruction_with_identifier( - Instruction::BindingPatternBindRest, - identifier_string, - ); - } - ast::BindingPatternKind::ObjectPattern(pattern) => { - ctx.add_instruction(Instruction::BindingPatternGetRestValue); - simple_object_pattern(pattern, ctx, has_environment); - } - ast::BindingPatternKind::ArrayPattern(pattern) => { - ctx.add_instruction(Instruction::BindingPatternGetRestValue); - simple_array_pattern( - ctx, - pattern.elements.iter().map(Option::as_ref), - pattern.rest.as_deref(), - pattern.elements.len(), - has_environment, - ); - } - ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), - } - } else { - ctx.add_instruction(Instruction::FinishBindingPattern); - } -} - -fn complex_array_pattern<'a, 'b, I>( - ctx: &mut CompileContext, - elements: I, - rest: Option<&BindingRestElement>, - has_environment: bool, -) where - 'b: 'a, - I: Iterator>>, -{ - for ele in elements { - ctx.add_instruction(Instruction::IteratorStepValueOrUndefined); - - let Some(ele) = ele else { - continue; - }; - - let binding_pattern = match &ele.kind { - ast::BindingPatternKind::AssignmentPattern(pattern) => { - // Run the initializer if the result value is undefined. - ctx.add_instruction(Instruction::LoadCopy); - ctx.add_instruction(Instruction::IsUndefined); - let jump_slot = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); - ctx.add_instruction(Instruction::Store); - if is_anonymous_function_definition(&pattern.right) { - if let ast::BindingPatternKind::BindingIdentifier(identifier) = - &pattern.left.kind - { - let identifier_string = ctx.create_identifier(&identifier.name); - ctx.add_instruction_with_constant( - Instruction::StoreConstant, - identifier_string, - ); - ctx.name_identifier = Some(NamedEvaluationParameter::Result); - } - } - pattern.right.compile(ctx); - ctx.name_identifier = None; - if is_reference(&pattern.right) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::Load); - ctx.set_jump_target_here(jump_slot); - ctx.add_instruction(Instruction::Store); - - &pattern.left.kind - } - _ => &ele.kind, - }; - - match binding_pattern { - ast::BindingPatternKind::BindingIdentifier(identifier) => { - let identifier_string = ctx.create_identifier(&identifier.name); - ctx.add_instruction_with_identifier(Instruction::ResolveBinding, identifier_string); - if !has_environment { - ctx.add_instruction(Instruction::PutValue); - } else { - ctx.add_instruction(Instruction::InitializeReferencedBinding); - } - } - ast::BindingPatternKind::ObjectPattern(pattern) => { - ctx.add_instruction(Instruction::Load); - pattern.compile(ctx); - } - ast::BindingPatternKind::ArrayPattern(pattern) => { - ctx.add_instruction(Instruction::Load); - pattern.compile(ctx); - } - ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), - } - } - - if let Some(rest) = rest { - ctx.add_instruction(Instruction::IteratorRestIntoArray); - match &rest.argument.kind { - ast::BindingPatternKind::BindingIdentifier(identifier) => { - let identifier_string = ctx.create_identifier(&identifier.name); - ctx.add_instruction_with_identifier(Instruction::ResolveBinding, identifier_string); - if !has_environment { - ctx.add_instruction(Instruction::PutValue); - } else { - ctx.add_instruction(Instruction::InitializeReferencedBinding); - } - } - ast::BindingPatternKind::ObjectPattern(pattern) => { - ctx.add_instruction(Instruction::Load); - pattern.compile(ctx); - } - ast::BindingPatternKind::ArrayPattern(pattern) => { - ctx.add_instruction(Instruction::Load); - pattern.compile(ctx); - } - ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), - } - } else { - ctx.add_instruction(Instruction::IteratorClose); - } -} - -impl CompileEvaluation for ast::ObjectPattern<'_> { - fn compile(&self, ctx: &mut CompileContext) { - if !self.contains_expression() { - simple_object_pattern(self, ctx, ctx.lexical_binding_state); - } else { - complex_object_pattern(self, ctx, ctx.lexical_binding_state); - } - } -} - -fn simple_object_pattern( - pattern: &ast::ObjectPattern<'_>, - ctx: &mut CompileContext, - has_environment: bool, -) { - ctx.add_instruction_with_immediate( - Instruction::BeginSimpleObjectBindingPattern, - has_environment.into(), - ); - - for ele in &pattern.properties { - if ele.shorthand { - let ast::PropertyKey::StaticIdentifier(identifier) = &ele.key else { - unreachable!() - }; - assert!(matches!( - &ele.value.kind, - ast::BindingPatternKind::BindingIdentifier(_) - )); - let identifier_string = ctx.create_identifier(&identifier.name); - ctx.add_instruction_with_identifier(Instruction::BindingPatternBind, identifier_string); - } else { - let key_string = match &ele.key { - ast::PropertyKey::StaticIdentifier(identifier) => { - PropertyKey::from_str(ctx.agent, &identifier.name).into_value() - } - ast::PropertyKey::NumericLiteral(literal) => { - let numeric_value = Number::from_f64(ctx.agent, literal.value); - if let Number::Integer(_) = numeric_value { - numeric_value.into_value() - } else { - Number::to_string_radix_10(ctx.agent, numeric_value).into_value() - } - } - ast::PropertyKey::StringLiteral(literal) => { - PropertyKey::from_str(ctx.agent, &literal.value).into_value() - } - _ => unreachable!(), - }; - - match &ele.value.kind { - ast::BindingPatternKind::BindingIdentifier(identifier) => { - let value_identifier_string = ctx.create_identifier(&identifier.name); - ctx.add_instruction_with_identifier_and_constant( - Instruction::BindingPatternBindNamed, - value_identifier_string, - key_string, - ) - } - ast::BindingPatternKind::ObjectPattern(pattern) => { - ctx.add_instruction_with_constant( - Instruction::BindingPatternGetValueNamed, - key_string, - ); - simple_object_pattern(pattern, ctx, has_environment); - } - ast::BindingPatternKind::ArrayPattern(pattern) => { - ctx.add_instruction_with_constant( - Instruction::BindingPatternGetValueNamed, - key_string, - ); - simple_array_pattern( - ctx, - pattern.elements.iter().map(Option::as_ref), - pattern.rest.as_deref(), - pattern.elements.len(), - has_environment, - ); - } - ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), - } - } - } - - if let Some(rest) = &pattern.rest { - match &rest.argument.kind { - ast::BindingPatternKind::BindingIdentifier(identifier) => { - let identifier_string = ctx.create_identifier(&identifier.name); - ctx.add_instruction_with_identifier( - Instruction::BindingPatternBindRest, - identifier_string, - ); - } - _ => unreachable!(), - } - } else { - ctx.add_instruction(Instruction::FinishBindingPattern); - } -} - -fn complex_object_pattern( - object_pattern: &ast::ObjectPattern<'_>, - ctx: &mut CompileContext, - has_environment: bool, -) { - // 8.6.2 Runtime Semantics: BindingInitialization - // BindingPattern : ObjectBindingPattern - // 1. Perform ? RequireObjectCoercible(value). - // NOTE: RequireObjectCoercible throws in the same cases as ToObject, and other operations - // later on (such as GetV) also perform ToObject, so we convert to an object early. - ctx.add_instruction(Instruction::Store); - ctx.add_instruction(Instruction::ToObject); - ctx.add_instruction(Instruction::Load); - - for property in &object_pattern.properties { - match &property.key { - ast::PropertyKey::StaticIdentifier(identifier) => { - ctx.add_instruction(Instruction::Store); - ctx.add_instruction(Instruction::LoadCopy); - let identifier_string = ctx.create_identifier(&identifier.name); - ctx.add_instruction_with_identifier( - Instruction::EvaluatePropertyAccessWithIdentifierKey, - identifier_string, - ); - } - ast::PropertyKey::PrivateIdentifier(_) => todo!(), - _ => { - property.key.to_expression().compile(ctx); - ctx.add_instruction(Instruction::EvaluatePropertyAccessWithExpressionKey); - } - } - if object_pattern.rest.is_some() { - ctx.add_instruction(Instruction::GetValueKeepReference); - ctx.add_instruction(Instruction::PushReference); - } else { - ctx.add_instruction(Instruction::GetValue); - } - - let binding_pattern = match &property.value.kind { - ast::BindingPatternKind::AssignmentPattern(pattern) => { - // Run the initializer if the result value is undefined. - ctx.add_instruction(Instruction::LoadCopy); - ctx.add_instruction(Instruction::IsUndefined); - let jump_slot = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); - ctx.add_instruction(Instruction::Store); - if is_anonymous_function_definition(&pattern.right) { - if let ast::BindingPatternKind::BindingIdentifier(identifier) = - &pattern.left.kind - { - let identifier_string = ctx.create_identifier(&identifier.name); - ctx.add_instruction_with_constant( - Instruction::StoreConstant, - identifier_string, - ); - ctx.name_identifier = Some(NamedEvaluationParameter::Result); - } - } - pattern.right.compile(ctx); - ctx.name_identifier = None; - if is_reference(&pattern.right) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::Load); - ctx.set_jump_target_here(jump_slot); - ctx.add_instruction(Instruction::Store); - - &pattern.left.kind - } - _ => &property.value.kind, - }; - - match binding_pattern { - ast::BindingPatternKind::BindingIdentifier(identifier) => { - let identifier_string = ctx.create_identifier(&identifier.name); - ctx.add_instruction_with_identifier(Instruction::ResolveBinding, identifier_string); - if !has_environment { - ctx.add_instruction(Instruction::PutValue); - } else { - ctx.add_instruction(Instruction::InitializeReferencedBinding); - } - } - ast::BindingPatternKind::ObjectPattern(pattern) => { - ctx.add_instruction(Instruction::Load); - pattern.compile(ctx); - } - ast::BindingPatternKind::ArrayPattern(pattern) => { - ctx.add_instruction(Instruction::Load); - pattern.compile(ctx); - } - ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), - } - } - - if let Some(rest) = &object_pattern.rest { - let ast::BindingPatternKind::BindingIdentifier(identifier) = &rest.argument.kind else { - unreachable!() - }; - - // We have kept the references for all of the properties read in the reference stack, so we - // can now use them to exclude those properties from the rest object. - ctx.add_instruction_with_immediate( - Instruction::CopyDataPropertiesIntoObject, - object_pattern.properties.len(), - ); - - let identifier_string = ctx.create_identifier(&identifier.name); - ctx.add_instruction_with_identifier(Instruction::ResolveBinding, identifier_string); - if !has_environment { - ctx.add_instruction(Instruction::PutValue); - } else { - ctx.add_instruction(Instruction::InitializeReferencedBinding); - } - } else { - // Don't keep the object on the stack. - ctx.add_instruction(Instruction::Store); - } -} - -impl CompileEvaluation for ast::VariableDeclaration<'_> { - fn compile(&self, ctx: &mut CompileContext) { - match self.kind { - // VariableStatement : var VariableDeclarationList ; - ast::VariableDeclarationKind::Var => { - for decl in &self.declarations { - // VariableDeclaration : BindingIdentifier - let Some(init) = &decl.init else { - // 1. Return EMPTY. - continue; - }; - // VariableDeclaration : BindingIdentifier Initializer - - let ast::BindingPatternKind::BindingIdentifier(identifier) = &decl.id.kind - else { - // VariableDeclaration : BindingPattern Initializer - ctx.lexical_binding_state = false; - // 1. Let rhs be ? Evaluation of Initializer. - init.compile(ctx); - // 2. Let rval be ? GetValue(rhs). - if is_reference(init) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::Load); - // 3. Return ? BindingInitialization of BidingPattern with arguments rval and undefined. - match &decl.id.kind { - ast::BindingPatternKind::BindingIdentifier(_) => unreachable!(), - ast::BindingPatternKind::ObjectPattern(pattern) => pattern.compile(ctx), - ast::BindingPatternKind::ArrayPattern(pattern) => pattern.compile(ctx), - ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), - } - return; - }; - - // 1. Let bindingId be StringValue of BindingIdentifier. - // 2. Let lhs be ? ResolveBinding(bindingId). - let identifier_string = String::from_str(ctx.agent, identifier.name.as_str()); - ctx.add_instruction_with_identifier( - Instruction::ResolveBinding, - identifier_string, - ); - ctx.add_instruction(Instruction::PushReference); - - // 3. If IsAnonymousFunctionDefinition(Initializer) is true, then - if is_anonymous_function_definition(init) { - // a. Let value be ? NamedEvaluation of Initializer with argument bindingId. - ctx.name_identifier = Some(NamedEvaluationParameter::ReferenceStack); - init.compile(ctx); - } else { - // 4. Else, - // a. Let rhs be ? Evaluation of Initializer. - init.compile(ctx); - // b. Let value be ? GetValue(rhs). - if is_reference(init) { - ctx.add_instruction(Instruction::GetValue); - } - } - // 5. Perform ? PutValue(lhs, value). - ctx.add_instruction(Instruction::PopReference); - ctx.add_instruction(Instruction::PutValue); - - // 6. Return EMPTY. - // Store Undefined as the result value. - ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); - } - } - ast::VariableDeclarationKind::Let | ast::VariableDeclarationKind::Const => { - for decl in &self.declarations { - let ast::BindingPatternKind::BindingIdentifier(identifier) = &decl.id.kind - else { - ctx.lexical_binding_state = true; - let init = decl.init.as_ref().unwrap(); - - // LexicalBinding : BindingPattern Initializer - // 1. Let rhs be ? Evaluation of Initializer. - init.compile(ctx); - // 2. Let value be ? GetValue(rhs). - if is_reference(init) { - ctx.add_instruction(Instruction::GetValue); - } - // 3. Let env be the running execution context's LexicalEnvironment. - // 4. Return ? BindingInitialization of BindingPattern with arguments value and env. - ctx.add_instruction(Instruction::Load); - match &decl.id.kind { - ast::BindingPatternKind::BindingIdentifier(_) => unreachable!(), - ast::BindingPatternKind::ObjectPattern(pattern) => pattern.compile(ctx), - ast::BindingPatternKind::ArrayPattern(pattern) => pattern.compile(ctx), - ast::BindingPatternKind::AssignmentPattern(_) => unreachable!(), - } - return; - }; - - // 1. Let lhs be ! ResolveBinding(StringValue of BindingIdentifier). - let identifier_string = String::from_str(ctx.agent, identifier.name.as_str()); - ctx.add_instruction_with_identifier( - Instruction::ResolveBinding, - identifier_string, - ); - - let Some(init) = &decl.init else { - // LexicalBinding : BindingIdentifier - // 2. Perform ! InitializeReferencedBinding(lhs, undefined). - ctx.add_instruction_with_constant( - Instruction::StoreConstant, - Value::Undefined, - ); - ctx.add_instruction(Instruction::InitializeReferencedBinding); - // 3. Return empty. - ctx.add_instruction_with_constant( - Instruction::StoreConstant, - Value::Undefined, - ); - return; - }; - - // LexicalBinding : BindingIdentifier Initializer - ctx.add_instruction(Instruction::PushReference); - // 3. If IsAnonymousFunctionDefinition(Initializer) is true, then - if is_anonymous_function_definition(init) { - // a. Let value be ? NamedEvaluation of Initializer with argument bindingId. - ctx.name_identifier = Some(NamedEvaluationParameter::ReferenceStack); - init.compile(ctx); - } else { - // 4. Else, - // a. Let rhs be ? Evaluation of Initializer. - init.compile(ctx); - // b. Let value be ? GetValue(rhs). - if is_reference(init) { - ctx.add_instruction(Instruction::GetValue); - } - } - - // 5. Perform ! InitializeReferencedBinding(lhs, value). - ctx.add_instruction(Instruction::PopReference); - ctx.add_instruction(Instruction::InitializeReferencedBinding); - // 6. Return empty. - ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); - } - } - ast::VariableDeclarationKind::Using => todo!(), - ast::VariableDeclarationKind::AwaitUsing => todo!(), - } - } -} - -impl CompileEvaluation for ast::Declaration<'_> { - fn compile(&self, ctx: &mut CompileContext) { - match self { - ast::Declaration::VariableDeclaration(x) => x.compile(ctx), - ast::Declaration::FunctionDeclaration(x) => x.compile(ctx), - other => todo!("{other:?}"), - } - } -} - -impl CompileEvaluation for ast::BlockStatement<'_> { - fn compile(&self, ctx: &mut CompileContext) { - if self.body.is_empty() { - // Block : {} - // 1. Return EMPTY. - return; - } - ctx.add_instruction(Instruction::EnterDeclarativeEnvironment); - // SAFETY: Stupid lifetime transmute. - let body = unsafe { - std::mem::transmute::< - &oxc_allocator::Vec<'_, Statement<'_>>, - &'static oxc_allocator::Vec<'static, Statement<'static>>, - >(&self.body) - }; - body.lexically_scoped_declarations(&mut |decl| { - match decl { - LexicallyScopedDeclaration::Variable(decl) => { - if decl.kind.is_const() { - decl.id.bound_names(&mut |name| { - let identifier = String::from_str(ctx.agent, name.name.as_str()); - ctx.add_instruction_with_identifier( - Instruction::CreateImmutableBinding, - identifier, - ); - }); - } else if decl.kind.is_lexical() { - decl.id.bound_names(&mut |name| { - let identifier = String::from_str(ctx.agent, name.name.as_str()); - ctx.add_instruction_with_identifier( - Instruction::CreateMutableBinding, - identifier, - ); - }); - } - } - LexicallyScopedDeclaration::Function(decl) => { - // TODO: InstantiateFunctionObject and InitializeBinding - decl.bound_names(&mut |name| { - let identifier = String::from_str(ctx.agent, name.name.as_str()); - ctx.add_instruction_with_identifier( - Instruction::CreateMutableBinding, - identifier, - ); - }); - } - LexicallyScopedDeclaration::Class(decl) => { - decl.bound_names(&mut |name| { - let identifier = String::from_str(ctx.agent, name.name.as_str()); - ctx.add_instruction_with_identifier( - Instruction::CreateMutableBinding, - identifier, - ); - }); - } - LexicallyScopedDeclaration::DefaultExport => unreachable!(), - } - }); - for ele in &self.body { - ele.compile(ctx); - } - if ctx.peek_last_instruction() != Some(Instruction::Return.as_u8()) { - // Block did not end in a return so we overwrite the result with undefined. - ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); - } - ctx.add_instruction(Instruction::ExitDeclarativeEnvironment); - } -} - -impl CompileEvaluation for ast::ForStatement<'_> { - fn compile(&self, ctx: &mut CompileContext) { - let previous_continue = ctx.current_continue.replace(vec![]); - let previous_break = ctx.current_break.replace(vec![]); - - let mut per_iteration_lets = vec![]; - let mut is_lexical = false; - - if let Some(init) = &self.init { - match init { - ast::ForStatementInit::ArrayExpression(init) => init.compile(ctx), - ast::ForStatementInit::ArrowFunctionExpression(init) => init.compile(ctx), - ast::ForStatementInit::AssignmentExpression(init) => init.compile(ctx), - ast::ForStatementInit::AwaitExpression(init) => init.compile(ctx), - ast::ForStatementInit::BigIntLiteral(init) => init.compile(ctx), - ast::ForStatementInit::BinaryExpression(init) => init.compile(ctx), - ast::ForStatementInit::BooleanLiteral(init) => init.compile(ctx), - ast::ForStatementInit::CallExpression(init) => init.compile(ctx), - ast::ForStatementInit::ChainExpression(init) => init.compile(ctx), - ast::ForStatementInit::ClassExpression(init) => init.compile(ctx), - ast::ForStatementInit::ComputedMemberExpression(init) => init.compile(ctx), - ast::ForStatementInit::ConditionalExpression(init) => init.compile(ctx), - ast::ForStatementInit::FunctionExpression(init) => init.compile(ctx), - ast::ForStatementInit::Identifier(init) => init.compile(ctx), - ast::ForStatementInit::ImportExpression(init) => init.compile(ctx), - ast::ForStatementInit::LogicalExpression(init) => init.compile(ctx), - ast::ForStatementInit::MetaProperty(init) => init.compile(ctx), - ast::ForStatementInit::NewExpression(init) => init.compile(ctx), - ast::ForStatementInit::NullLiteral(init) => init.compile(ctx), - ast::ForStatementInit::NumericLiteral(init) => init.compile(ctx), - ast::ForStatementInit::ObjectExpression(init) => init.compile(ctx), - ast::ForStatementInit::ParenthesizedExpression(init) => init.compile(ctx), - ast::ForStatementInit::PrivateFieldExpression(init) => init.compile(ctx), - ast::ForStatementInit::PrivateInExpression(init) => init.compile(ctx), - ast::ForStatementInit::RegExpLiteral(init) => init.compile(ctx), - ast::ForStatementInit::SequenceExpression(init) => init.compile(ctx), - ast::ForStatementInit::StaticMemberExpression(init) => init.compile(ctx), - ast::ForStatementInit::StringLiteral(init) => init.compile(ctx), - ast::ForStatementInit::Super(init) => init.compile(ctx), - ast::ForStatementInit::TaggedTemplateExpression(init) => init.compile(ctx), - ast::ForStatementInit::TemplateLiteral(init) => init.compile(ctx), - ast::ForStatementInit::ThisExpression(init) => init.compile(ctx), - ast::ForStatementInit::UnaryExpression(init) => init.compile(ctx), - ast::ForStatementInit::UpdateExpression(init) => init.compile(ctx), - ast::ForStatementInit::VariableDeclaration(init) => { - is_lexical = init.kind.is_lexical(); - if is_lexical { - // 1. Let oldEnv be the running execution context's LexicalEnvironment. - // 2. Let loopEnv be NewDeclarativeEnvironment(oldEnv). - ctx.add_instruction(Instruction::EnterDeclarativeEnvironment); - // 3. Let isConst be IsConstantDeclaration of LexicalDeclaration. - let is_const = init.kind.is_const(); - // 4. Let boundNames be the BoundNames of LexicalDeclaration. - // 5. For each element dn of boundNames, do - // a. If isConst is true, then - if is_const { - init.bound_names(&mut |dn| { - // i. Perform ! loopEnv.CreateImmutableBinding(dn, true). - let identifier = String::from_str(ctx.agent, dn.name.as_str()); - ctx.add_instruction_with_identifier( - Instruction::CreateImmutableBinding, - identifier, - ) - }); - } else { - // b. Else, - // i. Perform ! loopEnv.CreateMutableBinding(dn, false). - init.bound_names(&mut |dn| { - let identifier = String::from_str(ctx.agent, dn.name.as_str()); - // 9. If isConst is false, let perIterationLets - // be boundNames; otherwise let perIterationLets - // be a new empty List. - per_iteration_lets.push(identifier); - ctx.add_instruction_with_identifier( - Instruction::CreateMutableBinding, - identifier, - ) - }); - } - // 6. Set the running execution context's LexicalEnvironment to loopEnv. - } - init.compile(ctx); - } - ast::ForStatementInit::YieldExpression(init) => init.compile(ctx), - ast::ForStatementInit::JSXElement(_) - | ast::ForStatementInit::JSXFragment(_) - | ast::ForStatementInit::TSAsExpression(_) - | ast::ForStatementInit::TSSatisfiesExpression(_) - | ast::ForStatementInit::TSTypeAssertion(_) - | ast::ForStatementInit::TSNonNullExpression(_) - | ast::ForStatementInit::TSInstantiationExpression(_) => unreachable!(), - } - } - // 2. Perform ? CreatePerIterationEnvironment(perIterationBindings). - let create_per_iteration_env = if !per_iteration_lets.is_empty() { - Some(|ctx: &mut CompileContext| { - if per_iteration_lets.len() == 1 { - // NOTE: Optimization for the usual case of a single let - // binding. We do not need to push and pop from the stack - // in this case but can use the result register directly. - // There are rather easy further optimizations available as - // well around creating a sibling environment directly, - // creating an initialized mutable binding directly, and - // importantly: The whole loop environment is unnecessary - // if the loop contains no closures (that capture the - // per-iteration lets). - - let binding = *per_iteration_lets.first().unwrap(); - // Get value of binding from lastIterationEnv. - ctx.add_instruction_with_identifier(Instruction::ResolveBinding, binding); - ctx.add_instruction(Instruction::GetValue); - // Current declarative environment is now "outer" - ctx.add_instruction(Instruction::ExitDeclarativeEnvironment); - // NewDeclarativeEnvironment(outer) - ctx.add_instruction(Instruction::EnterDeclarativeEnvironment); - ctx.add_instruction_with_identifier(Instruction::CreateMutableBinding, binding); - ctx.add_instruction_with_identifier(Instruction::ResolveBinding, binding); - ctx.add_instruction(Instruction::InitializeReferencedBinding); - } else { - for bn in &per_iteration_lets { - ctx.add_instruction_with_identifier(Instruction::ResolveBinding, *bn); - ctx.add_instruction(Instruction::GetValue); - ctx.add_instruction(Instruction::Load); - } - ctx.add_instruction(Instruction::ExitDeclarativeEnvironment); - ctx.add_instruction(Instruction::EnterDeclarativeEnvironment); - for bn in per_iteration_lets.iter().rev() { - ctx.add_instruction_with_identifier(Instruction::CreateMutableBinding, *bn); - ctx.add_instruction_with_identifier(Instruction::ResolveBinding, *bn); - ctx.add_instruction(Instruction::Store); - ctx.add_instruction(Instruction::InitializeReferencedBinding); - } - } - }) - } else { - None - }; - - if let Some(create_per_iteration_env) = create_per_iteration_env { - create_per_iteration_env(ctx); - } - - let loop_jump = ctx.get_jump_index_to_here(); - if let Some(test) = &self.test { - test.compile(ctx); - if is_reference(test) { - ctx.add_instruction(Instruction::GetValue); - } - } - // jump over consequent if test fails - let end_jump = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); - - self.body.compile(ctx); - - let own_continues = ctx.current_continue.take().unwrap(); - for continue_entry in own_continues { - ctx.set_jump_target_here(continue_entry); - } - - if let Some(create_per_iteration_env) = create_per_iteration_env { - create_per_iteration_env(ctx); - } - - if let Some(update) = &self.update { - update.compile(ctx); - } - ctx.add_jump_instruction_to_index(Instruction::Jump, loop_jump); - ctx.set_jump_target_here(end_jump); - - let own_breaks = ctx.current_break.take().unwrap(); - for break_entry in own_breaks { - ctx.set_jump_target_here(break_entry); - } - if is_lexical { - // Lexical binding loops have an extra declarative environment that - // we need to exit from once we exit the loop. - ctx.add_instruction(Instruction::ExitDeclarativeEnvironment); - } - ctx.current_break = previous_break; - ctx.current_continue = previous_continue; - } -} - -impl CompileEvaluation for ast::SwitchStatement<'_> { - fn compile(&self, ctx: &mut CompileContext) { - let previous_break = ctx.current_break.replace(vec![]); - // 1. Let exprRef be ? Evaluation of Expression. - self.discriminant.compile(ctx); - if is_reference(&self.discriminant) { - // 2. Let switchValue be ? GetValue(exprRef). - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::Load); - // 3. Let oldEnv be the running execution context's LexicalEnvironment. - // 4. Let blockEnv be NewDeclarativeEnvironment(oldEnv). - // 6. Set the running execution context's LexicalEnvironment to blockEnv. - ctx.add_instruction(Instruction::EnterDeclarativeEnvironment); - // 5. Perform BlockDeclarationInstantiation(CaseBlock, blockEnv). - // TODO: Analyze switch env instantiation. - - // 7. Let R be Completion(CaseBlockEvaluation of CaseBlock with argument switchValue). - let mut has_default = false; - let mut jump_indexes = Vec::with_capacity(self.cases.len()); - for case in &self.cases { - let Some(test) = &case.test else { - // Default case test does not care about the write order: After - // all other cases have been tested, default will be entered if - // no other was entered previously. The placement of the - // default case only matters for fall-through behaviour. - has_default = true; - continue; - }; - // Duplicate the switchValue on the stack. One will remain, one is - // used by the IsStrictlyEqual - ctx.add_instruction(Instruction::Store); - ctx.add_instruction(Instruction::LoadCopy); - ctx.add_instruction(Instruction::Load); - // 2. Let exprRef be ? Evaluation of the Expression of C. - test.compile(ctx); - // 3. Let clauseSelector be ? GetValue(exprRef). - if is_reference(test) { - ctx.add_instruction(Instruction::GetValue); - } - // 4. Return IsStrictlyEqual(input, clauseSelector). - ctx.add_instruction(Instruction::IsStrictlyEqual); - // b. If found is true then [evaluate case] - jump_indexes.push(ctx.add_instruction_with_jump_slot(Instruction::JumpIfTrue)); - } - - if has_default { - // 10. If foundInB is true, return V. - // 11. Let defaultR be Completion(Evaluation of DefaultClause). - jump_indexes.push(ctx.add_instruction_with_jump_slot(Instruction::Jump)); - } - - let mut index = 0; - for (i, case) in self.cases.iter().enumerate() { - let fallthrough_jump = if i != 0 { - Some(ctx.add_instruction_with_jump_slot(Instruction::Jump)) - } else { - None - }; - // Jump from IsStrictlyEqual comparison to here. - let jump_index = if case.test.is_some() { - let jump_index = jump_indexes.get(index).unwrap(); - index += 1; - jump_index - } else { - // Default case! The jump index is last in the Vec. - jump_indexes.last().unwrap() - }; - ctx.set_jump_target_here(jump_index.clone()); - - // Pop the switchValue from the stack. - ctx.add_instruction(Instruction::Store); - // And override it with undefined - ctx.add_instruction_with_constant(Instruction::StoreConstant, Value::Undefined); - - if let Some(fallthrough_jump) = fallthrough_jump { - ctx.set_jump_target_here(fallthrough_jump); - } - - for ele in &case.consequent { - ele.compile(ctx); - } - } - - let own_breaks = ctx.current_break.take().unwrap(); - for break_entry in own_breaks { - ctx.set_jump_target_here(break_entry); - } - ctx.current_break = previous_break; - - // 8. Set the running execution context's LexicalEnvironment to oldEnv. - ctx.add_instruction(Instruction::ExitDeclarativeEnvironment); - // 9. Return R. - } -} - -impl CompileEvaluation for ast::ThrowStatement<'_> { - fn compile(&self, ctx: &mut CompileContext) { - self.argument.compile(ctx); - if is_reference(&self.argument) { - ctx.add_instruction(Instruction::GetValue); - } - ctx.add_instruction(Instruction::Throw) - } -} - -impl CompileEvaluation for ast::TryStatement<'_> { - fn compile(&self, ctx: &mut CompileContext) { - if self.finalizer.is_some() { - todo!(); - } - - let jump_to_catch = - ctx.add_instruction_with_jump_slot(Instruction::PushExceptionJumpTarget); - self.block.compile(ctx); - ctx.add_instruction(Instruction::PopExceptionJumpTarget); - let jump_to_end = ctx.add_instruction_with_jump_slot(Instruction::Jump); - - let catch_clause = self.handler.as_ref().unwrap(); - ctx.set_jump_target_here(jump_to_catch); - if let Some(exception_param) = &catch_clause.param { - let ast::BindingPatternKind::BindingIdentifier(identifier) = - &exception_param.pattern.kind - else { - todo!("{:?}", exception_param.pattern.kind); - }; - ctx.add_instruction(Instruction::EnterDeclarativeEnvironment); - let identifier_string = String::from_str(ctx.agent, identifier.name.as_str()); - ctx.add_instruction_with_identifier(Instruction::CreateCatchBinding, identifier_string); - } - catch_clause.body.compile(ctx); - if catch_clause.param.is_some() { - ctx.add_instruction(Instruction::ExitDeclarativeEnvironment); - } - ctx.set_jump_target_here(jump_to_end); - } -} - -impl CompileEvaluation for ast::WhileStatement<'_> { - fn compile(&self, ctx: &mut CompileContext) { - let previous_continue = ctx.current_continue.replace(vec![]); - let previous_break = ctx.current_break.replace(vec![]); - - // 2. Repeat - let start_jump = ctx.get_jump_index_to_here(); - - // a. Let exprRef be ? Evaluation of Expression. - - self.test.compile(ctx); - if is_reference(&self.test) { - // b. Let exprValue be ? GetValue(exprRef). - ctx.add_instruction(Instruction::GetValue); - } - - // c. If ToBoolean(exprValue) is false, return V. - // jump over loop jump if test fails - let end_jump = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); - // d. Let stmtResult be Completion(Evaluation of Statement). - self.body.compile(ctx); - - // e. If LoopContinues(stmtResult, labelSet) is false, return ? UpdateEmpty(stmtResult, V). - // f. If stmtResult.[[Value]] is not EMPTY, set V to stmtResult.[[Value]]. - ctx.add_jump_instruction_to_index(Instruction::Jump, start_jump.clone()); - let own_continues = ctx.current_continue.take().unwrap(); - for continue_entry in own_continues { - ctx.set_jump_target(continue_entry, start_jump.clone()); - } - - ctx.set_jump_target_here(end_jump); - - let own_breaks = ctx.current_break.take().unwrap(); - for break_entry in own_breaks { - ctx.set_jump_target_here(break_entry); - } - ctx.current_break = previous_break; - ctx.current_continue = previous_continue; - } -} - -impl CompileEvaluation for ast::DoWhileStatement<'_> { - fn compile(&self, ctx: &mut CompileContext) { - let previous_continue = ctx.current_continue.replace(vec![]); - let previous_break = ctx.current_break.replace(vec![]); - - let start_jump = ctx.get_jump_index_to_here(); - self.body.compile(ctx); - - let own_continues = ctx.current_continue.take().unwrap(); - for continue_entry in own_continues { - ctx.set_jump_target_here(continue_entry); - } - - self.test.compile(ctx); - if is_reference(&self.test) { - ctx.add_instruction(Instruction::GetValue); - } - // jump over loop jump if test fails - let end_jump = ctx.add_instruction_with_jump_slot(Instruction::JumpIfNot); - ctx.add_jump_instruction_to_index(Instruction::Jump, start_jump); - ctx.set_jump_target_here(end_jump); - - let own_breaks = ctx.current_break.take().unwrap(); - for break_entry in own_breaks { - ctx.set_jump_target_here(break_entry); - } - ctx.current_break = previous_break; - ctx.current_continue = previous_continue; - } -} - -impl CompileEvaluation for ast::BreakStatement<'_> { - fn compile(&self, ctx: &mut CompileContext) { - if let Some(label) = &self.label { - let label = label.name.as_str(); - todo!("break {};", label); - } - let break_jump = ctx.add_instruction_with_jump_slot(Instruction::Jump); - ctx.current_break.as_mut().unwrap().push(break_jump); - } -} - -impl CompileEvaluation for ast::ContinueStatement<'_> { - fn compile(&self, ctx: &mut CompileContext) { - if let Some(label) = &self.label { - let label = label.name.as_str(); - todo!("continue {};", label); - } - let continue_jump = ctx.add_instruction_with_jump_slot(Instruction::Jump); - ctx.current_continue.as_mut().unwrap().push(continue_jump); - } -} - -impl CompileEvaluation for ast::Statement<'_> { - fn compile(&self, ctx: &mut CompileContext) { - match self { - ast::Statement::ExpressionStatement(x) => x.compile(ctx), - ast::Statement::ReturnStatement(x) => x.compile(ctx), - ast::Statement::IfStatement(x) => x.compile(ctx), - ast::Statement::VariableDeclaration(x) => x.compile(ctx), - ast::Statement::FunctionDeclaration(x) => x.compile(ctx), - ast::Statement::BlockStatement(x) => x.compile(ctx), - ast::Statement::EmptyStatement(_) => {} - ast::Statement::ForStatement(x) => x.compile(ctx), - ast::Statement::ThrowStatement(x) => x.compile(ctx), - ast::Statement::TryStatement(x) => x.compile(ctx), - Statement::BreakStatement(statement) => statement.compile(ctx), - Statement::ContinueStatement(statement) => statement.compile(ctx), - Statement::DebuggerStatement(_) => todo!(), - Statement::DoWhileStatement(statement) => statement.compile(ctx), - Statement::ForInStatement(statement) => statement.compile(ctx), - Statement::ForOfStatement(statement) => statement.compile(ctx), - Statement::LabeledStatement(_) => todo!(), - Statement::SwitchStatement(statement) => statement.compile(ctx), - Statement::WhileStatement(statement) => statement.compile(ctx), - Statement::WithStatement(_) => todo!(), - Statement::ClassDeclaration(x) => x.compile(ctx), - Statement::ImportDeclaration(_) => todo!(), - Statement::ExportAllDeclaration(_) => todo!(), - Statement::ExportDefaultDeclaration(_) => todo!(), - Statement::ExportNamedDeclaration(_) => todo!(), - #[cfg(feature = "typescript")] - Statement::TSTypeAliasDeclaration(_) | Statement::TSInterfaceDeclaration(_) => {} - #[cfg(not(feature = "typescript"))] - Statement::TSTypeAliasDeclaration(_) | Statement::TSInterfaceDeclaration(_) => { - unreachable!() - } - Statement::TSEnumDeclaration(_) - | Statement::TSExportAssignment(_) - | Statement::TSImportEqualsDeclaration(_) - | Statement::TSModuleDeclaration(_) - | Statement::TSNamespaceExportDeclaration(_) => unreachable!(), - } - } -} - -fn is_anonymous_function_definition(expression: &ast::Expression) -> bool { - match expression { - ast::Expression::ArrowFunctionExpression(_) => true, - ast::Expression::FunctionExpression(f) => f.id.is_none(), - _ => false, - } -} - -impl HeapMarkAndSweep for Executable { - fn mark_values(&self, queues: &mut WorkQueues) { - self.constants.mark_values(queues); - } - - fn sweep_values(&mut self, compactions: &CompactionLists) { - self.constants.sweep_values(compactions); - } +#[cold] +fn handle_identifier_failure() -> ! { + panic!("Invalid identifier index: Value was not a String") } diff --git a/nova_vm/src/engine/bytecode/iterator.rs b/nova_vm/src/engine/bytecode/iterator.rs index 1aa8132a9..d8bd85bc8 100644 --- a/nova_vm/src/engine/bytecode/iterator.rs +++ b/nova_vm/src/engine/bytecode/iterator.rs @@ -235,17 +235,29 @@ impl ArrayValuesIterator { impl HeapMarkAndSweep for ObjectPropertiesIterator { fn mark_values(&self, queues: &mut WorkQueues) { - self.object.mark_values(queues); - self.visited_keys.as_slice().mark_values(queues); - for key in self.remaining_keys.iter() { + let Self { + object, + object_was_visited: _, + visited_keys, + remaining_keys, + } = self; + object.mark_values(queues); + visited_keys.as_slice().mark_values(queues); + for key in remaining_keys.iter() { key.mark_values(queues); } } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.object.sweep_values(compactions); - self.visited_keys.as_mut_slice().sweep_values(compactions); - for key in self.remaining_keys.iter_mut() { + let Self { + object, + object_was_visited: _, + visited_keys, + remaining_keys, + } = self; + object.sweep_values(compactions); + visited_keys.as_mut_slice().sweep_values(compactions); + for key in remaining_keys.iter_mut() { key.sweep_values(compactions); } } diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index 86c8ab4d4..72ec8c1b1 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -9,6 +9,8 @@ use oxc_ast::ast; use oxc_span::Span; use oxc_syntax::operator::BinaryOperator; +#[cfg(feature = "interleaved-gc")] +use crate::{ecmascript::execution::RealmIdentifier, heap::heap_gc::heap_gc}; use crate::{ ecmascript::{ abstract_operations::{ @@ -51,10 +53,11 @@ use crate::{ }; use super::{ - executable::{NamedEvaluationParameter, SendableRef}, + executable::{ArrowFunctionExpression, SendableRef}, instructions::Instr, iterator::{ObjectPropertiesIterator, VmIterator}, - Executable, IndexType, Instruction, InstructionIter, + Executable, FunctionExpression, IndexType, Instruction, InstructionIter, + NamedEvaluationParameter, }; struct EmptyParametersList(ast::FormalParameters<'static>); @@ -144,7 +147,7 @@ impl SuspendedVm { pub(crate) fn resume( self, agent: &mut Agent, - executable: &Executable, + executable: Executable, value: Value, ) -> ExecutionResult { let vm = Vm::from_suspended(self); @@ -154,7 +157,7 @@ impl SuspendedVm { pub(crate) fn resume_throw( self, agent: &mut Agent, - executable: &Executable, + executable: Executable, err: Value, ) -> ExecutionResult { // Optimisation: Avoid unsuspending the Vm if we're just going to throw @@ -205,19 +208,10 @@ impl Vm { } } - fn fetch_identifier(&self, exe: &Executable, index: usize) -> String { - String::try_from(exe.constants[index]) - .expect("Invalid identifier index: Value was not a String") - } - - fn fetch_constant(&self, exe: &Executable, index: usize) -> Value { - exe.constants[index] - } - /// Executes an executable using the virtual machine. pub(crate) fn execute( agent: &mut Agent, - executable: &Executable, + executable: Executable, arguments: Option<&[Value]>, ) -> ExecutionResult { let mut vm = Vm::new(); @@ -233,11 +227,11 @@ impl Vm { if agent.options.print_internals { eprintln!(); eprintln!("=== Executing Executable ==="); - eprintln!("Constants: {:?}", executable.constants); + eprintln!("Constants: {:?}", executable.get_constants(agent)); eprintln!(); eprintln!("Instructions:"); - let iter = InstructionIter::new(&executable.instructions); + let iter = InstructionIter::new(executable.get_instructions(agent)); for (ip, instr) in iter { match instr.kind.argument_count() { 0 => { @@ -264,7 +258,7 @@ impl Vm { pub fn resume( mut self, agent: &mut Agent, - executable: &Executable, + executable: Executable, value: Value, ) -> ExecutionResult { self.result = Some(value); @@ -274,7 +268,7 @@ impl Vm { pub fn resume_throw( mut self, agent: &mut Agent, - executable: &Executable, + executable: Executable, err: Value, ) -> ExecutionResult { let err = JsError::new(err); @@ -284,8 +278,33 @@ impl Vm { self.inner_execute(agent, executable) } - fn inner_execute(mut self, agent: &mut Agent, executable: &Executable) -> ExecutionResult { - while let Some(instr) = executable.get_instruction(&mut self.ip) { + fn inner_execute(mut self, agent: &mut Agent, executable: Executable) -> ExecutionResult { + #[cfg(feature = "interleaved-gc")] + let do_gc = !agent.options.disable_gc; + #[cfg(feature = "interleaved-gc")] + let mut instr_count = 0u8; + while let Some(instr) = executable.get_instruction(agent, &mut self.ip) { + #[cfg(feature = "interleaved-gc")] + if do_gc { + instr_count = instr_count.wrapping_add(1); + if instr_count == 0 { + let mut root_realms = agent + .heap + .realms + .iter() + .enumerate() + .map(|(i, _)| Some(RealmIdentifier::from_index(i))) + .collect::>(); + let vm = unsafe { NonNull::new_unchecked(&mut self) }; + agent + .vm_stack + // SAFETY: Pointer to self is never null. + .push(vm); + heap_gc(agent, &mut root_realms); + let return_vm = agent.vm_stack.pop().unwrap(); + assert_eq!(vm, return_vm, "VM Stack was misused"); + } + } match Self::execute_instruction(agent, &mut self, executable, &instr) { Ok(ContinuationKind::Normal) => {} Ok(ContinuationKind::Return) => { @@ -337,7 +356,7 @@ impl Vm { fn execute_instruction( agent: &mut Agent, vm: &mut Vm, - executable: &Executable, + executable: Executable, instr: &Instr, ) -> JsResult { if agent.options.print_internals { @@ -398,7 +417,8 @@ impl Vm { } } Instruction::ResolveBinding => { - let identifier = vm.fetch_identifier(executable, instr.args[0].unwrap() as usize); + let identifier = + executable.fetch_identifier(agent, instr.args[0].unwrap() as usize); let reference = resolve_binding(agent, identifier, None)?; @@ -416,7 +436,7 @@ impl Vm { }); } Instruction::LoadConstant => { - let constant = vm.fetch_constant(executable, instr.args[0].unwrap() as usize); + let constant = executable.fetch_constant(agent, instr.args[0].unwrap() as usize); vm.stack.push(constant); } Instruction::Load => { @@ -443,7 +463,7 @@ impl Vm { vm.result = Some(*vm.stack.last().expect("Trying to get from empty stack")); } Instruction::StoreConstant => { - let constant = vm.fetch_constant(executable, instr.args[0].unwrap() as usize); + let constant = executable.fetch_constant(agent, instr.args[0].unwrap() as usize); vm.result = Some(constant); } Instruction::UnaryMinus => { @@ -489,10 +509,9 @@ impl Vm { create_data_property_or_throw(agent, object, key, value).unwrap() } Instruction::ObjectDefineMethod => { - let function_expression = executable - .function_expressions - .get(instr.args[0].unwrap() as usize) - .unwrap(); + let FunctionExpression { expression, .. } = + executable.fetch_function_expression(agent, instr.args[0].unwrap() as usize); + let function_expression = expression.get(); let enumerable = instr.args[1].unwrap() != 0; // 1. Let propKey be ? Evaluation of ClassElementName. let prop_key = to_property_key(agent, vm.stack.pop().unwrap())?; @@ -519,12 +538,12 @@ impl Vm { function_prototype: None, source_code: None, // 4. Let sourceText be the source text matched by MethodDefinition. - source_text: function_expression.expression.get().span, - parameters_list: &function_expression.expression.get().params, - body: function_expression.expression.get().body.as_ref().unwrap(), + source_text: function_expression.span, + parameters_list: &function_expression.params, + body: function_expression.body.as_ref().unwrap(), is_concise_arrow_function: false, - is_async: function_expression.expression.get().r#async, - is_generator: function_expression.expression.get().generator, + is_async: function_expression.r#async, + is_generator: function_expression.generator, lexical_this: false, env, private_env, @@ -576,10 +595,9 @@ impl Vm { // c. Return unused. } Instruction::ObjectDefineGetter => { - let function_expression = executable - .function_expressions - .get(instr.args[0].unwrap() as usize) - .unwrap(); + let FunctionExpression { expression, .. } = + executable.fetch_function_expression(agent, instr.args[0].unwrap() as usize); + let function_expression = expression.get(); let enumerable = instr.args[1].unwrap() != 0; // 1. Let propKey be ? Evaluation of ClassElementName. let prop_key = to_property_key(agent, vm.stack.pop().unwrap())?; @@ -613,11 +631,11 @@ impl Vm { function_prototype: None, source_code: None, // 4. Let sourceText be the source text matched by MethodDefinition. - source_text: function_expression.expression.get().span, + source_text: function_expression.span, parameters_list: &empty_parameters.0, - body: function_expression.expression.get().body.as_ref().unwrap(), - is_async: function_expression.expression.get().r#async, - is_generator: function_expression.expression.get().generator, + body: function_expression.body.as_ref().unwrap(), + is_async: function_expression.r#async, + is_generator: function_expression.generator, is_concise_arrow_function: false, lexical_this: false, env, @@ -655,10 +673,9 @@ impl Vm { // c. Return unused. } Instruction::ObjectDefineSetter => { - let function_expression = executable - .function_expressions - .get(instr.args[0].unwrap() as usize) - .unwrap(); + let FunctionExpression { expression, .. } = + executable.fetch_function_expression(agent, instr.args[0].unwrap() as usize); + let function_expression = expression.get(); let enumerable = instr.args[1].unwrap() != 0; // 1. Let propKey be ? Evaluation of ClassElementName. let prop_key = to_property_key(agent, vm.stack.pop().unwrap())?; @@ -677,12 +694,12 @@ impl Vm { function_prototype: None, source_code: None, // 4. Let sourceText be the source text matched by MethodDefinition. - source_text: function_expression.expression.get().span, - parameters_list: &function_expression.expression.get().params, - body: function_expression.expression.get().body.as_ref().unwrap(), + source_text: function_expression.span, + parameters_list: &function_expression.params, + body: function_expression.body.as_ref().unwrap(), is_concise_arrow_function: false, - is_async: function_expression.expression.get().r#async, - is_generator: function_expression.expression.get().generator, + is_async: function_expression.r#async, + is_generator: function_expression.generator, lexical_this: false, env, private_env, @@ -807,10 +824,13 @@ impl Vm { } Instruction::InstantiateArrowFunctionExpression => { // ArrowFunction : ArrowParameters => ConciseBody - let function_expression = executable - .arrow_function_expressions - .get(instr.args[0].unwrap() as usize) - .unwrap(); + let ArrowFunctionExpression { + expression, + identifier, + } = executable + .fetch_arrow_function_expression(agent, instr.args[0].unwrap() as usize); + let function_expression = expression.get(); + let identifier = *identifier; // 2. Let env be the LexicalEnvironment of the running execution context. // 3. Let privateEnv be the running execution context's PrivateEnvironment. // 4. Let sourceText be the source text matched by ArrowFunction. @@ -830,18 +850,18 @@ impl Vm { let params = OrdinaryFunctionCreateParams { function_prototype: None, source_code: None, - source_text: function_expression.expression.get().span, - parameters_list: &function_expression.expression.get().params, - body: &function_expression.expression.get().body, - is_concise_arrow_function: function_expression.expression.get().expression, - is_async: function_expression.expression.get().r#async, + source_text: function_expression.span, + parameters_list: &function_expression.params, + body: &function_expression.body, + is_concise_arrow_function: function_expression.expression, + is_async: function_expression.r#async, is_generator: false, lexical_this: true, env: lexical_environment, private_env: private_environment, }; let function = ordinary_function_create(agent, params); - let name = if let Some(parameter) = function_expression.identifier { + let name = if let Some(parameter) = &identifier { match parameter { NamedEvaluationParameter::Result => { to_property_key(agent, vm.result.unwrap())? @@ -863,10 +883,14 @@ impl Vm { vm.result = Some(function.into_value()); } Instruction::InstantiateOrdinaryFunctionExpression => { - let function_expression = executable - .function_expressions - .get(instr.args[0].unwrap() as usize) - .unwrap(); + let FunctionExpression { + expression, + identifier, + compiled_bytecode, + } = executable.fetch_function_expression(agent, instr.args[0].unwrap() as usize); + let function_expression = expression.get(); + let identifier = *identifier; + let compiled_bytecode = *compiled_bytecode; let ECMAScriptCodeEvaluationState { lexical_environment, private_environment, @@ -877,10 +901,8 @@ impl Vm { .as_ref() .unwrap(); - let (name, env, init_binding) = if let Some(parameter) = - function_expression.identifier - { - debug_assert!(function_expression.expression.get().id.is_none()); + let (name, env, init_binding) = if let Some(parameter) = identifier { + debug_assert!(function_expression.id.is_none()); let name = match parameter { NamedEvaluationParameter::Result => { to_property_key(agent, vm.result.unwrap())? @@ -896,7 +918,7 @@ impl Vm { } }; (name, lexical_environment, false) - } else if let Some(binding_identifier) = &function_expression.expression.get().id { + } else if let Some(binding_identifier) = &function_expression.id { let name = String::from_str(agent, &binding_identifier.name); let func_env = new_declarative_environment(agent, Some(lexical_environment)); func_env.create_immutable_binding(agent, name, false); @@ -907,30 +929,26 @@ impl Vm { let params = OrdinaryFunctionCreateParams { function_prototype: None, source_code: None, - source_text: function_expression.expression.get().span, - parameters_list: &function_expression.expression.get().params, - body: function_expression.expression.get().body.as_ref().unwrap(), + source_text: function_expression.span, + parameters_list: &function_expression.params, + body: function_expression.body.as_ref().unwrap(), is_concise_arrow_function: false, - is_async: function_expression.expression.get().r#async, - is_generator: function_expression.expression.get().generator, + is_async: function_expression.r#async, + is_generator: function_expression.generator, lexical_this: false, env, private_env: private_environment, }; let function = ordinary_function_create(agent, params); - if let Some(compiled_bytecode) = &function_expression.compiled_bytecode { - agent[function].compiled_bytecode = Some(NonNull::from(Box::leak(Box::new( - compiled_bytecode.clone(), - )))); + if let Some(compiled_bytecode) = compiled_bytecode { + agent[function].compiled_bytecode = Some(compiled_bytecode); } set_function_name(agent, function, name, None); - if !function_expression.expression.get().r#async - && !function_expression.expression.get().generator - { + if !function_expression.r#async && !function_expression.generator { make_constructor(agent, function, None, None); } - if function_expression.expression.get().generator { + if function_expression.generator { // InstantiateGeneratorFunctionExpression // 7. Let prototype be OrdinaryObjectCreate(%GeneratorFunction.prototype.prototype%). // NOTE: Although `prototype` has the generator prototype, it doesn't have the generator @@ -975,10 +993,13 @@ impl Vm { vm.result = Some(function.into_value()); } Instruction::ClassDefineConstructor => { - let function_expression = executable - .function_expressions - .get(instr.args[0].unwrap() as usize) - .unwrap(); + let FunctionExpression { + expression, + compiled_bytecode, + .. + } = executable.fetch_function_expression(agent, instr.args[0].unwrap() as usize); + let function_expression = expression.get(); + let compiled_bytecode = *compiled_bytecode; let has_constructor_parent = instr.args[1].unwrap(); assert!(has_constructor_parent <= 1); let has_constructor_parent = has_constructor_parent == 1; @@ -1007,21 +1028,19 @@ impl Vm { let params = OrdinaryFunctionCreateParams { function_prototype, source_code: None, - source_text: function_expression.expression.get().span, - parameters_list: &function_expression.expression.get().params, - body: function_expression.expression.get().body.as_ref().unwrap(), + source_text: function_expression.span, + parameters_list: &function_expression.params, + body: function_expression.body.as_ref().unwrap(), is_concise_arrow_function: false, - is_async: function_expression.expression.get().r#async, - is_generator: function_expression.expression.get().generator, + is_async: function_expression.r#async, + is_generator: function_expression.generator, lexical_this: false, env: lexical_environment, private_env: private_environment, }; let function = ordinary_function_create(agent, params); - if let Some(compiled_bytecode) = &function_expression.compiled_bytecode { - agent[function].compiled_bytecode = Some(NonNull::from(Box::leak(Box::new( - compiled_bytecode.clone(), - )))); + if let Some(compiled_bytecode) = compiled_bytecode { + agent[function].compiled_bytecode = Some(compiled_bytecode); } set_function_name(agent, function, class_name.into(), None); make_constructor(agent, function, Some(false), Some(proto)); @@ -1052,13 +1071,10 @@ impl Vm { Instruction::ClassDefineDefaultConstructor => { let class_initializer_bytecode_index = instr.args[0].unwrap(); let (compiled_initializer_bytecode, has_constructor_parent) = executable - .class_initializer_bytecodes - .get(class_initializer_bytecode_index as usize) - .unwrap(); - let has_constructor_parent = *has_constructor_parent; - let compiled_initializer_bytecode = compiled_initializer_bytecode - .as_ref() - .map(|bytecode| Box::new(bytecode.clone())); + .fetch_class_initializer_bytecode( + agent, + class_initializer_bytecode_index as usize, + ); let class_name = String::try_from(vm.stack.pop().unwrap()).unwrap(); let function_prototype = if has_constructor_parent { @@ -1148,6 +1164,23 @@ impl Vm { // v. Return ? PerformEval(evalArg, strictCaller, true). vm.result = Some(perform_eval(agent, eval_arg, true, strict_caller)?); } + } else if cfg!(feature = "interleaved-gc") { + let mut vm = NonNull::from(vm); + agent.vm_stack.push(vm); + let result = call(agent, func, Value::Undefined, Some(ArgumentsList(&args))); + let return_vm = agent.vm_stack.pop().unwrap(); + assert_eq!(vm, return_vm, "VM Stack was misused"); + // SAFETY: This is fairly bonkers-unsafe. We have an + // exclusive reference to `Vm` so turning that to a NonNull + // and making the `&mut Vm` unreachable here isn't wrong. + // Passing that NonNull into a stack isn't wrong. + // Popping from that stack isn't wrong. + // Turning that back into a `&mut Vm` is probably wrong. + // Even though we can't reach the `vm: &mut Vm` in this + // scope anymore, it's still there. Hence we have two + // exclusive references alive at the same time. That's not + // a good look. I'm sorry. + unsafe { vm.as_mut() }.result = Some(result?); } else { vm.result = Some(call( agent, @@ -1183,7 +1216,17 @@ impl Vm { Value::Undefined }; let func = vm.stack.pop().unwrap(); - vm.result = Some(call(agent, func, this_value, Some(ArgumentsList(&args)))?); + if cfg!(feature = "interleaved-gc") { + let mut vm = NonNull::from(vm); + agent.vm_stack.push(vm); + let result = call(agent, func, this_value, Some(ArgumentsList(&args))); + let return_vm = agent.vm_stack.pop().unwrap(); + assert_eq!(vm, return_vm, "VM Stack was misused"); + // SAFETY: This is fairly bonkers-unsafe. I'm sorry. + unsafe { vm.as_mut() }.result = Some(result?); + } else { + vm.result = Some(call(agent, func, this_value, Some(ArgumentsList(&args)))?); + } } Instruction::EvaluateNew => { let args = vm.get_call_args(instr); @@ -1195,10 +1238,22 @@ impl Vm { ); return Err(agent.throw_exception(ExceptionType::TypeError, error_message)); }; - vm.result = Some( - construct(agent, constructor, Some(ArgumentsList(&args)), None) - .map(|result| result.into_value())?, - ); + + if cfg!(feature = "interleaved-gc") { + let mut vm = NonNull::from(vm); + agent.vm_stack.push(vm); + let result = construct(agent, constructor, Some(ArgumentsList(&args)), None) + .map(|result| result.into_value()); + let return_vm = agent.vm_stack.pop().unwrap(); + assert_eq!(vm, return_vm, "VM Stack was misused"); + // SAFETY: This is fairly bonkers-unsafe. I'm sorry. + unsafe { vm.as_mut() }.result = Some(result?); + } else { + vm.result = Some( + construct(agent, constructor, Some(ArgumentsList(&args)), None)? + .into_value(), + ); + } } Instruction::EvaluateSuper => { let EnvironmentIndex::Function(this_env) = get_this_environment(agent) else { @@ -1271,7 +1326,7 @@ impl Vm { } Instruction::EvaluatePropertyAccessWithIdentifierKey => { let property_name_string = - vm.fetch_identifier(executable, instr.args[0].unwrap() as usize); + executable.fetch_identifier(agent, instr.args[0].unwrap() as usize); let base_value = vm.result.take().unwrap(); let strict = agent .running_execution_context() @@ -1542,7 +1597,7 @@ impl Vm { .as_ref() .unwrap() .lexical_environment; - let name = vm.fetch_identifier(executable, instr.args[0].unwrap() as usize); + let name = executable.fetch_identifier(agent, instr.args[0].unwrap() as usize); lex_env.create_mutable_binding(agent, name, false).unwrap(); } Instruction::CreateImmutableBinding => { @@ -1552,7 +1607,7 @@ impl Vm { .as_ref() .unwrap() .lexical_environment; - let name = vm.fetch_identifier(executable, instr.args[0].unwrap() as usize); + let name = executable.fetch_identifier(agent, instr.args[0].unwrap() as usize); lex_env.create_immutable_binding(agent, name, true).unwrap(); } Instruction::CreateCatchBinding => { @@ -1562,7 +1617,7 @@ impl Vm { .as_ref() .unwrap() .lexical_environment; - let name = vm.fetch_identifier(executable, instr.args[0].unwrap() as usize); + let name = executable.fetch_identifier(agent, instr.args[0].unwrap() as usize); lex_env.create_mutable_binding(agent, name, false).unwrap(); lex_env .initialize_binding(agent, name, vm.exception.unwrap()) @@ -1598,7 +1653,17 @@ impl Vm { Instruction::InstanceofOperator => { let lval = vm.stack.pop().unwrap(); let rval = vm.result.take().unwrap(); - vm.result = Some(instanceof_operator(agent, lval, rval)?.into()); + if cfg!(feature = "interleaved-gc") { + let mut vm = NonNull::from(vm); + agent.vm_stack.push(vm); + let result = instanceof_operator(agent, lval, rval); + let return_vm = agent.vm_stack.pop().unwrap(); + assert_eq!(vm, return_vm, "VM Stack was misused"); + // SAFETY: This is fairly bonkers-unsafe. I'm sorry. + unsafe { vm.as_mut() }.result = Some(result?.into()); + } else { + vm.result = Some(instanceof_operator(agent, lval, rval)?.into()); + } } Instruction::BeginSimpleArrayBindingPattern => { let lexical = instr.args[1].unwrap() == 1; @@ -1836,14 +1901,14 @@ impl Vm { fn execute_simple_array_binding( agent: &mut Agent, vm: &mut Vm, - executable: &Executable, + executable: Executable, mut iterator: VmIterator, environment: Option, ) -> JsResult<()> { let mut iterator_is_done = false; loop { - let instr = executable.get_instruction(&mut vm.ip).unwrap(); + let instr = executable.get_instruction(agent, &mut vm.ip).unwrap(); let mut break_after_bind = false; let value = match instr.kind { @@ -1888,7 +1953,7 @@ impl Vm { match instr.kind { Instruction::BindingPatternBind | Instruction::BindingPatternBindRest => { let binding_id = - vm.fetch_identifier(executable, instr.args[0].unwrap() as usize); + executable.fetch_identifier(agent, instr.args[0].unwrap() as usize); let lhs = resolve_binding(agent, binding_id, environment)?; if environment.is_none() { put_value(agent, &lhs, value)?; @@ -1923,23 +1988,23 @@ impl Vm { fn execute_simple_object_binding( agent: &mut Agent, vm: &mut Vm, - executable: &Executable, + executable: Executable, object: Object, environment: Option, ) -> JsResult<()> { let mut excluded_names = AHashSet::new(); loop { - let instr = executable.get_instruction(&mut vm.ip).unwrap(); + let instr = executable.get_instruction(agent, &mut vm.ip).unwrap(); match instr.kind { Instruction::BindingPatternBind | Instruction::BindingPatternBindNamed => { let binding_id = - vm.fetch_identifier(executable, instr.args[0].unwrap() as usize); + executable.fetch_identifier(agent, instr.args[0].unwrap() as usize); let property_key = if instr.kind == Instruction::BindingPatternBind { binding_id.into() } else { let key_value = - vm.fetch_constant(executable, instr.args[1].unwrap() as usize); + executable.fetch_constant(agent, instr.args[1].unwrap() as usize); PropertyKey::try_from(key_value).unwrap() }; excluded_names.insert(property_key); @@ -1955,7 +2020,7 @@ impl Vm { Instruction::BindingPatternGetValueNamed => { let property_key = PropertyKey::from_value( agent, - vm.fetch_constant(executable, instr.args[0].unwrap() as usize), + executable.fetch_constant(agent, instr.args[0].unwrap() as usize), ) .unwrap(); excluded_names.insert(property_key); @@ -1965,7 +2030,7 @@ impl Vm { Instruction::BindingPatternBindRest => { // 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment). let binding_id = - vm.fetch_identifier(executable, instr.args[0].unwrap() as usize); + executable.fetch_identifier(agent, instr.args[0].unwrap() as usize); let lhs = resolve_binding(agent, binding_id, environment)?; // 2. Let restObj be OrdinaryObjectCreate(%Object.prototype%). // 3. Perform ? CopyDataProperties(restObj, value, excludedNames). @@ -1991,11 +2056,11 @@ impl Vm { fn execute_nested_simple_binding( agent: &mut Agent, vm: &mut Vm, - executable: &Executable, + executable: Executable, value: Value, environment: Option, ) -> JsResult<()> { - let instr = executable.get_instruction(&mut vm.ip).unwrap(); + let instr = executable.get_instruction(agent, &mut vm.ip).unwrap(); match instr.kind { Instruction::BeginSimpleArrayBindingPattern => { let new_iterator = VmIterator::from_value(agent, value)?; @@ -2279,26 +2344,92 @@ pub(crate) fn instanceof_operator( impl HeapMarkAndSweep for ExceptionJumpTarget { fn mark_values(&self, queues: &mut WorkQueues) { - self.lexical_environment.mark_values(queues); + let Self { + ip: _, + lexical_environment, + } = self; + lexical_environment.mark_values(queues); + } + + fn sweep_values(&mut self, compactions: &CompactionLists) { + let Self { + ip: _, + lexical_environment, + } = self; + lexical_environment.sweep_values(compactions); + } +} + +impl HeapMarkAndSweep for Vm { + fn mark_values(&self, queues: &mut WorkQueues) { + let Vm { + ip: _, + stack, + reference_stack, + iterator_stack, + exception_jump_target_stack, + result, + exception, + reference, + } = self; + stack.as_slice().mark_values(queues); + reference_stack.as_slice().mark_values(queues); + iterator_stack.as_slice().mark_values(queues); + exception_jump_target_stack.as_slice().mark_values(queues); + result.mark_values(queues); + exception.mark_values(queues); + reference.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.lexical_environment.sweep_values(compactions); + let Vm { + ip: _, + stack, + reference_stack, + iterator_stack, + exception_jump_target_stack, + result, + exception, + reference, + } = self; + stack.as_mut_slice().sweep_values(compactions); + reference_stack.as_mut_slice().sweep_values(compactions); + iterator_stack.as_mut_slice().sweep_values(compactions); + exception_jump_target_stack + .as_mut_slice() + .sweep_values(compactions); + result.sweep_values(compactions); + exception.sweep_values(compactions); + reference.sweep_values(compactions); } } impl HeapMarkAndSweep for SuspendedVm { fn mark_values(&self, queues: &mut WorkQueues) { - self.stack.mark_values(queues); - self.reference_stack.mark_values(queues); - self.iterator_stack.mark_values(queues); - self.exception_jump_target_stack.mark_values(queues); + let Self { + ip: _, + stack, + reference_stack, + iterator_stack, + exception_jump_target_stack, + } = self; + stack.mark_values(queues); + reference_stack.mark_values(queues); + iterator_stack.mark_values(queues); + exception_jump_target_stack.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.stack.sweep_values(compactions); - self.reference_stack.sweep_values(compactions); - self.iterator_stack.sweep_values(compactions); - self.exception_jump_target_stack.sweep_values(compactions); + let Self { + ip: _, + stack, + reference_stack, + iterator_stack, + exception_jump_target_stack, + } = self; + stack.sweep_values(compactions); + reference_stack.sweep_values(compactions); + iterator_stack.sweep_values(compactions); + exception_jump_target_stack.sweep_values(compactions); } } diff --git a/nova_vm/src/engine/local_value.rs b/nova_vm/src/engine/local_value.rs index 274ec893c..127cb86d3 100644 --- a/nova_vm/src/engine/local_value.rs +++ b/nova_vm/src/engine/local_value.rs @@ -295,11 +295,11 @@ where { fn root(self, agent: &Agent) -> Local { let value = self.into_value(); - let stack_values = agent.stack_values.borrow_mut(); + let mut stack_values = agent.stack_values.borrow_mut(); let Ok(index) = u32::try_from(stack_values.len()) else { handle_index_overflow(); }; - agent.stack_values.borrow_mut().push(value); + stack_values.push(value); let inner = LocalInner::ScopedValue(index); Local { @@ -309,6 +309,8 @@ where } } +impl ObjectScopeRoot for T where T: Sized + IntoObject + TryFrom {} + #[cold] #[inline(never)] fn handle_index_overflow() -> ! { diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 46eee04c1..f6ce54e35 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -42,49 +42,53 @@ use crate::ecmascript::builtins::{ weak_map::data::WeakMapHeapData, weak_ref::data::WeakRefHeapData, weak_set::data::WeakSetHeapData, }; -use crate::ecmascript::{ - builtins::ArrayHeapData, - execution::{Environments, Realm, RealmIdentifier}, - scripts_and_modules::{ - module::ModuleIdentifier, - script::{Script, ScriptIdentifier}, - }, - types::{ - BigIntHeapData, BoundFunctionHeapData, BuiltinFunctionHeapData, ECMAScriptFunctionHeapData, - NumberHeapData, Object, ObjectHeapData, String, StringHeapData, SymbolHeapData, Value, - }, -}; -use crate::ecmascript::{ - builtins::{ - control_abstraction_objects::{ - async_function_objects::await_reaction::AwaitReaction, - generator_objects::GeneratorHeapData, - promise_objects::promise_abstract_operations::{ - promise_reaction_records::PromiseReactionRecord, - promise_resolving_functions::PromiseResolvingFunctionHeapData, +use crate::{ + ecmascript::{ + builtins::{ + control_abstraction_objects::{ + async_function_objects::await_reaction::AwaitReaction, + generator_objects::GeneratorHeapData, + promise_objects::promise_abstract_operations::{ + promise_reaction_records::PromiseReactionRecord, + promise_resolving_functions::PromiseResolvingFunctionHeapData, + }, + }, + map::data::MapHeapData, + module::data::ModuleHeapData, + primitive_objects::PrimitiveObjectHeapData, + promise::data::PromiseHeapData, + proxy::data::ProxyHeapData, + regexp::RegExpHeapData, + set::data::SetHeapData, + }, + builtins::{ + embedder_object::data::EmbedderObjectHeapData, + error::ErrorHeapData, + finalization_registry::data::FinalizationRegistryHeapData, + indexed_collections::array_objects::array_iterator_objects::array_iterator::ArrayIteratorHeapData, + keyed_collections::{ + map_objects::map_iterator_objects::map_iterator::MapIteratorHeapData, + set_objects::set_iterator_objects::set_iterator::SetIteratorHeapData, }, + ArrayHeapData, }, - embedder_object::data::EmbedderObjectHeapData, - error::ErrorHeapData, - finalization_registry::data::FinalizationRegistryHeapData, - indexed_collections::array_objects::array_iterator_objects::array_iterator::ArrayIteratorHeapData, - keyed_collections::{ - map_objects::map_iterator_objects::map_iterator::MapIteratorHeapData, - set_objects::set_iterator_objects::set_iterator::SetIteratorHeapData, + execution::{Environments, Realm, RealmIdentifier}, + scripts_and_modules::source_code::SourceCodeHeapData, + scripts_and_modules::{ + module::ModuleIdentifier, + script::{Script, ScriptIdentifier}, + }, + types::{ + bigint::HeapBigInt, BuiltinConstructorHeapData, HeapNumber, HeapString, OrdinaryObject, + BUILTIN_STRINGS_LIST, + }, + types::{ + BigIntHeapData, BoundFunctionHeapData, BuiltinFunctionHeapData, + ECMAScriptFunctionHeapData, NumberHeapData, Object, ObjectHeapData, String, + StringHeapData, SymbolHeapData, Value, }, - map::data::MapHeapData, - module::data::ModuleHeapData, - primitive_objects::PrimitiveObjectHeapData, - promise::data::PromiseHeapData, - proxy::data::ProxyHeapData, - regexp::RegExpHeapData, - set::data::SetHeapData, - }, - scripts_and_modules::source_code::SourceCodeHeapData, - types::{ - bigint::HeapBigInt, BuiltinConstructorHeapData, HeapNumber, HeapString, OrdinaryObject, - BUILTIN_STRINGS_LIST, }, + engine::ExecutableHeapData, }; pub(crate) use heap_bits::{CompactionLists, HeapMarkAndSweep, WorkQueues}; @@ -111,6 +115,8 @@ pub struct Heap { pub embedder_objects: Vec>, pub environments: Environments, pub errors: Vec>, + /// Stores compiled bytecodes + pub(crate) executables: Vec, pub finalization_registrys: Vec>, pub generators: Vec>, pub globals: Vec>, @@ -206,6 +212,7 @@ impl Heap { embedder_objects: Vec::with_capacity(0), environments: Default::default(), errors: Vec::with_capacity(1024), + executables: Vec::with_capacity(1024), source_codes: Vec::with_capacity(0), finalization_registrys: Vec::with_capacity(0), generators: Vec::with_capacity(1024), diff --git a/nova_vm/src/heap/heap_bits.rs b/nova_vm/src/heap/heap_bits.rs index bc1243a5a..77bc16963 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -1,3 +1,5 @@ +use std::num::NonZeroU32; + use ahash::AHashMap; // This Source Code Form is subject to the terms of the Mozilla Public @@ -56,6 +58,7 @@ use crate::ecmascript::{ BUILTIN_STRINGS_LIST, }, }; +use crate::engine::Executable; #[derive(Debug)] pub struct HeapBits { @@ -84,6 +87,7 @@ pub struct HeapBits { pub ecmascript_functions: Box<[bool]>, pub embedder_objects: Box<[bool]>, pub errors: Box<[bool]>, + pub executables: Box<[bool]>, pub source_codes: Box<[bool]>, pub finalization_registrys: Box<[bool]>, pub function_environments: Box<[bool]>, @@ -147,6 +151,7 @@ pub(crate) struct WorkQueues { pub embedder_objects: Vec, pub source_codes: Vec, pub errors: Vec, + pub executables: Vec, pub finalization_registrys: Vec, pub function_environments: Vec, pub generators: Vec, @@ -208,6 +213,7 @@ impl HeapBits { let ecmascript_functions = vec![false; heap.ecmascript_functions.len()]; let embedder_objects = vec![false; heap.embedder_objects.len()]; let errors = vec![false; heap.errors.len()]; + let executables = vec![false; heap.executables.len()]; let source_codes = vec![false; heap.source_codes.len()]; let finalization_registrys = vec![false; heap.finalization_registrys.len()]; let function_environments = vec![false; heap.environments.function.len()]; @@ -267,6 +273,7 @@ impl HeapBits { ecmascript_functions: ecmascript_functions.into_boxed_slice(), embedder_objects: embedder_objects.into_boxed_slice(), errors: errors.into_boxed_slice(), + executables: executables.into_boxed_slice(), source_codes: source_codes.into_boxed_slice(), finalization_registrys: finalization_registrys.into_boxed_slice(), function_environments: function_environments.into_boxed_slice(), @@ -332,6 +339,7 @@ impl WorkQueues { ecmascript_functions: Vec::with_capacity(heap.ecmascript_functions.len() / 4), embedder_objects: Vec::with_capacity(heap.embedder_objects.len() / 4), errors: Vec::with_capacity(heap.errors.len() / 4), + executables: Vec::with_capacity(heap.executables.len() / 4), source_codes: Vec::with_capacity(heap.source_codes.len() / 4), finalization_registrys: Vec::with_capacity(heap.finalization_registrys.len() / 4), function_environments: Vec::with_capacity(heap.environments.function.len() / 4), @@ -421,6 +429,7 @@ impl WorkQueues { embedder_objects, source_codes, errors, + executables, finalization_registrys, function_environments, generators, @@ -494,6 +503,7 @@ impl WorkQueues { && ecmascript_functions.is_empty() && embedder_objects.is_empty() && errors.is_empty() + && executables.is_empty() && source_codes.is_empty() && finalization_registrys.is_empty() && function_environments.is_empty() @@ -547,6 +557,20 @@ impl CompactionList { *index = BaseIndex::from_u32_index(base_index - self.get_shift_for_index(base_index)); } + pub(crate) fn shift_u32_index(&self, index: &mut u32) { + *index -= self.get_shift_for_index(*index); + } + + pub(crate) fn shift_non_zero_u32_index(&self, index: &mut NonZeroU32) { + // 1-indexed value + let base_index: u32 = (*index).into(); + // 0-indexed value + let base_index = base_index - 1; + let shifted_base_index = base_index - self.get_shift_for_index(base_index); + // SAFETY: Shifted base index can be 0, adding 1 makes it non-zero. + *index = unsafe { NonZeroU32::new_unchecked(shifted_base_index + 1) }; + } + fn build(indexes: Vec, shifts: Vec) -> Self { assert_eq!(indexes.len(), shifts.len()); Self { @@ -690,6 +714,7 @@ pub(crate) struct CompactionLists { pub embedder_objects: CompactionList, pub source_codes: CompactionList, pub errors: CompactionList, + pub executables: CompactionList, pub finalization_registrys: CompactionList, pub function_environments: CompactionList, pub generators: CompactionList, @@ -769,6 +794,7 @@ impl CompactionLists { #[cfg(feature = "date")] dates: CompactionList::from_mark_bits(&bits.dates), errors: CompactionList::from_mark_bits(&bits.errors), + executables: CompactionList::from_mark_bits(&bits.executables), maps: CompactionList::from_mark_bits(&bits.maps), map_iterators: CompactionList::from_mark_bits(&bits.map_iterators), numbers: CompactionList::from_mark_bits(&bits.numbers), diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index 31cd035b4..db8b74a1b 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -25,45 +25,59 @@ use crate::ecmascript::builtins::shared_array_buffer::SharedArrayBuffer; use crate::ecmascript::builtins::{data_view::DataView, ArrayBuffer}; #[cfg(feature = "weak-refs")] use crate::ecmascript::builtins::{weak_map::WeakMap, weak_ref::WeakRef, weak_set::WeakSet}; -use crate::ecmascript::{ - builtins::{ - bound_function::BoundFunction, - control_abstraction_objects::{ - async_function_objects::await_reaction::AwaitReactionIdentifier, - generator_objects::Generator, - promise_objects::promise_abstract_operations::{ - promise_reaction_records::PromiseReaction, - promise_resolving_functions::BuiltinPromiseResolvingFunction, +use crate::{ + ecmascript::{ + builtins::{ + bound_function::BoundFunction, + control_abstraction_objects::{ + async_function_objects::await_reaction::AwaitReactionIdentifier, + generator_objects::Generator, + promise_objects::promise_abstract_operations::{ + promise_reaction_records::PromiseReaction, + promise_resolving_functions::BuiltinPromiseResolvingFunction, + }, }, + embedder_object::EmbedderObject, + error::Error, + finalization_registry::FinalizationRegistry, + indexed_collections::array_objects::array_iterator_objects::array_iterator::ArrayIterator, + keyed_collections::{ + map_objects::map_iterator_objects::map_iterator::MapIterator, + set_objects::set_iterator_objects::set_iterator::SetIterator, + }, + map::Map, + module::Module, + primitive_objects::PrimitiveObject, + promise::Promise, + proxy::Proxy, + regexp::RegExp, + set::Set, + Array, BuiltinConstructorFunction, BuiltinFunction, ECMAScriptFunction, }, - embedder_object::EmbedderObject, - error::Error, - finalization_registry::FinalizationRegistry, - indexed_collections::array_objects::array_iterator_objects::array_iterator::ArrayIterator, - keyed_collections::{ - map_objects::map_iterator_objects::map_iterator::MapIterator, - set_objects::set_iterator_objects::set_iterator::SetIterator, + execution::{ + Agent, DeclarativeEnvironmentIndex, Environments, FunctionEnvironmentIndex, + GlobalEnvironmentIndex, ObjectEnvironmentIndex, RealmIdentifier, + }, + scripts_and_modules::{script::ScriptIdentifier, source_code::SourceCode}, + types::{ + bigint::HeapBigInt, HeapNumber, HeapString, OrdinaryObject, Symbol, + BUILTIN_STRINGS_LIST, }, - map::Map, - module::Module, - primitive_objects::PrimitiveObject, - promise::Promise, - proxy::Proxy, - regexp::RegExp, - set::Set, - Array, BuiltinConstructorFunction, BuiltinFunction, ECMAScriptFunction, - }, - execution::{ - DeclarativeEnvironmentIndex, Environments, FunctionEnvironmentIndex, - GlobalEnvironmentIndex, ObjectEnvironmentIndex, RealmIdentifier, - }, - scripts_and_modules::{script::ScriptIdentifier, source_code::SourceCode}, - types::{ - bigint::HeapBigInt, HeapNumber, HeapString, OrdinaryObject, Symbol, BUILTIN_STRINGS_LIST, }, + engine::Executable, }; -pub fn heap_gc(heap: &mut Heap, root_realms: &mut [Option]) { +pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option]) { + let Agent { + heap, + execution_context_stack, + stack_values, + vm_stack, + options: _, + symbol_id: _, + global_symbol_registry: _, + host_hooks: _, + } = agent; let mut bits = HeapBits::new(heap); let mut queues = WorkQueues::new(heap); @@ -73,6 +87,16 @@ pub fn heap_gc(heap: &mut Heap, root_realms: &mut [Option]) { } }); + execution_context_stack.iter().for_each(|ctx| { + ctx.mark_values(&mut queues); + }); + stack_values + .borrow() + .iter() + .for_each(|value| value.mark_values(&mut queues)); + vm_stack.iter().for_each(|vm_ptr| { + unsafe { vm_ptr.as_ref() }.mark_values(&mut queues); + }); let mut last_filled_global_value = None; heap.globals.iter().enumerate().for_each(|(i, &value)| { if let Some(value) = value { @@ -124,6 +148,7 @@ pub fn heap_gc(heap: &mut Heap, root_realms: &mut [Option]) { embedder_objects, environments, errors, + executables, source_codes, finalization_registrys, generators, @@ -382,6 +407,19 @@ pub fn heap_gc(heap: &mut Heap, root_realms: &mut [Option]) { errors.get(index).mark_values(&mut queues); } }); + let mut executable_marks: Box<[Executable]> = queues.executables.drain(..).collect(); + executable_marks.sort(); + executable_marks.iter().for_each(|&idx| { + let index = idx.get_index(); + if let Some(marked) = bits.executables.get_mut(index) { + if *marked { + // Already marked, ignore + return; + } + *marked = true; + executables.get(index).mark_values(&mut queues); + } + }); let mut source_code_marks: Box<[SourceCode]> = queues.source_codes.drain(..).collect(); source_code_marks.sort(); source_code_marks.iter().for_each(|&idx| { @@ -931,16 +969,27 @@ pub fn heap_gc(heap: &mut Heap, root_realms: &mut [Option]) { }); } - sweep(heap, &bits, root_realms); + sweep(agent, &bits, root_realms); } -fn sweep(heap: &mut Heap, bits: &HeapBits, root_realms: &mut [Option]) { +fn sweep(agent: &mut Agent, bits: &HeapBits, root_realms: &mut [Option]) { let compactions = CompactionLists::create_from_bits(bits); for realm in root_realms { realm.sweep_values(&compactions); } + let Agent { + heap, + execution_context_stack, + stack_values, + vm_stack, + options: _, + symbol_id: _, + global_symbol_registry: _, + host_hooks: _, + } = agent; + let Heap { #[cfg(feature = "array-buffer")] array_buffers, @@ -960,6 +1009,7 @@ fn sweep(heap: &mut Heap, bits: &HeapBits, root_realms: &mut [Option