From bbfb20506dbe975e8cebecc9222e5bd484fd1057 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Mon, 14 Oct 2024 15:49:06 +0300 Subject: [PATCH 01/13] feat(engine): Implement interleaved garbage collection This is a mostly-working interleaved, exact GC except that it is currently built in a very unsafe way and exhibits undefined behaviour in about 50 test262 cases. That's not good at all. I will need to keep working on this to put it on a stronger footing, but the basic idea is here. --- .../operations_on_objects.rs | 1 - .../builtins/builtin_constructor.rs | 29 +-- .../async_function_objects/await_reaction.rs | 3 +- .../generator_objects.rs | 10 +- .../builtins/ecmascript_function.rs | 18 +- .../src/ecmascript/builtins/global_object.rs | 13 +- nova_vm/src/ecmascript/execution/agent.rs | 14 +- nova_vm/src/ecmascript/execution/realm.rs | 30 ++- .../ecmascript/scripts_and_modules/script.rs | 13 +- .../function_definitions.rs | 26 +- .../types/language/function/data.rs | 12 +- nova_vm/src/engine/bytecode.rs | 2 + nova_vm/src/engine/bytecode/executable.rs | 52 ++-- .../bytecode/heap_allocated_bytecode.rs | 141 +++++++++++ nova_vm/src/engine/bytecode/vm.rs | 234 ++++++++++++------ nova_vm/src/heap/heap_gc.rs | 83 +++++-- 16 files changed, 471 insertions(+), 210 deletions(-) create mode 100644 nova_vm/src/engine/bytecode/heap_allocated_bytecode.rs 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/builtin_constructor.rs b/nova_vm/src/ecmascript/builtins/builtin_constructor.rs index 4a68baaed..d724d81b3 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; @@ -29,7 +26,7 @@ use crate::{ BUILTIN_STRING_MEMORY, }, }, - engine::Executable, + engine::{Executable, HeapAllocatedBytecode}, heap::{ indexes::BuiltinConstructorIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, ObjectEntry, ObjectEntryPropertyDescriptor, WorkQueues, @@ -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, @@ -416,7 +413,7 @@ pub(crate) fn create_builtin_constructor( let compiled_initializer_bytecode = args .compiled_initializer_bytecode - .map(|bytecode| NonNull::from(Box::leak(bytecode))); + .map(HeapAllocatedBytecode::new); // 13. Return func. agent.heap.create(BuiltinConstructorHeapData { @@ -458,11 +455,7 @@ impl HeapMarkAndSweep for BuiltinConstructorHeapData { 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); - } + self.compiled_initializer_bytecode.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { @@ -471,16 +464,6 @@ impl HeapMarkAndSweep for BuiltinConstructorHeapData { 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); - } + self.compiled_initializer_bytecode.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..b746c2a06 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), 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..a0358ec9b 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::{ExecutionResult, HeapAllocatedBytecode, SuspendedVm, Vm}, heap::{ indexes::{BaseIndex, GeneratorIndex}, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, WorkQueues, @@ -76,8 +76,8 @@ impl Generator { // 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), }; // GeneratorStart: 4.f. Remove acGenContext from the execution context stack and restore the @@ -194,7 +194,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 @@ -346,7 +346,7 @@ pub(crate) enum GeneratorState { // SUSPENDED-START has `vm_or_args` set to Arguments, SUSPENDED-YIELD has it set to Vm. Suspended { vm_or_args: VmOrArguments, - executable: Executable, + executable: HeapAllocatedBytecode, execution_context: ExecutionContext, }, Executing, diff --git a/nova_vm/src/ecmascript/builtins/ecmascript_function.rs b/nova_vm/src/ecmascript/builtins/ecmascript_function.rs index 26bb4e4b2..24ac08e89 100644 --- a/nova_vm/src/ecmascript/builtins/ecmascript_function.rs +++ b/nova_vm/src/ecmascript/builtins/ecmascript_function.rs @@ -1050,11 +1050,7 @@ impl HeapMarkAndSweep for ECMAScriptFunctionHeapData { .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); - } + self.compiled_bytecode.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { @@ -1073,16 +1069,6 @@ impl HeapMarkAndSweep for ECMAScriptFunctionHeapData { 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); - } + self.compiled_bytecode.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..2b08ac239 100644 --- a/nova_vm/src/ecmascript/builtins/global_object.rs +++ b/nova_vm/src/ecmascript/builtins/global_object.rs @@ -28,7 +28,7 @@ use crate::{ }, types::{Function, IntoValue, String, Value, BUILTIN_STRING_MEMORY}, }, - engine::{Executable, Vm}, + engine::{Executable, HeapAllocatedBytecode, Vm}, heap::IntrinsicFunctionIndexes, }; @@ -331,11 +331,18 @@ pub fn perform_eval( // 29. If result is a normal completion, then let result = if result.is_ok() { - let exe = Executable::compile_eval_body(agent, &script.body); + // TODO: We have a problem here. First, we're forcing an extra heap + // allocation for no good reason. + // Second, this executable is not accessible by the heap and will thus + // break if garbage collection triggers within the eval. + let exe = HeapAllocatedBytecode::new(Executable::compile_eval_body(agent, &script.body)); // 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.drop() }; + result } else { Err(result.err().unwrap()) }; diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index c504a2511..ada415710 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 { @@ -244,7 +242,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 +251,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 +260,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 +269,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/realm.rs b/nova_vm/src/ecmascript/execution/realm.rs index 5fef330bf..c64588609 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 Realm { + 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 Realm { + 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/scripts_and_modules/script.rs b/nova_vm/src/ecmascript/scripts_and_modules/script.rs index e3938229e..7fd4d31e0 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules/script.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules/script.rs @@ -19,7 +19,7 @@ use crate::{ }, types::{IntoValue, String, Value, BUILTIN_STRING_MEMORY}, }, - engine::{Executable, Vm}, + engine::{Executable, HeapAllocatedBytecode, Vm}, heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}, }; use ahash::AHashSet; @@ -140,6 +140,9 @@ pub struct Script { /// parts. pub(crate) ecmascript_code: ManuallyDrop>, + /// Stores the compiled bytecode of a Script. + pub(crate) compiled_bytecode: Option, + /// ### \[\[LoadedModules]] /// /// A map from the specifier strings imported by this script to the @@ -168,11 +171,13 @@ impl HeapMarkAndSweep for Script { fn mark_values(&self, queues: &mut WorkQueues) { self.realm.mark_values(queues); self.source_code.mark_values(queues); + self.compiled_bytecode.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { self.realm.sweep_values(compactions); self.source_code.sweep_values(compactions); + self.compiled_bytecode.sweep_values(compactions); } } @@ -225,6 +230,7 @@ pub fn parse_script( // [[HostDefined]]: hostDefined, host_defined, source_code, + compiled_bytecode: None, }) // } } @@ -284,11 +290,12 @@ 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 = HeapAllocatedBytecode::new(Executable::compile_script(agent, script)); + agent[script].compiled_bytecode = Some(bytecode); // 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() + Vm::execute(agent, bytecode, None).into_js_result() } else { Err(result.err().unwrap()) }; 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..bab041e90 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, @@ -36,7 +34,7 @@ use crate::{ Value, BUILTIN_STRING_MEMORY, }, }, - engine::{Executable, ExecutionResult, FunctionExpression, Vm}, + engine::{Executable, ExecutionResult, FunctionExpression, HeapAllocatedBytecode, Vm}, heap::CreateHeapData, }; use oxc_ast::ast::{self}; @@ -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 = HeapAllocatedBytecode::new(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 = HeapAllocatedBytecode::new(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 @@ -385,7 +375,7 @@ pub(crate) fn evaluate_generator_body( // 4. Perform GeneratorStart(G, FunctionBody). // SAFETY: We're alive so SourceCode must be too. let data = CompileFunctionBodyData::new(agent, function_object); - let executable = Executable::compile_function_body(agent, data); + let executable = HeapAllocatedBytecode::new(Executable::compile_function_body(agent, data)); agent[generator].generator_state = Some(GeneratorState::Suspended { vm_or_args: VmOrArguments::Arguments(arguments_list.0.into()), executable, diff --git a/nova_vm/src/ecmascript/types/language/function/data.rs b/nova_vm/src/ecmascript/types/language/function/data.rs index 2cf8af264..406a67bf5 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::{ @@ -13,7 +11,7 @@ use crate::{ scripts_and_modules::source_code::SourceCode, types::{OrdinaryObject, String, Value}, }, - engine::Executable, + engine::HeapAllocatedBytecode, heap::element_array::ElementsVector, }; @@ -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. @@ -96,7 +94,7 @@ impl Drop for BuiltinConstructorHeapData { 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()) }); + unsafe { exe.drop() }; } } } @@ -107,7 +105,7 @@ pub struct ECMAScriptFunctionHeapData { 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, } @@ -118,7 +116,7 @@ impl Drop for ECMAScriptFunctionHeapData { 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()) }); + unsafe { exe.drop() }; } } } diff --git a/nova_vm/src/engine/bytecode.rs b/nova_vm/src/engine/bytecode.rs index 2d7f96317..a4bb8055b 100644 --- a/nova_vm/src/engine/bytecode.rs +++ b/nova_vm/src/engine/bytecode.rs @@ -3,6 +3,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. mod executable; +mod heap_allocated_bytecode; mod instructions; pub(super) mod iterator; mod vm; @@ -11,5 +12,6 @@ pub(crate) use executable::{ is_reference, CompileContext, CompileEvaluation, Executable, FunctionExpression, IndexType, NamedEvaluationParameter, SendableRef, }; +pub(crate) use heap_allocated_bytecode::HeapAllocatedBytecode; pub(crate) use instructions::{Instruction, InstructionIter}; pub(crate) use vm::{instanceof_operator, ExecutionResult, SuspendedVm, Vm}; diff --git a/nova_vm/src/engine/bytecode/executable.rs b/nova_vm/src/engine/bytecode/executable.rs index de5acd35e..8cc54f36a 100644 --- a/nova_vm/src/engine/bytecode/executable.rs +++ b/nova_vm/src/engine/bytecode/executable.rs @@ -184,6 +184,8 @@ impl CompileContext<'_> { eprintln!(); } + println!("{:?}", data.params); + function_declaration_instantiation::instantiation( self, data.params, @@ -527,35 +529,37 @@ pub(crate) struct Executable { pub(crate) class_initializer_bytecodes: Box<[(Option, bool)]>, } -impl Executable { - pub(super) fn get_instruction(&self, ip: &mut usize) -> Option { - if *ip >= self.instructions.len() { - return None; - } +pub(super) fn get_instruction(instructions: &[u8], ip: &mut usize) -> Option { + if *ip >= instructions.len() { + return None; + } - let kind: Instruction = - unsafe { std::mem::transmute::(self.instructions[*ip]) }; - *ip += 1; + let kind: Instruction = unsafe { std::mem::transmute::(instructions[*ip]) }; + *ip += 1; - let mut args: [Option; 2] = [None, None]; + 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; - } + 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; } + } - Some(Instr { kind, args }) + Some(Instr { kind, args }) +} + +impl Executable { + #[inline] + pub(super) fn get_instruction(&self, ip: &mut usize) -> Option { + get_instruction(&self.instructions, ip) } pub(crate) fn compile_script(agent: &mut Agent, script: ScriptIdentifier) -> Executable { diff --git a/nova_vm/src/engine/bytecode/heap_allocated_bytecode.rs b/nova_vm/src/engine/bytecode/heap_allocated_bytecode.rs new file mode 100644 index 000000000..68c09c8d7 --- /dev/null +++ b/nova_vm/src/engine/bytecode/heap_allocated_bytecode.rs @@ -0,0 +1,141 @@ +// 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/. + +use std::ptr::NonNull; + +use crate::{ + ecmascript::types::{String, Value}, + heap::HeapMarkAndSweep, +}; + +use super::{ + executable::{get_instruction, ArrowFunctionExpression}, + instructions::Instr, + Executable, FunctionExpression, +}; + +/// Abstracts over a heap-allocated bytecode structure that can be safely +/// run interleaved with garbage collection +#[derive(Debug, Clone, Copy)] +#[repr(transparent)] +pub(crate) struct HeapAllocatedBytecode { + pointer: NonNull, +} + +impl HeapAllocatedBytecode { + pub(crate) fn new(bytecode: Executable) -> Self { + Self { + // SAFETY: Box::leak(Box::new()) never returns a null pointer for + // non-ZST. + pointer: unsafe { NonNull::new_unchecked(Box::leak(Box::new(bytecode))) }, + } + } + + pub(crate) fn clone(&self) -> Self { + Self::new(unsafe { self.pointer.as_ref() }.clone()) + } + + /// SAFETY: The returned reference is valid until the Script, Module, or + /// Function it was compiled from is garbage collected. + #[inline] + pub(super) fn get_instructions(self) -> &'static [u8] { + // SAFETY: As long as we're alive the instructions Box lives, and it is + // never accessed mutably. + unsafe { &(*self.pointer.as_ptr()).instructions } + } + + /// SAFETY: The returned reference is valid until the next garbage + /// collection happens. + #[inline] + pub(super) fn get_constants(self) -> &'static [Value] { + // SAFETY: As long as we're alive the instructions Box lives, and it is + // never accessed mutably. + unsafe { &(*self.pointer.as_ptr()).constants } + } + + #[inline] + pub(super) fn get_instruction(self, ip: &mut usize) -> Option { + // SAFETY: As long as we're alive the instructions Box lives, and it is + // never accessed mutably. + get_instruction(unsafe { &(*self.pointer.as_ptr()).instructions }, ip) + } + + #[inline] + pub(super) fn fetch_identifier(self, 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 = unsafe { (*self.pointer.as_ptr()).constants[index] }; + let Ok(value) = String::try_from(value) else { + handle_identifier_failure() + }; + value + } + + #[inline] + pub(super) fn fetch_constant(self, 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. + unsafe { (*self.pointer.as_ptr()).constants[index] } + } + + /// SAFETY: The returned reference is valid the next garbage collection + /// happens. + pub(super) fn fetch_function_expression(self, index: usize) -> &'static FunctionExpression { + unsafe { &(*self.pointer.as_ptr()).function_expressions[index] } + } + + /// SAFETY: The returned reference is valid the next garbage collection + /// happens. + pub(super) fn fetch_arrow_function_expression( + self, + index: usize, + ) -> &'static ArrowFunctionExpression { + unsafe { &(*self.pointer.as_ptr()).arrow_function_expressions[index] } + } + + /// SAFETY: The returned reference is valid the next garbage collection + /// happens. + pub(super) fn fetch_class_initializer_bytecode( + self, + index: usize, + ) -> &'static (Option, bool) { + unsafe { &(*self.pointer.as_ptr()).class_initializer_bytecodes[index] } + } + + /// SAFETY: Normal drop safety rules apply. + pub(crate) unsafe fn drop(self) { + drop(unsafe { Box::from_raw(self.pointer.as_ptr()) }); + } +} + +#[cold] +fn handle_identifier_failure() -> ! { + panic!("Invalid identifier index: Value was not a String") +} + +impl HeapMarkAndSweep for HeapAllocatedBytecode { + fn mark_values(&self, queues: &mut crate::heap::WorkQueues) { + // SAFETY: This is a valid, non-null pointer to an owned Executable + // that cannot have any live mutable references to it. + unsafe { self.pointer.as_ref() }.mark_values(queues); + } + + fn sweep_values(&mut self, compactions: &crate::heap::CompactionLists) { + // 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 for running the bytecode. 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 { self.pointer.as_mut() }.sweep_values(compactions); + } +} + +unsafe impl Send for HeapAllocatedBytecode {} diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index 86c8ab4d4..909feb582 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -38,7 +38,7 @@ use crate::{ agent::{resolve_binding, ExceptionType, JsError}, get_this_environment, new_class_static_element_environment, new_declarative_environment, Agent, ECMAScriptCodeEvaluationState, EnvironmentIndex, - JsResult, ProtoIntrinsics, + JsResult, ProtoIntrinsics, RealmIdentifier, }, types::{ get_this_value, get_value, initialize_referenced_binding, is_private_reference, @@ -47,14 +47,16 @@ use crate::{ Reference, String, Value, BUILTIN_STRING_MEMORY, }, }, - heap::{CompactionLists, HeapMarkAndSweep, WellKnownSymbolIndexes, WorkQueues}, + heap::{ + heap_gc::heap_gc, CompactionLists, HeapMarkAndSweep, WellKnownSymbolIndexes, WorkQueues, + }, }; use super::{ executable::{NamedEvaluationParameter, SendableRef}, instructions::Instr, iterator::{ObjectPropertiesIterator, VmIterator}, - Executable, IndexType, Instruction, InstructionIter, + Executable, HeapAllocatedBytecode, IndexType, Instruction, InstructionIter, }; struct EmptyParametersList(ast::FormalParameters<'static>); @@ -144,7 +146,7 @@ impl SuspendedVm { pub(crate) fn resume( self, agent: &mut Agent, - executable: &Executable, + executable: HeapAllocatedBytecode, value: Value, ) -> ExecutionResult { let vm = Vm::from_suspended(self); @@ -154,7 +156,7 @@ impl SuspendedVm { pub(crate) fn resume_throw( self, agent: &mut Agent, - executable: &Executable, + executable: HeapAllocatedBytecode, err: Value, ) -> ExecutionResult { // Optimisation: Avoid unsuspending the Vm if we're just going to throw @@ -217,7 +219,7 @@ impl Vm { /// Executes an executable using the virtual machine. pub(crate) fn execute( agent: &mut Agent, - executable: &Executable, + executable: HeapAllocatedBytecode, arguments: Option<&[Value]>, ) -> ExecutionResult { let mut vm = Vm::new(); @@ -233,11 +235,11 @@ impl Vm { if agent.options.print_internals { eprintln!(); eprintln!("=== Executing Executable ==="); - eprintln!("Constants: {:?}", executable.constants); + eprintln!("Constants: {:?}", executable.get_constants()); eprintln!(); eprintln!("Instructions:"); - let iter = InstructionIter::new(&executable.instructions); + let iter = InstructionIter::new(executable.get_instructions()); for (ip, instr) in iter { match instr.kind.argument_count() { 0 => { @@ -264,7 +266,7 @@ impl Vm { pub fn resume( mut self, agent: &mut Agent, - executable: &Executable, + executable: HeapAllocatedBytecode, value: Value, ) -> ExecutionResult { self.result = Some(value); @@ -274,7 +276,7 @@ impl Vm { pub fn resume_throw( mut self, agent: &mut Agent, - executable: &Executable, + executable: HeapAllocatedBytecode, err: Value, ) -> ExecutionResult { let err = JsError::new(err); @@ -284,8 +286,34 @@ impl Vm { self.inner_execute(agent, executable) } - fn inner_execute(mut self, agent: &mut Agent, executable: &Executable) -> ExecutionResult { + fn inner_execute( + mut self, + agent: &mut Agent, + executable: HeapAllocatedBytecode, + ) -> ExecutionResult { + let do_gc = !agent.options.disable_gc; + let mut instr_count = 0u8; while let Some(instr) = executable.get_instruction(&mut self.ip) { + 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 +365,7 @@ impl Vm { fn execute_instruction( agent: &mut Agent, vm: &mut Vm, - executable: &Executable, + executable: HeapAllocatedBytecode, instr: &Instr, ) -> JsResult { if agent.options.print_internals { @@ -398,7 +426,7 @@ impl Vm { } } Instruction::ResolveBinding => { - let identifier = vm.fetch_identifier(executable, instr.args[0].unwrap() as usize); + let identifier = executable.fetch_identifier(instr.args[0].unwrap() as usize); let reference = resolve_binding(agent, identifier, None)?; @@ -416,7 +444,7 @@ impl Vm { }); } Instruction::LoadConstant => { - let constant = vm.fetch_constant(executable, instr.args[0].unwrap() as usize); + let constant = executable.fetch_constant(instr.args[0].unwrap() as usize); vm.stack.push(constant); } Instruction::Load => { @@ -443,7 +471,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(instr.args[0].unwrap() as usize); vm.result = Some(constant); } Instruction::UnaryMinus => { @@ -489,10 +517,8 @@ 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 function_expression = + executable.fetch_function_expression(instr.args[0].unwrap() as usize); 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())?; @@ -576,10 +602,8 @@ impl Vm { // c. Return unused. } Instruction::ObjectDefineGetter => { - let function_expression = executable - .function_expressions - .get(instr.args[0].unwrap() as usize) - .unwrap(); + let function_expression = + executable.fetch_function_expression(instr.args[0].unwrap() as usize); 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())?; @@ -655,10 +679,8 @@ impl Vm { // c. Return unused. } Instruction::ObjectDefineSetter => { - let function_expression = executable - .function_expressions - .get(instr.args[0].unwrap() as usize) - .unwrap(); + let function_expression = + executable.fetch_function_expression(instr.args[0].unwrap() as usize); 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())?; @@ -807,10 +829,8 @@ impl Vm { } Instruction::InstantiateArrowFunctionExpression => { // ArrowFunction : ArrowParameters => ConciseBody - let function_expression = executable - .arrow_function_expressions - .get(instr.args[0].unwrap() as usize) - .unwrap(); + let function_expression = + executable.fetch_arrow_function_expression(instr.args[0].unwrap() as usize); // 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. @@ -863,10 +883,8 @@ 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 function_expression = + executable.fetch_function_expression(instr.args[0].unwrap() as usize); let ECMAScriptCodeEvaluationState { lexical_environment, private_environment, @@ -919,9 +937,8 @@ impl Vm { }; 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(), - )))); + agent[function].compiled_bytecode = + Some(HeapAllocatedBytecode::new(compiled_bytecode.clone())); } set_function_name(agent, function, name, None); if !function_expression.expression.get().r#async @@ -975,10 +992,8 @@ 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 function_expression = + executable.fetch_function_expression(instr.args[0].unwrap() as usize); let has_constructor_parent = instr.args[1].unwrap(); assert!(has_constructor_parent <= 1); let has_constructor_parent = has_constructor_parent == 1; @@ -1019,9 +1034,8 @@ impl Vm { }; 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(), - )))); + agent[function].compiled_bytecode = + Some(HeapAllocatedBytecode::new(compiled_bytecode.clone())); } set_function_name(agent, function, class_name.into(), None); make_constructor(agent, function, Some(false), Some(proto)); @@ -1052,13 +1066,9 @@ 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(); + .fetch_class_initializer_bytecode(class_initializer_bytecode_index as usize); let has_constructor_parent = *has_constructor_parent; - let compiled_initializer_bytecode = compiled_initializer_bytecode - .as_ref() - .map(|bytecode| Box::new(bytecode.clone())); + let compiled_initializer_bytecode = compiled_initializer_bytecode.clone(); let class_name = String::try_from(vm.stack.pop().unwrap()).unwrap(); let function_prototype = if has_constructor_parent { @@ -1149,12 +1159,22 @@ impl Vm { vm.result = Some(perform_eval(agent, eval_arg, true, strict_caller)?); } } else { - vm.result = Some(call( - agent, - func, - Value::Undefined, - Some(ArgumentsList(&args)), - )?); + 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?); } } Instruction::EvaluateCall => { @@ -1183,7 +1203,13 @@ impl Vm { Value::Undefined }; let func = vm.stack.pop().unwrap(); - vm.result = Some(call(agent, func, this_value, Some(ArgumentsList(&args)))?); + 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?); } Instruction::EvaluateNew => { let args = vm.get_call_args(instr); @@ -1195,10 +1221,14 @@ 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())?, - ); + 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?); } Instruction::EvaluateSuper => { let EnvironmentIndex::Function(this_env) = get_this_environment(agent) else { @@ -1271,7 +1301,7 @@ impl Vm { } Instruction::EvaluatePropertyAccessWithIdentifierKey => { let property_name_string = - vm.fetch_identifier(executable, instr.args[0].unwrap() as usize); + executable.fetch_identifier(instr.args[0].unwrap() as usize); let base_value = vm.result.take().unwrap(); let strict = agent .running_execution_context() @@ -1542,7 +1572,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(instr.args[0].unwrap() as usize); lex_env.create_mutable_binding(agent, name, false).unwrap(); } Instruction::CreateImmutableBinding => { @@ -1552,7 +1582,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(instr.args[0].unwrap() as usize); lex_env.create_immutable_binding(agent, name, true).unwrap(); } Instruction::CreateCatchBinding => { @@ -1562,7 +1592,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(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 +1628,13 @@ 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()); + 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()); } Instruction::BeginSimpleArrayBindingPattern => { let lexical = instr.args[1].unwrap() == 1; @@ -1836,7 +1872,7 @@ impl Vm { fn execute_simple_array_binding( agent: &mut Agent, vm: &mut Vm, - executable: &Executable, + executable: HeapAllocatedBytecode, mut iterator: VmIterator, environment: Option, ) -> JsResult<()> { @@ -1887,8 +1923,7 @@ impl Vm { match instr.kind { Instruction::BindingPatternBind | Instruction::BindingPatternBindRest => { - let binding_id = - vm.fetch_identifier(executable, instr.args[0].unwrap() as usize); + let binding_id = executable.fetch_identifier(instr.args[0].unwrap() as usize); let lhs = resolve_binding(agent, binding_id, environment)?; if environment.is_none() { put_value(agent, &lhs, value)?; @@ -1923,7 +1958,7 @@ impl Vm { fn execute_simple_object_binding( agent: &mut Agent, vm: &mut Vm, - executable: &Executable, + executable: HeapAllocatedBytecode, object: Object, environment: Option, ) -> JsResult<()> { @@ -1933,13 +1968,11 @@ impl Vm { let instr = executable.get_instruction(&mut vm.ip).unwrap(); match instr.kind { Instruction::BindingPatternBind | Instruction::BindingPatternBindNamed => { - let binding_id = - vm.fetch_identifier(executable, instr.args[0].unwrap() as usize); + let binding_id = executable.fetch_identifier(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); + let key_value = executable.fetch_constant(instr.args[1].unwrap() as usize); PropertyKey::try_from(key_value).unwrap() }; excluded_names.insert(property_key); @@ -1955,7 +1988,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(instr.args[0].unwrap() as usize), ) .unwrap(); excluded_names.insert(property_key); @@ -1964,8 +1997,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); + let binding_id = executable.fetch_identifier(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,7 +2023,7 @@ impl Vm { fn execute_nested_simple_binding( agent: &mut Agent, vm: &mut Vm, - executable: &Executable, + executable: HeapAllocatedBytecode, value: Value, environment: Option, ) -> JsResult<()> { @@ -2287,6 +2319,50 @@ impl HeapMarkAndSweep for ExceptionJumpTarget { } } +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) { + 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); diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index 31cd035b4..546188af9 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -54,7 +54,7 @@ use crate::ecmascript::{ Array, BuiltinConstructorFunction, BuiltinFunction, ECMAScriptFunction, }, execution::{ - DeclarativeEnvironmentIndex, Environments, FunctionEnvironmentIndex, + Agent, DeclarativeEnvironmentIndex, Environments, FunctionEnvironmentIndex, GlobalEnvironmentIndex, ObjectEnvironmentIndex, RealmIdentifier, }, scripts_and_modules::{script::ScriptIdentifier, source_code::SourceCode}, @@ -63,7 +63,17 @@ use crate::ecmascript::{ }, }; -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 +83,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 { @@ -931,16 +951,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, @@ -1366,22 +1397,44 @@ fn sweep(heap: &mut Heap, bits: &HeapBits, root_realms: &mut [Option Date: Thu, 17 Oct 2024 11:40:40 +0300 Subject: [PATCH 02/13] fix: Incorrect HeapMarkAndSweep logic in functions, try Local usage --- .../operations_on_iterator_objects.rs | 18 +++- nova_vm/src/ecmascript/builtins/array/data.rs | 22 +++-- .../ecmascript/builtins/array_buffer/data.rs | 12 ++- .../src/ecmascript/builtins/bound_function.rs | 36 +++++-- .../builtins/builtin_constructor.rs | 44 ++++++--- .../ecmascript/builtins/builtin_function.rs | 26 +++-- .../async_function_objects/await_reaction.rs | 26 +++-- .../generator_objects.rs | 39 ++++++-- .../promise_capability_records.rs | 12 ++- .../src/ecmascript/builtins/data_view/data.rs | 6 +- nova_vm/src/ecmascript/builtins/date/data.rs | 12 ++- .../builtins/ecmascript_function.rs | 95 +++++++++++++------ .../builtins/embedder_object/data.rs | 8 +- nova_vm/src/ecmascript/builtins/error/data.rs | 25 +++-- .../builtins/finalization_registry/data.rs | 6 +- .../array_iterator_objects/array_iterator.rs | 20 +++- .../map_iterator_objects/map_iterator.rs | 20 +++- .../set_iterator_objects/set_iterator.rs | 20 +++- nova_vm/src/ecmascript/builtins/map/data.rs | 10 +- .../src/ecmascript/builtins/module/data.rs | 38 ++++++-- .../ecmascript/builtins/primitive_objects.rs | 10 +- .../src/ecmascript/builtins/promise/data.rs | 30 ++++-- .../src/ecmascript/builtins/regexp/data.rs | 18 +++- nova_vm/src/ecmascript/builtins/set/data.rs | 8 +- .../src/ecmascript/builtins/weak_map/data.rs | 22 +++-- .../src/ecmascript/builtins/weak_ref/data.rs | 22 +++-- nova_vm/src/ecmascript/execution/agent.rs | 2 + .../environments/declarative_environment.rs | 22 +++-- .../environments/function_environment.rs | 30 ++++-- .../environments/global_environment.rs | 28 ++++-- .../environments/object_environment.rs | 18 +++- .../ecmascript/execution/execution_context.rs | 58 +++++++---- nova_vm/src/ecmascript/execution/realm.rs | 4 +- .../ecmascript/execution/realm/intrinsics.rs | 16 +++- .../ecmascript/scripts_and_modules/script.rs | 28 ++++-- .../scripts_and_modules/source_code.rs | 13 ++- .../ecmascript/types/language/bigint/data.rs | 8 +- .../ecmascript/types/language/number/data.rs | 8 +- .../ecmascript/types/language/object/data.rs | 25 +++-- .../ecmascript/types/language/string/data.rs | 14 ++- .../ecmascript/types/language/symbol/data.rs | 6 +- .../src/ecmascript/types/spec/reference.rs | 24 +++-- nova_vm/src/engine/bytecode/executable.rs | 26 ++++- nova_vm/src/engine/bytecode/iterator.rs | 24 +++-- nova_vm/src/engine/bytecode/vm.rs | 42 ++++++-- nova_vm/src/engine/local_value.rs | 6 +- nova_vm/src/heap/heap_gc.rs | 1 + 47 files changed, 760 insertions(+), 248 deletions(-) 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/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 d724d81b3..aa24bb60b 100644 --- a/nova_vm/src/ecmascript/builtins/builtin_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/builtin_constructor.rs @@ -450,20 +450,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); - self.compiled_initializer_bytecode.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); - self.compiled_initializer_bytecode.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 b746c2a06..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 @@ -178,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 a0358ec9b..b882b760a 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,9 @@ use crate::{ InternalMethods, InternalSlots, IntoObject, IntoValue, Object, OrdinaryObject, Value, }, }, - engine::{ExecutionResult, HeapAllocatedBytecode, SuspendedVm, Vm}, + engine::{ + local_value::ObjectScopeRoot, ExecutionResult, HeapAllocatedBytecode, SuspendedVm, Vm, + }, heap::{ indexes::{BaseIndex, GeneratorIndex}, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, WorkQueues, @@ -35,7 +37,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,6 +74,8 @@ 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. @@ -80,6 +84,8 @@ impl Generator { 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. @@ -92,6 +98,7 @@ impl Generator { // 11. Return ? result. match execution_result { ExecutionResult::Return(result_value) => { + println!("Generator: {:?}", self); // GeneratorStart step 4: // g. Set acGenerator.[[GeneratorState]] to completed. // h. NOTE: Once a generator enters the completed state it never leaves it and its @@ -264,6 +271,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 +374,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 +395,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 24ac08e89..32b9d4090 100644 --- a/nova_vm/src/ecmascript/builtins/ecmascript_function.rs +++ b/nova_vm/src/ecmascript/builtins/ecmascript_function.rs @@ -1038,37 +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); - self.compiled_bytecode.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); - self.compiled_bytecode.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/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 ada415710..251a7c922 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -234,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 } 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 c64588609..d5abf644b 100644 --- a/nova_vm/src/ecmascript/execution/realm.rs +++ b/nova_vm/src/ecmascript/execution/realm.rs @@ -175,7 +175,7 @@ impl Realm { impl HeapMarkAndSweep for Realm { fn mark_values(&self, queues: &mut WorkQueues) { - let Realm { + let Self { agent_signifier: _, intrinsics, global_object, @@ -190,7 +190,7 @@ impl HeapMarkAndSweep for Realm { } fn sweep_values(&mut self, compactions: &CompactionLists) { - let Realm { + let Self { agent_signifier: _, intrinsics, global_object, 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 7fd4d31e0..c52aa060f 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules/script.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules/script.rs @@ -169,15 +169,31 @@ 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); - self.compiled_bytecode.mark_values(queues); + let Self { + realm, + ecmascript_code: _, + compiled_bytecode, + loaded_modules: _, + host_defined: _, + source_code, + } = self; + realm.mark_values(queues); + source_code.mark_values(queues); + compiled_bytecode.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.realm.sweep_values(compactions); - self.source_code.sweep_values(compactions); - self.compiled_bytecode.sweep_values(compactions); + let Self { + realm, + ecmascript_code: _, + compiled_bytecode, + loaded_modules: _, + host_defined: _, + source_code, + } = self; + realm.sweep_values(compactions); + source_code.sweep_values(compactions); + compiled_bytecode.sweep_values(compactions); } } 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..79f254f5c 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules/source_code.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules/source_code.rs @@ -151,6 +151,7 @@ impl Debug for SourceCodeHeapData { impl Drop for SourceCodeHeapData { fn drop(&mut self) { + println!("Dropping SourceCode {:?}", self.source); // SAFETY: All references to this SourceCode should have been dropped // before we drop this. drop(unsafe { Box::from_raw(self.allocator.as_mut()) }); @@ -179,11 +180,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/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/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/executable.rs b/nova_vm/src/engine/bytecode/executable.rs index 8cc54f36a..ced3872cf 100644 --- a/nova_vm/src/engine/bytecode/executable.rs +++ b/nova_vm/src/engine/bytecode/executable.rs @@ -184,7 +184,7 @@ impl CompileContext<'_> { eprintln!(); } - println!("{:?}", data.params); + println!("{:?}", data.params as *const _ as *const ()); function_declaration_instantiation::instantiation( self, @@ -3079,10 +3079,30 @@ fn is_anonymous_function_definition(expression: &ast::Expression) -> bool { impl HeapMarkAndSweep for Executable { fn mark_values(&self, queues: &mut WorkQueues) { - self.constants.mark_values(queues); + 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); + } } fn sweep_values(&mut self, compactions: &CompactionLists) { - self.constants.sweep_values(compactions); + 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); + } } } 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 909feb582..101171c7d 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -2311,11 +2311,19 @@ 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) { - self.lexical_environment.sweep_values(compactions); + let Self { + ip: _, + lexical_environment, + } = self; + lexical_environment.sweep_values(compactions); } } @@ -2365,16 +2373,30 @@ impl HeapMarkAndSweep for Vm { 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/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index 546188af9..6313c9824 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -64,6 +64,7 @@ use crate::ecmascript::{ }; pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option]) { + println!("Performing GC"); let Agent { heap, execution_context_stack, From f5db867ef52ba1fac2ab0ea528c70cf3d5c13863 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 18 Oct 2024 13:59:52 +0300 Subject: [PATCH 03/13] fix: Remove unsafety by moving Executable to a heap vector --- .../builtins/builtin_constructor.rs | 8 +- .../generator_objects.rs | 6 +- .../src/ecmascript/builtins/global_object.rs | 6 +- .../ecmascript/scripts_and_modules/script.rs | 6 +- .../function_definitions.rs | 8 +- .../types/language/function/data.rs | 30 +- nova_vm/src/engine/bytecode.rs | 9 +- .../src/engine/bytecode/bytecode_compiler.rs | 2924 ++++++++++++++++ .../class_definition_evaluation.rs | 0 .../for_in_of_statement.rs | 0 .../function_declaration_instantiation.rs | 2 +- nova_vm/src/engine/bytecode/executable.rs | 3096 ++--------------- .../bytecode/heap_allocated_bytecode.rs | 141 - nova_vm/src/engine/bytecode/vm.rs | 208 +- nova_vm/src/heap.rs | 49 +- nova_vm/src/heap/heap_bits.rs | 26 + nova_vm/src/heap/heap_gc.rs | 88 +- 17 files changed, 3364 insertions(+), 3243 deletions(-) create mode 100644 nova_vm/src/engine/bytecode/bytecode_compiler.rs rename nova_vm/src/engine/bytecode/{executable => bytecode_compiler}/class_definition_evaluation.rs (100%) rename nova_vm/src/engine/bytecode/{executable => bytecode_compiler}/for_in_of_statement.rs (100%) rename nova_vm/src/engine/bytecode/{executable => bytecode_compiler}/function_declaration_instantiation.rs (99%) delete mode 100644 nova_vm/src/engine/bytecode/heap_allocated_bytecode.rs diff --git a/nova_vm/src/ecmascript/builtins/builtin_constructor.rs b/nova_vm/src/ecmascript/builtins/builtin_constructor.rs index aa24bb60b..9f72f2229 100644 --- a/nova_vm/src/ecmascript/builtins/builtin_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/builtin_constructor.rs @@ -26,7 +26,7 @@ use crate::{ BUILTIN_STRING_MEMORY, }, }, - engine::{Executable, HeapAllocatedBytecode}, + engine::Executable, heap::{ indexes::BuiltinConstructorIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, ObjectEntry, ObjectEntryPropertyDescriptor, WorkQueues, @@ -411,17 +411,13 @@ pub(crate) fn create_builtin_constructor( agent.heap.create_null_object(&entries) }; - let compiled_initializer_bytecode = args - .compiled_initializer_bytecode - .map(HeapAllocatedBytecode::new); - // 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, 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 b882b760a..0ef2a4271 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,9 +15,7 @@ use crate::{ InternalMethods, InternalSlots, IntoObject, IntoValue, Object, OrdinaryObject, Value, }, }, - engine::{ - local_value::ObjectScopeRoot, ExecutionResult, HeapAllocatedBytecode, SuspendedVm, Vm, - }, + engine::{local_value::ObjectScopeRoot, Executable, ExecutionResult, SuspendedVm, Vm}, heap::{ indexes::{BaseIndex, GeneratorIndex}, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, WorkQueues, @@ -365,7 +363,7 @@ pub(crate) enum GeneratorState { // SUSPENDED-START has `vm_or_args` set to Arguments, SUSPENDED-YIELD has it set to Vm. Suspended { vm_or_args: VmOrArguments, - executable: HeapAllocatedBytecode, + executable: Executable, execution_context: ExecutionContext, }, Executing, diff --git a/nova_vm/src/ecmascript/builtins/global_object.rs b/nova_vm/src/ecmascript/builtins/global_object.rs index 2b08ac239..994f25b32 100644 --- a/nova_vm/src/ecmascript/builtins/global_object.rs +++ b/nova_vm/src/ecmascript/builtins/global_object.rs @@ -28,7 +28,7 @@ use crate::{ }, types::{Function, IntoValue, String, Value, BUILTIN_STRING_MEMORY}, }, - engine::{Executable, HeapAllocatedBytecode, Vm}, + engine::{Executable, Vm}, heap::IntrinsicFunctionIndexes, }; @@ -335,13 +335,13 @@ pub fn perform_eval( // allocation for no good reason. // Second, this executable is not accessible by the heap and will thus // break if garbage collection triggers within the eval. - let exe = HeapAllocatedBytecode::new(Executable::compile_eval_body(agent, &script.body)); + let exe = Executable::compile_eval_body(agent, &script.body); // 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). let result = Vm::execute(agent, exe, None).into_js_result(); // SAFETY: No one can access the bytecode anymore. - unsafe { exe.drop() }; + unsafe { exe.try_drop(agent) }; result } else { Err(result.err().unwrap()) diff --git a/nova_vm/src/ecmascript/scripts_and_modules/script.rs b/nova_vm/src/ecmascript/scripts_and_modules/script.rs index c52aa060f..408df313f 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules/script.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules/script.rs @@ -19,7 +19,7 @@ use crate::{ }, types::{IntoValue, String, Value, BUILTIN_STRING_MEMORY}, }, - engine::{Executable, HeapAllocatedBytecode, Vm}, + engine::{Executable, Vm}, heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}, }; use ahash::AHashSet; @@ -141,7 +141,7 @@ pub struct Script { pub(crate) ecmascript_code: ManuallyDrop>, /// Stores the compiled bytecode of a Script. - pub(crate) compiled_bytecode: Option, + pub(crate) compiled_bytecode: Option, /// ### \[\[LoadedModules]] /// @@ -306,7 +306,7 @@ 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 bytecode = HeapAllocatedBytecode::new(Executable::compile_script(agent, script)); + let bytecode = Executable::compile_script(agent, script); agent[script].compiled_bytecode = Some(bytecode); // a. Set result to Completion(Evaluation of script). // b. If result.[[Type]] is normal and result.[[Value]] is empty, then 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 bab041e90..4855bf631 100644 --- a/nova_vm/src/ecmascript/syntax_directed_operations/function_definitions.rs +++ b/nova_vm/src/ecmascript/syntax_directed_operations/function_definitions.rs @@ -34,7 +34,7 @@ use crate::{ Value, BUILTIN_STRING_MEMORY, }, }, - engine::{Executable, ExecutionResult, FunctionExpression, HeapAllocatedBytecode, Vm}, + engine::{Executable, ExecutionResult, FunctionExpression, Vm}, heap::CreateHeapData, }; use oxc_ast::ast::{self}; @@ -271,7 +271,7 @@ pub(crate) fn evaluate_function_body( exe } else { let data = CompileFunctionBodyData::new(agent, function_object); - let exe = HeapAllocatedBytecode::new(Executable::compile_function_body(agent, data)); + let exe = Executable::compile_function_body(agent, data); agent[function_object].compiled_bytecode = Some(exe); exe }; @@ -298,7 +298,7 @@ pub(crate) fn evaluate_async_function_body( exe } else { let data = CompileFunctionBodyData::new(agent, function_object); - let exe = HeapAllocatedBytecode::new(Executable::compile_function_body(agent, data)); + let exe = Executable::compile_function_body(agent, data); agent[function_object].compiled_bytecode = Some(exe); exe }; @@ -375,7 +375,7 @@ pub(crate) fn evaluate_generator_body( // 4. Perform GeneratorStart(G, FunctionBody). // SAFETY: We're alive so SourceCode must be too. let data = CompileFunctionBodyData::new(agent, function_object); - let executable = HeapAllocatedBytecode::new(Executable::compile_function_body(agent, data)); + let executable = Executable::compile_function_body(agent, data); agent[generator].generator_state = Some(GeneratorState::Suspended { vm_or_args: VmOrArguments::Arguments(arguments_list.0.into()), executable, diff --git a/nova_vm/src/ecmascript/types/language/function/data.rs b/nova_vm/src/ecmascript/types/language/function/data.rs index 406a67bf5..7b482b0e2 100644 --- a/nova_vm/src/ecmascript/types/language/function/data.rs +++ b/nova_vm/src/ecmascript/types/language/function/data.rs @@ -11,7 +11,7 @@ use crate::{ scripts_and_modules::source_code::SourceCode, types::{OrdinaryObject, String, Value}, }, - engine::HeapAllocatedBytecode, + engine::Executable, heap::element_array::ElementsVector, }; @@ -66,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. @@ -85,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. - unsafe { exe.drop() }; - } - } -} - #[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. - unsafe { exe.drop() }; - } - } -} diff --git a/nova_vm/src/engine/bytecode.rs b/nova_vm/src/engine/bytecode.rs index a4bb8055b..64bf889c7 100644 --- a/nova_vm/src/engine/bytecode.rs +++ b/nova_vm/src/engine/bytecode.rs @@ -2,16 +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 heap_allocated_bytecode; 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 heap_allocated_bytecode::HeapAllocatedBytecode; 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..87737074c --- /dev/null +++ b/nova_vm/src/engine/bytecode/bytecode_compiler.rs @@ -0,0 +1,2924 @@ +// 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!(); + } + + println!("{:?}", data.params as *const _ as *const ()); + + 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 ced3872cf..1f296e078 100644 --- a/nova_vm/src/engine/bytecode/executable.rs +++ b/nova_vm/src/engine/bytecode/executable.rs @@ -2,457 +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!(); - } - - println!("{:?}", data.params as *const _ as *const ()); - - 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 @@ -502,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>, @@ -516,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]>, @@ -529,40 +104,8 @@ pub(crate) struct Executable { pub(crate) class_initializer_bytecodes: Box<[(Option, bool)]>, } -pub(super) fn get_instruction(instructions: &[u8], ip: &mut usize) -> Option { - if *ip >= instructions.len() { - return None; - } - - let kind: Instruction = unsafe { std::mem::transmute::(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 = 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; - } - } - - Some(Instr { kind, args }) -} - impl Executable { - #[inline] - pub(super) fn get_instruction(&self, ip: &mut usize) -> Option { - get_instruction(&self.instructions, ip) - } - - 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 ==="); @@ -582,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; @@ -596,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 ==="); @@ -608,2487 +151,231 @@ 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) + /// 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(); } - _ => 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, + pub(crate) fn get_index(self) -> usize { + (self.0.get() - 1) as usize } -} -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); + /// 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[..]) } } -} -impl CompileEvaluation for ast::BooleanLiteral { - fn compile(&self, ctx: &mut CompileContext) { - ctx.add_instruction_with_constant(Instruction::StoreConstant, self.value); + #[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) } -} -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), + #[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; } } + + Some(Instr { kind, args }) } -impl CompileEvaluation for ast::BinaryExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - // 1. Let lref be ? Evaluation of leftOperand. - self.left.compile(ctx); +impl ExecutableHeapData { + #[inline] + pub(super) fn get_instruction(&self, ip: &mut usize) -> Option { + get_instruction(&self.instructions, ip) + } - // 2. Let lval be ? GetValue(lref). - if is_reference(&self.left) { - 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!(); } - ctx.add_instruction(Instruction::Load); + // 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); - // 3. Let rref be ? Evaluation of rightOperand. - self.right.compile(ctx); + ctx.compile_statements(body); + ctx.do_implicit_return(); + ctx.finish() + } - // 4. Let rval be ? GetValue(rref). - if is_reference(&self.right) { - ctx.add_instruction(Instruction::GetValue); - } + pub(crate) fn compile_function_body( + agent: &mut Agent, + data: CompileFunctionBodyData<'_>, + ) -> Executable { + 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, - )); - } - } - } -} + let is_concise = data.is_concise_body; -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); + ctx.compile_function_body(data); - 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); - } + if is_concise { + ctx.do_implicit_return(); } - 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); + ctx.finish() + } - self.right.compile(ctx); - if is_reference(&self.right) { - ctx.add_instruction(Instruction::GetValue); + pub(crate) fn compile_eval_body(agent: &mut Agent, body: &[Statement]) -> Executable { + if agent.options.print_internals { + eprintln!(); + eprintln!("=== Compiling Eval Body ==="); + eprintln!(); } - let jump_to_end = ctx.add_instruction_with_jump_slot(Instruction::Jump); + let mut ctx = CompileContext::new(agent); - 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.compile_statements(body); + ctx.do_implicit_return(); + 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); +impl Index for Agent { + type Output = ExecutableHeapData; - 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); - } + fn index(&self, index: Executable) -> &Self::Output { + self.heap + .executables + .get(index.get_index()) + .expect("Executable out of bounds") } } -impl CompileEvaluation for ast::ParenthesizedExpression<'_> { - fn compile(&self, ctx: &mut CompileContext) { - self.expression.compile(ctx); +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") } } -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 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 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 HeapMarkAndSweep for Executable { + fn mark_values(&self, queues: &mut WorkQueues) { + queues.executables.push(*self); } -} -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 sweep_values(&mut self, compactions: &CompactionLists) { + compactions + .executables + .shift_non_zero_u32_index(&mut self.0); } } -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, - } -} - -impl HeapMarkAndSweep for Executable { - 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 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); } } @@ -3106,3 +393,8 @@ impl HeapMarkAndSweep for Executable { } } } + +#[cold] +fn handle_identifier_failure() -> ! { + panic!("Invalid identifier index: Value was not a String") +} diff --git a/nova_vm/src/engine/bytecode/heap_allocated_bytecode.rs b/nova_vm/src/engine/bytecode/heap_allocated_bytecode.rs deleted file mode 100644 index 68c09c8d7..000000000 --- a/nova_vm/src/engine/bytecode/heap_allocated_bytecode.rs +++ /dev/null @@ -1,141 +0,0 @@ -// 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/. - -use std::ptr::NonNull; - -use crate::{ - ecmascript::types::{String, Value}, - heap::HeapMarkAndSweep, -}; - -use super::{ - executable::{get_instruction, ArrowFunctionExpression}, - instructions::Instr, - Executable, FunctionExpression, -}; - -/// Abstracts over a heap-allocated bytecode structure that can be safely -/// run interleaved with garbage collection -#[derive(Debug, Clone, Copy)] -#[repr(transparent)] -pub(crate) struct HeapAllocatedBytecode { - pointer: NonNull, -} - -impl HeapAllocatedBytecode { - pub(crate) fn new(bytecode: Executable) -> Self { - Self { - // SAFETY: Box::leak(Box::new()) never returns a null pointer for - // non-ZST. - pointer: unsafe { NonNull::new_unchecked(Box::leak(Box::new(bytecode))) }, - } - } - - pub(crate) fn clone(&self) -> Self { - Self::new(unsafe { self.pointer.as_ref() }.clone()) - } - - /// SAFETY: The returned reference is valid until the Script, Module, or - /// Function it was compiled from is garbage collected. - #[inline] - pub(super) fn get_instructions(self) -> &'static [u8] { - // SAFETY: As long as we're alive the instructions Box lives, and it is - // never accessed mutably. - unsafe { &(*self.pointer.as_ptr()).instructions } - } - - /// SAFETY: The returned reference is valid until the next garbage - /// collection happens. - #[inline] - pub(super) fn get_constants(self) -> &'static [Value] { - // SAFETY: As long as we're alive the instructions Box lives, and it is - // never accessed mutably. - unsafe { &(*self.pointer.as_ptr()).constants } - } - - #[inline] - pub(super) fn get_instruction(self, ip: &mut usize) -> Option { - // SAFETY: As long as we're alive the instructions Box lives, and it is - // never accessed mutably. - get_instruction(unsafe { &(*self.pointer.as_ptr()).instructions }, ip) - } - - #[inline] - pub(super) fn fetch_identifier(self, 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 = unsafe { (*self.pointer.as_ptr()).constants[index] }; - let Ok(value) = String::try_from(value) else { - handle_identifier_failure() - }; - value - } - - #[inline] - pub(super) fn fetch_constant(self, 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. - unsafe { (*self.pointer.as_ptr()).constants[index] } - } - - /// SAFETY: The returned reference is valid the next garbage collection - /// happens. - pub(super) fn fetch_function_expression(self, index: usize) -> &'static FunctionExpression { - unsafe { &(*self.pointer.as_ptr()).function_expressions[index] } - } - - /// SAFETY: The returned reference is valid the next garbage collection - /// happens. - pub(super) fn fetch_arrow_function_expression( - self, - index: usize, - ) -> &'static ArrowFunctionExpression { - unsafe { &(*self.pointer.as_ptr()).arrow_function_expressions[index] } - } - - /// SAFETY: The returned reference is valid the next garbage collection - /// happens. - pub(super) fn fetch_class_initializer_bytecode( - self, - index: usize, - ) -> &'static (Option, bool) { - unsafe { &(*self.pointer.as_ptr()).class_initializer_bytecodes[index] } - } - - /// SAFETY: Normal drop safety rules apply. - pub(crate) unsafe fn drop(self) { - drop(unsafe { Box::from_raw(self.pointer.as_ptr()) }); - } -} - -#[cold] -fn handle_identifier_failure() -> ! { - panic!("Invalid identifier index: Value was not a String") -} - -impl HeapMarkAndSweep for HeapAllocatedBytecode { - fn mark_values(&self, queues: &mut crate::heap::WorkQueues) { - // SAFETY: This is a valid, non-null pointer to an owned Executable - // that cannot have any live mutable references to it. - unsafe { self.pointer.as_ref() }.mark_values(queues); - } - - fn sweep_values(&mut self, compactions: &crate::heap::CompactionLists) { - // 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 for running the bytecode. 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 { self.pointer.as_mut() }.sweep_values(compactions); - } -} - -unsafe impl Send for HeapAllocatedBytecode {} diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index 101171c7d..674e446d3 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -53,10 +53,11 @@ use crate::{ }; use super::{ - executable::{NamedEvaluationParameter, SendableRef}, + executable::{ArrowFunctionExpression, SendableRef}, instructions::Instr, iterator::{ObjectPropertiesIterator, VmIterator}, - Executable, HeapAllocatedBytecode, IndexType, Instruction, InstructionIter, + Executable, ExecutableHeapData, FunctionExpression, IndexType, Instruction, InstructionIter, + NamedEvaluationParameter, }; struct EmptyParametersList(ast::FormalParameters<'static>); @@ -146,7 +147,7 @@ impl SuspendedVm { pub(crate) fn resume( self, agent: &mut Agent, - executable: HeapAllocatedBytecode, + executable: Executable, value: Value, ) -> ExecutionResult { let vm = Vm::from_suspended(self); @@ -156,7 +157,7 @@ impl SuspendedVm { pub(crate) fn resume_throw( self, agent: &mut Agent, - executable: HeapAllocatedBytecode, + executable: Executable, err: Value, ) -> ExecutionResult { // Optimisation: Avoid unsuspending the Vm if we're just going to throw @@ -207,19 +208,19 @@ impl Vm { } } - fn fetch_identifier(&self, exe: &Executable, index: usize) -> String { + fn fetch_identifier(&self, exe: &ExecutableHeapData, 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 { + fn fetch_constant(&self, exe: &ExecutableHeapData, index: usize) -> Value { exe.constants[index] } /// Executes an executable using the virtual machine. pub(crate) fn execute( agent: &mut Agent, - executable: HeapAllocatedBytecode, + executable: Executable, arguments: Option<&[Value]>, ) -> ExecutionResult { let mut vm = Vm::new(); @@ -235,11 +236,11 @@ impl Vm { if agent.options.print_internals { eprintln!(); eprintln!("=== Executing Executable ==="); - eprintln!("Constants: {:?}", executable.get_constants()); + eprintln!("Constants: {:?}", executable.get_constants(agent)); eprintln!(); eprintln!("Instructions:"); - let iter = InstructionIter::new(executable.get_instructions()); + let iter = InstructionIter::new(executable.get_instructions(agent)); for (ip, instr) in iter { match instr.kind.argument_count() { 0 => { @@ -266,7 +267,7 @@ impl Vm { pub fn resume( mut self, agent: &mut Agent, - executable: HeapAllocatedBytecode, + executable: Executable, value: Value, ) -> ExecutionResult { self.result = Some(value); @@ -276,7 +277,7 @@ impl Vm { pub fn resume_throw( mut self, agent: &mut Agent, - executable: HeapAllocatedBytecode, + executable: Executable, err: Value, ) -> ExecutionResult { let err = JsError::new(err); @@ -286,14 +287,10 @@ impl Vm { self.inner_execute(agent, executable) } - fn inner_execute( - mut self, - agent: &mut Agent, - executable: HeapAllocatedBytecode, - ) -> ExecutionResult { + fn inner_execute(mut self, agent: &mut Agent, executable: Executable) -> ExecutionResult { let do_gc = !agent.options.disable_gc; let mut instr_count = 0u8; - while let Some(instr) = executable.get_instruction(&mut self.ip) { + while let Some(instr) = executable.get_instruction(agent, &mut self.ip) { if do_gc { instr_count = instr_count.wrapping_add(1); if instr_count == 0 { @@ -365,7 +362,7 @@ impl Vm { fn execute_instruction( agent: &mut Agent, vm: &mut Vm, - executable: HeapAllocatedBytecode, + executable: Executable, instr: &Instr, ) -> JsResult { if agent.options.print_internals { @@ -426,7 +423,8 @@ impl Vm { } } Instruction::ResolveBinding => { - let identifier = executable.fetch_identifier(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)?; @@ -444,7 +442,7 @@ impl Vm { }); } Instruction::LoadConstant => { - let constant = executable.fetch_constant(instr.args[0].unwrap() as usize); + let constant = executable.fetch_constant(agent, instr.args[0].unwrap() as usize); vm.stack.push(constant); } Instruction::Load => { @@ -471,7 +469,7 @@ impl Vm { vm.result = Some(*vm.stack.last().expect("Trying to get from empty stack")); } Instruction::StoreConstant => { - let constant = executable.fetch_constant(instr.args[0].unwrap() as usize); + let constant = executable.fetch_constant(agent, instr.args[0].unwrap() as usize); vm.result = Some(constant); } Instruction::UnaryMinus => { @@ -517,8 +515,9 @@ impl Vm { create_data_property_or_throw(agent, object, key, value).unwrap() } Instruction::ObjectDefineMethod => { - let function_expression = - executable.fetch_function_expression(instr.args[0].unwrap() as usize); + 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())?; @@ -545,12 +544,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, @@ -602,8 +601,9 @@ impl Vm { // c. Return unused. } Instruction::ObjectDefineGetter => { - let function_expression = - executable.fetch_function_expression(instr.args[0].unwrap() as usize); + 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())?; @@ -637,11 +637,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, @@ -679,8 +679,9 @@ impl Vm { // c. Return unused. } Instruction::ObjectDefineSetter => { - let function_expression = - executable.fetch_function_expression(instr.args[0].unwrap() as usize); + 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())?; @@ -699,12 +700,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, @@ -829,8 +830,13 @@ impl Vm { } Instruction::InstantiateArrowFunctionExpression => { // ArrowFunction : ArrowParameters => ConciseBody - let function_expression = - executable.fetch_arrow_function_expression(instr.args[0].unwrap() as usize); + 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. @@ -850,18 +856,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())? @@ -883,8 +889,14 @@ impl Vm { vm.result = Some(function.into_value()); } Instruction::InstantiateOrdinaryFunctionExpression => { - let function_expression = - executable.fetch_function_expression(instr.args[0].unwrap() as usize); + 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, @@ -895,10 +907,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())? @@ -914,7 +924,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); @@ -925,29 +935,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(HeapAllocatedBytecode::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 @@ -992,8 +999,13 @@ impl Vm { vm.result = Some(function.into_value()); } Instruction::ClassDefineConstructor => { - let function_expression = - executable.fetch_function_expression(instr.args[0].unwrap() as usize); + 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; @@ -1022,20 +1034,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(HeapAllocatedBytecode::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)); @@ -1066,9 +1077,10 @@ impl Vm { Instruction::ClassDefineDefaultConstructor => { let class_initializer_bytecode_index = instr.args[0].unwrap(); let (compiled_initializer_bytecode, has_constructor_parent) = executable - .fetch_class_initializer_bytecode(class_initializer_bytecode_index as usize); - let has_constructor_parent = *has_constructor_parent; - let compiled_initializer_bytecode = compiled_initializer_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 { @@ -1301,7 +1313,7 @@ impl Vm { } Instruction::EvaluatePropertyAccessWithIdentifierKey => { let property_name_string = - executable.fetch_identifier(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() @@ -1572,7 +1584,7 @@ impl Vm { .as_ref() .unwrap() .lexical_environment; - let name = executable.fetch_identifier(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 => { @@ -1582,7 +1594,7 @@ impl Vm { .as_ref() .unwrap() .lexical_environment; - let name = executable.fetch_identifier(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 => { @@ -1592,7 +1604,7 @@ impl Vm { .as_ref() .unwrap() .lexical_environment; - let name = executable.fetch_identifier(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()) @@ -1872,14 +1884,14 @@ impl Vm { fn execute_simple_array_binding( agent: &mut Agent, vm: &mut Vm, - executable: HeapAllocatedBytecode, + 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 { @@ -1923,7 +1935,8 @@ impl Vm { match instr.kind { Instruction::BindingPatternBind | Instruction::BindingPatternBindRest => { - let binding_id = executable.fetch_identifier(instr.args[0].unwrap() as usize); + let binding_id = + 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)?; @@ -1958,21 +1971,23 @@ impl Vm { fn execute_simple_object_binding( agent: &mut Agent, vm: &mut Vm, - executable: HeapAllocatedBytecode, + 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 = executable.fetch_identifier(instr.args[0].unwrap() as usize); + let binding_id = + 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 = executable.fetch_constant(instr.args[1].unwrap() as usize); + let key_value = + executable.fetch_constant(agent, instr.args[1].unwrap() as usize); PropertyKey::try_from(key_value).unwrap() }; excluded_names.insert(property_key); @@ -1988,7 +2003,7 @@ impl Vm { Instruction::BindingPatternGetValueNamed => { let property_key = PropertyKey::from_value( agent, - executable.fetch_constant(instr.args[0].unwrap() as usize), + executable.fetch_constant(agent, instr.args[0].unwrap() as usize), ) .unwrap(); excluded_names.insert(property_key); @@ -1997,7 +2012,8 @@ impl Vm { } Instruction::BindingPatternBindRest => { // 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment). - let binding_id = executable.fetch_identifier(instr.args[0].unwrap() as usize); + let binding_id = + 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). @@ -2023,11 +2039,11 @@ impl Vm { fn execute_nested_simple_binding( agent: &mut Agent, vm: &mut Vm, - executable: HeapAllocatedBytecode, + 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)?; diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 46eee04c1..031201d32 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -42,18 +42,6 @@ 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::{ @@ -64,14 +52,6 @@ use crate::ecmascript::{ promise_resolving_functions::PromiseResolvingFunctionHeapData, }, }, - 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, - }, map::data::MapHeapData, module::data::ModuleHeapData, primitive_objects::PrimitiveObjectHeapData, @@ -86,6 +66,32 @@ use crate::ecmascript::{ BUILTIN_STRINGS_LIST, }, }; +use crate::{ + ecmascript::{ + 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, + }, + execution::{Environments, Realm, RealmIdentifier}, + scripts_and_modules::{ + module::ModuleIdentifier, + script::{Script, ScriptIdentifier}, + }, + types::{ + BigIntHeapData, BoundFunctionHeapData, BuiltinFunctionHeapData, + ECMAScriptFunctionHeapData, NumberHeapData, Object, ObjectHeapData, String, + StringHeapData, SymbolHeapData, Value, + }, + }, + engine::ExecutableHeapData, +}; pub(crate) use heap_bits::{CompactionLists, HeapMarkAndSweep, WorkQueues}; #[derive(Debug)] @@ -111,6 +117,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 +214,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 6313c9824..f8af1ec6d 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -25,42 +25,46 @@ 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::{ - 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, }, + engine::Executable, }; pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option]) { @@ -145,6 +149,7 @@ pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option]) { embedder_objects, environments, errors, + executables, source_codes, finalization_registrys, generators, @@ -403,6 +408,19 @@ pub fn heap_gc(agent: &mut Agent, 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| { @@ -992,6 +1010,7 @@ fn sweep(agent: &mut Agent, bits: &HeapBits, root_realms: &mut [Option Date: Fri, 18 Oct 2024 15:11:11 +0300 Subject: [PATCH 04/13] fix: Make interleaved GC a non-default feature --- nova_vm/Cargo.toml | 11 +-- nova_vm/src/engine/bytecode/vm.rs | 113 +++++++++++++++++++----------- 2 files changed, 77 insertions(+), 47 deletions(-) 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/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index 674e446d3..550ca7f38 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::{ @@ -38,7 +40,7 @@ use crate::{ agent::{resolve_binding, ExceptionType, JsError}, get_this_environment, new_class_static_element_environment, new_declarative_environment, Agent, ECMAScriptCodeEvaluationState, EnvironmentIndex, - JsResult, ProtoIntrinsics, RealmIdentifier, + JsResult, ProtoIntrinsics, }, types::{ get_this_value, get_value, initialize_referenced_binding, is_private_reference, @@ -47,9 +49,7 @@ use crate::{ Reference, String, Value, BUILTIN_STRING_MEMORY, }, }, - heap::{ - heap_gc::heap_gc, CompactionLists, HeapMarkAndSweep, WellKnownSymbolIndexes, WorkQueues, - }, + heap::{CompactionLists, HeapMarkAndSweep, WellKnownSymbolIndexes, WorkQueues}, }; use super::{ @@ -288,9 +288,12 @@ impl Vm { } 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 { @@ -1171,22 +1174,32 @@ impl Vm { vm.result = Some(perform_eval(agent, eval_arg, true, strict_caller)?); } } else { - 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?); + 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, + func, + Value::Undefined, + Some(ArgumentsList(&args)), + )?); + } } } Instruction::EvaluateCall => { @@ -1215,13 +1228,17 @@ impl Vm { Value::Undefined }; let func = vm.stack.pop().unwrap(); - 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?); + 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); @@ -1233,14 +1250,22 @@ impl Vm { ); return Err(agent.throw_exception(ExceptionType::TypeError, error_message)); }; - 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?); + + 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 { @@ -1640,13 +1665,17 @@ impl Vm { Instruction::InstanceofOperator => { let lval = vm.stack.pop().unwrap(); let rval = vm.result.take().unwrap(); - 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()); + 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; From c015a4d3f80224f280de456b390aae68a1830a3b Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 18 Oct 2024 15:11:30 +0300 Subject: [PATCH 05/13] chore(test262): Update epxectations --- tests/expectations.json | 570 +++++++++++++++++++++++++++++++++------- tests/metrics.json | 6 +- 2 files changed, 475 insertions(+), 101 deletions(-) diff --git a/tests/expectations.json b/tests/expectations.json index c46d3412d..dd738e3dd 100644 --- a/tests/expectations.json +++ b/tests/expectations.json @@ -6,99 +6,99 @@ "built-ins/Array/from/mapfn-throws-exception.js": "CRASH", "built-ins/Array/from/proto-from-ctor-realm.js": "FAIL", "built-ins/Array/fromAsync/async-iterable-async-mapped-awaits-once.js": "CRASH", - "built-ins/Array/fromAsync/async-iterable-input-does-not-await-input.js": "FAIL", + "built-ins/Array/fromAsync/async-iterable-input-does-not-await-input.js": "UNRESOLVED", "built-ins/Array/fromAsync/async-iterable-input-iteration-err.js": "CRASH", "built-ins/Array/fromAsync/async-iterable-input.js": "CRASH", - "built-ins/Array/fromAsync/asyncitems-array-add-to-empty.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-array-add-to-singleton.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-array-add.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-array-mutate.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-array-remove.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-arraybuffer.js": "CRASH", - "built-ins/Array/fromAsync/asyncitems-arraylike-holes.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-arraylike-length-accessor-throws.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-array-add-to-empty.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-array-add-to-singleton.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-array-add.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-array-mutate.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-array-remove.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-arraybuffer.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-arraylike-holes.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-arraylike-length-accessor-throws.js": "UNRESOLVED", "built-ins/Array/fromAsync/asyncitems-arraylike-promise.js": "CRASH", - "built-ins/Array/fromAsync/asyncitems-arraylike-too-long.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-asynciterator-exists.js": "CRASH", - "built-ins/Array/fromAsync/asyncitems-asynciterator-not-callable.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-asynciterator-null.js": "CRASH", - "built-ins/Array/fromAsync/asyncitems-asynciterator-sync.js": "CRASH", - "built-ins/Array/fromAsync/asyncitems-asynciterator-throws.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-bigint.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-boolean.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-function.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-iterator-exists.js": "CRASH", - "built-ins/Array/fromAsync/asyncitems-iterator-not-callable.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-iterator-null.js": "CRASH", - "built-ins/Array/fromAsync/asyncitems-iterator-promise.js": "CRASH", - "built-ins/Array/fromAsync/asyncitems-iterator-throws.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-null-undefined.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-number.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-object-not-arraylike.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-operations.js": "CRASH", - "built-ins/Array/fromAsync/asyncitems-string.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-symbol.js": "FAIL", - "built-ins/Array/fromAsync/asyncitems-uses-intrinsic-iterator-symbols.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-arraylike-too-long.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-asynciterator-exists.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-asynciterator-not-callable.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-asynciterator-null.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-asynciterator-sync.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-asynciterator-throws.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-bigint.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-boolean.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-function.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-iterator-exists.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-iterator-not-callable.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-iterator-null.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-iterator-promise.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-iterator-throws.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-null-undefined.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-number.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-object-not-arraylike.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-operations.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-string.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-symbol.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-uses-intrinsic-iterator-symbols.js": "UNRESOLVED", "built-ins/Array/fromAsync/builtin.js": "FAIL", "built-ins/Array/fromAsync/length.js": "FAIL", - "built-ins/Array/fromAsync/mapfn-async-arraylike.js": "FAIL", + "built-ins/Array/fromAsync/mapfn-async-arraylike.js": "UNRESOLVED", "built-ins/Array/fromAsync/mapfn-async-iterable-async.js": "CRASH", - "built-ins/Array/fromAsync/mapfn-async-iterable-sync.js": "CRASH", - "built-ins/Array/fromAsync/mapfn-async-throws-close-async-iterator.js": "FAIL", - "built-ins/Array/fromAsync/mapfn-async-throws-close-sync-iterator.js": "FAIL", - "built-ins/Array/fromAsync/mapfn-async-throws.js": "FAIL", - "built-ins/Array/fromAsync/mapfn-not-callable.js": "FAIL", - "built-ins/Array/fromAsync/mapfn-result-awaited-once-per-iteration.js": "FAIL", - "built-ins/Array/fromAsync/mapfn-sync-arraylike.js": "FAIL", + "built-ins/Array/fromAsync/mapfn-async-iterable-sync.js": "UNRESOLVED", + "built-ins/Array/fromAsync/mapfn-async-throws-close-async-iterator.js": "UNRESOLVED", + "built-ins/Array/fromAsync/mapfn-async-throws-close-sync-iterator.js": "UNRESOLVED", + "built-ins/Array/fromAsync/mapfn-async-throws.js": "UNRESOLVED", + "built-ins/Array/fromAsync/mapfn-not-callable.js": "UNRESOLVED", + "built-ins/Array/fromAsync/mapfn-result-awaited-once-per-iteration.js": "UNRESOLVED", + "built-ins/Array/fromAsync/mapfn-sync-arraylike.js": "UNRESOLVED", "built-ins/Array/fromAsync/mapfn-sync-iterable-async.js": "CRASH", - "built-ins/Array/fromAsync/mapfn-sync-iterable-sync.js": "CRASH", - "built-ins/Array/fromAsync/mapfn-sync-throws-close-async-iterator.js": "FAIL", - "built-ins/Array/fromAsync/mapfn-sync-throws-close-sync-iterator.js": "FAIL", - "built-ins/Array/fromAsync/mapfn-sync-throws.js": "FAIL", + "built-ins/Array/fromAsync/mapfn-sync-iterable-sync.js": "UNRESOLVED", + "built-ins/Array/fromAsync/mapfn-sync-throws-close-async-iterator.js": "UNRESOLVED", + "built-ins/Array/fromAsync/mapfn-sync-throws-close-sync-iterator.js": "UNRESOLVED", + "built-ins/Array/fromAsync/mapfn-sync-throws.js": "UNRESOLVED", "built-ins/Array/fromAsync/name.js": "FAIL", "built-ins/Array/fromAsync/non-iterable-input-does-not-use-array-prototype.js": "CRASH", - "built-ins/Array/fromAsync/non-iterable-input-element-access-err.js": "FAIL", - "built-ins/Array/fromAsync/non-iterable-input-with-thenable-async-mapped-awaits-callback-result-once.js": "FAIL", - "built-ins/Array/fromAsync/non-iterable-input-with-thenable-async-mapped-callback-err.js": "FAIL", - "built-ins/Array/fromAsync/non-iterable-input-with-thenable-element-rejects.js": "FAIL", - "built-ins/Array/fromAsync/non-iterable-input-with-thenable-sync-mapped-callback-err.js": "FAIL", - "built-ins/Array/fromAsync/non-iterable-input-with-thenable.js": "FAIL", - "built-ins/Array/fromAsync/non-iterable-input.js": "FAIL", - "built-ins/Array/fromAsync/non-iterable-sync-mapped-callback-err.js": "FAIL", - "built-ins/Array/fromAsync/non-iterable-with-non-promise-thenable.js": "FAIL", - "built-ins/Array/fromAsync/non-iterable-with-thenable-async-mapped-awaits-once.js": "FAIL", - "built-ins/Array/fromAsync/non-iterable-with-thenable-awaits-once.js": "FAIL", - "built-ins/Array/fromAsync/non-iterable-with-thenable-sync-mapped-awaits-once.js": "FAIL", - "built-ins/Array/fromAsync/non-iterable-with-thenable-then-method-err.js": "FAIL", + "built-ins/Array/fromAsync/non-iterable-input-element-access-err.js": "UNRESOLVED", + "built-ins/Array/fromAsync/non-iterable-input-with-thenable-async-mapped-awaits-callback-result-once.js": "UNRESOLVED", + "built-ins/Array/fromAsync/non-iterable-input-with-thenable-async-mapped-callback-err.js": "UNRESOLVED", + "built-ins/Array/fromAsync/non-iterable-input-with-thenable-element-rejects.js": "UNRESOLVED", + "built-ins/Array/fromAsync/non-iterable-input-with-thenable-sync-mapped-callback-err.js": "UNRESOLVED", + "built-ins/Array/fromAsync/non-iterable-input-with-thenable.js": "UNRESOLVED", + "built-ins/Array/fromAsync/non-iterable-input.js": "UNRESOLVED", + "built-ins/Array/fromAsync/non-iterable-sync-mapped-callback-err.js": "UNRESOLVED", + "built-ins/Array/fromAsync/non-iterable-with-non-promise-thenable.js": "UNRESOLVED", + "built-ins/Array/fromAsync/non-iterable-with-thenable-async-mapped-awaits-once.js": "UNRESOLVED", + "built-ins/Array/fromAsync/non-iterable-with-thenable-awaits-once.js": "UNRESOLVED", + "built-ins/Array/fromAsync/non-iterable-with-thenable-sync-mapped-awaits-once.js": "UNRESOLVED", + "built-ins/Array/fromAsync/non-iterable-with-thenable-then-method-err.js": "UNRESOLVED", "built-ins/Array/fromAsync/not-a-constructor.js": "FAIL", "built-ins/Array/fromAsync/prop-desc.js": "FAIL", - "built-ins/Array/fromAsync/returned-promise-resolves-to-array.js": "FAIL", - "built-ins/Array/fromAsync/returns-promise.js": "FAIL", - "built-ins/Array/fromAsync/sync-iterable-input-with-non-promise-thenable.js": "CRASH", - "built-ins/Array/fromAsync/sync-iterable-input-with-thenable.js": "CRASH", - "built-ins/Array/fromAsync/sync-iterable-input.js": "CRASH", - "built-ins/Array/fromAsync/sync-iterable-iteration-err.js": "CRASH", - "built-ins/Array/fromAsync/sync-iterable-with-thenable-async-mapped-awaits-once.js": "CRASH", - "built-ins/Array/fromAsync/sync-iterable-with-thenable-async-mapped-callback-err.js": "CRASH", - "built-ins/Array/fromAsync/sync-iterable-with-thenable-awaits-once.js": "CRASH", - "built-ins/Array/fromAsync/sync-iterable-with-thenable-element-rejects.js": "CRASH", - "built-ins/Array/fromAsync/sync-iterable-with-thenable-sync-mapped-awaits-once.js": "CRASH", - "built-ins/Array/fromAsync/sync-iterable-with-thenable-sync-mapped-callback-err.js": "CRASH", - "built-ins/Array/fromAsync/sync-iterable-with-thenable-then-method-err.js": "CRASH", - "built-ins/Array/fromAsync/this-constructor-operations.js": "FAIL", - "built-ins/Array/fromAsync/this-constructor-with-bad-length-setter.js": "CRASH", - "built-ins/Array/fromAsync/this-constructor-with-readonly-elements.js": "FAIL", - "built-ins/Array/fromAsync/this-constructor-with-readonly-length.js": "FAIL", - "built-ins/Array/fromAsync/this-constructor-with-unsettable-element-closes-async-iterator.js": "FAIL", - "built-ins/Array/fromAsync/this-constructor-with-unsettable-element-closes-sync-iterator.js": "FAIL", - "built-ins/Array/fromAsync/this-constructor-with-unsettable-element.js": "FAIL", - "built-ins/Array/fromAsync/this-constructor.js": "FAIL", - "built-ins/Array/fromAsync/this-non-constructor.js": "FAIL", - "built-ins/Array/fromAsync/thisarg-object.js": "FAIL", - "built-ins/Array/fromAsync/thisarg-omitted-sloppy.js": "FAIL", - "built-ins/Array/fromAsync/thisarg-omitted-strict.js": "FAIL", - "built-ins/Array/fromAsync/thisarg-primitive-sloppy.js": "FAIL", - "built-ins/Array/fromAsync/thisarg-primitive-strict.js": "FAIL", + "built-ins/Array/fromAsync/returned-promise-resolves-to-array.js": "UNRESOLVED", + "built-ins/Array/fromAsync/returns-promise.js": "UNRESOLVED", + "built-ins/Array/fromAsync/sync-iterable-input-with-non-promise-thenable.js": "UNRESOLVED", + "built-ins/Array/fromAsync/sync-iterable-input-with-thenable.js": "UNRESOLVED", + "built-ins/Array/fromAsync/sync-iterable-input.js": "UNRESOLVED", + "built-ins/Array/fromAsync/sync-iterable-iteration-err.js": "UNRESOLVED", + "built-ins/Array/fromAsync/sync-iterable-with-thenable-async-mapped-awaits-once.js": "UNRESOLVED", + "built-ins/Array/fromAsync/sync-iterable-with-thenable-async-mapped-callback-err.js": "UNRESOLVED", + "built-ins/Array/fromAsync/sync-iterable-with-thenable-awaits-once.js": "UNRESOLVED", + "built-ins/Array/fromAsync/sync-iterable-with-thenable-element-rejects.js": "UNRESOLVED", + "built-ins/Array/fromAsync/sync-iterable-with-thenable-sync-mapped-awaits-once.js": "UNRESOLVED", + "built-ins/Array/fromAsync/sync-iterable-with-thenable-sync-mapped-callback-err.js": "UNRESOLVED", + "built-ins/Array/fromAsync/sync-iterable-with-thenable-then-method-err.js": "UNRESOLVED", + "built-ins/Array/fromAsync/this-constructor-operations.js": "UNRESOLVED", + "built-ins/Array/fromAsync/this-constructor-with-bad-length-setter.js": "UNRESOLVED", + "built-ins/Array/fromAsync/this-constructor-with-readonly-elements.js": "UNRESOLVED", + "built-ins/Array/fromAsync/this-constructor-with-readonly-length.js": "UNRESOLVED", + "built-ins/Array/fromAsync/this-constructor-with-unsettable-element-closes-async-iterator.js": "UNRESOLVED", + "built-ins/Array/fromAsync/this-constructor-with-unsettable-element-closes-sync-iterator.js": "UNRESOLVED", + "built-ins/Array/fromAsync/this-constructor-with-unsettable-element.js": "UNRESOLVED", + "built-ins/Array/fromAsync/this-constructor.js": "UNRESOLVED", + "built-ins/Array/fromAsync/this-non-constructor.js": "UNRESOLVED", + "built-ins/Array/fromAsync/thisarg-object.js": "UNRESOLVED", + "built-ins/Array/fromAsync/thisarg-omitted-sloppy.js": "UNRESOLVED", + "built-ins/Array/fromAsync/thisarg-omitted-strict.js": "UNRESOLVED", + "built-ins/Array/fromAsync/thisarg-primitive-sloppy.js": "UNRESOLVED", + "built-ins/Array/fromAsync/thisarg-primitive-strict.js": "UNRESOLVED", "built-ins/Array/isArray/15.4.3.2-1-10.js": "CRASH", "built-ins/Array/isArray/15.4.3.2-1-9.js": "CRASH", "built-ins/Array/isArray/proxy-revoked.js": "CRASH", @@ -700,7 +700,7 @@ "built-ins/AsyncDisposableStack/prototype/disposeAsync/name.js": "CRASH", "built-ins/AsyncDisposableStack/prototype/disposeAsync/not-a-constructor.js": "FAIL", "built-ins/AsyncDisposableStack/prototype/disposeAsync/prop-desc.js": "CRASH", - "built-ins/AsyncDisposableStack/prototype/disposeAsync/this-does-not-have-internal-asyncdisposablestate-rejects.js": "FAIL", + "built-ins/AsyncDisposableStack/prototype/disposeAsync/this-does-not-have-internal-asyncdisposablestate-rejects.js": "UNRESOLVED", "built-ins/AsyncDisposableStack/prototype/disposeAsync/this-not-object-rejects.js": "FAIL", "built-ins/AsyncDisposableStack/prototype/disposed/does-not-have-asyncdisposablestate-internal-slot.js": "FAIL", "built-ins/AsyncDisposableStack/prototype/disposed/getter.js": "CRASH", @@ -3538,7 +3538,10 @@ "built-ins/Promise/any/resolved-sequence.js": "CRASH", "built-ins/Promise/any/returns-promise.js": "CRASH", "built-ins/Promise/any/species-get-error.js": "CRASH", - "built-ins/Promise/exception-after-resolve-in-thenable-job.js": "FAIL", + "built-ins/Promise/create-resolving-functions-reject.js": "UNRESOLVED", + "built-ins/Promise/create-resolving-functions-resolve.js": "UNRESOLVED", + "built-ins/Promise/exception-after-resolve-in-executor.js": "UNRESOLVED", + "built-ins/Promise/exception-after-resolve-in-thenable-job.js": "UNRESOLVED", "built-ins/Promise/executor-function-extensible.js": "CRASH", "built-ins/Promise/executor-function-length.js": "CRASH", "built-ins/Promise/executor-function-name.js": "CRASH", @@ -3548,6 +3551,8 @@ "built-ins/Promise/get-prototype-abrupt-executor-not-callable.js": "CRASH", "built-ins/Promise/get-prototype-abrupt.js": "CRASH", "built-ins/Promise/proto-from-ctor-realm.js": "FAIL", + "built-ins/Promise/prototype/catch/S25.4.5.1_A3.1_T1.js": "UNRESOLVED", + "built-ins/Promise/prototype/catch/S25.4.5.1_A3.1_T2.js": "UNRESOLVED", "built-ins/Promise/prototype/finally/invokes-then-with-function.js": "CRASH", "built-ins/Promise/prototype/finally/invokes-then-with-non-function.js": "CRASH", "built-ins/Promise/prototype/finally/rejected-observable-then-calls-PromiseResolve.js": "CRASH", @@ -3570,14 +3575,71 @@ "built-ins/Promise/prototype/finally/this-value-then-poisoned.js": "CRASH", "built-ins/Promise/prototype/finally/this-value-then-throws.js": "CRASH", "built-ins/Promise/prototype/finally/this-value-thenable.js": "CRASH", + "built-ins/Promise/prototype/then/S25.4.4_A1.1_T1.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/S25.4.4_A2.1_T1.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/S25.4.4_A2.1_T2.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/S25.4.4_A2.1_T3.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/S25.4.5.3_A4.1_T1.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/S25.4.5.3_A4.1_T2.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/S25.4.5.3_A4.2_T1.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/S25.4.5.3_A4.2_T2.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/S25.4.5.3_A5.1_T1.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/S25.4.5.3_A5.2_T1.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/S25.4.5.3_A5.3_T1.js": "UNRESOLVED", "built-ins/Promise/prototype/then/capability-executor-called-twice.js": "CRASH", "built-ins/Promise/prototype/then/capability-executor-not-callable.js": "CRASH", - "built-ins/Promise/prototype/then/ctor-access-count.js": "FAIL", + "built-ins/Promise/prototype/then/ctor-access-count.js": "UNRESOLVED", "built-ins/Promise/prototype/then/ctor-custom.js": "CRASH", "built-ins/Promise/prototype/then/ctor-null.js": "FAIL", "built-ins/Promise/prototype/then/ctor-poisoned.js": "FAIL", "built-ins/Promise/prototype/then/ctor-throws.js": "CRASH", "built-ins/Promise/prototype/then/deferred-is-resolved-value.js": "CRASH", + "built-ins/Promise/prototype/then/prfm-fulfilled.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/prfm-pending-fulfulled.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/prfm-pending-rejected.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/prfm-rejected.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/reject-pending-fulfilled.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/reject-pending-rejected.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/reject-settled-fulfilled.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/reject-settled-rejected.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-pending-fulfilled-non-obj.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-pending-fulfilled-non-thenable.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-pending-fulfilled-poisoned-then.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-pending-fulfilled-prms-cstm-then.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-pending-fulfilled-self.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-pending-fulfilled-thenable.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-pending-rejected-non-obj.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-pending-rejected-non-thenable.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-pending-rejected-poisoned-then.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-pending-rejected-prms-cstm-then.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-pending-rejected-self.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-pending-rejected-thenable.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-settled-fulfilled-non-obj.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-settled-fulfilled-non-thenable.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-settled-fulfilled-poisoned-then.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-settled-fulfilled-prms-cstm-then.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-settled-fulfilled-self.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-settled-fulfilled-thenable.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-settled-rejected-non-obj.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-settled-rejected-non-thenable.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-settled-rejected-poisoned-then.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-settled-rejected-prms-cstm-then.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-settled-rejected-self.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/resolve-settled-rejected-thenable.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/rxn-handler-fulfilled-invoke-nonstrict.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/rxn-handler-fulfilled-invoke-strict.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/rxn-handler-fulfilled-next-abrupt.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/rxn-handler-fulfilled-next.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/rxn-handler-fulfilled-return-abrupt.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/rxn-handler-fulfilled-return-normal.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/rxn-handler-identity.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/rxn-handler-rejected-invoke-nonstrict.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/rxn-handler-rejected-invoke-strict.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/rxn-handler-rejected-next-abrupt.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/rxn-handler-rejected-next.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/rxn-handler-rejected-return-abrupt.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/rxn-handler-rejected-return-normal.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/rxn-handler-thrower.js": "UNRESOLVED", "built-ins/Promise/race/S25.4.4.3_A2.1_T1.js": "CRASH", "built-ins/Promise/race/S25.4.4.3_A2.2_T1.js": "CRASH", "built-ins/Promise/race/S25.4.4.3_A2.2_T2.js": "CRASH", @@ -3665,6 +3727,16 @@ "built-ins/Promise/race/same-reject-function.js": "CRASH", "built-ins/Promise/race/same-resolve-function.js": "CRASH", "built-ins/Promise/race/species-get-error.js": "CRASH", + "built-ins/Promise/reject-ignored-via-abrupt.js": "UNRESOLVED", + "built-ins/Promise/reject-ignored-via-fn-deferred.js": "UNRESOLVED", + "built-ins/Promise/reject-ignored-via-fn-immed.js": "UNRESOLVED", + "built-ins/Promise/reject-via-abrupt-queue.js": "UNRESOLVED", + "built-ins/Promise/reject-via-abrupt.js": "UNRESOLVED", + "built-ins/Promise/reject-via-fn-deferred-queue.js": "UNRESOLVED", + "built-ins/Promise/reject-via-fn-deferred.js": "UNRESOLVED", + "built-ins/Promise/reject-via-fn-immed-queue.js": "UNRESOLVED", + "built-ins/Promise/reject-via-fn-immed.js": "UNRESOLVED", + "built-ins/Promise/reject/S25.4.4.4_A2.1_T1.js": "UNRESOLVED", "built-ins/Promise/reject/S25.4.4.4_A3.1_T1.js": "CRASH", "built-ins/Promise/reject/capability-executor-called-twice.js": "CRASH", "built-ins/Promise/reject/capability-executor-not-callable.js": "CRASH", @@ -3674,6 +3746,27 @@ "built-ins/Promise/reject/ctx-ctor.js": "CRASH", "built-ins/Promise/reject/ctx-non-ctor.js": "CRASH", "built-ins/Promise/reject/ctx-non-object.js": "CRASH", + "built-ins/Promise/resolve-ignored-via-fn-deferred.js": "UNRESOLVED", + "built-ins/Promise/resolve-ignored-via-fn-immed.js": "UNRESOLVED", + "built-ins/Promise/resolve-non-obj-deferred.js": "UNRESOLVED", + "built-ins/Promise/resolve-non-obj-immed.js": "UNRESOLVED", + "built-ins/Promise/resolve-non-thenable-deferred.js": "UNRESOLVED", + "built-ins/Promise/resolve-non-thenable-immed.js": "UNRESOLVED", + "built-ins/Promise/resolve-poisoned-then-deferred.js": "UNRESOLVED", + "built-ins/Promise/resolve-poisoned-then-immed.js": "UNRESOLVED", + "built-ins/Promise/resolve-prms-cstm-then-deferred.js": "UNRESOLVED", + "built-ins/Promise/resolve-prms-cstm-then-immed.js": "UNRESOLVED", + "built-ins/Promise/resolve-self.js": "UNRESOLVED", + "built-ins/Promise/resolve-thenable-deferred.js": "UNRESOLVED", + "built-ins/Promise/resolve-thenable-immed.js": "UNRESOLVED", + "built-ins/Promise/resolve/S25.4.4.5_A2.2_T1.js": "UNRESOLVED", + "built-ins/Promise/resolve/S25.4.4.5_A2.3_T1.js": "UNRESOLVED", + "built-ins/Promise/resolve/S25.4.4.5_A3.1_T1.js": "UNRESOLVED", + "built-ins/Promise/resolve/S25.4.4.5_A4.1_T1.js": "UNRESOLVED", + "built-ins/Promise/resolve/S25.Promise_resolve_foreign_thenable_1.js": "UNRESOLVED", + "built-ins/Promise/resolve/S25.Promise_resolve_foreign_thenable_2.js": "UNRESOLVED", + "built-ins/Promise/resolve/arg-non-thenable.js": "UNRESOLVED", + "built-ins/Promise/resolve/arg-poisoned-then.js": "UNRESOLVED", "built-ins/Promise/resolve/arg-uniq-ctor.js": "FAIL", "built-ins/Promise/resolve/capability-executor-called-twice.js": "CRASH", "built-ins/Promise/resolve/capability-executor-not-callable.js": "CRASH", @@ -3684,9 +3777,16 @@ "built-ins/Promise/resolve/ctx-non-ctor.js": "CRASH", "built-ins/Promise/resolve/ctx-non-object.js": "CRASH", "built-ins/Promise/resolve/resolve-from-promise-capability.js": "CRASH", + "built-ins/Promise/resolve/resolve-non-obj.js": "UNRESOLVED", + "built-ins/Promise/resolve/resolve-non-thenable.js": "UNRESOLVED", + "built-ins/Promise/resolve/resolve-poisoned-then.js": "UNRESOLVED", "built-ins/Promise/resolve/resolve-self.js": "CRASH", + "built-ins/Promise/resolve/resolve-thenable.js": "UNRESOLVED", + "built-ins/Promise/try/args.js": "UNRESOLVED", "built-ins/Promise/try/ctx-ctor-throws.js": "FAIL", "built-ins/Promise/try/ctx-ctor.js": "CRASH", + "built-ins/Promise/try/return-value.js": "UNRESOLVED", + "built-ins/Promise/try/throws.js": "UNRESOLVED", "built-ins/Promise/withResolvers/ctx-ctor.js": "CRASH", "built-ins/Proxy/apply/arguments-realm.js": "FAIL", "built-ins/Proxy/apply/call-parameters.js": "CRASH", @@ -12673,10 +12773,16 @@ "built-ins/parseInt/S15.1.2.2_A9.4.js": "FAIL", "harness/assert-throws-same-realm.js": "FAIL", "harness/assertRelativeDateMs.js": "CRASH", + "harness/asyncHelpers-asyncTest-returns-undefined.js": "UNRESOLVED", + "harness/asyncHelpers-asyncTest-then-rejects.js": "UNRESOLVED", + "harness/asyncHelpers-asyncTest-then-resolves.js": "UNRESOLVED", "harness/asyncHelpers-throwsAsync-custom-typeerror.js": "CRASH", + "harness/asyncHelpers-throwsAsync-custom.js": "UNRESOLVED", + "harness/asyncHelpers-throwsAsync-func-never-settles.js": "UNRESOLVED", "harness/asyncHelpers-throwsAsync-func-throws-sync.js": "CRASH", "harness/asyncHelpers-throwsAsync-incorrect-ctor.js": "CRASH", "harness/asyncHelpers-throwsAsync-invalid-func.js": "CRASH", + "harness/asyncHelpers-throwsAsync-native.js": "UNRESOLVED", "harness/asyncHelpers-throwsAsync-no-arg.js": "CRASH", "harness/asyncHelpers-throwsAsync-no-error.js": "CRASH", "harness/asyncHelpers-throwsAsync-null.js": "CRASH", @@ -12867,6 +12973,42 @@ "language/destructuring/binding/keyed-destructuring-property-reference-target-evaluation-order-with-bindings.js": "CRASH", "language/destructuring/binding/syntax/recursive-array-and-object-patterns.js": "FAIL", "language/destructuring/binding/typedarray-backed-by-resizable-buffer.js": "CRASH", + "language/eval-code/direct/async-func-decl-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-decl-a-following-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-decl-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-decl-fn-body-cntns-arguments-func-decl-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-decl-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-decl-fn-body-cntns-arguments-lex-bind-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-decl-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-decl-fn-body-cntns-arguments-var-bind-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-decl-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-decl-no-pre-existing-arguments-bindings-are-present-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-named-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-named-a-following-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-named-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-named-a-preceding-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-named-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-named-fn-body-cntns-arguments-func-decl-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-named-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-named-fn-body-cntns-arguments-lex-bind-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-named-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-named-fn-body-cntns-arguments-var-bind-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-named-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-named-no-pre-existing-arguments-bindings-are-present-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-nameless-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-nameless-a-following-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-nameless-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-nameless-a-preceding-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-nameless-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-nameless-fn-body-cntns-arguments-func-decl-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-nameless-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-nameless-fn-body-cntns-arguments-lex-bind-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-nameless-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-nameless-fn-body-cntns-arguments-var-bind-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-nameless-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-func-expr-nameless-no-pre-existing-arguments-bindings-are-present-declare-arguments.js": "UNRESOLVED", "language/eval-code/direct/async-gen-func-decl-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js": "CRASH", "language/eval-code/direct/async-gen-func-decl-a-following-parameter-is-named-arguments-declare-arguments.js": "CRASH", "language/eval-code/direct/async-gen-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": "CRASH", @@ -12915,6 +13057,18 @@ "language/eval-code/direct/async-gen-named-func-expr-fn-body-cntns-arguments-var-bind-declare-arguments.js": "CRASH", "language/eval-code/direct/async-gen-named-func-expr-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js": "CRASH", "language/eval-code/direct/async-gen-named-func-expr-no-pre-existing-arguments-bindings-are-present-declare-arguments.js": "CRASH", + "language/eval-code/direct/async-meth-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-meth-a-following-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-meth-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-meth-a-preceding-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-meth-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-meth-fn-body-cntns-arguments-func-decl-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-meth-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-meth-fn-body-cntns-arguments-lex-bind-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-meth-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-meth-fn-body-cntns-arguments-var-bind-declare-arguments.js": "UNRESOLVED", + "language/eval-code/direct/async-meth-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js": "UNRESOLVED", + "language/eval-code/direct/async-meth-no-pre-existing-arguments-bindings-are-present-declare-arguments.js": "UNRESOLVED", "language/eval-code/direct/cptn-nrml-expr-prim.js": "FAIL", "language/eval-code/direct/gen-func-decl-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js": "CRASH", "language/eval-code/direct/gen-func-decl-a-following-parameter-is-named-arguments-declare-arguments.js": "CRASH", @@ -13422,6 +13576,22 @@ "language/expressions/assignment/target-super-computed-reference.js": "CRASH", "language/expressions/assignment/target-super-identifier-reference-null.js": "CRASH", "language/expressions/assignmenttargettype/simple-basic-identifierreference-await.js": "FAIL", + "language/expressions/async-arrow-function/arrow-returns-promise.js": "UNRESOLVED", + "language/expressions/async-arrow-function/dflt-params-abrupt.js": "UNRESOLVED", + "language/expressions/async-arrow-function/dflt-params-arg-val-not-undefined.js": "UNRESOLVED", + "language/expressions/async-arrow-function/dflt-params-arg-val-undefined.js": "UNRESOLVED", + "language/expressions/async-arrow-function/dflt-params-ref-later.js": "UNRESOLVED", + "language/expressions/async-arrow-function/dflt-params-ref-prior.js": "UNRESOLVED", + "language/expressions/async-arrow-function/dflt-params-ref-self.js": "UNRESOLVED", + "language/expressions/async-arrow-function/dflt-params-trailing-comma.js": "UNRESOLVED", + "language/expressions/async-arrow-function/eval-var-scope-syntax-err.js": "UNRESOLVED", + "language/expressions/async-arrow-function/forbidden-ext/b1/async-arrow-function-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", + "language/expressions/async-arrow-function/forbidden-ext/b1/async-arrow-function-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", + "language/expressions/async-arrow-function/forbidden-ext/b2/async-arrow-function-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", + "language/expressions/async-arrow-function/forbidden-ext/b2/async-arrow-function-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", + "language/expressions/async-arrow-function/forbidden-ext/b2/async-arrow-function-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", + "language/expressions/async-arrow-function/params-trailing-comma-multiple.js": "UNRESOLVED", + "language/expressions/async-arrow-function/params-trailing-comma-single.js": "UNRESOLVED", "language/expressions/async-arrow-function/try-reject-finally-reject.js": "CRASH", "language/expressions/async-arrow-function/try-reject-finally-return.js": "CRASH", "language/expressions/async-arrow-function/try-reject-finally-throw.js": "CRASH", @@ -13433,10 +13603,50 @@ "language/expressions/async-arrow-function/try-throw-finally-throw.js": "CRASH", "language/expressions/async-arrow-function/unscopables-with-in-nested-fn.js": "CRASH", "language/expressions/async-arrow-function/unscopables-with.js": "CRASH", + "language/expressions/async-function/forbidden-ext/b1/async-func-expr-named-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", + "language/expressions/async-function/forbidden-ext/b1/async-func-expr-named-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", + "language/expressions/async-function/forbidden-ext/b1/async-func-expr-nameless-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", + "language/expressions/async-function/forbidden-ext/b1/async-func-expr-nameless-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", + "language/expressions/async-function/forbidden-ext/b2/async-func-expr-named-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", + "language/expressions/async-function/forbidden-ext/b2/async-func-expr-named-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", + "language/expressions/async-function/forbidden-ext/b2/async-func-expr-named-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", + "language/expressions/async-function/forbidden-ext/b2/async-func-expr-nameless-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", + "language/expressions/async-function/forbidden-ext/b2/async-func-expr-nameless-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", + "language/expressions/async-function/forbidden-ext/b2/async-func-expr-nameless-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", + "language/expressions/async-function/named-dflt-params-abrupt.js": "UNRESOLVED", + "language/expressions/async-function/named-dflt-params-arg-val-not-undefined.js": "UNRESOLVED", + "language/expressions/async-function/named-dflt-params-arg-val-undefined.js": "UNRESOLVED", + "language/expressions/async-function/named-dflt-params-ref-later.js": "UNRESOLVED", + "language/expressions/async-function/named-dflt-params-ref-prior.js": "UNRESOLVED", + "language/expressions/async-function/named-dflt-params-ref-self.js": "UNRESOLVED", + "language/expressions/async-function/named-dflt-params-trailing-comma.js": "UNRESOLVED", + "language/expressions/async-function/named-eval-var-scope-syntax-err.js": "UNRESOLVED", + "language/expressions/async-function/named-params-trailing-comma-multiple.js": "UNRESOLVED", + "language/expressions/async-function/named-params-trailing-comma-single.js": "UNRESOLVED", + "language/expressions/async-function/named-reassign-fn-name-in-body-in-arrow.js": "UNRESOLVED", + "language/expressions/async-function/named-reassign-fn-name-in-body-in-eval.js": "UNRESOLVED", + "language/expressions/async-function/named-reassign-fn-name-in-body.js": "UNRESOLVED", + "language/expressions/async-function/named-returns-async-arrow-returns-arguments-from-parent-function.js": "UNRESOLVED", "language/expressions/async-function/named-returns-async-arrow-returns-newtarget.js": "CRASH", + "language/expressions/async-function/named-returns-async-arrow.js": "UNRESOLVED", + "language/expressions/async-function/named-returns-async-function-returns-arguments-from-own-function.js": "UNRESOLVED", "language/expressions/async-function/named-returns-async-function-returns-newtarget.js": "CRASH", + "language/expressions/async-function/named-returns-async-function.js": "UNRESOLVED", + "language/expressions/async-function/named-strict-error-reassign-fn-name-in-body-in-arrow.js": "UNRESOLVED", + "language/expressions/async-function/named-strict-error-reassign-fn-name-in-body-in-eval.js": "UNRESOLVED", + "language/expressions/async-function/named-strict-error-reassign-fn-name-in-body.js": "UNRESOLVED", "language/expressions/async-function/named-unscopables-with-in-nested-fn.js": "CRASH", "language/expressions/async-function/named-unscopables-with.js": "CRASH", + "language/expressions/async-function/nameless-dflt-params-abrupt.js": "UNRESOLVED", + "language/expressions/async-function/nameless-dflt-params-arg-val-not-undefined.js": "UNRESOLVED", + "language/expressions/async-function/nameless-dflt-params-arg-val-undefined.js": "UNRESOLVED", + "language/expressions/async-function/nameless-dflt-params-ref-later.js": "UNRESOLVED", + "language/expressions/async-function/nameless-dflt-params-ref-prior.js": "UNRESOLVED", + "language/expressions/async-function/nameless-dflt-params-ref-self.js": "UNRESOLVED", + "language/expressions/async-function/nameless-dflt-params-trailing-comma.js": "UNRESOLVED", + "language/expressions/async-function/nameless-eval-var-scope-syntax-err.js": "UNRESOLVED", + "language/expressions/async-function/nameless-params-trailing-comma-multiple.js": "UNRESOLVED", + "language/expressions/async-function/nameless-params-trailing-comma-single.js": "UNRESOLVED", "language/expressions/async-function/nameless-unscopables-with-in-nested-fn.js": "CRASH", "language/expressions/async-function/nameless-unscopables-with.js": "CRASH", "language/expressions/async-function/try-reject-finally-reject.js": "CRASH", @@ -13984,14 +14194,25 @@ "language/expressions/async-generator/yield-star-sync-throw.js": "CRASH", "language/expressions/async-generator/yield-thenable-create-resolving-functions-reject.js": "CRASH", "language/expressions/async-generator/yield-thenable-create-resolving-functions-resolve.js": "CRASH", + "language/expressions/await/async-await-interleaved.js": "UNRESOLVED", "language/expressions/await/async-generator-interleaved.js": "CRASH", "language/expressions/await/await-BindingIdentifier-in-global.js": "FAIL", + "language/expressions/await/await-awaits-thenable-not-callable.js": "UNRESOLVED", + "language/expressions/await/await-awaits-thenables-that-throw.js": "UNRESOLVED", + "language/expressions/await/await-awaits-thenables.js": "UNRESOLVED", "language/expressions/await/await-in-function.js": "FAIL", "language/expressions/await/await-in-generator.js": "FAIL", "language/expressions/await/await-in-global.js": "FAIL", "language/expressions/await/await-in-nested-function.js": "FAIL", "language/expressions/await/await-in-nested-generator.js": "FAIL", + "language/expressions/await/await-monkey-patched-promise.js": "UNRESOLVED", + "language/expressions/await/await-non-promise-thenable.js": "UNRESOLVED", + "language/expressions/await/await-non-promise.js": "UNRESOLVED", + "language/expressions/await/await-throws-rejections.js": "UNRESOLVED", "language/expressions/await/for-await-of-interleaved.js": "CRASH", + "language/expressions/await/syntax-await-has-UnaryExpression-with-MultiplicativeExpression.js": "UNRESOLVED", + "language/expressions/await/syntax-await-has-UnaryExpression.js": "UNRESOLVED", + "language/expressions/await/syntax-await-in-ConditionalExpression.js": "UNRESOLVED", "language/expressions/bitwise-and/bigint-non-primitive.js": "CRASH", "language/expressions/bitwise-and/bigint-toprimitive.js": "CRASH", "language/expressions/bitwise-and/bigint-wrapped-values.js": "FAIL", @@ -14174,10 +14395,46 @@ "language/expressions/class/async-gen-method/yield-star-sync-next.js": "CRASH", "language/expressions/class/async-gen-method/yield-star-sync-return.js": "CRASH", "language/expressions/class/async-gen-method/yield-star-sync-throw.js": "CRASH", + "language/expressions/class/async-method-static/dflt-params-abrupt.js": "UNRESOLVED", + "language/expressions/class/async-method-static/dflt-params-arg-val-not-undefined.js": "UNRESOLVED", + "language/expressions/class/async-method-static/dflt-params-arg-val-undefined.js": "UNRESOLVED", + "language/expressions/class/async-method-static/dflt-params-ref-later.js": "UNRESOLVED", + "language/expressions/class/async-method-static/dflt-params-ref-prior.js": "UNRESOLVED", + "language/expressions/class/async-method-static/dflt-params-ref-self.js": "UNRESOLVED", + "language/expressions/class/async-method-static/dflt-params-trailing-comma.js": "UNRESOLVED", + "language/expressions/class/async-method-static/forbidden-ext/b1/cls-expr-async-meth-static-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", + "language/expressions/class/async-method-static/forbidden-ext/b1/cls-expr-async-meth-static-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", + "language/expressions/class/async-method-static/forbidden-ext/b2/cls-expr-async-meth-static-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", + "language/expressions/class/async-method-static/forbidden-ext/b2/cls-expr-async-meth-static-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", + "language/expressions/class/async-method-static/forbidden-ext/b2/cls-expr-async-meth-static-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", + "language/expressions/class/async-method-static/params-trailing-comma-multiple.js": "UNRESOLVED", + "language/expressions/class/async-method-static/params-trailing-comma-single.js": "UNRESOLVED", + "language/expressions/class/async-method-static/returns-async-arrow-returns-arguments-from-parent-function.js": "UNRESOLVED", "language/expressions/class/async-method-static/returns-async-arrow-returns-newtarget.js": "CRASH", + "language/expressions/class/async-method-static/returns-async-arrow.js": "UNRESOLVED", + "language/expressions/class/async-method-static/returns-async-function-returns-arguments-from-own-function.js": "UNRESOLVED", "language/expressions/class/async-method-static/returns-async-function-returns-newtarget.js": "CRASH", + "language/expressions/class/async-method-static/returns-async-function.js": "UNRESOLVED", + "language/expressions/class/async-method/dflt-params-abrupt.js": "UNRESOLVED", + "language/expressions/class/async-method/dflt-params-arg-val-not-undefined.js": "UNRESOLVED", + "language/expressions/class/async-method/dflt-params-arg-val-undefined.js": "UNRESOLVED", + "language/expressions/class/async-method/dflt-params-ref-later.js": "UNRESOLVED", + "language/expressions/class/async-method/dflt-params-ref-prior.js": "UNRESOLVED", + "language/expressions/class/async-method/dflt-params-ref-self.js": "UNRESOLVED", + "language/expressions/class/async-method/dflt-params-trailing-comma.js": "UNRESOLVED", + "language/expressions/class/async-method/forbidden-ext/b1/cls-expr-async-meth-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", + "language/expressions/class/async-method/forbidden-ext/b1/cls-expr-async-meth-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", + "language/expressions/class/async-method/forbidden-ext/b2/cls-expr-async-meth-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", + "language/expressions/class/async-method/forbidden-ext/b2/cls-expr-async-meth-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", + "language/expressions/class/async-method/forbidden-ext/b2/cls-expr-async-meth-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", + "language/expressions/class/async-method/params-trailing-comma-multiple.js": "UNRESOLVED", + "language/expressions/class/async-method/params-trailing-comma-single.js": "UNRESOLVED", + "language/expressions/class/async-method/returns-async-arrow-returns-arguments-from-parent-function.js": "UNRESOLVED", "language/expressions/class/async-method/returns-async-arrow-returns-newtarget.js": "CRASH", + "language/expressions/class/async-method/returns-async-arrow.js": "UNRESOLVED", + "language/expressions/class/async-method/returns-async-function-returns-arguments-from-own-function.js": "UNRESOLVED", "language/expressions/class/async-method/returns-async-function-returns-newtarget.js": "CRASH", + "language/expressions/class/async-method/returns-async-function.js": "UNRESOLVED", "language/expressions/class/class-name-ident-await-escaped.js": "FAIL", "language/expressions/class/class-name-ident-await.js": "FAIL", "language/expressions/class/class-name-ident-let-escaped.js": "CRASH", @@ -15873,11 +16130,17 @@ "language/expressions/class/elements/after-same-line-static-async-gen-static-private-methods-with-fields.js": "CRASH", "language/expressions/class/elements/after-same-line-static-async-gen-static-private-methods.js": "CRASH", "language/expressions/class/elements/after-same-line-static-async-gen-string-literal-names.js": "CRASH", + "language/expressions/class/elements/after-same-line-static-async-method-computed-names.js": "UNRESOLVED", + "language/expressions/class/elements/after-same-line-static-async-method-computed-symbol-names.js": "UNRESOLVED", "language/expressions/class/elements/after-same-line-static-async-method-grammar-privatename-identifier-semantics-stringvalue.js": "CRASH", + "language/expressions/class/elements/after-same-line-static-async-method-literal-names-asi.js": "UNRESOLVED", + "language/expressions/class/elements/after-same-line-static-async-method-literal-names.js": "UNRESOLVED", "language/expressions/class/elements/after-same-line-static-async-method-private-field-usage.js": "CRASH", "language/expressions/class/elements/after-same-line-static-async-method-private-method-getter-usage.js": "CRASH", "language/expressions/class/elements/after-same-line-static-async-method-private-method-usage.js": "CRASH", "language/expressions/class/elements/after-same-line-static-async-method-private-names.js": "CRASH", + "language/expressions/class/elements/after-same-line-static-async-method-rs-field-identifier-initializer.js": "UNRESOLVED", + "language/expressions/class/elements/after-same-line-static-async-method-rs-field-identifier.js": "UNRESOLVED", "language/expressions/class/elements/after-same-line-static-async-method-rs-private-getter-alt.js": "CRASH", "language/expressions/class/elements/after-same-line-static-async-method-rs-private-getter.js": "CRASH", "language/expressions/class/elements/after-same-line-static-async-method-rs-private-method-alt.js": "CRASH", @@ -16553,11 +16816,17 @@ "language/expressions/class/elements/same-line-async-gen-static-private-methods-with-fields.js": "CRASH", "language/expressions/class/elements/same-line-async-gen-static-private-methods.js": "CRASH", "language/expressions/class/elements/same-line-async-gen-string-literal-names.js": "CRASH", + "language/expressions/class/elements/same-line-async-method-computed-names.js": "UNRESOLVED", + "language/expressions/class/elements/same-line-async-method-computed-symbol-names.js": "UNRESOLVED", "language/expressions/class/elements/same-line-async-method-grammar-privatename-identifier-semantics-stringvalue.js": "CRASH", + "language/expressions/class/elements/same-line-async-method-literal-names-asi.js": "UNRESOLVED", + "language/expressions/class/elements/same-line-async-method-literal-names.js": "UNRESOLVED", "language/expressions/class/elements/same-line-async-method-private-field-usage.js": "CRASH", "language/expressions/class/elements/same-line-async-method-private-method-getter-usage.js": "CRASH", "language/expressions/class/elements/same-line-async-method-private-method-usage.js": "CRASH", "language/expressions/class/elements/same-line-async-method-private-names.js": "CRASH", + "language/expressions/class/elements/same-line-async-method-rs-field-identifier-initializer.js": "UNRESOLVED", + "language/expressions/class/elements/same-line-async-method-rs-field-identifier.js": "UNRESOLVED", "language/expressions/class/elements/same-line-async-method-rs-private-getter-alt.js": "CRASH", "language/expressions/class/elements/same-line-async-method-rs-private-getter.js": "CRASH", "language/expressions/class/elements/same-line-async-method-rs-private-method-alt.js": "CRASH", @@ -18314,17 +18583,36 @@ "language/expressions/object/method-definition/async-gen-yield-star-sync-next.js": "CRASH", "language/expressions/object/method-definition/async-gen-yield-star-sync-return.js": "CRASH", "language/expressions/object/method-definition/async-gen-yield-star-sync-throw.js": "CRASH", + "language/expressions/object/method-definition/async-meth-dflt-params-abrupt.js": "UNRESOLVED", + "language/expressions/object/method-definition/async-meth-dflt-params-arg-val-not-undefined.js": "UNRESOLVED", + "language/expressions/object/method-definition/async-meth-dflt-params-arg-val-undefined.js": "UNRESOLVED", + "language/expressions/object/method-definition/async-meth-dflt-params-ref-later.js": "UNRESOLVED", + "language/expressions/object/method-definition/async-meth-dflt-params-ref-prior.js": "UNRESOLVED", + "language/expressions/object/method-definition/async-meth-dflt-params-ref-self.js": "UNRESOLVED", + "language/expressions/object/method-definition/async-meth-dflt-params-trailing-comma.js": "UNRESOLVED", + "language/expressions/object/method-definition/async-meth-eval-var-scope-syntax-err.js": "UNRESOLVED", + "language/expressions/object/method-definition/async-meth-params-trailing-comma-multiple.js": "UNRESOLVED", + "language/expressions/object/method-definition/async-meth-params-trailing-comma-single.js": "UNRESOLVED", + "language/expressions/object/method-definition/async-returns-async-arrow-returns-arguments-from-parent-function.js": "UNRESOLVED", "language/expressions/object/method-definition/async-returns-async-arrow-returns-newtarget.js": "CRASH", + "language/expressions/object/method-definition/async-returns-async-arrow.js": "UNRESOLVED", + "language/expressions/object/method-definition/async-returns-async-function-returns-arguments-from-own-function.js": "UNRESOLVED", "language/expressions/object/method-definition/async-returns-async-function-returns-newtarget.js": "CRASH", + "language/expressions/object/method-definition/async-returns-async-function.js": "UNRESOLVED", "language/expressions/object/method-definition/async-super-call-body.js": "CRASH", "language/expressions/object/method-definition/async-super-call-param.js": "CRASH", "language/expressions/object/method-definition/early-errors-object-async-method-duplicate-parameters.js": "FAIL", "language/expressions/object/method-definition/early-errors-object-method-duplicate-parameters.js": "FAIL", "language/expressions/object/method-definition/forbidden-ext/b1/async-gen-meth-forbidden-ext-direct-access-prop-arguments.js": "CRASH", "language/expressions/object/method-definition/forbidden-ext/b1/async-gen-meth-forbidden-ext-direct-access-prop-caller.js": "CRASH", + "language/expressions/object/method-definition/forbidden-ext/b1/async-meth-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", + "language/expressions/object/method-definition/forbidden-ext/b1/async-meth-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", "language/expressions/object/method-definition/forbidden-ext/b2/async-gen-meth-forbidden-ext-indirect-access-own-prop-caller-get.js": "CRASH", "language/expressions/object/method-definition/forbidden-ext/b2/async-gen-meth-forbidden-ext-indirect-access-own-prop-caller-value.js": "CRASH", "language/expressions/object/method-definition/forbidden-ext/b2/async-gen-meth-forbidden-ext-indirect-access-prop-caller.js": "CRASH", + "language/expressions/object/method-definition/forbidden-ext/b2/async-meth-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", + "language/expressions/object/method-definition/forbidden-ext/b2/async-meth-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", + "language/expressions/object/method-definition/forbidden-ext/b2/async-meth-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", "language/expressions/object/method-definition/gen-meth-dflt-params-abrupt.js": "CRASH", "language/expressions/object/method-definition/gen-meth-dflt-params-ref-later.js": "CRASH", "language/expressions/object/method-definition/gen-meth-dflt-params-ref-self.js": "CRASH", @@ -18357,8 +18645,13 @@ "language/expressions/object/setter-super-prop.js": "CRASH", "language/expressions/optional-chaining/call-expression.js": "CRASH", "language/expressions/optional-chaining/iteration-statement-for-await-of.js": "CRASH", + "language/expressions/optional-chaining/member-expression-async-identifier.js": "UNRESOLVED", + "language/expressions/optional-chaining/member-expression-async-literal.js": "UNRESOLVED", + "language/expressions/optional-chaining/member-expression-async-this.js": "UNRESOLVED", "language/expressions/optional-chaining/member-expression.js": "CRASH", "language/expressions/optional-chaining/new-target-optional-call.js": "CRASH", + "language/expressions/optional-chaining/optional-chain-async-optional-chain-square-brackets.js": "UNRESOLVED", + "language/expressions/optional-chaining/optional-chain-async-square-brackets.js": "UNRESOLVED", "language/expressions/optional-chaining/super-property-optional-call.js": "CRASH", "language/expressions/postfix-decrement/S11.3.2_A5_T1.js": "CRASH", "language/expressions/postfix-decrement/S11.3.2_A5_T2.js": "CRASH", @@ -18863,9 +19156,37 @@ "language/statementList/fn-block-with-labels.js": "CRASH", "language/statementList/fn-let-declaration.js": "FAIL", "language/statements/async-function/cptn-decl.js": "FAIL", - "language/statements/async-function/evaluation-mapped-arguments.js": "FAIL", + "language/statements/async-function/dflt-params-abrupt.js": "UNRESOLVED", + "language/statements/async-function/dflt-params-arg-val-not-undefined.js": "UNRESOLVED", + "language/statements/async-function/dflt-params-arg-val-undefined.js": "UNRESOLVED", + "language/statements/async-function/dflt-params-ref-later.js": "UNRESOLVED", + "language/statements/async-function/dflt-params-ref-prior.js": "UNRESOLVED", + "language/statements/async-function/dflt-params-ref-self.js": "UNRESOLVED", + "language/statements/async-function/dflt-params-trailing-comma.js": "UNRESOLVED", + "language/statements/async-function/eval-var-scope-syntax-err.js": "UNRESOLVED", + "language/statements/async-function/evaluation-body-that-returns-after-await.js": "UNRESOLVED", + "language/statements/async-function/evaluation-body-that-returns.js": "UNRESOLVED", + "language/statements/async-function/evaluation-body-that-throws-after-await.js": "UNRESOLVED", + "language/statements/async-function/evaluation-body-that-throws.js": "UNRESOLVED", + "language/statements/async-function/evaluation-default-that-throws.js": "UNRESOLVED", + "language/statements/async-function/evaluation-mapped-arguments.js": "UNRESOLVED", + "language/statements/async-function/evaluation-this-value-global.js": "UNRESOLVED", + "language/statements/async-function/evaluation-this-value-passed.js": "UNRESOLVED", + "language/statements/async-function/evaluation-unmapped-arguments.js": "UNRESOLVED", + "language/statements/async-function/forbidden-ext/b1/async-func-decl-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", + "language/statements/async-function/forbidden-ext/b1/async-func-decl-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", + "language/statements/async-function/forbidden-ext/b2/async-func-decl-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", + "language/statements/async-function/forbidden-ext/b2/async-func-decl-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", + "language/statements/async-function/forbidden-ext/b2/async-func-decl-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", + "language/statements/async-function/params-trailing-comma-multiple.js": "UNRESOLVED", + "language/statements/async-function/params-trailing-comma-single.js": "UNRESOLVED", + "language/statements/async-function/returns-async-arrow-returns-arguments-from-parent-function.js": "UNRESOLVED", "language/statements/async-function/returns-async-arrow-returns-newtarget.js": "CRASH", + "language/statements/async-function/returns-async-arrow.js": "UNRESOLVED", + "language/statements/async-function/returns-async-function-returns-arguments-from-own-function.js": "UNRESOLVED", "language/statements/async-function/returns-async-function-returns-newtarget.js": "CRASH", + "language/statements/async-function/returns-async-function.js": "UNRESOLVED", + "language/statements/async-function/syntax-declaration.js": "UNRESOLVED", "language/statements/async-function/try-reject-finally-reject.js": "CRASH", "language/statements/async-function/try-reject-finally-return.js": "CRASH", "language/statements/async-function/try-reject-finally-throw.js": "CRASH", @@ -19315,10 +19636,46 @@ "language/statements/class/async-gen-method/yield-star-sync-next.js": "CRASH", "language/statements/class/async-gen-method/yield-star-sync-return.js": "CRASH", "language/statements/class/async-gen-method/yield-star-sync-throw.js": "CRASH", + "language/statements/class/async-method-static/dflt-params-abrupt.js": "UNRESOLVED", + "language/statements/class/async-method-static/dflt-params-arg-val-not-undefined.js": "UNRESOLVED", + "language/statements/class/async-method-static/dflt-params-arg-val-undefined.js": "UNRESOLVED", + "language/statements/class/async-method-static/dflt-params-ref-later.js": "UNRESOLVED", + "language/statements/class/async-method-static/dflt-params-ref-prior.js": "UNRESOLVED", + "language/statements/class/async-method-static/dflt-params-ref-self.js": "UNRESOLVED", + "language/statements/class/async-method-static/dflt-params-trailing-comma.js": "UNRESOLVED", + "language/statements/class/async-method-static/forbidden-ext/b1/cls-decl-async-meth-static-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", + "language/statements/class/async-method-static/forbidden-ext/b1/cls-decl-async-meth-static-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", + "language/statements/class/async-method-static/forbidden-ext/b2/cls-decl-async-meth-static-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", + "language/statements/class/async-method-static/forbidden-ext/b2/cls-decl-async-meth-static-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", + "language/statements/class/async-method-static/forbidden-ext/b2/cls-decl-async-meth-static-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", + "language/statements/class/async-method-static/params-trailing-comma-multiple.js": "UNRESOLVED", + "language/statements/class/async-method-static/params-trailing-comma-single.js": "UNRESOLVED", + "language/statements/class/async-method-static/returns-async-arrow-returns-arguments-from-parent-function.js": "UNRESOLVED", "language/statements/class/async-method-static/returns-async-arrow-returns-newtarget.js": "CRASH", + "language/statements/class/async-method-static/returns-async-arrow.js": "UNRESOLVED", + "language/statements/class/async-method-static/returns-async-function-returns-arguments-from-own-function.js": "UNRESOLVED", "language/statements/class/async-method-static/returns-async-function-returns-newtarget.js": "CRASH", + "language/statements/class/async-method-static/returns-async-function.js": "UNRESOLVED", + "language/statements/class/async-method/dflt-params-abrupt.js": "UNRESOLVED", + "language/statements/class/async-method/dflt-params-arg-val-not-undefined.js": "UNRESOLVED", + "language/statements/class/async-method/dflt-params-arg-val-undefined.js": "UNRESOLVED", + "language/statements/class/async-method/dflt-params-ref-later.js": "UNRESOLVED", + "language/statements/class/async-method/dflt-params-ref-prior.js": "UNRESOLVED", + "language/statements/class/async-method/dflt-params-ref-self.js": "UNRESOLVED", + "language/statements/class/async-method/dflt-params-trailing-comma.js": "UNRESOLVED", + "language/statements/class/async-method/forbidden-ext/b1/cls-decl-async-meth-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", + "language/statements/class/async-method/forbidden-ext/b1/cls-decl-async-meth-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", + "language/statements/class/async-method/forbidden-ext/b2/cls-decl-async-meth-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", + "language/statements/class/async-method/forbidden-ext/b2/cls-decl-async-meth-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", + "language/statements/class/async-method/forbidden-ext/b2/cls-decl-async-meth-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", + "language/statements/class/async-method/params-trailing-comma-multiple.js": "UNRESOLVED", + "language/statements/class/async-method/params-trailing-comma-single.js": "UNRESOLVED", + "language/statements/class/async-method/returns-async-arrow-returns-arguments-from-parent-function.js": "UNRESOLVED", "language/statements/class/async-method/returns-async-arrow-returns-newtarget.js": "CRASH", + "language/statements/class/async-method/returns-async-arrow.js": "UNRESOLVED", + "language/statements/class/async-method/returns-async-function-returns-arguments-from-own-function.js": "UNRESOLVED", "language/statements/class/async-method/returns-async-function-returns-newtarget.js": "CRASH", + "language/statements/class/async-method/returns-async-function.js": "UNRESOLVED", "language/statements/class/class-name-ident-await-escaped.js": "FAIL", "language/statements/class/class-name-ident-await.js": "FAIL", "language/statements/class/class-name-ident-let-escaped.js": "CRASH", @@ -21022,11 +21379,17 @@ "language/statements/class/elements/after-same-line-static-async-gen-static-private-methods-with-fields.js": "CRASH", "language/statements/class/elements/after-same-line-static-async-gen-static-private-methods.js": "CRASH", "language/statements/class/elements/after-same-line-static-async-gen-string-literal-names.js": "CRASH", + "language/statements/class/elements/after-same-line-static-async-method-computed-names.js": "UNRESOLVED", + "language/statements/class/elements/after-same-line-static-async-method-computed-symbol-names.js": "UNRESOLVED", "language/statements/class/elements/after-same-line-static-async-method-grammar-privatename-identifier-semantics-stringvalue.js": "CRASH", + "language/statements/class/elements/after-same-line-static-async-method-literal-names-asi.js": "UNRESOLVED", + "language/statements/class/elements/after-same-line-static-async-method-literal-names.js": "UNRESOLVED", "language/statements/class/elements/after-same-line-static-async-method-private-field-usage.js": "CRASH", "language/statements/class/elements/after-same-line-static-async-method-private-method-getter-usage.js": "CRASH", "language/statements/class/elements/after-same-line-static-async-method-private-method-usage.js": "CRASH", "language/statements/class/elements/after-same-line-static-async-method-private-names.js": "CRASH", + "language/statements/class/elements/after-same-line-static-async-method-rs-field-identifier-initializer.js": "UNRESOLVED", + "language/statements/class/elements/after-same-line-static-async-method-rs-field-identifier.js": "UNRESOLVED", "language/statements/class/elements/after-same-line-static-async-method-rs-private-getter-alt.js": "CRASH", "language/statements/class/elements/after-same-line-static-async-method-rs-private-getter.js": "CRASH", "language/statements/class/elements/after-same-line-static-async-method-rs-private-method-alt.js": "CRASH", @@ -21780,11 +22143,17 @@ "language/statements/class/elements/same-line-async-gen-static-private-methods-with-fields.js": "CRASH", "language/statements/class/elements/same-line-async-gen-static-private-methods.js": "CRASH", "language/statements/class/elements/same-line-async-gen-string-literal-names.js": "CRASH", + "language/statements/class/elements/same-line-async-method-computed-names.js": "UNRESOLVED", + "language/statements/class/elements/same-line-async-method-computed-symbol-names.js": "UNRESOLVED", "language/statements/class/elements/same-line-async-method-grammar-privatename-identifier-semantics-stringvalue.js": "CRASH", + "language/statements/class/elements/same-line-async-method-literal-names-asi.js": "UNRESOLVED", + "language/statements/class/elements/same-line-async-method-literal-names.js": "UNRESOLVED", "language/statements/class/elements/same-line-async-method-private-field-usage.js": "CRASH", "language/statements/class/elements/same-line-async-method-private-method-getter-usage.js": "CRASH", "language/statements/class/elements/same-line-async-method-private-method-usage.js": "CRASH", "language/statements/class/elements/same-line-async-method-private-names.js": "CRASH", + "language/statements/class/elements/same-line-async-method-rs-field-identifier-initializer.js": "UNRESOLVED", + "language/statements/class/elements/same-line-async-method-rs-field-identifier.js": "UNRESOLVED", "language/statements/class/elements/same-line-async-method-rs-private-getter-alt.js": "CRASH", "language/statements/class/elements/same-line-async-method-rs-private-getter.js": "CRASH", "language/statements/class/elements/same-line-async-method-rs-private-method-alt.js": "CRASH", @@ -24673,15 +25042,20 @@ "staging/explicit-resource-management/Symbol/dispose/cross-realm.js": "FAIL", "staging/explicit-resource-management/Symbol/dispose/prop-desc.js": "FAIL", "staging/explicit-resource-management/async-disposable-stack-adopt-and-defer-not-callable.js": "FAIL", - "staging/explicit-resource-management/async-disposable-stack-adopt.js": "FAIL", - "staging/explicit-resource-management/async-disposable-stack-async-dispose-symbol-throws.js": "FAIL", + "staging/explicit-resource-management/async-disposable-stack-adopt-on-disposed-stack.js": "UNRESOLVED", + "staging/explicit-resource-management/async-disposable-stack-adopt.js": "UNRESOLVED", + "staging/explicit-resource-management/async-disposable-stack-async-dispose-symbol-throws.js": "UNRESOLVED", "staging/explicit-resource-management/async-disposable-stack-constructor-and-prototype.js": "CRASH", - "staging/explicit-resource-management/async-disposable-stack-defer.js": "FAIL", - "staging/explicit-resource-management/async-disposable-stack-dispose-on-disposed-stack.js": "FAIL", - "staging/explicit-resource-management/async-disposable-stack-dispose-sync-calls.js": "FAIL", - "staging/explicit-resource-management/async-disposable-stack-dispose-throws-suppressed-error.js": "FAIL", - "staging/explicit-resource-management/async-disposable-stack-dispose.js": "FAIL", + "staging/explicit-resource-management/async-disposable-stack-defer-on-disposed-stack.js": "UNRESOLVED", + "staging/explicit-resource-management/async-disposable-stack-defer.js": "UNRESOLVED", + "staging/explicit-resource-management/async-disposable-stack-dispose-on-disposed-stack.js": "UNRESOLVED", + "staging/explicit-resource-management/async-disposable-stack-dispose-sync-calls.js": "UNRESOLVED", + "staging/explicit-resource-management/async-disposable-stack-dispose-throws-suppressed-error.js": "UNRESOLVED", + "staging/explicit-resource-management/async-disposable-stack-dispose.js": "UNRESOLVED", + "staging/explicit-resource-management/async-disposable-stack-disposed-getter.js": "UNRESOLVED", + "staging/explicit-resource-management/async-disposable-stack-move-on-disposed-stack.js": "UNRESOLVED", "staging/explicit-resource-management/async-disposable-stack-move.js": "FAIL", + "staging/explicit-resource-management/async-disposable-stack-use-on-disposed-stack.js": "UNRESOLVED", "staging/explicit-resource-management/async-disposable-stack-use.js": "FAIL", "staging/explicit-resource-management/await-using-dispose-method-throws-after-await.js": "CRASH", "staging/explicit-resource-management/await-using-dispose-method-throws.js": "CRASH", @@ -24695,12 +25069,12 @@ "staging/explicit-resource-management/await-using-in-switch-case-block.js": "CRASH", "staging/explicit-resource-management/await-using-mixed-async-throws.js": "CRASH", "staging/explicit-resource-management/await-using-mixed-sync-throws.js": "CRASH", - "staging/explicit-resource-management/await-using-mixed-throws-suppressed-error-from-sync-and-async-disposals.js": "FAIL", - "staging/explicit-resource-management/await-using-mixed-throws-suppressed-error.js": "FAIL", + "staging/explicit-resource-management/await-using-mixed-throws-suppressed-error-from-sync-and-async-disposals.js": "UNRESOLVED", + "staging/explicit-resource-management/await-using-mixed-throws-suppressed-error.js": "UNRESOLVED", "staging/explicit-resource-management/await-using-throws-from-symbol-dispose.js": "CRASH", - "staging/explicit-resource-management/await-using-throws-suppressed-error-from-disposals.js": "FAIL", - "staging/explicit-resource-management/await-using-throws-suppressed-error-from-try-and-disposal.js": "FAIL", - "staging/explicit-resource-management/await-using-throws-suppressed-error-of-undefined.js": "FAIL", + "staging/explicit-resource-management/await-using-throws-suppressed-error-from-disposals.js": "UNRESOLVED", + "staging/explicit-resource-management/await-using-throws-suppressed-error-from-try-and-disposal.js": "UNRESOLVED", + "staging/explicit-resource-management/await-using-throws-suppressed-error-of-undefined.js": "UNRESOLVED", "staging/explicit-resource-management/await-using-user-code-throws-after.js": "CRASH", "staging/explicit-resource-management/await-using-user-code-throws-before.js": "CRASH", "staging/explicit-resource-management/await-using-with-no-async-dispose-method.js": "CRASH", diff --git a/tests/metrics.json b/tests/metrics.json index be7aeed90..ebd0aee5a 100644 --- a/tests/metrics.json +++ b/tests/metrics.json @@ -1,11 +1,11 @@ { "results": { "crash": 16285, - "fail": 8262, - "pass": 20701, + "fail": 8164, + "pass": 20327, "skip": 40, "timeout": 3, - "unresolved": 0 + "unresolved": 472 }, "total": 45291 } \ No newline at end of file From 9482476378bb831556a6335fa524c78ea45bf73d Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 18 Oct 2024 15:40:14 +0300 Subject: [PATCH 06/13] Remove unused code, debug prints --- .../control_abstraction_objects/generator_objects.rs | 1 - .../src/ecmascript/scripts_and_modules/source_code.rs | 1 - nova_vm/src/engine/bytecode/bytecode_compiler.rs | 2 -- nova_vm/src/engine/bytecode/vm.rs | 11 +---------- 4 files changed, 1 insertion(+), 14 deletions(-) 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 0ef2a4271..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 @@ -96,7 +96,6 @@ impl Generator { // 11. Return ? result. match execution_result { ExecutionResult::Return(result_value) => { - println!("Generator: {:?}", self); // GeneratorStart step 4: // g. Set acGenerator.[[GeneratorState]] to completed. // h. NOTE: Once a generator enters the completed state it never leaves it and its 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 79f254f5c..bd00c8755 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules/source_code.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules/source_code.rs @@ -151,7 +151,6 @@ impl Debug for SourceCodeHeapData { impl Drop for SourceCodeHeapData { fn drop(&mut self) { - println!("Dropping SourceCode {:?}", self.source); // SAFETY: All references to this SourceCode should have been dropped // before we drop this. drop(unsafe { Box::from_raw(self.allocator.as_mut()) }); diff --git a/nova_vm/src/engine/bytecode/bytecode_compiler.rs b/nova_vm/src/engine/bytecode/bytecode_compiler.rs index 87737074c..a4fe144aa 100644 --- a/nova_vm/src/engine/bytecode/bytecode_compiler.rs +++ b/nova_vm/src/engine/bytecode/bytecode_compiler.rs @@ -186,8 +186,6 @@ impl CompileContext<'_> { eprintln!(); } - println!("{:?}", data.params as *const _ as *const ()); - function_declaration_instantiation::instantiation( self, data.params, diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index 550ca7f38..d4bd2e8df 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -56,7 +56,7 @@ use super::{ executable::{ArrowFunctionExpression, SendableRef}, instructions::Instr, iterator::{ObjectPropertiesIterator, VmIterator}, - Executable, ExecutableHeapData, FunctionExpression, IndexType, Instruction, InstructionIter, + Executable, FunctionExpression, IndexType, Instruction, InstructionIter, NamedEvaluationParameter, }; @@ -208,15 +208,6 @@ impl Vm { } } - fn fetch_identifier(&self, exe: &ExecutableHeapData, index: usize) -> String { - String::try_from(exe.constants[index]) - .expect("Invalid identifier index: Value was not a String") - } - - fn fetch_constant(&self, exe: &ExecutableHeapData, index: usize) -> Value { - exe.constants[index] - } - /// Executes an executable using the virtual machine. pub(crate) fn execute( agent: &mut Agent, From 7ef4267e0ffaaa1006ffd4009141f495117d7785 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 18 Oct 2024 15:40:49 +0300 Subject: [PATCH 07/13] More debug print --- nova_vm/src/heap/heap_gc.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index f8af1ec6d..db8b74a1b 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -68,7 +68,6 @@ use crate::{ }; pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option]) { - println!("Performing GC"); let Agent { heap, execution_context_stack, From 35141cf548106837fdf8c3743fd3bb9066ee51cc Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 18 Oct 2024 15:43:23 +0300 Subject: [PATCH 08/13] chore(test262): Update epxectations --- tests/expectations.json | 570 +++++++--------------------------------- tests/metrics.json | 6 +- 2 files changed, 101 insertions(+), 475 deletions(-) diff --git a/tests/expectations.json b/tests/expectations.json index dd738e3dd..f3b1482d8 100644 --- a/tests/expectations.json +++ b/tests/expectations.json @@ -6,99 +6,99 @@ "built-ins/Array/from/mapfn-throws-exception.js": "CRASH", "built-ins/Array/from/proto-from-ctor-realm.js": "FAIL", "built-ins/Array/fromAsync/async-iterable-async-mapped-awaits-once.js": "CRASH", - "built-ins/Array/fromAsync/async-iterable-input-does-not-await-input.js": "UNRESOLVED", + "built-ins/Array/fromAsync/async-iterable-input-does-not-await-input.js": "FAIL", "built-ins/Array/fromAsync/async-iterable-input-iteration-err.js": "CRASH", "built-ins/Array/fromAsync/async-iterable-input.js": "CRASH", - "built-ins/Array/fromAsync/asyncitems-array-add-to-empty.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-array-add-to-singleton.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-array-add.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-array-mutate.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-array-remove.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-arraybuffer.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-arraylike-holes.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-arraylike-length-accessor-throws.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-array-add-to-empty.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-array-add-to-singleton.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-array-add.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-array-mutate.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-array-remove.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-arraybuffer.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-arraylike-holes.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-arraylike-length-accessor-throws.js": "FAIL", "built-ins/Array/fromAsync/asyncitems-arraylike-promise.js": "CRASH", - "built-ins/Array/fromAsync/asyncitems-arraylike-too-long.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-asynciterator-exists.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-asynciterator-not-callable.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-asynciterator-null.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-asynciterator-sync.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-asynciterator-throws.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-bigint.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-boolean.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-function.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-iterator-exists.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-iterator-not-callable.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-iterator-null.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-iterator-promise.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-iterator-throws.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-null-undefined.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-number.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-object-not-arraylike.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-operations.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-string.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-symbol.js": "UNRESOLVED", - "built-ins/Array/fromAsync/asyncitems-uses-intrinsic-iterator-symbols.js": "UNRESOLVED", + "built-ins/Array/fromAsync/asyncitems-arraylike-too-long.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-asynciterator-exists.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-asynciterator-not-callable.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-asynciterator-null.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-asynciterator-sync.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-asynciterator-throws.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-bigint.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-boolean.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-function.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-iterator-exists.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-iterator-not-callable.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-iterator-null.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-iterator-promise.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-iterator-throws.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-null-undefined.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-number.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-object-not-arraylike.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-operations.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-string.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-symbol.js": "FAIL", + "built-ins/Array/fromAsync/asyncitems-uses-intrinsic-iterator-symbols.js": "FAIL", "built-ins/Array/fromAsync/builtin.js": "FAIL", "built-ins/Array/fromAsync/length.js": "FAIL", - "built-ins/Array/fromAsync/mapfn-async-arraylike.js": "UNRESOLVED", + "built-ins/Array/fromAsync/mapfn-async-arraylike.js": "FAIL", "built-ins/Array/fromAsync/mapfn-async-iterable-async.js": "CRASH", - "built-ins/Array/fromAsync/mapfn-async-iterable-sync.js": "UNRESOLVED", - "built-ins/Array/fromAsync/mapfn-async-throws-close-async-iterator.js": "UNRESOLVED", - "built-ins/Array/fromAsync/mapfn-async-throws-close-sync-iterator.js": "UNRESOLVED", - "built-ins/Array/fromAsync/mapfn-async-throws.js": "UNRESOLVED", - "built-ins/Array/fromAsync/mapfn-not-callable.js": "UNRESOLVED", - "built-ins/Array/fromAsync/mapfn-result-awaited-once-per-iteration.js": "UNRESOLVED", - "built-ins/Array/fromAsync/mapfn-sync-arraylike.js": "UNRESOLVED", + "built-ins/Array/fromAsync/mapfn-async-iterable-sync.js": "FAIL", + "built-ins/Array/fromAsync/mapfn-async-throws-close-async-iterator.js": "FAIL", + "built-ins/Array/fromAsync/mapfn-async-throws-close-sync-iterator.js": "FAIL", + "built-ins/Array/fromAsync/mapfn-async-throws.js": "FAIL", + "built-ins/Array/fromAsync/mapfn-not-callable.js": "FAIL", + "built-ins/Array/fromAsync/mapfn-result-awaited-once-per-iteration.js": "FAIL", + "built-ins/Array/fromAsync/mapfn-sync-arraylike.js": "FAIL", "built-ins/Array/fromAsync/mapfn-sync-iterable-async.js": "CRASH", - "built-ins/Array/fromAsync/mapfn-sync-iterable-sync.js": "UNRESOLVED", - "built-ins/Array/fromAsync/mapfn-sync-throws-close-async-iterator.js": "UNRESOLVED", - "built-ins/Array/fromAsync/mapfn-sync-throws-close-sync-iterator.js": "UNRESOLVED", - "built-ins/Array/fromAsync/mapfn-sync-throws.js": "UNRESOLVED", + "built-ins/Array/fromAsync/mapfn-sync-iterable-sync.js": "FAIL", + "built-ins/Array/fromAsync/mapfn-sync-throws-close-async-iterator.js": "FAIL", + "built-ins/Array/fromAsync/mapfn-sync-throws-close-sync-iterator.js": "FAIL", + "built-ins/Array/fromAsync/mapfn-sync-throws.js": "FAIL", "built-ins/Array/fromAsync/name.js": "FAIL", "built-ins/Array/fromAsync/non-iterable-input-does-not-use-array-prototype.js": "CRASH", - "built-ins/Array/fromAsync/non-iterable-input-element-access-err.js": "UNRESOLVED", - "built-ins/Array/fromAsync/non-iterable-input-with-thenable-async-mapped-awaits-callback-result-once.js": "UNRESOLVED", - "built-ins/Array/fromAsync/non-iterable-input-with-thenable-async-mapped-callback-err.js": "UNRESOLVED", - "built-ins/Array/fromAsync/non-iterable-input-with-thenable-element-rejects.js": "UNRESOLVED", - "built-ins/Array/fromAsync/non-iterable-input-with-thenable-sync-mapped-callback-err.js": "UNRESOLVED", - "built-ins/Array/fromAsync/non-iterable-input-with-thenable.js": "UNRESOLVED", - "built-ins/Array/fromAsync/non-iterable-input.js": "UNRESOLVED", - "built-ins/Array/fromAsync/non-iterable-sync-mapped-callback-err.js": "UNRESOLVED", - "built-ins/Array/fromAsync/non-iterable-with-non-promise-thenable.js": "UNRESOLVED", - "built-ins/Array/fromAsync/non-iterable-with-thenable-async-mapped-awaits-once.js": "UNRESOLVED", - "built-ins/Array/fromAsync/non-iterable-with-thenable-awaits-once.js": "UNRESOLVED", - "built-ins/Array/fromAsync/non-iterable-with-thenable-sync-mapped-awaits-once.js": "UNRESOLVED", - "built-ins/Array/fromAsync/non-iterable-with-thenable-then-method-err.js": "UNRESOLVED", + "built-ins/Array/fromAsync/non-iterable-input-element-access-err.js": "FAIL", + "built-ins/Array/fromAsync/non-iterable-input-with-thenable-async-mapped-awaits-callback-result-once.js": "FAIL", + "built-ins/Array/fromAsync/non-iterable-input-with-thenable-async-mapped-callback-err.js": "FAIL", + "built-ins/Array/fromAsync/non-iterable-input-with-thenable-element-rejects.js": "FAIL", + "built-ins/Array/fromAsync/non-iterable-input-with-thenable-sync-mapped-callback-err.js": "FAIL", + "built-ins/Array/fromAsync/non-iterable-input-with-thenable.js": "FAIL", + "built-ins/Array/fromAsync/non-iterable-input.js": "FAIL", + "built-ins/Array/fromAsync/non-iterable-sync-mapped-callback-err.js": "FAIL", + "built-ins/Array/fromAsync/non-iterable-with-non-promise-thenable.js": "FAIL", + "built-ins/Array/fromAsync/non-iterable-with-thenable-async-mapped-awaits-once.js": "FAIL", + "built-ins/Array/fromAsync/non-iterable-with-thenable-awaits-once.js": "FAIL", + "built-ins/Array/fromAsync/non-iterable-with-thenable-sync-mapped-awaits-once.js": "FAIL", + "built-ins/Array/fromAsync/non-iterable-with-thenable-then-method-err.js": "FAIL", "built-ins/Array/fromAsync/not-a-constructor.js": "FAIL", "built-ins/Array/fromAsync/prop-desc.js": "FAIL", - "built-ins/Array/fromAsync/returned-promise-resolves-to-array.js": "UNRESOLVED", - "built-ins/Array/fromAsync/returns-promise.js": "UNRESOLVED", - "built-ins/Array/fromAsync/sync-iterable-input-with-non-promise-thenable.js": "UNRESOLVED", - "built-ins/Array/fromAsync/sync-iterable-input-with-thenable.js": "UNRESOLVED", - "built-ins/Array/fromAsync/sync-iterable-input.js": "UNRESOLVED", - "built-ins/Array/fromAsync/sync-iterable-iteration-err.js": "UNRESOLVED", - "built-ins/Array/fromAsync/sync-iterable-with-thenable-async-mapped-awaits-once.js": "UNRESOLVED", - "built-ins/Array/fromAsync/sync-iterable-with-thenable-async-mapped-callback-err.js": "UNRESOLVED", - "built-ins/Array/fromAsync/sync-iterable-with-thenable-awaits-once.js": "UNRESOLVED", - "built-ins/Array/fromAsync/sync-iterable-with-thenable-element-rejects.js": "UNRESOLVED", - "built-ins/Array/fromAsync/sync-iterable-with-thenable-sync-mapped-awaits-once.js": "UNRESOLVED", - "built-ins/Array/fromAsync/sync-iterable-with-thenable-sync-mapped-callback-err.js": "UNRESOLVED", - "built-ins/Array/fromAsync/sync-iterable-with-thenable-then-method-err.js": "UNRESOLVED", - "built-ins/Array/fromAsync/this-constructor-operations.js": "UNRESOLVED", - "built-ins/Array/fromAsync/this-constructor-with-bad-length-setter.js": "UNRESOLVED", - "built-ins/Array/fromAsync/this-constructor-with-readonly-elements.js": "UNRESOLVED", - "built-ins/Array/fromAsync/this-constructor-with-readonly-length.js": "UNRESOLVED", - "built-ins/Array/fromAsync/this-constructor-with-unsettable-element-closes-async-iterator.js": "UNRESOLVED", - "built-ins/Array/fromAsync/this-constructor-with-unsettable-element-closes-sync-iterator.js": "UNRESOLVED", - "built-ins/Array/fromAsync/this-constructor-with-unsettable-element.js": "UNRESOLVED", - "built-ins/Array/fromAsync/this-constructor.js": "UNRESOLVED", - "built-ins/Array/fromAsync/this-non-constructor.js": "UNRESOLVED", - "built-ins/Array/fromAsync/thisarg-object.js": "UNRESOLVED", - "built-ins/Array/fromAsync/thisarg-omitted-sloppy.js": "UNRESOLVED", - "built-ins/Array/fromAsync/thisarg-omitted-strict.js": "UNRESOLVED", - "built-ins/Array/fromAsync/thisarg-primitive-sloppy.js": "UNRESOLVED", - "built-ins/Array/fromAsync/thisarg-primitive-strict.js": "UNRESOLVED", + "built-ins/Array/fromAsync/returned-promise-resolves-to-array.js": "FAIL", + "built-ins/Array/fromAsync/returns-promise.js": "FAIL", + "built-ins/Array/fromAsync/sync-iterable-input-with-non-promise-thenable.js": "FAIL", + "built-ins/Array/fromAsync/sync-iterable-input-with-thenable.js": "FAIL", + "built-ins/Array/fromAsync/sync-iterable-input.js": "FAIL", + "built-ins/Array/fromAsync/sync-iterable-iteration-err.js": "FAIL", + "built-ins/Array/fromAsync/sync-iterable-with-thenable-async-mapped-awaits-once.js": "FAIL", + "built-ins/Array/fromAsync/sync-iterable-with-thenable-async-mapped-callback-err.js": "FAIL", + "built-ins/Array/fromAsync/sync-iterable-with-thenable-awaits-once.js": "FAIL", + "built-ins/Array/fromAsync/sync-iterable-with-thenable-element-rejects.js": "FAIL", + "built-ins/Array/fromAsync/sync-iterable-with-thenable-sync-mapped-awaits-once.js": "FAIL", + "built-ins/Array/fromAsync/sync-iterable-with-thenable-sync-mapped-callback-err.js": "FAIL", + "built-ins/Array/fromAsync/sync-iterable-with-thenable-then-method-err.js": "FAIL", + "built-ins/Array/fromAsync/this-constructor-operations.js": "FAIL", + "built-ins/Array/fromAsync/this-constructor-with-bad-length-setter.js": "FAIL", + "built-ins/Array/fromAsync/this-constructor-with-readonly-elements.js": "FAIL", + "built-ins/Array/fromAsync/this-constructor-with-readonly-length.js": "FAIL", + "built-ins/Array/fromAsync/this-constructor-with-unsettable-element-closes-async-iterator.js": "FAIL", + "built-ins/Array/fromAsync/this-constructor-with-unsettable-element-closes-sync-iterator.js": "FAIL", + "built-ins/Array/fromAsync/this-constructor-with-unsettable-element.js": "FAIL", + "built-ins/Array/fromAsync/this-constructor.js": "FAIL", + "built-ins/Array/fromAsync/this-non-constructor.js": "FAIL", + "built-ins/Array/fromAsync/thisarg-object.js": "FAIL", + "built-ins/Array/fromAsync/thisarg-omitted-sloppy.js": "FAIL", + "built-ins/Array/fromAsync/thisarg-omitted-strict.js": "FAIL", + "built-ins/Array/fromAsync/thisarg-primitive-sloppy.js": "FAIL", + "built-ins/Array/fromAsync/thisarg-primitive-strict.js": "FAIL", "built-ins/Array/isArray/15.4.3.2-1-10.js": "CRASH", "built-ins/Array/isArray/15.4.3.2-1-9.js": "CRASH", "built-ins/Array/isArray/proxy-revoked.js": "CRASH", @@ -700,7 +700,7 @@ "built-ins/AsyncDisposableStack/prototype/disposeAsync/name.js": "CRASH", "built-ins/AsyncDisposableStack/prototype/disposeAsync/not-a-constructor.js": "FAIL", "built-ins/AsyncDisposableStack/prototype/disposeAsync/prop-desc.js": "CRASH", - "built-ins/AsyncDisposableStack/prototype/disposeAsync/this-does-not-have-internal-asyncdisposablestate-rejects.js": "UNRESOLVED", + "built-ins/AsyncDisposableStack/prototype/disposeAsync/this-does-not-have-internal-asyncdisposablestate-rejects.js": "FAIL", "built-ins/AsyncDisposableStack/prototype/disposeAsync/this-not-object-rejects.js": "FAIL", "built-ins/AsyncDisposableStack/prototype/disposed/does-not-have-asyncdisposablestate-internal-slot.js": "FAIL", "built-ins/AsyncDisposableStack/prototype/disposed/getter.js": "CRASH", @@ -3538,10 +3538,7 @@ "built-ins/Promise/any/resolved-sequence.js": "CRASH", "built-ins/Promise/any/returns-promise.js": "CRASH", "built-ins/Promise/any/species-get-error.js": "CRASH", - "built-ins/Promise/create-resolving-functions-reject.js": "UNRESOLVED", - "built-ins/Promise/create-resolving-functions-resolve.js": "UNRESOLVED", - "built-ins/Promise/exception-after-resolve-in-executor.js": "UNRESOLVED", - "built-ins/Promise/exception-after-resolve-in-thenable-job.js": "UNRESOLVED", + "built-ins/Promise/exception-after-resolve-in-thenable-job.js": "FAIL", "built-ins/Promise/executor-function-extensible.js": "CRASH", "built-ins/Promise/executor-function-length.js": "CRASH", "built-ins/Promise/executor-function-name.js": "CRASH", @@ -3551,8 +3548,6 @@ "built-ins/Promise/get-prototype-abrupt-executor-not-callable.js": "CRASH", "built-ins/Promise/get-prototype-abrupt.js": "CRASH", "built-ins/Promise/proto-from-ctor-realm.js": "FAIL", - "built-ins/Promise/prototype/catch/S25.4.5.1_A3.1_T1.js": "UNRESOLVED", - "built-ins/Promise/prototype/catch/S25.4.5.1_A3.1_T2.js": "UNRESOLVED", "built-ins/Promise/prototype/finally/invokes-then-with-function.js": "CRASH", "built-ins/Promise/prototype/finally/invokes-then-with-non-function.js": "CRASH", "built-ins/Promise/prototype/finally/rejected-observable-then-calls-PromiseResolve.js": "CRASH", @@ -3575,71 +3570,14 @@ "built-ins/Promise/prototype/finally/this-value-then-poisoned.js": "CRASH", "built-ins/Promise/prototype/finally/this-value-then-throws.js": "CRASH", "built-ins/Promise/prototype/finally/this-value-thenable.js": "CRASH", - "built-ins/Promise/prototype/then/S25.4.4_A1.1_T1.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/S25.4.4_A2.1_T1.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/S25.4.4_A2.1_T2.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/S25.4.4_A2.1_T3.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/S25.4.5.3_A4.1_T1.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/S25.4.5.3_A4.1_T2.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/S25.4.5.3_A4.2_T1.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/S25.4.5.3_A4.2_T2.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/S25.4.5.3_A5.1_T1.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/S25.4.5.3_A5.2_T1.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/S25.4.5.3_A5.3_T1.js": "UNRESOLVED", "built-ins/Promise/prototype/then/capability-executor-called-twice.js": "CRASH", "built-ins/Promise/prototype/then/capability-executor-not-callable.js": "CRASH", - "built-ins/Promise/prototype/then/ctor-access-count.js": "UNRESOLVED", + "built-ins/Promise/prototype/then/ctor-access-count.js": "FAIL", "built-ins/Promise/prototype/then/ctor-custom.js": "CRASH", "built-ins/Promise/prototype/then/ctor-null.js": "FAIL", "built-ins/Promise/prototype/then/ctor-poisoned.js": "FAIL", "built-ins/Promise/prototype/then/ctor-throws.js": "CRASH", "built-ins/Promise/prototype/then/deferred-is-resolved-value.js": "CRASH", - "built-ins/Promise/prototype/then/prfm-fulfilled.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/prfm-pending-fulfulled.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/prfm-pending-rejected.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/prfm-rejected.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/reject-pending-fulfilled.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/reject-pending-rejected.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/reject-settled-fulfilled.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/reject-settled-rejected.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-pending-fulfilled-non-obj.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-pending-fulfilled-non-thenable.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-pending-fulfilled-poisoned-then.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-pending-fulfilled-prms-cstm-then.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-pending-fulfilled-self.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-pending-fulfilled-thenable.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-pending-rejected-non-obj.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-pending-rejected-non-thenable.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-pending-rejected-poisoned-then.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-pending-rejected-prms-cstm-then.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-pending-rejected-self.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-pending-rejected-thenable.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-settled-fulfilled-non-obj.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-settled-fulfilled-non-thenable.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-settled-fulfilled-poisoned-then.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-settled-fulfilled-prms-cstm-then.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-settled-fulfilled-self.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-settled-fulfilled-thenable.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-settled-rejected-non-obj.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-settled-rejected-non-thenable.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-settled-rejected-poisoned-then.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-settled-rejected-prms-cstm-then.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-settled-rejected-self.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/resolve-settled-rejected-thenable.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/rxn-handler-fulfilled-invoke-nonstrict.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/rxn-handler-fulfilled-invoke-strict.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/rxn-handler-fulfilled-next-abrupt.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/rxn-handler-fulfilled-next.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/rxn-handler-fulfilled-return-abrupt.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/rxn-handler-fulfilled-return-normal.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/rxn-handler-identity.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/rxn-handler-rejected-invoke-nonstrict.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/rxn-handler-rejected-invoke-strict.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/rxn-handler-rejected-next-abrupt.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/rxn-handler-rejected-next.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/rxn-handler-rejected-return-abrupt.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/rxn-handler-rejected-return-normal.js": "UNRESOLVED", - "built-ins/Promise/prototype/then/rxn-handler-thrower.js": "UNRESOLVED", "built-ins/Promise/race/S25.4.4.3_A2.1_T1.js": "CRASH", "built-ins/Promise/race/S25.4.4.3_A2.2_T1.js": "CRASH", "built-ins/Promise/race/S25.4.4.3_A2.2_T2.js": "CRASH", @@ -3727,16 +3665,6 @@ "built-ins/Promise/race/same-reject-function.js": "CRASH", "built-ins/Promise/race/same-resolve-function.js": "CRASH", "built-ins/Promise/race/species-get-error.js": "CRASH", - "built-ins/Promise/reject-ignored-via-abrupt.js": "UNRESOLVED", - "built-ins/Promise/reject-ignored-via-fn-deferred.js": "UNRESOLVED", - "built-ins/Promise/reject-ignored-via-fn-immed.js": "UNRESOLVED", - "built-ins/Promise/reject-via-abrupt-queue.js": "UNRESOLVED", - "built-ins/Promise/reject-via-abrupt.js": "UNRESOLVED", - "built-ins/Promise/reject-via-fn-deferred-queue.js": "UNRESOLVED", - "built-ins/Promise/reject-via-fn-deferred.js": "UNRESOLVED", - "built-ins/Promise/reject-via-fn-immed-queue.js": "UNRESOLVED", - "built-ins/Promise/reject-via-fn-immed.js": "UNRESOLVED", - "built-ins/Promise/reject/S25.4.4.4_A2.1_T1.js": "UNRESOLVED", "built-ins/Promise/reject/S25.4.4.4_A3.1_T1.js": "CRASH", "built-ins/Promise/reject/capability-executor-called-twice.js": "CRASH", "built-ins/Promise/reject/capability-executor-not-callable.js": "CRASH", @@ -3746,27 +3674,6 @@ "built-ins/Promise/reject/ctx-ctor.js": "CRASH", "built-ins/Promise/reject/ctx-non-ctor.js": "CRASH", "built-ins/Promise/reject/ctx-non-object.js": "CRASH", - "built-ins/Promise/resolve-ignored-via-fn-deferred.js": "UNRESOLVED", - "built-ins/Promise/resolve-ignored-via-fn-immed.js": "UNRESOLVED", - "built-ins/Promise/resolve-non-obj-deferred.js": "UNRESOLVED", - "built-ins/Promise/resolve-non-obj-immed.js": "UNRESOLVED", - "built-ins/Promise/resolve-non-thenable-deferred.js": "UNRESOLVED", - "built-ins/Promise/resolve-non-thenable-immed.js": "UNRESOLVED", - "built-ins/Promise/resolve-poisoned-then-deferred.js": "UNRESOLVED", - "built-ins/Promise/resolve-poisoned-then-immed.js": "UNRESOLVED", - "built-ins/Promise/resolve-prms-cstm-then-deferred.js": "UNRESOLVED", - "built-ins/Promise/resolve-prms-cstm-then-immed.js": "UNRESOLVED", - "built-ins/Promise/resolve-self.js": "UNRESOLVED", - "built-ins/Promise/resolve-thenable-deferred.js": "UNRESOLVED", - "built-ins/Promise/resolve-thenable-immed.js": "UNRESOLVED", - "built-ins/Promise/resolve/S25.4.4.5_A2.2_T1.js": "UNRESOLVED", - "built-ins/Promise/resolve/S25.4.4.5_A2.3_T1.js": "UNRESOLVED", - "built-ins/Promise/resolve/S25.4.4.5_A3.1_T1.js": "UNRESOLVED", - "built-ins/Promise/resolve/S25.4.4.5_A4.1_T1.js": "UNRESOLVED", - "built-ins/Promise/resolve/S25.Promise_resolve_foreign_thenable_1.js": "UNRESOLVED", - "built-ins/Promise/resolve/S25.Promise_resolve_foreign_thenable_2.js": "UNRESOLVED", - "built-ins/Promise/resolve/arg-non-thenable.js": "UNRESOLVED", - "built-ins/Promise/resolve/arg-poisoned-then.js": "UNRESOLVED", "built-ins/Promise/resolve/arg-uniq-ctor.js": "FAIL", "built-ins/Promise/resolve/capability-executor-called-twice.js": "CRASH", "built-ins/Promise/resolve/capability-executor-not-callable.js": "CRASH", @@ -3777,16 +3684,9 @@ "built-ins/Promise/resolve/ctx-non-ctor.js": "CRASH", "built-ins/Promise/resolve/ctx-non-object.js": "CRASH", "built-ins/Promise/resolve/resolve-from-promise-capability.js": "CRASH", - "built-ins/Promise/resolve/resolve-non-obj.js": "UNRESOLVED", - "built-ins/Promise/resolve/resolve-non-thenable.js": "UNRESOLVED", - "built-ins/Promise/resolve/resolve-poisoned-then.js": "UNRESOLVED", "built-ins/Promise/resolve/resolve-self.js": "CRASH", - "built-ins/Promise/resolve/resolve-thenable.js": "UNRESOLVED", - "built-ins/Promise/try/args.js": "UNRESOLVED", "built-ins/Promise/try/ctx-ctor-throws.js": "FAIL", "built-ins/Promise/try/ctx-ctor.js": "CRASH", - "built-ins/Promise/try/return-value.js": "UNRESOLVED", - "built-ins/Promise/try/throws.js": "UNRESOLVED", "built-ins/Promise/withResolvers/ctx-ctor.js": "CRASH", "built-ins/Proxy/apply/arguments-realm.js": "FAIL", "built-ins/Proxy/apply/call-parameters.js": "CRASH", @@ -12773,16 +12673,10 @@ "built-ins/parseInt/S15.1.2.2_A9.4.js": "FAIL", "harness/assert-throws-same-realm.js": "FAIL", "harness/assertRelativeDateMs.js": "CRASH", - "harness/asyncHelpers-asyncTest-returns-undefined.js": "UNRESOLVED", - "harness/asyncHelpers-asyncTest-then-rejects.js": "UNRESOLVED", - "harness/asyncHelpers-asyncTest-then-resolves.js": "UNRESOLVED", "harness/asyncHelpers-throwsAsync-custom-typeerror.js": "CRASH", - "harness/asyncHelpers-throwsAsync-custom.js": "UNRESOLVED", - "harness/asyncHelpers-throwsAsync-func-never-settles.js": "UNRESOLVED", "harness/asyncHelpers-throwsAsync-func-throws-sync.js": "CRASH", "harness/asyncHelpers-throwsAsync-incorrect-ctor.js": "CRASH", "harness/asyncHelpers-throwsAsync-invalid-func.js": "CRASH", - "harness/asyncHelpers-throwsAsync-native.js": "UNRESOLVED", "harness/asyncHelpers-throwsAsync-no-arg.js": "CRASH", "harness/asyncHelpers-throwsAsync-no-error.js": "CRASH", "harness/asyncHelpers-throwsAsync-null.js": "CRASH", @@ -12973,42 +12867,6 @@ "language/destructuring/binding/keyed-destructuring-property-reference-target-evaluation-order-with-bindings.js": "CRASH", "language/destructuring/binding/syntax/recursive-array-and-object-patterns.js": "FAIL", "language/destructuring/binding/typedarray-backed-by-resizable-buffer.js": "CRASH", - "language/eval-code/direct/async-func-decl-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-decl-a-following-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-decl-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-decl-fn-body-cntns-arguments-func-decl-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-decl-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-decl-fn-body-cntns-arguments-lex-bind-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-decl-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-decl-fn-body-cntns-arguments-var-bind-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-decl-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-decl-no-pre-existing-arguments-bindings-are-present-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-named-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-named-a-following-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-named-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-named-a-preceding-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-named-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-named-fn-body-cntns-arguments-func-decl-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-named-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-named-fn-body-cntns-arguments-lex-bind-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-named-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-named-fn-body-cntns-arguments-var-bind-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-named-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-named-no-pre-existing-arguments-bindings-are-present-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-nameless-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-nameless-a-following-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-nameless-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-nameless-a-preceding-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-nameless-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-nameless-fn-body-cntns-arguments-func-decl-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-nameless-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-nameless-fn-body-cntns-arguments-lex-bind-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-nameless-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-nameless-fn-body-cntns-arguments-var-bind-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-nameless-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-func-expr-nameless-no-pre-existing-arguments-bindings-are-present-declare-arguments.js": "UNRESOLVED", "language/eval-code/direct/async-gen-func-decl-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js": "CRASH", "language/eval-code/direct/async-gen-func-decl-a-following-parameter-is-named-arguments-declare-arguments.js": "CRASH", "language/eval-code/direct/async-gen-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": "CRASH", @@ -13057,18 +12915,6 @@ "language/eval-code/direct/async-gen-named-func-expr-fn-body-cntns-arguments-var-bind-declare-arguments.js": "CRASH", "language/eval-code/direct/async-gen-named-func-expr-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js": "CRASH", "language/eval-code/direct/async-gen-named-func-expr-no-pre-existing-arguments-bindings-are-present-declare-arguments.js": "CRASH", - "language/eval-code/direct/async-meth-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-meth-a-following-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-meth-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-meth-a-preceding-parameter-is-named-arguments-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-meth-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-meth-fn-body-cntns-arguments-func-decl-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-meth-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-meth-fn-body-cntns-arguments-lex-bind-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-meth-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-meth-fn-body-cntns-arguments-var-bind-declare-arguments.js": "UNRESOLVED", - "language/eval-code/direct/async-meth-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js": "UNRESOLVED", - "language/eval-code/direct/async-meth-no-pre-existing-arguments-bindings-are-present-declare-arguments.js": "UNRESOLVED", "language/eval-code/direct/cptn-nrml-expr-prim.js": "FAIL", "language/eval-code/direct/gen-func-decl-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js": "CRASH", "language/eval-code/direct/gen-func-decl-a-following-parameter-is-named-arguments-declare-arguments.js": "CRASH", @@ -13576,22 +13422,6 @@ "language/expressions/assignment/target-super-computed-reference.js": "CRASH", "language/expressions/assignment/target-super-identifier-reference-null.js": "CRASH", "language/expressions/assignmenttargettype/simple-basic-identifierreference-await.js": "FAIL", - "language/expressions/async-arrow-function/arrow-returns-promise.js": "UNRESOLVED", - "language/expressions/async-arrow-function/dflt-params-abrupt.js": "UNRESOLVED", - "language/expressions/async-arrow-function/dflt-params-arg-val-not-undefined.js": "UNRESOLVED", - "language/expressions/async-arrow-function/dflt-params-arg-val-undefined.js": "UNRESOLVED", - "language/expressions/async-arrow-function/dflt-params-ref-later.js": "UNRESOLVED", - "language/expressions/async-arrow-function/dflt-params-ref-prior.js": "UNRESOLVED", - "language/expressions/async-arrow-function/dflt-params-ref-self.js": "UNRESOLVED", - "language/expressions/async-arrow-function/dflt-params-trailing-comma.js": "UNRESOLVED", - "language/expressions/async-arrow-function/eval-var-scope-syntax-err.js": "UNRESOLVED", - "language/expressions/async-arrow-function/forbidden-ext/b1/async-arrow-function-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", - "language/expressions/async-arrow-function/forbidden-ext/b1/async-arrow-function-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", - "language/expressions/async-arrow-function/forbidden-ext/b2/async-arrow-function-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", - "language/expressions/async-arrow-function/forbidden-ext/b2/async-arrow-function-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", - "language/expressions/async-arrow-function/forbidden-ext/b2/async-arrow-function-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", - "language/expressions/async-arrow-function/params-trailing-comma-multiple.js": "UNRESOLVED", - "language/expressions/async-arrow-function/params-trailing-comma-single.js": "UNRESOLVED", "language/expressions/async-arrow-function/try-reject-finally-reject.js": "CRASH", "language/expressions/async-arrow-function/try-reject-finally-return.js": "CRASH", "language/expressions/async-arrow-function/try-reject-finally-throw.js": "CRASH", @@ -13603,50 +13433,10 @@ "language/expressions/async-arrow-function/try-throw-finally-throw.js": "CRASH", "language/expressions/async-arrow-function/unscopables-with-in-nested-fn.js": "CRASH", "language/expressions/async-arrow-function/unscopables-with.js": "CRASH", - "language/expressions/async-function/forbidden-ext/b1/async-func-expr-named-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", - "language/expressions/async-function/forbidden-ext/b1/async-func-expr-named-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", - "language/expressions/async-function/forbidden-ext/b1/async-func-expr-nameless-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", - "language/expressions/async-function/forbidden-ext/b1/async-func-expr-nameless-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", - "language/expressions/async-function/forbidden-ext/b2/async-func-expr-named-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", - "language/expressions/async-function/forbidden-ext/b2/async-func-expr-named-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", - "language/expressions/async-function/forbidden-ext/b2/async-func-expr-named-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", - "language/expressions/async-function/forbidden-ext/b2/async-func-expr-nameless-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", - "language/expressions/async-function/forbidden-ext/b2/async-func-expr-nameless-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", - "language/expressions/async-function/forbidden-ext/b2/async-func-expr-nameless-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", - "language/expressions/async-function/named-dflt-params-abrupt.js": "UNRESOLVED", - "language/expressions/async-function/named-dflt-params-arg-val-not-undefined.js": "UNRESOLVED", - "language/expressions/async-function/named-dflt-params-arg-val-undefined.js": "UNRESOLVED", - "language/expressions/async-function/named-dflt-params-ref-later.js": "UNRESOLVED", - "language/expressions/async-function/named-dflt-params-ref-prior.js": "UNRESOLVED", - "language/expressions/async-function/named-dflt-params-ref-self.js": "UNRESOLVED", - "language/expressions/async-function/named-dflt-params-trailing-comma.js": "UNRESOLVED", - "language/expressions/async-function/named-eval-var-scope-syntax-err.js": "UNRESOLVED", - "language/expressions/async-function/named-params-trailing-comma-multiple.js": "UNRESOLVED", - "language/expressions/async-function/named-params-trailing-comma-single.js": "UNRESOLVED", - "language/expressions/async-function/named-reassign-fn-name-in-body-in-arrow.js": "UNRESOLVED", - "language/expressions/async-function/named-reassign-fn-name-in-body-in-eval.js": "UNRESOLVED", - "language/expressions/async-function/named-reassign-fn-name-in-body.js": "UNRESOLVED", - "language/expressions/async-function/named-returns-async-arrow-returns-arguments-from-parent-function.js": "UNRESOLVED", "language/expressions/async-function/named-returns-async-arrow-returns-newtarget.js": "CRASH", - "language/expressions/async-function/named-returns-async-arrow.js": "UNRESOLVED", - "language/expressions/async-function/named-returns-async-function-returns-arguments-from-own-function.js": "UNRESOLVED", "language/expressions/async-function/named-returns-async-function-returns-newtarget.js": "CRASH", - "language/expressions/async-function/named-returns-async-function.js": "UNRESOLVED", - "language/expressions/async-function/named-strict-error-reassign-fn-name-in-body-in-arrow.js": "UNRESOLVED", - "language/expressions/async-function/named-strict-error-reassign-fn-name-in-body-in-eval.js": "UNRESOLVED", - "language/expressions/async-function/named-strict-error-reassign-fn-name-in-body.js": "UNRESOLVED", "language/expressions/async-function/named-unscopables-with-in-nested-fn.js": "CRASH", "language/expressions/async-function/named-unscopables-with.js": "CRASH", - "language/expressions/async-function/nameless-dflt-params-abrupt.js": "UNRESOLVED", - "language/expressions/async-function/nameless-dflt-params-arg-val-not-undefined.js": "UNRESOLVED", - "language/expressions/async-function/nameless-dflt-params-arg-val-undefined.js": "UNRESOLVED", - "language/expressions/async-function/nameless-dflt-params-ref-later.js": "UNRESOLVED", - "language/expressions/async-function/nameless-dflt-params-ref-prior.js": "UNRESOLVED", - "language/expressions/async-function/nameless-dflt-params-ref-self.js": "UNRESOLVED", - "language/expressions/async-function/nameless-dflt-params-trailing-comma.js": "UNRESOLVED", - "language/expressions/async-function/nameless-eval-var-scope-syntax-err.js": "UNRESOLVED", - "language/expressions/async-function/nameless-params-trailing-comma-multiple.js": "UNRESOLVED", - "language/expressions/async-function/nameless-params-trailing-comma-single.js": "UNRESOLVED", "language/expressions/async-function/nameless-unscopables-with-in-nested-fn.js": "CRASH", "language/expressions/async-function/nameless-unscopables-with.js": "CRASH", "language/expressions/async-function/try-reject-finally-reject.js": "CRASH", @@ -14194,25 +13984,14 @@ "language/expressions/async-generator/yield-star-sync-throw.js": "CRASH", "language/expressions/async-generator/yield-thenable-create-resolving-functions-reject.js": "CRASH", "language/expressions/async-generator/yield-thenable-create-resolving-functions-resolve.js": "CRASH", - "language/expressions/await/async-await-interleaved.js": "UNRESOLVED", "language/expressions/await/async-generator-interleaved.js": "CRASH", "language/expressions/await/await-BindingIdentifier-in-global.js": "FAIL", - "language/expressions/await/await-awaits-thenable-not-callable.js": "UNRESOLVED", - "language/expressions/await/await-awaits-thenables-that-throw.js": "UNRESOLVED", - "language/expressions/await/await-awaits-thenables.js": "UNRESOLVED", "language/expressions/await/await-in-function.js": "FAIL", "language/expressions/await/await-in-generator.js": "FAIL", "language/expressions/await/await-in-global.js": "FAIL", "language/expressions/await/await-in-nested-function.js": "FAIL", "language/expressions/await/await-in-nested-generator.js": "FAIL", - "language/expressions/await/await-monkey-patched-promise.js": "UNRESOLVED", - "language/expressions/await/await-non-promise-thenable.js": "UNRESOLVED", - "language/expressions/await/await-non-promise.js": "UNRESOLVED", - "language/expressions/await/await-throws-rejections.js": "UNRESOLVED", "language/expressions/await/for-await-of-interleaved.js": "CRASH", - "language/expressions/await/syntax-await-has-UnaryExpression-with-MultiplicativeExpression.js": "UNRESOLVED", - "language/expressions/await/syntax-await-has-UnaryExpression.js": "UNRESOLVED", - "language/expressions/await/syntax-await-in-ConditionalExpression.js": "UNRESOLVED", "language/expressions/bitwise-and/bigint-non-primitive.js": "CRASH", "language/expressions/bitwise-and/bigint-toprimitive.js": "CRASH", "language/expressions/bitwise-and/bigint-wrapped-values.js": "FAIL", @@ -14395,46 +14174,10 @@ "language/expressions/class/async-gen-method/yield-star-sync-next.js": "CRASH", "language/expressions/class/async-gen-method/yield-star-sync-return.js": "CRASH", "language/expressions/class/async-gen-method/yield-star-sync-throw.js": "CRASH", - "language/expressions/class/async-method-static/dflt-params-abrupt.js": "UNRESOLVED", - "language/expressions/class/async-method-static/dflt-params-arg-val-not-undefined.js": "UNRESOLVED", - "language/expressions/class/async-method-static/dflt-params-arg-val-undefined.js": "UNRESOLVED", - "language/expressions/class/async-method-static/dflt-params-ref-later.js": "UNRESOLVED", - "language/expressions/class/async-method-static/dflt-params-ref-prior.js": "UNRESOLVED", - "language/expressions/class/async-method-static/dflt-params-ref-self.js": "UNRESOLVED", - "language/expressions/class/async-method-static/dflt-params-trailing-comma.js": "UNRESOLVED", - "language/expressions/class/async-method-static/forbidden-ext/b1/cls-expr-async-meth-static-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", - "language/expressions/class/async-method-static/forbidden-ext/b1/cls-expr-async-meth-static-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", - "language/expressions/class/async-method-static/forbidden-ext/b2/cls-expr-async-meth-static-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", - "language/expressions/class/async-method-static/forbidden-ext/b2/cls-expr-async-meth-static-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", - "language/expressions/class/async-method-static/forbidden-ext/b2/cls-expr-async-meth-static-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", - "language/expressions/class/async-method-static/params-trailing-comma-multiple.js": "UNRESOLVED", - "language/expressions/class/async-method-static/params-trailing-comma-single.js": "UNRESOLVED", - "language/expressions/class/async-method-static/returns-async-arrow-returns-arguments-from-parent-function.js": "UNRESOLVED", "language/expressions/class/async-method-static/returns-async-arrow-returns-newtarget.js": "CRASH", - "language/expressions/class/async-method-static/returns-async-arrow.js": "UNRESOLVED", - "language/expressions/class/async-method-static/returns-async-function-returns-arguments-from-own-function.js": "UNRESOLVED", "language/expressions/class/async-method-static/returns-async-function-returns-newtarget.js": "CRASH", - "language/expressions/class/async-method-static/returns-async-function.js": "UNRESOLVED", - "language/expressions/class/async-method/dflt-params-abrupt.js": "UNRESOLVED", - "language/expressions/class/async-method/dflt-params-arg-val-not-undefined.js": "UNRESOLVED", - "language/expressions/class/async-method/dflt-params-arg-val-undefined.js": "UNRESOLVED", - "language/expressions/class/async-method/dflt-params-ref-later.js": "UNRESOLVED", - "language/expressions/class/async-method/dflt-params-ref-prior.js": "UNRESOLVED", - "language/expressions/class/async-method/dflt-params-ref-self.js": "UNRESOLVED", - "language/expressions/class/async-method/dflt-params-trailing-comma.js": "UNRESOLVED", - "language/expressions/class/async-method/forbidden-ext/b1/cls-expr-async-meth-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", - "language/expressions/class/async-method/forbidden-ext/b1/cls-expr-async-meth-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", - "language/expressions/class/async-method/forbidden-ext/b2/cls-expr-async-meth-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", - "language/expressions/class/async-method/forbidden-ext/b2/cls-expr-async-meth-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", - "language/expressions/class/async-method/forbidden-ext/b2/cls-expr-async-meth-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", - "language/expressions/class/async-method/params-trailing-comma-multiple.js": "UNRESOLVED", - "language/expressions/class/async-method/params-trailing-comma-single.js": "UNRESOLVED", - "language/expressions/class/async-method/returns-async-arrow-returns-arguments-from-parent-function.js": "UNRESOLVED", "language/expressions/class/async-method/returns-async-arrow-returns-newtarget.js": "CRASH", - "language/expressions/class/async-method/returns-async-arrow.js": "UNRESOLVED", - "language/expressions/class/async-method/returns-async-function-returns-arguments-from-own-function.js": "UNRESOLVED", "language/expressions/class/async-method/returns-async-function-returns-newtarget.js": "CRASH", - "language/expressions/class/async-method/returns-async-function.js": "UNRESOLVED", "language/expressions/class/class-name-ident-await-escaped.js": "FAIL", "language/expressions/class/class-name-ident-await.js": "FAIL", "language/expressions/class/class-name-ident-let-escaped.js": "CRASH", @@ -16130,17 +15873,11 @@ "language/expressions/class/elements/after-same-line-static-async-gen-static-private-methods-with-fields.js": "CRASH", "language/expressions/class/elements/after-same-line-static-async-gen-static-private-methods.js": "CRASH", "language/expressions/class/elements/after-same-line-static-async-gen-string-literal-names.js": "CRASH", - "language/expressions/class/elements/after-same-line-static-async-method-computed-names.js": "UNRESOLVED", - "language/expressions/class/elements/after-same-line-static-async-method-computed-symbol-names.js": "UNRESOLVED", "language/expressions/class/elements/after-same-line-static-async-method-grammar-privatename-identifier-semantics-stringvalue.js": "CRASH", - "language/expressions/class/elements/after-same-line-static-async-method-literal-names-asi.js": "UNRESOLVED", - "language/expressions/class/elements/after-same-line-static-async-method-literal-names.js": "UNRESOLVED", "language/expressions/class/elements/after-same-line-static-async-method-private-field-usage.js": "CRASH", "language/expressions/class/elements/after-same-line-static-async-method-private-method-getter-usage.js": "CRASH", "language/expressions/class/elements/after-same-line-static-async-method-private-method-usage.js": "CRASH", "language/expressions/class/elements/after-same-line-static-async-method-private-names.js": "CRASH", - "language/expressions/class/elements/after-same-line-static-async-method-rs-field-identifier-initializer.js": "UNRESOLVED", - "language/expressions/class/elements/after-same-line-static-async-method-rs-field-identifier.js": "UNRESOLVED", "language/expressions/class/elements/after-same-line-static-async-method-rs-private-getter-alt.js": "CRASH", "language/expressions/class/elements/after-same-line-static-async-method-rs-private-getter.js": "CRASH", "language/expressions/class/elements/after-same-line-static-async-method-rs-private-method-alt.js": "CRASH", @@ -16816,17 +16553,11 @@ "language/expressions/class/elements/same-line-async-gen-static-private-methods-with-fields.js": "CRASH", "language/expressions/class/elements/same-line-async-gen-static-private-methods.js": "CRASH", "language/expressions/class/elements/same-line-async-gen-string-literal-names.js": "CRASH", - "language/expressions/class/elements/same-line-async-method-computed-names.js": "UNRESOLVED", - "language/expressions/class/elements/same-line-async-method-computed-symbol-names.js": "UNRESOLVED", "language/expressions/class/elements/same-line-async-method-grammar-privatename-identifier-semantics-stringvalue.js": "CRASH", - "language/expressions/class/elements/same-line-async-method-literal-names-asi.js": "UNRESOLVED", - "language/expressions/class/elements/same-line-async-method-literal-names.js": "UNRESOLVED", "language/expressions/class/elements/same-line-async-method-private-field-usage.js": "CRASH", "language/expressions/class/elements/same-line-async-method-private-method-getter-usage.js": "CRASH", "language/expressions/class/elements/same-line-async-method-private-method-usage.js": "CRASH", "language/expressions/class/elements/same-line-async-method-private-names.js": "CRASH", - "language/expressions/class/elements/same-line-async-method-rs-field-identifier-initializer.js": "UNRESOLVED", - "language/expressions/class/elements/same-line-async-method-rs-field-identifier.js": "UNRESOLVED", "language/expressions/class/elements/same-line-async-method-rs-private-getter-alt.js": "CRASH", "language/expressions/class/elements/same-line-async-method-rs-private-getter.js": "CRASH", "language/expressions/class/elements/same-line-async-method-rs-private-method-alt.js": "CRASH", @@ -18583,36 +18314,17 @@ "language/expressions/object/method-definition/async-gen-yield-star-sync-next.js": "CRASH", "language/expressions/object/method-definition/async-gen-yield-star-sync-return.js": "CRASH", "language/expressions/object/method-definition/async-gen-yield-star-sync-throw.js": "CRASH", - "language/expressions/object/method-definition/async-meth-dflt-params-abrupt.js": "UNRESOLVED", - "language/expressions/object/method-definition/async-meth-dflt-params-arg-val-not-undefined.js": "UNRESOLVED", - "language/expressions/object/method-definition/async-meth-dflt-params-arg-val-undefined.js": "UNRESOLVED", - "language/expressions/object/method-definition/async-meth-dflt-params-ref-later.js": "UNRESOLVED", - "language/expressions/object/method-definition/async-meth-dflt-params-ref-prior.js": "UNRESOLVED", - "language/expressions/object/method-definition/async-meth-dflt-params-ref-self.js": "UNRESOLVED", - "language/expressions/object/method-definition/async-meth-dflt-params-trailing-comma.js": "UNRESOLVED", - "language/expressions/object/method-definition/async-meth-eval-var-scope-syntax-err.js": "UNRESOLVED", - "language/expressions/object/method-definition/async-meth-params-trailing-comma-multiple.js": "UNRESOLVED", - "language/expressions/object/method-definition/async-meth-params-trailing-comma-single.js": "UNRESOLVED", - "language/expressions/object/method-definition/async-returns-async-arrow-returns-arguments-from-parent-function.js": "UNRESOLVED", "language/expressions/object/method-definition/async-returns-async-arrow-returns-newtarget.js": "CRASH", - "language/expressions/object/method-definition/async-returns-async-arrow.js": "UNRESOLVED", - "language/expressions/object/method-definition/async-returns-async-function-returns-arguments-from-own-function.js": "UNRESOLVED", "language/expressions/object/method-definition/async-returns-async-function-returns-newtarget.js": "CRASH", - "language/expressions/object/method-definition/async-returns-async-function.js": "UNRESOLVED", "language/expressions/object/method-definition/async-super-call-body.js": "CRASH", "language/expressions/object/method-definition/async-super-call-param.js": "CRASH", "language/expressions/object/method-definition/early-errors-object-async-method-duplicate-parameters.js": "FAIL", "language/expressions/object/method-definition/early-errors-object-method-duplicate-parameters.js": "FAIL", "language/expressions/object/method-definition/forbidden-ext/b1/async-gen-meth-forbidden-ext-direct-access-prop-arguments.js": "CRASH", "language/expressions/object/method-definition/forbidden-ext/b1/async-gen-meth-forbidden-ext-direct-access-prop-caller.js": "CRASH", - "language/expressions/object/method-definition/forbidden-ext/b1/async-meth-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", - "language/expressions/object/method-definition/forbidden-ext/b1/async-meth-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", "language/expressions/object/method-definition/forbidden-ext/b2/async-gen-meth-forbidden-ext-indirect-access-own-prop-caller-get.js": "CRASH", "language/expressions/object/method-definition/forbidden-ext/b2/async-gen-meth-forbidden-ext-indirect-access-own-prop-caller-value.js": "CRASH", "language/expressions/object/method-definition/forbidden-ext/b2/async-gen-meth-forbidden-ext-indirect-access-prop-caller.js": "CRASH", - "language/expressions/object/method-definition/forbidden-ext/b2/async-meth-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", - "language/expressions/object/method-definition/forbidden-ext/b2/async-meth-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", - "language/expressions/object/method-definition/forbidden-ext/b2/async-meth-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", "language/expressions/object/method-definition/gen-meth-dflt-params-abrupt.js": "CRASH", "language/expressions/object/method-definition/gen-meth-dflt-params-ref-later.js": "CRASH", "language/expressions/object/method-definition/gen-meth-dflt-params-ref-self.js": "CRASH", @@ -18645,13 +18357,8 @@ "language/expressions/object/setter-super-prop.js": "CRASH", "language/expressions/optional-chaining/call-expression.js": "CRASH", "language/expressions/optional-chaining/iteration-statement-for-await-of.js": "CRASH", - "language/expressions/optional-chaining/member-expression-async-identifier.js": "UNRESOLVED", - "language/expressions/optional-chaining/member-expression-async-literal.js": "UNRESOLVED", - "language/expressions/optional-chaining/member-expression-async-this.js": "UNRESOLVED", "language/expressions/optional-chaining/member-expression.js": "CRASH", "language/expressions/optional-chaining/new-target-optional-call.js": "CRASH", - "language/expressions/optional-chaining/optional-chain-async-optional-chain-square-brackets.js": "UNRESOLVED", - "language/expressions/optional-chaining/optional-chain-async-square-brackets.js": "UNRESOLVED", "language/expressions/optional-chaining/super-property-optional-call.js": "CRASH", "language/expressions/postfix-decrement/S11.3.2_A5_T1.js": "CRASH", "language/expressions/postfix-decrement/S11.3.2_A5_T2.js": "CRASH", @@ -19156,37 +18863,9 @@ "language/statementList/fn-block-with-labels.js": "CRASH", "language/statementList/fn-let-declaration.js": "FAIL", "language/statements/async-function/cptn-decl.js": "FAIL", - "language/statements/async-function/dflt-params-abrupt.js": "UNRESOLVED", - "language/statements/async-function/dflt-params-arg-val-not-undefined.js": "UNRESOLVED", - "language/statements/async-function/dflt-params-arg-val-undefined.js": "UNRESOLVED", - "language/statements/async-function/dflt-params-ref-later.js": "UNRESOLVED", - "language/statements/async-function/dflt-params-ref-prior.js": "UNRESOLVED", - "language/statements/async-function/dflt-params-ref-self.js": "UNRESOLVED", - "language/statements/async-function/dflt-params-trailing-comma.js": "UNRESOLVED", - "language/statements/async-function/eval-var-scope-syntax-err.js": "UNRESOLVED", - "language/statements/async-function/evaluation-body-that-returns-after-await.js": "UNRESOLVED", - "language/statements/async-function/evaluation-body-that-returns.js": "UNRESOLVED", - "language/statements/async-function/evaluation-body-that-throws-after-await.js": "UNRESOLVED", - "language/statements/async-function/evaluation-body-that-throws.js": "UNRESOLVED", - "language/statements/async-function/evaluation-default-that-throws.js": "UNRESOLVED", - "language/statements/async-function/evaluation-mapped-arguments.js": "UNRESOLVED", - "language/statements/async-function/evaluation-this-value-global.js": "UNRESOLVED", - "language/statements/async-function/evaluation-this-value-passed.js": "UNRESOLVED", - "language/statements/async-function/evaluation-unmapped-arguments.js": "UNRESOLVED", - "language/statements/async-function/forbidden-ext/b1/async-func-decl-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", - "language/statements/async-function/forbidden-ext/b1/async-func-decl-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", - "language/statements/async-function/forbidden-ext/b2/async-func-decl-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", - "language/statements/async-function/forbidden-ext/b2/async-func-decl-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", - "language/statements/async-function/forbidden-ext/b2/async-func-decl-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", - "language/statements/async-function/params-trailing-comma-multiple.js": "UNRESOLVED", - "language/statements/async-function/params-trailing-comma-single.js": "UNRESOLVED", - "language/statements/async-function/returns-async-arrow-returns-arguments-from-parent-function.js": "UNRESOLVED", + "language/statements/async-function/evaluation-mapped-arguments.js": "FAIL", "language/statements/async-function/returns-async-arrow-returns-newtarget.js": "CRASH", - "language/statements/async-function/returns-async-arrow.js": "UNRESOLVED", - "language/statements/async-function/returns-async-function-returns-arguments-from-own-function.js": "UNRESOLVED", "language/statements/async-function/returns-async-function-returns-newtarget.js": "CRASH", - "language/statements/async-function/returns-async-function.js": "UNRESOLVED", - "language/statements/async-function/syntax-declaration.js": "UNRESOLVED", "language/statements/async-function/try-reject-finally-reject.js": "CRASH", "language/statements/async-function/try-reject-finally-return.js": "CRASH", "language/statements/async-function/try-reject-finally-throw.js": "CRASH", @@ -19636,46 +19315,10 @@ "language/statements/class/async-gen-method/yield-star-sync-next.js": "CRASH", "language/statements/class/async-gen-method/yield-star-sync-return.js": "CRASH", "language/statements/class/async-gen-method/yield-star-sync-throw.js": "CRASH", - "language/statements/class/async-method-static/dflt-params-abrupt.js": "UNRESOLVED", - "language/statements/class/async-method-static/dflt-params-arg-val-not-undefined.js": "UNRESOLVED", - "language/statements/class/async-method-static/dflt-params-arg-val-undefined.js": "UNRESOLVED", - "language/statements/class/async-method-static/dflt-params-ref-later.js": "UNRESOLVED", - "language/statements/class/async-method-static/dflt-params-ref-prior.js": "UNRESOLVED", - "language/statements/class/async-method-static/dflt-params-ref-self.js": "UNRESOLVED", - "language/statements/class/async-method-static/dflt-params-trailing-comma.js": "UNRESOLVED", - "language/statements/class/async-method-static/forbidden-ext/b1/cls-decl-async-meth-static-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", - "language/statements/class/async-method-static/forbidden-ext/b1/cls-decl-async-meth-static-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", - "language/statements/class/async-method-static/forbidden-ext/b2/cls-decl-async-meth-static-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", - "language/statements/class/async-method-static/forbidden-ext/b2/cls-decl-async-meth-static-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", - "language/statements/class/async-method-static/forbidden-ext/b2/cls-decl-async-meth-static-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", - "language/statements/class/async-method-static/params-trailing-comma-multiple.js": "UNRESOLVED", - "language/statements/class/async-method-static/params-trailing-comma-single.js": "UNRESOLVED", - "language/statements/class/async-method-static/returns-async-arrow-returns-arguments-from-parent-function.js": "UNRESOLVED", "language/statements/class/async-method-static/returns-async-arrow-returns-newtarget.js": "CRASH", - "language/statements/class/async-method-static/returns-async-arrow.js": "UNRESOLVED", - "language/statements/class/async-method-static/returns-async-function-returns-arguments-from-own-function.js": "UNRESOLVED", "language/statements/class/async-method-static/returns-async-function-returns-newtarget.js": "CRASH", - "language/statements/class/async-method-static/returns-async-function.js": "UNRESOLVED", - "language/statements/class/async-method/dflt-params-abrupt.js": "UNRESOLVED", - "language/statements/class/async-method/dflt-params-arg-val-not-undefined.js": "UNRESOLVED", - "language/statements/class/async-method/dflt-params-arg-val-undefined.js": "UNRESOLVED", - "language/statements/class/async-method/dflt-params-ref-later.js": "UNRESOLVED", - "language/statements/class/async-method/dflt-params-ref-prior.js": "UNRESOLVED", - "language/statements/class/async-method/dflt-params-ref-self.js": "UNRESOLVED", - "language/statements/class/async-method/dflt-params-trailing-comma.js": "UNRESOLVED", - "language/statements/class/async-method/forbidden-ext/b1/cls-decl-async-meth-forbidden-ext-direct-access-prop-arguments.js": "UNRESOLVED", - "language/statements/class/async-method/forbidden-ext/b1/cls-decl-async-meth-forbidden-ext-direct-access-prop-caller.js": "UNRESOLVED", - "language/statements/class/async-method/forbidden-ext/b2/cls-decl-async-meth-forbidden-ext-indirect-access-own-prop-caller-get.js": "UNRESOLVED", - "language/statements/class/async-method/forbidden-ext/b2/cls-decl-async-meth-forbidden-ext-indirect-access-own-prop-caller-value.js": "UNRESOLVED", - "language/statements/class/async-method/forbidden-ext/b2/cls-decl-async-meth-forbidden-ext-indirect-access-prop-caller.js": "UNRESOLVED", - "language/statements/class/async-method/params-trailing-comma-multiple.js": "UNRESOLVED", - "language/statements/class/async-method/params-trailing-comma-single.js": "UNRESOLVED", - "language/statements/class/async-method/returns-async-arrow-returns-arguments-from-parent-function.js": "UNRESOLVED", "language/statements/class/async-method/returns-async-arrow-returns-newtarget.js": "CRASH", - "language/statements/class/async-method/returns-async-arrow.js": "UNRESOLVED", - "language/statements/class/async-method/returns-async-function-returns-arguments-from-own-function.js": "UNRESOLVED", "language/statements/class/async-method/returns-async-function-returns-newtarget.js": "CRASH", - "language/statements/class/async-method/returns-async-function.js": "UNRESOLVED", "language/statements/class/class-name-ident-await-escaped.js": "FAIL", "language/statements/class/class-name-ident-await.js": "FAIL", "language/statements/class/class-name-ident-let-escaped.js": "CRASH", @@ -21379,17 +21022,11 @@ "language/statements/class/elements/after-same-line-static-async-gen-static-private-methods-with-fields.js": "CRASH", "language/statements/class/elements/after-same-line-static-async-gen-static-private-methods.js": "CRASH", "language/statements/class/elements/after-same-line-static-async-gen-string-literal-names.js": "CRASH", - "language/statements/class/elements/after-same-line-static-async-method-computed-names.js": "UNRESOLVED", - "language/statements/class/elements/after-same-line-static-async-method-computed-symbol-names.js": "UNRESOLVED", "language/statements/class/elements/after-same-line-static-async-method-grammar-privatename-identifier-semantics-stringvalue.js": "CRASH", - "language/statements/class/elements/after-same-line-static-async-method-literal-names-asi.js": "UNRESOLVED", - "language/statements/class/elements/after-same-line-static-async-method-literal-names.js": "UNRESOLVED", "language/statements/class/elements/after-same-line-static-async-method-private-field-usage.js": "CRASH", "language/statements/class/elements/after-same-line-static-async-method-private-method-getter-usage.js": "CRASH", "language/statements/class/elements/after-same-line-static-async-method-private-method-usage.js": "CRASH", "language/statements/class/elements/after-same-line-static-async-method-private-names.js": "CRASH", - "language/statements/class/elements/after-same-line-static-async-method-rs-field-identifier-initializer.js": "UNRESOLVED", - "language/statements/class/elements/after-same-line-static-async-method-rs-field-identifier.js": "UNRESOLVED", "language/statements/class/elements/after-same-line-static-async-method-rs-private-getter-alt.js": "CRASH", "language/statements/class/elements/after-same-line-static-async-method-rs-private-getter.js": "CRASH", "language/statements/class/elements/after-same-line-static-async-method-rs-private-method-alt.js": "CRASH", @@ -22143,17 +21780,11 @@ "language/statements/class/elements/same-line-async-gen-static-private-methods-with-fields.js": "CRASH", "language/statements/class/elements/same-line-async-gen-static-private-methods.js": "CRASH", "language/statements/class/elements/same-line-async-gen-string-literal-names.js": "CRASH", - "language/statements/class/elements/same-line-async-method-computed-names.js": "UNRESOLVED", - "language/statements/class/elements/same-line-async-method-computed-symbol-names.js": "UNRESOLVED", "language/statements/class/elements/same-line-async-method-grammar-privatename-identifier-semantics-stringvalue.js": "CRASH", - "language/statements/class/elements/same-line-async-method-literal-names-asi.js": "UNRESOLVED", - "language/statements/class/elements/same-line-async-method-literal-names.js": "UNRESOLVED", "language/statements/class/elements/same-line-async-method-private-field-usage.js": "CRASH", "language/statements/class/elements/same-line-async-method-private-method-getter-usage.js": "CRASH", "language/statements/class/elements/same-line-async-method-private-method-usage.js": "CRASH", "language/statements/class/elements/same-line-async-method-private-names.js": "CRASH", - "language/statements/class/elements/same-line-async-method-rs-field-identifier-initializer.js": "UNRESOLVED", - "language/statements/class/elements/same-line-async-method-rs-field-identifier.js": "UNRESOLVED", "language/statements/class/elements/same-line-async-method-rs-private-getter-alt.js": "CRASH", "language/statements/class/elements/same-line-async-method-rs-private-getter.js": "CRASH", "language/statements/class/elements/same-line-async-method-rs-private-method-alt.js": "CRASH", @@ -25042,20 +24673,15 @@ "staging/explicit-resource-management/Symbol/dispose/cross-realm.js": "FAIL", "staging/explicit-resource-management/Symbol/dispose/prop-desc.js": "FAIL", "staging/explicit-resource-management/async-disposable-stack-adopt-and-defer-not-callable.js": "FAIL", - "staging/explicit-resource-management/async-disposable-stack-adopt-on-disposed-stack.js": "UNRESOLVED", - "staging/explicit-resource-management/async-disposable-stack-adopt.js": "UNRESOLVED", - "staging/explicit-resource-management/async-disposable-stack-async-dispose-symbol-throws.js": "UNRESOLVED", + "staging/explicit-resource-management/async-disposable-stack-adopt.js": "FAIL", + "staging/explicit-resource-management/async-disposable-stack-async-dispose-symbol-throws.js": "FAIL", "staging/explicit-resource-management/async-disposable-stack-constructor-and-prototype.js": "CRASH", - "staging/explicit-resource-management/async-disposable-stack-defer-on-disposed-stack.js": "UNRESOLVED", - "staging/explicit-resource-management/async-disposable-stack-defer.js": "UNRESOLVED", - "staging/explicit-resource-management/async-disposable-stack-dispose-on-disposed-stack.js": "UNRESOLVED", - "staging/explicit-resource-management/async-disposable-stack-dispose-sync-calls.js": "UNRESOLVED", - "staging/explicit-resource-management/async-disposable-stack-dispose-throws-suppressed-error.js": "UNRESOLVED", - "staging/explicit-resource-management/async-disposable-stack-dispose.js": "UNRESOLVED", - "staging/explicit-resource-management/async-disposable-stack-disposed-getter.js": "UNRESOLVED", - "staging/explicit-resource-management/async-disposable-stack-move-on-disposed-stack.js": "UNRESOLVED", + "staging/explicit-resource-management/async-disposable-stack-defer.js": "FAIL", + "staging/explicit-resource-management/async-disposable-stack-dispose-on-disposed-stack.js": "FAIL", + "staging/explicit-resource-management/async-disposable-stack-dispose-sync-calls.js": "FAIL", + "staging/explicit-resource-management/async-disposable-stack-dispose-throws-suppressed-error.js": "FAIL", + "staging/explicit-resource-management/async-disposable-stack-dispose.js": "FAIL", "staging/explicit-resource-management/async-disposable-stack-move.js": "FAIL", - "staging/explicit-resource-management/async-disposable-stack-use-on-disposed-stack.js": "UNRESOLVED", "staging/explicit-resource-management/async-disposable-stack-use.js": "FAIL", "staging/explicit-resource-management/await-using-dispose-method-throws-after-await.js": "CRASH", "staging/explicit-resource-management/await-using-dispose-method-throws.js": "CRASH", @@ -25069,12 +24695,12 @@ "staging/explicit-resource-management/await-using-in-switch-case-block.js": "CRASH", "staging/explicit-resource-management/await-using-mixed-async-throws.js": "CRASH", "staging/explicit-resource-management/await-using-mixed-sync-throws.js": "CRASH", - "staging/explicit-resource-management/await-using-mixed-throws-suppressed-error-from-sync-and-async-disposals.js": "UNRESOLVED", - "staging/explicit-resource-management/await-using-mixed-throws-suppressed-error.js": "UNRESOLVED", + "staging/explicit-resource-management/await-using-mixed-throws-suppressed-error-from-sync-and-async-disposals.js": "FAIL", + "staging/explicit-resource-management/await-using-mixed-throws-suppressed-error.js": "FAIL", "staging/explicit-resource-management/await-using-throws-from-symbol-dispose.js": "CRASH", - "staging/explicit-resource-management/await-using-throws-suppressed-error-from-disposals.js": "UNRESOLVED", - "staging/explicit-resource-management/await-using-throws-suppressed-error-from-try-and-disposal.js": "UNRESOLVED", - "staging/explicit-resource-management/await-using-throws-suppressed-error-of-undefined.js": "UNRESOLVED", + "staging/explicit-resource-management/await-using-throws-suppressed-error-from-disposals.js": "FAIL", + "staging/explicit-resource-management/await-using-throws-suppressed-error-from-try-and-disposal.js": "FAIL", + "staging/explicit-resource-management/await-using-throws-suppressed-error-of-undefined.js": "FAIL", "staging/explicit-resource-management/await-using-user-code-throws-after.js": "CRASH", "staging/explicit-resource-management/await-using-user-code-throws-before.js": "CRASH", "staging/explicit-resource-management/await-using-with-no-async-dispose-method.js": "CRASH", diff --git a/tests/metrics.json b/tests/metrics.json index ebd0aee5a..be7aeed90 100644 --- a/tests/metrics.json +++ b/tests/metrics.json @@ -1,11 +1,11 @@ { "results": { "crash": 16285, - "fail": 8164, - "pass": 20327, + "fail": 8262, + "pass": 20701, "skip": 40, "timeout": 3, - "unresolved": 472 + "unresolved": 0 }, "total": 45291 } \ No newline at end of file From 176eebd61b0845aaa12d2f34114cb07cf1261437 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 18 Oct 2024 15:52:09 +0300 Subject: [PATCH 09/13] Remove old comment --- nova_vm/src/ecmascript/builtins/global_object.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/global_object.rs b/nova_vm/src/ecmascript/builtins/global_object.rs index 994f25b32..4c63b452b 100644 --- a/nova_vm/src/ecmascript/builtins/global_object.rs +++ b/nova_vm/src/ecmascript/builtins/global_object.rs @@ -331,10 +331,6 @@ pub fn perform_eval( // 29. If result is a normal completion, then let result = if result.is_ok() { - // TODO: We have a problem here. First, we're forcing an extra heap - // allocation for no good reason. - // Second, this executable is not accessible by the heap and will thus - // break if garbage collection triggers within the eval. let exe = Executable::compile_eval_body(agent, &script.body); // a. Set result to Completion(Evaluation of body). // 30. If result is a normal completion and result.[[Value]] is empty, then From 56c365c551785443538ec3161518f2edd1a3e0e5 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 18 Oct 2024 16:21:35 +0300 Subject: [PATCH 10/13] Fix heap.rs imports --- nova_vm/src/heap.rs | 46 ++++++++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 031201d32..f6ce54e35 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -42,32 +42,25 @@ use crate::ecmascript::builtins::{ weak_map::data::WeakMapHeapData, weak_ref::data::WeakRefHeapData, weak_set::data::WeakSetHeapData, }; -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, - }, - scripts_and_modules::source_code::SourceCodeHeapData, - types::{ - bigint::HeapBigInt, BuiltinConstructorHeapData, HeapNumber, HeapString, OrdinaryObject, - BUILTIN_STRINGS_LIST, - }, -}; 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, @@ -80,10 +73,15 @@ use crate::{ ArrayHeapData, }, 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, From c91095b323f79134cf06a32246202d47e27c2642 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 18 Oct 2024 16:24:05 +0300 Subject: [PATCH 11/13] clippy --- nova_vm/src/engine/bytecode/vm.rs | 50 +++++++++++++++---------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index d4bd2e8df..c709baf8d 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -1164,33 +1164,31 @@ 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 { - 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, - func, - Value::Undefined, - Some(ArgumentsList(&args)), - )?); - } + vm.result = Some(call( + agent, + func, + Value::Undefined, + Some(ArgumentsList(&args)), + )?); } } Instruction::EvaluateCall => { From f5716cb2375b0783d3f3f564019387c7680ba807 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 18 Oct 2024 16:37:29 +0300 Subject: [PATCH 12/13] Remove unnecessary bytecode reference from Script heap data --- .../src/ecmascript/scripts_and_modules/script.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/nova_vm/src/ecmascript/scripts_and_modules/script.rs b/nova_vm/src/ecmascript/scripts_and_modules/script.rs index 408df313f..4d7cd1e76 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules/script.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules/script.rs @@ -140,9 +140,6 @@ pub struct Script { /// parts. pub(crate) ecmascript_code: ManuallyDrop>, - /// Stores the compiled bytecode of a Script. - pub(crate) compiled_bytecode: Option, - /// ### \[\[LoadedModules]] /// /// A map from the specifier strings imported by this script to the @@ -172,28 +169,24 @@ impl HeapMarkAndSweep for Script { let Self { realm, ecmascript_code: _, - compiled_bytecode, loaded_modules: _, host_defined: _, source_code, } = self; realm.mark_values(queues); source_code.mark_values(queues); - compiled_bytecode.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { let Self { realm, ecmascript_code: _, - compiled_bytecode, loaded_modules: _, host_defined: _, source_code, } = self; realm.sweep_values(compactions); source_code.sweep_values(compactions); - compiled_bytecode.sweep_values(compactions); } } @@ -246,7 +239,6 @@ pub fn parse_script( // [[HostDefined]]: hostDefined, host_defined, source_code, - compiled_bytecode: None, }) // } } @@ -307,11 +299,14 @@ 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 bytecode = Executable::compile_script(agent, script); - agent[script].compiled_bytecode = Some(bytecode); // 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, bytecode, 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()) }; From e0b6c253f4a41b6cc7dfd16e006a4b878883dbe8 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Fri, 18 Oct 2024 16:37:53 +0300 Subject: [PATCH 13/13] fmt --- nova_vm/src/engine/bytecode/vm.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index c709baf8d..72ec8c1b1 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -1167,8 +1167,7 @@ impl Vm { } 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 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