From e85e07cd03be413bff19cf22b5cb17d000dbe795 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 19 Apr 2026 14:37:52 +0300 Subject: [PATCH 1/7] feat(vm): Singleton VM --- .../operations_on_objects.rs | 13 +- nova_vm/src/ecmascript/builtins/arguments.rs | 2 +- .../ecmascript/builtins/builtin_function.rs | 1 + .../async_function_objects/await_reaction.rs | 34 +- .../async_generator_objects.rs | 31 +- .../async_generator_abstract_operations.rs | 29 +- .../generator_objects.rs | 39 +- .../builtins/ecmascript_function.rs | 13 +- .../src/ecmascript/builtins/global_object.rs | 22 +- nova_vm/src/ecmascript/execution/agent.rs | 208 +- .../ecmascript/execution/execution_context.rs | 38 +- .../source_text_module_records.rs | 48 +- .../ecmascript/scripts_and_modules/script.rs | 21 +- .../function_definitions.rs | 48 +- .../src/ecmascript/types/language/object.rs | 11 +- .../src/ecmascript/types/spec/reference.rs | 22 +- .../src/engine/bytecode/bytecode_compiler.rs | 164 +- .../bytecode/bytecode_compiler/assignment.rs | 35 +- .../bytecode_compiler/for_in_of_statement.rs | 48 +- nova_vm/src/engine/bytecode/executable.rs | 169 +- nova_vm/src/engine/bytecode/instructions.rs | 55 +- nova_vm/src/engine/bytecode/iterator.rs | 7 +- nova_vm/src/engine/bytecode/vm.rs | 856 +++++---- .../src/engine/bytecode/vm/binding_methods.rs | 483 ++--- .../bytecode/vm/execute_instructions.rs | 1695 ++++++----------- 25 files changed, 1867 insertions(+), 2225 deletions(-) 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 69158b974..3e07b83ad 100644 --- a/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs +++ b/nova_vm/src/ecmascript/abstract_operations/operations_on_objects.rs @@ -22,7 +22,7 @@ use crate::{ }, engine::{ Bindable, GcScope, NoGcScope, Rootable, Scopable, ScopableCollection, Scoped, - ScopedCollection, Vm, instanceof_operator, + ScopedCollection, instanceof_operator, }, heap::{ArenaAccess, ElementDescriptor, ObjectEntry, WellKnownSymbols}, }; @@ -2708,7 +2708,7 @@ pub(crate) fn initialize_instance_elements<'a>( // a. Perform ? DefineField(O, fieldRecord). // 5. Return unused. let constructor_data = constructor.get(agent); - if let Some(bytecode) = constructor_data.compiled_initializer_bytecode { + if let Some(executable) = constructor_data.compiled_initializer_bytecode { // Note: The code here looks quite a bit different from what the spec // says. For one, the spec is bugged and doesn't consider default // constructors at all. Second, we compile field initializers into @@ -2729,6 +2729,12 @@ pub(crate) fn initialize_instance_elements<'a>( let decl_env = new_class_field_initializer_environment(agent, f, o, outer_env, gc.nogc()); agent.push_execution_context(ExecutionContext { ecmascript_code: Some(ECMAScriptCodeEvaluationState { + ip: 0, + executable: Some(executable.unbind()), + stack_base: 0, + iterator_stack_base: 0, + reference_stack_base: 0, + exception_handler_stack_base: 0, lexical_environment: Environment::Function(decl_env.unbind()), variable_environment: Environment::Function(decl_env.unbind()), private_environment: outer_priv_env.unbind(), @@ -2739,8 +2745,7 @@ pub(crate) fn initialize_instance_elements<'a>( realm: constructor.get(agent).realm.unbind(), script_or_module: None, }); - let bytecode = bytecode.scope(agent, gc.nogc()); - let result = Vm::execute(agent, bytecode, None, gc).into_js_result(); + let result = agent.execute(None, gc).into_js_result(); agent.pop_execution_context(); result?; } diff --git a/nova_vm/src/ecmascript/builtins/arguments.rs b/nova_vm/src/ecmascript/builtins/arguments.rs index 401f480b9..506b93b64 100644 --- a/nova_vm/src/ecmascript/builtins/arguments.rs +++ b/nova_vm/src/ecmascript/builtins/arguments.rs @@ -103,7 +103,7 @@ pub struct UnmappedArguments<'a>(OrdinaryObject<'a>); /// ordinary object. pub(crate) fn create_unmapped_arguments_object<'a, 'b>( agent: &mut Agent, - arguments_list: &ScopedArgumentsList<'b>, + arguments_list: ScopedArgumentsList<'b>, gc: NoGcScope<'a, 'b>, ) -> Result, TryReserveError> { // 1. Let len be the number of elements in argumentsList. diff --git a/nova_vm/src/ecmascript/builtins/builtin_function.rs b/nova_vm/src/ecmascript/builtins/builtin_function.rs index f42c14c3c..1c74c84a7 100644 --- a/nova_vm/src/ecmascript/builtins/builtin_function.rs +++ b/nova_vm/src/ecmascript/builtins/builtin_function.rs @@ -251,6 +251,7 @@ impl<'value> Deref for ArgumentsList<'_, 'value> { /// /// The arguments can be accessed through the Agent for the duration of the /// function call. +#[derive(Clone)] pub struct ScopedArgumentsList<'scope> { index: u32, value: PhantomData>, 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 ee2f0b2d6..d6136da75 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 @@ -7,9 +7,7 @@ use crate::{ Agent, ECMAScriptFunction, ExecutionContext, PromiseCapability, PromiseReactionHandler, PromiseReactionType, SourceTextModule, Value, inner_promise_then, }, - engine::{ - Bindable, Executable, ExecutionResult, GcScope, Scopable, SuspendedVm, bindable_handle, - }, + engine::{Bindable, ExecutionResult, GcScope, Scopable, SuspendedVm, bindable_handle}, heap::{ ArenaAccess, ArenaAccessMut, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, WorkQueues, arena_vec_access, {BaseIndex, index_handle}, @@ -38,7 +36,6 @@ impl AwaitReaction<'_> { let record = reaction.get_mut(agent); let execution_context = record.execution_context.take().unwrap(); let vm = record.vm.take().unwrap(); - let async_function = record.async_executable.unwrap().bind(gc.nogc()); agent.push_execution_context(execution_context); let reaction = reaction.scope(agent, gc.nogc()); @@ -49,18 +46,14 @@ impl AwaitReaction<'_> { // ThrowCompletion(reason) as the result of the operation that // suspended it. let execution_result = match reaction_type { - PromiseReactionType::Fulfill => { - let executable = async_function.get_executable(agent).scope(agent, gc.nogc()); - vm.resume(agent, executable, value.unbind(), gc.reborrow()) - .unbind() - .bind(gc.nogc()) - } - PromiseReactionType::Reject => { - let executable = async_function.get_executable(agent).scope(agent, gc.nogc()); - vm.resume_throw(agent, executable, value.unbind(), gc.reborrow()) - .unbind() - .bind(gc.nogc()) - } + PromiseReactionType::Fulfill => agent + .resume(vm, value.unbind(), gc.reborrow()) + .unbind() + .bind(gc.nogc()), + PromiseReactionType::Reject => agent + .resume_throw(vm, value.unbind(), gc.reborrow()) + .unbind() + .bind(gc.nogc()), }; match execution_result { @@ -151,15 +144,6 @@ pub(crate) enum AsyncExecutable<'a> { AsyncModule(SourceTextModule<'a>), } -impl<'a> AsyncExecutable<'a> { - fn get_executable(self, agent: &Agent) -> Executable<'a> { - match self { - AsyncExecutable::AsyncFunction(f) => f.get_executable(agent), - AsyncExecutable::AsyncModule(m) => m.get_executable(agent), - } - } -} - impl<'a> From> for AsyncExecutable<'a> { fn from(value: ECMAScriptFunction<'a>) -> Self { Self::AsyncFunction(value) diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects.rs index 7443a26f1..fbb368764 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects.rs @@ -15,7 +15,7 @@ use crate::{ Agent, ExecutionContext, InternalMethods, InternalSlots, JsError, OrdinaryObject, PromiseCapability, PromiseReactionType, ProtoIntrinsics, Value, object_handle, }, - engine::{Bindable, Executable, GcScope, NoGcScope, Scopable, SuspendedVm, bindable_handle}, + engine::{Bindable, GcScope, NoGcScope, Scopable, SuspendedVm, bindable_handle}, heap::{ ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access, @@ -39,14 +39,6 @@ object_handle!(AsyncGenerator); arena_vec_access!(AsyncGenerator, 'a, AsyncGeneratorHeapData, async_generators); impl AsyncGenerator<'_> { - pub(crate) fn get_executable<'gc>( - self, - agent: &Agent, - gc: NoGcScope<'gc, '_>, - ) -> Executable<'gc> { - self.get(agent).executable.unwrap().bind(gc) - } - /// Returns true if the state of the AsyncGenerator is DRAINING-QUEUE or /// EXECUTING. /// @@ -207,8 +199,8 @@ impl AsyncGenerator<'_> { pub(crate) fn transition_to_executing<'gc>( self, agent: &mut Agent, - gc: NoGcScope<'gc, '_>, - ) -> (SuspendedVm, ExecutionContext, Executable<'gc>) { + _: NoGcScope<'gc, '_>, + ) -> (SuspendedVm, ExecutionContext) { let async_generator_state = &mut self.get_mut(agent).async_generator_state; let (vm, execution_context, queue) = match async_generator_state.take() { Some(AsyncGeneratorState::SuspendedStart { @@ -224,7 +216,7 @@ impl AsyncGenerator<'_> { _ => unreachable!(), }; async_generator_state.replace(AsyncGeneratorState::Executing(queue)); - (vm, execution_context, self.get_executable(agent, gc)) + (vm, execution_context) } pub(crate) fn transition_to_suspended( @@ -287,13 +279,10 @@ impl AsyncGenerator<'_> { let execution_result = match kind { AsyncGeneratorAwaitKind::Await => { // Await only. - let executable = self.get(agent).executable.unwrap().scope(agent, gc.nogc()); match reaction_type { - PromiseReactionType::Fulfill => { - vm.resume(agent, executable, value.unbind(), gc.reborrow()) - } + PromiseReactionType::Fulfill => agent.resume(vm, value.unbind(), gc.reborrow()), PromiseReactionType::Reject => { - vm.resume_throw(agent, executable, value.unbind(), gc.reborrow()) + agent.resume_throw(vm, value.unbind(), gc.reborrow()) } } } @@ -302,8 +291,7 @@ impl AsyncGenerator<'_> { if reaction_type == PromiseReactionType::Reject { // ? Yield ( ? Await ( Value ) ), so Yield doesn't get // performed at all and value is just thrown. - let executable = self.get(agent).executable.unwrap().scope(agent, gc.nogc()); - vm.resume_throw(agent, executable, value.unbind(), gc.reborrow()) + agent.resume_throw(vm, value.unbind(), gc.reborrow()) } else { async_generator_yield( agent, @@ -352,7 +340,6 @@ impl<'a> CreateHeapData, AsyncGenerator<'a>> for Heap pub(crate) struct AsyncGeneratorHeapData<'a> { pub(crate) object_index: Option>, pub(crate) async_generator_state: Option>, - pub(crate) executable: Option>, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -506,10 +493,8 @@ impl HeapMarkAndSweep for AsyncGeneratorHeapData<'static> { let Self { object_index, async_generator_state: generator_state, - executable, } = self; object_index.mark_values(queues); - executable.mark_values(queues); let Some(generator_state) = generator_state else { return; }; @@ -550,10 +535,8 @@ impl HeapMarkAndSweep for AsyncGeneratorHeapData<'static> { let Self { object_index, async_generator_state: generator_state, - executable, } = self; object_index.sweep_values(compactions); - executable.sweep_values(compactions); let Some(generator_state) = generator_state else { return; }; diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_abstract_operations.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_abstract_operations.rs index 1ae5e1aa3..70242b819 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_abstract_operations.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_generator_objects/async_generator_abstract_operations.rs @@ -144,8 +144,7 @@ pub(crate) fn async_generator_resume( // 2. Let genContext be generator.[[AsyncGeneratorContext]]. // 5. Set generator.[[AsyncGeneratorState]] to executing. assert!(generator.is_suspended_start(agent) || generator.is_suspended_yield(agent)); - let (vm, gen_context, executable) = generator.transition_to_executing(agent, gc.nogc()); - let executable = executable.scope(agent, gc.nogc()); + let (vm, gen_context) = generator.transition_to_executing(agent, gc.nogc()); // 3. Let callerContext be the running execution context. // 4. Suspend callerContext. @@ -160,13 +159,13 @@ pub(crate) fn async_generator_resume( // Completion Record returned by the resumed computation. let execution_result = match completion { AsyncGeneratorRequestCompletion::Ok(value) => { - vm.resume(agent, executable, value.unbind(), gc.reborrow()) + agent.resume(vm, value.unbind(), gc.reborrow()) } AsyncGeneratorRequestCompletion::Err(err) => { - vm.resume_throw(agent, executable, err.value().unbind(), gc.reborrow()) + agent.resume_throw(vm, err.value().unbind(), gc.reborrow()) } AsyncGeneratorRequestCompletion::Return(value) => { - vm.resume_return(agent, executable, value.unbind(), gc.reborrow()) + agent.resume_return(vm, value.unbind(), gc.reborrow()) } }; // 8. Assert: result is never an abrupt completion. @@ -282,28 +281,14 @@ fn async_generator_unwrap_yield_resumption( let resumption_value = resumption_value.bind(gc.nogc()); // 1. If resumptionValue is not a return completion, return ? resumptionValue. let execution_result = match resumption_value { - AsyncGeneratorRequestCompletion::Ok(v) => { - let executable = generator - .get(agent) - .get_executable(agent, gc.nogc()) - .scope(agent, gc.nogc()); - vm.resume(agent, executable, v.unbind(), gc.reborrow()) - } + AsyncGeneratorRequestCompletion::Ok(v) => agent.resume(vm, v.unbind(), gc.reborrow()), AsyncGeneratorRequestCompletion::Err(e) => { - let executable = generator - .get(agent) - .get_executable(agent, gc.nogc()) - .scope(agent, gc.nogc()); - vm.resume_throw(agent, executable, e.value().unbind(), gc.reborrow()) + agent.resume_throw(vm, e.value().unbind(), gc.reborrow()) } AsyncGeneratorRequestCompletion::Return(value) => { - let executable = generator - .get(agent) - .get_executable(agent, gc.nogc()) - .scope(agent, gc.nogc()); // 2. Let awaited be Completion(Await(resumptionValue.[[Value]])). // Note: the Await instruction is performed in the bytecode. - vm.resume_return(agent, executable, value.unbind(), gc.reborrow()) + agent.resume_return(vm, value.unbind(), gc.reborrow()) } }; resume_handle_result(agent, execution_result.unbind(), generator, gc); 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 c3d166e10..d127661dd 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 @@ -7,9 +7,7 @@ use crate::{ Agent, ExceptionType, ExecutionContext, InternalMethods, InternalSlots, JsError, JsResult, OrdinaryObject, ProtoIntrinsics, Value, create_iter_result_object, object_handle, }, - engine::{ - Bindable, Executable, ExecutionResult, GcScope, Scopable, SuspendedVm, bindable_handle, - }, + engine::{Bindable, ExecutionResult, GcScope, Scopable, SuspendedVm, bindable_handle}, heap::{ ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access, @@ -63,7 +61,6 @@ impl Generator<'_> { // 7. Set generator.[[GeneratorState]] to executing. let SuspendedGeneratorState { vm, - executable, execution_context, } = match generator .get_mut(agent) @@ -74,7 +71,6 @@ impl Generator<'_> { | Some(GeneratorState::SuspendedStart(state)) => state, _ => unreachable!(), }; - let executable = executable.scope(agent, gc.nogc()); // 4. Let genContext be generator.[[GeneratorContext]]. // 5. Let methodContext be the running execution context. @@ -88,9 +84,7 @@ impl Generator<'_> { // 9. Resume the suspended evaluation of genContext using NormalCompletion(value) as the // result of the operation that suspended it. Let result be the value returned by the // resumed computation. - let execution_result = vm.resume(agent, executable.clone(), value.unbind(), gc.reborrow()); - - let execution_result = execution_result.unbind(); + let execution_result = agent.resume(vm, value.unbind(), gc.reborrow()).unbind(); let gc = gc.into_nogc(); let generator = saved.get(agent).bind(gc); let execution_result = execution_result.bind(gc); @@ -141,7 +135,6 @@ impl Generator<'_> { generator.get_mut(agent).generator_state = Some(GeneratorState::SuspendedYield(SuspendedGeneratorState { vm, - executable: executable.get(agent), execution_context, })); // 8. Resume callerContext passing NormalCompletion(iterNextObj). ... @@ -197,7 +190,6 @@ impl Generator<'_> { // 8. Set generator.[[GeneratorState]] to executing. let Some(GeneratorState::SuspendedYield(SuspendedGeneratorState { vm, - executable, execution_context, })) = generator .get_mut(agent) @@ -207,7 +199,6 @@ impl Generator<'_> { unreachable!() }; let generator = generator.scope(agent, gc.nogc()); - let executable = executable.scope(agent, gc.nogc()); // 5. Let genContext be generator.[[GeneratorContext]]. // 6. Let methodContext be the running execution context. @@ -219,13 +210,11 @@ 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.clone(), value.unbind(), gc.reborrow()) + let execution_result = agent + .resume_throw(vm, value.unbind(), gc.reborrow()) .unbind(); let gc = gc.into_nogc(); let execution_result = execution_result.bind(gc); - // SAFETY: shared but not stored by resume. - let executable = unsafe { executable.take(agent).bind(gc) }; // SAFETY: not shared. let generator = unsafe { generator.take(agent).bind(gc) }; @@ -253,7 +242,6 @@ impl Generator<'_> { generator.get_mut(agent).generator_state = Some(GeneratorState::SuspendedYield(SuspendedGeneratorState { vm, - executable: executable.unbind(), execution_context, })); Ok(yielded_value.unbind()) @@ -320,7 +308,6 @@ impl Generator<'_> { // 8. Set generator.[[GeneratorState]] to executing. let Some(GeneratorState::SuspendedYield(SuspendedGeneratorState { vm, - executable, execution_context, })) = generator .get_mut(agent) @@ -330,7 +317,6 @@ impl Generator<'_> { unreachable!() }; let generator = generator.scope(agent, gc.nogc()); - let executable = executable.scope(agent, gc.nogc()); // 5. Let genContext be generator.[[GeneratorContext]]. // 6. Let methodContext be the running execution context. @@ -343,18 +329,11 @@ impl Generator<'_> { // abruptCompletion as the result of the operation that suspended // it. Let result be the Completion Record returned by the resumed // computation. - let execution_result = vm - .resume_return( - agent, - executable.clone(), - abrupt_completion.unbind(), - gc.reborrow(), - ) + let execution_result = agent + .resume_return(vm, abrupt_completion.unbind(), gc.reborrow()) .unbind(); let gc = gc.into_nogc(); let execution_result = execution_result.bind(gc); - // SAFETY: shared but not stored by resume. - let executable = unsafe { executable.take(agent).bind(gc) }; // SAFETY: not shared. let generator = unsafe { generator.take(agent).bind(gc) }; @@ -381,7 +360,6 @@ impl Generator<'_> { generator.get_mut(agent).generator_state = Some(GeneratorState::SuspendedYield(SuspendedGeneratorState { vm, - executable: executable.unbind(), execution_context, })); Ok(yielded_value) @@ -444,7 +422,6 @@ pub(crate) struct GeneratorHeapData<'a> { #[derive(Debug)] pub(crate) struct SuspendedGeneratorState { pub(crate) vm: SuspendedVm, - pub(crate) executable: Executable<'static>, pub(crate) execution_context: ExecutionContext, } @@ -460,22 +437,18 @@ impl HeapMarkAndSweep for SuspendedGeneratorState { fn mark_values(&self, queues: &mut WorkQueues) { let Self { vm, - executable, execution_context, } = self; vm.mark_values(queues); - executable.mark_values(queues); execution_context.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { let Self { vm, - executable, execution_context, } = self; vm.sweep_values(compactions); - executable.sweep_values(compactions); execution_context.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/builtins/ecmascript_function.rs b/nova_vm/src/ecmascript/builtins/ecmascript_function.rs index 78a534cb9..d4f678828 100644 --- a/nova_vm/src/ecmascript/builtins/ecmascript_function.rs +++ b/nova_vm/src/ecmascript/builtins/ecmascript_function.rs @@ -21,7 +21,7 @@ use crate::{ new_function_environment, ordinary_create_from_constructor, ordinary_object_create_with_intrinsics, to_object, }, - engine::{Bindable, Executable, GcScope, NoGcScope, Scopable, bindable_handle}, + engine::{Bindable, GcScope, NoGcScope, Scopable, bindable_handle}, heap::{ ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access, @@ -302,11 +302,6 @@ impl<'a> ECMAScriptFunction<'a> { self.get(agent).ecmascript_function.this_mode } - #[inline] - pub(crate) fn get_executable(self, agent: &Agent) -> Executable<'a> { - self.get(agent).compiled_bytecode.unwrap().unbind() - } - #[inline] pub(crate) fn get_source_code(self, agent: &Agent) -> SourceCode<'a> { self.get(agent).ecmascript_function.source_code @@ -666,6 +661,12 @@ pub(crate) fn prepare_for_ordinary_call<'a>( // 9. Set the VariableEnvironment of calleeContext to localEnv. // 10. Set the PrivateEnvironment of calleeContext to F.[[PrivateEnvironment]]. ecmascript_code: Some(ECMAScriptCodeEvaluationState { + ip: 0, + executable: None, + stack_base: 0, + iterator_stack_base: 0, + reference_stack_base: 0, + exception_handler_stack_base: 0, lexical_environment: Environment::Function(local_env.unbind()), variable_environment: Environment::Function(local_env.unbind()), private_environment: private_environment.unbind(), diff --git a/nova_vm/src/ecmascript/builtins/global_object.rs b/nova_vm/src/ecmascript/builtins/global_object.rs index b73de04a3..2742e537e 100644 --- a/nova_vm/src/ecmascript/builtins/global_object.rs +++ b/nova_vm/src/ecmascript/builtins/global_object.rs @@ -20,7 +20,7 @@ use crate::{ script_var_declared_names, script_var_scoped_declarations, to_int32, to_int32_number, to_number, to_number_primitive, to_string, }, - engine::{Bindable, Executable, GcScope, NoGcScope, Scopable, Vm, string_literal_to_wtf8}, + engine::{Bindable, Executable, GcScope, NoGcScope, Scopable, string_literal_to_wtf8}, heap::{ArenaAccess, HeapIndexHandle, IntrinsicFunctionIndexes}, ndt, }; @@ -339,6 +339,12 @@ pub(crate) fn perform_eval<'gc>( let running_context_private_env = running_context_private_env.bind(gc.nogc()); ECMAScriptCodeEvaluationState { + ip: 0, + executable: None, + stack_base: 0, + iterator_stack_base: 0, + reference_stack_base: 0, + exception_handler_stack_base: 0, // a. Let lexEnv be NewDeclarativeEnvironment(runningContext's LexicalEnvironment). lexical_environment: Environment::Declarative( new_declarative_environment(agent, Some(running_context_lex_env), gc.nogc()) @@ -358,6 +364,12 @@ pub(crate) fn perform_eval<'gc>( Environment::Global(eval_realm.get(agent).global_env.unwrap()).bind(gc.nogc()); ECMAScriptCodeEvaluationState { + ip: 0, + executable: None, + stack_base: 0, + iterator_stack_base: 0, + reference_stack_base: 0, + exception_handler_stack_base: 0, // a. Let lexEnv be NewDeclarativeEnvironment(evalRealm.[[GlobalEnv]]). lexical_environment: Environment::Declarative( new_declarative_environment(agent, Some(global_env), gc.nogc()).unbind(), @@ -421,14 +433,12 @@ pub(crate) fn perform_eval<'gc>( match result { Ok(_) => { let source_code = agent.current_source_code(gc.nogc()); - let exe = Executable::compile_eval_body(agent, body, source_code, gc.nogc()) - .scope(agent, gc.nogc()); + let exe = Executable::compile_eval_body(agent, body, source_code, gc.nogc()); + agent.set_running_executable(exe); // 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.clone(), None, gc).into_js_result(); - // SAFETY: No one can access the bytecode anymore. - unsafe { exe.take(agent).try_drop(agent) }; + let result = agent.execute(None, gc).into_js_result(); result } Err(err) => Err(err.unbind().bind(gc.into_nogc())), diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index 3a20cf3ed..2993d0f67 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -40,17 +40,17 @@ use crate::{ parse_script, script_evaluation, to_string, try_get_identifier_reference, }, engine::{ - Bindable, GcScope, Global, HeapRootCollection, HeapRootData, HeapRootRef, NoGcScope, - Rootable, Vm, bindable_handle, + Bindable, Executable, ExecutionResult, GcScope, Global, HeapRootCollection, HeapRootData, + HeapRootRef, Instr, Instruction, NoGcScope, Rootable, SuspendedVm, Vm, bindable_handle, }, heap::{ - ArenaAccess, CompactionLists, CreateHeapData, Heap, HeapIndexHandle, HeapMarkAndSweep, - PrimitiveHeapAccess, WorkQueues, heap_gc, + ArenaAccess, CompactionLists, CreateHeapData, DirectArenaAccess, Heap, HeapIndexHandle, + HeapMarkAndSweep, PrimitiveHeapAccess, WorkQueues, heap_gc, }, ndt, }; -use core::{any::Any, cell::RefCell, ops::ControlFlow, ptr::NonNull}; +use core::{any::Any, cell::RefCell, ops::ControlFlow}; use std::collections::TryReserveError; /// Creation options for [`GcAgent`]. @@ -810,7 +810,6 @@ impl GcAgent { #[cfg(feature = "weak-refs")] clear_kept_objects(&mut self.agent); assert!(self.agent.execution_context_stack.is_empty()); - assert!(self.agent.vm_stack.is_empty()); self.agent.stack_refs.borrow_mut().clear(); result } @@ -829,7 +828,6 @@ impl GcAgent { #[cfg(feature = "weak-refs")] clear_kept_objects(&mut self.agent); assert!(self.agent.execution_context_stack.is_empty()); - assert!(self.agent.vm_stack.is_empty()); self.agent.stack_refs.borrow_mut().clear(); result } @@ -885,8 +883,6 @@ pub struct Agent { pub(crate) stack_refs: RefCell>, /// Temporary storage for on-stack heap root collections. pub(crate) stack_ref_collections: RefCell>, - /// Temporary storage for on-stack VMs. - pub(crate) vm_stack: Vec>, /// ### \[\[KeptAlive]] /// /// > Note: instead of storing objects in a list here, we only store a @@ -901,6 +897,7 @@ pub struct Agent { /// \[\[AsyncEvaluationOrder]] field of modules that are asynchronous or /// have asynchronous dependencies. module_async_evaluation_count: u32, + pub(crate) vm: Vm, } impl Agent { @@ -914,7 +911,7 @@ impl Agent { execution_context_stack: Vec::new(), stack_refs: RefCell::new(Vec::with_capacity(64)), stack_ref_collections: RefCell::new(Vec::with_capacity(32)), - vm_stack: Vec::with_capacity(16), + vm: Vm::default(), #[cfg(feature = "weak-refs")] kept_alive: false, private_names_counter: 0, @@ -1071,6 +1068,24 @@ impl Agent { result } + /// Get the `Executable` currently being executed. + pub(crate) fn current_executable<'a>(&self, gc: NoGcScope<'a, '_>) -> Executable<'a> { + if let Some(exe) = self + .running_execution_context() + .ecmascript_code + .as_ref() + .and_then(|r| r.executable.bind(gc)) + { + exe + } else { + eprintln!( + "current_executable: {:#?}", + self.execution_context_stack.last() + ); + panic_corrupted_agent(); + } + } + /// Get current Realm's global environment. #[cfg(test)] pub(crate) fn current_global_env<'a>(&self, gc: NoGcScope<'a, '_>) -> GlobalEnvironment<'a> { @@ -1139,6 +1154,75 @@ impl Agent { ) } + pub(crate) fn execute<'gc>( + &mut self, + arguments: Option<&mut [Value]>, + gc: GcScope<'gc, '_>, + ) -> ExecutionResult<'gc> { + let _ = self.vm.push_stack_frame(); + Vm::execute(self, arguments, gc) + } + + pub(crate) fn resume<'gc>( + &mut self, + vm: SuspendedVm, + value: Value, + gc: GcScope<'gc, '_>, + ) -> ExecutionResult<'gc> { + let value = value.bind(gc.nogc()); + if self.options.print_internals { + eprintln!("Resuming function with value\n"); + } + self.vm.unsuspend(vm); + Vm::resume(self, value.unbind(), gc) + } + + pub(crate) fn resume_throw<'gc>( + &mut self, + vm: SuspendedVm, + err: Value, + gc: GcScope<'gc, '_>, + ) -> ExecutionResult<'gc> { + let err = err.bind(gc.nogc()); + if self.options.print_internals { + eprintln!("Resuming function with error\n"); + } + // Optimisation: Avoid unsuspending the Vm if we're just going to throw + // out of it immediately. + if !vm.has_catch_handler() { + return ExecutionResult::Throw(JsError::new(err.unbind())); + } + self.vm.unsuspend(vm); + Vm::resume_throw(self, err.unbind(), gc) + } + + pub(crate) fn resume_return<'gc>( + &mut self, + mut vm: SuspendedVm, + result: Value, + gc: GcScope<'gc, '_>, + ) -> ExecutionResult<'gc> { + let result = result.bind(gc.nogc()); + if self.options.print_internals { + eprintln!("Resuming function with return\n"); + } + // Following a yield point, the next instruction is a Jump to the + // Normal continue handling. We need to ignore that. + let executable = self.current_executable(gc.nogc()); + let ip = vm.get_ip_mut(); + let instructions = &executable.get_direct(&self.heap.executables).instructions; + let next_instruction = Instr::consume_instruction(instructions, ip); + assert_eq!(next_instruction.map(|i| i.kind), Some(Instruction::Jump)); + let peek_next_instruction = instructions.get(*ip).copied(); + if peek_next_instruction == Some(Instruction::Return.as_u8()) { + // Our return handling is to just return; we can do that without + // unsuspending the VM. + return ExecutionResult::Return(result.unbind()); + } + self.vm.unsuspend(vm); + Vm::resume(self, result.unbind(), gc) + } + /// ### [5.2.3.2 Throw an Exception](https://tc39.es/ecma262/#sec-throw-an-exception) #[must_use] pub fn throw_exception_with_static_message<'a>( @@ -1202,6 +1286,41 @@ impl Agent { ctx } + pub(crate) fn set_running_executable(&mut self, exe: Executable) { + let Some(eval_state) = self + .execution_context_stack + .last_mut() + .and_then(|ctx| ctx.ecmascript_code.as_mut()) + .and_then(|e| { + // Check that we're not replacing an already-set executable. + if e.executable.is_none() { + Some(e) + } else { + None + } + }) + else { + panic_corrupted_agent() + }; + eval_state.executable = Some(exe.unbind()); + } + + pub(crate) fn current_instructions(&self) -> &[u8] { + let Some(exe) = self + .execution_context_stack + .last() + .and_then(|ctx| ctx.ecmascript_code.as_ref()) + .and_then(|code| code.executable.as_ref().cloned()) + else { + eprintln!( + "Current instructions: {:#?}", + self.execution_context_stack.last() + ); + panic_corrupted_agent() + }; + exe.get_instructions(self) + } + pub(crate) fn is_evaluating_strict_code(&self) -> bool { let Some(strict) = self .running_execution_context() @@ -1241,12 +1360,65 @@ impl Agent { previous_context.realm.bind(gc) } - pub(crate) fn push_execution_context(&mut self, context: ExecutionContext) { + pub(crate) fn push_execution_context(&mut self, mut context: ExecutionContext) { + let new_stack_frame = self.vm.push_stack_frame(); + // Store current instruction pointer to current ExecutionContext. + if let Some(eval_state) = self + .execution_context_stack + .last_mut() + .and_then(|ctx| ctx.ecmascript_code.as_mut()) + { + eval_state.ip = self.vm.get_instruction_pointer(); + } + // If we're executing more ECMAScript code then store the instruction + // pointer from the ExecutionContext we're pushing and store the new VM + // stack frame data into the ExecutionContext we're pushing. + if let Some(eval_state) = &mut context.ecmascript_code { + self.vm.set_instruction_pointer(eval_state.ip); + ( + eval_state.stack_base, + eval_state.reference_stack_base, + eval_state.iterator_stack_base, + eval_state.exception_handler_stack_base, + ) = new_stack_frame; + } self.execution_context_stack.push(context); } pub(crate) fn pop_execution_context(&mut self) -> Option { - self.execution_context_stack.pop() + // Pop current ExecutionContext. + let result = self.execution_context_stack.pop(); + // If we were evaluating ECMAScript code, then drop off any remnants it + // might've left on the VM stacks. + if let Some(eval_state) = result.as_ref().and_then(|r| r.ecmascript_code.as_ref()) { + self.vm.pop_stack_frame(( + eval_state.stack_base, + eval_state.reference_stack_base, + eval_state.iterator_stack_base, + eval_state.exception_handler_stack_base, + )); + } + // If we're returning to evaluating ECMAScript code that was previously + // paused, then return the instruction pointer and stack base to their + // previous values. + if let Some(eval_state) = self + .execution_context_stack + .last() + .and_then(|ctx| ctx.ecmascript_code.as_ref()) + { + self.vm.set_instruction_pointer(eval_state.ip); + self.vm.set_stack_bases( + eval_state.stack_base, + #[cfg(debug_assertions)] + eval_state.reference_stack_base, + #[cfg(debug_assertions)] + eval_state.iterator_stack_base, + eval_state.exception_handler_stack_base, + ); + } else if self.execution_context_stack.is_empty() { + self.vm.clear_stack(); + } + result } pub(crate) fn current_source_code<'a>(&self, gc: NoGcScope<'a, '_>) -> SourceCode<'a> { @@ -1623,7 +1795,7 @@ impl HeapMarkAndSweep for Agent { execution_context_stack, stack_refs, stack_ref_collections, - vm_stack, + vm, options: _, symbol_id: _, global_symbol_registry, @@ -1645,9 +1817,7 @@ impl HeapMarkAndSweep for Agent { .borrow() .iter() .for_each(|collection| collection.mark_values(queues)); - vm_stack.iter().for_each(|vm_ptr| { - unsafe { vm_ptr.as_ref() }.mark_values(queues); - }); + vm.mark_values(queues); global_symbol_registry.mark_values(queues); let mut last_filled_global_value = None; heap.globals @@ -1674,7 +1844,7 @@ impl HeapMarkAndSweep for Agent { execution_context_stack, stack_refs, stack_ref_collections, - vm_stack, + vm, options: _, symbol_id: _, global_symbol_registry, @@ -1696,9 +1866,7 @@ impl HeapMarkAndSweep for Agent { .borrow_mut() .iter_mut() .for_each(|entry| entry.sweep_values(compactions)); - vm_stack - .iter_mut() - .for_each(|entry| unsafe { entry.as_mut().sweep_values(compactions) }); + vm.sweep_values(compactions); global_symbol_registry.sweep_values(compactions); } } diff --git a/nova_vm/src/ecmascript/execution/execution_context.rs b/nova_vm/src/ecmascript/execution/execution_context.rs index 3c868c564..ce2b8ea53 100644 --- a/nova_vm/src/ecmascript/execution/execution_context.rs +++ b/nova_vm/src/ecmascript/execution/execution_context.rs @@ -5,7 +5,7 @@ use super::{Agent, Environment, JsResult, PrivateEnvironment, Realm, get_this_environment}; use crate::{ ecmascript::{Function, Object, ScriptOrModule, SourceCode, Value}, - engine::{Bindable, NoGcScope}, + engine::{Bindable, Executable, NoGcScope}, heap::{CompactionLists, HeapMarkAndSweep, WorkQueues}, }; @@ -18,6 +18,18 @@ pub(crate) type ECMAScriptCode = ECMAScriptCodeEvaluationState; /// listed in Table 26. #[derive(Debug, Clone, Copy)] pub(crate) struct ECMAScriptCodeEvaluationState { + /// Instruction pointer. + pub(crate) ip: usize, + /// Executable being evaluated. + pub(crate) executable: Option>, + /// Names the Vm stack 0 position for the code being evaluated. + pub(crate) stack_base: u32, + /// Names the Vm reference stack 0 position for the code being evaluated. + pub(crate) reference_stack_base: u32, + /// Names the Vm iterator stack 0 position for the code being evaluated. + pub(crate) iterator_stack_base: u32, + /// Names the Vm exception jump target stack 0 position for the code being evaluated. + pub(crate) exception_handler_stack_base: u32, /// ### LexicalEnvironment /// /// Identifies the Environment Record used to resolve identifier references @@ -92,17 +104,34 @@ impl ExecutionContext { pub(crate) fn suspend(&self) { // TODO: What does this actually mean in the end? } + + pub(crate) fn get_stack_bases(&self) -> (u32, u32, u32, u32) { + let ecmascript_code = self.ecmascript_code.as_ref().unwrap(); + ( + ecmascript_code.stack_base, + ecmascript_code.reference_stack_base, + ecmascript_code.iterator_stack_base, + ecmascript_code.exception_handler_stack_base, + ) + } } impl HeapMarkAndSweep for ECMAScriptCodeEvaluationState { fn mark_values(&self, queues: &mut WorkQueues) { let Self { + ip: _, + executable, + stack_base: _, + reference_stack_base: _, + iterator_stack_base: _, + exception_handler_stack_base: _, lexical_environment, variable_environment, private_environment, is_strict_mode: _, source_code, } = self; + executable.mark_values(queues); lexical_environment.mark_values(queues); variable_environment.mark_values(queues); private_environment.mark_values(queues); @@ -111,12 +140,19 @@ impl HeapMarkAndSweep for ECMAScriptCodeEvaluationState { fn sweep_values(&mut self, compactions: &CompactionLists) { let Self { + ip: _, + executable, + stack_base: _, + reference_stack_base: _, + iterator_stack_base: _, + exception_handler_stack_base: _, lexical_environment, variable_environment, private_environment, is_strict_mode: _, source_code, } = self; + executable.sweep_values(compactions); lexical_environment.sweep_values(compactions); variable_environment.sweep_values(compactions); private_environment.sweep_values(compactions); diff --git a/nova_vm/src/ecmascript/scripts_and_modules/module/module_semantics/source_text_module_records.rs b/nova_vm/src/ecmascript/scripts_and_modules/module/module_semantics/source_text_module_records.rs index 336a92568..fd6b169a2 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules/module/module_semantics/source_text_module_records.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules/module/module_semantics/source_text_module_records.rs @@ -33,7 +33,7 @@ use crate::{ }, engine::{ Bindable, Executable, ExecutionResult, GcScope, GcToken, HeapRootData, NoGcScope, Scopable, - Scoped, Vm, bindable_handle, + Scoped, bindable_handle, }, heap::{CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, WorkQueues}, ndt, @@ -236,10 +236,6 @@ impl<'m> SourceTextModule<'m> { } } - pub(crate) fn get_executable(self, agent: &Agent) -> Executable<'m> { - self.get(agent).compiled_bytecode.unwrap() - } - fn set_executable(self, agent: &mut Agent, executable: Executable<'m>) { assert!( self.get_mut(agent) @@ -1238,6 +1234,12 @@ impl CyclicModuleMethods for SourceTextModule<'_> { // 8. Let moduleContext be a new ECMAScript code execution context. let module_context = ExecutionContext { ecmascript_code: Some(ECMAScriptCodeEvaluationState { + ip: 0, + executable: None, + stack_base: 0, + iterator_stack_base: 0, + reference_stack_base: 0, + exception_handler_stack_base: 0, // 14. Set the LexicalEnvironment of moduleContext to // module.[[Environment]]. lexical_environment: env.unbind().into(), @@ -1431,6 +1433,12 @@ impl CyclicModuleMethods for SourceTextModule<'_> { let source_code = module.source_code(agent); let module_context = ExecutionContext { ecmascript_code: Some(ECMAScriptCodeEvaluationState { + ip: 0, + executable: None, + stack_base: 0, + iterator_stack_base: 0, + reference_stack_base: 0, + exception_handler_stack_base: 0, // 7. Set the LexicalEnvironment of moduleContext to // module.[[Environment]]. lexical_environment: environment.unbind().into(), @@ -1459,9 +1467,10 @@ impl CyclicModuleMethods for SourceTextModule<'_> { agent.push_execution_context(module_context); // c. Let result be Completion(Evaluation of // module.[[ECMAScriptCode]]). - let bytecode = - Executable::compile_module(agent, module, gc.nogc()).scope(agent, gc.nogc()); - let result = Vm::execute(agent, bytecode.clone(), None, gc.reborrow()) + let bytecode = Executable::compile_module(agent, module, gc.nogc()); + agent.set_running_executable(bytecode); + let result = agent + .execute(None, gc.reborrow()) .into_js_result() .unbind() .bind(gc.into_nogc()); @@ -1514,7 +1523,7 @@ fn async_module_start( agent: &mut Agent, promise_capability: PromiseCapability, module: SourceTextModule, - async_context: ExecutionContext, + mut async_context: ExecutionContext, mut gc: GcScope, ) { let promise_capability = promise_capability.bind(gc.nogc()); @@ -1525,15 +1534,14 @@ fn async_module_start( // 3. Set the code evaluation state of asyncContext such that when // evaluation is resumed for that execution context, closure will be // called with no arguments. - let bytecode = Executable::compile_module(agent, module, gc.nogc()).scope(agent, gc.nogc()); + async_context.ecmascript_code.as_mut().unwrap().executable = + Some(Executable::compile_module(agent, module, gc.nogc()).unbind()); // 4. Push asyncContext onto the execution context stack; asyncContext is // now the running execution context. agent.push_execution_context(async_context); // 5. Resume the suspended evaluation of asyncContext. Let result be the // value returned by the resumed computation. - let result = Vm::execute(agent, bytecode.clone(), None, gc.reborrow()) - .unbind() - .bind(gc.nogc()); + let result = agent.execute(None, gc.reborrow()).unbind().bind(gc.nogc()); // AsyncBlockStart will run the module until it returns, throws or // gets suspended with an await. @@ -1542,7 +1550,6 @@ fn async_module_start( let _ = agent.pop_execution_context().unwrap(); // SAFETY: not shared. let promise = unsafe { - let _ = bytecode.take(agent); let promise = promise.take(agent).bind(gc.nogc()); let _ = scoped_module.take(agent); promise @@ -1560,7 +1567,6 @@ fn async_module_start( let _ = agent.pop_execution_context().unwrap(); // SAFETY: not shared. let promise = unsafe { - let _ = bytecode.take(agent); let promise = promise.take(agent).bind(gc.nogc()); let _ = scoped_module.take(agent); promise @@ -1578,12 +1584,18 @@ fn async_module_start( } => { let async_context = agent.pop_execution_context().unwrap(); // SAFETY: not shared. - let (bytecode, promise, module) = unsafe { - let bytecode = bytecode.take(agent).bind(gc.nogc()); + let (promise, module) = unsafe { let promise = promise.take(agent).bind(gc.nogc()); let module = scoped_module.take(agent).bind(gc.nogc()); - (bytecode, promise, module) + (promise, module) }; + let bytecode = async_context + .ecmascript_code + .as_ref() + .unwrap() + .executable + .unwrap() + .bind(gc.nogc()); let promise_capability = PromiseCapability::from_promise(promise, true); let handler = agent .heap diff --git a/nova_vm/src/ecmascript/scripts_and_modules/script.rs b/nova_vm/src/ecmascript/scripts_and_modules/script.rs index 189515383..7540c1af8 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules/script.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules/script.rs @@ -13,7 +13,7 @@ use crate::{ script_lexically_declared_names, script_lexically_scoped_declarations, script_var_declared_names, script_var_scoped_declarations, }, - engine::{Bindable, Executable, GcScope, NoGcScope, Scopable, Vm, bindable_handle}, + engine::{Bindable, Executable, GcScope, NoGcScope, Scopable, bindable_handle}, heap::{ ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap, HeapIndexHandle, HeapMarkAndSweep, WorkQueues, arena_vec_access, index_handle, @@ -315,6 +315,12 @@ pub fn script_evaluation<'a>( script_or_module: Some(ScriptOrModule::Script(script.unbind())), ecmascript_code: Some(ECMAScriptCode { + ip: 0, + executable: None, + stack_base: 0, + iterator_stack_base: 0, + reference_stack_base: 0, + exception_handler_stack_base: 0, // 6. Set the VariableEnvironment of scriptContext to globalEnv. variable_environment: Environment::Global(global_env.unbind()), @@ -356,19 +362,12 @@ pub fn script_evaluation<'a>( // 13. If result.[[Type]] is normal, then let result: JsResult = match result { Ok(_) => { - let bytecode = - Executable::compile_script(agent, script, gc.nogc()).scope(agent, gc.nogc()); + let bytecode = Executable::compile_script(agent, script, gc.nogc()); + agent.set_running_executable(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). - let result = Vm::execute(agent, bytecode.clone(), None, gc.reborrow()) - .into_js_result() - .unbind() - .bind(gc.into_nogc()); - // SAFETY: The bytecode is not accessible by anyone anymore and no one - // will try to re-run it. - unsafe { bytecode.take(agent).try_drop(agent) }; - result + agent.execute(None, gc).into_js_result() } Err(err) => Err(err.unbind().bind(gc.into_nogc())), }; 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 0184efc92..7cb8ef556 100644 --- a/nova_vm/src/ecmascript/syntax_directed_operations/function_definitions.rs +++ b/nova_vm/src/ecmascript/syntax_directed_operations/function_definitions.rs @@ -15,7 +15,7 @@ use crate::{ ordinary_populate_from_constructor, set_function_name, try_define_property_or_throw, unwrap_try, }, - engine::{Bindable, Executable, ExecutionResult, GcScope, NoGcScope, Scopable, Vm}, + engine::{Bindable, Executable, ExecutionResult, GcScope, NoGcScope, Scopable}, heap::{ArenaAccess, ArenaAccessMut, CreateHeapData}, }; use oxc_ast::ast::{self}; @@ -241,8 +241,10 @@ pub(crate) fn evaluate_function_body<'gc>( function_object.get_mut(agent).compiled_bytecode = Some(exe.unbind()); exe }; - let exe = exe.scope(agent, gc.nogc()); - Vm::execute(agent, exe, Some(arguments_list.unbind().as_mut_slice()), gc).into_js_result() + agent.set_running_executable(exe); + agent + .execute(Some(arguments_list.unbind().as_mut_slice()), gc) + .into_js_result() } /// ### [15.8.4 Runtime Semantics: EvaluateAsyncFunctionBody](https://tc39.es/ecma262/#sec-runtime-semantics-evaluateasyncfunctionbody) @@ -276,15 +278,11 @@ pub(crate) fn evaluate_async_function_body<'a>( function_object.get_mut(agent).compiled_bytecode = Some(exe.unbind()); exe }; - let exe = exe.scope(agent, gc.nogc()); + agent.set_running_executable(exe); - let result = Vm::execute( - agent, - exe, - Some(arguments_list.unbind().as_mut_slice()), - gc.reborrow(), - ) - .unbind(); + let result = agent + .execute(Some(arguments_list.unbind().as_mut_slice()), gc.reborrow()) + .unbind(); let gc = gc.into_nogc(); let result = result.bind(gc); // SAFETY: not shared. @@ -355,25 +353,21 @@ pub(crate) fn evaluate_generator_body<'gc>( let function_object = function_object.bind(gc.nogc()); let exe = if let Some(exe) = function_object.get(agent).compiled_bytecode { - exe.scope(agent, gc.nogc()) + exe.bind(gc.nogc()) } else { let data = CompileFunctionBodyData::new(agent, function_object, gc.nogc()); let exe = Executable::compile_function_body(agent, data, gc.nogc()); function_object.get_mut(agent).compiled_bytecode = Some(exe.unbind()); - exe.scope(agent, gc.nogc()) + exe }; + agent.set_running_executable(exe); let function_object = function_object.scope(agent, gc.nogc()); // 1. Perform ? FunctionDeclarationInstantiation(functionObject, argumentsList). // Note: FunctionDeclarationInstantiation is done at the beginning of the // bytecode, followed by a Yield. - let vm = match Vm::execute( - agent, - exe.clone(), - Some(arguments_list.unbind().as_mut_slice()), - gc.reborrow(), - ) { + let vm = match agent.execute(Some(arguments_list.unbind().as_mut_slice()), gc.reborrow()) { ExecutionResult::Throw(err) => { return Err(err.unbind().bind(gc.into_nogc())); } @@ -396,8 +390,6 @@ pub(crate) fn evaluate_generator_body<'gc>( object_index: None, generator_state: Some(GeneratorState::SuspendedStart(SuspendedGeneratorState { vm, - // SAFETY: exe is not shared. - executable: unsafe { exe.take(agent) }, execution_context: agent.running_execution_context().clone(), })), }) @@ -429,25 +421,21 @@ pub(crate) fn evaluate_async_generator_body<'gc>( let arguments_list = arguments_list.bind(gc.nogc()); let exe = if let Some(exe) = function_object.get(agent).compiled_bytecode { - exe.scope(agent, gc.nogc()) + exe.bind(gc.nogc()) } else { let data = CompileFunctionBodyData::new(agent, function_object, gc.nogc()); let exe = Executable::compile_function_body(agent, data, gc.nogc()); function_object.get_mut(agent).compiled_bytecode = Some(exe.unbind()); - exe.scope(agent, gc.nogc()) + exe }; + agent.set_running_executable(exe); let function_object = function_object.scope(agent, gc.nogc()); // 1. Perform ? FunctionDeclarationInstantiation(functionObject, argumentsList). // Note: FunctionDeclarationInstantiation is done at the beginning of the // bytecode, followed by a Yield. - let vm = match Vm::execute( - agent, - exe.clone(), - Some(arguments_list.unbind().as_mut_slice()), - gc.reborrow(), - ) { + let vm = match agent.execute(Some(arguments_list.unbind().as_mut_slice()), gc.reborrow()) { ExecutionResult::Throw(err) => { return Err(err.unbind().bind(gc.into_nogc())); } @@ -470,8 +458,6 @@ pub(crate) fn evaluate_async_generator_body<'gc>( .heap .create(AsyncGeneratorHeapData { object_index: None, - // SAFETY: exe is not shared. - executable: Some(unsafe { exe.take(agent) }), async_generator_state: Some(AsyncGeneratorState::SuspendedStart { vm, execution_context: agent.running_execution_context().clone(), diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index 66de5eadc..b68b39929 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -576,7 +576,7 @@ impl<'a> OrdinaryObject<'a> { } pub(crate) fn create_object_with_shape_and_data_properties( - agent: &mut Agent, + heap: &mut Heap, shape: ObjectShape<'a>, values: &[Value<'a>], ) -> Self { @@ -587,14 +587,13 @@ impl<'a> OrdinaryObject<'a> { cap, len, len_writable: _, - } = agent - .heap + } = heap .elements .allocate_property_storage(values, None) .expect("Failed to create object"); - assert_eq!(cap, shape.values_capacity(agent)); - assert_eq!(len, shape.len(agent)); - agent.heap.create(ObjectRecord::new(shape, values)) + assert_eq!(cap, shape.values_capacity(&heap.object_shapes)); + assert_eq!(len, shape.len(&heap.object_shapes)); + heap.create(ObjectRecord::new(shape, values)) } pub(crate) fn create_object_with_shape( diff --git a/nova_vm/src/ecmascript/types/spec/reference.rs b/nova_vm/src/ecmascript/types/spec/reference.rs index e1fb660b3..60f49af55 100644 --- a/nova_vm/src/ecmascript/types/spec/reference.rs +++ b/nova_vm/src/ecmascript/types/spec/reference.rs @@ -18,7 +18,7 @@ use crate::{ use super::PrivateName; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub(crate) struct VariableReference<'a> { /// ### \[\[Base]] /// @@ -32,7 +32,7 @@ pub(crate) struct VariableReference<'a> { cache: Option>, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub(crate) struct PropertyExpressionReference<'a> { /// ### \[\[Base]] /// @@ -44,7 +44,7 @@ pub(crate) struct PropertyExpressionReference<'a> { referenced_name: Value<'a>, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub(crate) struct PropertyReference<'a> { /// ### \[\[Base]] /// @@ -56,7 +56,7 @@ pub(crate) struct PropertyReference<'a> { referenced_name: PropertyKey<'a>, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub(crate) struct SuperExpressionReference<'a> { /// ### \[\[Base]] /// @@ -73,7 +73,7 @@ pub(crate) struct SuperExpressionReference<'a> { this_value: Value<'a>, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub(crate) struct SuperReference<'a> { /// ### \[\[Base]] /// @@ -96,7 +96,7 @@ pub(crate) struct SuperReference<'a> { /// operators as delete, typeof, the assignment operators, the super keyword /// and other language features. For example, the left-hand operand of an /// assignment is expected to produce a Reference Record. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] #[repr(u8)] pub(crate) enum Reference<'a> { /// Unresolvable Reference. @@ -383,6 +383,16 @@ impl<'a> Reference<'a> { ) } + pub(crate) fn is_expression(&self) -> bool { + matches!( + self, + Reference::SuperExpression(_) + | Reference::SuperExpressionStrict(_) + | Reference::PropertyExpression(_) + | Reference::PropertyExpressionStrict(_) + ) + } + /// ### \[\[Strict]] pub(crate) fn strict(&self) -> bool { matches!( diff --git a/nova_vm/src/engine/bytecode/bytecode_compiler.rs b/nova_vm/src/engine/bytecode/bytecode_compiler.rs index a3929e690..048eeea71 100644 --- a/nova_vm/src/engine/bytecode/bytecode_compiler.rs +++ b/nova_vm/src/engine/bytecode/bytecode_compiler.rs @@ -208,19 +208,27 @@ impl<'s, 'gc> Place<'s, 'gc> { // Variable is stored in the global environment. Caching helps with // these accesses. let cache = ctx.create_property_lookup_cache(name.to_property_key()); - ctx.add_instruction_with_cache(Instruction::GetValueWithCacheKeepReference, cache); + // No need to evaluate property key since we already have a + // property key. + ctx.add_instruction(Instruction::PushReferenceCopy); + ctx.add_instruction_with_cache(Instruction::GetValueWithCache, cache); Ok(ValueOutput::Value) } Self::Member { name: Some(name) } => { // Property access. Caching helps with these. let cache = ctx.create_property_lookup_cache(*name); - ctx.add_instruction_with_cache(Instruction::GetValueWithCacheKeepReference, cache); + // No need to evaluate property key since we already have a + // property key. + ctx.add_instruction(Instruction::PushReferenceCopy); + ctx.add_instruction_with_cache(Instruction::GetValueWithCache, cache); Ok(ValueOutput::Value) } Self::Member { .. } | Self::Env { .. } => { // Variable is stored in the environment or we don't know the // property name at compile time. Caching doesn't help with these. - ctx.add_instruction(Instruction::GetValueKeepReference); + ctx.add_instruction(Instruction::PushReference); + ctx.add_instruction(Instruction::EvaluateReference); + ctx.add_instruction(Instruction::GetValue); Ok(ValueOutput::Value) } Self::TemporalDeadZone { name } => { @@ -664,13 +672,6 @@ pub(crate) fn is_boolean_literal_false(expression: &ast::Expression) -> bool { matches!(expression.get_inner_expression(), ast::Expression::BooleanLiteral(lit) if !lit.value) } -fn is_chain_expression(expression: &ast::Expression) -> bool { - matches!( - expression.get_inner_expression(), - ast::Expression::ChainExpression(_) - ) -} - impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::NumericLiteral<'s> { type Output = Primitive<'gc>; fn compile(&'s self, ctx: &mut CompileContext<'a, 's, 'gc, 'scope>) -> Self::Output { @@ -1193,16 +1194,6 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Logica } } -impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> - for ast::ParenthesizedExpression<'s> -{ - type Output = Result, ExpressionError>; - - fn compile(&'s self, ctx: &mut CompileContext<'a, 's, 'gc, 'scope>) -> Self::Output { - self.expression.compile(ctx) - } -} - impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::ArrowFunctionExpression<'s> { @@ -1737,18 +1728,18 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::CallEx return Ok(ValueOutput::Value); } // 1. Let ref be ? Evaluation of CallExpression. - ctx.is_call_optional_chain_this = is_chain_expression(&self.callee); - let r#ref = self.callee.compile(ctx)?; - // Optimization: If we know arguments is empty, we don't need to - // worry about arguments evaluation clobbering our function's this - // reference. - let need_pop_reference = r#ref.has_reference() && !self.arguments.is_empty(); + let r#ref = + if let ast::Expression::ChainExpression(callee) = self.callee.get_inner_expression() { + ctx.is_call_optional_chain_this = true; + let result = callee.compile(ctx)?.into(); + ctx.is_call_optional_chain_this = false; + result + } else { + self.callee.compile(ctx)? + }; // 2. Let func be ? GetValue(ref). let _func = r#ref.get_value_keep_reference(ctx)?; - if need_pop_reference { - ctx.add_instruction(Instruction::PushReference); - } - + let did_keep_reference = r#ref.has_reference(); let func_on_stack = if self.optional { // Optional Chains @@ -1759,7 +1750,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::CallEx // a. Return undefined // To return undefined we jump over the rest of the call handling. - let jump_over_call = if need_pop_reference { + let jump_over_call = if did_keep_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! @@ -1787,9 +1778,8 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::CallEx let dynamic_arg_count = prep_arguments(ctx, &self.arguments); let result = compile_arguments(ctx, &self.arguments, &dynamic_arg_count); // 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); - } + debug_assert_eq!(ctx.optional_chains, None); + ctx.optional_chains = optional_chain; // Note: func on stack and the possible dynamic arg count are consumed // by EvaluateCall. @@ -1800,7 +1790,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::CallEx let num_arguments = result?; - if need_pop_reference { + if did_keep_reference { ctx.add_instruction(Instruction::PopReference); } ctx.add_instruction_with_immediate(Instruction::EvaluateCall, num_arguments); @@ -1911,9 +1901,8 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> let output = self.expression.compile(ctx).and_then(|r| r.get_value(ctx)); // 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); - } + debug_assert_eq!(ctx.optional_chains, None); + ctx.optional_chains = optional_chain; let output = match output { Ok(o) => o, @@ -2023,39 +2012,42 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::ChainE } else { false }; + + fn handle_place_result<'s, 'gc>( + ctx: &mut CompileContext<'_, 's, 'gc, '_>, + place: Result, ExpressionError>, + ) -> Result, ExpressionError> { + let result = place.and_then(|p| { + let r = p.get_value_maybe_keep_reference(ctx, ctx.is_call_optional_chain_this); + if ctx.is_call_optional_chain_this { + // Move kept reference into register. + ctx.add_instruction(Instruction::PopReference); + } + r + }); + ctx.is_call_optional_chain_this = false; + result + } + let result = match &self.expression { ast::ChainElement::CallExpression(expr) => expr.compile(ctx), ast::ChainElement::ComputedMemberExpression(expr) => { let place = expr.compile(ctx); - let result = place.and_then(|p| { - p.get_value_maybe_keep_reference(ctx, ctx.is_call_optional_chain_this) - }); - ctx.is_call_optional_chain_this = false; - result + handle_place_result(ctx, place) } ast::ChainElement::StaticMemberExpression(expr) => { let place = expr.compile(ctx); - let result = place.and_then(|p| { - p.get_value_maybe_keep_reference(ctx, ctx.is_call_optional_chain_this) - }); - ctx.is_call_optional_chain_this = false; - result + handle_place_result(ctx, place) } ast::ChainElement::PrivateFieldExpression(expr) => { let place = expr.compile(ctx); - let result = place.and_then(|p| { - p.get_value_maybe_keep_reference(ctx, ctx.is_call_optional_chain_this) - }); - ctx.is_call_optional_chain_this = false; - result + handle_place_result(ctx, place) } #[cfg(feature = "typescript")] ast::ChainElement::TSNonNullExpression(expr) => { let result = expr.expression.compile(ctx).and_then(|r| match r { PlaceOrValue::Value(r) => Ok(r), - PlaceOrValue::Place(place) => { - place.get_value_maybe_keep_reference(ctx, ctx.is_call_optional_chain_this) - } + PlaceOrValue::Place(place) => handle_place_result(ctx, place), }); ctx.is_call_optional_chain_this = false; result @@ -2284,8 +2276,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> let tag_ref = self.tag.compile(ctx)?; // 2. Let tagFunc be ? GetValue(tagRef). let _tag_func = tag_ref.get_value_keep_reference(ctx)?; - let need_pop_reference = - tag_ref.has_reference() && !self.quasi.is_no_substitution_template(); + let did_keep_reference = tag_ref.has_reference(); // Load tagFunc to the stack. let tag_func_on_stack = ctx.load_to_stack(); @@ -2296,10 +2287,6 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> // ### 13.3.8.1 Runtime Semantics: ArgumentListEvaluation - if need_pop_reference { - ctx.add_instruction(Instruction::PushReference); - } - // TemplateLiteral : NoSubstitutionTemplate let mut arguments = Vec::with_capacity(self.quasi.expressions.len()); if self.quasi.is_no_substitution_template() { @@ -2337,7 +2324,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> // 5. Return the list-concatenation of « firstSub » and restSub. } } - if need_pop_reference { + if did_keep_reference { ctx.add_instruction(Instruction::PopReference); } let num_arguments = arguments.len(); @@ -2456,11 +2443,18 @@ fn compile_delegate_yield_expression<'s>( // ? Await(innerResult). ctx.add_instruction(Instruction::Await); } - let jump_to_end = ctx.add_instruction_with_jump_slot(Instruction::IteratorComplete); + ctx.add_instruction(Instruction::LoadCopy); + // innerResult is duplicated on both top of stack and result. + // iii. If innerResult is not an Object, throw a TypeError exception. // iv. Let done be ? IteratorComplete(innerResult). + ctx.add_instruction(Instruction::IteratorComplete); + // done is now in result and innerResult is on top of stack. // v. If done is true, then // 1. Return ? IteratorValue(innerResult). + let jump_to_end = ctx.add_instruction_with_jump_slot(Instruction::JumpIfTrue); + // Bring innerResult back into result. + ctx.add_instruction(Instruction::Store); let inner_result_yield_label = ctx.get_jump_index_to_here(); // vi. If generatorKind is async, @@ -2495,11 +2489,16 @@ fn compile_delegate_yield_expression<'s>( // We should be +1 try-catch block here. // +0 let jump_to_throw_result_handling = try_catch_block.exit(ctx); + ctx.add_instruction(Instruction::LoadCopy); let jump_over_return_call = ctx.add_instruction_with_jump_slot(Instruction::IteratorReturn); // ii. Let return be ? GetMethod(iterator, "return"). // iii. If return is undefined, then ... (jump over return call) // iv. Let innerReturnResult be // ? Call(return, iterator, « received.[[Value]] »). + + // Return method result is in result, iterator result object on the + // stack as extra. Pop it off. + ctx.add_instruction(Instruction::PopStack); // v. If generatorKind is async, if generator_kind_is_async { // set innerReturnResult to ? Await(innerReturnResult). @@ -2507,10 +2506,18 @@ fn compile_delegate_yield_expression<'s>( } // vi. If innerReturnResult is not an Object, throw a TypeError exception. // vii. Let done be ? IteratorComplete(innerReturnResult). + ctx.add_instruction(Instruction::LoadCopy); + // innerReturnResult is now top of stack and in result register. + ctx.add_instruction(Instruction::IteratorComplete); + // done is in result register, innerReturnResult on stack. + // viii. If done is true, then - // 1. Set value to ? IteratorValue(innerReturnResult). - // 2. Return ReturnCompletion(value). - let jump_to_return = ctx.add_instruction_with_jump_slot(Instruction::IteratorComplete); + let jump_to_iterator_value = ctx.add_instruction_with_jump_slot(Instruction::JumpIfTrue); + + // else branch: innerReturnResult is on stack... + ctx.add_instruction(Instruction::Store); + // ...and now in result register. + // ix. If generatorKind is async, // set received to Completion( // AsyncGeneratorYield(? IteratorValue(innerReturnResult)) @@ -2521,9 +2528,19 @@ fn compile_delegate_yield_expression<'s>( // completion handling, so we jump there to reduce duplication. ctx.add_jump_instruction_to_index(Instruction::Jump, inner_result_yield_label); + ctx.set_jump_target_here(jump_to_iterator_value); + // innerReturnResult now in result register. + ctx.add_instruction(Instruction::Store); + // viii. If done is true, then + // 1. Set value to ? IteratorValue(innerReturnResult). + ctx.add_instruction(Instruction::IteratorValue); + // 2. Return ReturnCompletion(value). + let jump_to_return = ctx.add_instruction_with_jump_slot(Instruction::Jump); + // We should be +0 try-catch block here. ctx.set_jump_target_here(jump_over_return_call); // 1. Set value to received.[[Value]]. + ctx.add_instruction(Instruction::Store); ctx.set_jump_target_here(jump_to_return); // 2. If generatorKind is async, then // a. Set value to ? Await(value). @@ -2608,6 +2625,11 @@ fn compile_delegate_yield_expression<'s>( // We should be +0 try-catch block here. ctx.set_jump_target_here(jump_to_end); + // Top of stack is the iterator innerResult. + ctx.add_instruction(Instruction::Store); + // v. If done is true, then + // 1. Return ? IteratorValue(innerResult). + ctx.add_instruction(Instruction::IteratorValue); ctx.add_instruction(Instruction::PopExceptionJumpTarget); ctx.add_instruction(Instruction::IteratorPop); Ok(()) @@ -2709,7 +2731,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Expres ast::Expression::NullLiteral(x) => Ok(x.compile(ctx).into()), ast::Expression::NumericLiteral(x) => Ok(x.compile(ctx).into()), ast::Expression::ObjectExpression(x) => x.compile(ctx).map(Into::into), - ast::Expression::ParenthesizedExpression(x) => x.compile(ctx), + ast::Expression::ParenthesizedExpression(x) => x.expression.compile(ctx), ast::Expression::PrivateFieldExpression(x) => x.compile(ctx).map(Into::into), ast::Expression::PrivateInExpression(x) => x.compile(ctx).map(Into::into), #[cfg(feature = "regexp")] @@ -2815,6 +2837,9 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Update } } let value_on_stack = value_on_stack.unwrap_or_else(|| ctx.load_copy_to_stack()); + if lref.has_reference() { + ctx.add_instruction(Instruction::PopReference); + } let result = lref.put_value(ctx, ValueOutput::Value); value_on_stack.store(ctx); result.map(|_| ValueOutput::Value) @@ -3354,8 +3379,7 @@ fn complex_object_pattern<'s>( break 'iter Err(err); } if object_pattern.rest.is_some() { - assert!(place.has_reference()); - ctx.add_instruction(Instruction::PushReference); + debug_assert!(place.has_reference()); } if let Err(err) = property.value.compile(ctx) { diff --git a/nova_vm/src/engine/bytecode/bytecode_compiler/assignment.rs b/nova_vm/src/engine/bytecode/bytecode_compiler/assignment.rs index bb9580fd7..856299180 100644 --- a/nova_vm/src/engine/bytecode/bytecode_compiler/assignment.rs +++ b/nova_vm/src/engine/bytecode/bytecode_compiler/assignment.rs @@ -17,6 +17,7 @@ use crate::engine::{ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::AssignmentExpression<'s> { type Output = Result, ExpressionError>; fn compile(&'s self, ctx: &mut CompileContext<'a, 's, 'gc, 'scope>) -> Self::Output { + ctx.add_instruction(Instruction::Empty); let mut do_named_evaluation = false; // 1. Let lref be ? Evaluation of LeftHandSideExpression. let lref = match &self.left { @@ -120,10 +121,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Assign } else if let Some(operator) = self.operator.to_logical_operator() { // 2. Let lval be ? GetValue(lref). lref.get_value_keep_reference(ctx)?; - let push_reference = lref.has_reference() && !self.right.is_literal(); - if push_reference { - ctx.add_instruction(Instruction::PushReference); - } + let did_keep_reference = lref.has_reference(); // We store the lval on the stack because we to branch based on its // value to either return lval directly, or go into the // `PutValue(lRef, rVal)` branch and return rval. For the lval @@ -179,7 +177,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Assign // it onto the stack. let rval_copy = ctx.load_copy_to_stack(); - if push_reference { + if did_keep_reference { // Pop lref off the reference stack. ctx.add_instruction(Instruction::PopReference); } @@ -189,7 +187,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Assign // a stand-in for lval_copy. break 'rval (rval_copy, None); }; - if push_reference { + if did_keep_reference { // If lref was pushed onto the reference stack then the we // have to jump over the if-branch popping it as we already // popped it above. As we return from 'rval we're @@ -216,7 +214,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Assign // 3. ..., return lval. value_on_stack.store(ctx); - if push_reference { + if did_keep_reference { // Pop lref off the reference stack. ctx.add_instruction(Instruction::PopReference); } @@ -230,10 +228,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Assign // 2. let lval be ? GetValue(lref). let _lval = lref.get_value_keep_reference(ctx)?; let lval_copy = ctx.load_to_stack(); - let do_push_reference = lref.has_reference() && !self.right.is_literal(); - if do_push_reference { - ctx.add_instruction(Instruction::PushReference); - } + let did_keep_reference = lref.has_reference(); // 3. Let rref be ? Evaluation of AssignmentExpression. // 4. Let rval be ? GetValue(rref). let _rval = match self.right.compile(ctx).and_then(|r| r.get_value(ctx)) { @@ -272,7 +267,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::Assign ctx.add_instruction(op_text); let r_copy = ctx.load_copy_to_stack(); let r = ValueOutput::Value; - if do_push_reference { + if did_keep_reference { ctx.add_instruction(Instruction::PopReference); } // 8. Perform ? PutValue(lref, r). @@ -422,9 +417,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> break 'args; } }; - let needs_push_reference = - lref.has_reference() && !element.init.is_literal(); - if needs_push_reference { + if lref.has_reference() { ctx.add_instruction(Instruction::PushReference); } // 2. Let value be undefined. @@ -436,7 +429,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> // 4. If Initializer is present and value is undefined, then // ... compile_initializer(element, ctx); - if needs_push_reference { + if lref.has_reference() { ctx.add_instruction(Instruction::PopReference); } // 7. Return ? PutValue(lRef, v). @@ -479,6 +472,9 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> break 'args; } }; + if lref.has_reference() { + ctx.add_instruction(Instruction::PushReference); + } // 2. Let value be undefined. // 3. If iteratorRecord.[[Done]] is false, then // a. Let next be ? IteratorStepValue(iteratorRecord). @@ -490,6 +486,9 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> // 5. Else, // a. Let v be value. // 7. Return ? PutValue(lRef, v). + if lref.has_reference() { + ctx.add_instruction(Instruction::PopReference); + } if let Err(e) = lref.put_value(ctx, ValueOutput::Value) { result = Err(e); break 'args; @@ -535,8 +534,10 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> break 'args; } }; + ctx.add_instruction(Instruction::PushReference); ctx.add_instruction(Instruction::IteratorRestIntoArray); // a. Return ? PutValue(lRef, A). + ctx.add_instruction(Instruction::PopReference); if let Err(e) = lref.put_value(ctx, ValueOutput::Value) { result = Err(e); break 'args; @@ -699,7 +700,6 @@ fn compile_assignment_target_property<'s>( place.get_value_maybe_keep_reference(ctx, has_rest)?; if has_rest { debug_assert!(place.has_reference()); - ctx.add_instruction(Instruction::PushReference); } // result: source.identifier // stack: [source?] @@ -721,7 +721,6 @@ fn compile_assignment_target_property<'s>( place.get_value_maybe_keep_reference(ctx, has_rest)?; if has_rest { debug_assert!(place.has_reference()); - ctx.add_instruction(Instruction::PushReference); } // result: source.property // stack: [source?] diff --git a/nova_vm/src/engine/bytecode/bytecode_compiler/for_in_of_statement.rs b/nova_vm/src/engine/bytecode/bytecode_compiler/for_in_of_statement.rs index b2d067c13..a4e4e071c 100644 --- a/nova_vm/src/engine/bytecode/bytecode_compiler/for_in_of_statement.rs +++ b/nova_vm/src/engine/bytecode/bytecode_compiler/for_in_of_statement.rs @@ -156,10 +156,25 @@ fn for_in_of_body_evaluation<'s, 'gc>( ctx.add_instruction(Instruction::Await); // c. If nextResult is not an Object, throw a TypeError exception. // d. Let done be ? IteratorComplete(nextResult). - let jump_to_done = ctx.add_instruction_with_jump_slot(Instruction::IteratorComplete); - // e. If done is true, return V. + + // nextResult now on top of stack and result. + ctx.add_instruction(Instruction::LoadCopy); + ctx.add_instruction(Instruction::IteratorComplete); + // done now in result, nextResult on top of stack. + ctx.add_instruction(Instruction::LoadStoreSwap); + // nextResult now in result, done on top of stack. + // f. Let nextValue be ? IteratorValue(nextResult). ctx.add_instruction(Instruction::IteratorValue); + ctx.add_instruction(Instruction::LoadStoreSwap); + // done back in result, nextValue on top of stack. + + // e. If done is true, return V. + let jump_to_done = ctx.add_instruction_with_jump_slot(Instruction::JumpIfTrue); + + // If done is not true, we want nextValue into result. + ctx.add_instruction(Instruction::Store); + jump_to_done } else { // Note: IteratorStepValue performs all of the following steps without @@ -356,21 +371,29 @@ fn for_in_of_body_evaluation<'s, 'gc>( // performed only when an abrupt completion (throw here) happens in the // stmt evaluation. But! UpdateEmpty is effectively only a stack pop // when result value exists, and in catch handling we _always_ have a - // result value. Thus, the UpdateEmpty here has zero effect except that - // it takes care of removing V from the stack, which would otherwise be - // leaked here. - ctx.add_instruction(Instruction::UpdateEmpty); + // result value. Thus, we just pop stack. + ctx.add_instruction(Instruction::PopStack); // i. Set the running execution context's LexicalEnvironment to oldEnv. // Note: the jump target has already returned to the old environment. match iteration_kind { // ii. If iteratorKind is ASYNC, IterationKind::AsyncIterate => { // return ? AsyncIteratorClose(iteratorRecord, status). + + // First load the thrown error onto the stack. + ctx.add_instruction(Instruction::Load); + // Then perform AsyncIteratorCloseWithError - it will return + // `true` if we can jump over Await. ctx.add_instruction(Instruction::AsyncIteratorCloseWithError); - // If AsyncIteratorCloseWithError ends up performing an Await then - // it will have added the thrown error into the stack: we need to - // rethrow it manually. - ctx.add_instruction(Instruction::PopExceptionJumpTarget); + let jump_over_await = ctx.add_instruction_with_jump_slot(Instruction::JumpIfTrue); + // We have to Await the result and ignore any rejections. + let try_catch = ctx.enter_try_catch_block(); + ctx.add_instruction(Instruction::Await); + let jump_to_error = try_catch.exit(ctx); + + // Ignore error completely. + ctx.set_jump_target_here(jump_to_error); + ctx.set_jump_target_here(jump_over_await); ctx.add_instruction(Instruction::Store); } // iii. If iterationKind is ENUMERATE, then @@ -414,6 +437,11 @@ fn for_in_of_body_evaluation<'s, 'gc>( // d. Let done be ? IteratorComplete(nextResult). // e. If done is true, return V. ctx.set_jump_target_here(jump_to_done); + // Top of stack is nextResult, followed by V. + if iteration_kind == IterationKind::AsyncIterate { + // AsyncIterate comes here with an extra nextValue on the stack. + ctx.add_instruction(Instruction::PopStack); + } ctx.add_instruction(Instruction::Store); if let Some(jump_over_return_v) = jump_over_return_v { ctx.set_jump_target_here(jump_over_return_v); diff --git a/nova_vm/src/engine/bytecode/executable.rs b/nova_vm/src/engine/bytecode/executable.rs index c9708d172..8f8334c53 100644 --- a/nova_vm/src/engine/bytecode/executable.rs +++ b/nova_vm/src/engine/bytecode/executable.rs @@ -8,8 +8,8 @@ use crate::{ SourceCode, SourceTextModule, String, Value, }, engine::{ - Bindable, NoGcScope, Scoped, bindable_handle, - bytecode::{CompileContext, NamedEvaluationParameter, instructions::Instr}, + Bindable, NoGcScope, bindable_handle, + bytecode::{CompileContext, NamedEvaluationParameter}, }, heap::{ ArenaAccess, CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, WorkQueues, @@ -208,49 +208,26 @@ impl<'gc> Executable<'gc> { ctx.finish() } - /// 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. - agent.heap.alloc_counter = agent - .heap - .alloc_counter - .saturating_sub(core::mem::size_of::()); - let _ = agent.heap.executables.pop().unwrap(); - } - } - /// SAFETY: The returned reference is valid until the Executable is garbage /// collected. #[inline] - fn get_instructions(self, agent: &Agent) -> &'static [u8] { + pub(crate) 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 { core::mem::transmute(&self.get(agent).instructions[..]) } } #[inline] - fn get_instruction(self, agent: &Agent, ip: &mut usize) -> Option { - Instr::consume_instruction(&self.get(agent).instructions, ip) - } - - #[inline] - fn get_constants<'a>(self, agent: &'a Agent, _: NoGcScope<'gc, '_>) -> &'a [Value<'gc>] { + pub(crate) fn get_constants<'a>( + self, + agent: &'a Agent, + _: NoGcScope<'gc, '_>, + ) -> &'a [Value<'gc>] { &self.get(agent).constants[..] } #[inline] - fn fetch_cache( + pub(crate) fn fetch_cache( self, agent: &Agent, index: usize, @@ -260,12 +237,22 @@ impl<'gc> Executable<'gc> { } #[inline] - fn fetch_constant(self, agent: &Agent, index: usize, gc: NoGcScope<'gc, '_>) -> Value<'gc> { + pub(crate) fn fetch_constant( + self, + agent: &Agent, + index: usize, + gc: NoGcScope<'gc, '_>, + ) -> Value<'gc> { self.get(agent).constants[index].bind(gc) } #[inline] - fn fetch_identifier(self, agent: &Agent, index: usize, gc: NoGcScope<'gc, '_>) -> String<'gc> { + pub(crate) fn fetch_identifier( + self, + agent: &Agent, + index: usize, + gc: NoGcScope<'gc, '_>, + ) -> String<'gc> { let value = self.get(agent).constants[index]; let Ok(value) = String::try_from(value) else { handle_identifier_failure() @@ -274,7 +261,7 @@ impl<'gc> Executable<'gc> { } #[inline] - fn fetch_property_key( + pub(crate) fn fetch_property_key( self, agent: &Agent, index: usize, @@ -285,7 +272,7 @@ impl<'gc> Executable<'gc> { unsafe { PropertyKey::from_value_unchecked(value).bind(gc) } } - fn fetch_function_expression<'a>( + pub(crate) fn fetch_function_expression<'a>( self, agent: &'a Agent, index: usize, @@ -294,7 +281,7 @@ impl<'gc> Executable<'gc> { &self.get(agent).function_expressions[index] } - fn fetch_arrow_function_expression<'a>( + pub(crate) fn fetch_arrow_function_expression<'a>( self, agent: &'a Agent, index: usize, @@ -305,7 +292,7 @@ impl<'gc> Executable<'gc> { &self.get(agent).arrow_function_expressions[index] } - fn fetch_class_initializer_bytecode( + pub(crate) fn fetch_class_initializer_bytecode( self, agent: &Agent, index: usize, @@ -314,7 +301,7 @@ impl<'gc> Executable<'gc> { self.get(agent).class_initializer_bytecodes[index] } - fn fetch_object_shape( + pub(crate) fn fetch_object_shape( self, agent: &Agent, index: usize, @@ -324,110 +311,6 @@ impl<'gc> Executable<'gc> { } } -impl Scoped<'_, Executable<'static>> { - #[inline] - pub(super) fn get_instructions(&self, agent: &Agent) -> &[u8] { - // SAFETY: Executable is scoped, the instructions reference is bound to - // the Scoped. - self.get(agent).get_instructions(agent) - } - - #[inline] - pub(super) fn get_instruction(&self, agent: &Agent, ip: &mut usize) -> Option { - self.get(agent).get_instruction(agent, ip) - } - - #[inline] - pub(super) fn get_constants<'a, 'gc>( - &self, - agent: &'a Agent, - gc: NoGcScope<'gc, '_>, - ) -> &'a [Value<'gc>] { - self.get(agent).get_constants(agent, gc) - } - - #[inline] - pub(super) fn fetch_cache<'gc>( - &self, - agent: &Agent, - index: usize, - gc: NoGcScope<'gc, '_>, - ) -> PropertyLookupCache<'gc> { - self.get(agent).fetch_cache(agent, index, gc) - } - - #[inline] - pub(super) fn fetch_identifier<'gc>( - &self, - agent: &Agent, - index: usize, - gc: NoGcScope<'gc, '_>, - ) -> String<'gc> { - self.get(agent).fetch_identifier(agent, index, gc) - } - - #[inline] - pub(super) fn fetch_property_key<'gc>( - &self, - agent: &Agent, - index: usize, - gc: NoGcScope<'gc, '_>, - ) -> PropertyKey<'gc> { - self.get(agent).fetch_property_key(agent, index, gc) - } - - #[inline] - pub(super) fn fetch_constant<'gc>( - &self, - agent: &Agent, - index: usize, - gc: NoGcScope<'gc, '_>, - ) -> Value<'gc> { - self.get(agent).fetch_constant(agent, index, gc) - } - - #[inline] - pub(super) fn fetch_function_expression<'a, 'gc>( - &self, - agent: &'a Agent, - index: usize, - gc: NoGcScope<'gc, '_>, - ) -> &'a FunctionExpression<'gc> { - self.get(agent).fetch_function_expression(agent, index, gc) - } - - #[inline] - pub(super) fn fetch_arrow_function_expression<'a>( - &self, - agent: &'a Agent, - index: usize, - ) -> &'a ArrowFunctionExpression { - self.get(agent) - .fetch_arrow_function_expression(agent, index) - } - - #[inline] - pub(super) fn fetch_class_initializer_bytecode<'gc>( - &self, - agent: &Agent, - index: usize, - gc: NoGcScope<'gc, '_>, - ) -> (Option>, bool) { - self.get(agent) - .fetch_class_initializer_bytecode(agent, index, gc) - } - - #[inline] - pub(super) fn fetch_object_shape<'gc>( - &self, - agent: &Agent, - index: usize, - gc: NoGcScope<'gc, '_>, - ) -> ObjectShape<'gc> { - self.get(agent).fetch_object_shape(agent, index, gc) - } -} - impl<'a> CreateHeapData, Executable<'a>> for Heap { fn create(&mut self, data: ExecutableHeapData<'a>) -> Executable<'a> { let index = u32::try_from(self.executables.len()).expect("Executables overflowed"); diff --git a/nova_vm/src/engine/bytecode/instructions.rs b/nova_vm/src/engine/bytecode/instructions.rs index 8490e4d7b..e7b9e5f30 100644 --- a/nova_vm/src/engine/bytecode/instructions.rs +++ b/nova_vm/src/engine/bytecode/instructions.rs @@ -7,7 +7,7 @@ use oxc_syntax::number::ToJsString; use crate::{ ecmascript::{Agent, String}, - engine::{Executable, NoGcScope, Scoped, bytecode::bytecode_compiler::IndexType}, + engine::{Executable, NoGcScope, bytecode::bytecode_compiler::IndexType}, }; /// ## Notes @@ -40,6 +40,9 @@ pub(crate) enum Instruction { JumpIfNot, /// Store ResolveBinding() in the reference register. ResolveBinding, + /// Evaluate the current top reference stack entry's key into a PropertyKey. + /// A copy is stored in the reference register. + EvaluateReference, /// Store [GetValue()](https://tc39.es/ecma262/#sec-getvalue) as the result /// value. /// @@ -56,11 +59,6 @@ pub(crate) enum Instruction { /// analysed from the AST. Non-reference values are already in the result /// value so a `GetValue` call would be a no-op. GetValueWithCache, - /// Same as GetValue without taking the reference slot. Used for reference - /// property updates and function calls (where `this` comes from the - /// reference). - GetValueKeepReference, - GetValueWithCacheKeepReference, /// Call PutValue() with the last reference on the reference stack and the /// result value. PutValue, @@ -77,10 +75,12 @@ pub(crate) enum Instruction { /// Perform InitializeReferencedBinding with parameters reference (V) and /// result (W). InitializeReferencedBinding, - /// Push the last evaluated reference, if any. + /// Push the last evaluated reference. PushReference, /// Pop the last stored reference. PopReference, + /// Push a copy of the last evaluated reference. + PushReferenceCopy, /// Perform EvaluatePropertyAccessWithIdentifierKey with the `baseValue` in /// the result register and the `propertyNameString` given as the first /// immediate argument, and store the result in the reference register. @@ -553,7 +553,6 @@ impl Instruction { | Self::ClassDefinePrivateProperty | Self::InitializeVariableEnvironment | Self::IteratorStepValue - | Self::IteratorComplete | Self::IteratorThrow | Self::IteratorReturn | Self::Jump @@ -584,7 +583,6 @@ impl Instruction { | Self::EvaluatePropertyAccessWithIdentifierKey | Self::EvaluateSuper | Self::GetValueWithCache - | Self::GetValueWithCacheKeepReference | Self::InstantiateArrowFunctionExpression | Self::InstantiateOrdinaryFunctionExpression | Self::LoadConstant @@ -617,10 +615,7 @@ impl Instruction { pub const fn has_cache_index(self) -> bool { matches!( self, - Self::GetValueWithCache - | Self::GetValueWithCacheKeepReference - | Self::PutValueWithCache - | Self::ResolveBindingWithCache + Self::GetValueWithCache | Self::PutValueWithCache | Self::ResolveBindingWithCache ) } @@ -681,8 +676,7 @@ impl Instruction { pub const fn has_jump_slot(self) -> bool { matches!( self, - Self::IteratorComplete - | Self::IteratorThrow + Self::IteratorThrow | Self::IteratorReturn | Self::Jump | Self::JumpIfNot @@ -719,7 +713,7 @@ pub(crate) struct Instr { } impl Instr { - pub(super) fn consume_instruction(instructions: &[u8], ip: &mut usize) -> Option { + pub(crate) fn consume_instruction(instructions: &[u8], ip: &mut usize) -> Option { let len = instructions.len(); let cur_ip = *ip; if cur_ip >= len { @@ -808,6 +802,10 @@ impl Instr { } } + pub(crate) fn get_first_stack_slot(&self, agent: &Agent) -> usize { + self.get_first_arg() as usize + agent.vm.get_stack_base() as usize + } + pub(crate) fn get_first_index(&self) -> usize { self.get_first_arg() as usize } @@ -865,13 +863,7 @@ impl Instr { jump as usize } - pub(crate) fn debug_print( - &self, - agent: &mut Agent, - ip: usize, - exe: Scoped, - gc: NoGcScope, - ) { + pub(crate) fn debug_print(&self, agent: &mut Agent, ip: usize, exe: Executable, gc: NoGcScope) { match self.kind.argument_count() { 0 => { eprintln!(" {}: {:?}", ip, self.kind); @@ -909,7 +901,7 @@ impl Instr { agent: &mut Agent, kind: Instruction, arg: IndexType, - exe: Scoped, + exe: Executable, gc: NoGcScope, ) -> std::string::String { let index = arg as usize; @@ -964,7 +956,7 @@ impl Instr { kind: Instruction, arg0: IndexType, arg1: IndexType, - exe: Scoped, + exe: Executable, gc: NoGcScope, ) -> std::string::String { match kind { @@ -1031,7 +1023,7 @@ impl Instr { fn debug_print_constant( agent: &mut Agent, - exe: Scoped, + exe: Executable, index: usize, gc: NoGcScope, ) -> std::string::String { @@ -1048,7 +1040,7 @@ fn debug_print_constant( fn debug_print_identifier( agent: &Agent, - exe: Scoped, + exe: Executable, index: usize, gc: NoGcScope, ) -> std::string::String { @@ -1206,9 +1198,7 @@ impl TryFrom for Instruction { Instruction::MakeSuperPropertyReferenceWithIdentifierKey.as_u8(); const GETVALUE: u8 = Instruction::GetValue.as_u8(); const GETVALUEWITHCACHE: u8 = Instruction::GetValueWithCache.as_u8(); - const GETVALUEKEEPREFERENCE: u8 = Instruction::GetValueKeepReference.as_u8(); - const GETVALUEWITHCACHEKEEPREFERENCE: u8 = - Instruction::GetValueWithCacheKeepReference.as_u8(); + const EVALUATE_REFERENCE: u8 = Instruction::EvaluateReference.as_u8(); const GREATERTHAN: u8 = Instruction::GreaterThan.as_u8(); const GREATERTHANEQUALS: u8 = Instruction::GreaterThanEquals.as_u8(); const HASPROPERTY: u8 = Instruction::HasProperty.as_u8(); @@ -1262,6 +1252,7 @@ impl TryFrom for Instruction { const POPREFERENCE: u8 = Instruction::PopReference.as_u8(); const PUSHEXCEPTIONJUMPTARGET: u8 = Instruction::PushExceptionJumpTarget.as_u8(); const PUSHREFERENCE: u8 = Instruction::PushReference.as_u8(); + const PUSHREFERENCECOPY: u8 = Instruction::PushReferenceCopy.as_u8(); const PUTVALUE: u8 = Instruction::PutValue.as_u8(); const PUTVALUEWITHCACHE: u8 = Instruction::PutValueWithCache.as_u8(); const RESOLVEBINDING: u8 = Instruction::ResolveBinding.as_u8(); @@ -1373,8 +1364,7 @@ impl TryFrom for Instruction { } GETVALUE => Ok(Instruction::GetValue), GETVALUEWITHCACHE => Ok(Instruction::GetValueWithCache), - GETVALUEKEEPREFERENCE => Ok(Instruction::GetValueKeepReference), - GETVALUEWITHCACHEKEEPREFERENCE => Ok(Instruction::GetValueWithCacheKeepReference), + EVALUATE_REFERENCE => Ok(Instruction::EvaluateReference), GREATERTHAN => Ok(Instruction::GreaterThan), GREATERTHANEQUALS => Ok(Instruction::GreaterThanEquals), HASPROPERTY => Ok(Instruction::HasProperty), @@ -1428,6 +1418,7 @@ impl TryFrom for Instruction { POPREFERENCE => Ok(Instruction::PopReference), PUSHEXCEPTIONJUMPTARGET => Ok(Instruction::PushExceptionJumpTarget), PUSHREFERENCE => Ok(Instruction::PushReference), + PUSHREFERENCECOPY => Ok(Instruction::PushReferenceCopy), PUTVALUE => Ok(Instruction::PutValue), PUTVALUEWITHCACHE => Ok(Instruction::PutValueWithCache), RESOLVEBINDING => Ok(Instruction::ResolveBinding), diff --git a/nova_vm/src/engine/bytecode/iterator.rs b/nova_vm/src/engine/bytecode/iterator.rs index a1ce8ce8a..b7a65a81c 100644 --- a/nova_vm/src/engine/bytecode/iterator.rs +++ b/nova_vm/src/engine/bytecode/iterator.rs @@ -165,17 +165,14 @@ impl<'a> ActiveIterator<'a> { } pub(super) fn get<'agent>(&self, agent: &'agent Agent) -> &'agent VmIteratorRecord<'a> { - // SAFETY: VM is held exclusively in an above call stack. - unsafe { agent.vm_stack.last().expect("No VM found").as_ref() }.get_active_iterator() + agent.vm.get_active_iterator() } fn get_mut<'agent>( &mut self, agent: &'agent mut Agent, ) -> &'agent mut VmIteratorRecord<'static> { - // SAFETY: VM is held exclusively in an above call stack. - unsafe { agent.vm_stack.last_mut().expect("No VM found").as_mut() } - .get_active_iterator_mut() + agent.vm.get_active_iterator_mut() } } diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index c7febba87..f769aa442 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -7,7 +7,7 @@ mod execute_instructions; use execute_instructions::*; -use std::{hint::unreachable_unchecked, ptr::NonNull}; +use std::hint::unreachable_unchecked; use wtf8::Wtf8Buf; use crate::{ @@ -19,11 +19,12 @@ use crate::{ try_get_object_method, try_result_into_option_js, }, engine::{ - Bindable, GcScope, NoGcScope, Scopable, Scoped, bindable_handle, + Bindable, GcScope, NoGcScope, Scopable, bindable_handle, bytecode::{ - Executable, IndexType, Instruction, InstructionIter, instructions::Instr, + IndexType, Instruction, InstructionIter, instructions::Instr, iterator::VmIteratorRecord, }, + trivially_bindable, }, heap::{CompactionLists, HeapMarkAndSweep, WellKnownSymbols, WorkQueues}, }; @@ -62,21 +63,17 @@ enum ContinuationKind { Yield, Await, } +trivially_bindable!(ContinuationKind); /// VM exception handler. #[derive(Debug)] -enum ExceptionHandler<'a> { - /// Indicates a jump to catch block. - CatchBlock { - /// Instruction pointer. - ip: u32, - /// The lexical environment which contains this exception jump target. - lexical_environment: Environment<'a>, - }, - /// Indicates that any error should be ignored and the next instruction - /// skipped. This is used in AsyncIteratorClose handling. - IgnoreErrorAndNextInstruction, +pub(crate) struct ExceptionHandler<'a> { + /// Instruction pointer. + ip: u32, + /// The lexical environment which contains this exception jump target. + lexical_environment: Environment<'a>, } +bindable_handle!(ExceptionHandler); /// ## Notes /// @@ -87,9 +84,15 @@ pub(crate) struct Vm { /// Instruction pointer. ip: usize, stack: Vec>, + stack_base: u32, reference_stack: Vec>, + #[cfg(debug_assertions)] + reference_stack_base: u32, iterator_stack: Vec>, + #[cfg(debug_assertions)] + iterator_stack_base: u32, exception_handler_stack: Vec>, + exception_handler_stack_base: u32, result: Option>, reference: Option>, } @@ -111,112 +114,191 @@ pub(crate) struct SuspendedVm { /// Note: Exception jump stack is non-empty only if the code awaits inside a /// try block or an await for-of loop. This means that often no heap data /// clone is required. - exception_jump_target_stack: Box<[ExceptionHandler<'static>]>, + exception_handler_stack: Box<[ExceptionHandler<'static>]>, } impl SuspendedVm { - pub(crate) fn resume<'gc>( - self, - agent: &mut Agent, - executable: Scoped, - value: Value, - gc: GcScope<'gc, '_>, - ) -> ExecutionResult<'gc> { - if agent.options.print_internals { - eprintln!("Resuming function with value\n"); - } - let vm = Vm::from_suspended(self); - vm.resume(agent, executable, value, gc) + pub(crate) fn get_ip_mut(&mut self) -> &mut usize { + &mut self.ip } - pub(crate) fn resume_throw<'gc>( - self, - agent: &mut Agent, - executable: Scoped, - err: Value, - gc: GcScope<'gc, '_>, - ) -> ExecutionResult<'gc> { - if agent.options.print_internals { - eprintln!("Resuming function with error\n"); - } - // Optimisation: Avoid unsuspending the Vm if we're just going to throw - // out of it immediately. - if self.exception_jump_target_stack.is_empty() { - let err = JsError::new(err.unbind()); - return ExecutionResult::Throw(err); - } - let vm = Vm::from_suspended(self); - vm.resume_throw(agent, executable, err, gc) + #[inline(always)] + pub(crate) fn has_catch_handler(&self) -> bool { + !self.exception_handler_stack.is_empty() } +} - pub(crate) fn resume_return<'gc>( - mut self, - agent: &mut Agent, - executable: Scoped, - result: Value, - gc: GcScope<'gc, '_>, - ) -> ExecutionResult<'gc> { - if agent.options.print_internals { - eprintln!("Resuming function with return\n"); - } - // Following a yield point, the next instruction is a Jump to the - // Normal continue handling. We need to ignore that. - let next_instruction = executable.get_instruction(agent, &mut self.ip); - assert_eq!(next_instruction.map(|i| i.kind), Some(Instruction::Jump)); - let peek_next_instruction = executable.get_instructions(agent).get(self.ip).copied(); - if peek_next_instruction == Some(Instruction::Return.as_u8()) { - // Our return handling is to just return; we can do that without - // unsuspending the VM. - return ExecutionResult::Return(result.bind(gc.into_nogc())); +impl Vm { + fn suspend( + &mut self, + (stack_base, reference_stack_base, iterator_stack_base, exception_handler_stack_base): ( + u32, + u32, + u32, + u32, + ), + ) -> SuspendedVm { + debug_assert!(self.stack.len() >= stack_base as usize); + debug_assert!(self.reference_stack.len() >= reference_stack_base as usize); + debug_assert!(self.iterator_stack.len() >= iterator_stack_base as usize); + debug_assert!(self.exception_handler_stack.len() >= exception_handler_stack_base as usize); + SuspendedVm { + ip: self.ip, + stack: self.stack.split_off(stack_base as usize).into_boxed_slice(), + reference_stack: self + .reference_stack + .split_off(reference_stack_base as usize) + .into_boxed_slice(), + iterator_stack: self + .iterator_stack + .split_off(iterator_stack_base as usize) + .into_boxed_slice(), + exception_handler_stack: self + .exception_handler_stack + .split_off(exception_handler_stack_base as usize) + .into_boxed_slice(), } - let vm = Vm::from_suspended(self); - vm.resume(agent, executable, result, gc) } -} -impl Vm { - fn new() -> Self { - Self { - ip: 0, - stack: Vec::with_capacity(32), - reference_stack: Vec::new(), - iterator_stack: Vec::new(), - exception_handler_stack: Vec::new(), - result: None, - reference: None, + pub(crate) fn get_instruction_pointer(&mut self) -> usize { + self.ip + } + + pub(crate) fn set_instruction_pointer(&mut self, ip: usize) { + self.ip = ip; + } + + pub(crate) fn get_stack_base(&self) -> u32 { + self.stack_base + } + + pub(crate) fn set_stack_bases( + &mut self, + stack_base: u32, + #[cfg(debug_assertions)] reference_stack_base: u32, + #[cfg(debug_assertions)] iterator_stack_base: u32, + exception_handler_stack_base: u32, + ) { + self.stack_base = stack_base; + #[cfg(debug_assertions)] + { + self.reference_stack_base = reference_stack_base; + self.iterator_stack_base = iterator_stack_base; } + self.exception_handler_stack_base = exception_handler_stack_base; } - fn suspend(self) -> SuspendedVm { - SuspendedVm { - ip: self.ip, - stack: self.stack.into_boxed_slice(), - reference_stack: self.reference_stack.into_boxed_slice(), - iterator_stack: self.iterator_stack.into_boxed_slice(), - exception_jump_target_stack: self.exception_handler_stack.into_boxed_slice(), + pub(crate) fn push_stack_frame(&mut self) -> (u32, u32, u32, u32) { + let stack_base = u32::try_from(self.stack.len()).unwrap(); + self.stack_base = stack_base; + let reference_stack_base = u32::try_from(self.reference_stack.len()).unwrap(); + let iterator_stack_base = u32::try_from(self.iterator_stack.len()).unwrap(); + let exception_handler_stack_base = + u32::try_from(self.exception_handler_stack.len()).unwrap(); + self.exception_handler_stack_base = exception_handler_stack_base; + #[cfg(debug_assertions)] + { + self.reference_stack_base = reference_stack_base; + self.iterator_stack_base = iterator_stack_base; } + ( + stack_base, + reference_stack_base, + iterator_stack_base, + exception_handler_stack_base, + ) } - fn from_suspended(suspended: SuspendedVm) -> Self { - Self { - ip: suspended.ip, - stack: suspended.stack.into_vec(), - reference_stack: suspended.reference_stack.into_vec(), - iterator_stack: suspended.iterator_stack.into_vec(), - exception_handler_stack: suspended.exception_jump_target_stack.into_vec(), - result: None, - reference: None, + pub(crate) fn pop_stack_frame(&mut self, stack_frame: (u32, u32, u32, u32)) { + let (stack_base, reference_stack_base, iterator_stack_base, exception_handler_stack_base) = + stack_frame; + // Truncate the stacks to what we're told. + debug_assert!(self.stack.len() >= stack_base as usize); + debug_assert!(self.reference_stack.len() >= reference_stack_base as usize); + debug_assert!(self.iterator_stack.len() >= iterator_stack_base as usize); + debug_assert!(self.exception_handler_stack.len() >= exception_handler_stack_base as usize); + self.stack.truncate(stack_base as usize); + self.reference_stack.truncate(reference_stack_base as usize); + self.iterator_stack.truncate(iterator_stack_base as usize); + self.exception_handler_stack + .truncate(exception_handler_stack_base as usize); + } + + pub(crate) fn clear_stack(&mut self) { + self.stack.clear(); + self.reference_stack.clear(); + self.iterator_stack.clear(); + self.exception_handler_stack.clear(); + self.ip = 0; + self.stack_base = 0; + self.reference = None; + self.result = None; + #[cfg(debug_assertions)] + { + self.reference_stack_base = 0; + self.iterator_stack_base = 0; } + self.exception_handler_stack_base = 0; + } + + pub(crate) fn pop_stack<'gc>(&mut self, gc: NoGcScope<'gc, '_>) -> Value<'gc> { + debug_assert!(self.stack.len() > self.stack_base as usize); + self.stack + .pop() + .expect("Attempted to pop from an empty stack") + .bind(gc) + } + + pub(crate) fn get_last_stack_value<'gc>(&mut self, gc: NoGcScope<'gc, '_>) -> Value<'gc> { + debug_assert!(self.stack.len() > self.stack_base as usize); + self.stack + .last() + .expect("Attempted to pop from an empty stack") + .bind(gc) + } + + pub(crate) fn take_result<'gc>(&mut self, gc: NoGcScope<'gc, '_>) -> Value<'gc> { + self.result + .take() + .expect("Attempted to take an empty result") + .bind(gc) + } + + pub(crate) fn take_any_result<'gc>(&mut self, gc: NoGcScope<'gc, '_>) -> Option> { + self.result.take().bind(gc) + } + + pub(crate) fn unsuspend( + &mut self, + SuspendedVm { + ip, + stack, + reference_stack, + iterator_stack, + exception_handler_stack, + }: SuspendedVm, + ) { + self.ip = ip; + self.stack.extend(stack); + self.reference_stack.extend(reference_stack); + self.iterator_stack.extend(iterator_stack); + self.exception_handler_stack.extend(exception_handler_stack); } /// Executes an executable using the virtual machine. pub(crate) fn execute<'gc>( agent: &mut Agent, - executable: Scoped, - arguments: Option<&mut [Value<'static>]>, + arguments: Option<&mut [Value]>, gc: GcScope<'gc, '_>, ) -> ExecutionResult<'gc> { - let mut vm = Vm::new(); + debug_assert_eq!( + agent.vm.result, None, + "In development mode result register should always be cleared before a call - this indicates a bug in the bytecode leading to the result register getting clobbered by an unexpected function call.", + ); + debug_assert_eq!( + agent.vm.reference, None, + "In development mode reference register should always be cleared before a call - this indicates a bug in the bytecode leading to the reference register getting clobbered by an unexpected function call." + ); if let Some(arguments) = arguments { ArgumentsList::from_mut_slice(arguments).with_scoped( @@ -230,26 +312,29 @@ impl Vm { arguments, ) }; - vm.iterator_stack + agent + .vm + .iterator_stack .push(VmIteratorRecord::SliceIterator(arguments)); if agent.options.print_internals { - vm.print_internals(agent, executable.clone(), gc.nogc()); + Vm::print_internals(agent, gc.nogc()); } - vm.inner_execute(agent, executable, gc) + Vm::inner_execute(agent, gc) }, gc, ) } else { if agent.options.print_internals { - vm.print_internals(agent, executable.clone(), gc.nogc()); + Vm::print_internals(agent, gc.nogc()); } - vm.inner_execute(agent, executable, gc) + Vm::inner_execute(agent, gc) } } - fn print_internals(&self, agent: &mut Agent, executable: Scoped, gc: NoGcScope) { + fn print_internals(agent: &mut Agent, gc: NoGcScope) { + let executable = agent.current_executable(gc); eprintln!(); eprintln!("=== Executing Executable ==="); eprintln!("Constants: {:?}", executable.get_constants(agent, gc)); @@ -258,68 +343,67 @@ impl Vm { eprintln!("Instructions:"); let iter = InstructionIter::new(executable.get_instructions(agent)); for (ip, instr) in iter { - instr.debug_print(agent, ip, executable.clone(), gc); + instr.debug_print(agent, ip, executable, gc); } eprintln!(); } - fn resume<'gc>( - mut self, + pub(crate) fn resume<'gc>( agent: &mut Agent, - executable: Scoped, value: Value, gc: GcScope<'gc, '_>, ) -> ExecutionResult<'gc> { - self.result = Some(value.unbind()); - self.inner_execute(agent, executable, gc) + agent.vm.result = Some(value.unbind()); + Vm::inner_execute(agent, gc) } - fn resume_throw<'gc>( - mut self, + pub(crate) fn resume_throw<'gc>( agent: &mut Agent, - executable: Scoped, err: Value, gc: GcScope<'gc, '_>, ) -> ExecutionResult<'gc> { let err = err.bind(gc.nogc()); let err = JsError::new(err.unbind()); - if !self.handle_error(agent, err) { + if !Vm::handle_error(agent, err) { if agent.options.print_internals { - eprintln!("Exiting function with error\n"); + Vm::print_exiting_with_error(); } + // Clear the result on throw. + agent.vm.result = None; + agent.vm.reference = None; return ExecutionResult::Throw(err); } - self.inner_execute(agent, executable, gc) + Vm::inner_execute(agent, gc) } - fn inner_execute<'gc>( - mut self, - agent: &mut Agent, - executable: Scoped, - mut gc: GcScope<'gc, '_>, - ) -> ExecutionResult<'gc> { + fn inner_execute<'gc>(agent: &mut Agent, mut gc: GcScope<'gc, '_>) -> ExecutionResult<'gc> { + // SAFETY: The instructions buffer is owned by the current Executable, + // which is currently rooted by the execution context stack. The buffer + // is not accessible as exclusive excecpt for dropping the Executable's + // heap data, and isn't dropped while the Executable is rooted. Thus, + // the instructions slice is guaranteed to be valid while we perform the + // execution inner loop. + let instructions = + unsafe { core::mem::transmute::<&[u8], &[u8]>(agent.current_instructions()) }; let stack_depth = agent.stack_refs.borrow().len(); - let instructions = executable.get_instructions(agent); - while let Some(instr) = Instr::consume_instruction(instructions, &mut self.ip) { + let print_internals = agent.options.print_internals; + while let Some(instr) = Instr::consume_instruction(instructions, &mut agent.vm.ip) { if agent.check_gc() { - self.trigger_gc(agent, gc.reborrow()); + agent.gc(gc.reborrow()); } - if agent.options.print_internals { + if print_internals { Self::print_executing(instr.kind); } - let result = Self::execute_instruction( - agent, - &mut self, - executable.clone(), - instr, - gc.reborrow(), - ); + let result = Self::execute_instruction(agent, instr, gc.reborrow()); match result { Ok(ContinuationKind::Normal) => {} // SAFETY: result is not Ok(ContinuationKind::Normal). _ => unsafe { - if let Some(r) = self.handle_execute_instruction_abnormal_result(agent, result) - { + if let Some(r) = Vm::handle_execute_instruction_abnormal_result( + agent, + result.unbind(), + gc.nogc(), + ) { return r.unbind().bind(gc.into_nogc()); } }, @@ -327,6 +411,9 @@ impl Vm { agent.stack_refs.borrow_mut().truncate(stack_depth); } + // Clear result on "fall-off-the-edge". + agent.vm.result = None; + agent.vm.reference = None; ExecutionResult::Return(Value::Undefined) } @@ -336,9 +423,9 @@ impl Vm { #[inline(never)] #[cold] unsafe fn handle_execute_instruction_abnormal_result<'a>( - &mut self, agent: &mut Agent, result: JsResult<'a, ContinuationKind>, + gc: NoGcScope<'a, '_>, ) -> Option> { match result { // SAFETY: method only called if result is not normal. @@ -347,16 +434,20 @@ impl Vm { if agent.options.print_internals { Self::print_exiting(); } - let result = self.result.unwrap_or(Value::Undefined); + let result = agent.vm.take_any_result(gc).unwrap_or(Value::Undefined); + agent.vm.reference = None; Some(ExecutionResult::Return(result)) } Ok(ContinuationKind::Yield) => { - let yielded_value = self.result.take().unwrap(); + let yielded_value = agent.vm.take_result(gc); + agent.vm.reference = None; if agent.options.print_internals { Self::print_yielding(yielded_value); } Some(ExecutionResult::Yield { - vm: core::mem::take(self).suspend(), + vm: agent + .vm + .suspend(agent.running_execution_context().get_stack_bases()), yielded_value, }) } @@ -364,16 +455,22 @@ impl Vm { if agent.options.print_internals { Self::print_awaiting(); } - let Value::Promise(promise) = self.result.take().unwrap() else { + let Value::Promise(promise) = agent.vm.take_result(gc) else { unreachable!() }; + agent.vm.reference = None; Some(ExecutionResult::Await { - vm: core::mem::take(self).suspend(), + vm: agent + .vm + .suspend(agent.running_execution_context().get_stack_bases()), promise, }) } Err(err) => { - if !self.handle_error(agent, err) { + // Clear the result on error. + agent.vm.result = None; + agent.vm.reference = None; + if !Vm::handle_error(agent, err) { if agent.options.print_internals { Self::print_exiting_with_error(); } @@ -385,12 +482,6 @@ impl Vm { } } - #[inline(never)] - #[cold] - fn trigger_gc(&mut self, agent: &mut Agent, gc: GcScope) { - with_vm_gc(agent, self, |agent, gc| agent.gc(gc), gc); - } - #[inline(never)] #[cold] fn print_executing(instruction: Instruction) { @@ -424,28 +515,19 @@ impl Vm { #[inline(never)] #[cold] #[must_use] - fn handle_error(&mut self, agent: &mut Agent, err: JsError) -> bool { - if let Some(handler) = self.exception_handler_stack.pop() { - match handler { - ExceptionHandler::CatchBlock { - ip, - lexical_environment, - } => { - if agent.options.print_internals { - eprintln!("Error: {:?}", err.value()); - eprintln!("Jumping to catch block in {ip}\n"); - } - self.ip = ip as usize; - agent.set_current_lexical_environment(lexical_environment); - self.result = Some(err.value().unbind()); - } - ExceptionHandler::IgnoreErrorAndNextInstruction => { - if agent.options.print_internals { - eprintln!("Ignoring throw error and skipping to {}\n", self.ip + 1); - } - self.ip += 1; - } + fn handle_error(agent: &mut Agent, err: JsError) -> bool { + if let Some(ExceptionHandler { + ip, + lexical_environment, + }) = agent.vm.pop_exception_handler_stack() + { + if agent.options.print_internals { + eprintln!("Error: {:?}", err.value()); + eprintln!("Jumping to catch block in {ip}\n"); } + agent.vm.ip = ip as usize; + agent.set_current_lexical_environment(lexical_environment); + agent.vm.result = Some(err.value().unbind()); true } else { false @@ -454,83 +536,78 @@ impl Vm { fn execute_instruction<'a>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, gc: GcScope<'a, '_>, ) -> JsResult<'a, ContinuationKind> { // Hot instructions; apply #[inline(always)] to the execute methods. match instr.kind { Instruction::Load => { - vm.execute_load(); + agent.vm.execute_load(); } Instruction::LoadCopy => { - vm.execute_load_copy(); + agent.vm.execute_load_copy(); } Instruction::PutValueToIndex => { - vm.execute_load_to_index(instr.get_first_index()); + agent + .vm + .execute_load_to_index(instr.get_first_stack_slot(agent)); } Instruction::Store => { - vm.execute_store(); + agent.vm.execute_store(); } Instruction::GetValueFromIndex => { - vm.execute_store_from_index(instr.get_first_index()); + agent + .vm + .execute_store_from_index(instr.get_first_stack_slot(agent)); } Instruction::StoreConstant => { - execute_store_constant(agent, vm, executable, instr, gc.into_nogc()); + execute_store_constant(agent, instr, gc.into_nogc()); } Instruction::PopStack => { - vm.execute_pop_stack(); + agent.vm.execute_pop_stack(); } - Instruction::Jump => execute_jump(agent, vm, instr), - Instruction::JumpIfNot => execute_jump_if_not(agent, vm, instr), + Instruction::Jump => execute_jump(agent, instr), + Instruction::JumpIfNot => execute_jump_if_not(agent, instr, gc.into_nogc()), Instruction::ResolveBinding => { - execute_resolve_binding(agent, vm, executable, instr, gc)?; + execute_resolve_binding(agent, instr, gc)?; } - Instruction::GetValue - | Instruction::GetValueWithCache - | Instruction::GetValueKeepReference - | Instruction::GetValueWithCacheKeepReference => { - let cache = matches!( - instr.kind, - Instruction::GetValueWithCache | Instruction::GetValueWithCacheKeepReference - ); - let keep_reference = matches!( - instr.kind, - Instruction::GetValueKeepReference - | Instruction::GetValueWithCacheKeepReference - ); - execute_get_value(agent, vm, executable, instr, cache, keep_reference, gc)? + Instruction::GetValue | Instruction::GetValueWithCache => { + execute_get_value( + agent, + instr, + instr.kind == Instruction::GetValueWithCache, + gc, + )?; } - Instruction::PutValue | Instruction::PutValueWithCache => { - let cache = matches!(instr.kind, Instruction::PutValueWithCache); - execute_put_value(agent, vm, executable, instr, cache, gc)? + Instruction::EvaluateReference => { + execute_evaluate_reference(agent, gc)?; } - Instruction::ToNumeric => execute_to_numeric(agent, vm, gc)?, + Instruction::PutValue | Instruction::PutValueWithCache => execute_put_value( + agent, + instr, + instr.kind == Instruction::PutValueWithCache, + gc, + )?, + Instruction::ToNumeric => execute_to_numeric(agent, gc)?, Instruction::CreateImmutableBinding => { - execute_create_immutable_binding(agent, executable, instr, gc.into_nogc()) + execute_create_immutable_binding(agent, instr, gc.into_nogc()) } Instruction::CreateMutableBinding => { - execute_create_mutable_binding(agent, executable, instr, gc.into_nogc()) + execute_create_mutable_binding(agent, instr, gc.into_nogc()) } Instruction::InitializeReferencedBinding => { - execute_initialize_referenced_binding(agent, vm, gc.into_nogc()) + execute_initialize_referenced_binding(agent, gc.into_nogc()) } - Instruction::PushReference => vm.execute_push_reference(), - Instruction::PopReference => vm.execute_pop_reference(), + Instruction::PushReference => agent.vm.execute_push_reference(), + Instruction::PopReference => agent.vm.execute_pop_reference(), + Instruction::PushReferenceCopy => agent.vm.execute_push_reference_copy(), Instruction::EvaluatePropertyAccessWithIdentifierKey => { - execute_evaluate_property_access_with_identifier_key( - agent, - vm, - executable, - instr, - gc.into_nogc(), - ) + execute_evaluate_property_access_with_identifier_key(agent, instr, gc.into_nogc()) } Instruction::EvaluatePropertyAccessWithExpressionKey => { - execute_evaluate_property_access_with_expression_key(agent, vm, gc.into_nogc()) + execute_evaluate_property_access_with_expression_key(agent, gc.into_nogc()) } - _ => return Self::execute_cold_instruction(agent, vm, executable, instr, gc), + _ => return Self::execute_cold_instruction(agent, instr, gc), } Ok(ContinuationKind::Normal) @@ -539,8 +616,6 @@ impl Vm { #[inline(never)] fn execute_cold_instruction<'a>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, gc: GcScope<'a, '_>, ) -> JsResult<'a, ContinuationKind> { @@ -557,8 +632,7 @@ impl Vm { | Instruction::ResolveBinding | Instruction::GetValue | Instruction::GetValueWithCache - | Instruction::GetValueKeepReference - | Instruction::GetValueWithCacheKeepReference + | Instruction::EvaluateReference | Instruction::PutValue | Instruction::PutValueWithCache | Instruction::ToNumeric @@ -567,34 +641,33 @@ impl Vm { | Instruction::InitializeReferencedBinding | Instruction::PushReference | Instruction::PopReference + | Instruction::PushReferenceCopy | Instruction::EvaluatePropertyAccessWithIdentifierKey | Instruction::EvaluatePropertyAccessWithExpressionKey => { unreachable!("hot instruction not handled before execute_cold_instruction") } Instruction::Return => return Ok(ContinuationKind::Return), Instruction::Await => { - execute_await_promise_resolve(agent, vm, gc)?; + execute_await_promise_resolve(agent, gc)?; return Ok(ContinuationKind::Await); } Instruction::Yield => return Ok(ContinuationKind::Yield), - Instruction::IsStrictlyEqual => execute_is_strictly_equal(agent, vm, gc.into_nogc()), - Instruction::IsNullOrUndefined => vm.execute_is_null_or_undefined(), - Instruction::IsNull => vm.execute_is_null(), - Instruction::IsUndefined => vm.execute_is_undefined(), - Instruction::IsObject => vm.execute_is_object(), - Instruction::IsConstructor => execute_is_constructor(agent, vm), - Instruction::JumpIfTrue => execute_jump_if_true(agent, vm, instr), - Instruction::LoadReplace => vm.execute_load_replace(), - Instruction::LoadConstant => { - execute_load_constant(agent, vm, executable, instr, gc.into_nogc()) - } - Instruction::LoadStoreSwap => vm.execute_load_store_swap(), - Instruction::UpdateEmpty => vm.execute_update_empty(), - Instruction::Swap => vm.execute_swap(), - Instruction::Empty => vm.execute_empty(), - Instruction::LogicalNot => execute_logical_not(agent, vm), + Instruction::IsStrictlyEqual => execute_is_strictly_equal(agent, gc.into_nogc()), + Instruction::IsNullOrUndefined => agent.vm.execute_is_null_or_undefined(), + Instruction::IsNull => agent.vm.execute_is_null(), + Instruction::IsUndefined => agent.vm.execute_is_undefined(), + Instruction::IsObject => agent.vm.execute_is_object(), + Instruction::IsConstructor => execute_is_constructor(agent, gc.into_nogc()), + Instruction::JumpIfTrue => execute_jump_if_true(agent, instr, gc.into_nogc()), + Instruction::LoadReplace => agent.vm.execute_load_replace(), + Instruction::LoadConstant => execute_load_constant(agent, instr, gc.into_nogc()), + Instruction::LoadStoreSwap => agent.vm.execute_load_store_swap(), + Instruction::UpdateEmpty => agent.vm.execute_update_empty(), + Instruction::Swap => agent.vm.execute_swap(), + Instruction::Empty => agent.vm.execute_empty(), + Instruction::LogicalNot => execute_logical_not(agent, gc.into_nogc()), Instruction::ApplyAdditionBinaryOperator => { - execute_apply_addition_binary_operator(agent, vm, gc)? + execute_apply_addition_binary_operator(agent, gc)? } Instruction::ApplySubtractionBinaryOperator | Instruction::ApplyMultiplicationBinaryOperator @@ -607,121 +680,107 @@ impl Vm { | Instruction::ApplyBitwiseORBinaryOperator | Instruction::ApplyBitwiseXORBinaryOperator | Instruction::ApplyBitwiseAndBinaryOperator => { - execute_apply_binary_operator(agent, vm, instr.kind, gc)? + execute_apply_binary_operator(agent, instr.kind, gc)? } - Instruction::ArrayCreate => execute_array_create(agent, vm, instr, gc.into_nogc())?, - Instruction::ArrayPush => execute_array_push(agent, vm, gc)?, - Instruction::ArrayElision => execute_array_elision(agent, vm, gc)?, - Instruction::BitwiseNot => execute_bitwise_not(agent, vm, gc.into_nogc())?, + Instruction::ArrayCreate => execute_array_create(agent, instr, gc.into_nogc())?, + Instruction::ArrayPush => execute_array_push(agent, gc)?, + Instruction::ArrayElision => execute_array_elision(agent, gc)?, + Instruction::BitwiseNot => execute_bitwise_not(agent, gc.into_nogc())?, Instruction::CreateUnmappedArgumentsObject => { - execute_create_unmapped_arguments_object(agent, vm, gc.into_nogc())? + execute_create_unmapped_arguments_object(agent, gc.into_nogc())? } - Instruction::CopyDataProperties => execute_copy_data_properties(agent, vm, gc)?, + Instruction::CopyDataProperties => execute_copy_data_properties(agent, gc)?, Instruction::CopyDataPropertiesIntoObject => { - execute_copy_data_properties_into_object(agent, vm, instr, gc)? + execute_copy_data_properties_into_object(agent, instr, gc)? } - Instruction::Delete => execute_delete(agent, vm, gc)?, - Instruction::DirectEvalCall => execute_direct_eval_call(agent, vm, instr, gc)?, - Instruction::EvaluateCall => execute_evaluate_call(agent, vm, instr, gc)?, - Instruction::EvaluateNew => execute_evaluate_new(agent, vm, instr, gc)?, - Instruction::EvaluateSuper => execute_evaluate_super(agent, vm, instr, gc)?, + Instruction::Delete => execute_delete(agent, gc)?, + Instruction::DirectEvalCall => execute_direct_eval_call(agent, instr, gc)?, + Instruction::EvaluateCall => execute_evaluate_call(agent, instr, gc)?, + Instruction::EvaluateNew => execute_evaluate_new(agent, instr, gc)?, + Instruction::EvaluateSuper => execute_evaluate_super(agent, instr, gc)?, Instruction::MakePrivateReference => { - execute_make_private_reference(agent, vm, executable, instr, gc.into_nogc()) + execute_make_private_reference(agent, instr, gc.into_nogc()) } Instruction::MakeSuperPropertyReferenceWithExpressionKey => { - execute_make_super_property_reference_with_expression_key( - agent, - vm, - gc.into_nogc(), - )? + execute_make_super_property_reference_with_expression_key(agent, gc.into_nogc())? } Instruction::MakeSuperPropertyReferenceWithIdentifierKey => { execute_make_super_property_reference_with_identifier_key( agent, - vm, - executable, instr, gc.into_nogc(), )? } - Instruction::GreaterThan => execute_greater_than(agent, vm, gc)?, - Instruction::GreaterThanEquals => execute_greater_than_equals(agent, vm, gc)?, - Instruction::HasProperty => execute_has_property(agent, vm, gc)?, - Instruction::HasPrivateElement => { - execute_has_private_element(agent, vm, gc.into_nogc())? - } - Instruction::Increment => execute_increment(agent, vm, gc.into_nogc()), - Instruction::Decrement => execute_decrement(agent, vm, gc.into_nogc()), - Instruction::InstanceofOperator => execute_instanceof_operator(agent, vm, gc)?, + Instruction::GreaterThan => execute_greater_than(agent, gc)?, + Instruction::GreaterThanEquals => execute_greater_than_equals(agent, gc)?, + Instruction::HasProperty => execute_has_property(agent, gc)?, + Instruction::HasPrivateElement => execute_has_private_element(agent, gc.into_nogc())?, + Instruction::Increment => execute_increment(agent, gc.into_nogc()), + Instruction::Decrement => execute_decrement(agent, gc.into_nogc()), + Instruction::InstanceofOperator => execute_instanceof_operator(agent, gc)?, Instruction::InstantiateArrowFunctionExpression => { - execute_instantiate_arrow_function_expression(agent, vm, executable, instr, gc)? + execute_instantiate_arrow_function_expression(agent, instr, gc)? } Instruction::InstantiateOrdinaryFunctionExpression => { - execute_instantiate_ordinary_function_expression(agent, vm, executable, instr, gc)? + execute_instantiate_ordinary_function_expression(agent, instr, gc)? } Instruction::ClassDefineConstructor => { - execute_class_define_constructor(agent, vm, executable, instr, gc)? + execute_class_define_constructor(agent, instr, gc)? } Instruction::ClassDefineDefaultConstructor => { - execute_class_define_default_constructor(agent, vm, executable, instr, gc)? + execute_class_define_default_constructor(agent, instr, gc)? } Instruction::ClassDefinePrivateMethod => { - execute_class_define_private_method(agent, vm, executable, instr, gc)? + execute_class_define_private_method(agent, instr, gc)? } Instruction::ClassDefinePrivateProperty => { - execute_class_define_private_property(agent, vm, executable, instr, gc.into_nogc())? + execute_class_define_private_property(agent, instr, gc.into_nogc())? } Instruction::ClassInitializePrivateElements => { - execute_class_initialize_private_elements(agent, vm, gc.into_nogc())? + execute_class_initialize_private_elements(agent, gc.into_nogc())? } Instruction::ClassInitializePrivateValue => { - execute_class_initialize_private_value(agent, vm, instr, gc.into_nogc())? + execute_class_initialize_private_value(agent, instr, gc.into_nogc())? } - Instruction::LessThan => execute_less_than(agent, vm, gc)?, - Instruction::LessThanEquals => execute_less_than_equals(agent, vm, gc)?, - Instruction::IsLooselyEqual => execute_is_loosely_equal(agent, vm, gc)?, - Instruction::ObjectCreate => execute_object_create(agent, vm, gc.into_nogc()), + Instruction::LessThan => execute_less_than(agent, gc)?, + Instruction::LessThanEquals => execute_less_than_equals(agent, gc)?, + Instruction::IsLooselyEqual => execute_is_loosely_equal(agent, gc)?, + Instruction::ObjectCreate => execute_object_create(agent, gc.into_nogc()), Instruction::ObjectCreateWithShape => { - execute_object_create_with_shape(agent, vm, executable, instr, gc.into_nogc()) - } - Instruction::ObjectDefineProperty => execute_object_define_property(agent, vm, gc)?, - Instruction::ObjectDefineMethod => { - execute_object_define_method(agent, vm, executable, instr, gc)? - } - Instruction::ObjectDefineGetter => { - execute_object_define_getter(agent, vm, executable, instr, gc)? - } - Instruction::ObjectDefineSetter => { - execute_object_define_setter(agent, vm, executable, instr, gc)? - } - Instruction::ObjectSetPrototype => execute_object_set_prototype(agent, vm, gc)?, - Instruction::PopExceptionJumpTarget => vm.execute_pop_exception_jump_target(), + execute_object_create_with_shape(agent, instr, gc.into_nogc()) + } + Instruction::ObjectDefineProperty => execute_object_define_property(agent, gc)?, + Instruction::ObjectDefineMethod => execute_object_define_method(agent, instr, gc)?, + Instruction::ObjectDefineGetter => execute_object_define_getter(agent, instr, gc)?, + Instruction::ObjectDefineSetter => execute_object_define_setter(agent, instr, gc)?, + Instruction::ObjectSetPrototype => execute_object_set_prototype(agent, gc)?, + Instruction::PopExceptionJumpTarget => agent.vm.execute_pop_exception_jump_target(), Instruction::PushExceptionJumpTarget => { - execute_push_exception_jump_target(agent, vm, instr, gc.into_nogc()) - } - Instruction::TruncateStack => vm.stack.truncate(instr.get_first_arg() as usize), - Instruction::ResolveBindingWithCache => { - execute_resolve_binding_with_cache(agent, vm, executable, instr, gc)? + execute_push_exception_jump_target(agent, instr, gc.into_nogc()) } - Instruction::ResolveThisBinding => { - execute_resolve_this_binding(agent, vm, gc.into_nogc())? + Instruction::TruncateStack => { + agent.vm.stack.truncate(instr.get_first_stack_slot(agent)) } - Instruction::StoreCopy => vm.execute_store_copy(), - Instruction::StringConcat => execute_string_concat(agent, vm, instr, gc)?, - Instruction::Throw => vm.execute_throw(gc.into_nogc())?, - Instruction::ThrowError => execute_throw_error(agent, vm, instr, gc.into_nogc())?, - Instruction::ToNumber => execute_to_number(agent, vm, gc)?, - Instruction::ToObject => execute_to_object(agent, vm, gc.into_nogc())?, - Instruction::Typeof => execute_typeof(agent, vm, gc)?, - Instruction::UnaryMinus => execute_unary_minus(agent, vm, gc.into_nogc()), + Instruction::ResolveBindingWithCache => { + execute_resolve_binding_with_cache(agent, instr, gc)? + } + Instruction::ResolveThisBinding => execute_resolve_this_binding(agent, gc.into_nogc())?, + Instruction::StoreCopy => agent.vm.execute_store_copy(gc.into_nogc()), + Instruction::StringConcat => execute_string_concat(agent, instr, gc)?, + Instruction::Throw => agent.vm.execute_throw(gc.into_nogc())?, + Instruction::ThrowError => execute_throw_error(agent, instr, gc.into_nogc())?, + Instruction::ToNumber => execute_to_number(agent, gc)?, + Instruction::ToObject => execute_to_object(agent, gc.into_nogc())?, + Instruction::Typeof => execute_typeof(agent, gc)?, + Instruction::UnaryMinus => execute_unary_minus(agent, gc.into_nogc()), Instruction::InitializeVariableEnvironment => { - execute_initialize_variable_environment(agent, vm, instr, gc.into_nogc()) + execute_initialize_variable_environment(agent, instr, gc.into_nogc()) } Instruction::EnterDeclarativeEnvironment => { execute_enter_declarative_environment(agent, gc.into_nogc()) } Instruction::EnterClassStaticElementEnvironment => { - execute_enter_class_static_element_environment(agent, vm, gc.into_nogc()) + execute_enter_class_static_element_environment(agent, gc.into_nogc()) } Instruction::EnterPrivateEnvironment => { execute_enter_private_environment(agent, instr, gc.into_nogc()) @@ -736,10 +795,10 @@ impl Vm { execute_exit_private_environment(agent, gc.into_nogc()) } Instruction::BeginSimpleObjectBindingPattern => { - execute_begin_simple_object_binding_pattern(agent, vm, executable, instr, gc)? + execute_begin_simple_object_binding_pattern(agent, instr, gc)? } Instruction::BeginSimpleArrayBindingPattern => { - execute_begin_simple_array_binding_pattern(agent, vm, executable, instr, gc)? + execute_begin_simple_array_binding_pattern(agent, instr, gc)? } Instruction::BindingPatternBind | Instruction::BindingPatternBindNamed @@ -752,44 +811,38 @@ impl Vm { | Instruction::BindingPatternGetRestValue | Instruction::FinishBindingPattern => execute_binding_pattern(), Instruction::EnumerateObjectProperties => { - execute_enumerate_object_properties(agent, vm, gc.into_nogc()) + execute_enumerate_object_properties(agent, gc.into_nogc()) } - Instruction::GetIteratorSync => execute_get_iterator_sync(agent, vm, gc)?, - Instruction::GetIteratorAsync => execute_get_iterator_async(agent, vm, gc)?, - Instruction::IteratorStepValue => execute_iterator_step_value(agent, vm, instr, gc)?, + Instruction::GetIteratorSync => execute_get_iterator_sync(agent, gc)?, + Instruction::GetIteratorAsync => execute_get_iterator_async(agent, gc)?, + Instruction::IteratorStepValue => execute_iterator_step_value(agent, instr, gc)?, Instruction::IteratorStepValueOrUndefined => { - execute_iterator_step_value_or_undefined(agent, vm, gc)? - } - Instruction::IteratorCallNextMethod => { - execute_iterator_call_next_method(agent, vm, gc)? - } - Instruction::IteratorComplete => execute_iterator_complete(agent, vm, instr, gc)?, - Instruction::IteratorValue => execute_iterator_value(agent, vm, gc)?, - Instruction::IteratorThrow => execute_iterator_throw(agent, vm, instr, gc)?, - Instruction::IteratorReturn => execute_iterator_return(agent, vm, instr, gc)?, - Instruction::IteratorRestIntoArray => execute_iterator_rest_into_array(agent, vm, gc)?, - Instruction::IteratorClose => execute_iterator_close(agent, vm, gc)?, + execute_iterator_step_value_or_undefined(agent, gc)? + } + Instruction::IteratorCallNextMethod => execute_iterator_call_next_method(agent, gc)?, + Instruction::IteratorComplete => execute_iterator_complete(agent, gc)?, + Instruction::IteratorValue => execute_iterator_value(agent, gc)?, + Instruction::IteratorThrow => execute_iterator_throw(agent, instr, gc)?, + Instruction::IteratorReturn => execute_iterator_return(agent, instr, gc)?, + Instruction::IteratorRestIntoArray => execute_iterator_rest_into_array(agent, gc)?, + Instruction::IteratorClose => execute_iterator_close(agent, gc)?, Instruction::AsyncIteratorClose => { - if execute_async_iterator_close(agent, vm, gc)? { + if execute_async_iterator_close(agent, gc)? { return Ok(ContinuationKind::Await); } } - Instruction::IteratorCloseWithError => execute_iterator_close_with_error(agent, vm, gc), + Instruction::IteratorCloseWithError => execute_iterator_close_with_error(agent, gc), Instruction::AsyncIteratorCloseWithError => { - if execute_async_iterator_close_with_error(agent, vm, gc) { - return Ok(ContinuationKind::Await); - } + execute_async_iterator_close_with_error(agent, gc) } Instruction::IteratorPop => { - let _ = vm.pop_iterator(gc.into_nogc()); - } - Instruction::GetNewTarget => execute_get_new_target(agent, vm, gc.into_nogc()), - Instruction::ImportCall => execute_import_call(agent, vm, gc), - Instruction::ImportMeta => execute_import_meta(agent, vm, gc.into_nogc()), - Instruction::VerifyIsObject => { - execute_verify_is_object(agent, vm, executable, instr, gc.into_nogc())? + let _ = agent.vm.pop_iterator(gc.into_nogc()); } - Instruction::Debug => execute_debug(agent, vm), + Instruction::GetNewTarget => execute_get_new_target(agent, gc.into_nogc()), + Instruction::ImportCall => execute_import_call(agent, gc), + Instruction::ImportMeta => execute_import_meta(agent, gc.into_nogc()), + Instruction::VerifyIsObject => execute_verify_is_object(agent, instr, gc.into_nogc())?, + Instruction::Debug => execute_debug(agent), }; Ok(ContinuationKind::Normal) } @@ -798,8 +851,9 @@ impl Vm { let instr_arg0 = instr.get_first_arg(); if instr_arg0 != IndexType::MAX { // Static number of arguments less than IndexType::MAX. + self.result = None; let arg_count = instr_arg0 as usize; - debug_assert!(self.stack.len() >= arg_count); + debug_assert!(self.stack.len() >= self.stack_base as usize + arg_count); self.stack.split_off(self.stack.len() - arg_count) } else { // Dynamic number of arguments, or exactly IndexType::MAX or more @@ -810,8 +864,9 @@ impl Vm { panic!("Expected the number of function arguments to be an integer") }; let arg_count = usize::try_from(integer.into_i64()).unwrap(); - debug_assert!(self.stack.len() > arg_count); + debug_assert!(self.stack.len() > self.stack_base as usize + arg_count); let args = self.stack.split_off(self.stack.len() - arg_count); + debug_assert!(self.stack.len() > self.stack_base as usize); let integer_copy = self.stack.pop().unwrap(); debug_assert_eq!(Value::Integer(integer), integer_copy); args @@ -824,6 +879,7 @@ impl Vm { /// /// Panics if the iterator stack is empty. pub(super) fn pop_iterator<'gc>(&mut self, gc: NoGcScope<'gc, '_>) -> VmIteratorRecord<'gc> { + debug_assert!(self.iterator_stack.len() > self.iterator_stack_base as usize); self.iterator_stack .pop() .expect("Iterator stack is empty") @@ -836,6 +892,7 @@ impl Vm { /// /// Panics if the iterator stack is empty. pub(super) fn get_active_iterator(&self) -> &VmIteratorRecord<'static> { + debug_assert!(self.iterator_stack.len() > self.iterator_stack_base as usize); self.iterator_stack.last().expect("Iterator stack is empty") } @@ -845,6 +902,7 @@ impl Vm { /// /// Panics if the iterator stack is empty. pub(super) fn get_active_iterator_mut(&mut self) -> &mut VmIteratorRecord<'static> { + debug_assert!(self.iterator_stack.len() > self.iterator_stack_base as usize); self.iterator_stack .last_mut() .expect("Iterator stack is empty") @@ -878,6 +936,7 @@ impl Vm { .result .take() .expect("Expected result value to not be empty"); + debug_assert!(self.stack.len() > self.stack_base as usize); self.result = Some(self.stack.pop().expect("Trying to pop from empty stack")); self.stack.push(temp); } @@ -897,6 +956,7 @@ impl Vm { #[inline(always)] fn execute_update_empty(&mut self) { + debug_assert!(self.stack.len() > self.stack_base as usize); // Take top of the stack value, set it as the result if no // result exists yet. let temp = self.stack.pop().expect("Trying to pop from empty stack"); @@ -907,6 +967,7 @@ impl Vm { #[inline(always)] fn execute_store(&mut self) { + debug_assert!(self.stack.len() > self.stack_base as usize); self.result = Some(self.stack.pop().expect("Trying to pop from empty stack")); } @@ -916,12 +977,13 @@ impl Vm { } #[inline(always)] - fn execute_store_copy(&mut self) { - self.result = Some(*self.stack.last().expect("Trying to get from empty stack")); + fn execute_store_copy(&mut self, gc: NoGcScope) { + self.result = Some(self.get_last_stack_value(gc).unbind()); } #[inline(always)] fn execute_pop_stack(&mut self) { + debug_assert!(self.stack.len() > self.stack_base as usize); let _ = self.stack.pop().expect("Trying to pop from empty stack"); } @@ -930,14 +992,28 @@ impl Vm { self.reference_stack.push(self.reference.take().unwrap()); } + #[inline(always)] + fn execute_push_reference_copy(&mut self) { + self.reference_stack.push(self.reference.clone().unwrap()); + } + + fn pop_reference_stack(&mut self) -> Reference<'static> { + debug_assert!(self.reference_stack.len() > self.reference_stack_base as usize); + self.reference_stack + .pop() + .expect("Attempted to pop a Reference from an empty stack") + } + #[inline(always)] fn execute_pop_reference(&mut self) { - self.reference = Some(self.reference_stack.pop().unwrap()); + self.reference = Some(self.pop_reference_stack()); } #[inline(always)] fn execute_swap(&mut self) { + debug_assert!(self.stack.len() > self.stack_base as usize); let a = self.stack.pop().unwrap(); + debug_assert!(self.stack.len() > self.stack_base as usize); let b = self.stack.pop().unwrap(); self.stack.push(a); self.stack.push(b); @@ -969,9 +1045,22 @@ impl Vm { self.result = Some(result.into()); } + pub(crate) fn pop_exception_handler_stack(&mut self) -> Option> { + if self.exception_handler_stack.len() > self.exception_handler_stack_base as usize { + self.exception_handler_stack.pop() + } else { + None + } + } + #[inline(always)] fn execute_pop_exception_jump_target(&mut self) { - self.exception_handler_stack.pop().unwrap(); + // Bytecode should never attempt to pop an exception handler past the + // stack base. + debug_assert!( + self.exception_handler_stack.len() > self.exception_handler_stack_base as usize + ); + self.pop_exception_handler_stack().unwrap(); } #[inline(always)] @@ -1422,44 +1511,21 @@ pub(crate) fn instanceof_operator<'a, 'b>( } } -#[inline(always)] -fn with_vm_gc<'a, 'b, R: 'a>( - agent: &mut Agent, - vm: &mut Vm, - work: impl FnOnce(&mut Agent, GcScope<'a, 'b>) -> R, - gc: GcScope<'a, 'b>, -) -> R { - let vm = NonNull::from(vm); - agent.vm_stack.push(vm); - let result = work(agent, gc); - let return_vm = agent.vm_stack.pop().unwrap(); - assert_eq!(vm, return_vm, "VM Stack was misused"); - result -} - impl HeapMarkAndSweep for ExceptionHandler<'static> { fn mark_values(&self, queues: &mut WorkQueues) { - match self { - ExceptionHandler::CatchBlock { - ip: _, - lexical_environment, - } => { - lexical_environment.mark_values(queues); - } - ExceptionHandler::IgnoreErrorAndNextInstruction => {} - } + let Self { + ip: _, + lexical_environment, + } = self; + lexical_environment.mark_values(queues); } fn sweep_values(&mut self, compactions: &CompactionLists) { - match self { - ExceptionHandler::CatchBlock { - ip: _, - lexical_environment, - } => { - lexical_environment.sweep_values(compactions); - } - ExceptionHandler::IgnoreErrorAndNextInstruction => {} - } + let Self { + ip: _, + lexical_environment, + } = self; + lexical_environment.sweep_values(compactions); } } @@ -1468,16 +1534,23 @@ impl HeapMarkAndSweep for Vm { let Vm { ip: _, stack, + stack_base: _, reference_stack, + #[cfg(debug_assertions)] + reference_stack_base: _, iterator_stack, - exception_handler_stack: exception_jump_target_stack, + #[cfg(debug_assertions)] + iterator_stack_base: _, + exception_handler_stack, + #[cfg(debug_assertions)] + exception_handler_stack_base: _, result, 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); + exception_handler_stack.as_slice().mark_values(queues); result.mark_values(queues); reference.mark_values(queues); } @@ -1486,16 +1559,23 @@ impl HeapMarkAndSweep for Vm { let Vm { ip: _, stack, + stack_base: _, reference_stack, + #[cfg(debug_assertions)] + reference_stack_base: _, iterator_stack, - exception_handler_stack: exception_jump_target_stack, + #[cfg(debug_assertions)] + iterator_stack_base: _, + exception_handler_stack, + #[cfg(debug_assertions)] + exception_handler_stack_base: _, result, 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 + exception_handler_stack .as_mut_slice() .sweep_values(compactions); result.sweep_values(compactions); @@ -1510,7 +1590,7 @@ impl HeapMarkAndSweep for SuspendedVm { stack, reference_stack, iterator_stack, - exception_jump_target_stack, + exception_handler_stack: exception_jump_target_stack, } = self; stack.mark_values(queues); reference_stack.mark_values(queues); @@ -1524,7 +1604,7 @@ impl HeapMarkAndSweep for SuspendedVm { stack, reference_stack, iterator_stack, - exception_jump_target_stack, + exception_handler_stack: exception_jump_target_stack, } = self; stack.sweep_values(compactions); reference_stack.sweep_values(compactions); @@ -1537,7 +1617,6 @@ impl HeapMarkAndSweep for SuspendedVm { /// function name and returns it. fn set_class_name<'a>( agent: &mut Agent, - vm: &mut Vm, name: Value, mut gc: GcScope<'a, '_>, ) -> JsResult<'a, String<'a>> { @@ -1559,18 +1638,13 @@ fn set_class_name<'a>( // 3. Return ? ToPropertyKey(propName). let prop_key = { let name = name.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| to_property_key(agent, name, gc), - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()) + to_property_key(agent, name, gc.reborrow()) + .unbind()? + .bind(gc.nogc()) }; let name = prop_key.convert_to_value(agent, gc.nogc()); - set_class_name(agent, vm, name.unbind().into(), gc) + set_class_name(agent, name.unbind().into(), gc) } } diff --git a/nova_vm/src/engine/bytecode/vm/binding_methods.rs b/nova_vm/src/engine/bytecode/vm/binding_methods.rs index a4b463a7d..3e4e35e45 100644 --- a/nova_vm/src/engine/bytecode/vm/binding_methods.rs +++ b/nova_vm/src/engine/bytecode/vm/binding_methods.rs @@ -2,6 +2,8 @@ // 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::{ Agent, Environment, JsResult, Object, PropertyKey, PropertyKeySet, Value, array_create, @@ -9,24 +11,20 @@ use crate::{ resolve_binding, to_object, try_create_data_property_or_throw, unwrap_try, }, engine::{ - ActiveIterator, Bindable, Executable, GcScope, Instruction, Scopable, ScopableCollection, - Scoped, Vm, VmIteratorRecord, + ActiveIterator, Bindable, GcScope, Instr, Instruction, Scopable, ScopableCollection, + Scoped, VmIteratorRecord, }, }; -use super::with_vm_gc; - pub(super) fn execute_simple_array_binding<'a>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, environment: Option>, mut gc: GcScope<'a, '_>, ) -> JsResult<'a, ()> { let mut iterator_is_done = false; - + let instructions = agent.current_executable(gc.nogc()).get_instructions(agent); loop { - let instr = executable.get_instruction(agent, &mut vm.ip).unwrap(); + let instr = Instr::consume_instruction(instructions, &mut agent.vm.ip).unwrap(); if agent.options.print_internals { eprintln!("Executing: {:?}", instr.kind); } @@ -35,7 +33,7 @@ pub(super) fn execute_simple_array_binding<'a>( let value = match instr.kind { Instruction::Debug => { if agent.options.print_internals { - eprintln!("Debug: {vm:#?}"); + eprintln!("Debug: {:#?}", agent.vm); } continue; } @@ -43,14 +41,10 @@ pub(super) fn execute_simple_array_binding<'a>( | Instruction::BindingPatternBindToIndex | Instruction::BindingPatternGetValue | Instruction::BindingPatternSkip => { - let result = with_vm_gc( - agent, - vm, - |agent, gc| ActiveIterator::new(agent, gc.nogc()).step_value(agent, gc), - gc.reborrow(), - ) - .unbind() - .bind(gc.nogc()); + let result = ActiveIterator::new(agent, gc.nogc()) + .step_value(agent, gc.reborrow()) + .unbind() + .bind(gc.nogc()); result.map(|r| { iterator_is_done = r.is_none(); @@ -64,40 +58,31 @@ pub(super) fn execute_simple_array_binding<'a>( if iterator_is_done { Ok(array_create(agent, 0, 0, None, gc.nogc()).unwrap().into()) } else { - with_vm_gc( - agent, - vm, - |agent, mut gc| { - let mut iterator = ActiveIterator::new(agent, gc.nogc()); - let capacity = iterator.remaining_length_estimate(agent).unwrap_or(0); - let rest = array_create(agent, 0, capacity, None, gc.nogc()) - .unwrap() - .scope(agent, gc.nogc()); - let mut idx = 0u32; - while let Some(result) = iterator - .step_value(agent, gc.reborrow()) - .unbind()? - .bind(gc.nogc()) - { - unwrap_try(try_create_data_property_or_throw( - agent, - rest.get(agent), - PropertyKey::from(idx), - result.unbind(), - None, - gc.nogc(), - )); - idx += 1; - } - iterator_is_done = true; - // SAFETY: rest is not shared - let rest: Value = unsafe { rest.take(agent).into() }; - JsResult::Ok(rest) - }, - gc.reborrow(), - ) - .unbind() - .bind(gc.nogc()) + let mut iterator = ActiveIterator::new(agent, gc.nogc()); + let capacity = iterator.remaining_length_estimate(agent).unwrap_or(0); + let rest = array_create(agent, 0, capacity, None, gc.nogc()) + .unwrap() + .scope(agent, gc.nogc()); + let mut idx = 0u32; + while let Some(result) = iterator + .step_value(agent, gc.reborrow()) + .unbind()? + .bind(gc.nogc()) + { + unwrap_try(try_create_data_property_or_throw( + agent, + rest.get(agent), + PropertyKey::from(idx), + result.unbind(), + None, + gc.nogc(), + )); + idx += 1; + } + iterator_is_done = true; + // SAFETY: rest is not shared + let rest: Value = unsafe { rest.take(agent).into() }; + JsResult::Ok(rest) } } Instruction::FinishBindingPattern => break, @@ -112,7 +97,7 @@ pub(super) fn execute_simple_array_binding<'a>( // observably called by our error handler. To ensure that, we // replace the iterator with the empty slice iterator that will // simply ignore a return call. - *vm.get_active_iterator_mut() = VmIteratorRecord::EmptySliceIterator; + *agent.vm.get_active_iterator_mut() = VmIteratorRecord::EmptySliceIterator; // Now we're ready to rethrow the error. return Err(err.unbind()); } @@ -127,59 +112,50 @@ pub(super) fn execute_simple_array_binding<'a>( } } Instruction::BindingPatternBind | Instruction::BindingPatternBindRest => { - let value = value.unbind(); - with_vm_gc( + let value = value.scope(agent, gc.nogc()); + let binding_id = agent.current_executable(gc.nogc()).fetch_identifier( agent, - vm, - |agent, mut gc| { - let value = value.scope(agent, gc.nogc()); - let binding_id = - executable.fetch_identifier(agent, instr.get_first_index(), gc.nogc()); - let lhs = resolve_binding( - agent, - binding_id.unbind(), - None, - environment.as_ref().map(|v| v.get(agent)), - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); - if environment.is_none() { - put_value( - agent, - &lhs.unbind(), - // SAFETY: value is not shared. - unsafe { value.take(agent) }, - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); - } else { - initialize_referenced_binding( - agent, - lhs.unbind(), - // SAFETY: value is not shared. - unsafe { value.take(agent) }, - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); - } - JsResult::Ok(()) - }, + instr.get_first_index(), + gc.nogc(), + ); + let lhs = resolve_binding( + agent, + binding_id.unbind(), + None, + environment.as_ref().map(|v| v.get(agent)), gc.reborrow(), ) - .unbind()?; + .unbind()? + .bind(gc.nogc()); + if environment.is_none() { + put_value( + agent, + &lhs.unbind(), + // SAFETY: value is not shared. + unsafe { value.take(agent) }, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + } else { + initialize_referenced_binding( + agent, + lhs.unbind(), + // SAFETY: value is not shared. + unsafe { value.take(agent) }, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + } } Instruction::BindingPatternBindToIndex | Instruction::BindingPatternBindRestToIndex => { - let stack_slot = instr.get_first_index(); - vm.stack[stack_slot] = value.unbind(); + let stack_slot = instr.get_first_stack_slot(agent); + agent.vm.stack[stack_slot] = value.unbind(); } Instruction::BindingPatternGetValue | Instruction::BindingPatternGetRestValue => { execute_nested_simple_binding( agent, - vm, - executable.clone(), value.unbind(), environment.clone(), gc.reborrow(), @@ -202,21 +178,17 @@ pub(super) fn execute_simple_array_binding<'a>( // NOTE: `result` here is always UNUSED. We use `undefined` as a stand-in // since that way we don't need to implement a separate iterator_close. if !iterator_is_done { - let iter = vm.get_active_iterator_mut(); - if iter.requires_return_call(agent, gc.nogc()) { - let result = with_vm_gc( + // SAFETY: requires_return_call cannot call into JavaScript and therefore + // does not access agent.vm. + if unsafe { NonNull::from_ref(agent.vm.get_active_iterator()).as_ref() } + .requires_return_call(agent, gc.nogc()) + { + let result = ActiveIterator::new(agent, gc.nogc()).r#return( agent, - vm, - |agent, gc| { - ActiveIterator::new(agent, gc.nogc()).r#return( - agent, - Some(Value::Undefined), - gc, - ) - }, + Some(Value::Undefined), gc, )?; - vm.result = result.unbind(); + agent.vm.result = result.unbind(); } } @@ -225,8 +197,6 @@ pub(super) fn execute_simple_array_binding<'a>( pub(super) fn execute_simple_object_binding<'a>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, object: Object, environment: Option>, mut gc: GcScope<'a, '_>, @@ -234,118 +204,100 @@ pub(super) fn execute_simple_object_binding<'a>( let object = object.scope(agent, gc.nogc()); let mut excluded_names = PropertyKeySet::new(gc.nogc()).scope(agent, gc.nogc()); + let instructions = agent.current_executable(gc.nogc()).get_instructions(agent); loop { - let instr = executable.get_instruction(agent, &mut vm.ip).unwrap(); + let instr = Instr::consume_instruction(instructions, &mut agent.vm.ip).unwrap(); if agent.options.print_internals { eprintln!("Executing: {:?}", instr.kind); } match instr.kind { Instruction::Debug => { if agent.options.print_internals { - eprintln!("Debug: {vm:#?}"); + eprintln!("Debug: {:#?}", agent.vm); } } Instruction::BindingPatternBind | Instruction::BindingPatternBindNamed => { - with_vm_gc( - agent, - vm, - |agent, mut gc| { - let binding_id = - executable.fetch_identifier(agent, instr.get_first_index(), gc.nogc()); - let property_key = if instr.kind == Instruction::BindingPatternBind { - binding_id.into() - } else { - let key_value = executable.fetch_constant( - agent, - instr.get_second_index(), - gc.nogc(), - ); - // SAFETY: It should be impossible for binding pattern - // names to be integer strings. - unsafe { PropertyKey::from_value_unchecked(key_value) } - }; + let exe = agent.current_executable(gc.nogc()); + let binding_id = exe.fetch_identifier(agent, instr.get_first_index(), gc.nogc()); - excluded_names.insert(agent, property_key); + let property_key = if instr.kind == Instruction::BindingPatternBind { + binding_id.into() + } else { + let key_value = exe.fetch_constant(agent, instr.get_second_index(), gc.nogc()); + // SAFETY: It should be impossible for binding pattern + // names to be integer strings. + unsafe { PropertyKey::from_value_unchecked(key_value) } + }; - let property_key = property_key.scope(agent, gc.nogc()); - let lhs = resolve_binding( - agent, - binding_id.unbind(), - None, - environment.as_ref().map(|v| v.get(agent)), - gc.reborrow(), - ) - .unbind()?; - let v = get( - agent, - object.get(agent), - // SAFETY: property_key is not shared. - unsafe { property_key.take(agent) }, - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); - if environment.is_none() { - put_value(agent, &lhs, v.unbind(), gc.reborrow()).unbind()?; - } else { - initialize_referenced_binding(agent, lhs, v.unbind(), gc.reborrow()) - .unbind()? - } - JsResult::Ok(()) - }, + excluded_names.insert(agent, property_key); + + let property_key = property_key.scope(agent, gc.nogc()); + let lhs = resolve_binding( + agent, + binding_id.unbind(), + None, + environment.as_ref().map(|v| v.get(agent)), gc.reborrow(), ) .unbind()?; - } - Instruction::BindingPatternBindToIndex => { - let value = with_vm_gc( + let v = get( agent, - vm, - |agent, gc| { - let key_value = - executable.fetch_constant(agent, instr.get_second_index(), gc.nogc()); - // SAFETY: It should be impossible for binding pattern - // names to be integer strings. - let key_value = unsafe { PropertyKey::from_value_unchecked(key_value) }; - - excluded_names.insert(agent, key_value); - - get(agent, object.get(agent), key_value.unbind(), gc) - }, + object.get(agent), + // SAFETY: property_key is not shared. + unsafe { property_key.take(agent) }, gc.reborrow(), ) .unbind()? .bind(gc.nogc()); - let stack_slot = instr.get_first_index(); - vm.stack[stack_slot] = value.unbind(); + if environment.is_none() { + put_value(agent, &lhs, v.unbind(), gc.reborrow()).unbind()?; + } else { + initialize_referenced_binding(agent, lhs, v.unbind(), gc.reborrow()).unbind()? + } } - Instruction::BindingPatternGetValueNamed => { - let v = with_vm_gc( + Instruction::BindingPatternBindToIndex => { + let key_value = agent.current_executable(gc.nogc()).fetch_constant( agent, - vm, - |agent, gc| { - // SAFETY: The constant was created using PropertyKey::from_str - // which checks for integer-ness, and then converted to Value - // without conversion, or is a floating point number string. - let property_key = unsafe { - PropertyKey::from_value_unchecked(executable.fetch_constant( - agent, - instr.get_first_index(), - gc.nogc(), - )) - }; + instr.get_second_index(), + gc.nogc(), + ); + // SAFETY: It should be impossible for binding pattern + // names to be integer strings. + let key_value = unsafe { PropertyKey::from_value_unchecked(key_value) }; + + excluded_names.insert(agent, key_value); - excluded_names.insert(agent, property_key); - get(agent, object.get(agent), property_key.unbind(), gc) - }, + let value = get(agent, object.get(agent), key_value.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + let stack_slot = instr.get_first_stack_slot(agent); + agent.vm.stack[stack_slot] = value.unbind(); + } + Instruction::BindingPatternGetValueNamed => { + // SAFETY: The constant was created using PropertyKey::from_str + // which checks for integer-ness, and then converted to Value + // without conversion, or is a floating point number string. + let property_key = unsafe { + PropertyKey::from_value_unchecked( + agent.current_executable(gc.nogc()).fetch_constant( + agent, + instr.get_first_index(), + gc.nogc(), + ), + ) + }; + + excluded_names.insert(agent, property_key); + let v = get( + agent, + object.get(agent), + property_key.unbind(), gc.reborrow(), ) .unbind()? .bind(gc.nogc()); execute_nested_simple_binding( agent, - vm, - executable.clone(), v.unbind(), environment.clone(), gc.reborrow(), @@ -353,72 +305,59 @@ pub(super) fn execute_simple_object_binding<'a>( .unbind()?; } Instruction::BindingPatternBindRest => { - with_vm_gc( + // 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment). + let binding_id = agent.current_executable(gc.nogc()).fetch_identifier( agent, - vm, - |agent, mut gc| { - // 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment). - let binding_id = - executable.fetch_identifier(agent, instr.get_first_index(), gc.nogc()); - let lhs = resolve_binding( - agent, - binding_id.unbind(), - None, - environment.as_ref().map(|v| v.get(agent)), - gc.reborrow(), - ) - .unbind()?; - // 2. Let restObj be OrdinaryObjectCreate(%Object.prototype%). - // 3. Perform ? CopyDataProperties(restObj, value, excludedNames). - let rest_obj = copy_data_properties_into_object( - agent, - object.get(agent), - excluded_names, - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); - // 4. If environment is undefined, return ? PutValue(lhs, restObj). - // 5. Return ? InitializeReferencedBinding(lhs, restObj). - if environment.is_none() { - put_value(agent, &lhs, rest_obj.unbind().into(), gc.reborrow()) - .unbind()?; - } else { - initialize_referenced_binding( - agent, - lhs, - rest_obj.unbind().into(), - gc.reborrow(), - ) - .unbind()?; - } - JsResult::Ok(()) - }, + instr.get_first_index(), + gc.nogc(), + ); + let lhs = resolve_binding( + agent, + binding_id.unbind(), + None, + environment.as_ref().map(|v| v.get(agent)), gc.reborrow(), ) .unbind()?; + // 2. Let restObj be OrdinaryObjectCreate(%Object.prototype%). + // 3. Perform ? CopyDataProperties(restObj, value, excludedNames). + let rest_obj = copy_data_properties_into_object( + agent, + object.get(agent), + excluded_names, + gc.reborrow(), + ) + .unbind()? + .bind(gc.nogc()); + // 4. If environment is undefined, return ? PutValue(lhs, restObj). + // 5. Return ? InitializeReferencedBinding(lhs, restObj). + if environment.is_none() { + put_value(agent, &lhs, rest_obj.unbind().into(), gc.reborrow()).unbind()?; + } else { + initialize_referenced_binding( + agent, + lhs, + rest_obj.unbind().into(), + gc.reborrow(), + ) + .unbind()?; + } break; } Instruction::BindingPatternBindRestToIndex => { - let rest_obj = with_vm_gc( + // 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment). + // 2. Let restObj be OrdinaryObjectCreate(%Object.prototype%). + // 3. Perform ? CopyDataProperties(restObj, value, excludedNames). + let rest_obj = copy_data_properties_into_object( agent, - vm, - |agent, gc| { - // 1. Let lhs be ? ResolveBinding(StringValue of BindingIdentifier, environment). - // 2. Let restObj be OrdinaryObjectCreate(%Object.prototype%). - // 3. Perform ? CopyDataProperties(restObj, value, excludedNames). - copy_data_properties_into_object( - agent, - object.get(agent), - excluded_names, - gc, - ) - }, + object.get(agent), + excluded_names, gc.reborrow(), ) - .unbind()?; - let stack_slot = instr.get_first_index(); - vm.stack[stack_slot] = rest_obj.unbind().into(); + .unbind()? + .bind(gc.nogc()); + let stack_slot = instr.get_first_stack_slot(agent); + agent.vm.stack[stack_slot] = rest_obj.unbind().into(); break; } Instruction::FinishBindingPattern => break, @@ -430,50 +369,36 @@ pub(super) fn execute_simple_object_binding<'a>( pub(super) fn execute_nested_simple_binding<'a>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, value: Value, environment: Option>, mut gc: GcScope<'a, '_>, ) -> JsResult<'a, ()> { - let instr = executable.get_instruction(agent, &mut vm.ip).unwrap(); + let value = value.bind(gc.nogc()); + let instr = Instr::consume_instruction( + agent.current_executable(gc.nogc()).get_instructions(agent), + &mut agent.vm.ip, + ) + .unwrap(); if agent.options.print_internals { eprintln!("Executing: {:?}", instr.kind); } match instr.kind { Instruction::BeginSimpleArrayBindingPattern => { - let result = with_vm_gc( - agent, - vm, - |agent, gc| VmIteratorRecord::from_value(agent, value, gc), - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); - vm.iterator_stack.push(result.unbind()); - let result = execute_simple_array_binding( - agent, - vm, - executable, - environment.clone(), - gc.reborrow(), - ) - .unbind(); + let result = VmIteratorRecord::from_value(agent, value.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + agent.vm.iterator_stack.push(result.unbind()); + let result = execute_simple_array_binding(agent, environment, gc.reborrow()).unbind(); let gc = gc.into_nogc(); let result = result.bind(gc); - vm.pop_iterator(gc); + agent.vm.pop_iterator(gc); result } Instruction::BeginSimpleObjectBindingPattern => { - let object = to_object(agent, value, gc.nogc()).unbind()?.bind(gc.nogc()); - execute_simple_object_binding( - agent, - vm, - executable, - object.unbind(), - environment.clone(), - gc, - ) + let object = to_object(agent, value.unbind(), gc.nogc()) + .unbind()? + .bind(gc.nogc()); + execute_simple_object_binding(agent, object.unbind(), environment, gc) } _ => unreachable!(), } diff --git a/nova_vm/src/engine/bytecode/vm/execute_instructions.rs b/nova_vm/src/engine/bytecode/vm/execute_instructions.rs index df9b663c5..fa642703a 100644 --- a/nova_vm/src/engine/bytecode/vm/execute_instructions.rs +++ b/nova_vm/src/engine/bytecode/vm/execute_instructions.rs @@ -4,6 +4,7 @@ use core::ops::ControlFlow; use oxc_span::Span; +use std::ptr::NonNull; use crate::{ ecmascript::{ @@ -33,9 +34,9 @@ use crate::{ try_result_into_option_js, unwrap_try, }, engine::{ - ActiveIterator, Bindable, GcScope, NoGcScope, Scopable, ScopableCollection, Scoped, + ActiveIterator, Bindable, GcScope, NoGcScope, Scopable, ScopableCollection, bytecode::{ - Executable, FunctionExpression, Instruction, NamedEvaluationParameter, + FunctionExpression, Instruction, NamedEvaluationParameter, executable::ArrowFunctionExpression, instructions::Instr, iterator::{ObjectPropertiesIteratorRecord, VmIteratorRecord}, @@ -46,50 +47,42 @@ use crate::{ }; use super::{ - ExceptionHandler, Vm, apply_string_or_numeric_addition, - apply_string_or_numeric_binary_operator, bigint_binary_operator, + ExceptionHandler, apply_string_or_numeric_addition, apply_string_or_numeric_binary_operator, + bigint_binary_operator, binding_methods::{execute_simple_array_binding, execute_simple_object_binding}, concat_string_from_slice, instanceof_operator, number_binary_operator, set_class_name, - throw_error_in_target_not_object, typeof_operator, verify_is_object, with_vm_gc, + throw_error_in_target_not_object, typeof_operator, verify_is_object, }; // Perform step 2 of `Await( value )` in the VM context. pub(super) fn execute_await_promise_resolve<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let value = vm.result.take().unwrap(); + let value = agent.vm.take_result(gc.nogc()); // 1. Let asyncContext be the running execution context. // 2. Let promise be ? PromiseResolve(%Promise%, value). - let promise = with_vm_gc( - agent, - vm, - |agent, gc| Promise::resolve(agent, value, gc), - gc, - )?; - vm.result = Some(promise.unbind().into()); + let promise = Promise::resolve(agent, value.unbind(), gc)?; + agent.vm.result = Some(promise.unbind().into()); Ok(()) } pub(super) fn execute_array_create<'gc>( agent: &mut Agent, - vm: &mut Vm, instr: Instr, gc: NoGcScope<'gc, '_>, ) -> JsResult<'gc, ()> { let result = array_create(agent, 0, instr.get_first_index(), None, gc)?; - vm.result = Some(result.unbind().into()); + agent.vm.result = Some(result.unbind().into()); Ok(()) } pub(super) fn execute_array_push<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let value = vm.result.take().unwrap().bind(gc.nogc()); - let array = vm.stack.last().unwrap().bind(gc.nogc()); + let value = agent.vm.take_result(gc.nogc()); + let array = agent.vm.get_last_stack_value(gc.nogc()); let Ok(array) = Array::try_from(array) else { unreachable!(); }; @@ -97,39 +90,26 @@ pub(super) fn execute_array_push<'gc>( let key = PropertyKey::Integer(len.into()); let array = array.unbind(); let value = value.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| create_data_property_or_throw(agent, array, key, value, gc), - gc, - )?; + create_data_property_or_throw(agent, array, key, value, gc)?; Ok(()) } pub(super) fn execute_array_elision<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let array = vm.stack.last().unwrap().bind(gc.nogc()); + let array = agent.vm.get_last_stack_value(gc.nogc()); let Ok(array) = Array::try_from(array) else { unreachable!(); }; let length = array.len(agent) + 1; let array = array.unbind().into(); - with_vm_gc( + set( agent, - vm, - |agent, gc| { - set( - agent, - array, - BUILTIN_STRING_MEMORY.length.into(), - length.into(), - true, - gc, - ) - }, + array, + BUILTIN_STRING_MEMORY.length.into(), + length.into(), + true, gc, )?; Ok(()) @@ -137,19 +117,18 @@ pub(super) fn execute_array_elision<'gc>( pub(super) fn execute_bitwise_not<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: NoGcScope<'gc, '_>, ) -> JsResult<'gc, ()> { // 2. Let oldValue be ? ToNumeric(? GetValue(expr)). // Note: This step is a separate instruction. - let old_value = Numeric::try_from(vm.result.take().unwrap()) + let old_value = Numeric::try_from(agent.vm.take_result(gc)) .unwrap() .bind(gc); // 3. If oldValue is a Number, then if let Ok(old_value) = Number::try_from(old_value) { // a. Return Number::bitwiseNOT(oldValue). - vm.result = Some(Number::bitwise_not(agent, old_value).unbind().into()); + agent.vm.result = Some(Number::bitwise_not(agent, old_value).unbind().into()); } else { // 4. Else, // a. Assert: oldValue is a BigInt. @@ -158,28 +137,30 @@ pub(super) fn execute_bitwise_not<'gc>( }; // b. Return BigInt::bitwiseNOT(oldValue). - vm.result = Some(BigInt::bitwise_not(agent, old_value).unbind().into()); + agent.vm.result = Some(BigInt::bitwise_not(agent, old_value).unbind().into()); } Ok(()) } #[inline(never)] #[cold] -pub(super) fn execute_debug(agent: &Agent, vm: &Vm) { +pub(super) fn execute_debug(agent: &Agent) { if agent.options.print_internals { - eprintln!("Debug: {vm:#?}"); + eprintln!("Debug: {:#?}", agent.vm); } } #[inline(always)] pub(super) fn execute_resolve_binding<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let identifier = executable.fetch_identifier(agent, instr.get_first_index(), gc.nogc()); + let identifier = agent.current_executable(gc.nogc()).fetch_identifier( + agent, + instr.get_first_index(), + gc.nogc(), + ); let reference = if let TryResult::Continue(reference) = try_resolve_binding(agent, identifier, None, gc.nogc()) @@ -187,95 +168,80 @@ pub(super) fn execute_resolve_binding<'gc>( reference } else { let identifier = identifier.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| resolve_binding(agent, identifier, None, None, gc), - gc, - )? + resolve_binding(agent, identifier, None, None, gc)? }; - vm.reference = Some(reference.unbind()); + agent.vm.reference = Some(reference.unbind()); Ok(()) } pub(super) fn execute_resolve_binding_with_cache<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let identifier = executable.fetch_identifier(agent, instr.get_first_index(), gc.nogc()); - let cache = executable.fetch_cache(agent, instr.get_second_index(), gc.nogc()); + let identifier = agent.current_executable(gc.nogc()).fetch_identifier( + agent, + instr.get_first_index(), + gc.nogc(), + ); + let cache = + agent + .current_executable(gc.nogc()) + .fetch_cache(agent, instr.get_second_index(), gc.nogc()); let reference = if let TryResult::Continue(reference) = try_resolve_binding(agent, identifier, Some(cache), gc.nogc()) { reference } else { - execute_resolve_binding_with_cache_cold(agent, vm, identifier.unbind(), cache.unbind(), gc) + execute_resolve_binding_with_cache_cold(agent, identifier.unbind(), cache.unbind(), gc) .unbind()? }; - vm.reference = Some(reference.unbind()); + agent.vm.reference = Some(reference.unbind()); Ok(()) } #[cold] fn execute_resolve_binding_with_cache_cold<'gc>( agent: &mut Agent, - vm: &mut Vm, identifier: String, cache: PropertyLookupCache, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Reference<'gc>> { let identifier = identifier.unbind(); let cache = cache.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| resolve_binding(agent, identifier, Some(cache), None, gc), - gc, - ) + resolve_binding(agent, identifier, Some(cache), None, gc) } pub(super) fn execute_resolve_this_binding<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: NoGcScope<'gc, '_>, ) -> JsResult<'gc, ()> { let this = resolve_this_binding(agent, gc)?.unbind(); - vm.result = Some(this); + agent.vm.result = Some(this); Ok(()) } #[inline(always)] -pub(super) fn execute_load_constant( - agent: &Agent, - vm: &mut Vm, - executable: Scoped, - instr: Instr, - gc: NoGcScope, -) { - let constant = executable.fetch_constant(agent, instr.get_first_index(), gc); - vm.stack.push(constant.unbind()); +pub(super) fn execute_load_constant(agent: &mut Agent, instr: Instr, gc: NoGcScope) { + let constant = agent + .current_executable(gc) + .fetch_constant(agent, instr.get_first_index(), gc); + agent.vm.stack.push(constant.unbind()); } #[inline(always)] -pub(super) fn execute_store_constant( - agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, - instr: Instr, - gc: NoGcScope, -) { - let constant = executable.fetch_constant(agent, instr.get_first_index(), gc); - vm.result = Some(constant.unbind()); +pub(super) fn execute_store_constant(agent: &mut Agent, instr: Instr, gc: NoGcScope) { + let constant = agent + .current_executable(gc) + .fetch_constant(agent, instr.get_first_index(), gc); + agent.vm.result = Some(constant.unbind()); } -pub(super) fn execute_unary_minus(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) { - let old_value = vm.result.unwrap().bind(gc); +pub(super) fn execute_unary_minus(agent: &mut Agent, gc: NoGcScope) { + let old_value = agent.vm.take_result(gc); // 3. If oldValue is a Number, then let result: Value = if let Ok(old_value) = Number::try_from(old_value) { @@ -290,38 +256,32 @@ pub(super) fn execute_unary_minus(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) // b. Return BigInt::unaryMinus(oldValue). BigInt::unary_minus(agent, old_value).into() }; - vm.result = Some(result.unbind()); + agent.vm.result = Some(result.unbind()); } -pub(super) fn execute_to_number<'gc>( - agent: &mut Agent, - vm: &mut Vm, - gc: GcScope<'gc, '_>, -) -> JsResult<'gc, ()> { - let arg0 = vm.result.unwrap().bind(gc.nogc()); +pub(super) fn execute_to_number<'gc>(agent: &mut Agent, gc: GcScope<'gc, '_>) -> JsResult<'gc, ()> { + let arg0 = agent.vm.take_result(gc.nogc()); let result = if let Ok(arg0) = Primitive::try_from(arg0) { to_number_primitive(agent, arg0.unbind(), gc.into_nogc()) } else { - let arg0 = arg0.unbind(); - with_vm_gc(agent, vm, |agent, gc| to_number(agent, arg0, gc), gc) + to_number(agent, arg0.unbind(), gc) }; - vm.result = Some(result?.unbind().into()); + agent.vm.result = Some(result?.unbind().into()); Ok(()) } #[inline(always)] pub(super) fn execute_to_numeric<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let arg0 = vm.result.unwrap().bind(gc.nogc()); + let arg0 = agent.vm.take_result(gc.nogc()); let result = if let Ok(arg0) = Primitive::try_from(arg0) { to_numeric_primitive(agent, arg0.unbind(), gc.into_nogc()) } else { - execute_to_numeric_cold(agent, vm, arg0.unbind(), gc) + execute_to_numeric_cold(agent, arg0.unbind(), gc) }; - vm.result = Some(result?.unbind().into()); + agent.vm.result = Some(result?.unbind().into()); Ok(()) } @@ -329,88 +289,90 @@ pub(super) fn execute_to_numeric<'gc>( #[cold] fn execute_to_numeric_cold<'gc>( agent: &mut Agent, - vm: &mut Vm, arg0: Value, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Numeric<'gc>> { - with_vm_gc(agent, vm, |agent, gc| to_numeric(agent, arg0, gc), gc) + to_numeric(agent, arg0, gc) } pub(super) fn execute_to_object<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: NoGcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - vm.result = Some(to_object(agent, vm.result.unwrap(), gc)?.unbind().into()); + let value = agent.vm.take_result(gc); + agent.vm.result = Some(to_object(agent, value, gc)?.unbind().into()); Ok(()) } pub(super) fn execute_apply_addition_binary_operator<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let lval = vm.stack.pop().unwrap(); - let rval = vm.result.take().unwrap(); + let lval = agent.vm.pop_stack(gc.nogc()); + let rval = agent.vm.take_result(gc.nogc()); let result = if let (Ok(lnum), Ok(rnum)) = (Number::try_from(lval), Number::try_from(rval)) { Number::add(agent, lnum, rnum).into() } else if let (Ok(lstr), Ok(rstr)) = (String::try_from(lval), String::try_from(rval)) { - String::concat(agent, [lstr, rstr], gc.into_nogc()).into() + String::concat(agent, [lstr.unbind(), rstr.unbind()], gc.into_nogc()).into() } else if let (Ok(lnum), Ok(rnum)) = (BigInt::try_from(lval), BigInt::try_from(rval)) { BigInt::add(agent, lnum, rnum).into() } else { - with_vm_gc( - agent, - vm, - |agent, gc| apply_string_or_numeric_addition(agent, lval, rval, gc), - gc, - )? + apply_string_or_numeric_addition(agent, lval.unbind(), rval.unbind(), gc)? }; - vm.result = Some(result.unbind()); + agent.vm.result = Some(result.unbind()); Ok(()) } pub(super) fn execute_apply_binary_operator<'gc>( agent: &mut Agent, - vm: &mut Vm, instruction: Instruction, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let lval = vm.stack.pop().unwrap(); - let rval = vm.result.take().unwrap(); + let lval = agent.vm.pop_stack(gc.nogc()); + let rval = agent.vm.take_result(gc.nogc()); let result = if let (Ok(lnum), Ok(rnum)) = (Number::try_from(lval), Number::try_from(rval)) { - number_binary_operator(agent, instruction, lnum, rnum, gc.into_nogc())?.into() + number_binary_operator( + agent, + instruction, + lnum.unbind(), + rnum.unbind(), + gc.into_nogc(), + )? + .into() } else if let (Ok(lnum), Ok(rnum)) = (BigInt::try_from(lval), BigInt::try_from(rval)) { - bigint_binary_operator(agent, instruction, lnum, rnum, gc.into_nogc())?.into() + bigint_binary_operator( + agent, + instruction, + lnum.unbind(), + rnum.unbind(), + gc.into_nogc(), + )? + .into() } else { - with_vm_gc( + apply_string_or_numeric_binary_operator( agent, - vm, - |agent, gc| apply_string_or_numeric_binary_operator(agent, lval, instruction, rval, gc), + lval.unbind(), + instruction, + rval.unbind(), gc, )? }; - vm.result = Some(result.unbind()); + agent.vm.result = Some(result.unbind()); Ok(()) } pub(super) fn execute_object_define_property<'gc>( agent: &mut Agent, - vm: &mut Vm, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let key = vm.stack.pop().unwrap(); - let key = with_vm_gc( - agent, - vm, - |agent, gc| to_property_key(agent, key, gc), - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); + let key = agent.vm.pop_stack(gc.nogc()); + agent.vm.execute_load(); + let key = to_property_key(agent, key.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); let key = key.unbind().bind(gc.nogc()); - let value = vm.result.take().unwrap().bind(gc.nogc()); - let object = vm.stack.last().unwrap().bind(gc.nogc()); + let value = agent.vm.pop_stack(gc.nogc()); + let object = agent.vm.get_last_stack_value(gc.nogc()); let object = Object::try_from(object).unwrap(); create_data_property_or_throw(agent, object.unbind(), key.unbind(), value.unbind(), gc)?; @@ -419,26 +381,20 @@ pub(super) fn execute_object_define_property<'gc>( pub(super) fn execute_object_define_method<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let FunctionExpression { expression, .. } = - executable.fetch_function_expression(agent, instr.get_first_index(), gc.nogc()); + let FunctionExpression { expression, .. } = agent + .current_executable(gc.nogc()) + .fetch_function_expression(agent, instr.get_first_index(), gc.nogc()); let function_expression = expression.get(); let enumerable = instr.get_second_bool(); // 1. Let propKey be ? Evaluation of ClassElementName. - let prop_key = vm.stack.pop().unwrap(); - let prop_key = with_vm_gc( - agent, - vm, - |agent, gc| to_property_key(agent, prop_key, gc), - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); - let object = Object::try_from(*vm.stack.last().unwrap()) + let prop_key = agent.vm.pop_stack(gc.nogc()); + let prop_key = to_property_key(agent, prop_key.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + let object = Object::try_from(agent.vm.get_last_stack_value(gc.nogc())) .unwrap() .bind(gc.nogc()); @@ -508,37 +464,27 @@ pub(super) fn execute_object_define_method<'gc>( let object = object.unbind(); let prop_key = prop_key.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| define_property_or_throw(agent, object, prop_key, desc, gc), - gc, - )?; + + define_property_or_throw(agent, object, prop_key, desc, gc)?; // c. Return unused. Ok(()) } pub(super) fn execute_object_define_getter<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let FunctionExpression { expression, .. } = - executable.fetch_function_expression(agent, instr.get_first_index(), gc.nogc()); + let FunctionExpression { expression, .. } = agent + .current_executable(gc.nogc()) + .fetch_function_expression(agent, instr.get_first_index(), gc.nogc()); let function_expression = expression.get(); let enumerable = instr.get_second_bool(); // 1. Let propKey be ? Evaluation of ClassElementName. - let prop_key = vm.stack.pop().unwrap(); - let prop_key = with_vm_gc( - agent, - vm, - |agent, gc| to_property_key(agent, prop_key, gc), - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); + let prop_key = agent.vm.pop_stack(gc.nogc()); + let prop_key = to_property_key(agent, prop_key.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); // 2. Let env be the running execution context's LexicalEnvironment. let env = agent.current_lexical_environment(gc.nogc()); // 3. Let privateEnv be the running execution context's PrivateEnvironment. @@ -568,7 +514,7 @@ pub(super) fn execute_object_define_getter<'gc>( // ). let closure = ordinary_function_create(agent, params, gc.nogc()); // 7. Perform MakeMethod(closure, object). - let object = Object::try_from(*vm.stack.last().unwrap()) + let object = Object::try_from(agent.vm.get_last_stack_value(gc.nogc())) .unwrap() .bind(gc.nogc()); make_method(agent, closure, object); @@ -597,37 +543,26 @@ pub(super) fn execute_object_define_getter<'gc>( // b. Perform ? DefinePropertyOrThrow(object, propKey, desc). let object = object.unbind(); let prop_key = prop_key.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| define_property_or_throw(agent, object, prop_key, desc, gc), - gc, - )?; + define_property_or_throw(agent, object, prop_key, desc, gc)?; // c. Return unused. Ok(()) } pub(super) fn execute_object_define_setter<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let FunctionExpression { expression, .. } = - executable.fetch_function_expression(agent, instr.get_first_index(), gc.nogc()); + let FunctionExpression { expression, .. } = agent + .current_executable(gc.nogc()) + .fetch_function_expression(agent, instr.get_first_index(), gc.nogc()); let function_expression = expression.get(); let enumerable = instr.get_second_bool(); // 1. Let propKey be ? Evaluation of ClassElementName. - let prop_key = vm.stack.pop().unwrap(); - let prop_key = with_vm_gc( - agent, - vm, - |agent, gc| to_property_key(agent, prop_key, gc), - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); + let prop_key = agent.vm.pop_stack(gc.nogc()); + let prop_key = to_property_key(agent, prop_key.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); // 2. Let env be the running execution context's LexicalEnvironment. let env = agent.current_lexical_environment(gc.nogc()); // 3. Let privateEnv be the running execution context's PrivateEnvironment. @@ -653,7 +588,7 @@ pub(super) fn execute_object_define_setter<'gc>( // ). let closure = ordinary_function_create(agent, params, gc.nogc()); // 6. Perform MakeMethod(closure, object). - let object = Object::try_from(*vm.stack.last().unwrap()) + let object = Object::try_from(agent.vm.get_last_stack_value(gc.nogc())) .unwrap() .bind(gc.nogc()); make_method(agent, closure, object); @@ -682,24 +617,18 @@ pub(super) fn execute_object_define_setter<'gc>( // b. Perform ? DefinePropertyOrThrow(object, propKey, desc). let object = object.unbind(); let prop_key = prop_key.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| define_property_or_throw(agent, object, prop_key, desc, gc), - gc, - )?; + define_property_or_throw(agent, object, prop_key, desc, gc)?; // c. Return unused. Ok(()) } pub(super) fn execute_object_set_prototype<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let prop_value = vm.result.take().unwrap().bind(gc.nogc()); + let prop_value = agent.vm.take_result(gc.nogc()); // i. Perform ! object.[[SetPrototypeOf]](propValue). - let object = Object::try_from(*vm.stack.last().unwrap()) + let object = Object::try_from(agent.vm.get_last_stack_value(gc.nogc())) .unwrap() .bind(gc.nogc()); @@ -714,12 +643,7 @@ pub(super) fn execute_object_set_prototype<'gc>( }; let object = object.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| object.internal_set_prototype_of(agent, prop_value, gc), - gc, - )?; + object.internal_set_prototype_of(agent, prop_value, gc)?; // b. Return unused. Ok(()) } @@ -727,17 +651,19 @@ pub(super) fn execute_object_set_prototype<'gc>( #[inline(always)] pub(super) fn execute_put_value<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, cache: bool, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let value = vm.result.take().unwrap().bind(gc.nogc()); - let mut reference = vm.reference.take().unwrap().bind(gc.nogc()); + let value = agent.vm.take_result(gc.nogc()); + let mut reference = agent.vm.reference.take().unwrap().bind(gc.nogc()); let cache = if cache { - Some(executable.fetch_cache(agent, instr.get_first_index(), gc.nogc())) + Some(agent.current_executable(gc.nogc()).fetch_cache( + agent, + instr.get_first_index(), + gc.nogc(), + )) } else { None }; @@ -752,7 +678,6 @@ pub(super) fn execute_put_value<'gc>( } _ => handle_set_value_break( agent, - vm, &reference.unbind(), result.unbind(), value.unbind(), @@ -765,24 +690,23 @@ pub(super) fn execute_put_value<'gc>( #[inline(always)] pub(super) fn execute_get_value<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, cache: bool, - keep_reference: bool, - mut gc: GcScope<'gc, '_>, + gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { + // Clobber result before acting. + if cfg!(debug_assertions) { + agent.vm.result = None; + } // 1. If V is not a Reference Record, return V. - let reference = if keep_reference { - handle_keep_reference(agent, vm, gc.reborrow()) - .unbind()? - .bind(gc.nogc()) - } else { - vm.reference.take().unwrap() - }; + let reference = agent.vm.reference.take().unwrap(); let cache = if cache { - Some(executable.fetch_cache(agent, instr.get_first_index(), gc.nogc())) + Some(agent.current_executable(gc.nogc()).fetch_cache( + agent, + instr.get_first_index(), + gc.nogc(), + )) } else { None }; @@ -794,41 +718,39 @@ pub(super) fn execute_get_value<'gc>( ControlFlow::Break(TryError::Err(err)) => { return Err(err.unbind().bind(gc.into_nogc())); } - _ => handle_get_value_break(agent, vm, &reference.unbind(), result.unbind(), gc)?, + _ => handle_get_value_break(agent, &reference.unbind(), result.unbind(), gc)?, }; - vm.result = Some(result.unbind()); + agent.vm.result = Some(result.unbind()); Ok(()) } #[cold] -fn handle_keep_reference<'gc>( +pub(super) fn execute_evaluate_reference<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, -) -> JsResult<'gc, Reference<'gc>> { - let reference = vm.reference.as_mut().unwrap(); - if is_property_reference(reference) && !reference.is_static_property_reference() { - if let Ok(referenced_name) = - Primitive::try_from(reference.referenced_name_value().bind(gc.nogc())) - { - let referenced_name = to_property_key_primitive(agent, referenced_name, gc.nogc()); - reference.set_referenced_name_to_property_key(referenced_name); - Ok(reference.clone().bind(gc.into_nogc())) - } else { - mutate_reference_property_key(agent, vm, gc) - } +) -> JsResult<'gc, ()> { + #[cfg(debug_assertions)] + let stack_len = agent.vm.reference_stack.len(); + let reference = agent.vm.reference_stack.last().unwrap(); + if reference.is_expression() { + mutate_reference_property_key(agent, gc)?; } else { - Ok(reference.clone().bind(gc.into_nogc())) + agent.vm.reference = Some(reference.clone()); } + debug_assert_eq!( + agent.vm.reference_stack.len(), + stack_len, + "Evaluating reference key changed reference stack length" + ); + Ok(()) } pub(super) fn execute_typeof<'gc>( agent: &mut Agent, - vm: &mut Vm, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { // 2. If val is a Reference Record, then - let val = if let Some(reference) = vm.reference.take() { + let val = if let Some(reference) = agent.vm.reference.take() { // a. If IsUnresolvableReference(val) is true, if is_unresolvable_reference(&reference) { // return "undefined". @@ -842,80 +764,62 @@ pub(super) fn execute_typeof<'gc>( ControlFlow::Break(TryError::Err(err)) => { return Err(err.unbind().bind(gc.into_nogc())); } - _ => with_vm_gc( - agent, - vm, - |agent, gc| get_value(agent, &reference, gc), - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()), + _ => get_value(agent, &reference, gc.reborrow()) + .unbind()? + .bind(gc.nogc()), } } } else { - vm.result.unwrap().bind(gc.nogc()) + agent.vm.take_result(gc.nogc()) }; - vm.result = Some(typeof_operator(agent, val, gc.nogc()).into()); + agent.vm.result = Some(typeof_operator(agent, val, gc.nogc()).into()); Ok(()) } -pub(super) fn execute_object_create(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) { +pub(super) fn execute_object_create(agent: &mut Agent, gc: NoGcScope) { let object = ordinary_object_create_with_intrinsics(agent, ProtoIntrinsics::Object, None, gc); - vm.stack.push(object.unbind().into()); + agent.vm.stack.push(object.unbind().into()); } -pub(super) fn execute_object_create_with_shape( - agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, - instr: Instr, - gc: NoGcScope, -) { - let shape = executable.fetch_object_shape(agent, instr.get_first_index(), gc); +pub(super) fn execute_object_create_with_shape(agent: &mut Agent, instr: Instr, gc: NoGcScope) { + let shape = agent + .current_executable(gc) + .fetch_object_shape(agent, instr.get_first_index(), gc); let len = shape.len(agent); - let first_property_index = vm.stack.len() - len as usize; + let first_property_index = agent.vm.stack.len() - len as usize; let obj = OrdinaryObject::create_object_with_shape_and_data_properties( - agent, + &mut agent.heap, shape, - &vm.stack[first_property_index..], + &agent.vm.stack[first_property_index..], ); - vm.stack.truncate(first_property_index); - vm.result = Some(obj.unbind().into()); + agent.vm.stack.truncate(first_property_index); + agent.vm.result = Some(obj.unbind().into()); } pub(super) fn execute_copy_data_properties<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let source = vm.result.take().unwrap(); - let Value::Object(target) = *vm.stack.last().unwrap() else { + let source = agent.vm.take_result(gc.nogc()); + let Value::Object(target) = agent.vm.get_last_stack_value(gc.nogc()) else { unreachable!() }; - with_vm_gc( - agent, - vm, - |agent, gc| copy_data_properties(agent, target, source, gc), - gc, - )?; + copy_data_properties(agent, target.unbind(), source.unbind(), gc)?; Ok(()) } pub(super) fn execute_copy_data_properties_into_object<'gc>( agent: &mut Agent, - vm: &mut Vm, instr: Instr, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let from = Object::try_from(vm.result.unwrap()) - .unwrap() - .bind(gc.nogc()); + let from = Object::try_from(agent.vm.take_result(gc.nogc())).unwrap(); let num_excluded_items = instr.get_first_index(); let mut excluded_items = PropertyKeySet::with_capacity(num_excluded_items, gc.nogc()); - assert!(vm.reference.is_none()); + assert!(agent.vm.reference.is_none()); for _ in 0..num_excluded_items { - let reference = vm.reference_stack.pop().unwrap(); + let reference = agent.vm.pop_reference_stack(); debug_assert_eq!(reference.base_value(), from.into()); debug_assert!(!is_super_reference(&reference)); excluded_items.insert(agent, reference.referenced_name_property_key()); @@ -924,25 +828,18 @@ pub(super) fn execute_copy_data_properties_into_object<'gc>( if let TryResult::Continue(result) = try_copy_data_properties_into_object(agent, from, &excluded_items, gc.nogc()) { - vm.result = Some(result.unbind().into()); + agent.vm.result = Some(result.unbind().into()); } else { let from = from.unbind(); let excluded_items = excluded_items.scope(agent, gc.nogc()); - let result = with_vm_gc( - agent, - vm, - |agent, gc| copy_data_properties_into_object(agent, from, excluded_items, gc), - gc, - )?; - vm.result = Some(result.unbind().into()); + let result = copy_data_properties_into_object(agent, from, excluded_items, gc)?; + agent.vm.result = Some(result.unbind().into()); } Ok(()) } pub(super) fn execute_instantiate_arrow_function_expression<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { @@ -950,7 +847,9 @@ pub(super) fn execute_instantiate_arrow_function_expression<'gc>( let ArrowFunctionExpression { expression, identifier, - } = executable.fetch_arrow_function_expression(agent, instr.get_first_index()); + } = agent + .current_executable(gc.nogc()) + .fetch_arrow_function_expression(agent, instr.get_first_index()); let function_expression = expression.get(); let identifier = *identifier; // 2. Let env be the LexicalEnvironment of the running execution context. @@ -975,7 +874,7 @@ pub(super) fn execute_instantiate_arrow_function_expression<'gc>( let name = if let Some(parameter) = &identifier { let pk_result = match parameter { NamedEvaluationParameter::Result => { - let value = vm.result.take().unwrap().bind(gc.nogc()); + let value = agent.vm.take_result(gc.nogc()); if let Some(pk) = to_property_key_simple(agent, value, gc.nogc()) { Ok(pk) } else { @@ -983,7 +882,7 @@ pub(super) fn execute_instantiate_arrow_function_expression<'gc>( } } NamedEvaluationParameter::Stack => { - let value = vm.stack.last().unwrap().bind(gc.nogc()); + let value = agent.vm.get_last_stack_value(gc.nogc()); if let Some(pk) = to_property_key_simple(agent, value, gc.nogc()) { Ok(pk) } else { @@ -997,14 +896,9 @@ pub(super) fn execute_instantiate_arrow_function_expression<'gc>( Err(pk_value) => { let scoped_function = function.scope(agent, gc.nogc()); let pk_value = pk_value.unbind(); - let pk = with_vm_gc( - agent, - vm, - |agent, gc| to_property_key_complex(agent, pk_value, gc), - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); + let pk = to_property_key_complex(agent, pk_value, gc.reborrow()) + .unbind()? + .bind(gc.nogc()); // SAFETY: not shared. function = unsafe { scoped_function.take(agent).bind(gc.nogc()) }; pk @@ -1015,14 +909,12 @@ pub(super) fn execute_instantiate_arrow_function_expression<'gc>( pk.bind(gc.nogc()) }; set_function_name(agent, function, name, None, gc.nogc()); - vm.result = Some(function.unbind().into()); + agent.vm.result = Some(function.unbind().into()); Ok(()) } pub(super) fn execute_instantiate_ordinary_function_expression<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { @@ -1030,7 +922,9 @@ pub(super) fn execute_instantiate_ordinary_function_expression<'gc>( expression, identifier, .. - } = executable.fetch_function_expression(agent, instr.get_first_index(), gc.nogc()); + } = agent + .current_executable(gc.nogc()) + .fetch_function_expression(agent, instr.get_first_index(), gc.nogc()); let function_expression = expression.get(); let identifier = *identifier; @@ -1038,17 +932,12 @@ pub(super) fn execute_instantiate_ordinary_function_expression<'gc>( debug_assert!(function_expression.id.is_none()); // 1. If name is not present, set name to "". let pk = match parameter { - NamedEvaluationParameter::Result => vm.result.take().unwrap(), - NamedEvaluationParameter::Stack => *vm.stack.last().unwrap(), + NamedEvaluationParameter::Result => agent.vm.take_result(gc.nogc()), + NamedEvaluationParameter::Stack => agent.vm.get_last_stack_value(gc.nogc()), }; - let name = with_vm_gc( - agent, - vm, - |agent, gc| to_property_key(agent, pk, gc), - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); + let name = to_property_key(agent, pk.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); // 2. Let env be the LexicalEnvironment of the running execution // context. let env = agent.current_lexical_environment(gc.nogc()); @@ -1141,7 +1030,9 @@ pub(super) fn execute_instantiate_ordinary_function_expression<'gc>( let function = ordinary_function_create(agent, params, gc.nogc()); let FunctionExpression { compiled_bytecode, .. - } = executable.fetch_function_expression(agent, instr.get_first_index(), gc.nogc()); + } = agent + .current_executable(gc.nogc()) + .fetch_function_expression(agent, instr.get_first_index(), gc.nogc()); if let Some(compiled_bytecode) = compiled_bytecode { function.get_mut(agent).compiled_bytecode = Some(compiled_bytecode.unbind()); } @@ -1201,19 +1092,17 @@ pub(super) fn execute_instantiate_ordinary_function_expression<'gc>( unwrap_try(env.try_initialize_binding(agent, name, None, function.into(), gc.nogc())); } - vm.result = Some(function.unbind().into()); + agent.vm.result = Some(function.unbind().into()); Ok(()) } pub(super) fn execute_class_define_constructor<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let name = vm.stack.pop().unwrap(); - let class_name = set_class_name(agent, vm, name, gc.reborrow()) + let name = agent.vm.pop_stack(gc.nogc()); + let class_name = set_class_name(agent, name.unbind(), gc.reborrow()) .unbind()? .bind(gc.nogc()); @@ -1221,17 +1110,19 @@ pub(super) fn execute_class_define_constructor<'gc>( expression, compiled_bytecode, .. - } = executable.fetch_function_expression(agent, instr.get_first_index(), gc.nogc()); + } = agent + .current_executable(gc.nogc()) + .fetch_function_expression(agent, instr.get_first_index(), gc.nogc()); let function_expression = expression.get(); let compiled_bytecode = *compiled_bytecode; let has_constructor_parent = instr.get_second_bool(); let function_prototype = if has_constructor_parent { - Some(Object::try_from(vm.stack.pop().unwrap()).unwrap()) + Some(Object::try_from(agent.vm.pop_stack(gc.nogc())).unwrap()) } else { None }; - let proto = OrdinaryObject::try_from(*vm.stack.last().unwrap()).unwrap(); + let proto = OrdinaryObject::try_from(agent.vm.get_last_stack_value(gc.nogc())).unwrap(); let is_null_derived_class = !has_constructor_parent && unwrap_try(proto.try_get_prototype_of(agent, gc.nogc())).is_none(); @@ -1278,27 +1169,26 @@ pub(super) fn execute_class_define_constructor<'gc>( gc.nogc(), )); - vm.result = Some(function.unbind().into()); + agent.vm.result = Some(function.unbind().into()); Ok(()) } pub(super) fn execute_class_define_default_constructor<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let name = vm.stack.pop().unwrap(); - let class_name = set_class_name(agent, vm, name, gc.reborrow()) + let name = agent.vm.pop_stack(gc.nogc()); + let class_name = set_class_name(agent, name.unbind(), gc.reborrow()) .unbind()? .bind(gc.nogc()); let class_initializer_bytecode_index = instr.get_first_index(); - let (compiled_initializer_bytecode, has_constructor_parent) = executable + let (compiled_initializer_bytecode, has_constructor_parent) = agent + .current_executable(gc.nogc()) .fetch_class_initializer_bytecode(agent, class_initializer_bytecode_index, gc.nogc()); let function_prototype = if has_constructor_parent { - Some(Object::try_from(vm.stack.pop().unwrap()).unwrap()) + Some(Object::try_from(agent.vm.pop_stack(gc.nogc())).unwrap()) } else { Some( agent @@ -1308,7 +1198,7 @@ pub(super) fn execute_class_define_default_constructor<'gc>( .into(), ) }; - let proto = Object::try_from(*vm.stack.last().unwrap()).unwrap(); + let proto = Object::try_from(agent.vm.get_last_stack_value(gc.nogc())).unwrap(); let env = agent.current_lexical_environment(gc.nogc()); let private_env = agent.current_private_environment(gc.nogc()); @@ -1344,29 +1234,28 @@ pub(super) fn execute_class_define_default_constructor<'gc>( gc.nogc(), )); - vm.result = Some(function.unbind().into()); + agent.vm.result = Some(function.unbind().into()); Ok(()) } pub(super) fn execute_class_define_private_method<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let description = String::try_from(vm.result.take().unwrap().bind(gc.nogc())).unwrap(); - let FunctionExpression { expression, .. } = - executable.fetch_function_expression(agent, instr.get_first_index(), gc.nogc()); + let description = String::try_from(agent.vm.take_result(gc.nogc())).unwrap(); + let FunctionExpression { expression, .. } = agent + .current_executable(gc.nogc()) + .fetch_function_expression(agent, instr.get_first_index(), gc.nogc()); let bits = instr.get_second_index() as u8; let is_static = bits & 0b100 == 0b100; let is_setter = bits & 0b10 == 0b10; let is_getter = bits & 0b1 == 0b1; - assert!(vm.stack.len() >= 2); + assert!(agent.vm.stack.len() >= 2); let object = if is_static { - vm.stack[vm.stack.len() - 1] + agent.vm.stack[agent.vm.stack.len() - 1] } else { - vm.stack[vm.stack.len() - 2] + agent.vm.stack[agent.vm.stack.len() - 2] }; let object = Object::try_from(object).unwrap().bind(gc.nogc()); let function_expression = expression.get(); @@ -1444,12 +1333,7 @@ pub(super) fn execute_class_define_private_method<'gc>( // b. Perform ? DefinePropertyOrThrow(object, propKey, desc). let private_name = private_env.add_static_private_method(agent, description); let object = object.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| define_property_or_throw(agent, object, private_name.into(), desc, gc), - gc, - )?; + define_property_or_throw(agent, object, private_name.into(), desc, gc)?; } else { // a. Return PrivateElement { // [[Key]]: propKey, @@ -1472,19 +1356,20 @@ pub(super) fn execute_class_define_private_method<'gc>( pub(super) fn execute_class_define_private_property<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, gc: NoGcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let description = executable.fetch_identifier(agent, instr.get_first_index(), gc); + let description = + agent + .current_executable(gc) + .fetch_identifier(agent, instr.get_first_index(), gc); let is_static = instr.get_second_bool(); let private_env = agent .current_private_environment(gc) .expect("Attempted to define private property with no PrivateEnvironment"); if is_static { let private_name = private_env.add_static_private_field(agent, description); - let object = vm.stack.last().unwrap().bind(gc); + let object = agent.vm.get_last_stack_value(gc); let object = Object::try_from(object).unwrap(); if let Err(err) = object .get_or_create_backing_object(agent) @@ -1502,10 +1387,9 @@ pub(super) fn execute_class_define_private_property<'gc>( pub(super) fn execute_class_initialize_private_elements<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: NoGcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let target = Object::try_from(vm.stack.last().unwrap().bind(gc)).unwrap(); + let target = Object::try_from(agent.vm.get_last_stack_value(gc)).unwrap(); target .get_or_create_backing_object(agent) .property_storage() @@ -1515,12 +1399,11 @@ pub(super) fn execute_class_initialize_private_elements<'gc>( pub(super) fn execute_class_initialize_private_value<'gc>( agent: &mut Agent, - vm: &mut Vm, instr: Instr, gc: NoGcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let target = Object::try_from(vm.stack.last().unwrap().bind(gc)).unwrap(); - let value = vm.result.take().unwrap().bind(gc); + let target = Object::try_from(agent.vm.get_last_stack_value(gc)).unwrap(); + let value = agent.vm.take_result(gc); if target.is_proxy() { return Err(throw_no_proxy_private_names(agent, gc)); } @@ -1543,25 +1426,16 @@ pub(super) fn execute_class_initialize_private_value<'gc>( pub(super) fn execute_direct_eval_call<'gc>( agent: &mut Agent, - vm: &mut Vm, instr: Instr, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let func = with_vm_gc( - agent, - vm, - |agent, mut gc| { - let func_ref = - resolve_binding(agent, BUILTIN_STRING_MEMORY.eval, None, None, gc.reborrow()) - .unbind()? - .bind(gc.nogc()); - get_value(agent, &func_ref.unbind(), gc) - }, - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); - let args = vm.get_call_args(instr, gc.nogc()); + let func_ref = resolve_binding(agent, BUILTIN_STRING_MEMORY.eval, None, None, gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + let func = get_value(agent, &func_ref.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + let args = agent.vm.get_call_args(instr, gc.nogc()); // a. If SameValue(func, %eval%) is true, then let result = if func == agent.current_realm_record().intrinsics().eval().into() { @@ -1578,42 +1452,29 @@ pub(super) fn execute_direct_eval_call<'gc>( let strict_caller = agent.is_evaluating_strict_code(); // v. Return ? PerformEval(evalArg, strictCaller, true). let eval_arg = eval_arg.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| perform_eval(agent, eval_arg, true, strict_caller, gc), - gc, - )? + perform_eval(agent, eval_arg, true, strict_caller, gc)? } } else { let func = func.unbind(); let mut args = args.unbind(); - with_vm_gc( + call( agent, - vm, - |agent, gc| { - call( - agent, - func, - Value::Undefined, - Some(ArgumentsList::from_mut_slice(args.as_mut_slice())), - gc, - ) - }, + func, + Value::Undefined, + Some(ArgumentsList::from_mut_slice(args.as_mut_slice())), gc, )? }; - vm.result = Some(result.unbind()); + agent.vm.result = Some(result.unbind()); Ok(()) } pub(super) fn execute_evaluate_call<'gc>( agent: &mut Agent, - vm: &mut Vm, instr: Instr, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let reference = vm.reference.take(); + let reference = agent.vm.reference.take(); // 1. If ref is a Reference Record, then let this_value = if let Some(reference) = reference { // a. If IsPropertyReference(ref) is true, then @@ -1636,45 +1497,29 @@ pub(super) fn execute_evaluate_call<'gc>( // a. Let thisValue be undefined. Value::Undefined }; - let mut args = vm.get_call_args(instr, gc.nogc()).unbind(); - let func = vm.stack.pop().unwrap().unbind(); + let mut args = agent.vm.get_call_args(instr, gc.nogc()).unbind(); + let func = agent.vm.pop_stack(gc.nogc()).unbind(); let this_value = this_value.unbind(); - let result = with_vm_gc( + let result = call( agent, - vm, - |agent, gc| { - call( - agent, - func, - this_value, - Some(ArgumentsList::from_mut_slice(args.as_mut_slice())), - gc, - ) - }, + func, + this_value, + Some(ArgumentsList::from_mut_slice(args.as_mut_slice())), gc, - ); - vm.result = Some(result?.unbind()); + )?; + agent.vm.result = Some(result.unbind()); Ok(()) } pub(super) fn execute_evaluate_new<'gc>( agent: &mut Agent, - vm: &mut Vm, instr: Instr, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let args = vm.get_call_args(instr, gc.nogc()); - let constructor = vm.stack.pop().unwrap().bind(gc.nogc()); + let args = agent.vm.get_call_args(instr, gc.nogc()); + let constructor = agent.vm.pop_stack(gc.nogc()).bind(gc.nogc()); let Some(constructor) = is_constructor(agent, constructor) else { - let constructor_string = { - let constructor = constructor.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| constructor.string_repr(agent, gc), - gc.reborrow(), - ) - }; + let constructor_string = constructor.unbind().string_repr(agent, gc.reborrow()); let error_message = format!( "'{}' is not a constructor.", constructor_string.to_string_lossy_(agent) @@ -1684,27 +1529,19 @@ pub(super) fn execute_evaluate_new<'gc>( let constructor = constructor.unbind(); let mut args = args.unbind(); - let result = with_vm_gc( + let result = construct( agent, - vm, - |agent, gc| { - construct( - agent, - constructor, - Some(ArgumentsList::from_mut_slice(args.as_mut_slice())), - None, - gc, - ) - }, + constructor, + Some(ArgumentsList::from_mut_slice(args.as_mut_slice())), + None, gc, )?; - vm.result = Some(result.unbind().into()); + agent.vm.result = Some(result.unbind().into()); Ok(()) } pub(super) fn execute_evaluate_super<'gc>( agent: &mut Agent, - vm: &mut Vm, instr: Instr, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { @@ -1725,20 +1562,16 @@ pub(super) fn execute_evaluate_super<'gc>( ) }; // 4. Let argList be ? ArgumentListEvaluation of Arguments. - let arg_list = vm.get_call_args(instr, gc.nogc()); + let arg_list = agent.vm.get_call_args(instr, gc.nogc()); // 5. If IsConstructor(func) is false, throw a TypeError exception. let Some(func) = func.and_then(|func| is_constructor(agent, func)) else { - let constructor = func.map_or(Value::Null, |f| f.unbind().into()); - let error_message = with_vm_gc( - agent, - vm, - |agent, gc| { - format!( - "'{}' is not a constructor.", - constructor.string_repr(agent, gc).to_string_lossy_(agent) - ) - }, - gc.reborrow(), + let constructor = func.map_or(Value::Null, |f| f.into()); + let error_message = format!( + "'{}' is not a constructor.", + constructor + .unbind() + .string_repr(agent, gc.reborrow()) + .to_string_lossy_(agent) ); return Err(agent.throw_exception(ExceptionType::TypeError, error_message, gc.into_nogc())); }; @@ -1747,18 +1580,11 @@ pub(super) fn execute_evaluate_super<'gc>( let func = func.unbind(); let mut arg_list = arg_list.unbind(); let new_target = new_target.unbind(); - let result = with_vm_gc( + let result = construct( agent, - vm, - |agent, gc| { - construct( - agent, - func, - Some(ArgumentsList::from_mut_slice(arg_list.as_mut_slice())), - Some(new_target), - gc, - ) - }, + func, + Some(ArgumentsList::from_mut_slice(arg_list.as_mut_slice())), + Some(new_target), gc.reborrow(), ) .unbind()? @@ -1781,21 +1607,20 @@ pub(super) fn execute_evaluate_super<'gc>( }; // 11. Perform ? InitializeInstanceElements(result, F). // 12. Return result. - vm.result = Some(result.unbind().into()); + agent.vm.result = Some(result.unbind().into()); Ok(()) } #[inline(always)] pub(super) fn execute_evaluate_property_access_with_expression_key( agent: &mut Agent, - vm: &mut Vm, gc: NoGcScope, ) { - let property_name_value = vm.result.take().unwrap().bind(gc); - let base_value = vm.stack.pop().unwrap().bind(gc); + let property_name_value = agent.vm.take_result(gc); + let base_value = agent.vm.pop_stack(gc); let strict = agent.is_evaluating_strict_code(); - vm.reference = Some( + agent.vm.reference = Some( Reference::new_property_expression_reference(base_value, property_name_value, strict) .unbind(), ); @@ -1804,28 +1629,26 @@ pub(super) fn execute_evaluate_property_access_with_expression_key( #[inline(always)] pub(super) fn execute_evaluate_property_access_with_identifier_key( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, gc: NoGcScope, ) { - let property_key = executable.fetch_property_key(agent, instr.get_first_index(), gc); - let base_value = vm.result.take().unwrap().bind(gc); + let property_key = + agent + .current_executable(gc) + .fetch_property_key(agent, instr.get_first_index(), gc); + let base_value = agent.vm.take_result(gc); let strict = agent.is_evaluating_strict_code(); - vm.reference = + agent.vm.reference = Some(Reference::new_property_reference(base_value, property_key, strict).unbind()); } -pub(super) fn execute_make_private_reference( - agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, - instr: Instr, - gc: NoGcScope, -) { - let private_identifier = executable.fetch_identifier(agent, instr.get_first_index(), gc); - let base_value = vm.result.take().unwrap().bind(gc); +pub(super) fn execute_make_private_reference(agent: &mut Agent, instr: Instr, gc: NoGcScope) { + let private_identifier = + agent + .current_executable(gc) + .fetch_identifier(agent, instr.get_first_index(), gc); + let base_value = agent.vm.take_result(gc); // 1. Let privateEnv be the running execution context's // PrivateEnvironment. // 2. Assert: privateEnv is not null. @@ -1840,13 +1663,14 @@ pub(super) fn execute_make_private_reference( // [[Strict]]: true, // [[ThisValue]]: empty // }. - vm.reference + agent + .vm + .reference .replace(Reference::new_private_reference(base_value, private_name).unbind()); } pub(super) fn execute_make_super_property_reference_with_expression_key<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: NoGcScope<'gc, '_>, ) -> JsResult<'gc, ()> { // ### SuperProperty : super [ Expression ] @@ -1857,7 +1681,7 @@ pub(super) fn execute_make_super_property_reference_with_expression_key<'gc>( let actual_this = env.get_this_binding(agent, gc).unbind()?.bind(gc); // 3. Let propertyNameReference be ? Evaluation of Expression. // 4. Let propertyNameValue be ? GetValue(propertyNameReference). - let property_name_value = vm.result.take().unwrap().bind(gc); + let property_name_value = agent.vm.take_result(gc); // 5. Let strict be IsStrict(this SuperProperty). let strict = agent.is_evaluating_strict_code(); // 6. NOTE: In most cases, ToPropertyKey will be performed on @@ -1881,7 +1705,7 @@ pub(super) fn execute_make_super_property_reference_with_expression_key<'gc>( // 5. Return the Reference Record { // [[Base]]: baseValue, - vm.reference = Some( + agent.vm.reference = Some( Reference::new_super_expression_reference( base_value, // [[ReferencedName]]: propertyKey, @@ -1899,8 +1723,6 @@ pub(super) fn execute_make_super_property_reference_with_expression_key<'gc>( pub(super) fn execute_make_super_property_reference_with_identifier_key<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, gc: NoGcScope<'gc, '_>, ) -> JsResult<'gc, ()> { @@ -1911,7 +1733,10 @@ pub(super) fn execute_make_super_property_reference_with_identifier_key<'gc>( // 2. Let actualThis be ? env.GetThisBinding(). let actual_this = env.get_this_binding(agent, gc).unbind()?.bind(gc); // 3. Let propertyKey be the StringValue of IdentifierName. - let property_key = executable.fetch_property_key(agent, instr.get_first_index(), gc); + let property_key = + agent + .current_executable(gc) + .fetch_property_key(agent, instr.get_first_index(), gc); // 4. Let strict be IsStrict(this SuperProperty). let strict = agent.is_evaluating_strict_code(); // 5. Return MakeSuperPropertyReference(actualThis, propertyKey, strict). @@ -1926,7 +1751,7 @@ pub(super) fn execute_make_super_property_reference_with_identifier_key<'gc>( let base_value = env.get_super_base(agent, gc); // 4. Let baseValue be GetSuperBase(env). // 5. Return the Reference Record { - vm.reference = Some( + agent.vm.reference = Some( Reference::new_super_reference( // [[Base]]: baseValue, base_value, @@ -1944,29 +1769,29 @@ pub(super) fn execute_make_super_property_reference_with_identifier_key<'gc>( } #[inline(always)] -pub(super) fn execute_jump(agent: &Agent, vm: &mut Vm, instr: Instr) { +pub(super) fn execute_jump(agent: &mut Agent, instr: Instr) { let ip = instr.get_jump_slot(); if agent.options.print_internals { eprintln!("Jumping to {ip}"); } - vm.ip = ip; + agent.vm.ip = ip; } #[inline(always)] -pub(super) fn execute_jump_if_not(agent: &Agent, vm: &mut Vm, instr: Instr) { - let result = vm.result.take().unwrap(); +pub(super) fn execute_jump_if_not(agent: &mut Agent, instr: Instr, gc: NoGcScope) { + let result = agent.vm.take_result(gc); let ip = instr.get_jump_slot(); if !to_boolean(agent, result) { if agent.options.print_internals { eprintln!("Comparison failed, jumping to {ip}"); } - vm.ip = ip; + agent.vm.ip = ip; } } #[inline(always)] -pub(super) fn execute_jump_if_true(agent: &Agent, vm: &mut Vm, instr: Instr) { - let result = vm.result.take().unwrap(); +pub(super) fn execute_jump_if_true(agent: &mut Agent, instr: Instr, gc: NoGcScope) { + let result = agent.vm.take_result(gc); let Value::Boolean(result) = result else { unreachable!() }; @@ -1975,12 +1800,12 @@ pub(super) fn execute_jump_if_true(agent: &Agent, vm: &mut Vm, instr: Instr) { if agent.options.print_internals { eprintln!("Comparison succeeded, jumping to {ip}"); } - vm.ip = ip; + agent.vm.ip = ip; } } -pub(super) fn execute_increment(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) { - let lhs = vm.result.take().unwrap().bind(gc); +pub(super) fn execute_increment(agent: &mut Agent, gc: NoGcScope) { + let lhs = agent.vm.take_result(gc); // Note: This is done by the previous instruction. let old_value = Numeric::try_from(lhs).unwrap(); let new_value: Value = if let Ok(old_value) = Number::try_from(old_value) { @@ -1989,11 +1814,11 @@ pub(super) fn execute_increment(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) { let old_value = BigInt::try_from(old_value).unwrap(); BigInt::add(agent, old_value, 1.into()).into() }; - vm.result = Some(new_value.unbind()); + agent.vm.result = Some(new_value.unbind()); } -pub(super) fn execute_decrement(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) { - let lhs = vm.result.take().unwrap().bind(gc); +pub(super) fn execute_decrement(agent: &mut Agent, gc: NoGcScope) { + let lhs = agent.vm.take_result(gc); // Note: This is done by the previous instruction. let old_value = Numeric::try_from(lhs).unwrap(); let new_value: Value = if let Ok(old_value) = Number::try_from(old_value) { @@ -2002,88 +1827,60 @@ pub(super) fn execute_decrement(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) { let old_value = BigInt::try_from(old_value).unwrap(); BigInt::subtract(agent, old_value, 1.into()).into() }; - vm.result = Some(new_value.unbind()); + agent.vm.result = Some(new_value.unbind()); } -pub(super) fn execute_less_than<'gc>( - agent: &mut Agent, - vm: &mut Vm, - gc: GcScope<'gc, '_>, -) -> JsResult<'gc, ()> { - let lval = vm.stack.pop().unwrap(); - let rval = vm.result.take().unwrap(); - let result = with_vm_gc( - agent, - vm, - |agent, gc| is_less_than::(agent, lval, rval, gc), - gc, - )?; +pub(super) fn execute_less_than<'gc>(agent: &mut Agent, gc: GcScope<'gc, '_>) -> JsResult<'gc, ()> { + let lval = agent.vm.pop_stack(gc.nogc()); + let rval = agent.vm.take_result(gc.nogc()); + let result = is_less_than::(agent, lval.unbind(), rval.unbind(), gc)?; let result = result == Some(true); - vm.result = Some(result.into()); + agent.vm.result = Some(result.into()); Ok(()) } pub(super) fn execute_less_than_equals<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let lval = vm.stack.pop().unwrap(); - let rval = vm.result.take().unwrap(); - let result = with_vm_gc( - agent, - vm, - |agent, gc| is_less_than::(agent, rval, lval, gc), - gc, - )?; + let lval = agent.vm.pop_stack(gc.nogc()); + let rval = agent.vm.take_result(gc.nogc()); + let result = is_less_than::(agent, rval.unbind(), lval.unbind(), gc)?; let result = result == Some(false); - vm.result = Some(result.into()); + agent.vm.result = Some(result.into()); Ok(()) } pub(super) fn execute_greater_than<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let lval = vm.stack.pop().unwrap(); - let rval = vm.result.take().unwrap(); - let result = with_vm_gc( - agent, - vm, - |agent, gc| is_less_than::(agent, rval, lval, gc), - gc, - )?; + let lval = agent.vm.pop_stack(gc.nogc()); + let rval = agent.vm.take_result(gc.nogc()); + let result = is_less_than::(agent, rval.unbind(), lval.unbind(), gc)?; let result = result == Some(true); - vm.result = Some(result.into()); + agent.vm.result = Some(result.into()); Ok(()) } pub(super) fn execute_greater_than_equals<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let lval = vm.stack.pop().unwrap(); - let rval = vm.result.take().unwrap(); - let result = with_vm_gc( - agent, - vm, - |agent, gc| is_less_than::(agent, lval, rval, gc), - gc, - )?; + let lval = agent.vm.pop_stack(gc.nogc()); + let rval = agent.vm.take_result(gc.nogc()); + let result = is_less_than::(agent, lval.unbind(), rval.unbind(), gc)?; let result = result == Some(false); - vm.result = Some(result.into()); + agent.vm.result = Some(result.into()); Ok(()) } pub(super) fn execute_has_property<'gc>( agent: &mut Agent, - vm: &mut Vm, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let lval = vm.stack.pop().unwrap().bind(gc.nogc()); - let rval = vm.result.take().unwrap().bind(gc.nogc()); + let lval = agent.vm.pop_stack(gc.nogc()); + let rval = agent.vm.take_result(gc.nogc()); // RelationalExpression : RelationalExpression in ShiftExpression // 5. If rval is not an Object, throw a TypeError exception. let Ok(rval) = Object::try_from(rval) else { @@ -2099,25 +1896,18 @@ pub(super) fn execute_has_property<'gc>( } else { let scoped_rval = rval.scope(agent, gc.nogc()); let lval = lval.unbind(); - let result = with_vm_gc( + let property_key = to_property_key(agent, lval, gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + let result = has_property( agent, - vm, - |agent, mut gc| { - let property_key = to_property_key(agent, lval, gc.reborrow()) - .unbind()? - .bind(gc.nogc()); - has_property( - agent, - // SAFETY: not shred. - unsafe { scoped_rval.take(agent) }, - property_key.unbind(), - gc, - ) - }, + // SAFETY: not shred. + unsafe { scoped_rval.take(agent) }, + property_key.unbind(), gc.reborrow(), ) .unbind()?; - vm.result = Some(result.into()); + agent.vm.result = Some(result.into()); return Ok(()); }; let result = match try_has_property(agent, rval, property_key, None, gc.nogc()) { @@ -2127,12 +1917,7 @@ pub(super) fn execute_has_property<'gc>( TryHasResult::Proxy(proxy) => { let proxy = proxy.unbind(); let property_key = property_key.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| proxy.internal_has_property(agent, property_key, gc), - gc, - )? + proxy.internal_has_property(agent, property_key, gc)? } }, ControlFlow::Break(TryError::Err(err)) => { @@ -2141,24 +1926,18 @@ pub(super) fn execute_has_property<'gc>( ControlFlow::Break(TryError::GcError) => { let rval = rval.unbind(); let property_key = property_key.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| has_property(agent, rval.unbind(), property_key.unbind(), gc), - gc, - )? + has_property(agent, rval.unbind(), property_key.unbind(), gc)? } }; - vm.result = Some(result.into()); + agent.vm.result = Some(result.into()); Ok(()) } pub(super) fn execute_has_private_element<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: NoGcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let Some(reference) = vm.reference.take() else { + let Some(reference) = agent.vm.reference.take() else { unreachable!() }; let (r_val, private_name) = reference.into_private_reference_data(); @@ -2168,7 +1947,7 @@ pub(super) fn execute_has_private_element<'gc>( // empty, return true. // 9. Return false. let result = private_element_find(agent, r_val, private_name).is_some(); - vm.result = Some(result.into()); + agent.vm.result = Some(result.into()); Ok(()) } else { // throw a TypeError exception. @@ -2177,55 +1956,50 @@ pub(super) fn execute_has_private_element<'gc>( } #[inline(always)] -pub(super) fn execute_is_strictly_equal(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) { - let lval = vm.stack.pop().unwrap().bind(gc); - let rval = vm.result.take().unwrap().bind(gc); +pub(super) fn execute_is_strictly_equal(agent: &mut Agent, gc: NoGcScope) { + let lval = agent.vm.pop_stack(gc); + let rval = agent.vm.take_result(gc); let result = is_strictly_equal(agent, lval, rval); - vm.result = Some(result.into()); + agent.vm.result = Some(result.into()); } pub(super) fn execute_is_loosely_equal<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let lval = vm.stack.pop().unwrap(); - let rval = vm.result.take().unwrap(); - let result = with_vm_gc( - agent, - vm, - |agent, gc| is_loosely_equal(agent, lval, rval, gc), - gc, - )?; - vm.result = Some(result.into()); + let lval = agent.vm.pop_stack(gc.nogc()); + let rval = agent.vm.take_result(gc.nogc()); + let result = is_loosely_equal(agent, lval.unbind(), rval.unbind(), gc)?; + agent.vm.result = Some(result.into()); Ok(()) } #[inline(always)] -pub(super) fn execute_is_constructor(agent: &Agent, vm: &mut Vm) { - let val = vm.result.take().unwrap(); +pub(super) fn execute_is_constructor(agent: &mut Agent, gc: NoGcScope) { + let val = agent.vm.take_result(gc); let result = if let Ok(val) = Function::try_from(val) { val.is_constructor(agent) } else { false }; - vm.result = Some(result.into()); + agent.vm.result = Some(result.into()); } #[inline(always)] -pub(super) fn execute_logical_not(agent: &Agent, vm: &mut Vm) { +pub(super) fn execute_logical_not(agent: &mut Agent, gc: NoGcScope) { + let value = agent.vm.take_result(gc); // 2. Let oldValue be ToBoolean(? GetValue(expr)). - let old_value = to_boolean(agent, vm.result.take().unwrap()); + let old_value = to_boolean(agent, value); // 3. If oldValue is true, return false. // 4. Return true. - vm.result = Some((!old_value).into()); + agent.vm.result = Some((!old_value).into()); } #[inline(always)] -pub(super) fn execute_initialize_referenced_binding(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) { - let v = vm.reference.take().unwrap(); - let w = vm.result.take().unwrap(); +pub(super) fn execute_initialize_referenced_binding(agent: &mut Agent, gc: NoGcScope) { + let v = agent.vm.reference.take().unwrap(); + let w = agent.vm.take_result(gc); // Note: https://tc39.es/ecma262/#sec-initializereferencedbinding // suggests this cannot call user code, hence NoGC. unwrap_try(try_initialize_referenced_binding(agent, v, w, gc)); @@ -2233,7 +2007,6 @@ pub(super) fn execute_initialize_referenced_binding(agent: &mut Agent, vm: &mut pub(super) fn execute_initialize_variable_environment<'gc>( agent: &mut Agent, - vm: &mut Vm, instr: Instr, gc: NoGcScope<'gc, '_>, ) { @@ -2249,8 +2022,8 @@ pub(super) fn execute_initialize_variable_environment<'gc>( // e. For each element n of varNames, do for _ in 0..num_variables { - let n = String::try_from(vm.stack.pop().unwrap()).unwrap(); - let initial_value = vm.stack.pop().unwrap(); + let n = String::try_from(agent.vm.pop_stack(gc)).unwrap(); + let initial_value = agent.vm.pop_stack(gc); // 2. Perform ! varEnv.CreateMutableBinding(n, false). var_env.create_mutable_binding(agent, n, false); // 5. Perform ! varEnv.InitializeBinding(n, initialValue). @@ -2277,14 +2050,8 @@ pub(super) fn execute_enter_declarative_environment(agent: &mut Agent, gc: NoGcS agent.set_current_lexical_environment(new_env.into()); } -pub(super) fn execute_enter_class_static_element_environment( - agent: &mut Agent, - vm: &mut Vm, - gc: NoGcScope, -) { - let class_constructor = Function::try_from(*vm.stack.last().unwrap()) - .unwrap() - .bind(gc); +pub(super) fn execute_enter_class_static_element_environment(agent: &mut Agent, gc: NoGcScope) { + let class_constructor = Function::try_from(agent.vm.get_last_stack_value(gc)).unwrap(); let local_env = new_class_static_element_environment(agent, class_constructor, gc); let local_env = Environment::Function(local_env); @@ -2323,27 +2090,21 @@ pub(super) fn execute_exit_private_environment(agent: &mut Agent, gc: NoGcScope) } #[inline(always)] -pub(super) fn execute_create_mutable_binding( - agent: &mut Agent, - executable: Scoped, - instr: Instr, - gc: NoGcScope, -) { +pub(super) fn execute_create_mutable_binding(agent: &mut Agent, instr: Instr, gc: NoGcScope) { let lex_env = agent.current_lexical_environment(gc); - let name = executable.fetch_identifier(agent, instr.get_first_index(), gc); + let name = agent + .current_executable(gc) + .fetch_identifier(agent, instr.get_first_index(), gc); unwrap_try(lex_env.try_create_mutable_binding(agent, name.unbind(), false, None, gc)); } #[inline(always)] -pub(super) fn execute_create_immutable_binding( - agent: &mut Agent, - executable: Scoped, - instr: Instr, - gc: NoGcScope, -) { +pub(super) fn execute_create_immutable_binding(agent: &mut Agent, instr: Instr, gc: NoGcScope) { let lex_env = agent.current_lexical_environment(gc); - let name = executable.fetch_identifier(agent, instr.get_first_index(), gc); + let name = agent + .current_executable(gc) + .fetch_identifier(agent, instr.get_first_index(), gc); lex_env .create_immutable_binding(agent, name, true, gc) .unwrap(); @@ -2351,54 +2112,39 @@ pub(super) fn execute_create_immutable_binding( pub(super) fn execute_throw_error<'gc>( agent: &mut Agent, - vm: &mut Vm, instr: Instr, gc: NoGcScope<'gc, '_>, ) -> JsResult<'gc, ()> { let exception_type_immediate = instr.get_first_arg(); - let message = String::try_from(vm.result.take().unwrap()).unwrap(); + let message = String::try_from(agent.vm.take_result(gc)).unwrap(); let exception_type = ExceptionType::try_from(exception_type_immediate).unwrap(); Err(agent.throw_exception_with_message(exception_type, message, gc)) } -pub(super) fn execute_push_exception_jump_target( - agent: &Agent, - vm: &mut Vm, - instr: Instr, - gc: NoGcScope, -) { - vm.exception_handler_stack - .push(ExceptionHandler::CatchBlock { - // Note: jump slots are passed to us in 32 bits, this - // conversion is lossless. - ip: instr.get_jump_slot() as u32, - lexical_environment: agent.current_lexical_environment(gc).unbind(), - }); +pub(super) fn execute_push_exception_jump_target(agent: &mut Agent, instr: Instr, gc: NoGcScope) { + agent.vm.exception_handler_stack.push(ExceptionHandler { + // Note: jump slots are passed to us in 32 bits, this + // conversion is lossless. + ip: instr.get_jump_slot() as u32, + lexical_environment: agent.current_lexical_environment(gc).unbind(), + }); } pub(super) fn execute_instanceof_operator<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let lval = vm.stack.pop().unwrap(); - let rval = vm.result.take().unwrap(); - let result = with_vm_gc( - agent, - vm, - |agent, gc| instanceof_operator(agent, lval, rval, gc), - gc, - )?; - vm.result = Some(result.into()); + let lval = agent.vm.pop_stack(gc.nogc()); + let rval = agent.vm.take_result(gc.nogc()); + let result = instanceof_operator(agent, lval.unbind(), rval.unbind(), gc)?; + agent.vm.result = Some(result.into()); Ok(()) } pub(super) fn execute_begin_simple_array_binding_pattern<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { @@ -2414,14 +2160,12 @@ pub(super) fn execute_begin_simple_array_binding_pattern<'gc>( // Var binding, var [] = a; None }; - execute_simple_array_binding(agent, vm, executable, env, gc).unbind()?; + execute_simple_array_binding(agent, env, gc).unbind()?; Ok(()) } pub(super) fn execute_begin_simple_object_binding_pattern<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { @@ -2437,10 +2181,11 @@ pub(super) fn execute_begin_simple_object_binding_pattern<'gc>( // Var binding, var {} = a; None }; - let object = to_object(agent, vm.result.take().unwrap(), gc.nogc()) + let result = agent.vm.take_result(gc.nogc()); + let object = to_object(agent, result, gc.nogc()) .unbind()? .bind(gc.nogc()); - execute_simple_object_binding(agent, vm, executable, object.unbind(), env, gc) + execute_simple_object_binding(agent, object.unbind(), env, gc) } #[inline(never)] @@ -2450,61 +2195,57 @@ pub(super) fn execute_binding_pattern() -> ! { pub(super) fn execute_string_concat<'gc>( agent: &mut Agent, - vm: &mut Vm, instr: Instr, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { let argument_count = instr.get_first_index(); - let first_arg_index = vm.stack.len() - argument_count; + let first_arg_index = agent.vm.stack.len() - argument_count; let mut length = 0; - let all_easy = vm.stack[first_arg_index..] + let all_easy = agent.vm.stack[first_arg_index..] .iter() .all(|ele| ele.is_primitive() && !ele.is_symbol()); let string = if all_easy { let gc = gc.nogc(); - let args = &mut vm.stack[first_arg_index..]; - for arg in args.iter_mut() { - let string: String<'_> = - to_string_primitive(agent, Primitive::try_from(*arg).unwrap(), gc).unwrap(); + let args = NonNull::from_mut(&mut agent.vm.stack[first_arg_index..]); + let len = args.len(); + let start = args.cast::(); + for i in 0..len { + // SAFETY: args are in start..end, and calling to_string_primitive + // cannot call into the VM and therefore the pointer range is + // untouched. + let arg = unsafe { start.add(i) }; + // SAFETY: reading inside the Agent while we have exclusive access. + let value = unsafe { arg.read() }.bind(gc); + let string: String<'_> = to_string_primitive(agent, value, gc).unwrap(); length += string.len_(agent); - // Note: We write String into each arg. - *arg = string.unbind().into(); + // SAFETY: Writing back out into arg while we have exclusive access. + unsafe { arg.write(string.unbind().into()) }; } - let args = &*args; - // SAFETY: String is a sub-enum of Value and we've written - // a String into each of the args. - let args = unsafe { std::mem::transmute::<&[Value<'_>], &[String<'_>]>(args) }; + // SAFETY: String is a sub-enum of Value and we've written a String into + // each of the args. + let args = unsafe { std::mem::transmute::]>, &[String<'_>]>(args) }; concat_string_from_slice(agent, args, length, gc) } else { - let mut args = vm + let mut args = agent + .vm .stack .split_off(first_arg_index) .iter_mut() .map(|v| v.scope(agent, gc.nogc())) .collect::>(); - with_vm_gc::>( - agent, - vm, - |agent, mut gc| { - for ele in args.iter_mut() { - let maybe_string = ele.get(agent).bind(gc.nogc()); - if maybe_string.is_string() { - continue; - } - let string = to_string(agent, maybe_string.unbind(), gc.reborrow()) - .unbind()? - .bind(gc.nogc()); - length += string.len_(agent); - let string: Value = string.into(); - // SAFETY: args are never shared - unsafe { ele.replace(agent, string.unbind()) }; - } - Ok(()) - }, - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); + for ele in args.iter_mut() { + let maybe_string = ele.get(agent).bind(gc.nogc()); + if maybe_string.is_string() { + continue; + } + let string = to_string(agent, maybe_string.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + length += string.len_(agent); + let string: Value = string.into(); + // SAFETY: args are never shared + unsafe { ele.replace(agent, string.unbind()) }; + } let gc = gc.nogc(); let args = args .into_iter() @@ -2512,14 +2253,15 @@ pub(super) fn execute_string_concat<'gc>( .collect::>(); concat_string_from_slice(agent, &args, length, gc) }; - vm.stack.truncate(first_arg_index); - vm.result = Some(string.unbind().into()); + agent.vm.stack.truncate(first_arg_index); + agent.vm.result = Some(string.unbind().into()); Ok(()) } -pub(super) fn execute_enumerate_object_properties(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) { - let object = to_object(agent, vm.result.take().unwrap(), gc).unwrap(); - vm.iterator_stack.push( +pub(super) fn execute_enumerate_object_properties(agent: &mut Agent, gc: NoGcScope) { + let result = agent.vm.take_result(gc); + let object = to_object(agent, result, gc).unwrap(); + agent.vm.iterator_stack.push( VmIteratorRecord::ObjectProperties(Box::new(ObjectPropertiesIteratorRecord::new(object))) .unbind(), ); @@ -2527,205 +2269,125 @@ pub(super) fn execute_enumerate_object_properties(agent: &mut Agent, vm: &mut Vm pub(super) fn execute_get_iterator_sync<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let expr_value = vm.result.take().unwrap(); - let result = with_vm_gc( - agent, - vm, - |agent, gc| VmIteratorRecord::from_value(agent, expr_value, gc), - gc, - )?; - vm.iterator_stack.push(result.unbind()); + let expr_value = agent.vm.take_result(gc.nogc()); + let result = VmIteratorRecord::from_value(agent, expr_value.unbind(), gc)?; + agent.vm.iterator_stack.push(result.unbind()); Ok(()) } pub(super) fn execute_get_iterator_async<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let expr_value = vm.result.take().unwrap(); - let result = with_vm_gc( - agent, - vm, - |agent, gc| VmIteratorRecord::async_from_value(agent, expr_value, gc), - gc, - )?; - vm.iterator_stack.push(result.unbind()); + let expr_value = agent.vm.take_result(gc.nogc()); + let result = VmIteratorRecord::async_from_value(agent, expr_value.unbind(), gc)?; + agent.vm.iterator_stack.push(result.unbind()); Ok(()) } pub(super) fn execute_iterator_step_value<'gc>( agent: &mut Agent, - vm: &mut Vm, instr: Instr, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let result = with_vm_gc( - agent, - vm, - |agent, gc| ActiveIterator::new(agent, gc.nogc()).step_value(agent, gc), - gc, - )?; - vm.result = result.unbind(); + let result = ActiveIterator::new(agent, gc.nogc()).step_value(agent, gc)?; + agent.vm.result = result.unbind(); if result.is_none() { // Iterator finished: jump to escape the iterator loop. let ip = instr.get_jump_slot(); if agent.options.print_internals { eprintln!("Iterator finished, jumping to {ip}"); } - vm.ip = ip; + agent.vm.ip = ip; } Ok(()) } pub(super) fn execute_iterator_step_value_or_undefined<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { + if cfg!(debug_assertions) { + // Clobber result eagerly. + agent.vm.result = None; + } let result = if let Some(r) = try_result_into_option_js( - vm.get_active_iterator_mut() + // SAFETY: try_step_value does not access the VM. + unsafe { NonNull::from_mut(agent.vm.get_active_iterator_mut()).as_mut() } .try_step_value(agent, gc.nogc()), ) { r.unbind().bind(gc.into_nogc()) } else { - with_vm_gc( - agent, - vm, - |agent, gc| ActiveIterator::new(agent, gc.nogc()).step_value(agent, gc), - gc, - ) + ActiveIterator::new(agent, gc.nogc()).step_value(agent, gc) }; if result.map_or(true, |r| r.is_none()) { // We have exhausted the iterator or it threw an error; // replace the top iterator with an empty slice iterator so // further instructions aren't observable. - *vm.get_active_iterator_mut() = VmIteratorRecord::EmptySliceIterator; + *agent.vm.get_active_iterator_mut() = VmIteratorRecord::EmptySliceIterator; } - vm.result = Some(result?.unwrap_or(Value::Undefined).unbind()); + agent.vm.result = Some(result?.unwrap_or(Value::Undefined).unbind()); Ok(()) } pub(super) fn execute_iterator_call_next_method<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let result = vm.result.take(); - vm.result = Some( - with_vm_gc( - agent, - vm, - |agent, gc| ActiveIterator::new(agent, gc.nogc()).call_next(agent, result, gc), - gc, - )? - .unbind(), + let result = agent.vm.take_any_result(gc.nogc()); + agent.vm.result = Some( + ActiveIterator::new(agent, gc.nogc()) + .call_next(agent, result.unbind(), gc)? + .unbind(), ); Ok(()) } pub(super) fn execute_iterator_complete<'gc>( agent: &mut Agent, - vm: &mut Vm, - instr: Instr, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { // 1. If innerResult is not an Object, throw a TypeError // exception. - let result = vm - .result - .expect("No iterator result object") - .bind(gc.nogc()); + let result = agent.vm.take_result(gc.nogc()); let Ok(result) = Object::try_from(result) else { return Err(throw_iterator_returned_non_object(agent, gc.into_nogc())); }; // 2. Let done be ? IteratorComplete(innerResult). - let done = { - let result = result.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| iterator_complete(agent, result, gc), - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()) - }; - // 3. If done is true, then - if done { - // SAFETY: Result was checked to be an Object already. - let result = unsafe { - Object::try_from(vm.result.unwrap_unchecked()) - .unwrap_unchecked() - .bind(gc.nogc()) - }; - // i. Return ? IteratorValue(innerResult). - let result = { - let result = result.unbind(); - with_vm_gc( - agent, - vm, - // SAFETY: not shared. - |agent, gc| iterator_value(agent, result, gc), - gc, - )? - }; - vm.result = Some(result.unbind()); - let ip = instr.get_jump_slot(); - if agent.options.print_internals { - eprintln!("IteratorComplete returned true, jumping to {ip}"); - } - vm.ip = ip; - } + agent.vm.result = Some( + iterator_complete(agent, result.unbind(), gc.reborrow()) + .unbind()? + .into(), + ); Ok(()) } pub(super) fn execute_iterator_value<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let result = vm - .result - .expect("No iterator result object") - .bind(gc.nogc()); + let result = agent.vm.take_result(gc.nogc()); // NOTE: We crash here because this check should've been done // already. let result = Object::try_from(result).expect("Iterator returned a non-object result"); // 1. Return ? IteratorValue(innerResult). - let result = { - let result = result.unbind(); - with_vm_gc( - agent, - vm, - // SAFETY: not shared. - |agent, gc| iterator_value(agent, result, gc), - gc, - )? - }; - vm.result = Some(result.unbind()); + let result = iterator_value(agent, result.unbind(), gc)?; + agent.vm.result = Some(result.unbind()); Ok(()) } pub(super) fn execute_iterator_throw<'gc>( agent: &mut Agent, - vm: &mut Vm, instr: Instr, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let result = vm.result.take().expect("IteratorThrow with no error"); - let result = with_vm_gc( - agent, - vm, - |agent, gc| ActiveIterator::new(agent, gc.nogc()).throw(agent, result, gc), - gc, - )?; + let result = agent.vm.take_result(gc.nogc()); + let result = ActiveIterator::new(agent, gc.nogc()).throw(agent, result.unbind(), gc)?; if let Some(result) = result { // Throw method was found and called successfully. - vm.result = Some(result.unbind()); + agent.vm.result = Some(result.unbind()); } else { // No throw method was found, we should jump to provided // instruction. @@ -2733,45 +2395,37 @@ pub(super) fn execute_iterator_throw<'gc>( if agent.options.print_internals { eprintln!("No iterator throw method found, jumping to {ip}"); } - vm.ip = ip; + agent.vm.ip = ip; } Ok(()) } pub(super) fn execute_iterator_return<'gc>( agent: &mut Agent, - vm: &mut Vm, instr: Instr, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let result = vm.result.expect("IteratorReturn with no received value"); - let value = with_vm_gc( - agent, - vm, - |agent, gc| ActiveIterator::new(agent, gc.nogc()).r#return(agent, Some(result), gc), - gc, - )?; - if let Some(value) = value { - // Return method was found and called successfully. - vm.result = Some(value.unbind()); - } else { - // No return method was found, we should jump to provided - // instruction. + let result = agent.vm.take_result(gc.nogc()); + agent.vm.result = ActiveIterator::new(agent, gc.nogc()) + .r#return(agent, Some(result.unbind()), gc)? + .unbind(); + if agent.vm.result.is_none() { + // No return method was found, we should jump to provided instruction. let ip = instr.get_jump_slot(); if agent.options.print_internals { eprintln!("No iterator return method found, jumping to {ip}"); } - vm.ip = ip; + agent.vm.ip = ip; } Ok(()) } pub(super) fn execute_iterator_rest_into_array<'gc>( agent: &mut Agent, - vm: &mut Vm, - gc: GcScope<'gc, '_>, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let capacity = vm + let capacity = agent + .vm .get_active_iterator() .remaining_length_estimate(agent) .unwrap_or(0); @@ -2779,63 +2433,63 @@ pub(super) fn execute_iterator_rest_into_array<'gc>( .unbind()? .scope(agent, gc.nogc()); - let result = with_vm_gc::>( - agent, - vm, - |agent, mut gc| { - let mut idx: u32 = 0; - while let Some(value) = ActiveIterator::new(agent, gc.nogc()) - .step_value(agent, gc.reborrow()) - .unbind()? - .bind(gc.nogc()) - { - let key = PropertyKey::Integer(idx.into()); - unwrap_try(try_create_data_property( - agent, - array.get(agent), - key, - value.unbind(), - None, - gc.nogc(), - )); - idx += 1; + let mut idx: u32 = 0; + + while let Some(value) = + match ActiveIterator::new(agent, gc.nogc()).step_value(agent, gc.reborrow()) { + Ok(value) => value, + Err(err) => { + // Iterator threw an error; replace it with an empty slice + // iterator so further instructions aren't observable. + *agent.vm.get_active_iterator_mut() = VmIteratorRecord::EmptySliceIterator; + return Err(err.unbind()); } - Ok(()) - }, - gc, - ); - // We have exhausted the iterator or it threw an error; replace - // the top iterator with an empty slice iterator so further - // instructions aren't observable. - *vm.get_active_iterator_mut() = VmIteratorRecord::EmptySliceIterator; - // Now we're ready to throw the possible error.; - result?; + } + { + let key = PropertyKey::Integer(idx.into()); + unwrap_try(try_create_data_property( + agent, + array.get(agent), + key, + value.unbind(), + None, + gc.nogc(), + )); + idx += 1; + } + + // We exhausted the iterator; replace it with an empty slice iterator so + // further instructions aren't observable. + *agent.vm.get_active_iterator_mut() = VmIteratorRecord::EmptySliceIterator; // Store the array as the result. // SAFETY: not shared. - vm.result = Some(unsafe { array.take(agent).into() }); + agent.vm.result = Some(unsafe { array.take(agent).into() }); Ok(()) } pub(super) fn execute_iterator_close<'gc>( agent: &mut Agent, - vm: &mut Vm, mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - if !vm - .get_active_iterator() + // SAFETY: requires_return_call cannot call into JavaScript and therefore + // does not access agent.vm. + if !unsafe { NonNull::from_ref(agent.vm.get_active_iterator()).as_ref() } .requires_return_call(agent, gc.nogc()) { return Ok(()); } - let result = with_vm_gc( - agent, - vm, - |agent, gc| ActiveIterator::new(agent, gc.nogc()).r#return(agent, None, gc), - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); + let load_result_to_stack = agent.vm.result.is_some(); + if load_result_to_stack { + agent.vm.execute_load(); + } + let result = ActiveIterator::new(agent, gc.nogc()) + .r#return(agent, None, gc.reborrow()) + .unbind()? + .bind(gc.nogc()); + if load_result_to_stack { + agent.vm.execute_store(); + } if let Some(result) = result { // We did get innerResult from return method call: we have // to check that it is an object. @@ -2846,133 +2500,104 @@ pub(super) fn execute_iterator_close<'gc>( pub(super) fn execute_async_iterator_close<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, ) -> JsResult<'gc, bool> { - if !vm - .get_active_iterator() + // SAFETY: requires_return_call cannot call into JavaScript and therefore + // does not access agent.vm. + if !unsafe { NonNull::from_ref(agent.vm.get_active_iterator()).as_ref() } .requires_return_call(agent, gc.nogc()) { // Skip over VerifyIsObject, message, and Store. - vm.ip += 4; + agent.vm.ip += 4; return Ok(false); } - let result = with_vm_gc( - agent, - vm, - |agent, gc| ActiveIterator::new(agent, gc.nogc()).r#return(agent, None, gc), - gc, - )?; + let load_result_to_stack = agent.vm.result.is_some(); + if load_result_to_stack { + agent.vm.execute_load(); + } + let result = ActiveIterator::new(agent, gc.nogc()).r#return(agent, None, gc)?; + if load_result_to_stack { + agent.vm.execute_store(); + } if let Some(result) = result { // AsyncIteratorClose // Iterator return method did return a value: we should // put it into the result slot, place our original // result into the stack, and perform an Await. - let result = vm.result.replace(result.unbind()); - vm.stack.push(result.unwrap_or(Value::Undefined)); + let result = agent.vm.result.replace(result.unbind()); + agent.vm.stack.push(result.unwrap_or(Value::Undefined)); return Ok(true); } else { // Skip over VerifyIsObject, message, and Store. - vm.ip += 4; + agent.vm.ip += 4; } Ok(false) } -pub(super) fn execute_iterator_close_with_error(agent: &mut Agent, vm: &mut Vm, gc: GcScope) { +pub(super) fn execute_iterator_close_with_error(agent: &mut Agent, gc: GcScope) { // If our current active iterator requires a return call, // perform said call. - if vm - .get_active_iterator() + // SAFETY: requires_return_call cannot call into JavaScript and therefore + // does not access agent.vm. + if unsafe { NonNull::from_ref(agent.vm.get_active_iterator()).as_ref() } .requires_return_call(agent, gc.nogc()) { // We don't care if the return call throws an error or not, // nor if its returned value is an object or not: the // original throw completion will be rethrown before that. - let _ = with_vm_gc( - agent, - vm, - |agent, gc| ActiveIterator::new(agent, gc.nogc()).r#return(agent, None, gc), - gc, - ); + // let load_result_to_stack = agent.vm.result.is_some(); + debug_assert!(agent.vm.result.is_some()); + agent.vm.execute_load(); + let _ = ActiveIterator::new(agent, gc.nogc()).r#return(agent, None, gc); + agent.vm.execute_store(); } // Continue to the next instruction which should either be a // throw or an IteratorPop followed by a throw. } -pub(super) fn execute_async_iterator_close_with_error( - agent: &mut Agent, - vm: &mut Vm, - gc: GcScope, -) -> bool { - if vm - .get_active_iterator() +pub(super) fn execute_async_iterator_close_with_error(agent: &mut Agent, gc: GcScope) { + debug_assert!(agent.vm.result.is_none()); + // SAFETY: requires_return_call cannot call into JavaScript and therefore + // does not access agent.vm. + let jump_over_await = if unsafe { NonNull::from_ref(agent.vm.get_active_iterator()).as_ref() } .requires_return_call(agent, gc.nogc()) { - let inner_result_value = with_vm_gc( - agent, - vm, - |agent, gc| ActiveIterator::new(agent, gc.nogc()).r#return(agent, None, gc), - gc, - ); + let inner_result_value = ActiveIterator::new(agent, gc.nogc()).r#return(agent, None, gc); if let Ok(Some(value)) = inner_result_value { // ### 7.4.13 AsyncIteratorClose // 4.d. If innerResult is a normal completion, set // innerResult to Completion(Await(innerResult.[[Value]])). // 5. If completion is a throw completion, return ? completion. - // We need to await the value and ignore any errors it - // might throw, then rethrow the error. First, we need - // to load the error to the stack for later throwing. - vm.stack.push(vm.result.take().unwrap()); - // Then we can put our value as the result. - vm.result = Some(value.unbind()); - // Before we await we need to make sure that any error - // throw by the await gets ignored. This also ignores - // the next instruction coming after this, which would - // pop the exception handler stack: the ignore is - // necessary because handling the error pops the - // exception handler stack as well. Without the ignore - // we'd pop twice. - vm.exception_handler_stack - .push(ExceptionHandler::IgnoreErrorAndNextInstruction); - // Now we're ready to await: if the await succeeds then - // we'll continue execution which will pop the above - // exception handler, store the error as the result and - // then rethrow it. If the await throws an error, it - // will trigger our exception handler which will skip - // the exception handler pop instruction but otherwise - // continue as above. As a result, our error is always - // rethrown. - return true; + agent.vm.stack.push(value.unbind()); + false + } else { + true } - } - // If we did not find a return method or get a value to await - // then we'll skip the PopExceptionJumpTarget and Store - // instructions, and go straight to rethrow handling. Note, we - // do not manually rethrow as there may be more steps between - // this and the final Throw instruction. - vm.ip += 2; - false + } else { + true + }; + agent.vm.result = Some(jump_over_await.into()); } pub(super) fn execute_create_unmapped_arguments_object<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: NoGcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let Some(VmIteratorRecord::SliceIterator(slice)) = vm.iterator_stack.last() else { + let Some(VmIteratorRecord::SliceIterator(slice)) = agent.vm.iterator_stack.last() else { unreachable!() }; + let slice = slice.clone(); match create_unmapped_arguments_object(agent, slice, gc) { Ok(o) => { - vm.result = Some(o.unbind().into()); + agent.vm.result = Some(o.unbind().into()); Ok(()) } Err(err) => Err(agent.throw_allocation_exception(err, gc)), } } -pub(super) fn execute_get_new_target(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) { +pub(super) fn execute_get_new_target(agent: &mut Agent, gc: NoGcScope) { // 1. Let envRec be GetThisEnvironment(). let env_rec = get_this_environment(agent, gc); // 2. Assert: envRec has a [[NewTarget]] field. @@ -2980,7 +2605,7 @@ pub(super) fn execute_get_new_target(agent: &mut Agent, vm: &mut Vm, gc: NoGcSco unreachable!() }; // 3. Return envRec.[[NewTarget]]. - vm.result = Some( + agent.vm.result = Some( env_rec .get_new_target(agent) .map_or(Value::Undefined, |v| v.into()) @@ -2988,26 +2613,17 @@ pub(super) fn execute_get_new_target(agent: &mut Agent, vm: &mut Vm, gc: NoGcSco ); } -pub(super) fn execute_import_call(agent: &mut Agent, vm: &mut Vm, gc: GcScope) { - let specifier = vm.stack.pop().unwrap().bind(gc.nogc()); - let options = vm.result.take().bind(gc.nogc()); - vm.result = { - let specifier = specifier.unbind(); - let options = options.unbind(); - Some( - with_vm_gc( - agent, - vm, - |agent, gc| evaluate_import_call(agent, specifier, options, gc), - gc, - ) +pub(super) fn execute_import_call(agent: &mut Agent, gc: GcScope) { + let specifier = agent.vm.pop_stack(gc.nogc()); + let options = agent.vm.take_any_result(gc.nogc()); + agent.vm.result = Some( + evaluate_import_call(agent, specifier.unbind(), options.unbind(), gc) .unbind() .into(), - ) - }; + ); } -pub(super) fn execute_import_meta(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) { +pub(super) fn execute_import_meta(agent: &mut Agent, gc: NoGcScope) { // 1. Let module be GetActiveScriptOrModule(). let module = agent.get_active_script_or_module(gc); // 2. Assert: module is a Source Text Module Record. @@ -3051,22 +2667,18 @@ pub(super) fn execute_import_meta(agent: &mut Agent, vm: &mut Vm, gc: NoGcScope) import_meta } }; - vm.result = Some(import_meta.unbind().into()); + agent.vm.result = Some(import_meta.unbind().into()); } /// ### [13.5.1.2 Runtime Semantics: Evaluation](https://tc39.es/ecma262/#sec-delete-operator-runtime-semantics-evaluation) -pub(super) fn execute_delete<'a>( - agent: &mut Agent, - vm: &mut Vm, - mut gc: GcScope<'a, '_>, -) -> JsResult<'a, ()> { - let reference = vm.reference.take().unwrap().bind(gc.nogc()); +pub(super) fn execute_delete<'a>(agent: &mut Agent, mut gc: GcScope<'a, '_>) -> JsResult<'a, ()> { + let reference = agent.vm.reference.take().unwrap().bind(gc.nogc()); // 3. If IsUnresolvableReference(ref) is true, then if is_unresolvable_reference(&reference) { // a. Assert: ref.[[Strict]] is false. debug_assert!(!reference.strict()); // b. Return true. - vm.result = Some(true.into()); + agent.vm.result = Some(true.into()); } else if is_property_reference(&reference) { // 4. If IsPropertyReference(ref) is true, then @@ -3092,16 +2704,11 @@ pub(super) fn execute_delete<'a>( { referenced_name } else { - let referenced_name = referenced_name.unbind(); let scoped_base_obj = base_obj.scope(agent, gc.nogc()); - let referenced_name = with_vm_gc( - agent, - vm, - |agent, gc| to_property_key_complex(agent, referenced_name, gc), - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()); + let referenced_name = + to_property_key_complex(agent, referenced_name.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()); // SAFETY: not shared. base_obj = unsafe { scoped_base_obj.take(agent) }.bind(gc.nogc()); referenced_name @@ -3115,16 +2722,11 @@ pub(super) fn execute_delete<'a>( { delete_status } else { - let base_obj = base_obj.unbind(); - let referenced_name = referenced_name.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| base_obj.internal_delete(agent, referenced_name, gc), - gc.reborrow(), - ) - .unbind()? - .bind(gc.nogc()) + base_obj + .unbind() + .internal_delete(agent, referenced_name.unbind(), gc.reborrow()) + .unbind()? + .bind(gc.nogc()) }; // f. If deleteStatus is false and ref.[[Strict]] is true, throw a TypeError exception. if !delete_status && strict { @@ -3135,7 +2737,7 @@ pub(super) fn execute_delete<'a>( )); } // g. Return deleteStatus. - vm.result = Some(delete_status.into()); + agent.vm.result = Some(delete_status.into()); } else { // 5. Else, @@ -3150,16 +2752,10 @@ pub(super) fn execute_delete<'a>( { result } else { - let referenced_name = referenced_name.unbind(); - let base = base.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| base.delete_binding(agent, referenced_name, gc), - gc, - )? + base.unbind() + .delete_binding(agent, referenced_name.unbind(), gc)? }; - vm.result = Some(result.into()); + agent.vm.result = Some(result.into()); } Ok(()) @@ -3183,14 +2779,15 @@ pub(super) fn execute_delete<'a>( pub(super) fn execute_verify_is_object<'gc>( agent: &mut Agent, - vm: &mut Vm, - executable: Scoped, instr: Instr, gc: NoGcScope<'gc, '_>, ) -> JsResult<'gc, ()> { - let result = vm.result.unwrap(); + let result = agent.vm.result.unwrap(); if !result.is_object() { - let message = executable.fetch_identifier(agent, instr.get_first_index(), gc); + let message = + agent + .current_executable(gc) + .fetch_identifier(agent, instr.get_first_index(), gc); return Err(agent.throw_exception_with_message(ExceptionType::TypeError, message, gc)); } Ok(()) @@ -3200,7 +2797,6 @@ pub(super) fn execute_verify_is_object<'gc>( #[cold] fn handle_set_value_break<'gc>( agent: &mut Agent, - vm: &mut Vm, reference: &Reference, result: TryResult, mut value: Value, @@ -3214,38 +2810,21 @@ fn handle_set_value_break<'gc>( let p = reference.referenced_name_property_key(); let receiver = reference.this_value(agent); let strict = reference.strict(); - with_vm_gc( - agent, - vm, - |agent, gc| call_proxy_set(agent, proxy, p, value, receiver, strict, gc), - gc, - ) + call_proxy_set(agent, proxy, p, value, receiver, strict, gc) } ControlFlow::Continue(SetResult::Set(setter)) => { let receiver = reference.this_value(agent); - with_vm_gc( + call_function( agent, - vm, - |agent, gc| { - call_function( - agent, - setter, - receiver, - Some(ArgumentsList::from_mut_value(&mut value)), - gc, - ) - }, + setter, + receiver, + Some(ArgumentsList::from_mut_value(&mut value)), gc, - ) - .map(|_| ()) + )?; + Ok(()) } ControlFlow::Break(TryError::Err(err)) => Err(err.unbind().bind(gc.into_nogc())), - ControlFlow::Break(TryError::GcError) => with_vm_gc( - agent, - vm, - |agent, gc| put_value(agent, reference, value, gc), - gc, - ), + ControlFlow::Break(TryError::GcError) => put_value(agent, reference, value, gc), } } @@ -3253,10 +2832,10 @@ fn handle_set_value_break<'gc>( #[cold] fn mutate_reference_property_key<'gc>( agent: &mut Agent, - vm: &mut Vm, gc: GcScope<'gc, '_>, -) -> JsResult<'gc, Reference<'gc>> { - let reference = vm.reference.as_ref().unwrap(); +) -> JsResult<'gc, ()> { + // SAFETY: Caller has checked this above. + let reference = unsafe { agent.vm.reference_stack.last().unwrap_unchecked() }; // Expression reference; we need to convert to PropertyKey // first. let referenced_name = reference.referenced_name_value().bind(gc.nogc()); @@ -3276,44 +2855,34 @@ fn mutate_reference_property_key<'gc>( )); } let referenced_name = referenced_name.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| to_property_key_complex(agent, referenced_name, gc), - gc, - )? + to_property_key_complex(agent, referenced_name, gc)? }; - let reference = vm.reference.as_mut().unwrap(); + let reference = agent.vm.reference_stack.last_mut().unwrap(); reference.set_referenced_name_to_property_key(referenced_name); - Ok(reference.clone()) + agent.vm.reference = Some(reference.clone()); + Ok(()) } #[inline(never)] #[cold] fn handle_get_value_break<'a>( agent: &mut Agent, - vm: &mut Vm, reference: &Reference, result: ControlFlow, gc: GcScope<'a, '_>, ) -> JsResult<'a, Value<'a>> { let result = result.unbind(); - with_vm_gc( - agent, - vm, - |agent, gc| match result { - ControlFlow::Continue(TryGetValueContinue::Get { getter, receiver }) => { - call_function(agent, getter, receiver, None, gc) - } - ControlFlow::Continue(TryGetValueContinue::Proxy { - proxy, - receiver, - property_key, - }) => proxy.internal_get(agent, property_key, receiver, gc), - ControlFlow::Break(TryError::Err(err)) => Err(err.bind(gc.into_nogc())), - ControlFlow::Break(TryError::GcError) => get_value(agent, reference, gc), - _ => unreachable!(), - }, - gc, - ) + match result { + ControlFlow::Continue(TryGetValueContinue::Get { getter, receiver }) => { + call_function(agent, getter, receiver, None, gc) + } + ControlFlow::Continue(TryGetValueContinue::Proxy { + proxy, + receiver, + property_key, + }) => proxy.internal_get(agent, property_key, receiver, gc), + ControlFlow::Break(TryError::Err(err)) => Err(err.bind(gc.into_nogc())), + ControlFlow::Break(TryError::GcError) => get_value(agent, reference, gc), + _ => unreachable!(), + } } From c408f0f5344e6e8894aa235b573e00bd2aa2031f Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sat, 24 Oct 2026 11:39:00 +0300 Subject: [PATCH 2/7] typo --- nova_vm/src/engine/bytecode/vm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index f769aa442..8f7cc43c8 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -379,7 +379,7 @@ impl Vm { fn inner_execute<'gc>(agent: &mut Agent, mut gc: GcScope<'gc, '_>) -> ExecutionResult<'gc> { // SAFETY: The instructions buffer is owned by the current Executable, // which is currently rooted by the execution context stack. The buffer - // is not accessible as exclusive excecpt for dropping the Executable's + // is not accessible as exclusive except for dropping the Executable's // heap data, and isn't dropped while the Executable is rooted. Thus, // the instructions slice is guaranteed to be valid while we perform the // execution inner loop. From 3d06cc4ba0f7533e01d224a2fef02bebb80ab9b6 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sat, 24 Oct 2026 11:44:23 +0300 Subject: [PATCH 3/7] clippy --- nova_vm/src/ecmascript/builtins/global_object.rs | 3 +-- nova_vm/src/ecmascript/execution/agent.rs | 8 ++------ nova_vm/src/engine/bytecode/instructions.rs | 2 +- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/global_object.rs b/nova_vm/src/ecmascript/builtins/global_object.rs index 2742e537e..0d994e2b5 100644 --- a/nova_vm/src/ecmascript/builtins/global_object.rs +++ b/nova_vm/src/ecmascript/builtins/global_object.rs @@ -438,8 +438,7 @@ pub(crate) fn perform_eval<'gc>( // 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 = agent.execute(None, gc).into_js_result(); - result + agent.execute(None, gc).into_js_result() } Err(err) => Err(err.unbind().bind(gc.into_nogc())), } diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index 2993d0f67..8e66e77b5 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -1291,13 +1291,9 @@ impl Agent { .execution_context_stack .last_mut() .and_then(|ctx| ctx.ecmascript_code.as_mut()) - .and_then(|e| { + .filter(|e| { // Check that we're not replacing an already-set executable. - if e.executable.is_none() { - Some(e) - } else { - None - } + e.executable.is_none() }) else { panic_corrupted_agent() diff --git a/nova_vm/src/engine/bytecode/instructions.rs b/nova_vm/src/engine/bytecode/instructions.rs index e7b9e5f30..f373a09f8 100644 --- a/nova_vm/src/engine/bytecode/instructions.rs +++ b/nova_vm/src/engine/bytecode/instructions.rs @@ -967,7 +967,7 @@ impl Instr { Instruction::BindingPatternBindNamed => { format!( "{{ {}: {} }}", - debug_print_constant(agent, exe.clone(), arg1 as usize, gc), + debug_print_constant(agent, exe, arg1 as usize, gc), debug_print_identifier(agent, exe, arg0 as usize, gc) ) } From 4fedb21f7b9351e3f8a15dee40b9bbca288f0fe4 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sat, 24 Oct 2026 11:54:31 +0300 Subject: [PATCH 4/7] more clippy --- nova_vm/src/engine/bytecode/bytecode_compiler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova_vm/src/engine/bytecode/bytecode_compiler.rs b/nova_vm/src/engine/bytecode/bytecode_compiler.rs index 048eeea71..abafe91c2 100644 --- a/nova_vm/src/engine/bytecode/bytecode_compiler.rs +++ b/nova_vm/src/engine/bytecode/bytecode_compiler.rs @@ -2047,7 +2047,7 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> for ast::ChainE ast::ChainElement::TSNonNullExpression(expr) => { let result = expr.expression.compile(ctx).and_then(|r| match r { PlaceOrValue::Value(r) => Ok(r), - PlaceOrValue::Place(place) => handle_place_result(ctx, place), + PlaceOrValue::Place(place) => handle_place_result(ctx, Ok(place)), }); ctx.is_call_optional_chain_this = false; result From 5fcc1393be630e60af7839c02f29ea2ffacecb75 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sat, 24 Oct 2026 11:54:44 +0300 Subject: [PATCH 5/7] Remove unnecessary dependency --- tests/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 42ec577d2..8f7ce91d9 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -15,4 +15,3 @@ serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.154" serde_yml = "0.0.13" wait-timeout = "0.2.1" -yaml-rust2 = "0.13.0" From 429a2a2c9214ac07b7745f5ca2989fbc63cd9bfc Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sat, 24 Oct 2026 12:08:25 +0300 Subject: [PATCH 6/7] Fix some errors --- .../engine/bytecode/bytecode_compiler/assignment.rs | 8 ++++++-- .../bytecode/bytecode_compiler/for_in_of_statement.rs | 1 + nova_vm/src/engine/bytecode/vm.rs | 10 ++++++---- nova_vm/src/engine/bytecode/vm/execute_instructions.rs | 1 + 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/nova_vm/src/engine/bytecode/bytecode_compiler/assignment.rs b/nova_vm/src/engine/bytecode/bytecode_compiler/assignment.rs index 856299180..af8774334 100644 --- a/nova_vm/src/engine/bytecode/bytecode_compiler/assignment.rs +++ b/nova_vm/src/engine/bytecode/bytecode_compiler/assignment.rs @@ -534,10 +534,14 @@ impl<'a, 's, 'gc, 'scope> CompileEvaluation<'a, 's, 'gc, 'scope> break 'args; } }; - ctx.add_instruction(Instruction::PushReference); + if lref.has_reference() { + ctx.add_instruction(Instruction::PushReference); + } ctx.add_instruction(Instruction::IteratorRestIntoArray); // a. Return ? PutValue(lRef, A). - ctx.add_instruction(Instruction::PopReference); + if lref.has_reference() { + ctx.add_instruction(Instruction::PopReference); + } if let Err(e) = lref.put_value(ctx, ValueOutput::Value) { result = Err(e); break 'args; diff --git a/nova_vm/src/engine/bytecode/bytecode_compiler/for_in_of_statement.rs b/nova_vm/src/engine/bytecode/bytecode_compiler/for_in_of_statement.rs index a4e4e071c..e076e48a9 100644 --- a/nova_vm/src/engine/bytecode/bytecode_compiler/for_in_of_statement.rs +++ b/nova_vm/src/engine/bytecode/bytecode_compiler/for_in_of_statement.rs @@ -388,6 +388,7 @@ fn for_in_of_body_evaluation<'s, 'gc>( let jump_over_await = ctx.add_instruction_with_jump_slot(Instruction::JumpIfTrue); // We have to Await the result and ignore any rejections. let try_catch = ctx.enter_try_catch_block(); + ctx.add_instruction(Instruction::Store); ctx.add_instruction(Instruction::Await); let jump_to_error = try_catch.exit(ctx); diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index 8f7cc43c8..afce7c8c6 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -879,6 +879,7 @@ impl Vm { /// /// Panics if the iterator stack is empty. pub(super) fn pop_iterator<'gc>(&mut self, gc: NoGcScope<'gc, '_>) -> VmIteratorRecord<'gc> { + #[cfg(debug_assertions)] debug_assert!(self.iterator_stack.len() > self.iterator_stack_base as usize); self.iterator_stack .pop() @@ -892,6 +893,7 @@ impl Vm { /// /// Panics if the iterator stack is empty. pub(super) fn get_active_iterator(&self) -> &VmIteratorRecord<'static> { + #[cfg(debug_assertions)] debug_assert!(self.iterator_stack.len() > self.iterator_stack_base as usize); self.iterator_stack.last().expect("Iterator stack is empty") } @@ -902,6 +904,7 @@ impl Vm { /// /// Panics if the iterator stack is empty. pub(super) fn get_active_iterator_mut(&mut self) -> &mut VmIteratorRecord<'static> { + #[cfg(debug_assertions)] debug_assert!(self.iterator_stack.len() > self.iterator_stack_base as usize); self.iterator_stack .last_mut() @@ -998,6 +1001,7 @@ impl Vm { } fn pop_reference_stack(&mut self) -> Reference<'static> { + #[cfg(debug_assertions)] debug_assert!(self.reference_stack.len() > self.reference_stack_base as usize); self.reference_stack .pop() @@ -1542,8 +1546,7 @@ impl HeapMarkAndSweep for Vm { #[cfg(debug_assertions)] iterator_stack_base: _, exception_handler_stack, - #[cfg(debug_assertions)] - exception_handler_stack_base: _, + exception_handler_stack_base: _, result, reference, } = self; @@ -1567,8 +1570,7 @@ impl HeapMarkAndSweep for Vm { #[cfg(debug_assertions)] iterator_stack_base: _, exception_handler_stack, - #[cfg(debug_assertions)] - exception_handler_stack_base: _, + exception_handler_stack_base: _, result, reference, } = self; diff --git a/nova_vm/src/engine/bytecode/vm/execute_instructions.rs b/nova_vm/src/engine/bytecode/vm/execute_instructions.rs index fa642703a..a05fdb4c2 100644 --- a/nova_vm/src/engine/bytecode/vm/execute_instructions.rs +++ b/nova_vm/src/engine/bytecode/vm/execute_instructions.rs @@ -737,6 +737,7 @@ pub(super) fn execute_evaluate_reference<'gc>( } else { agent.vm.reference = Some(reference.clone()); } + #[cfg(debug_assertions)] debug_assert_eq!( agent.vm.reference_stack.len(), stack_len, From ff2027382d47fce20364ab691e43c42bfb4f2bc1 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sat, 24 Oct 2026 12:18:56 +0300 Subject: [PATCH 7/7] Fix --- .../src/engine/bytecode/vm/binding_methods.rs | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/nova_vm/src/engine/bytecode/vm/binding_methods.rs b/nova_vm/src/engine/bytecode/vm/binding_methods.rs index 3e4e35e45..1e99bd40d 100644 --- a/nova_vm/src/engine/bytecode/vm/binding_methods.rs +++ b/nova_vm/src/engine/bytecode/vm/binding_methods.rs @@ -64,25 +64,31 @@ pub(super) fn execute_simple_array_binding<'a>( .unwrap() .scope(agent, gc.nogc()); let mut idx = 0u32; - while let Some(result) = iterator - .step_value(agent, gc.reborrow()) - .unbind()? - .bind(gc.nogc()) - { - unwrap_try(try_create_data_property_or_throw( - agent, - rest.get(agent), - PropertyKey::from(idx), - result.unbind(), - None, - gc.nogc(), - )); - idx += 1; + loop { + match iterator + .step_value(agent, gc.reborrow()) + .unbind() + .bind(gc.nogc()) + { + Ok(Some(result)) => { + unwrap_try(try_create_data_property_or_throw( + agent, + rest.get(agent), + PropertyKey::from(idx), + result.unbind(), + None, + gc.nogc(), + )); + idx += 1; + } + Ok(None) => { + iterator_is_done = true; + // SAFETY: rest is not shared + break Ok(unsafe { rest.take(agent).into() }); + } + Err(err) => break Err(err), + } } - iterator_is_done = true; - // SAFETY: rest is not shared - let rest: Value = unsafe { rest.take(agent).into() }; - JsResult::Ok(rest) } } Instruction::FinishBindingPattern => break,