From cb4e49a0ced6bb0662befd0c60b9a72a914bf711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= Date: Tue, 21 Mar 2023 16:52:45 +0000 Subject: [PATCH] Align iterator loops to the spec (#2686) ~Depends on #2683.~ Merged. This Pull Request fixes #2658. It changes the following: - Makes `for .. of` loop execution more spec compliant. - Rewrites iterator related opcodes to be able to use it on all for .. of/in loops. - Adds some utility op codes. --- .../iterable/async_from_sync_iterator.rs | 2 +- boa_engine/src/builtins/iterable/mod.rs | 26 ++- .../src/builtins/object/for_in_iterator.rs | 7 +- .../declaration/declaration_pattern.rs | 169 +++++++++++------- boa_engine/src/bytecompiler/expression/mod.rs | 18 +- boa_engine/src/bytecompiler/mod.rs | 26 +-- boa_engine/src/bytecompiler/statement/loop.rs | 28 +-- boa_engine/src/bytecompiler/utils.rs | 59 ++++++ boa_engine/src/tests/mod.rs | 1 + boa_engine/src/tests/promise.rs | 45 +++++ boa_engine/src/value/mod.rs | 30 +--- boa_engine/src/vm/code_block.rs | 21 ++- boa_engine/src/vm/flowgraph/mod.rs | 56 +++--- boa_engine/src/vm/mod.rs | 2 +- .../src/vm/opcode/control_flow/throw.rs | 26 ++- boa_engine/src/vm/opcode/generator/mod.rs | 22 +-- boa_engine/src/vm/opcode/get/property.rs | 29 ++- .../src/vm/opcode/iteration/for_await.rs | 71 -------- boa_engine/src/vm/opcode/iteration/for_in.rs | 73 +------- .../vm/opcode/iteration/{init.rs => get.rs} | 22 ++- .../src/vm/opcode/iteration/iterator.rs | 131 +++++++++----- boa_engine/src/vm/opcode/iteration/mod.rs | 6 +- boa_engine/src/vm/opcode/jump/mod.rs | 23 ++- boa_engine/src/vm/opcode/mod.rs | 107 ++++++----- boa_engine/src/vm/opcode/push/array.rs | 7 +- boa_engine/src/vm/opcode/value/mod.rs | 18 ++ 26 files changed, 577 insertions(+), 448 deletions(-) create mode 100644 boa_engine/src/bytecompiler/utils.rs create mode 100644 boa_engine/src/tests/promise.rs delete mode 100644 boa_engine/src/vm/opcode/iteration/for_await.rs rename boa_engine/src/vm/opcode/iteration/{init.rs => get.rs} (61%) diff --git a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs index abb7e6492e..5b1f67cee9 100644 --- a/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs +++ b/boa_engine/src/builtins/iterable/async_from_sync_iterator.rs @@ -114,7 +114,7 @@ impl AsyncFromSyncIterator { // a. Let result be Completion(IteratorNext(syncIteratorRecord, value)). // 6. Else, // a. Let result be Completion(IteratorNext(syncIteratorRecord)). - let result = sync_iterator_record.next(args.get(0).cloned(), context); + let result = sync_iterator_record.next(args.get(0), context); // 7. IfAbruptRejectPromise(result, promiseCapability). if_abrupt_reject_promise!(result, promise_capability, context); diff --git a/boa_engine/src/builtins/iterable/mod.rs b/boa_engine/src/builtins/iterable/mod.rs index b361fe4d81..c7e23bc4a5 100644 --- a/boa_engine/src/builtins/iterable/mod.rs +++ b/boa_engine/src/builtins/iterable/mod.rs @@ -392,26 +392,20 @@ impl IteratorRecord { /// [spec]: https://tc39.es/ecma262/#sec-iteratornext pub(crate) fn next( &self, - value: Option, + value: Option<&JsValue>, context: &mut Context<'_>, ) -> JsResult { let _timer = Profiler::global().start_event("IteratorRecord::next", "iterator"); - // Note: We check if iteratorRecord.[[NextMethod]] is callable here. - // This check would happen in `Call` according to the spec, but we do not implement call for `JsValue`. - let next_method = self.next_method.as_callable().ok_or_else(|| { - JsNativeError::typ().with_message("iterable next method not a function") - })?; - - let result = if let Some(value) = value { - // 2. Else, - // a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « value »). - next_method.call(&self.iterator.clone().into(), &[value], context)? - } else { - // 1. If value is not present, then - // a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]). - next_method.call(&self.iterator.clone().into(), &[], context)? - }; + // 1. If value is not present, then + // a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]). + // 2. Else, + // a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « value »). + let result = self.next_method.call( + &self.iterator.clone().into(), + value.map_or(&[], std::slice::from_ref), + context, + )?; // 3. If Type(result) is not Object, throw a TypeError exception. // 4. Return result. diff --git a/boa_engine/src/builtins/object/for_in_iterator.rs b/boa_engine/src/builtins/object/for_in_iterator.rs index 1038d8d11f..91ff390807 100644 --- a/boa_engine/src/builtins/object/for_in_iterator.rs +++ b/boa_engine/src/builtins/object/for_in_iterator.rs @@ -69,16 +69,15 @@ impl ForInIterator { /// - [ECMA reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-createforiniterator - pub(crate) fn create_for_in_iterator(object: JsValue, context: &Context<'_>) -> JsValue { - let for_in_iterator = JsObject::from_proto_and_data( + pub(crate) fn create_for_in_iterator(object: JsValue, context: &Context<'_>) -> JsObject { + JsObject::from_proto_and_data( context .intrinsics() .objects() .iterator_prototypes() .for_in(), ObjectData::for_in_iterator(Self::new(object)), - ); - for_in_iterator.into() + ) } /// %ForInIteratorPrototype%.next( ) diff --git a/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs b/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs index eaa8615988..ea655f8709 100644 --- a/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs +++ b/boa_engine/src/bytecompiler/declaration/declaration_pattern.rs @@ -175,80 +175,111 @@ impl ByteCompiler<'_, '_> { } Pattern::Array(pattern) => { self.emit_opcode(Opcode::ValueNotNullOrUndefined); - self.emit_opcode(Opcode::InitIterator); - - for binding in pattern.bindings().iter() { - use ArrayPatternElement::{ - Elision, Pattern, PatternRest, PropertyAccess, PropertyAccessRest, - SingleName, SingleNameRest, - }; - - match binding { - // ArrayBindingPattern : [ Elision ] - Elision => { - self.emit_opcode(Opcode::IteratorNext); - self.emit_opcode(Opcode::Pop); + self.emit_opcode(Opcode::GetIterator); + match pattern.bindings().split_last() { + None => self.emit_opcode(Opcode::PushFalse), + Some((last, rest)) => { + for element in rest { + self.compile_array_pattern_element(element, def, false); } - // SingleNameBinding : BindingIdentifier Initializer[opt] - SingleName { - ident, - default_init, - } => { - self.emit_opcode(Opcode::IteratorNext); - if let Some(init) = default_init { - let skip = - self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); - self.compile_expr(init, true); - self.patch_jump(skip); - } - self.emit_binding(def, *ident); - } - PropertyAccess { access } => { - self.emit_opcode(Opcode::IteratorNext); - self.access_set( - Access::Property { access }, - false, - ByteCompiler::access_set_top_of_stack_expr_fn, - ); - } - // BindingElement : BindingPattern Initializer[opt] - Pattern { - pattern, - default_init, - } => { - self.emit_opcode(Opcode::IteratorNext); + self.compile_array_pattern_element(last, def, true); + } + } + self.iterator_close(false); + } + } + } - if let Some(init) = default_init { - let skip = - self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); - self.compile_expr(init, true); - self.patch_jump(skip); - } + fn compile_array_pattern_element( + &mut self, + element: &ArrayPatternElement, + def: BindingOpcode, + with_done: bool, + ) { + use ArrayPatternElement::{ + Elision, Pattern, PatternRest, PropertyAccess, PropertyAccessRest, SingleName, + SingleNameRest, + }; - self.compile_declaration_pattern(pattern, def); - } - // BindingRestElement : ... BindingIdentifier - SingleNameRest { ident } => { - self.emit_opcode(Opcode::IteratorToArray); - self.emit_binding(def, *ident); - } - PropertyAccessRest { access } => { - self.emit_opcode(Opcode::IteratorToArray); - self.access_set( - Access::Property { access }, - false, - ByteCompiler::access_set_top_of_stack_expr_fn, - ); - } - // BindingRestElement : ... BindingPattern - PatternRest { pattern } => { - self.emit_opcode(Opcode::IteratorToArray); - self.compile_declaration_pattern(pattern, def); - } - } + let unwrapping = if with_done { + Opcode::IteratorUnwrapNext + } else { + Opcode::IteratorUnwrapValue + }; + match element { + // ArrayBindingPattern : [ Elision ] + Elision => { + self.emit_opcode(Opcode::IteratorNext); + if with_done { + self.emit_opcode(Opcode::IteratorUnwrapNext); + } + self.emit_opcode(Opcode::Pop); + } + // SingleNameBinding : BindingIdentifier Initializer[opt] + SingleName { + ident, + default_init, + } => { + self.emit_opcode(Opcode::IteratorNext); + self.emit_opcode(unwrapping); + if let Some(init) = default_init { + let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); + self.compile_expr(init, true); + self.patch_jump(skip); + } + self.emit_binding(def, *ident); + } + PropertyAccess { access } => { + self.emit_opcode(Opcode::IteratorNext); + self.emit_opcode(unwrapping); + self.access_set( + Access::Property { access }, + false, + ByteCompiler::access_set_top_of_stack_expr_fn, + ); + } + // BindingElement : BindingPattern Initializer[opt] + Pattern { + pattern, + default_init, + } => { + self.emit_opcode(Opcode::IteratorNext); + self.emit_opcode(unwrapping); + + if let Some(init) = default_init { + let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); + self.compile_expr(init, true); + self.patch_jump(skip); } - self.emit_opcode(Opcode::IteratorClose); + self.compile_declaration_pattern(pattern, def); + } + // BindingRestElement : ... BindingIdentifier + SingleNameRest { ident } => { + self.emit_opcode(Opcode::IteratorToArray); + self.emit_binding(def, *ident); + if with_done { + self.emit_opcode(Opcode::PushTrue); + } + } + PropertyAccessRest { access } => { + self.emit_opcode(Opcode::IteratorToArray); + self.access_set( + Access::Property { access }, + false, + ByteCompiler::access_set_top_of_stack_expr_fn, + ); + if with_done { + self.emit_opcode(Opcode::PushTrue); + } + } + // BindingRestElement : ... BindingPattern + PatternRest { pattern } => { + self.emit_opcode(Opcode::IteratorToArray); + self.compile_declaration_pattern(pattern, def); + if with_done { + self.emit_opcode(Opcode::PushTrue); + } } } } diff --git a/boa_engine/src/bytecompiler/expression/mod.rs b/boa_engine/src/bytecompiler/expression/mod.rs index d00752a763..777fc0e61f 100644 --- a/boa_engine/src/bytecompiler/expression/mod.rs +++ b/boa_engine/src/bytecompiler/expression/mod.rs @@ -104,7 +104,7 @@ impl ByteCompiler<'_, '_> { if let Some(element) = element { self.compile_expr(element, true); if let Expression::Spread(_) = element { - self.emit_opcode(Opcode::InitIterator); + self.emit_opcode(Opcode::GetIterator); self.emit_opcode(Opcode::PushIteratorToArray); } else { self.emit_opcode(Opcode::PushValueToArray); @@ -163,9 +163,9 @@ impl ByteCompiler<'_, '_> { if r#yield.delegate() { if self.in_async_generator { - self.emit_opcode(Opcode::InitIteratorAsync); + self.emit_opcode(Opcode::GetAsyncIterator); } else { - self.emit_opcode(Opcode::InitIterator); + self.emit_opcode(Opcode::GetIterator); } self.emit_opcode(Opcode::PushUndefined); let start_address = self.next_opcode_location(); @@ -174,15 +174,15 @@ impl ByteCompiler<'_, '_> { self.patch_jump(start); } else if self.in_async_generator { self.emit_opcode(Opcode::Await); - self.emit_opcode(Opcode::AsyncGeneratorNext); - let jump_return = self.emit_opcode_with_operand(Opcode::JumpIfFalse); - let jump = self.emit_opcode_with_operand(Opcode::JumpIfFalse); + let (skip_yield, skip_yield_await) = + self.emit_opcode_with_two_operands(Opcode::AsyncGeneratorNext); + self.emit_opcode(Opcode::PushUndefined); self.emit_opcode(Opcode::Yield); self.emit_opcode(Opcode::GeneratorNext); - self.patch_jump(jump); + self.patch_jump(skip_yield); self.emit_opcode(Opcode::Await); self.emit_opcode(Opcode::GeneratorNext); - self.patch_jump(jump_return); + self.patch_jump(skip_yield_await); } else { self.emit_opcode(Opcode::Yield); self.emit_opcode(Opcode::GeneratorNext); @@ -265,7 +265,7 @@ impl ByteCompiler<'_, '_> { for arg in super_call.arguments() { self.compile_expr(arg, true); if let Expression::Spread(_) = arg { - self.emit_opcode(Opcode::InitIterator); + self.emit_opcode(Opcode::GetIterator); self.emit_opcode(Opcode::PushIteratorToArray); } else { self.emit_opcode(Opcode::PushValueToArray); diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index 7b8ae96289..4be1a96a7a 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -7,6 +7,7 @@ mod function; mod jump_control; mod module; mod statement; +mod utils; use crate::{ builtins::function::ThisMode, @@ -508,23 +509,23 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { } fn jump(&mut self) -> Label { - let index = self.next_opcode_location(); - self.emit(Opcode::Jump, &[Self::DUMMY_ADDRESS]); - Label { index } + self.emit_opcode_with_operand(Opcode::Jump) + } + + fn jump_if_true(&mut self) -> Label { + self.emit_opcode_with_operand(Opcode::JumpIfTrue) } fn jump_if_false(&mut self) -> Label { - let index = self.next_opcode_location(); - self.emit(Opcode::JumpIfFalse, &[Self::DUMMY_ADDRESS]); + self.emit_opcode_with_operand(Opcode::JumpIfFalse) + } - Label { index } + fn jump_if_not_undefined(&mut self) -> Label { + self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined) } fn jump_if_null_or_undefined(&mut self) -> Label { - let index = self.next_opcode_location(); - self.emit(Opcode::JumpIfNullOrUndefined, &[Self::DUMMY_ADDRESS]); - - Label { index } + self.emit_opcode_with_operand(Opcode::JumpIfNullOrUndefined) } /// Emit an opcode with a dummy operand. @@ -900,7 +901,6 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { self.patch_jump(label); } - self.emit_opcode(Opcode::Pop); self.emit_opcode(Opcode::PushUndefined); self.patch_jump(skip_undef); @@ -957,7 +957,7 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { for arg in args { self.compile_expr(arg, true); if let Expression::Spread(_) = arg { - self.emit_opcode(Opcode::InitIterator); + self.emit_opcode(Opcode::GetIterator); self.emit_opcode(Opcode::PushIteratorToArray); } else { self.emit_opcode(Opcode::PushValueToArray); @@ -1274,7 +1274,7 @@ impl<'b, 'host> ByteCompiler<'b, 'host> { for arg in call.args() { self.compile_expr(arg, true); if let Expression::Spread(_) = arg { - self.emit_opcode(Opcode::InitIterator); + self.emit_opcode(Opcode::GetIterator); self.emit_opcode(Opcode::PushIteratorToArray); } else { self.emit_opcode(Opcode::PushValueToArray); diff --git a/boa_engine/src/bytecompiler/statement/loop.rs b/boa_engine/src/bytecompiler/statement/loop.rs index 0c5d873bf4..5b2ae10128 100644 --- a/boa_engine/src/bytecompiler/statement/loop.rs +++ b/boa_engine/src/bytecompiler/statement/loop.rs @@ -109,7 +109,9 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(Opcode::PopEnvironment); } - let early_exit = self.emit_opcode_with_operand(Opcode::ForInLoopInitIterator); + let early_exit = self.jump_if_null_or_undefined(); + self.emit_opcode(Opcode::CreateForInIterator); + self.emit_opcode(Opcode::PushFalse); let (loop_start, exit_label) = self.emit_opcode_with_two_operands(Opcode::LoopStart); let start_address = self.next_opcode_location(); @@ -121,7 +123,9 @@ impl ByteCompiler<'_, '_> { self.context.push_compile_time_environment(false); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); - let exit = self.emit_opcode_with_operand(Opcode::ForInLoopNext); + self.emit_opcode(Opcode::Pop); // pop the `done` value. + self.emit_opcode(Opcode::IteratorNext); + let exit = self.emit_opcode_with_operand(Opcode::IteratorUnwrapNextOrJump); match for_in_loop.initializer() { IterableLoopInitializer::Identifier(ident) => { @@ -197,7 +201,7 @@ impl ByteCompiler<'_, '_> { self.patch_jump(cont_exit_label); self.pop_loop_control_info(); self.emit_opcode(Opcode::LoopEnd); - self.emit_opcode(Opcode::IteratorClose); + self.iterator_close(false); self.patch_jump(early_exit); } @@ -228,10 +232,11 @@ impl ByteCompiler<'_, '_> { } if for_of_loop.r#await() { - self.emit_opcode(Opcode::InitIteratorAsync); + self.emit_opcode(Opcode::GetAsyncIterator); } else { - self.emit_opcode(Opcode::InitIterator); + self.emit_opcode(Opcode::GetIterator); } + self.emit_opcode(Opcode::PushFalse); let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart); let start_address = self.next_opcode_location(); @@ -244,14 +249,13 @@ impl ByteCompiler<'_, '_> { self.context.push_compile_time_environment(false); let push_env = self.emit_opcode_with_two_operands(Opcode::PushDeclarativeEnvironment); - let exit = if for_of_loop.r#await() { - self.emit_opcode(Opcode::ForAwaitOfLoopIterate); + self.emit_opcode(Opcode::Pop); // pop the `done` value. + self.emit_opcode(Opcode::IteratorNext); + if for_of_loop.r#await() { self.emit_opcode(Opcode::Await); self.emit_opcode(Opcode::GeneratorNext); - self.emit_opcode_with_operand(Opcode::ForAwaitOfLoopNext) - } else { - self.emit_opcode_with_operand(Opcode::ForInLoopNext) - }; + } + let exit = self.emit_opcode_with_operand(Opcode::IteratorUnwrapNextOrJump); match for_of_loop.initializer() { IterableLoopInitializer::Identifier(ref ident) => { @@ -326,7 +330,7 @@ impl ByteCompiler<'_, '_> { self.patch_jump(cont_exit_label); self.pop_loop_control_info(); self.emit_opcode(Opcode::LoopEnd); - self.emit_opcode(Opcode::IteratorClose); + self.iterator_close(for_of_loop.r#await()); } pub(crate) fn compile_while_loop( diff --git a/boa_engine/src/bytecompiler/utils.rs b/boa_engine/src/bytecompiler/utils.rs new file mode 100644 index 0000000000..0a093836f2 --- /dev/null +++ b/boa_engine/src/bytecompiler/utils.rs @@ -0,0 +1,59 @@ +use boa_interner::Sym; + +use crate::{js_string, vm::Opcode}; + +use super::{ByteCompiler, Literal}; + +impl ByteCompiler<'_, '_> { + /// Closes an iterator + /// + /// This is equivalent to the [`IteratorClose`][iter] and [`AsyncIteratorClose`][async] + /// operations. + /// + /// Stack: + /// - iterator, `next_method`, done **=>** \ + /// + /// [iter]: https://tc39.es/ecma262/#sec-iteratorclose + /// [async]: https://tc39.es/ecma262/#sec-asynciteratorclose + pub(super) fn iterator_close(&mut self, async_: bool) { + // Need to remove `next_method` to manipulate the iterator + self.emit_opcode(Opcode::Swap); + self.emit_opcode(Opcode::Pop); + + let skip_iter_pop = self.jump_if_false(); + + // `iterator` is done, we can skip calling `return`. + // `iterator` is still in the stack, so pop it to cleanup. + self.emit_opcode(Opcode::Pop); + let skip_return = self.jump(); + + // iterator didn't finish iterating. + self.patch_jump(skip_iter_pop); + let index = self.get_or_insert_name(Sym::RETURN.into()); + self.emit(Opcode::GetMethod, &[index]); + let skip_jump = self.jump_if_not_undefined(); + + // `iterator` didn't have a `return` method, so we can early exit. + // `iterator` is still in the stack, so pop it to cleanup. + self.emit_opcode(Opcode::Pop); + let early_exit = self.jump(); + + self.patch_jump(skip_jump); + self.emit(Opcode::Call, &[0]); + if async_ { + self.emit_opcode(Opcode::Await); + self.emit_opcode(Opcode::GeneratorNext); + } + self.emit_opcode(Opcode::IsObject); + let skip_throw = self.jump_if_true(); + + let error_msg = self.get_or_insert_literal(Literal::String(js_string!( + "inner result was not an object" + ))); + self.emit(Opcode::ThrowNewTypeError, &[error_msg]); + + self.patch_jump(skip_return); + self.patch_jump(skip_throw); + self.patch_jump(early_exit); + } +} diff --git a/boa_engine/src/tests/mod.rs b/boa_engine/src/tests/mod.rs index 8320c30c94..56d5c71cb3 100644 --- a/boa_engine/src/tests/mod.rs +++ b/boa_engine/src/tests/mod.rs @@ -3,6 +3,7 @@ use indoc::indoc; mod control_flow; mod function; mod operators; +mod promise; mod spread; use crate::{builtins::error::ErrorKind, run_test_actions, JsValue, TestAction}; diff --git a/boa_engine/src/tests/promise.rs b/boa_engine/src/tests/promise.rs new file mode 100644 index 0000000000..5277449fda --- /dev/null +++ b/boa_engine/src/tests/promise.rs @@ -0,0 +1,45 @@ +use indoc::indoc; + +use crate::{job::SimpleJobQueue, run_test_actions_with, Context, TestAction}; + +#[test] +#[allow(clippy::redundant_closure_for_method_calls)] +fn issue_2658() { + let queue = &SimpleJobQueue::new(); + let context = &mut Context::builder().job_queue(queue).build().unwrap(); + run_test_actions_with( + [ + TestAction::run(indoc! { + r#" + let result1; + let result2; + async function* agf(a) { + for await (m of a) { + yield m; + } + } + iterTwo = { + [Symbol.asyncIterator]() { + return this; + }, + next() { + return { + value: 5, + done: false, + }; + } + }; + const genTwo = agf(iterTwo); + genTwo.next().then(v => { result1 = v; }); + genTwo.next().then(v => { result2 = v; }); + "# + }), + TestAction::inspect_context(|ctx| ctx.run_jobs()), + TestAction::assert("!result1.done"), + TestAction::assert_eq("result1.value", 5), + TestAction::assert("!result2.done"), + TestAction::assert_eq("result2.value", 5), + ], + context, + ); +} diff --git a/boa_engine/src/value/mod.rs b/boa_engine/src/value/mod.rs index 6cf5578bcc..2da1cdfee6 100644 --- a/boa_engine/src/value/mod.rs +++ b/boa_engine/src/value/mod.rs @@ -321,34 +321,8 @@ impl JsValue { } } - /// Resolve the property in the object. - /// - /// A copy of the Property is returned. - pub(crate) fn get_property(&self, key: Key) -> Option - where - Key: Into, - { - let key = key.into(); - let _timer = Profiler::global().start_event("Value::get_property", "value"); - match self { - Self::Object(ref object) => { - // TODO: had to skip `__get_own_properties__` since we don't have context here - let property = object.borrow().properties().get(&key); - if property.is_some() { - return property; - } - - object - .prototype() - .as_ref() - .map_or(Self::Null, |obj| obj.clone().into()) - .get_property(key) - } - _ => None, - } - } - - /// The abstract operation `ToPrimitive` takes an input argument and an optional argumen`PreferredType`pe. + /// The abstract operation `ToPrimitive` takes an input argument and an optional argument + /// `PreferredType`. /// /// pub fn to_primitive( diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 82c18dc26b..0f41bc91b8 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -237,7 +237,9 @@ impl CodeBlock { ryu_js::Buffer::new().format(operand).to_string() } Opcode::PushLiteral + | Opcode::ThrowNewTypeError | Opcode::Jump + | Opcode::JumpIfTrue | Opcode::JumpIfFalse | Opcode::JumpIfNotUndefined | Opcode::JumpIfNullOrUndefined @@ -253,9 +255,7 @@ impl CodeBlock { | Opcode::Call | Opcode::New | Opcode::SuperCall - | Opcode::ForInLoopInitIterator - | Opcode::ForInLoopNext - | Opcode::ForAwaitOfLoopNext + | Opcode::IteratorUnwrapNextOrJump | Opcode::ConcatToString | Opcode::GeneratorNextDelegate => { let result = self.read::(*pc).to_string(); @@ -270,7 +270,8 @@ impl CodeBlock { | Opcode::Continue | Opcode::LoopContinue | Opcode::LoopStart - | Opcode::TryStart => { + | Opcode::TryStart + | Opcode::AsyncGeneratorNext => { let operand1 = self.read::(*pc); *pc += size_of::(); let operand2 = self.read::(*pc); @@ -310,6 +311,7 @@ impl CodeBlock { ) } Opcode::GetPropertyByName + | Opcode::GetMethod | Opcode::SetPropertyByName | Opcode::DefineOwnPropertyByName | Opcode::DefineClassStaticMethodByName @@ -422,10 +424,12 @@ impl CodeBlock { | Opcode::PopEnvironment | Opcode::LoopEnd | Opcode::LabelledEnd - | Opcode::InitIterator - | Opcode::InitIteratorAsync + | Opcode::CreateForInIterator + | Opcode::GetIterator + | Opcode::GetAsyncIterator | Opcode::IteratorNext - | Opcode::IteratorClose + | Opcode::IteratorUnwrapNext + | Opcode::IteratorUnwrapValue | Opcode::IteratorToArray | Opcode::RequireObjectCoercible | Opcode::ValueNotNullOrUndefined @@ -439,7 +443,6 @@ impl CodeBlock { | Opcode::PopOnReturnSub | Opcode::Yield | Opcode::GeneratorNext - | Opcode::AsyncGeneratorNext | Opcode::PushClassField | Opcode::SuperCallDerived | Opcode::Await @@ -448,9 +451,9 @@ impl CodeBlock { | Opcode::CallSpread | Opcode::NewSpread | Opcode::SuperCallSpread - | Opcode::ForAwaitOfLoopIterate | Opcode::SetPrototype | Opcode::PushObjectEnvironment + | Opcode::IsObject | Opcode::Nop => String::new(), } } diff --git a/boa_engine/src/vm/flowgraph/mod.rs b/boa_engine/src/vm/flowgraph/mod.rs index d4528d5719..8bfe8b66d1 100644 --- a/boa_engine/src/vm/flowgraph/mod.rs +++ b/boa_engine/src/vm/flowgraph/mod.rs @@ -108,7 +108,9 @@ impl CodeBlock { EdgeStyle::Line, ); } + Opcode::JumpIfFalse + | Opcode::JumpIfTrue | Opcode::JumpIfNotUndefined | Opcode::JumpIfNullOrUndefined => { let operand = self.read::(pc); @@ -217,22 +219,7 @@ impl CodeBlock { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, address, None, Color::None, EdgeStyle::Line); } - Opcode::ForInLoopInitIterator => { - let address = self.read::(pc) as usize; - pc += size_of::(); - graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); - graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); - graph.add_edge( - previous_pc, - address, - Some("NULL OR UNDEFINED".into()), - Color::None, - EdgeStyle::Line, - ); - } - Opcode::ForInLoopNext - | Opcode::ForAwaitOfLoopNext - | Opcode::GeneratorNextDelegate => { + Opcode::IteratorUnwrapNextOrJump | Opcode::GeneratorNextDelegate => { let address = self.read::(pc) as usize; pc += size_of::(); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); @@ -255,6 +242,29 @@ impl CodeBlock { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } + Opcode::AsyncGeneratorNext => { + let skip_yield = self.read::(pc); + pc += size_of::(); + let skip_yield_await = self.read::(pc); + pc += size_of::(); + graph.add_node(previous_pc, NodeShape::None, opcode_str.into(), Color::None); + + graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); + graph.add_edge( + previous_pc, + skip_yield as usize, + Some("return value pending".into()), + Color::None, + EdgeStyle::Line, + ); + graph.add_edge( + previous_pc, + skip_yield_await as usize, + Some("yield value pending".into()), + Color::None, + EdgeStyle::Line, + ); + } Opcode::TryStart => { let next_address = self.read::(pc); pc += size_of::(); @@ -371,6 +381,7 @@ impl CodeBlock { graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } Opcode::GetPropertyByName + | Opcode::GetMethod | Opcode::SetPropertyByName | Opcode::DefineOwnPropertyByName | Opcode::DefineClassStaticMethodByName @@ -402,7 +413,7 @@ impl CodeBlock { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } - Opcode::Throw => { + Opcode::Throw | Opcode::ThrowNewTypeError => { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); if let Some((_try_pc, next, _finally)) = try_entries.last() { graph.add_edge( @@ -487,10 +498,12 @@ impl CodeBlock { | Opcode::Super | Opcode::LoopEnd | Opcode::LabelledEnd - | Opcode::InitIterator - | Opcode::InitIteratorAsync + | Opcode::CreateForInIterator + | Opcode::GetIterator + | Opcode::GetAsyncIterator | Opcode::IteratorNext - | Opcode::IteratorClose + | Opcode::IteratorUnwrapNext + | Opcode::IteratorUnwrapValue | Opcode::IteratorToArray | Opcode::RequireObjectCoercible | Opcode::ValueNotNullOrUndefined @@ -504,7 +517,6 @@ impl CodeBlock { | Opcode::PopOnReturnSub | Opcode::Yield | Opcode::GeneratorNext - | Opcode::AsyncGeneratorNext | Opcode::PushClassField | Opcode::SuperCallDerived | Opcode::Await @@ -513,8 +525,8 @@ impl CodeBlock { | Opcode::CallSpread | Opcode::NewSpread | Opcode::SuperCallSpread - | Opcode::ForAwaitOfLoopIterate | Opcode::SetPrototype + | Opcode::IsObject | Opcode::Nop | Opcode::PushObjectEnvironment => { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index a34a05cf65..790a6687b7 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -304,7 +304,7 @@ impl Context<'_> { return CompletionRecord::Normal(result); } EarlyReturnType::Yield => { - let result = self.vm.stack.pop().unwrap_or(JsValue::Undefined); + let result = self.vm.pop(); self.vm.frame_mut().early_return = None; return CompletionRecord::Return(result); } diff --git a/boa_engine/src/vm/opcode/control_flow/throw.rs b/boa_engine/src/vm/opcode/control_flow/throw.rs index 50a0693070..05ec410b7c 100644 --- a/boa_engine/src/vm/opcode/control_flow/throw.rs +++ b/boa_engine/src/vm/opcode/control_flow/throw.rs @@ -1,6 +1,6 @@ use crate::{ vm::{call_frame::AbruptCompletionRecord, opcode::Operation, CompletionType}, - Context, JsError, JsResult, + Context, JsError, JsNativeError, JsResult, }; /// `Throw` implements the Opcode Operation for `Opcode::Throw` @@ -131,3 +131,27 @@ impl Operation for Throw { Ok(CompletionType::Throw) } } + +/// `ThrowNewTypeError` implements the Opcode Operation for `Opcode::ThrowNewTypeError` +/// +/// Operation: +/// - Throws a `TypeError` exception. +#[derive(Debug, Clone, Copy)] +pub(crate) struct ThrowNewTypeError; + +impl Operation for ThrowNewTypeError { + const NAME: &'static str = "ThrowNewTypeError"; + const INSTRUCTION: &'static str = "INST - ThrowNewTypeError"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let index = context.vm.read::(); + let msg = context.vm.frame().code_block.literals[index as usize] + .as_string() + .expect("throw message must be a string") + .clone(); + let msg = msg + .to_std_string() + .expect("throw message must be an ASCII string"); + Err(JsNativeError::typ().with_message(msg).into()) + } +} diff --git a/boa_engine/src/vm/opcode/generator/mod.rs b/boa_engine/src/vm/opcode/generator/mod.rs index 0dc9c5dfd3..ec3d664b1f 100644 --- a/boa_engine/src/vm/opcode/generator/mod.rs +++ b/boa_engine/src/vm/opcode/generator/mod.rs @@ -68,6 +68,9 @@ impl Operation for AsyncGeneratorNext { const INSTRUCTION: &'static str = "INST - AsyncGeneratorNext"; fn execute(context: &mut Context<'_>) -> JsResult { + let skip_yield = context.vm.read::(); + let skip_yield_await = context.vm.read::(); + if context.vm.frame().generator_resume_kind == GeneratorResumeKind::Throw { return Err(JsError::from_opaque(context.vm.pop())); } @@ -104,17 +107,13 @@ impl Operation for AsyncGeneratorNext { Err(e) => e.clone().to_opaque(context), }; context.vm.push(value); - context.vm.push(true); + context.vm.frame_mut().pc = skip_yield as usize; } else { context.vm.push(completion.clone()?); - context.vm.push(false); + context.vm.frame_mut().pc = skip_yield_await as usize; } - - context.vm.push(false); } else { gen.state = AsyncGeneratorState::SuspendedYield; - context.vm.push(true); - context.vm.push(true); } Ok(CompletionType::Normal) } @@ -134,11 +133,6 @@ impl Operation for GeneratorNextDelegate { fn execute(context: &mut Context<'_>) -> JsResult { let done_address = context.vm.read::(); let received = context.vm.pop(); - let done = context - .vm - .pop() - .as_boolean() - .expect("iterator [[Done]] was not a boolean"); let next_method = context.vm.pop(); let iterator = context.vm.pop(); let iterator = iterator.as_object().expect("iterator was not an object"); @@ -149,6 +143,7 @@ impl Operation for GeneratorNextDelegate { let result = result.as_object().ok_or_else(|| { JsNativeError::typ().with_message("generator next method returned non-object") })?; + // TODO: This is wrong for async generators, since we need to await the result first. let done = result.get(utf16!("done"), context)?.to_boolean(); if done { context.vm.frame_mut().pc = done_address as usize; @@ -159,7 +154,6 @@ impl Operation for GeneratorNextDelegate { let value = result.get(utf16!("value"), context)?; context.vm.push(iterator.clone()); context.vm.push(next_method.clone()); - context.vm.push(done); context.vm.push(value); context.vm.frame_mut().early_return = Some(EarlyReturnType::Yield); Ok(CompletionType::Return) @@ -182,13 +176,12 @@ impl Operation for GeneratorNextDelegate { let value = result_object.get(utf16!("value"), context)?; context.vm.push(iterator.clone()); context.vm.push(next_method.clone()); - context.vm.push(done); context.vm.push(value); context.vm.frame_mut().early_return = Some(EarlyReturnType::Yield); return Ok(CompletionType::Return); } context.vm.frame_mut().pc = done_address as usize; - let iterator_record = IteratorRecord::new(iterator.clone(), next_method, done); + let iterator_record = IteratorRecord::new(iterator.clone(), next_method, false); iterator_record.close(Ok(JsValue::Undefined), context)?; Err(JsNativeError::typ() @@ -213,7 +206,6 @@ impl Operation for GeneratorNextDelegate { let value = result_object.get(utf16!("value"), context)?; context.vm.push(iterator.clone()); context.vm.push(next_method.clone()); - context.vm.push(done); context.vm.push(value); context.vm.frame_mut().early_return = Some(EarlyReturnType::Yield); return Ok(CompletionType::Return); diff --git a/boa_engine/src/vm/opcode/get/property.rs b/boa_engine/src/vm/opcode/get/property.rs index f1e2eda1fb..b5de8900fb 100644 --- a/boa_engine/src/vm/opcode/get/property.rs +++ b/boa_engine/src/vm/opcode/get/property.rs @@ -1,7 +1,8 @@ use crate::{ + js_string, property::PropertyKey, vm::{opcode::Operation, CompletionType}, - Context, JsResult, + Context, JsResult, JsValue, }; /// `GetPropertyByName` implements the Opcode Operation for `Opcode::GetPropertyByName` @@ -62,6 +63,32 @@ impl Operation for GetPropertyByValue { } } +/// `GetMethod` implements the Opcode Operation for `Opcode::GetMethod` +/// +/// Operation: +/// - Get a property method or undefined if the property is null or undefined. +#[derive(Debug, Clone, Copy)] +pub(crate) struct GetMethod; + +impl Operation for GetMethod { + const NAME: &'static str = "GetMethod"; + const INSTRUCTION: &'static str = "INST - GetMethod"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let index = context.vm.read::(); + let name = context.vm.frame().code_block.names[index as usize]; + let key = js_string!(context.interner().resolve_expect(name.sym()).utf16()); + let value = context.vm.pop(); + + let method = value.get_method(key, context)?; + context.vm.push(value); + context + .vm + .push(method.map(JsValue::from).unwrap_or_default()); + Ok(CompletionType::Normal) + } +} + /// `GetPropertyByValuePush` implements the Opcode Operation for `Opcode::GetPropertyByValuePush` /// /// Operation: diff --git a/boa_engine/src/vm/opcode/iteration/for_await.rs b/boa_engine/src/vm/opcode/iteration/for_await.rs deleted file mode 100644 index 083e0315c7..0000000000 --- a/boa_engine/src/vm/opcode/iteration/for_await.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::{ - builtins::iterable::IteratorResult, - error::JsNativeError, - vm::{opcode::Operation, CompletionType}, - Context, JsResult, -}; - -/// `ForAwaitOfLoopIterate` implements the Opcode Operation for `Opcode::ForAwaitOfLoopIterator` -/// -/// Operation: -/// - Move to the next value in a for await..of loop. -#[derive(Debug, Clone, Copy)] -pub(crate) struct ForAwaitOfLoopIterate; - -impl Operation for ForAwaitOfLoopIterate { - const NAME: &'static str = "ForAwaitOfLoopIterate"; - const INSTRUCTION: &'static str = "INST - ForAwaitOfLoopIterate"; - - fn execute(context: &mut Context<'_>) -> JsResult { - let _done = context - .vm - .pop() - .as_boolean() - .expect("iterator [[Done]] was not a boolean"); - let next_method = context.vm.pop(); - let next_method_object = next_method.as_callable().ok_or_else(|| { - JsNativeError::typ().with_message("iterable next method not a function") - })?; - let iterator = context.vm.pop(); - let next_result = next_method_object.call(&iterator, &[], context)?; - context.vm.push(iterator); - context.vm.push(next_method); - context.vm.push(next_result); - Ok(CompletionType::Normal) - } -} - -/// `ForAwaitOfLoopNext` implements the Opcode Operation for `Opcode::ForAwaitOfLoopNext` -/// -/// Operation: -/// - Get the value from a for await..of loop next result. -#[derive(Debug, Clone, Copy)] -pub(crate) struct ForAwaitOfLoopNext; - -impl Operation for ForAwaitOfLoopNext { - const NAME: &'static str = "ForAwaitOfLoopNext"; - const INSTRUCTION: &'static str = "INST - ForAwaitOfLoopNext"; - - fn execute(context: &mut Context<'_>) -> JsResult { - let address = context.vm.read::(); - - let next_result = context.vm.pop(); - let next_result = next_result - .as_object() - .cloned() - .map(IteratorResult::new) - .ok_or_else(|| JsNativeError::typ().with_message("next value should be an object"))?; - - if next_result.complete(context)? { - context.vm.frame_mut().pc = address as usize; - context.vm.frame_mut().dec_frame_env_stack(); - context.realm.environments.pop(); - context.vm.push(true); - } else { - context.vm.push(false); - let value = next_result.value(context)?; - context.vm.push(value); - } - Ok(CompletionType::Normal) - } -} diff --git a/boa_engine/src/vm/opcode/iteration/for_in.rs b/boa_engine/src/vm/opcode/iteration/for_in.rs index 9bc91a01ee..a781a98395 100644 --- a/boa_engine/src/vm/opcode/iteration/for_in.rs +++ b/boa_engine/src/vm/opcode/iteration/for_in.rs @@ -1,86 +1,31 @@ use crate::{ - builtins::{iterable::IteratorRecord, object::for_in_iterator::ForInIterator}, - error::JsNativeError, - js_string, - property::PropertyDescriptor, + builtins::object::for_in_iterator::ForInIterator, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsValue, }; -/// `ForInLoopInitIterator` implements the Opcode Operation for `Opcode::ForInLoopInitIterator` +/// `CreateForInIterator` implements the Opcode Operation for `Opcode::CreateForInIterator` /// /// Operation: -/// - Initialize the iterator for a for..in loop or jump to after the loop if object is null or undefined. +/// - Creates a new `ForInIterator` for the provided object. #[derive(Debug, Clone, Copy)] -pub(crate) struct ForInLoopInitIterator; +pub(crate) struct CreateForInIterator; -impl Operation for ForInLoopInitIterator { - const NAME: &'static str = "ForInLoopInitIterator"; - const INSTRUCTION: &'static str = "INST - ForInLoopInitIterator"; +impl Operation for CreateForInIterator { + const NAME: &'static str = "CreateForInIterator"; + const INSTRUCTION: &'static str = "INST - CreateForInIterator"; fn execute(context: &mut Context<'_>) -> JsResult { - let address = context.vm.read::(); - let object = context.vm.pop(); - if object.is_null_or_undefined() { - context.vm.frame_mut().pc = address as usize; - return Ok(CompletionType::Normal); - } let object = object.to_object(context)?; let iterator = ForInIterator::create_for_in_iterator(JsValue::new(object), context); let next_method = iterator - .get_property(js_string!("next")) - .as_ref() - .map(PropertyDescriptor::expect_value) - .cloned() - .ok_or_else(|| JsNativeError::typ().with_message("Could not find property `next`"))?; + .get("next", context) + .expect("ForInIterator must have a `next` method"); context.vm.push(iterator); context.vm.push(next_method); - context.vm.push(false); - Ok(CompletionType::Normal) - } -} - -/// `ForInLoopNext` implements the Opcode Operation for `Opcode::ForInLoopNext` -/// -/// Operation: -/// - Move to the next value in a for..in loop or jump to exit of the loop if done. -#[derive(Debug, Clone, Copy)] -pub(crate) struct ForInLoopNext; - -impl Operation for ForInLoopNext { - const NAME: &'static str = "ForInLoopInitIterator"; - const INSTRUCTION: &'static str = "INST - ForInLoopInitIterator"; - - fn execute(context: &mut Context<'_>) -> JsResult { - let address = context.vm.read::(); - - let done = context - .vm - .pop() - .as_boolean() - .expect("iterator [[Done]] was not a boolean"); - let next_method = context.vm.pop(); - let iterator = context.vm.pop(); - let iterator = iterator.as_object().expect("iterator was not an object"); - - let iterator_record = IteratorRecord::new(iterator.clone(), next_method.clone(), done); - if let Some(next) = iterator_record.step(context)? { - context.vm.push(iterator.clone()); - context.vm.push(next_method); - context.vm.push(done); - let value = next.value(context)?; - context.vm.push(value); - } else { - context.vm.frame_mut().pc = address as usize; - context.vm.frame_mut().dec_frame_env_stack(); - context.realm.environments.pop(); - context.vm.push(iterator.clone()); - context.vm.push(next_method); - context.vm.push(done); - } Ok(CompletionType::Normal) } } diff --git a/boa_engine/src/vm/opcode/iteration/init.rs b/boa_engine/src/vm/opcode/iteration/get.rs similarity index 61% rename from boa_engine/src/vm/opcode/iteration/init.rs rename to boa_engine/src/vm/opcode/iteration/get.rs index 78605237d5..0b68663c1d 100644 --- a/boa_engine/src/vm/opcode/iteration/init.rs +++ b/boa_engine/src/vm/opcode/iteration/get.rs @@ -4,44 +4,42 @@ use crate::{ Context, JsResult, }; -/// `InitIterator` implements the Opcode Operation for `Opcode::InitIterator` +/// `GetIterator` implements the Opcode Operation for `Opcode::GetIterator` /// /// Operation: /// - Initialize an iterator #[derive(Debug, Clone, Copy)] -pub(crate) struct InitIterator; +pub(crate) struct GetIterator; -impl Operation for InitIterator { - const NAME: &'static str = "InitIterator"; - const INSTRUCTION: &'static str = "INST - InitIterator"; +impl Operation for GetIterator { + const NAME: &'static str = "GetIterator"; + const INSTRUCTION: &'static str = "INST - GetIterator"; fn execute(context: &mut Context<'_>) -> JsResult { let object = context.vm.pop(); let iterator = object.get_iterator(context, None, None)?; context.vm.push(iterator.iterator().clone()); context.vm.push(iterator.next_method().clone()); - context.vm.push(iterator.done()); Ok(CompletionType::Normal) } } -/// `InitIteratorAsync` implements the Opcode Operation for `Opcode::InitIteratorAsync` +/// `GetAsyncIterator` implements the Opcode Operation for `Opcode::GetAsyncIterator` /// /// Operation: /// - Initialize an async iterator. #[derive(Debug, Clone, Copy)] -pub(crate) struct InitIteratorAsync; +pub(crate) struct GetAsyncIterator; -impl Operation for InitIteratorAsync { - const NAME: &'static str = "InitIteratorAsync"; - const INSTRUCTION: &'static str = "INST - InitIteratorAsync"; +impl Operation for GetAsyncIterator { + const NAME: &'static str = "GetAsyncIterator"; + const INSTRUCTION: &'static str = "INST - GetAsyncIterator"; fn execute(context: &mut Context<'_>) -> JsResult { let object = context.vm.pop(); let iterator = object.get_iterator(context, Some(IteratorHint::Async), None)?; context.vm.push(iterator.iterator().clone()); context.vm.push(iterator.next_method().clone()); - context.vm.push(iterator.done()); Ok(CompletionType::Normal) } } diff --git a/boa_engine/src/vm/opcode/iteration/iterator.rs b/boa_engine/src/vm/opcode/iteration/iterator.rs index 5a832eace5..9e6cf08e16 100644 --- a/boa_engine/src/vm/opcode/iteration/iterator.rs +++ b/boa_engine/src/vm/opcode/iteration/iterator.rs @@ -1,13 +1,16 @@ use crate::{ - builtins::{iterable::IteratorRecord, Array}, + builtins::{ + iterable::{IteratorRecord, IteratorResult}, + Array, + }, vm::{opcode::Operation, CompletionType}, - Context, JsResult, JsValue, + Context, JsNativeError, JsResult, }; /// `IteratorNext` implements the Opcode Operation for `Opcode::IteratorNext` /// /// Operation: -/// - Advance the iterator by one and put the value on the stack. +/// - Calls the `next` method of `iterator` and puts its return value on the stack. #[derive(Debug, Clone, Copy)] pub(crate) struct IteratorNext; @@ -16,55 +19,99 @@ impl Operation for IteratorNext { const INSTRUCTION: &'static str = "INST - IteratorNext"; fn execute(context: &mut Context<'_>) -> JsResult { - let done = context - .vm - .pop() - .as_boolean() - .expect("iterator [[Done]] was not a boolean"); let next_method = context.vm.pop(); let iterator = context.vm.pop(); - let iterator = iterator.as_object().expect("iterator was not an object"); + let next_result = next_method.call(&iterator, &[], context)?; + context.vm.push(iterator); + context.vm.push(next_method); + context.vm.push(next_result); + Ok(CompletionType::Normal) + } +} - let iterator_record = IteratorRecord::new(iterator.clone(), next_method.clone(), done); - let next = iterator_record.step(context)?; +/// `IteratorUnwrapNext` implements the Opcode Operation for `Opcode::IteratorUnwrapNext` +/// +/// Operation: +/// - Gets the `value` and `done` properties of an iterator result. +#[derive(Debug, Clone, Copy)] +pub(crate) struct IteratorUnwrapNext; + +impl Operation for IteratorUnwrapNext { + const NAME: &'static str = "IteratorUnwrapNext"; + const INSTRUCTION: &'static str = "INST - IteratorUnwrapNext"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let next_result = context.vm.pop(); + let next_result = next_result + .as_object() + .cloned() + .map(IteratorResult::new) + .ok_or_else(|| JsNativeError::typ().with_message("next value should be an object"))?; + let complete = next_result.complete(context)?; + let value = next_result.value(context)?; + context.vm.push(complete); + context.vm.push(value); - context.vm.push(iterator.clone()); - context.vm.push(next_method); - if let Some(next) = next { - let value = next.value(context)?; - context.vm.push(false); - context.vm.push(value); - } else { - context.vm.push(true); - context.vm.push(JsValue::undefined()); - } Ok(CompletionType::Normal) } } -/// `IteratorClose` implements the Opcode Operation for `Opcode::IteratorClose` +/// `IteratorUnwrapValue` implements the Opcode Operation for `Opcode::IteratorUnwrapValue` /// /// Operation: -/// - Close an iterator +/// - Gets the `value` property of an iterator result. #[derive(Debug, Clone, Copy)] -pub(crate) struct IteratorClose; +pub(crate) struct IteratorUnwrapValue; -impl Operation for IteratorClose { - const NAME: &'static str = "IteratorClose"; - const INSTRUCTION: &'static str = "INST - IteratorClose"; +impl Operation for IteratorUnwrapValue { + const NAME: &'static str = "IteratorUnwrapValue"; + const INSTRUCTION: &'static str = "INST - IteratorUnwrapValue"; fn execute(context: &mut Context<'_>) -> JsResult { - let done = context - .vm - .pop() - .as_boolean() - .expect("iterator [[Done]] was not a boolean"); - let next_method = context.vm.pop(); - let iterator = context.vm.pop(); - let iterator = iterator.as_object().expect("iterator was not an object"); - if !done { - let iterator_record = IteratorRecord::new(iterator.clone(), next_method, done); - iterator_record.close(Ok(JsValue::Null), context)?; + let next_result = context.vm.pop(); + let next_result = next_result + .as_object() + .cloned() + .map(IteratorResult::new) + .ok_or_else(|| JsNativeError::typ().with_message("next value should be an object"))?; + let value = next_result.value(context)?; + context.vm.push(value); + + Ok(CompletionType::Normal) + } +} + +/// `IteratorUnwrapNextOrJump` implements the Opcode Operation for `Opcode::IteratorUnwrapNextOrJump` +/// +/// Operation: +/// - Gets the `value` and `done` properties of an iterator result, or jump to `address` if +/// `done` is true. +#[derive(Debug, Clone, Copy)] +pub(crate) struct IteratorUnwrapNextOrJump; + +impl Operation for IteratorUnwrapNextOrJump { + const NAME: &'static str = "IteratorUnwrapNextOrJump"; + const INSTRUCTION: &'static str = "INST - IteratorUnwrapNextOrJump"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let address = context.vm.read::(); + + let next_result = context.vm.pop(); + let next_result = next_result + .as_object() + .cloned() + .map(IteratorResult::new) + .ok_or_else(|| JsNativeError::typ().with_message("next value should be an object"))?; + + if next_result.complete(context)? { + context.vm.frame_mut().pc = address as usize; + context.vm.frame_mut().dec_frame_env_stack(); + context.realm.environments.pop(); + context.vm.push(true); + } else { + context.vm.push(false); + let value = next_result.value(context)?; + context.vm.push(value); } Ok(CompletionType::Normal) } @@ -82,16 +129,11 @@ impl Operation for IteratorToArray { const INSTRUCTION: &'static str = "INST - IteratorToArray"; fn execute(context: &mut Context<'_>) -> JsResult { - let done = context - .vm - .pop() - .as_boolean() - .expect("iterator [[Done]] was not a boolean"); let next_method = context.vm.pop(); let iterator = context.vm.pop(); let iterator = iterator.as_object().expect("iterator was not an object"); - let iterator_record = IteratorRecord::new(iterator.clone(), next_method.clone(), done); + let iterator_record = IteratorRecord::new(iterator.clone(), next_method.clone(), false); let mut values = Vec::new(); while let Some(result) = iterator_record.step(context)? { @@ -102,7 +144,6 @@ impl Operation for IteratorToArray { context.vm.push(iterator.clone()); context.vm.push(next_method); - context.vm.push(true); context.vm.push(array); Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/iteration/mod.rs b/boa_engine/src/vm/opcode/iteration/mod.rs index 5e52baa2bd..f7fe37c4e4 100644 --- a/boa_engine/src/vm/opcode/iteration/mod.rs +++ b/boa_engine/src/vm/opcode/iteration/mod.rs @@ -1,11 +1,9 @@ -pub(crate) mod for_await; pub(crate) mod for_in; -pub(crate) mod init; +pub(crate) mod get; pub(crate) mod iterator; pub(crate) mod loop_ops; -pub(crate) use for_await::*; pub(crate) use for_in::*; -pub(crate) use init::*; +pub(crate) use get::*; pub(crate) use iterator::*; pub(crate) use loop_ops::*; diff --git a/boa_engine/src/vm/opcode/jump/mod.rs b/boa_engine/src/vm/opcode/jump/mod.rs index aa9f40290d..b76b746460 100644 --- a/boa_engine/src/vm/opcode/jump/mod.rs +++ b/boa_engine/src/vm/opcode/jump/mod.rs @@ -21,6 +21,26 @@ impl Operation for Jump { } } +// `JumpIfTrue` implements the Opcode Operation for `Opcode::JumpIfTrue` +/// +/// Operation: +/// - Conditional jump to address. +#[derive(Debug, Clone, Copy)] +pub(crate) struct JumpIfTrue; + +impl Operation for JumpIfTrue { + const NAME: &'static str = "JumpIfTrue"; + const INSTRUCTION: &'static str = "INST - JumpIfTrue"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let address = context.vm.read::(); + if context.vm.pop().to_boolean() { + context.vm.frame_mut().pc = address as usize; + } + Ok(CompletionType::Normal) + } +} + /// `JumpIfFalse` implements the Opcode Operation for `Opcode::JumpIfFalse` /// /// Operation: @@ -79,8 +99,9 @@ impl Operation for JumpIfNullOrUndefined { let value = context.vm.pop(); if value.is_null_or_undefined() { context.vm.frame_mut().pc = address as usize; + } else { + context.vm.push(value); } - context.vm.push(value); Ok(CompletionType::Normal) } } diff --git a/boa_engine/src/vm/opcode/mod.rs b/boa_engine/src/vm/opcode/mod.rs index fd05207b28..e9c4a6a50b 100644 --- a/boa_engine/src/vm/opcode/mod.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -376,7 +376,7 @@ generate_impl! { /// /// Operands: /// - /// Stack: array, iterator, next_method, done **=>** array + /// Stack: array, iterator, next_method **=>** array PushIteratorToArray, /// Binary `+` operator. @@ -656,7 +656,7 @@ generate_impl! { /// /// Operands: name_index: `u32` /// - /// Stack: value, has_declarative_binding **=>** + /// Stack: value **=>** DefInitVar, /// Declare `let` type variable. @@ -717,6 +717,14 @@ generate_impl! { /// Stack: object **=>** value GetPropertyByName, + /// Get a property method or undefined if the property is null or undefined. + /// + /// Throws if the method is not a callable object. + /// + /// Operands: name_index: `u32` + /// Stack: object **=>** object, method + GetMethod, + /// Get a property by value from an object an push it on the stack. /// /// Like `object[key]` @@ -1054,6 +1062,17 @@ generate_impl! { /// Stack: **=>** Jump, + /// Conditional jump to address. + /// + /// If the value popped is [`truthy`][truthy] then jump to `address`. + /// + /// Operands: address: `u32` + /// + /// Stack: cond **=>** + /// + /// [truthy]: https://developer.mozilla.org/en-US/docs/Glossary/Truthy + JumpIfTrue, + /// Conditional jump to address. /// /// If the value popped is [`falsy`][falsy] then jump to `address`. @@ -1090,6 +1109,13 @@ generate_impl! { /// Stack: value **=>** Throw, + /// Throw a new `TypeError` exception + /// + /// Operands: message: u32 + /// + /// Stack: **=>** + ThrowNewTypeError, + /// Start of a try block. /// /// Operands: next_address: `u32`, finally_address: `u32` @@ -1364,70 +1390,56 @@ generate_impl! { /// Stack: **=>** LabelledEnd, - /// Initialize the iterator for a for..in loop or jump to after the loop if object is null or undefined. - /// - /// Operands: address: `u32` + /// Creates the ForInIterator of an object. /// - /// Stack: object **=>** iterator, next_method, done - ForInLoopInitIterator, + /// Stack: object **=>** iterator, next_method + CreateForInIterator, - /// Initialize an iterator. + /// Gets the iterator of an object. /// /// Operands: /// - /// Stack: object **=>** iterator, next_method, done - InitIterator, + /// Stack: object **=>** iterator, next_method + GetIterator, - /// Initialize an async iterator. + /// Gets the async iterator of an object. /// /// Operands: /// - /// Stack: object **=>** iterator, next_method, done - InitIteratorAsync, + /// Stack: object **=>** iterator, next_method + GetAsyncIterator, - /// Advance the iterator by one and put the value on the stack. + /// Calls the `next` method of `iterator` and puts its return value on the stack. /// /// Operands: /// - /// Stack: iterator, next_method, done **=>** iterator, next_method, done, next_value + /// Stack: iterator, next_method **=>** iterator, next_method, next_value IteratorNext, - /// Close an iterator. + /// Gets the `value` and `done` properties of an iterator result. /// - /// Operands: - /// - /// Stack: iterator, next_method, done **=>** - IteratorClose, + /// Stack: next_result **=>** done, next_value + IteratorUnwrapNext, - /// Consume the iterator and construct and array with all the values. - /// - /// Operands: + /// Gets the `value` property of an iterator result. /// - /// Stack: iterator, next_method, done **=>** iterator, next_method, done, array - IteratorToArray, + /// Stack: next_result **=>** next_value + IteratorUnwrapValue, - /// Move to the next value in a for..in loop or jump to exit of the loop if done. - /// - /// Note: next_result is only pushed if the iterator is not done. + /// Gets the `value` and `done` properties of an iterator result, or jump to `address` if + /// `done` is true. /// /// Operands: address: `u32` /// - /// Stack: iterator, next_method, done **=>** iterator, next_method, done, next_result - ForInLoopNext, + /// Stack: next_result **=>** done, next_value ( if done != true ) + IteratorUnwrapNextOrJump, - /// Move to the next value in a for await..of loop. + /// Consume the iterator and construct and array with all the values. /// /// Operands: /// - /// Stack: iterator, next_method, done **=>** iterator, next_method, next_result - ForAwaitOfLoopIterate, - - /// Get the value from a for await..of loop next result. - /// - /// Operands: address: `u32` - /// - /// Stack: next_result **=>** done, value - ForAwaitOfLoopNext, + /// Stack: iterator, next_method **=>** iterator, next_method, array + IteratorToArray, /// Concat multiple stack objects into a string. /// @@ -1494,16 +1506,16 @@ generate_impl! { /// Resumes the current generator function. /// - /// Operands: + /// Operands: skip_yield: u32, skip_yield_await: u32 /// - /// Stack: received **=>** `Option`, skip_0, skip_1 + /// Stack: received **=>** `Option` AsyncGeneratorNext, - /// Delegates the current generator function another generator. + /// Delegates the current generator function to another generator. /// /// Operands: done_address: `u32` /// - /// Stack: iterator, next_method, done, received **=>** iterator, next_method, done + /// Stack: iterator, next_method, received **=>** iterator, next_method GeneratorNextDelegate, /// Stops the current async function and schedules it to resume later. @@ -1520,6 +1532,13 @@ generate_impl! { /// Stack: **=>** new_target PushNewTarget, + /// Pushes `true` to the stack if the top stack value is an object, or `false` otherwise. + /// + /// Operands: + /// + /// Stack: value **=>** is_object + IsObject, + /// No-operation instruction, does nothing. /// /// Operands: diff --git a/boa_engine/src/vm/opcode/push/array.rs b/boa_engine/src/vm/opcode/push/array.rs index d459d0415c..18e5fa3e93 100644 --- a/boa_engine/src/vm/opcode/push/array.rs +++ b/boa_engine/src/vm/opcode/push/array.rs @@ -86,17 +86,12 @@ impl Operation for PushIteratorToArray { const INSTRUCTION: &'static str = "INST - PushIteratorToArray"; fn execute(context: &mut Context<'_>) -> JsResult { - let done = context - .vm - .pop() - .as_boolean() - .expect("iterator [[Done]] was not a boolean"); let next_method = context.vm.pop(); let iterator = context.vm.pop(); let iterator = iterator.as_object().expect("iterator was not an object"); let array = context.vm.pop(); - let iterator = IteratorRecord::new(iterator.clone(), next_method, done); + let iterator = IteratorRecord::new(iterator.clone(), next_method, false); while let Some(next) = iterator.step(context)? { let next_value = next.value(context)?; Array::push(&array, &[next_value], context)?; diff --git a/boa_engine/src/vm/opcode/value/mod.rs b/boa_engine/src/vm/opcode/value/mod.rs index f95638d704..ce2e87eeb3 100644 --- a/boa_engine/src/vm/opcode/value/mod.rs +++ b/boa_engine/src/vm/opcode/value/mod.rs @@ -31,3 +31,21 @@ impl Operation for ValueNotNullOrUndefined { Ok(CompletionType::Normal) } } + +/// `IsObject` implements the Opcode Operation for `Opcode::IsObject` +/// +/// Operation: +/// - Pushes `true` to the stack if the top stack value is an object, or `false` otherwise. +#[derive(Debug, Clone, Copy)] +pub(crate) struct IsObject; + +impl Operation for IsObject { + const NAME: &'static str = "IsObject"; + const INSTRUCTION: &'static str = "INST - IsObject"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let value = context.vm.pop(); + context.vm.push(value.is_object()); + Ok(CompletionType::Normal) + } +}