Browse Source

Unify async iterators and iterators compilation (#2976)

* Close active iterators when returning from async generator

* Fix remaining async generator tests

* Fix remaining async generator tests

* Replace some reserved opcodes

* Fix for await of loop flag

* Add tests for fix

* Remove whitespace

* Fix test display and improve test

* Change `usize` to `u32`
pull/2996/head
José Julián Espina 1 year ago committed by GitHub
parent
commit
a989462e25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      .vscode/launch.json
  2. 10
      .vscode/tasks.json
  3. 4
      boa_cli/src/main.rs
  4. 13
      boa_engine/src/builtins/array/mod.rs
  5. 65
      boa_engine/src/builtins/async_generator/mod.rs
  6. 6
      boa_engine/src/builtins/intl/list_format/mod.rs
  7. 72
      boa_engine/src/builtins/iterable/async_from_sync_iterator.rs
  8. 145
      boa_engine/src/builtins/iterable/mod.rs
  9. 8
      boa_engine/src/builtins/map/mod.rs
  10. 108
      boa_engine/src/builtins/promise/mod.rs
  11. 8
      boa_engine/src/builtins/set/mod.rs
  12. 8
      boa_engine/src/builtins/weak_set/mod.rs
  13. 85
      boa_engine/src/bytecompiler/declaration/declaration_pattern.rs
  14. 10
      boa_engine/src/bytecompiler/declarations.rs
  15. 68
      boa_engine/src/bytecompiler/expression/mod.rs
  16. 39
      boa_engine/src/bytecompiler/jump_control.rs
  17. 26
      boa_engine/src/bytecompiler/mod.rs
  18. 25
      boa_engine/src/bytecompiler/statement/continue.rs
  19. 35
      boa_engine/src/bytecompiler/statement/loop.rs
  20. 4
      boa_engine/src/bytecompiler/statement/mod.rs
  21. 93
      boa_engine/src/bytecompiler/utils.rs
  22. 4
      boa_engine/src/job.rs
  23. 222
      boa_engine/src/tests/iterators.rs
  24. 1
      boa_engine/src/tests/mod.rs
  25. 27
      boa_engine/src/vm/call_frame/env_stack.rs
  26. 9
      boa_engine/src/vm/call_frame/mod.rs
  27. 55
      boa_engine/src/vm/code_block.rs
  28. 16
      boa_engine/src/vm/completion_record.rs
  29. 45
      boa_engine/src/vm/flowgraph/graph.rs
  30. 143
      boa_engine/src/vm/flowgraph/mod.rs
  31. 6
      boa_engine/src/vm/mod.rs
  32. 85
      boa_engine/src/vm/opcode/control_flow/throw.rs
  33. 376
      boa_engine/src/vm/opcode/generator/mod.rs
  34. 86
      boa_engine/src/vm/opcode/generator/yield_stm.rs
  35. 10
      boa_engine/src/vm/opcode/iteration/for_in.rs
  36. 6
      boa_engine/src/vm/opcode/iteration/get.rs
  37. 330
      boa_engine/src/vm/opcode/iteration/iterator.rs
  38. 23
      boa_engine/src/vm/opcode/iteration/loop_ops.rs
  39. 151
      boa_engine/src/vm/opcode/mod.rs
  40. 18
      boa_engine/src/vm/opcode/push/array.rs

4
.vscode/launch.json vendored

@ -14,7 +14,7 @@
"program": "${workspaceFolder}/target/debug/boa",
"args": ["${workspaceFolder}/tests/js/test.js", "--debug-object"],
"sourceLanguages": ["rust"],
"preLaunchTask": "Cargo Build"
"preLaunchTask": "Cargo Build boa_cli"
},
{
"type": "lldb",
@ -32,7 +32,7 @@
"tests/js"
],
"sourceLanguages": ["rust"],
"preLaunchTask": "Cargo Build"
"preLaunchTask": "Cargo Build boa_cli"
}
]
}

10
.vscode/tasks.json vendored

@ -13,6 +13,16 @@
"clear": true
}
},
{
"type": "process",
"label": "Cargo Build boa_cli",
"command": "cargo",
"args": ["build", "-p", "boa_cli"],
"group": "build",
"presentation": {
"clear": true
}
},
{
"type": "process",
"label": "Cargo Run",

4
boa_cli/src/main.rs

@ -487,7 +487,7 @@ struct Jobs(RefCell<VecDeque<NativeJob>>);
impl JobQueue for Jobs {
fn enqueue_promise_job(&self, job: NativeJob, _: &mut Context<'_>) {
self.0.borrow_mut().push_front(job);
self.0.borrow_mut().push_back(job);
}
fn run_jobs(&self, context: &mut Context<'_>) {
@ -506,6 +506,6 @@ impl JobQueue for Jobs {
fn enqueue_future_job(&self, future: FutureJob, _: &mut Context<'_>) {
let job = pollster::block_on(future);
self.0.borrow_mut().push_front(job);
self.0.borrow_mut().push_back(job);
}
}

13
boa_engine/src/builtins/array/mod.rs

@ -561,7 +561,7 @@ impl Array {
};
// c. Let iteratorRecord be ? GetIterator(items, sync, usingIterator).
let iterator_record =
let mut iterator_record =
items.get_iterator(context, Some(IteratorHint::Sync), Some(using_iterator))?;
// d. Let k be 0.
@ -571,19 +571,16 @@ impl Array {
// x. Set k to k + 1.
for k in 0..9_007_199_254_740_991_u64 {
// iii. Let next be ? IteratorStep(iteratorRecord).
let next = iterator_record.step(context)?;
// iv. If next is false, then
let Some(next) = next else {
if iterator_record.step(context)? {
// 1. Perform ? Set(A, "length", 𝔽(k), true).
a.set(utf16!("length"), k, true, context)?;
// 2. Return A.
return Ok(a.into());
};
}
// iv. If next is false, then
// v. Let nextValue be ? IteratorValue(next).
let next_value = next.value(context)?;
let next_value = iterator_record.value(context)?;
// vi. If mapping is true, then
let mapped_value = if let Some(mapfn) = mapping {

65
boa_engine/src/builtins/async_generator/mod.rs

@ -18,7 +18,7 @@ use crate::{
realm::Realm,
symbol::JsSymbol,
value::JsValue,
vm::GeneratorResumeKind,
vm::{CompletionRecord, GeneratorResumeKind},
Context, JsArgs, JsError, JsResult,
};
use boa_gc::{Finalize, Trace};
@ -46,7 +46,7 @@ pub(crate) enum AsyncGeneratorState {
#[derive(Debug, Clone, Finalize, Trace)]
pub(crate) struct AsyncGeneratorRequest {
/// The `[[Completion]]` slot.
pub(crate) completion: (JsResult<JsValue>, bool),
pub(crate) completion: CompletionRecord,
/// The `[[Capability]]` slot.
capability: PromiseCapability,
@ -164,7 +164,7 @@ impl AsyncGenerator {
}
// 7. Let completion be NormalCompletion(value).
let completion = (Ok(args.get_or_undefined(0).clone()), false);
let completion = CompletionRecord::Normal(args.get_or_undefined(0).clone());
// 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
generator.enqueue(completion.clone(), promise_capability.clone());
@ -232,7 +232,8 @@ impl AsyncGenerator {
if_abrupt_reject_promise!(generator, promise_capability, context);
// 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
let completion = (Ok(args.get_or_undefined(0).clone()), true);
let return_value = args.get_or_undefined(0).clone();
let completion = CompletionRecord::Return(return_value.clone());
// 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
generator.enqueue(completion.clone(), promise_capability.clone());
@ -246,14 +247,8 @@ impl AsyncGenerator {
generator.state = AsyncGeneratorState::AwaitingReturn;
// b. Perform ! AsyncGeneratorAwaitReturn(generator).
let next = generator
.queue
.front()
.cloned()
.expect("queue cannot be empty here");
drop(generator_obj_mut);
let (completion, _) = &next.completion;
Self::await_return(generator_object.clone(), completion.clone(), context);
Self::await_return(generator_object.clone(), return_value, context);
}
// 9. Else if state is suspendedYield, then
else if state == AsyncGeneratorState::SuspendedYield {
@ -346,10 +341,8 @@ impl AsyncGenerator {
}
// 8. Let completion be ThrowCompletion(exception).
let completion = (
Err(JsError::from_opaque(args.get_or_undefined(0).clone())),
false,
);
let completion =
CompletionRecord::Throw(JsError::from_opaque(args.get_or_undefined(0).clone()));
// 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
generator.enqueue(completion.clone(), promise_capability.clone());
@ -384,7 +377,7 @@ impl AsyncGenerator {
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorenqueue
pub(crate) fn enqueue(
&mut self,
completion: (JsResult<JsValue>, bool),
completion: CompletionRecord,
promise_capability: PromiseCapability,
) {
// 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }.
@ -469,7 +462,7 @@ impl AsyncGenerator {
generator: &JsObject,
state: AsyncGeneratorState,
mut generator_context: GeneratorContext,
completion: (JsResult<JsValue>, bool),
completion: CompletionRecord,
context: &mut Context<'_>,
) {
// 1. Assert: generator.[[AsyncGeneratorState]] is either suspendedStart or suspendedYield.
@ -491,15 +484,9 @@ impl AsyncGenerator {
.state = AsyncGeneratorState::Executing;
let (value, resume_kind) = match completion {
(Ok(value), r#return) => (
value,
if r#return {
GeneratorResumeKind::Return
} else {
GeneratorResumeKind::Normal
},
),
(Err(value), _) => (value.to_opaque(context), GeneratorResumeKind::Throw),
CompletionRecord::Normal(val) => (val, GeneratorResumeKind::Normal),
CompletionRecord::Return(val) => (val, GeneratorResumeKind::Return),
CompletionRecord::Throw(err) => (err.to_opaque(context), GeneratorResumeKind::Throw),
};
// 6. Push genContext onto the execution context stack; genContext is now the running execution context.
@ -527,19 +514,12 @@ impl AsyncGenerator {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn
pub(crate) fn await_return(
generator: JsObject,
completion: JsResult<JsValue>,
context: &mut Context<'_>,
) {
pub(crate) fn await_return(generator: JsObject, value: JsValue, context: &mut Context<'_>) {
// 1. Let queue be generator.[[AsyncGeneratorQueue]].
// 2. Assert: queue is not empty.
// 3. Let next be the first element of queue.
// 4. Let completion be Completion(next.[[Completion]]).
// 5. Assert: completion.[[Type]] is return.
let value = completion.expect("completion must be a return completion");
// Note: The spec is currently broken here.
// See: https://github.com/tc39/ecma262/pull/2683
@ -681,29 +661,24 @@ impl AsyncGenerator {
let next = queue.front().expect("must have entry");
// b. Let completion be Completion(next.[[Completion]]).
match &next.completion {
match next.completion.clone() {
// c. If completion.[[Type]] is return, then
(completion, true) => {
CompletionRecord::Return(val) => {
// i. Set generator.[[AsyncGeneratorState]] to awaiting-return.
gen.state = AsyncGeneratorState::AwaitingReturn;
drop(generator_borrow_mut);
// ii. Perform ! AsyncGeneratorAwaitReturn(generator).
let completion = completion.clone();
drop(generator_borrow_mut);
Self::await_return(generator.clone(), completion, context);
Self::await_return(generator.clone(), val, context);
// iii. Set done to true.
break;
}
// d. Else,
(completion, false) => {
completion => {
// i. If completion.[[Type]] is normal, then
let completion = if completion.is_ok() {
// 1. Set completion to NormalCompletion(undefined).
Ok(JsValue::undefined())
} else {
completion.clone()
};
let completion = completion.consume().map(|_| JsValue::undefined());
// ii. Perform AsyncGeneratorCompleteStep(generator, completion, true).
let next = queue.pop_front().expect("must have entry");

6
boa_engine/src/builtins/intl/list_format/mod.rs

@ -469,7 +469,7 @@ fn string_list_from_iterable(
}
// 2. Let iteratorRecord be ? GetIterator(iterable).
let iterator = iterable.get_iterator(context, None, None)?;
let mut iterator = iterable.get_iterator(context, None, None)?;
// 3. Let list be a new empty List.
let mut list = Vec::new();
@ -478,9 +478,9 @@ fn string_list_from_iterable(
// 5. Repeat, while next is not false,
// a. Set next to ? IteratorStep(iteratorRecord).
// b. If next is not false, then
while let Some(item) = iterator.step(context)? {
while !iterator.step(context)? {
let item = iterator.value(context)?;
// i. Let nextValue be ? IteratorValue(next).
let item = item.value(context)?;
// ii. If Type(nextValue) is not String, then
let Some(s) = item.as_string().cloned() else {
// 1. Let error be ThrowCompletion(a newly created TypeError object).

72
boa_engine/src/builtins/iterable/async_from_sync_iterator.rs

@ -9,7 +9,7 @@ use crate::{
object::{FunctionObjectBuilder, JsObject, ObjectData},
realm::Realm,
string::utf16,
Context, JsArgs, JsNativeError, JsResult, JsValue,
Context, JsArgs, JsResult, JsValue,
};
use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler;
@ -84,7 +84,7 @@ impl AsyncFromSyncIterator {
// 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: asyncIterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
// 5. Return iteratorRecord.
IteratorRecord::new(async_iterator, next_method, false)
IteratorRecord::new(async_iterator, next_method)
}
/// `%AsyncFromSyncIteratorPrototype%.next ( [ value ] )`
@ -117,7 +117,15 @@ 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), context);
let iterator = sync_iterator_record.iterator().clone();
let next = sync_iterator_record.next_method();
let result = next
.call(
&iterator.into(),
args.get(0).map_or(&[], std::slice::from_ref),
context,
)
.and_then(IteratorResult::from_value);
// 7. IfAbruptRejectPromise(result, promiseCapability).
if_abrupt_reject_promise!(result, promise_capability, context);
@ -187,36 +195,15 @@ impl AsyncFromSyncIterator {
}
};
// 10. IfAbruptRejectPromise(result, promiseCapability).
if_abrupt_reject_promise!(result, promise_capability, context);
let Some(result) = result.as_object() else {
// 11. If Type(result) is not Object, then
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
promise_capability
.reject()
.call(
&JsValue::Undefined,
&[JsNativeError::typ()
.with_message("iterator return function returned non-object")
.to_opaque(context)
.into()],
context,
)
.expect("cannot fail according to spec");
let result = result.and_then(IteratorResult::from_value);
// b. Return promiseCapability.[[Promise]].
return Ok(promise_capability.promise().clone().into());
};
// 10. IfAbruptRejectPromise(result, promiseCapability).
if_abrupt_reject_promise!(result, promise_capability, context);
// 12. Return AsyncFromSyncIteratorContinuation(result, promiseCapability).
Self::continuation(
&IteratorResult {
object: result.clone(),
},
&promise_capability,
context,
)
Self::continuation(&result, &promise_capability, context)
}
/// `%AsyncFromSyncIteratorPrototype%.throw ( [ value ] )`
@ -280,36 +267,15 @@ impl AsyncFromSyncIterator {
}
};
// 10. IfAbruptRejectPromise(result, promiseCapability).
if_abrupt_reject_promise!(result, promise_capability, context);
let Some(result) = result.as_object() else {
// 11. If Type(result) is not Object, then
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
promise_capability
.reject()
.call(
&JsValue::Undefined,
&[JsNativeError::typ()
.with_message("iterator throw function returned non-object")
.to_opaque(context)
.into()],
context,
)
.expect("cannot fail according to spec");
let result = result.and_then(IteratorResult::from_value);
// b. Return promiseCapability.[[Promise]].
return Ok(promise_capability.promise().clone().into());
};
// 10. IfAbruptRejectPromise(result, promiseCapability).
if_abrupt_reject_promise!(result, promise_capability, context);
// 12. Return AsyncFromSyncIteratorContinuation(result, promiseCapability).
Self::continuation(
&IteratorResult {
object: result.clone(),
},
&promise_capability,
context,
)
Self::continuation(&result, &promise_capability, context)
}
/// `AsyncFromSyncIteratorContinuation ( result, promiseCapability )`

145
boa_engine/src/builtins/iterable/mod.rs

@ -285,24 +285,32 @@ impl JsValue {
// 6. Let iteratorRecord be the Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
// 7. Return iteratorRecord.
Ok(IteratorRecord::new(
iterator_obj.clone(),
next_method,
false,
))
Ok(IteratorRecord::new(iterator_obj.clone(), next_method))
}
}
/// The result of the iteration process.
#[derive(Debug)]
#[derive(Debug, Clone, Trace, Finalize)]
pub struct IteratorResult {
object: JsObject,
}
impl IteratorResult {
/// Create a new `IteratorResult`.
pub(crate) fn new(object: JsObject) -> Self {
Self { object }
/// Gets a new `IteratorResult` from a value. Returns `Err` if
/// the value is not a [`JsObject`]
pub(crate) fn from_value(value: JsValue) -> JsResult<Self> {
if let JsValue::Object(o) = value {
Ok(Self { object: o })
} else {
Err(JsNativeError::typ()
.with_message("next value should be an object")
.into())
}
}
/// Gets the inner object of this `IteratorResult`.
pub(crate) const fn object(&self) -> &JsObject {
&self.object
}
/// `IteratorComplete ( iterResult )`
@ -362,16 +370,22 @@ pub struct IteratorRecord {
///
/// Whether the iterator has been closed.
done: bool,
/// The result of the last call to `next`.
last_result: IteratorResult,
}
impl IteratorRecord {
/// Creates a new `IteratorRecord` with the given iterator object, next method and `done` flag.
#[inline]
pub fn new(iterator: JsObject, next_method: JsValue, done: bool) -> Self {
pub fn new(iterator: JsObject, next_method: JsValue) -> Self {
Self {
iterator,
next_method,
done,
done: false,
last_result: IteratorResult {
object: JsObject::with_null_proto(),
},
}
}
@ -380,19 +394,63 @@ impl IteratorRecord {
&self.iterator
}
/// Get the `[[NextMethod]]` field of the `IteratorRecord`.
/// Gets the `[[NextMethod]]` field of the `IteratorRecord`.
pub(crate) const fn next_method(&self) -> &JsValue {
&self.next_method
}
/// Gets the last result object of the iterator record.
pub(crate) const fn last_result(&self) -> &IteratorResult {
&self.last_result
}
/// Runs `f`, setting the `done` field of this `IteratorRecord` to `true` if `f` returns
/// an error.
fn set_done_on_err<R, F>(&mut self, f: F) -> JsResult<R>
where
F: FnOnce(&mut Self) -> JsResult<R>,
{
let result = f(self);
if result.is_err() {
self.done = true;
}
result
}
/// Gets the current value of the `IteratorRecord`.
pub(crate) fn value(&mut self, context: &mut Context<'_>) -> JsResult<JsValue> {
self.set_done_on_err(|iter| iter.last_result.value(context))
}
/// Get the `[[Done]]` field of the `IteratorRecord`.
pub(crate) const fn done(&self) -> bool {
self.done
}
/// Sets the `[[Done]]` field of the `IteratorRecord`.
pub(crate) fn set_done(&mut self, done: bool) {
self.done = done;
/// Updates the current result value of this iterator record.
pub(crate) fn update_result(
&mut self,
result: JsValue,
context: &mut Context<'_>,
) -> JsResult<()> {
self.set_done_on_err(|iter| {
// 3. If Type(result) is not Object, throw a TypeError exception.
// 4. Return result.
// `IteratorResult::from_value` does this for us.
// `IteratorStep(iteratorRecord)`
// https://tc39.es/ecma262/#sec-iteratorstep
// 1. Let result be ? IteratorNext(iteratorRecord).
let result = IteratorResult::from_value(result)?;
// 2. Let done be ? IteratorComplete(result).
// 3. If done is true, return false.
iter.done = result.complete(context)?;
iter.last_result = result;
Ok(())
})
}
/// `IteratorNext ( iteratorRecord [ , value ] )`
@ -405,63 +463,43 @@ impl IteratorRecord {
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iteratornext
pub(crate) fn next(
&self,
pub(crate) fn step_with(
&mut self,
value: Option<&JsValue>,
context: &mut Context<'_>,
) -> JsResult<IteratorResult> {
let _timer = Profiler::global().start_event("IteratorRecord::next", "iterator");
) -> JsResult<bool> {
let _timer = Profiler::global().start_event("IteratorRecord::step_with", "iterator");
self.set_done_on_err(|iter| {
// 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(),
let result = iter.next_method.call(
&iter.iterator.clone().into(),
value.map_or(&[], std::slice::from_ref),
context,
)?;
// 3. If Type(result) is not Object, throw a TypeError exception.
iter.update_result(result, context)?;
// 4. Return result.
result
.as_object()
.map(|o| IteratorResult { object: o.clone() })
.ok_or_else(|| {
JsNativeError::typ()
.with_message("next value should be an object")
.into()
Ok(iter.done)
})
}
/// `IteratorStep ( iteratorRecord )`
///
/// The abstract operation `IteratorStep` takes argument `iteratorRecord` (an `Iterator`
/// Record) and returns either a normal completion containing either an `Object` or `false`, or
/// a throw completion. It requests the next value from `iteratorRecord.[[Iterator]]` by
/// calling `iteratorRecord.[[NextMethod]]` and returns either `false` indicating that the
/// iterator has reached its end or the `IteratorResult` object if a next value is available.
/// Updates the `IteratorRecord` and returns `true` if the next result record returned
/// `done: true`, otherwise returns `false`. This differs slightly from the spec, but also
/// simplifies some logic around iterators.
///
/// More information:
/// - [ECMA reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-iteratorstep
pub(crate) fn step(&self, context: &mut Context<'_>) -> JsResult<Option<IteratorResult>> {
let _timer = Profiler::global().start_event("IteratorRecord::step", "iterator");
// 1. Let result be ? IteratorNext(iteratorRecord).
let result = self.next(None, context)?;
// 2. Let done be ? IteratorComplete(result).
let done = result.complete(context)?;
// 3. If done is true, return false.
if done {
return Ok(None);
}
// 4. Return result.
Ok(Some(result))
pub(crate) fn step(&mut self, context: &mut Context<'_>) -> JsResult<bool> {
self.step_with(None, context)
}
/// `IteratorClose ( iteratorRecord, completion )`
@ -548,7 +586,7 @@ pub(crate) fn iterable_to_list(
// a. Let iteratorRecord be ? GetIterator(items, sync, method).
// 2. Else,
// a. Let iteratorRecord be ? GetIterator(items, sync).
let iterator_record = items.get_iterator(context, Some(IteratorHint::Sync), method)?;
let mut iterator_record = items.get_iterator(context, Some(IteratorHint::Sync), method)?;
// 3. Let values be a new empty List.
let mut values = Vec::new();
@ -559,9 +597,8 @@ pub(crate) fn iterable_to_list(
// b. If next is not false, then
// i. Let nextValue be ? IteratorValue(next).
// ii. Append nextValue to the end of the List values.
while let Some(next) = iterator_record.step(context)? {
let next_value = next.value(context)?;
values.push(next_value);
while !iterator_record.step(context)? {
values.push(iterator_record.value(context)?);
}
// 6. Return values.

8
boa_engine/src/builtins/map/mod.rs

@ -542,20 +542,18 @@ pub(crate) fn add_entries_from_iterable(
})?;
// 2. Let iteratorRecord be ? GetIterator(iterable).
let iterator_record = iterable.get_iterator(context, None, None)?;
let mut iterator_record = iterable.get_iterator(context, None, None)?;
// 3. Repeat,
loop {
// a. Let next be ? IteratorStep(iteratorRecord).
let next = iterator_record.step(context)?;
// b. If next is false, return target.
// c. Let nextItem be ? IteratorValue(next).
let Some(next_item) = next else {
if iterator_record.step(context)? {
return Ok(target.clone().into());
};
let next_item = next_item.value(context)?;
let next_item = iterator_record.value(context)?;
let Some(next_item) = next_item.as_object() else {
// d. If Type(nextItem) is not Object, then

108
boa_engine/src/builtins/promise/mod.rs

@ -542,21 +542,13 @@ impl Promise {
// 4. Repeat,
loop {
// a. Let next be Completion(IteratorStep(iteratorRecord)).
let next = iterator_record.step(context);
let next_value = match next {
Err(e) => {
// b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// c. ReturnIfAbrupt(next).
return Err(e);
}
let done = iterator_record.step(context)?;
// d. If next is false, then
Ok(None) => {
// i. Set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
if done {
// ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
remaining_elements_count.set(remaining_elements_count.get() - 1);
@ -579,22 +571,11 @@ impl Promise {
// iv. Return resultCapability.[[Promise]].
return Ok(result_capability.promise.clone());
}
Ok(Some(next)) => {
// e. Let nextValue be Completion(IteratorValue(next)).
let next_value = next.value(context);
match next_value {
Err(e) => {
// e. Let nextValue be Completion(IteratorValue(next)).
// f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// g. ReturnIfAbrupt(nextValue).
return Err(e);
}
Ok(next_value) => next_value,
}
}
};
let next_value = iterator_record.value(context)?;
// h. Append undefined to values.
values.borrow_mut().push(JsValue::Undefined);
@ -785,21 +766,13 @@ impl Promise {
// 4. Repeat,
loop {
// a. Let next be Completion(IteratorStep(iteratorRecord)).
let next = iterator_record.step(context);
let next_value = match next {
Err(e) => {
// b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// c. ReturnIfAbrupt(next).
return Err(e);
}
let done = iterator_record.step(context)?;
// d. If next is false, then
Ok(None) => {
if done {
// i. Set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
remaining_elements_count.set(remaining_elements_count.get() - 1);
@ -822,22 +795,11 @@ impl Promise {
// iv. Return resultCapability.[[Promise]].
return Ok(result_capability.promise.clone());
}
Ok(Some(next)) => {
// e. Let nextValue be Completion(IteratorValue(next)).
let next_value = next.value(context);
match next_value {
Err(e) => {
// e. Let nextValue be Completion(IteratorValue(next)).
// f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// g. ReturnIfAbrupt(nextValue).
return Err(e);
}
Ok(next_value) => next_value,
}
}
};
let next_value = iterator_record.value(context)?;
// h. Append undefined to values.
values.borrow_mut().push(JsValue::undefined());
@ -1131,20 +1093,13 @@ impl Promise {
// 4. Repeat,
loop {
// a. Let next be Completion(IteratorStep(iteratorRecord)).
let next = iterator_record.step(context);
let next_value = match next {
Err(e) => {
// b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// c. ReturnIfAbrupt(next).
return Err(e);
}
let done = iterator_record.step(context)?;
// d. If next is false, then
Ok(None) => {
if done {
// i. Set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
remaining_elements_count.set(remaining_elements_count.get() - 1);
@ -1170,22 +1125,11 @@ impl Promise {
// iv. Return resultCapability.[[Promise]].
return Ok(result_capability.promise.clone());
}
Ok(Some(next)) => {
// e. Let nextValue be Completion(IteratorValue(next)).
let next_value = next.value(context);
match next_value {
Err(e) => {
// e. Let nextValue be Completion(IteratorValue(next)).
// f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// g. ReturnIfAbrupt(nextValue).
return Err(e);
}
Ok(next_value) => next_value,
}
}
};
let next_value = iterator_record.value(context)?;
// h. Append undefined to errors.
errors.borrow_mut().push(JsValue::undefined());
@ -1375,35 +1319,21 @@ impl Promise {
// 1. Repeat,
loop {
// a. Let next be Completion(IteratorStep(iteratorRecord)).
let next = iterator_record.step(context);
// b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
if next.is_err() {
iterator_record.set_done(true);
}
// c. ReturnIfAbrupt(next).
let next = next?;
let done = iterator_record.step(context)?;
let Some(next) = next else {
if done {
// d. If next is false, then
// i. Set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// ii. Return resultCapability.[[Promise]].
return Ok(result_capability.promise.clone());
};
}
// e. Let nextValue be Completion(IteratorValue(next)).
let next_value = next.value(context);
// f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true.
if next_value.is_err() {
iterator_record.set_done(true);
}
// g. ReturnIfAbrupt(nextValue).
let next_value = next_value?;
let next_value = iterator_record.value(context)?;
// h. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »).
let next_promise = promise_resolve.call(&constructor, &[next_value], context)?;

8
boa_engine/src/builtins/set/mod.rs

@ -145,7 +145,7 @@ impl BuiltInConstructor for Set {
})?;
// 7. Let iteratorRecord be ? GetIterator(iterable).
let iterator_record = iterable.clone().get_iterator(context, None, None)?;
let mut iterator_record = iterable.clone().get_iterator(context, None, None)?;
// 8. Repeat,
// a. Let next be ? IteratorStep(iteratorRecord).
@ -153,12 +153,12 @@ impl BuiltInConstructor for Set {
// c. Let nextValue be ? IteratorValue(next).
// d. Let status be Completion(Call(adder, set, « nextValue »)).
// e. IfAbruptCloseIterator(status, iteratorRecord).
while let Some(next) = iterator_record.step(context)? {
while !iterator_record.step(context)? {
let next = iterator_record.value(context)?;
// c
let next_value = next.value(context)?;
// d, e
if let Err(status) = adder.call(&set.clone().into(), &[next_value], context) {
if let Err(status) = adder.call(&set.clone().into(), &[next], context) {
return iterator_record.close(Err(status), context);
}
}

8
boa_engine/src/builtins/weak_set/mod.rs

@ -101,17 +101,17 @@ impl BuiltInConstructor for WeakSet {
.ok_or_else(|| JsNativeError::typ().with_message("WeakSet: 'add' is not a function"))?;
// 7. Let iteratorRecord be ? GetIterator(iterable).
let iterator_record = iterable.clone().get_iterator(context, None, None)?;
let mut iterator_record = iterable.clone().get_iterator(context, None, None)?;
// 8. Repeat,
// a. Let next be ? IteratorStep(iteratorRecord).
while let Some(next) = iterator_record.step(context)? {
while !iterator_record.step(context)? {
// c. Let nextValue be ? IteratorValue(next).
let next_value = next.value(context)?;
let next = iterator_record.value(context)?;
// d. Let status be Completion(Call(adder, set, « nextValue »)).
// e. IfAbruptCloseIterator(status, iteratorRecord).
if let Err(status) = adder.call(&weak_set.clone().into(), &[next_value], context) {
if let Err(status) = adder.call(&weak_set.clone().into(), &[next], context) {
return iterator_record.close(Err(status), context);
}
}

85
boa_engine/src/bytecompiler/declaration/declaration_pattern.rs

@ -176,54 +176,34 @@ impl ByteCompiler<'_, '_> {
Pattern::Array(pattern) => {
self.emit_opcode(Opcode::ValueNotNullOrUndefined);
self.emit_opcode(Opcode::GetIterator);
self.emit_opcode(Opcode::IteratorClosePush);
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);
}
self.compile_array_pattern_element(last, def, true);
}
for element in pattern.bindings() {
self.compile_array_pattern_element(element, def);
}
self.emit_opcode(Opcode::IteratorClosePop);
self.iterator_close(false);
}
}
}
fn compile_array_pattern_element(
&mut self,
element: &ArrayPatternElement,
def: BindingOpcode,
with_done: bool,
) {
fn compile_array_pattern_element(&mut self, element: &ArrayPatternElement, def: BindingOpcode) {
use ArrayPatternElement::{
Elision, Pattern, PatternRest, PropertyAccess, PropertyAccessRest, SingleName,
SingleNameRest,
};
let unwrapping = if with_done {
Opcode::IteratorUnwrapNext
} else {
Opcode::IteratorUnwrapValue
};
match element {
// ArrayBindingPattern : [ Elision ]
Elision => {
self.emit_opcode(Opcode::IteratorNextSetDone);
if with_done {
self.emit_opcode(Opcode::IteratorUnwrapNext);
}
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::IteratorNext);
}
// SingleNameBinding : BindingIdentifier Initializer[opt]
SingleName {
ident,
default_init,
} => {
self.emit_opcode(Opcode::IteratorNextSetDone);
self.emit_opcode(unwrapping);
self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(Opcode::IteratorValue);
if let Some(init) = default_init {
let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true);
@ -232,23 +212,9 @@ impl ByteCompiler<'_, '_> {
self.emit_binding(def, *ident);
}
PropertyAccess { access } => {
self.access_set(Access::Property { access }, false, |compiler, level| {
if level != 0 {
compiler.emit_opcode(Opcode::RotateLeft);
compiler.emit_u8(level + 2);
compiler.emit_opcode(Opcode::RotateLeft);
compiler.emit_u8(level + 2);
}
compiler.emit_opcode(Opcode::IteratorNextSetDone);
compiler.emit_opcode(unwrapping);
if level != 0 {
compiler.emit_opcode(Opcode::RotateLeft);
compiler.emit_u8(level + 3 + u8::from(with_done));
compiler.emit_opcode(Opcode::RotateLeft);
compiler.emit_u8(level + 3 + u8::from(with_done));
compiler.emit_opcode(Opcode::RotateLeft);
compiler.emit_u8(level + 1);
}
self.access_set(Access::Property { access }, false, |compiler, _level| {
compiler.emit_opcode(Opcode::IteratorNext);
compiler.emit_opcode(Opcode::IteratorValue);
});
}
// BindingElement : BindingPattern Initializer[opt]
@ -256,8 +222,8 @@ impl ByteCompiler<'_, '_> {
pattern,
default_init,
} => {
self.emit_opcode(Opcode::IteratorNextSetDone);
self.emit_opcode(unwrapping);
self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(Opcode::IteratorValue);
if let Some(init) = default_init {
let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
@ -271,39 +237,16 @@ impl ByteCompiler<'_, '_> {
SingleNameRest { ident } => {
self.emit_opcode(Opcode::IteratorToArray);
self.emit_binding(def, *ident);
if with_done {
self.emit_opcode(Opcode::PushTrue);
}
}
PropertyAccessRest { access } => {
self.access_set(Access::Property { access }, false, |compiler, level| {
if level != 0 {
compiler.emit_opcode(Opcode::RotateLeft);
compiler.emit_u8(level + 2);
compiler.emit_opcode(Opcode::RotateLeft);
compiler.emit_u8(level + 2);
}
self.access_set(Access::Property { access }, false, |compiler, _level| {
compiler.emit_opcode(Opcode::IteratorToArray);
if level != 0 {
compiler.emit_opcode(Opcode::RotateLeft);
compiler.emit_u8(level + 3);
compiler.emit_opcode(Opcode::RotateLeft);
compiler.emit_u8(level + 3);
compiler.emit_opcode(Opcode::RotateLeft);
compiler.emit_u8(level + 1);
}
});
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);
}
}
}
}

10
boa_engine/src/bytecompiler/declarations.rs

@ -272,6 +272,9 @@ impl ByteCompiler<'_, '_> {
self.context,
);
// Ensures global functions are printed when generating the global flowgraph.
self.functions.push(code.clone());
// b. Let fo be InstantiateFunctionObject of f with arguments env and privateEnv.
let function = if generator {
create_generator_function_object(code, r#async, None, self.context)
@ -686,6 +689,9 @@ impl ByteCompiler<'_, '_> {
// c. If varEnv is a Global Environment Record, then
if var_environment_is_global {
// Ensures global functions are printed when generating the global flowgraph.
self.functions.push(code.clone());
// b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv.
let function = if generator {
create_generator_function_object(code, r#async, None, self.context)
@ -988,7 +994,9 @@ impl ByteCompiler<'_, '_> {
}
if generator {
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Yield);
// Don't need to use `AsyncGeneratorYield` since
// we just want to stop the execution of the generator.
self.emit_opcode(Opcode::GeneratorYield);
}
// 27. If hasParameterExpressions is false, then

68
boa_engine/src/bytecompiler/expression/mod.rs

@ -4,7 +4,7 @@ mod object_literal;
mod unary;
mod update;
use super::{Access, Callable, Label, NodeKind};
use super::{Access, Callable, NodeKind};
use crate::{
bytecompiler::{ByteCompiler, Literal},
vm::Opcode,
@ -161,64 +161,48 @@ impl ByteCompiler<'_, '_> {
self.emit_opcode(Opcode::PushUndefined);
}
if r#yield.delegate() && self.in_async_generator {
if r#yield.delegate() {
if self.in_async_generator {
self.emit_opcode(Opcode::GetAsyncIterator);
self.emit_opcode(Opcode::PushUndefined);
} else {
self.emit_opcode(Opcode::GetIterator);
}
self.emit_opcode(Opcode::PushUndefined);
let start_address = self.next_opcode_location();
let (throw_method_undefined, return_method_undefined) =
self.emit_opcode_with_two_operands(Opcode::GeneratorAsyncDelegateNext);
self.emit_opcode(Opcode::Await);
self.emit_opcode_with_two_operands(Opcode::GeneratorDelegateNext);
let skip_yield = Label {
index: self.next_opcode_location(),
};
let exit = Label {
index: self.next_opcode_location() + 8,
};
self.emit(
Opcode::GeneratorAsyncDelegateResume,
&[Self::DUMMY_ADDRESS, start_address, Self::DUMMY_ADDRESS],
);
if self.in_async_generator {
self.emit_opcode(Opcode::Await);
}
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Yield);
let (return_gen, exit) =
self.emit_opcode_with_two_operands(Opcode::GeneratorDelegateResume);
if self.in_async_generator {
self.emit_opcode(Opcode::IteratorValue);
self.async_generator_yield();
} else {
self.emit_opcode(Opcode::IteratorResult);
self.emit_opcode(Opcode::GeneratorYield);
}
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(skip_yield);
self.patch_jump(return_gen);
self.patch_jump(return_method_undefined);
if self.in_async_generator {
self.emit_opcode(Opcode::Await);
}
self.close_active_iterators();
self.emit_opcode(Opcode::GeneratorResumeReturn);
self.patch_jump(throw_method_undefined);
self.iterator_close(true);
self.iterator_close(self.in_async_generator);
self.emit_opcode(Opcode::Throw);
self.patch_jump(exit);
} else if r#yield.delegate() {
self.emit_opcode(Opcode::GetIterator);
self.emit_opcode(Opcode::PushUndefined);
let start_address = self.next_opcode_location();
let start = self.emit_opcode_with_operand(Opcode::GeneratorNextDelegate);
self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(start);
} else if self.in_async_generator {
self.emit_opcode(Opcode::Await);
let (skip_yield, skip_yield_await) =
self.emit_opcode_with_two_operands(Opcode::AsyncGeneratorNext);
self.emit_opcode(Opcode::PushUndefined);
self.emit_opcode(Opcode::Yield);
let normal_completion =
self.emit_opcode_with_operand(Opcode::GeneratorAsyncResumeYield);
self.patch_jump(skip_yield);
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorResumeReturn);
self.patch_jump(skip_yield_await);
self.patch_jump(normal_completion);
} else {
self.emit_opcode(Opcode::Yield);
self.emit_opcode(Opcode::GeneratorNext);
self.r#yield();
}
if !use_expr {

39
boa_engine/src/bytecompiler/jump_control.rs

@ -27,7 +27,7 @@ pub(crate) struct JumpControlInfo {
bitflags! {
/// A bitflag that contains the type flags and relevant booleans for `JumpControlInfo`.
#[derive(Debug, Clone, Copy)]
pub(crate) struct JumpControlInfoFlags: u8 {
pub(crate) struct JumpControlInfoFlags: u16 {
const LOOP = 0b0000_0001;
const SWITCH = 0b0000_0010;
const TRY_BLOCK = 0b0000_0100;
@ -35,7 +35,8 @@ bitflags! {
const IN_CATCH = 0b0001_0000;
const IN_FINALLY = 0b0010_0000;
const HAS_FINALLY = 0b0100_0000;
const FOR_OF_IN_LOOP = 0b1000_0000;
const ITERATOR_LOOP = 0b1000_0000;
const FOR_AWAIT_OF_LOOP = 0b1_0000_0000;
}
}
@ -95,8 +96,14 @@ impl JumpControlInfo {
self
}
pub(crate) fn with_for_of_in_loop(mut self, value: bool) -> Self {
self.flags.set(JumpControlInfoFlags::FOR_OF_IN_LOOP, value);
pub(crate) fn with_iterator_loop(mut self, value: bool) -> Self {
self.flags.set(JumpControlInfoFlags::ITERATOR_LOOP, value);
self
}
pub(crate) fn with_for_await_of_loop(mut self, value: bool) -> Self {
self.flags
.set(JumpControlInfoFlags::FOR_AWAIT_OF_LOOP, value);
self
}
}
@ -139,8 +146,12 @@ impl JumpControlInfo {
self.flags.contains(JumpControlInfoFlags::HAS_FINALLY)
}
pub(crate) const fn for_of_in_loop(&self) -> bool {
self.flags.contains(JumpControlInfoFlags::FOR_OF_IN_LOOP)
pub(crate) const fn iterator_loop(&self) -> bool {
self.flags.contains(JumpControlInfoFlags::ITERATOR_LOOP)
}
pub(crate) const fn for_await_of_loop(&self) -> bool {
self.flags.contains(JumpControlInfoFlags::FOR_AWAIT_OF_LOOP)
}
}
@ -274,7 +285,21 @@ impl ByteCompiler<'_, '_> {
.with_loop_flag(true)
.with_label(label)
.with_start_address(start_address)
.with_for_of_in_loop(true);
.with_iterator_loop(true);
self.jump_info.push(new_info);
}
pub(crate) fn push_loop_control_info_for_await_of_loop(
&mut self,
label: Option<Sym>,
start_address: u32,
) {
let new_info = JumpControlInfo::default()
.with_loop_flag(true)
.with_label(label)
.with_start_address(start_address)
.with_iterator_loop(true)
.with_for_await_of_loop(true);
self.jump_info.push(new_info);
}

26
boa_engine/src/bytecompiler/mod.rs

@ -495,10 +495,6 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
self.emit_opcode_with_operand(Opcode::JumpIfFalse)
}
fn jump_if_not_undefined(&mut self) -> Label {
self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined)
}
fn jump_if_null_or_undefined(&mut self) -> Label {
self.emit_opcode_with_operand(Opcode::JumpIfNullOrUndefined)
}
@ -519,6 +515,28 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
(Label { index }, Label { index: index + 4 })
}
/// Emit an opcode with three dummy operands.
/// Return the `Label`s of the three operands.
pub(crate) fn emit_opcode_with_three_operands(
&mut self,
opcode: Opcode,
) -> (Label, Label, Label) {
let index = self.next_opcode_location();
self.emit(
opcode,
&[
Self::DUMMY_ADDRESS,
Self::DUMMY_ADDRESS,
Self::DUMMY_ADDRESS,
],
);
(
Label { index },
Label { index: index + 4 },
Label { index: index + 8 },
)
}
pub(crate) fn patch_jump_with_target(&mut self, label: Label, target: u32) {
const U32_SIZE: usize = std::mem::size_of::<u32>();

25
boa_engine/src/bytecompiler/statement/continue.rs

@ -18,21 +18,20 @@ impl ByteCompiler<'_, '_> {
// 1. Handle if node has a label.
if let Some(node_label) = node.label() {
let items = self.jump_info.iter().rev().filter(|info| info.is_loop());
let mut emit_for_of_in_exit = 0_u32;
let mut iterator_closes = Vec::new();
for info in items {
if info.label() == Some(node_label) {
break;
}
if info.for_of_in_loop() {
emit_for_of_in_exit += 1;
if info.iterator_loop() {
iterator_closes.push(info.for_await_of_loop());
}
}
for _ in 0..emit_for_of_in_exit {
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
for r#async in iterator_closes {
self.iterator_close(r#async);
}
let (cont_label, set_label) = self.emit_opcode_with_two_operands(Opcode::Continue);
@ -83,21 +82,19 @@ impl ByteCompiler<'_, '_> {
};
} else if let Some(node_label) = node.label() {
let items = self.jump_info.iter().rev().filter(|info| info.is_loop());
let mut emit_for_of_in_exit = 0_u32;
let mut iterator_closes = Vec::new();
for info in items {
if info.label() == Some(node_label) {
break;
}
if info.for_of_in_loop() {
emit_for_of_in_exit += 1;
if info.iterator_loop() {
iterator_closes.push(info.for_await_of_loop());
}
}
for _ in 0..emit_for_of_in_exit {
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
for r#async in iterator_closes {
self.iterator_close(r#async);
}
let (cont_label, set_label) = self.emit_opcode_with_two_operands(Opcode::Continue);

35
boa_engine/src/bytecompiler/statement/loop.rs

@ -160,17 +160,19 @@ impl ByteCompiler<'_, '_> {
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 (loop_start, exit_label) =
self.emit_opcode_with_two_operands(Opcode::IteratorLoopStart);
let start_address = self.next_opcode_location();
self.push_loop_control_info_for_of_in_loop(label, start_address);
self.emit_opcode(Opcode::LoopContinue);
self.patch_jump_with_target(loop_start, start_address);
self.emit_opcode(Opcode::Pop); // pop the `done` value.
self.emit_opcode(Opcode::IteratorNext);
let exit = self.emit_opcode_with_operand(Opcode::IteratorUnwrapNextOrJump);
self.emit_opcode(Opcode::IteratorDone);
let exit = self.jump_if_true();
self.emit_opcode(Opcode::IteratorValue);
let iteration_environment = if initializer_bound_names.is_empty() {
None
@ -245,11 +247,8 @@ impl ByteCompiler<'_, '_> {
self.patch_jump(exit_label);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
self.emit_opcode(Opcode::RotateRight);
self.emit_u8(4);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
self.iterator_close(false);
let skip_early_exit = self.jump();
self.patch_jump(early_exit);
@ -293,21 +292,27 @@ impl ByteCompiler<'_, '_> {
} else {
self.emit_opcode(Opcode::GetIterator);
}
self.emit_opcode(Opcode::PushFalse);
let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::LoopStart);
let (loop_start, loop_exit) = self.emit_opcode_with_two_operands(Opcode::IteratorLoopStart);
let start_address = self.next_opcode_location();
if for_of_loop.r#await() {
self.push_loop_control_info_for_await_of_loop(label, start_address);
} else {
self.push_loop_control_info_for_of_in_loop(label, start_address);
}
self.emit_opcode(Opcode::LoopContinue);
self.patch_jump_with_target(loop_start, start_address);
self.emit_opcode(Opcode::Pop); // pop the `done` value.
self.emit_opcode(Opcode::IteratorNext);
if for_of_loop.r#await() {
self.emit_opcode(Opcode::IteratorResult);
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::IteratorFinishAsyncNext);
self.emit_opcode(Opcode::GeneratorNext);
}
let exit = self.emit_opcode_with_operand(Opcode::IteratorUnwrapNextOrJump);
self.emit_opcode(Opcode::IteratorDone);
let exit = self.jump_if_true();
self.emit_opcode(Opcode::IteratorValue);
let iteration_environment = if initializer_bound_names.is_empty() {
None
@ -388,9 +393,9 @@ impl ByteCompiler<'_, '_> {
self.patch_jump(loop_exit);
self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd);
self.emit_opcode(Opcode::RotateRight);
self.emit_u8(4);
self.iterator_close(for_of_loop.r#await());
if !use_expr {
self.emit_opcode(Opcode::Pop);
}

4
boa_engine/src/bytecompiler/statement/mod.rs

@ -51,6 +51,10 @@ impl ByteCompiler<'_, '_> {
Statement::Return(ret) => {
if let Some(expr) = ret.target() {
self.compile_expr(expr, true);
if self.in_async_generator {
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext);
}
} else {
self.emit(Opcode::PushUndefined, &[]);
}

93
boa_engine/src/bytecompiler/utils.rs

@ -1,5 +1,3 @@
use boa_interner::Sym;
use crate::{js_string, vm::Opcode};
use super::{ByteCompiler, Literal};
@ -10,36 +8,21 @@ impl ByteCompiler<'_, '_> {
/// This is equivalent to the [`IteratorClose`][iter] and [`AsyncIteratorClose`][async]
/// operations.
///
/// Stack:
/// - iterator, `next_method`, done **=>** \<empty\>
/// Iterator Stack:
/// - iterator **=>** \<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();
self.emit_opcode(Opcode::IteratorDone);
// `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();
let skip_return = self.jump_if_true();
// 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();
self.emit_opcode(Opcode::IteratorReturn);
// `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]);
let early_exit = self.jump_if_false();
if async_ {
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext);
@ -53,7 +36,71 @@ impl ByteCompiler<'_, '_> {
self.emit(Opcode::ThrowNewTypeError, &[error_msg]);
self.patch_jump(skip_return);
self.emit_opcode(Opcode::IteratorPop);
self.patch_jump(skip_throw);
self.patch_jump(early_exit);
}
/// Closes all active iterators in the current [`CallFrame`][crate::vm::CallFrame].
pub(super) fn close_active_iterators(&mut self) {
let start = self.next_opcode_location();
self.emit_opcode(Opcode::IteratorStackEmpty);
let empty = self.jump_if_true();
self.iterator_close(self.in_async_generator);
self.emit(Opcode::Jump, &[start]);
self.patch_jump(empty);
}
/// Yields from the current generator.
///
/// This is equivalent to the [`Yield ( value )`][yield] operation from the spec.
///
/// stack:
/// - value **=>** received
///
/// [yield]: https://tc39.es/ecma262/#sec-yield
pub(super) fn r#yield(&mut self) {
// 1. Let generatorKind be GetGeneratorKind().
if self.in_async_generator {
// 2. If generatorKind is async, return ? AsyncGeneratorYield(? Await(value)).
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext);
self.async_generator_yield();
} else {
// 3. Otherwise, return ? GeneratorYield(CreateIterResultObject(value, false)).
self.emit_opcode(Opcode::CreateIteratorResult);
self.emit_u8(u8::from(false));
self.emit_opcode(Opcode::GeneratorYield);
}
self.emit_opcode(Opcode::GeneratorNext);
}
/// Yields from the current async generator.
///
/// This is equivalent to the [`AsyncGeneratorYield ( value )`][async_yield] operation from the spec.
///
/// stack:
/// - value **=>** received
///
/// [async_yield]: https://tc39.es/ecma262/#sec-asyncgeneratoryield
pub(super) fn async_generator_yield(&mut self) {
self.emit_opcode(Opcode::AsyncGeneratorYield);
let (normal, throw, r#return) =
self.emit_opcode_with_three_operands(Opcode::GeneratorJumpOnResumeKind);
{
self.patch_jump(r#return);
self.emit_opcode(Opcode::Await);
let (normal, throw, r#return) =
self.emit_opcode_with_three_operands(Opcode::GeneratorJumpOnResumeKind);
self.patch_jump(normal);
self.emit_opcode(Opcode::GeneratorSetReturn);
self.patch_jump(throw);
self.patch_jump(r#return);
}
self.patch_jump(normal);
self.patch_jump(throw);
}
}

4
boa_engine/src/job.rs

@ -276,10 +276,6 @@ impl SimpleJobQueue {
impl JobQueue for SimpleJobQueue {
fn enqueue_promise_job(&self, job: NativeJob, _: &mut Context<'_>) {
// If realm is not null ...
// TODO
// Let scriptOrModule be ...
// TODO
self.0.borrow_mut().push_back(job);
}

222
boa_engine/src/tests/iterators.rs

@ -0,0 +1,222 @@
use indoc::indoc;
use crate::{run_test_actions, TestAction};
#[test]
fn iterator_close_in_continue_before_jobs() {
run_test_actions([
TestAction::run_harness(),
TestAction::run(indoc! {r#"
var actual = [];
var iter = {
[Symbol.iterator]() {
return this;
},
next() {
actual.push("call next");
return {
done: false,
};
},
get return() {
actual.push("get return");
return function () {
actual.push("return call");
return {
done: true
}
}
}
};
Promise.resolve(0)
.then(() => actual.push("tick 1"))
.then(() => actual.push("tick 2"));
void async function f() {
actual.push("async fn start");
let count = 0;
loop: while (count === 0) {
count++;
for (_ of iter) {
continue loop;
}
}
actual.push("async fn end");
}();
"#}),
#[allow(clippy::redundant_closure_for_method_calls)]
TestAction::inspect_context(|ctx| ctx.run_jobs()),
TestAction::assert(indoc! {r#"
arrayEquals(
actual,
[
"async fn start",
"call next",
"get return",
"return call",
"async fn end",
"tick 1",
"tick 2",
]
)
"#}),
]);
}
#[test]
fn async_iterator_close_in_continue_is_awaited() {
run_test_actions([
TestAction::run_harness(),
TestAction::run(indoc! {r#"
var actual = [];
var asyncIter = {
[Symbol.asyncIterator]() {
return this;
},
next() {
actual.push("async call next");
return {
done: false,
};
},
get return() {
actual.push("get async return");
return function () {
actual.push("async return call");
return {
done: true
};
}
}
};
Promise.resolve(0)
.then(() => actual.push("tick 1"))
.then(() => actual.push("tick 2"))
.then(() => actual.push("tick 3"));
void async function f() {
actual.push("async fn start");
let count = 0;
loop: while (count === 0) {
count++;
for await (__ of asyncIter) {
continue loop;
}
}
actual.push("async fn end");
}();
"#}),
#[allow(clippy::redundant_closure_for_method_calls)]
TestAction::inspect_context(|ctx| ctx.run_jobs()),
TestAction::assert(indoc! {r#"
arrayEquals(
actual,
[
"async fn start",
"async call next",
"tick 1",
"get async return",
"async return call",
"tick 2",
"async fn end",
"tick 3"
]
)
"#}),
]);
}
#[test]
fn mixed_iterators_close_in_continue() {
run_test_actions([
TestAction::run_harness(),
TestAction::run(indoc! {r#"
var actual = [];
var iter = {
[Symbol.iterator]() {
return this;
},
next() {
actual.push("call next");
return {
done: false,
};
},
get return() {
actual.push("get return");
return function () {
actual.push("return call");
return {
done: true
}
}
}
};
var asyncIter = {
[Symbol.asyncIterator]() {
return this;
},
next() {
actual.push("async call next");
return {
done: false,
};
},
get return() {
actual.push("get async return");
return function () {
actual.push("async return call");
return {
done: true
};
}
}
};
Promise.resolve(0)
.then(() => actual.push("tick 1"))
.then(() => actual.push("tick 2"))
.then(() => actual.push("tick 3"));
void async function f() {
actual.push("async fn start");
let count = 0;
loop: while (count === 0) {
count++;
for (_ of iter) {
for await (__ of asyncIter) {
continue loop;
}
}
}
actual.push("async fn end");
}();
"#}),
#[allow(clippy::redundant_closure_for_method_calls)]
TestAction::inspect_context(|ctx| ctx.run_jobs()),
TestAction::assert(indoc! {r#"
arrayEquals(
actual,
[
"async fn start",
"call next",
"async call next",
"tick 1",
"get async return",
"async return call",
"tick 2",
"get return",
"return call",
"async fn end",
"tick 3",
]
)
"#}),
]);
}

1
boa_engine/src/tests/mod.rs

@ -3,6 +3,7 @@ use indoc::indoc;
mod control_flow;
mod env;
mod function;
mod iterators;
mod operators;
mod promise;
mod spread;

27
boa_engine/src/vm/call_frame/env_stack.rs

@ -7,11 +7,14 @@ use boa_gc::{Finalize, Trace};
pub(crate) enum EnvEntryKind {
Global,
Loop {
/// This is used to keep track of how many iterations a loop has done.
/// How many iterations a loop has done.
iteration_count: u64,
// This is the latest return value of the loop.
/// The latest return value of the loop.
value: JsValue,
/// The index of the currently active iterator.
iterator: Option<u32>,
},
Try,
Catch,
@ -71,12 +74,24 @@ impl EnvStackEntry {
self
}
/// Returns calling `EnvStackEntry` with `kind` field of `Loop`, loop iteration set to zero
/// and iterator index set to `iterator`.
pub(crate) fn with_iterator_loop_flag(mut self, iteration_count: u64, iterator: u32) -> Self {
self.kind = EnvEntryKind::Loop {
iteration_count,
value: JsValue::undefined(),
iterator: Some(iterator),
};
self
}
/// Returns calling `EnvStackEntry` with `kind` field of `Loop`.
/// And the loop iteration set to zero.
pub(crate) fn with_loop_flag(mut self, iteration_count: u64) -> Self {
self.kind = EnvEntryKind::Loop {
iteration_count,
value: JsValue::undefined(),
iterator: None,
};
self
}
@ -150,6 +165,14 @@ impl EnvStackEntry {
None
}
/// Returns the active iterator index if `EnvStackEntry` is an iterator loop.
pub(crate) const fn iterator(&self) -> Option<u32> {
if let EnvEntryKind::Loop { iterator, .. } = self.kind {
return iterator;
}
None
}
/// Returns true if an `EnvStackEntry` is a try block
pub(crate) fn is_try_env(&self) -> bool {
self.kind == EnvEntryKind::Try

9
boa_engine/src/vm/call_frame/mod.rs

@ -6,7 +6,9 @@ mod abrupt_record;
mod env_stack;
use crate::{
builtins::promise::PromiseCapability, environments::BindingLocator, object::JsObject,
builtins::{iterable::IteratorRecord, promise::PromiseCapability},
environments::BindingLocator,
object::JsObject,
vm::CodeBlock,
};
use boa_gc::{Finalize, Gc, Trace};
@ -39,7 +41,7 @@ pub struct CallFrame {
pub(crate) async_generator: Option<JsObject>,
// Iterators and their `[[Done]]` flags that must be closed when an abrupt completion is thrown.
pub(crate) iterators: ThinVec<(JsObject, bool)>,
pub(crate) iterators: ThinVec<IteratorRecord>,
// The stack of bindings being updated.
pub(crate) binding_stack: Vec<BindingLocator>,
@ -110,8 +112,9 @@ impl CallFrame {
}
/// Indicates how a generator function that has been called/resumed should return.
#[derive(Copy, Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq, Default)]
pub(crate) enum GeneratorResumeKind {
#[default]
Normal,
Throw,
Return,

55
boa_engine/src/vm/code_block.rs

@ -317,10 +317,7 @@ impl CodeBlock {
| Opcode::Call
| Opcode::New
| Opcode::SuperCall
| Opcode::IteratorUnwrapNextOrJump
| Opcode::ConcatToString
| Opcode::GeneratorAsyncResumeYield
| Opcode::GeneratorNextDelegate => {
| Opcode::ConcatToString => {
let result = self.read::<u32>(*pc).to_string();
*pc += size_of::<u32>();
result
@ -334,9 +331,10 @@ impl CodeBlock {
| Opcode::Break
| Opcode::Continue
| Opcode::LoopStart
| Opcode::IteratorLoopStart
| Opcode::TryStart
| Opcode::AsyncGeneratorNext
| Opcode::GeneratorAsyncDelegateNext => {
| Opcode::GeneratorDelegateNext
| Opcode::GeneratorDelegateResume => {
let operand1 = self.read::<u32>(*pc);
*pc += size_of::<u32>();
let operand2 = self.read::<u32>(*pc);
@ -350,15 +348,6 @@ impl CodeBlock {
*pc += size_of::<u64>();
format!("{operand1}, {operand2}")
}
Opcode::GeneratorAsyncDelegateResume => {
let operand1 = self.read::<u32>(*pc);
*pc += size_of::<u32>();
let operand2 = self.read::<u32>(*pc);
*pc += size_of::<u32>();
let operand3 = self.read::<u32>(*pc);
*pc += size_of::<u32>();
format!("{operand1}, {operand2}, {operand3}")
}
Opcode::GetArrowFunction
| Opcode::GetAsyncArrowFunction
| Opcode::GetFunction
@ -373,6 +362,7 @@ impl CodeBlock {
}
Opcode::GetGenerator | Opcode::GetGeneratorAsync => {
let operand = self.read::<u32>(*pc);
*pc += size_of::<u32>();
format!(
"{operand:04}: '{}' (length: {})",
interner.resolve_expect(self.functions[operand as usize].name),
@ -439,6 +429,20 @@ impl CodeBlock {
*pc += size_of::<u32>() * (count as usize + 1);
String::new()
}
Opcode::GeneratorJumpOnResumeKind => {
let normal = self.read::<u32>(*pc);
*pc += size_of::<u32>();
let throw = self.read::<u32>(*pc);
*pc += size_of::<u32>();
let r#return = self.read::<u32>(*pc);
*pc += size_of::<u32>();
format!("n: {normal}, t: {throw}, r: {return}")
}
Opcode::CreateIteratorResult => {
let done = self.read::<u8>(*pc) != 0;
*pc += size_of::<u8>();
format!("done: {done}")
}
Opcode::Pop
| Opcode::PopIfThrown
| Opcode::Dup
@ -522,12 +526,14 @@ impl CodeBlock {
| Opcode::GetAsyncIterator
| Opcode::GeneratorResumeReturn
| Opcode::IteratorNext
| Opcode::IteratorNextSetDone
| Opcode::IteratorUnwrapNext
| Opcode::IteratorUnwrapValue
| Opcode::IteratorFinishAsyncNext
| Opcode::IteratorValue
| Opcode::IteratorResult
| Opcode::IteratorDone
| Opcode::IteratorToArray
| Opcode::IteratorClosePush
| Opcode::IteratorClosePop
| Opcode::IteratorPop
| Opcode::IteratorReturn
| Opcode::IteratorStackEmpty
| Opcode::RequireObjectCoercible
| Opcode::ValueNotNullOrUndefined
| Opcode::RestParameterInit
@ -538,8 +544,10 @@ impl CodeBlock {
| Opcode::PushNewArray
| Opcode::PopOnReturnAdd
| Opcode::PopOnReturnSub
| Opcode::Yield
| Opcode::GeneratorYield
| Opcode::AsyncGeneratorYield
| Opcode::GeneratorNext
| Opcode::GeneratorSetReturn
| Opcode::PushClassField
| Opcode::SuperCallDerived
| Opcode::Await
@ -609,10 +617,7 @@ impl CodeBlock {
| Opcode::Reserved50
| Opcode::Reserved51
| Opcode::Reserved52
| Opcode::Reserved53
| Opcode::Reserved54
| Opcode::Reserved55
| Opcode::Reserved56 => unreachable!("Reserved opcodes are unrechable"),
| Opcode::Reserved53 => unreachable!("Reserved opcodes are unrechable"),
}
}
}

16
boa_engine/src/vm/completion_record.rs

@ -1,18 +1,32 @@
//! An implementation of a `CompletionRecord` for Boa's VM.
use boa_gc::{custom_trace, Finalize, Trace};
use crate::{JsError, JsResult, JsValue};
/// An implementation of the ECMAScript's `CompletionRecord` [specification] for
/// Boa's VM output Completion and Result.
///
/// [specification]: https://tc39.es/ecma262/#sec-completion-record-specification-type
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Finalize)]
pub(crate) enum CompletionRecord {
Normal(JsValue),
Return(JsValue),
Throw(JsError),
}
// SAFETY: this matches all possible variants and traces
// their inner contents, which makes this safe.
unsafe impl Trace for CompletionRecord {
custom_trace!(this, {
match this {
Self::Normal(v) => mark(v),
Self::Return(r) => mark(r),
Self::Throw(th) => mark(th),
}
});
}
// ---- `CompletionRecord` methods ----
impl CompletionRecord {
pub(crate) const fn is_throw_completion(&self) -> bool {

45
boa_engine/src/vm/flowgraph/graph.rs

@ -1,3 +1,8 @@
use std::{
collections::hash_map::RandomState,
hash::{BuildHasher, Hash, Hasher},
};
use crate::vm::flowgraph::{Color, Edge, EdgeStyle, EdgeType, Node, NodeShape};
/// This represents the direction of flow in the flowgraph.
@ -95,7 +100,10 @@ impl SubGraph {
/// Format into the graphviz format.
fn graphviz_format(&self, result: &mut String, prefix: &str) {
result.push_str(&format!("\tsubgraph cluster_{prefix}_{} {{\n", self.label));
let mut hasher = RandomState::new().build_hasher();
self.label.hash(&mut hasher);
let label = format!("{}", hasher.finish());
result.push_str(&format!("\tsubgraph cluster_{prefix}_{label} {{\n"));
result.push_str("\t\tstyle = filled;\n");
result.push_str(&format!(
"\t\tlabel = \"{}\";\n",
@ -107,13 +115,11 @@ impl SubGraph {
));
result.push_str(&format!(
"\t\t{prefix}_{}_start [label=\"Start\",shape=Mdiamond,style=filled,color=green]\n",
self.label
"\t\t{prefix}_{label}_start [label=\"Start\",shape=Mdiamond,style=filled,color=green]\n"
));
if !self.nodes.is_empty() {
result.push_str(&format!(
"\t\t{prefix}_{}_start -> {prefix}_{}_i_0\n",
self.label, self.label
"\t\t{prefix}_{label}_start -> {prefix}_{label}_i_0\n"
));
}
@ -126,7 +132,7 @@ impl SubGraph {
let color = format!(",style=filled,color=\"{}\"", node.color);
result.push_str(&format!(
"\t\t{prefix}_{}_i_{}[label=\"{:04}: {}\"{shape}{color}];\n",
self.label, node.location, node.location, node.label
label, node.location, node.location, node.label
));
}
@ -142,9 +148,9 @@ impl SubGraph {
};
result.push_str(&format!(
"\t\t{prefix}_{}_i_{} -> {prefix}_{}_i_{} [label=\"{}\", len=f{style}{color}];\n",
self.label,
label,
edge.from,
self.label,
label,
edge.to,
edge.label.as_deref().unwrap_or("")
));
@ -158,6 +164,9 @@ impl SubGraph {
/// Format into the mermaid format.
fn mermaid_format(&self, result: &mut String, prefix: &str) {
let mut hasher = RandomState::new().build_hasher();
self.label.hash(&mut hasher);
let label = format!("{}", hasher.finish());
let rankdir = match self.direction {
Direction::TopToBottom => "TB",
Direction::BottomToTop => "BT",
@ -166,7 +175,7 @@ impl SubGraph {
};
result.push_str(&format!(
" subgraph {prefix}_{}[\"{}\"]\n",
self.label,
label,
if self.label.is_empty() {
"Anonymous Function"
} else {
@ -175,15 +184,11 @@ impl SubGraph {
));
result.push_str(&format!(" direction {rankdir}\n"));
result.push_str(&format!(" {prefix}_{}_start{{Start}}\n", self.label));
result.push_str(&format!(
" style {prefix}_{}_start fill:green\n",
self.label
));
result.push_str(&format!(" {prefix}_{label}_start{{Start}}\n"));
result.push_str(&format!(" style {prefix}_{label}_start fill:green\n"));
if !self.nodes.is_empty() {
result.push_str(&format!(
" {prefix}_{}_start --> {prefix}_{}_i_0\n",
self.label, self.label
" {prefix}_{label}_start --> {prefix}_{label}_i_0\n"
));
}
@ -194,12 +199,12 @@ impl SubGraph {
};
result.push_str(&format!(
" {prefix}_{}_i_{}{shape_begin}\"{:04}: {}\"{shape_end}\n",
self.label, node.location, node.location, node.label
label, node.location, node.location, node.label
));
if !node.color.is_none() {
result.push_str(&format!(
" style {prefix}_{}_i_{} fill:{}\n",
self.label, node.location, node.color
label, node.location, node.color
));
}
}
@ -213,10 +218,10 @@ impl SubGraph {
};
result.push_str(&format!(
" {prefix}_{}_i_{} {style}| {}| {prefix}_{}_i_{}\n",
self.label,
label,
edge.from,
edge.label.as_deref().unwrap_or(""),
self.label,
label,
edge.to,
));

143
boa_engine/src/vm/flowgraph/mod.rs

@ -59,7 +59,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::RotateLeft | Opcode::RotateRight => {
Opcode::RotateLeft | Opcode::RotateRight | Opcode::CreateIteratorResult => {
pc += size_of::<u8>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
@ -138,13 +138,9 @@ impl CodeBlock {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
Opcode::LoopStart => {
let start_address = self.read::<u32>(pc);
pc += size_of::<u32>();
let end_address = self.read::<u32>(pc);
pc += size_of::<u32>();
Opcode::LoopStart | Opcode::IteratorLoopStart => {
pc += size_of::<u32>() * 2;
let label = format!("{opcode_str} {start_address}, {end_address}");
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
@ -228,51 +224,57 @@ 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::IteratorUnwrapNextOrJump
| Opcode::GeneratorAsyncResumeYield
| Opcode::GeneratorNextDelegate => {
let address = self.read::<u32>(pc) as usize;
Opcode::GeneratorDelegateNext => {
let throw_method_undefined = 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(
let return_method_undefined = self.read::<u32>(pc) as usize;
pc += size_of::<u32>();
graph.add_node(
previous_pc,
address,
Some("DONE".into()),
NodeShape::Diamond,
opcode_str.into(),
Color::None,
EdgeStyle::Line,
);
}
Opcode::GeneratorAsyncDelegateNext => {
let throw_method_undefined = self.read::<u32>(pc) as usize;
let return_method_undefined = self.read::<u32>(pc) as usize;
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,
throw_method_undefined,
Some("throw method undefined".into()),
Color::None,
Some("`throw` undefined".into()),
Color::Red,
EdgeStyle::Line,
);
graph.add_edge(
previous_pc,
return_method_undefined,
Some("return method undefined".into()),
Color::None,
Some("`return` undefined".into()),
Color::Blue,
EdgeStyle::Line,
);
}
Opcode::GeneratorAsyncDelegateResume => {
self.read::<u32>(pc);
self.read::<u32>(pc);
Opcode::GeneratorDelegateResume => {
let return_gen = self.read::<u32>(pc) as usize;
pc += size_of::<u32>();
let exit = self.read::<u32>(pc) as usize;
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_node(
previous_pc,
NodeShape::Diamond,
opcode_str.into(),
Color::None,
);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
graph.add_edge(
previous_pc,
return_gen,
Some("return".into()),
Color::Yellow,
EdgeStyle::Line,
);
graph.add_edge(
previous_pc,
exit,
Some("DONE".into()),
Color::None,
Some("done".into()),
Color::Blue,
EdgeStyle::Line,
);
}
@ -281,31 +283,45 @@ impl CodeBlock {
| Opcode::Call
| Opcode::New
| Opcode::SuperCall
| Opcode::ConcatToString => {
| Opcode::ConcatToString
| Opcode::FinallyStart => {
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);
}
Opcode::AsyncGeneratorNext => {
let skip_yield = self.read::<u32>(pc);
Opcode::GeneratorJumpOnResumeKind => {
let normal = self.read::<u32>(pc);
pc += size_of::<u32>();
let throw = self.read::<u32>(pc);
pc += size_of::<u32>();
let skip_yield_await = self.read::<u32>(pc);
let r#return = self.read::<u32>(pc);
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, opcode_str.into(), Color::None);
graph.add_node(
previous_pc,
NodeShape::Diamond,
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()),
normal as usize,
None,
Color::None,
EdgeStyle::Line,
);
graph.add_edge(
previous_pc,
skip_yield_await as usize,
Some("yield value pending".into()),
Color::None,
throw as usize,
Some("throw".into()),
Color::Red,
EdgeStyle::Line,
);
graph.add_edge(
previous_pc,
r#return as usize,
Some("return".into()),
Color::Yellow,
EdgeStyle::Line,
);
}
@ -465,7 +481,22 @@ 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::ThrowNewTypeError => {
Opcode::ThrowNewTypeError => {
pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
if let Some((_try_pc, next, _finally)) = try_entries.last() {
graph.add_edge(
previous_pc,
*next as usize,
Some("CAUGHT".into()),
Color::None,
EdgeStyle::Line,
);
} else {
returns.push(previous_pc);
}
}
Opcode::Throw => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
if let Some((_try_pc, next, _finally)) = try_entries.last() {
graph.add_edge(
@ -475,6 +506,8 @@ impl CodeBlock {
Color::None,
EdgeStyle::Line,
);
} else {
returns.push(previous_pc);
}
}
Opcode::PushPrivateEnvironment => {
@ -550,7 +583,6 @@ impl CodeBlock {
| Opcode::ToBoolean
| Opcode::CatchEnd
| Opcode::CatchEnd2
| Opcode::FinallyStart
| Opcode::FinallyEnd
| Opcode::This
| Opcode::Super
@ -562,12 +594,14 @@ impl CodeBlock {
| Opcode::GetIterator
| Opcode::GetAsyncIterator
| Opcode::IteratorNext
| Opcode::IteratorNextSetDone
| Opcode::IteratorUnwrapNext
| Opcode::IteratorUnwrapValue
| Opcode::IteratorFinishAsyncNext
| Opcode::IteratorValue
| Opcode::IteratorResult
| Opcode::IteratorDone
| Opcode::IteratorToArray
| Opcode::IteratorClosePush
| Opcode::IteratorClosePop
| Opcode::IteratorPop
| Opcode::IteratorReturn
| Opcode::IteratorStackEmpty
| Opcode::RequireObjectCoercible
| Opcode::ValueNotNullOrUndefined
| Opcode::RestParameterInit
@ -578,7 +612,8 @@ impl CodeBlock {
| Opcode::PushNewArray
| Opcode::PopOnReturnAdd
| Opcode::PopOnReturnSub
| Opcode::Yield
| Opcode::GeneratorYield
| Opcode::AsyncGeneratorYield
| Opcode::GeneratorNext
| Opcode::PushClassField
| Opcode::SuperCallDerived
@ -596,6 +631,7 @@ impl CodeBlock {
| Opcode::PushObjectEnvironment
| Opcode::PopPrivateEnvironment
| Opcode::ImportCall
| Opcode::GeneratorSetReturn
| Opcode::Nop => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
@ -674,10 +710,7 @@ impl CodeBlock {
| Opcode::Reserved50
| Opcode::Reserved51
| Opcode::Reserved52
| Opcode::Reserved53
| Opcode::Reserved54
| Opcode::Reserved55
| Opcode::Reserved56 => unreachable!("Reserved opcodes are unrechable"),
| Opcode::Reserved53 => unreachable!("Reserved opcodes are unrechable"),
}
}

6
boa_engine/src/vm/mod.rs

@ -259,8 +259,8 @@ impl Context<'_> {
let instant = Instant::now();
let result = self.execute_instruction();
let duration = instant.elapsed();
let duration = instant.elapsed();
println!(
"{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {operands:<OPERAND_COLUMN_WIDTH$} {}",
format!("{}μs", duration.as_micros()),
@ -270,7 +270,7 @@ impl Context<'_> {
Some(value) if value.is_object() => "[object]".to_string(),
Some(value) => value.display().to_string(),
None => "<empty>".to_string(),
}
},
);
result
@ -330,8 +330,8 @@ impl Context<'_> {
// Early return immediately after loop.
if self.vm.frame().r#yield {
let result = self.vm.pop();
self.vm.frame_mut().r#yield = false;
let result = self.vm.pop();
return CompletionRecord::Return(result);
}

85
boa_engine/src/vm/opcode/control_flow/throw.rs

@ -1,9 +1,11 @@
use crate::{
js_string,
vm::{call_frame::AbruptCompletionRecord, opcode::Operation, CompletionType},
Context, JsError, JsNativeError, JsResult,
vm::{
call_frame::{AbruptCompletionRecord, EnvStackEntry},
opcode::Operation,
CompletionType,
},
Context, JsError, JsNativeError, JsResult, JsValue,
};
use thin_vec::ThinVec;
/// `Throw` implements the Opcode Operation for `Opcode::Throw`
///
@ -23,30 +25,34 @@ impl Operation for Throw {
JsError::from_opaque(context.vm.pop())
};
// Close all iterators that are still open.
let mut iterators = ThinVec::new();
std::mem::swap(&mut iterators, &mut context.vm.frame_mut().iterators);
for (iterator, done) in iterators {
if done {
continue;
}
if let Ok(Some(f)) = iterator.get_method(js_string!("return"), context) {
drop(f.call(&iterator.into(), &[], context));
}
}
context.vm.err.take();
// 1. Find the viable catch and finally blocks
let current_address = context.vm.frame().pc;
let viable_catch_candidates = context
.vm
.frame()
.env_stack
let mut envs = context.vm.frame().env_stack.iter();
if let Some(idx) =
envs.rposition(|env| env.is_try_env() && env.start_address() < env.exit_address())
{
let active_iterator = context.vm.frame().env_stack[..idx]
.iter()
.filter(|env| env.is_try_env() && env.start_address() < env.exit_address());
.filter_map(EnvStackEntry::iterator)
.last();
// Close all iterators that are outside the catch context.
if let Some(active_iterator) = active_iterator {
let inactive = context
.vm
.frame_mut()
.iterators
.split_off(active_iterator as usize + 1);
for iterator in inactive {
if !iterator.done() {
drop(iterator.close(Ok(JsValue::undefined()), context));
}
}
context.vm.err.take();
}
if let Some(candidate) = viable_catch_candidates.last() {
let catch_target = candidate.start_address();
let catch_target = context.vm.frame().env_stack[idx].start_address();
let mut env_to_pop = 0;
let mut target_address = u32::MAX;
@ -126,6 +132,29 @@ impl Operation for Throw {
context.vm.frame_mut().env_stack.pop();
}
let active_iterator = context
.vm
.frame()
.env_stack
.iter()
.filter_map(EnvStackEntry::iterator)
.last();
// Close all iterators that are outside the finally context.
if let Some(active_iterator) = active_iterator {
let inactive = context
.vm
.frame_mut()
.iterators
.split_off(active_iterator as usize + 1);
for iterator in inactive {
if !iterator.done() {
drop(iterator.close(Ok(JsValue::undefined()), context));
}
}
context.vm.err.take();
}
let env_truncation_len = context.vm.environments.len().saturating_sub(env_to_pop);
context.vm.environments.truncate(env_truncation_len);
@ -143,6 +172,14 @@ impl Operation for Throw {
return Ok(CompletionType::Normal);
}
// Close all iterators that are still open.
for iterator in std::mem::take(&mut context.vm.frame_mut().iterators) {
if !iterator.done() {
drop(iterator.close(Ok(JsValue::undefined()), context));
}
}
context.vm.err.take();
context.vm.err = Some(error);
Ok(CompletionType::Throw)
}

376
boa_engine/src/vm/opcode/generator/mod.rs

@ -1,18 +1,14 @@
pub(crate) mod yield_stm;
use crate::{
builtins::{
async_generator::{AsyncGenerator, AsyncGeneratorState},
iterable::IteratorRecord,
},
error::JsNativeError,
string::utf16,
vm::{
call_frame::{AbruptCompletionRecord, GeneratorResumeKind},
call_frame::GeneratorResumeKind,
opcode::{control_flow::Return, Operation},
CompletionType,
},
Context, JsError, JsResult, JsValue,
Context, JsError, JsResult,
};
pub(crate) use yield_stm::*;
@ -32,116 +28,51 @@ impl Operation for GeneratorNext {
match context.vm.frame().generator_resume_kind {
GeneratorResumeKind::Normal => Ok(CompletionType::Normal),
GeneratorResumeKind::Throw => Err(JsError::from_opaque(context.vm.pop())),
GeneratorResumeKind::Return => {
let finally_entries = context
.vm
.frame()
.env_stack
.iter()
.filter(|entry| entry.is_finally_env());
if let Some(next_finally) = finally_entries.rev().next() {
if context.vm.frame().pc < next_finally.start_address() {
context.vm.frame_mut().pc = next_finally.start_address();
let return_record = AbruptCompletionRecord::new_return();
context.vm.frame_mut().abrupt_completion = Some(return_record);
return Ok(CompletionType::Normal);
}
}
let return_record = AbruptCompletionRecord::new_return();
context.vm.frame_mut().abrupt_completion = Some(return_record);
Ok(CompletionType::Return)
}
GeneratorResumeKind::Return => Return::execute(context),
}
}
}
/// `AsyncGeneratorNext` implements the Opcode Operation for `Opcode::AsyncGeneratorNext`
/// `GeneratorJumpOnResumeKind` implements the Opcode Operation for
/// `Opcode::GeneratorJumpOnResumeKind`
///
/// Operation:
/// - Resumes the current generator function.
/// - Jumps to the specified instruction if the current resume kind is `Return`.
#[derive(Debug, Clone, Copy)]
pub(crate) struct AsyncGeneratorNext;
pub(crate) struct GeneratorJumpOnResumeKind;
impl Operation for AsyncGeneratorNext {
const NAME: &'static str = "AsyncGeneratorNext";
const INSTRUCTION: &'static str = "INST - AsyncGeneratorNext";
impl Operation for GeneratorJumpOnResumeKind {
const NAME: &'static str = "GeneratorJumpOnResumeKind";
const INSTRUCTION: &'static str = "INST - GeneratorJumpOnResumeKind";
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()));
}
let value = context.vm.pop();
let completion = Ok(value);
let generator_object = context
.vm
.frame()
.async_generator
.as_ref()
.expect("must be in generator context here")
.clone();
let next = generator_object
.borrow_mut()
.as_async_generator_mut()
.expect("must be async generator object")
.queue
.pop_front()
.expect("must have item in queue");
AsyncGenerator::complete_step(&next, completion, false, None, context);
let mut generator_object_mut = generator_object.borrow_mut();
let gen = generator_object_mut
.as_async_generator_mut()
.expect("must be async generator object");
if let Some(next) = gen.queue.front() {
let (completion, r#return) = &next.completion;
if *r#return {
let value = match completion {
Ok(value) => value.clone(),
Err(e) => e.clone().to_opaque(context),
};
context.vm.push(value);
context.vm.frame_mut().pc = skip_yield;
} else {
context.vm.push(completion.clone()?);
context.vm.frame_mut().pc = skip_yield_await;
}
} else {
gen.state = AsyncGeneratorState::SuspendedYield;
let normal = context.vm.read::<u32>();
let throw = context.vm.read::<u32>();
let r#return = context.vm.read::<u32>();
match context.vm.frame().generator_resume_kind {
GeneratorResumeKind::Normal => context.vm.frame_mut().pc = normal,
GeneratorResumeKind::Throw => context.vm.frame_mut().pc = throw,
GeneratorResumeKind::Return => context.vm.frame_mut().pc = r#return,
}
Ok(CompletionType::Normal)
}
}
/// `GeneratorAsyncResumeYield` implements the Opcode Operation for `Opcode::GeneratorAsyncResumeYield`
/// `GeneratorSetReturn` implements the Opcode Operation for `Opcode::GeneratorSetReturn`
///
/// Operation:
/// - Resumes the current async generator function after a yield.
/// - Sets the current generator resume kind to `Return`.
#[derive(Debug, Clone, Copy)]
pub(crate) struct GeneratorAsyncResumeYield;
pub(crate) struct GeneratorSetReturn;
impl Operation for GeneratorAsyncResumeYield {
const NAME: &'static str = "GeneratorAsyncResumeYield";
const INSTRUCTION: &'static str = "INST - GeneratorAsyncResumeYield";
impl Operation for GeneratorSetReturn {
const NAME: &'static str = "GeneratorSetReturn";
const INSTRUCTION: &'static str = "INST - GeneratorSetReturn";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let normal_completion = context.vm.read::<u32>();
match context.vm.frame().generator_resume_kind {
GeneratorResumeKind::Normal => {
context.vm.frame_mut().pc = normal_completion;
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Return;
Ok(CompletionType::Normal)
}
GeneratorResumeKind::Throw => Err(JsError::from_opaque(context.vm.pop())),
GeneratorResumeKind::Return => Ok(CompletionType::Normal),
}
}
}
/// `GeneratorResumeReturn` implements the Opcode Operation for `Opcode::GeneratorResumeReturn`
@ -163,135 +94,51 @@ impl Operation for GeneratorResumeReturn {
}
}
/// `GeneratorNextDelegate` implements the Opcode Operation for `Opcode::GeneratorNextDelegate`
/// `GeneratorDelegateNext` implements the Opcode Operation for `Opcode::GeneratorDelegateNext`
///
/// Operation:
/// - Delegates the current generator function another generator.
/// - Delegates the current generator function to another iterator.
#[derive(Debug, Clone, Copy)]
pub(crate) struct GeneratorNextDelegate;
pub(crate) struct GeneratorDelegateNext;
impl Operation for GeneratorNextDelegate {
const NAME: &'static str = "GeneratorNextDelegate";
const INSTRUCTION: &'static str = "INST - GeneratorNextDelegate";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let done_address = context.vm.read::<u32>();
let received = context.vm.pop();
let next_method = context.vm.pop();
let iterator = context.vm.pop();
let iterator = iterator.as_object().expect("iterator was not an object");
match context.vm.frame().generator_resume_kind {
GeneratorResumeKind::Normal => {
let result_value =
next_method.call(&iterator.clone().into(), &[received], context)?;
let result = result_value.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("generator next method returned non-object")
})?;
let done = result.get(utf16!("done"), context)?.to_boolean();
if done {
context.vm.frame_mut().pc = done_address;
let value = result.get(utf16!("value"), context)?;
context.vm.push(value);
return Ok(CompletionType::Normal);
}
context.vm.push(iterator.clone());
context.vm.push(next_method.clone());
context.vm.push(result_value);
context.vm.frame_mut().r#yield = true;
Ok(CompletionType::Return)
}
GeneratorResumeKind::Throw => {
let throw = iterator.get_method(utf16!("throw"), context)?;
if let Some(throw) = throw {
let result = throw.call(&iterator.clone().into(), &[received], context)?;
let result_object = result.as_object().ok_or_else(|| {
JsNativeError::typ()
.with_message("generator throw method returned non-object")
})?;
let done = result_object.get(utf16!("done"), context)?.to_boolean();
if done {
context.vm.frame_mut().pc = done_address;
let value = result_object.get(utf16!("value"), context)?;
context.vm.push(value);
return Ok(CompletionType::Normal);
}
context.vm.push(iterator.clone());
context.vm.push(next_method.clone());
context.vm.push(result);
context.vm.frame_mut().r#yield = true;
return Ok(CompletionType::Return);
}
context.vm.frame_mut().pc = done_address;
let iterator_record = IteratorRecord::new(iterator.clone(), next_method, false);
iterator_record.close(Ok(JsValue::Undefined), context)?;
Err(JsNativeError::typ()
.with_message("iterator does not have a throw method")
.into())
}
GeneratorResumeKind::Return => {
let r#return = iterator.get_method(utf16!("return"), context)?;
if let Some(r#return) = r#return {
let result = r#return.call(&iterator.clone().into(), &[received], context)?;
let result_object = result.as_object().ok_or_else(|| {
JsNativeError::typ()
.with_message("generator return method returned non-object")
})?;
let done = result_object.get(utf16!("done"), context)?.to_boolean();
if done {
let value = result_object.get(utf16!("value"), context)?;
context.vm.push(value);
return Return::execute(context);
}
context.vm.push(iterator.clone());
context.vm.push(next_method.clone());
context.vm.push(result);
context.vm.frame_mut().r#yield = true;
return Ok(CompletionType::Return);
}
context.vm.push(received);
Return::execute(context)
}
}
}
}
/// `GeneratorAsyncDelegateNext` implements the Opcode Operation for `Opcode::GeneratorAsyncDelegateNext`
///
/// Operation:
/// - Delegates the current async generator function to another iterator.
#[derive(Debug, Clone, Copy)]
pub(crate) struct GeneratorAsyncDelegateNext;
impl Operation for GeneratorAsyncDelegateNext {
const NAME: &'static str = "GeneratorAsyncDelegateNext";
const INSTRUCTION: &'static str = "INST - GeneratorAsyncDelegateNext";
impl Operation for GeneratorDelegateNext {
const NAME: &'static str = "GeneratorDelegateNext";
const INSTRUCTION: &'static str = "INST - GeneratorDelegateNext";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let throw_method_undefined = context.vm.read::<u32>();
let return_method_undefined = context.vm.read::<u32>();
let received = context.vm.pop();
let next_method = context.vm.pop();
let iterator = context.vm.pop();
let iterator = iterator.as_object().expect("iterator was not an object");
match context.vm.frame().generator_resume_kind {
// Preemptively popping removes the iterator from the iterator stack if any operation
// throws, which avoids calling cleanup operations on the poisoned iterator.
let iterator_record = context
.vm
.frame_mut()
.iterators
.pop()
.expect("iterator stack should have at least an iterator");
match std::mem::take(&mut context.vm.frame_mut().generator_resume_kind) {
GeneratorResumeKind::Normal => {
let result_value =
next_method.call(&iterator.clone().into(), &[received], context)?;
context.vm.push(iterator.clone());
context.vm.push(next_method.clone());
let result = iterator_record.next_method().call(
&iterator_record.iterator().clone().into(),
&[received],
context,
)?;
context.vm.push(false);
context.vm.push(result_value);
Ok(CompletionType::Normal)
context.vm.push(result);
}
GeneratorResumeKind::Throw => {
let throw = iterator.get_method(utf16!("throw"), context)?;
let throw = iterator_record
.iterator()
.get_method(utf16!("throw"), context)?;
if let Some(throw) = throw {
let result = throw.call(&iterator.clone().into(), &[received], context)?;
context.vm.push(iterator.clone());
context.vm.push(next_method.clone());
let result = throw.call(
&iterator_record.iterator().clone().into(),
&[received],
context,
)?;
context.vm.push(false);
context.vm.push(result);
} else {
@ -299,111 +146,80 @@ impl Operation for GeneratorAsyncDelegateNext {
.with_message("iterator does not have a throw method")
.to_opaque(context);
context.vm.push(error);
context.vm.push(iterator.clone());
context.vm.push(next_method.clone());
context.vm.push(false);
context.vm.frame_mut().pc = throw_method_undefined;
}
Ok(CompletionType::Normal)
}
GeneratorResumeKind::Return => {
let r#return = iterator.get_method(utf16!("return"), context)?;
let r#return = iterator_record
.iterator()
.get_method(utf16!("return"), context)?;
if let Some(r#return) = r#return {
let result = r#return.call(&iterator.clone().into(), &[received], context)?;
context.vm.push(iterator.clone());
context.vm.push(next_method.clone());
let result = r#return.call(
&iterator_record.iterator().clone().into(),
&[received],
context,
)?;
context.vm.push(true);
context.vm.push(result);
return Ok(CompletionType::Normal);
}
context.vm.push(iterator.clone());
context.vm.push(next_method.clone());
} else {
context.vm.push(received);
context.vm.frame_mut().pc = return_method_undefined;
Ok(CompletionType::Normal)
// The current iterator didn't have a cleanup `return` method, so we can
// skip pushing it to the iterator stack for cleanup.
return Ok(CompletionType::Normal);
}
}
}
context.vm.frame_mut().iterators.push(iterator_record);
Ok(CompletionType::Normal)
}
}
/// `GeneratorAsyncDelegateResume` implements the Opcode Operation for `Opcode::GeneratorAsyncDelegateResume`
/// `GeneratorDelegateResume` implements the Opcode Operation for `Opcode::GeneratorDelegateResume`
///
/// Operation:
/// - Resume the async generator with yield delegate logic after it awaits a value.
/// - Resume the generator with yield delegate logic after it awaits a value.
#[derive(Debug, Clone, Copy)]
pub(crate) struct GeneratorAsyncDelegateResume;
pub(crate) struct GeneratorDelegateResume;
impl Operation for GeneratorAsyncDelegateResume {
const NAME: &'static str = "GeneratorAsyncDelegateResume";
const INSTRUCTION: &'static str = "INST - GeneratorAsyncDelegateResume";
impl Operation for GeneratorDelegateResume {
const NAME: &'static str = "GeneratorDelegateResume";
const INSTRUCTION: &'static str = "INST - GeneratorDelegateResume";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let skip_yield = context.vm.read::<u32>();
let normal_completion = context.vm.read::<u32>();
let return_gen = context.vm.read::<u32>();
let exit = context.vm.read::<u32>();
let mut iterator = context
.vm
.frame_mut()
.iterators
.pop()
.expect("iterator stack should have at least an iterator");
let result = context.vm.pop();
let is_return = context.vm.pop().to_boolean();
if context.vm.frame().generator_resume_kind == GeneratorResumeKind::Throw {
return Err(JsError::from_opaque(context.vm.pop()));
return Err(JsError::from_opaque(result));
}
let received = context.vm.pop();
let is_return = context.vm.pop().to_boolean();
iterator.update_result(result, context)?;
let result = received.as_object().ok_or_else(|| {
JsNativeError::typ().with_message("generator next method returned non-object")
})?;
let done = result.get(utf16!("done"), context)?.to_boolean();
let value = result.get(utf16!("value"), context)?;
if done {
if iterator.done() {
let value = iterator.value(context)?;
context.vm.push(value);
if is_return {
return Return::execute(context);
}
context.vm.frame_mut().pc = if is_return { return_gen } else { exit };
context.vm.frame_mut().pc = exit;
return Ok(CompletionType::Normal);
}
let completion = Ok(value);
let generator_object = context
.vm
.frame()
.async_generator
.as_ref()
.expect("must be in generator context here")
.clone();
let next = generator_object
.borrow_mut()
.as_async_generator_mut()
.expect("must be async generator object")
.queue
.pop_front()
.expect("must have item in queue");
AsyncGenerator::complete_step(&next, completion, false, None, context);
let mut generator_object_mut = generator_object.borrow_mut();
let gen = generator_object_mut
.as_async_generator_mut()
.expect("must be async generator object");
if let Some(next) = gen.queue.front() {
let (completion, r#return) = &next.completion;
if *r#return {
let value = match completion {
Ok(value) => value.clone(),
Err(e) => e.clone().to_opaque(context),
};
context.vm.push(value);
context.vm.frame_mut().pc = skip_yield;
} else {
context.vm.push(completion.clone()?);
context.vm.frame_mut().pc = normal_completion;
}
} else {
gen.state = AsyncGeneratorState::SuspendedYield;
}
context.vm.frame_mut().iterators.push(iterator);
Ok(CompletionType::Normal)
}
}

86
boa_engine/src/vm/opcode/generator/yield_stm.rs

@ -1,25 +1,87 @@
use crate::{
builtins::iterable::create_iter_result_object,
vm::{opcode::Operation, CompletionType},
Context, JsResult,
builtins::async_generator::{AsyncGenerator, AsyncGeneratorState},
vm::{opcode::Operation, CompletionRecord, CompletionType, GeneratorResumeKind},
Context, JsResult, JsValue,
};
/// `Yield` implements the Opcode Operation for `Opcode::Yield`
/// `GeneratorYield` implements the Opcode Operation for `Opcode::GeneratorYield`
///
/// Operation:
/// - Yield from the current execution.
/// - Yield from the current generator execution.
#[derive(Debug, Clone, Copy)]
pub(crate) struct Yield;
pub(crate) struct GeneratorYield;
impl Operation for Yield {
const NAME: &'static str = "Yield";
const INSTRUCTION: &'static str = "INST - Yield";
impl Operation for GeneratorYield {
const NAME: &'static str = "GeneratorYield";
const INSTRUCTION: &'static str = "INST - GeneratorYield";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let value = context.vm.pop();
let value = create_iter_result_object(value, false, context);
context.vm.push(value);
context.vm.frame_mut().r#yield = true;
Ok(CompletionType::Return)
}
}
/// `AsyncGeneratorYield` implements the Opcode Operation for `Opcode::AsyncGeneratorYield`
///
/// Operation:
/// - Yield from the current async generator execution.
#[derive(Debug, Clone, Copy)]
pub(crate) struct AsyncGeneratorYield;
impl Operation for AsyncGeneratorYield {
const NAME: &'static str = "AsyncGeneratorYield";
const INSTRUCTION: &'static str = "INST - AsyncGeneratorYield";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let value = context.vm.pop();
let async_gen = context
.vm
.frame()
.async_generator
.clone()
.expect("`AsyncGeneratorYield` must only be called inside async generators");
let completion = Ok(value);
let next = async_gen
.borrow_mut()
.as_async_generator_mut()
.expect("must be async generator object")
.queue
.pop_front()
.expect("must have item in queue");
// TODO: 7. Let previousContext be the second to top element of the execution context stack.
AsyncGenerator::complete_step(&next, completion, false, None, context);
let mut generator_object_mut = async_gen.borrow_mut();
let gen = generator_object_mut
.as_async_generator_mut()
.expect("must be async generator object");
if let Some(next) = gen.queue.front() {
let resume_kind = match next.completion.clone() {
CompletionRecord::Normal(val) => {
context.vm.push(val);
GeneratorResumeKind::Normal
}
CompletionRecord::Return(val) => {
context.vm.push(val);
GeneratorResumeKind::Return
}
CompletionRecord::Throw(err) => {
let err = err.to_opaque(context);
context.vm.push(err);
GeneratorResumeKind::Throw
}
};
context.vm.frame_mut().generator_resume_kind = resume_kind;
Ok(CompletionType::Normal)
} else {
gen.state = AsyncGeneratorState::SuspendedYield;
context.vm.push(JsValue::undefined());
GeneratorYield::execute(context)
}
}
}

10
boa_engine/src/vm/opcode/iteration/for_in.rs

@ -1,5 +1,5 @@
use crate::{
builtins::object::for_in_iterator::ForInIterator,
builtins::{iterable::IteratorRecord, object::for_in_iterator::ForInIterator},
vm::{opcode::Operation, CompletionType},
Context, JsResult, JsValue,
};
@ -24,8 +24,12 @@ impl Operation for CreateForInIterator {
.get("next", context)
.expect("ForInIterator must have a `next` method");
context.vm.push(iterator);
context.vm.push(next_method);
context
.vm
.frame_mut()
.iterators
.push(IteratorRecord::new(iterator, next_method));
Ok(CompletionType::Normal)
}
}

6
boa_engine/src/vm/opcode/iteration/get.rs

@ -18,8 +18,7 @@ impl Operation for 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.frame_mut().iterators.push(iterator);
Ok(CompletionType::Normal)
}
}
@ -38,8 +37,7 @@ impl Operation for 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.frame_mut().iterators.push(iterator);
Ok(CompletionType::Normal)
}
}

330
boa_engine/src/vm/opcode/iteration/iterator.rs

@ -1,16 +1,16 @@
use std::matches;
use crate::{
builtins::{
iterable::{IteratorRecord, IteratorResult},
Array,
},
vm::{opcode::Operation, CompletionType},
Context, JsNativeError, JsResult,
builtins::{iterable::create_iter_result_object, Array},
js_string,
vm::{opcode::Operation, CompletionType, GeneratorResumeKind},
Context, JsResult,
};
/// `IteratorNext` implements the Opcode Operation for `Opcode::IteratorNext`
///
/// Operation:
/// - Calls the `next` method of `iterator` and puts its return value on the stack.
/// - Calls the `next` method of `iterator`, updating its record with the next value.
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorNext;
@ -19,149 +19,171 @@ impl Operation for IteratorNext {
const INSTRUCTION: &'static str = "INST - IteratorNext";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let next_method = context.vm.pop();
let iterator = context.vm.pop();
let next_result = next_method.call(&iterator, &[], context)?;
context.vm.push(iterator);
context.vm.push(next_method);
context.vm.push(next_result);
let mut iterator = context
.vm
.frame_mut()
.iterators
.pop()
.expect("iterator stack should have at least an iterator");
iterator.step(context)?;
context.vm.frame_mut().iterators.push(iterator);
Ok(CompletionType::Normal)
}
}
/// `IteratorNextSetDone` implements the Opcode Operation for `Opcode::IteratorNextSetDone`
/// `IteratorFinishAsyncNext` implements the Opcode Operation for `Opcode::IteratorFinishAsyncNext`.
///
/// Operation:
/// - Calls the `next` method of `iterator`, puts its return value on the stack
/// and sets the `[[Done]]` value of the iterator on the call frame.
/// - Finishes the call to `Opcode::IteratorNext` within a `for await` loop by setting the current
/// result of the current iterator.
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorNextSetDone;
pub(crate) struct IteratorFinishAsyncNext;
impl Operation for IteratorNextSetDone {
const NAME: &'static str = "IteratorNextSetDone";
const INSTRUCTION: &'static str = "INST - IteratorNextSetDone";
impl Operation for IteratorFinishAsyncNext {
const NAME: &'static str = "IteratorFinishAsyncNext";
const INSTRUCTION: &'static str = "INST - IteratorFinishAsyncNext";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let next_method = context.vm.pop();
let iterator = context.vm.pop();
let mut done = true;
let result = next_method
.call(&iterator, &[], context)
.and_then(|next_result| {
next_method
.as_object()
.cloned()
.map(IteratorResult::new)
.ok_or_else(|| {
JsNativeError::typ()
.with_message("next value should be an object")
.into()
})
.and_then(|iterator_result| {
iterator_result.complete(context).map(|d| {
done = d;
context.vm.push(iterator);
context.vm.push(next_method);
context.vm.push(next_result);
CompletionType::Normal
})
})
});
context
let mut iterator = context
.vm
.frame_mut()
.iterators
.last_mut()
.pop()
.expect("iterator on the call frame must exist");
if matches!(
context.vm.frame().generator_resume_kind,
GeneratorResumeKind::Throw
) {
// If after awaiting the `next` call the iterator returned an error, it can be considered
// as poisoned, meaning we can remove it from the iterator stack to avoid calling
// cleanup operations on it.
return Ok(CompletionType::Normal);
}
let next_result = context.vm.pop();
iterator.update_result(next_result, context)?;
context.vm.frame_mut().iterators.push(iterator);
Ok(CompletionType::Normal)
}
}
/// `IteratorResult` implements the Opcode Operation for `Opcode::IteratorResult`
///
/// Operation:
/// - Gets the last iteration result of the current iterator record.
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorResult;
impl Operation for IteratorResult {
const NAME: &'static str = "IteratorResult";
const INSTRUCTION: &'static str = "INST - IteratorResult";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let last_result = context
.vm
.frame()
.iterators
.last()
.expect("iterator on the call frame must exist")
.1 = done;
.last_result()
.object()
.clone();
result
context.vm.push(last_result);
Ok(CompletionType::Normal)
}
}
/// `IteratorUnwrapNext` implements the Opcode Operation for `Opcode::IteratorUnwrapNext`
/// `IteratorValue` implements the Opcode Operation for `Opcode::IteratorValue`
///
/// Operation:
/// - Gets the `value` and `done` properties of an iterator result.
/// - Gets the `value` property of the current iterator record.
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorUnwrapNext;
pub(crate) struct IteratorValue;
impl Operation for IteratorUnwrapNext {
const NAME: &'static str = "IteratorUnwrapNext";
const INSTRUCTION: &'static str = "INST - IteratorUnwrapNext";
impl Operation for IteratorValue {
const NAME: &'static str = "IteratorValue";
const INSTRUCTION: &'static str = "INST - IteratorValue";
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);
let mut iterator = context
.vm
.frame_mut()
.iterators
.pop()
.expect("iterator on the call frame must exist");
let value = iterator.value(context)?;
context.vm.push(value);
context.vm.frame_mut().iterators.push(iterator);
Ok(CompletionType::Normal)
}
}
/// `IteratorUnwrapValue` implements the Opcode Operation for `Opcode::IteratorUnwrapValue`
/// `IteratorDone` implements the Opcode Operation for `Opcode::IteratorDone`
///
/// Operation:
/// - Gets the `value` property of an iterator result.
/// - Returns `true` if the current iterator is done, or `false` otherwise
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorUnwrapValue;
pub(crate) struct IteratorDone;
impl Operation for IteratorUnwrapValue {
const NAME: &'static str = "IteratorUnwrapValue";
const INSTRUCTION: &'static str = "INST - IteratorUnwrapValue";
impl Operation for IteratorDone {
const NAME: &'static str = "IteratorDone";
const INSTRUCTION: &'static str = "INST - IteratorDone";
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 value = next_result.value(context)?;
context.vm.push(value);
let done = context
.vm
.frame()
.iterators
.last()
.expect("iterator on the call frame must exist")
.done();
context.vm.push(done);
Ok(CompletionType::Normal)
}
}
/// `IteratorUnwrapNextOrJump` implements the Opcode Operation for `Opcode::IteratorUnwrapNextOrJump`
/// `IteratorReturn` implements the Opcode Operation for `Opcode::IteratorReturn`
///
/// Operation:
/// - Gets the `value` and `done` properties of an iterator result, or jump to `address` if
/// `done` is true.
/// - Calls `return` on the current iterator and returns the result.
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorUnwrapNextOrJump;
pub(crate) struct IteratorReturn;
impl Operation for IteratorUnwrapNextOrJump {
const NAME: &'static str = "IteratorUnwrapNextOrJump";
const INSTRUCTION: &'static str = "INST - IteratorUnwrapNextOrJump";
impl Operation for IteratorReturn {
const NAME: &'static str = "IteratorReturn";
const INSTRUCTION: &'static str = "INST - IteratorReturn";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let address = context.vm.read::<u32>();
let record = context
.vm
.frame_mut()
.iterators
.pop()
.expect("iterator on the call frame must exist");
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;
context.vm.push(true);
} else {
let Some(ret) = record.iterator().get_method(js_string!("return"), context)? else {
context.vm.push(false);
let value = next_result.value(context)?;
return Ok(CompletionType::Normal);
};
let value = ret.call(&record.iterator().clone().into(), &[], context)?;
context.vm.push(value);
}
context.vm.push(true);
Ok(CompletionType::Normal)
}
}
@ -178,83 +200,101 @@ impl Operation for IteratorToArray {
const INSTRUCTION: &'static str = "INST - IteratorToArray";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let next_method = context.vm.pop();
let iterator = context.vm.pop();
let iterator = iterator.as_object().expect("iterator was not an object");
let mut iterator = context
.vm
.frame_mut()
.iterators
.pop()
.expect("iterator on the call frame must exist");
let iterator_record = IteratorRecord::new(iterator.clone(), next_method.clone(), false);
let mut values = Vec::new();
let err = loop {
match iterator_record.step(context) {
Ok(Some(result)) => match result.value(context) {
Ok(value) => values.push(value),
Err(err) => break Some(err),
},
Ok(None) => break None,
Err(err) => break Some(err),
loop {
let done = match iterator.step(context) {
Ok(done) => done,
Err(err) => {
context.vm.frame_mut().iterators.push(iterator);
return Err(err);
}
};
context
.vm
.frame_mut()
.iterators
.last_mut()
.expect("should exist")
.1 = true;
if let Some(err) = err {
if done {
break;
}
match iterator.value(context) {
Ok(value) => values.push(value),
Err(err) => {
context.vm.frame_mut().iterators.push(iterator);
return Err(err);
}
}
}
context.vm.frame_mut().iterators.push(iterator);
let array = Array::create_array_from_list(values, context);
context.vm.push(iterator.clone());
context.vm.push(next_method);
context.vm.push(array);
Ok(CompletionType::Normal)
}
}
/// `IteratorClosePush` implements the Opcode Operation for `Opcode::IteratorClosePush`
/// `IteratorPop` implements the Opcode Operation for `Opcode::IteratorPop`
///
/// Operation:
/// - Push an iterator to the call frame close iterator stack.
/// - Pop an iterator from the call frame close iterator stack.
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorClosePush;
pub(crate) struct IteratorPop;
impl Operation for IteratorClosePush {
const NAME: &'static str = "IteratorClosePush";
const INSTRUCTION: &'static str = "INST - IteratorClosePush";
impl Operation for IteratorPop {
const NAME: &'static str = "IteratorPop";
const INSTRUCTION: &'static str = "INST - IteratorPop";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let next_method = context.vm.pop();
let iterator = context.vm.pop();
let iterator_object = iterator.as_object().expect("iterator was not an object");
context
.vm
.frame_mut()
.iterators
.push((iterator_object.clone(), false));
context.vm.push(iterator);
context.vm.push(next_method);
context.vm.frame_mut().iterators.pop();
Ok(CompletionType::Normal)
}
}
/// `IteratorClosePop` implements the Opcode Operation for `Opcode::IteratorClosePop`
/// `IteratorStackEmpty` implements the Opcode Operation for `Opcode::IteratorStackEmpty`
///
/// Operation:
/// - Pop an iterator from the call frame close iterator stack.
/// - Pushes `true` to the stack if the iterator stack is empty.
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorClosePop;
pub(crate) struct IteratorStackEmpty;
impl Operation for IteratorClosePop {
const NAME: &'static str = "IteratorClosePop";
const INSTRUCTION: &'static str = "INST - IteratorClosePop";
impl Operation for IteratorStackEmpty {
const NAME: &'static str = "IteratorStackEmpty";
const INSTRUCTION: &'static str = "INST - IteratorStackEmpty";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
context.vm.frame_mut().iterators.pop();
let is_empty = context.vm.frame().iterators.is_empty();
context.vm.push(is_empty);
Ok(CompletionType::Normal)
}
}
/// `CreateIteratorResult` implements the Opcode Operation for `Opcode::CreateIteratorResult`
///
/// Operation:
/// - Creates a new iterator result object
#[derive(Debug, Clone, Copy)]
pub(crate) struct CreateIteratorResult;
impl Operation for CreateIteratorResult {
const NAME: &'static str = "CreateIteratorResult";
const INSTRUCTION: &'static str = "INST - CreateIteratorResult";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let value = context.vm.pop();
let done = context.vm.read::<u8>() != 0;
let result = create_iter_result_object(value, done, context);
context.vm.push(result);
Ok(CompletionType::Normal)
}
}

23
boa_engine/src/vm/opcode/iteration/loop_ops.rs

@ -4,6 +4,29 @@ use crate::{
Context, JsResult,
};
/// `IteratorLoopStart` implements the Opcode Operation for `Opcode::IteratorLoopStart`
///
/// Operation:
/// - Push iterator loop start marker.
#[derive(Debug, Clone, Copy)]
pub(crate) struct IteratorLoopStart;
impl Operation for IteratorLoopStart {
const NAME: &'static str = "IteratorLoopStart";
const INSTRUCTION: &'static str = "INST - IteratorLoopStart";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let start = context.vm.read::<u32>();
let exit = context.vm.read::<u32>();
// Create and push loop evironment entry.
let entry = EnvStackEntry::new(start, exit)
.with_iterator_loop_flag(1, (context.vm.frame().iterators.len() - 1) as u32);
context.vm.frame_mut().env_stack.push(entry);
Ok(CompletionType::Normal)
}
}
/// `LoopStart` implements the Opcode Operation for `Opcode::LoopStart`
///
/// Operation:

151
boa_engine/src/vm/opcode/mod.rs

@ -1379,7 +1379,9 @@ generate_impl! {
/// Push loop start marker.
///
/// Operands: Exit Address: `u32`
/// Operands:
/// - start: `u32`
/// - exit: `u32`
///
/// Stack: **=>**
LoopStart,
@ -1421,82 +1423,122 @@ generate_impl! {
/// Creates the ForInIterator of an object.
///
/// Stack: object **=>** iterator, next_method
/// Stack: object **=>**
///
/// Iterator Stack: `iterator`
CreateForInIterator,
/// Push iterator loop start marker.
///
/// Operands:
/// - start: `u32`
/// - exit: `u32`
///
/// Stack: **=>**
IteratorLoopStart,
/// Gets the iterator of an object.
///
/// Operands:
///
/// Stack: object **=>** iterator, next_method
/// Stack: object **=>**
///
/// Iterator Stack: `iterator`
GetIterator,
/// Gets the async iterator of an object.
///
/// Operands:
///
/// Stack: object **=>** iterator, next_method
/// Stack: object **=>**
///
/// Iterator Stack: `iterator`
GetAsyncIterator,
/// Calls the `next` method of `iterator` and puts its return value on the stack.
/// Calls the `next` method of `iterator`, updating its record with the next value.
///
/// Operands:
///
/// Stack: iterator, next_method **=>** iterator, next_method, next_value
/// Iterator Stack: `iterator` **=>** `iterator`
IteratorNext,
/// Calls the `next` method of `iterator`, puts its return value on the stack
/// and sets the `[[Done]]` value of the iterator on the call frame.
/// Returns `true` if the current iterator is done, or `false` otherwise
///
/// Operands:
/// Stack: **=>** done
///
/// Stack: iterator, next_method **=>** iterator, next_method, next_value
IteratorNextSetDone,
/// Iterator Stack: `iterator` **=>** `iterator`
IteratorDone,
/// Gets the `value` and `done` properties of an iterator result.
/// Finishes the call to `Opcode::IteratorNext` within a `for await` loop by setting the current
/// result of the current iterator.
///
/// Operands:
///
/// Stack: next_result **=>** done, next_value
IteratorUnwrapNext,
/// Stack: `next_result` **=>**
///
/// Iterator Stack: iterator **=>** iterator
IteratorFinishAsyncNext,
/// Gets the `value` property of an iterator result.
/// - Gets the `value` property of the current iterator record.
///
/// Stack: **=>** `value`
///
/// Stack: next_result **=>** next_value
IteratorUnwrapValue,
/// Iterator Stack: `iterator` **=>** `iterator`
IteratorValue,
/// Gets the `value` and `done` properties of an iterator result, or jump to `address` if
/// `done` is true.
/// - Gets the last iteration result of the current iterator record.
///
/// Operands: address: `u32`
/// Stack: **=>** `result`
///
/// Stack: next_result **=>** done, next_value ( if done != true )
IteratorUnwrapNextOrJump,
/// Iterator Stack: `iterator` **=>** `iterator`
IteratorResult,
/// Consume the iterator and construct and array with all the values.
///
/// Operands:
///
/// Stack: iterator, next_method **=>** iterator, next_method, array
/// Stack: **=>** array
///
/// Iterator Stack: `iterator` **=>** `iterator`
IteratorToArray,
/// Push an iterator to the call frame close iterator stack.
/// Pop an iterator from the call frame close iterator stack.
///
/// Iterator Stack:
/// - `iterator` **=>**
IteratorPop,
/// Pushes `true` to the stack if the iterator stack is empty.
///
/// Operands:
/// Stack:
/// - **=>** `is_empty`
///
/// Stack: iterator, next_method => iterator, next_method
IteratorClosePush,
/// Iterator Stack:
/// - **=>**
IteratorStackEmpty,
/// Pop an iterator from the call frame close iterator stack.
/// Creates a new iterator result object.
///
/// Operands:
/// - done: bool (codified as u8 with `0` -> `false` and `!0` -> `true`)
///
/// Stack:
IteratorClosePop,
/// - value **=>**
///
CreateIteratorResult,
/// Calls `return` on the current iterator and returns the result.
///
/// Stack: **=>** return_val (if return is a method), is_return_method
///
/// Iterator Stack: `iterator` **=>**
IteratorReturn,
/// Concat multiple stack objects into a string.
///
/// Operands: value_count: `u32`
///
/// Stack: value_1,...value_n **=>** string
/// Stack: `value_1`,...`value_n` **=>** `string`
ConcatToString,
/// Call RequireObjectCoercible on the stack value.
@ -1541,12 +1583,12 @@ generate_impl! {
/// Stack: **=>**
PopOnReturnSub,
/// Yield from the current execution.
/// Yields from the current generator execution.
///
/// Operands:
///
/// Stack: value **=>**
Yield,
/// Stack: value **=>** received
GeneratorYield,
/// Resumes the current generator function.
///
@ -1562,46 +1604,49 @@ generate_impl! {
/// Stack: **=>**
GeneratorResumeReturn,
/// Resumes the current generator function.
/// Yields from the current async generator execution.
///
/// Operands: skip_yield: u32, skip_yield_await: u32
/// Operands:
///
/// Stack: received **=>** `Option<value>`
AsyncGeneratorNext,
/// Stack: value **=>** received
AsyncGeneratorYield,
/// Resumes the current async generator function after a yield.
/// Jumps to the specified instruction for each resume kind.
///
/// Operands: normal_completion: u32
/// Operands:
/// - normal: u32,
/// - throw: u32,
/// - return: u32,
///
/// Stack: **=>**
GeneratorAsyncResumeYield,
/// Stack:
GeneratorJumpOnResumeKind,
/// Delegates the current generator function to another iterator.
/// Sets the current generator resume kind to `Return`.
///
/// Operands: done_address: `u32`
/// Operands:
///
/// Stack: iterator, next_method, received **=>** iterator, next_method
GeneratorNextDelegate,
/// Stack:
GeneratorSetReturn,
/// Delegates the current async generator function to another iterator.
///
/// Operands: throw_method_undefined: `u32`, return_method_undefined: `u32`
///
/// Stack: iterator, next_method, received **=>** iterator, next_method, is_return, result
GeneratorAsyncDelegateNext,
/// Stack: received **=>** result
GeneratorDelegateNext,
/// Resume the async generator with yield delegate logic after it awaits a value.
///
/// Operands: skip_yield: `u32`, normal_completion: `u32`, exit: `u32`
/// Operands: return: `u32`, exit: `u32`
///
/// Stack: is_return, received **=>** value
GeneratorAsyncDelegateResume,
GeneratorDelegateResume,
/// Stops the current async function and schedules it to resume later.
///
/// Operands:
///
/// Stack: promise **=>**
/// Stack: promise **=>** received
Await,
/// Push the current new target to the stack.
@ -1766,12 +1811,6 @@ generate_impl! {
Reserved52 => Reserved,
/// Reserved [`Opcode`].
Reserved53 => Reserved,
/// Reserved [`Opcode`].
Reserved54 => Reserved,
/// Reserved [`Opcode`].
Reserved55 => Reserved,
/// Reserved [`Opcode`].
Reserved56 => Reserved,
}
}

18
boa_engine/src/vm/opcode/push/array.rs

@ -1,5 +1,5 @@
use crate::{
builtins::{iterable::IteratorRecord, Array},
builtins::Array,
object::ObjectData,
string::utf16,
vm::{opcode::Operation, CompletionType},
@ -90,15 +90,17 @@ impl Operation for PushIteratorToArray {
const INSTRUCTION: &'static str = "INST - PushIteratorToArray";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let next_method = context.vm.pop();
let iterator = context.vm.pop();
let iterator = iterator.as_object().expect("iterator was not an object");
let mut iterator = context
.vm
.frame_mut()
.iterators
.pop()
.expect("iterator stack should have at least an iterator");
let array = context.vm.pop();
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)?;
while !iterator.step(context)? {
let next = iterator.value(context)?;
Array::push(&array, &[next], context)?;
}
context.vm.push(array);

Loading…
Cancel
Save