Browse Source

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.
pull/2721/head
José Julián Espina 2 years ago
parent
commit
cb4e49a0ce
  1. 2
      boa_engine/src/builtins/iterable/async_from_sync_iterator.rs
  2. 26
      boa_engine/src/builtins/iterable/mod.rs
  3. 7
      boa_engine/src/builtins/object/for_in_iterator.rs
  4. 169
      boa_engine/src/bytecompiler/declaration/declaration_pattern.rs
  5. 18
      boa_engine/src/bytecompiler/expression/mod.rs
  6. 26
      boa_engine/src/bytecompiler/mod.rs
  7. 28
      boa_engine/src/bytecompiler/statement/loop.rs
  8. 59
      boa_engine/src/bytecompiler/utils.rs
  9. 1
      boa_engine/src/tests/mod.rs
  10. 45
      boa_engine/src/tests/promise.rs
  11. 30
      boa_engine/src/value/mod.rs
  12. 21
      boa_engine/src/vm/code_block.rs
  13. 56
      boa_engine/src/vm/flowgraph/mod.rs
  14. 2
      boa_engine/src/vm/mod.rs
  15. 26
      boa_engine/src/vm/opcode/control_flow/throw.rs
  16. 22
      boa_engine/src/vm/opcode/generator/mod.rs
  17. 29
      boa_engine/src/vm/opcode/get/property.rs
  18. 71
      boa_engine/src/vm/opcode/iteration/for_await.rs
  19. 73
      boa_engine/src/vm/opcode/iteration/for_in.rs
  20. 22
      boa_engine/src/vm/opcode/iteration/get.rs
  21. 131
      boa_engine/src/vm/opcode/iteration/iterator.rs
  22. 6
      boa_engine/src/vm/opcode/iteration/mod.rs
  23. 23
      boa_engine/src/vm/opcode/jump/mod.rs
  24. 107
      boa_engine/src/vm/opcode/mod.rs
  25. 7
      boa_engine/src/vm/opcode/push/array.rs
  26. 18
      boa_engine/src/vm/opcode/value/mod.rs

2
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);

26
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<JsValue>,
value: Option<&JsValue>,
context: &mut Context<'_>,
) -> JsResult<IteratorResult> {
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.

7
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( )

169
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);
}
}
}
}

18
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);

26
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);

28
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(

59
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 **=>** \<empty\>
///
/// [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);
}
}

1
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};

45
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,
);
}

30
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<Key>(&self, key: Key) -> Option<PropertyDescriptor>
where
Key: Into<PropertyKey>,
{
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`.
///
/// <https://tc39.es/ecma262/#sec-toprimitive>
pub fn to_primitive(

21
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::<u32>(*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::<u32>(*pc);
*pc += size_of::<u32>();
let operand2 = self.read::<u32>(*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(),
}
}

56
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::<u32>(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::<u32>(pc) as usize;
pc += size_of::<u32>();
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::<u32>(pc) as usize;
pc += size_of::<u32>();
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::<u32>(pc);
pc += size_of::<u32>();
let skip_yield_await = self.read::<u32>(pc);
pc += size_of::<u32>();
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::<u32>(pc);
pc += size_of::<u32>();
@ -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);

2
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);
}

26
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<CompletionType> {
let index = context.vm.read::<u32>();
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())
}
}

22
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<CompletionType> {
let skip_yield = context.vm.read::<u32>();
let skip_yield_await = context.vm.read::<u32>();
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<CompletionType> {
let done_address = context.vm.read::<u32>();
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);

29
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<CompletionType> {
let index = context.vm.read::<u32>();
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:

71
boa_engine/src/vm/opcode/iteration/for_await.rs

@ -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<CompletionType> {
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<CompletionType> {
let address = context.vm.read::<u32>();
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)
}
}

73
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<CompletionType> {
let address = context.vm.read::<u32>();
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<CompletionType> {
let address = context.vm.read::<u32>();
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)
}
}

22
boa_engine/src/vm/opcode/iteration/init.rs → 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<CompletionType> {
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<CompletionType> {
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)
}
}

131
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<CompletionType> {
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<CompletionType> {
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<CompletionType> {
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<CompletionType> {
let address = context.vm.read::<u32>();
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<CompletionType> {
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)
}

6
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::*;

23
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<CompletionType> {
let address = context.vm.read::<u32>();
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)
}
}

107
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<value>`, skip_0, skip_1
/// Stack: received **=>** `Option<value>`
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:

7
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<CompletionType> {
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)?;

18
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<CompletionType> {
let value = context.vm.pop();
context.vm.push(value.is_object());
Ok(CompletionType::Normal)
}
}

Loading…
Cancel
Save