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

10
.vscode/tasks.json vendored

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

4
boa_cli/src/main.rs

@ -487,7 +487,7 @@ struct Jobs(RefCell<VecDeque<NativeJob>>);
impl JobQueue for Jobs { impl JobQueue for Jobs {
fn enqueue_promise_job(&self, job: NativeJob, _: &mut Context<'_>) { 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<'_>) { fn run_jobs(&self, context: &mut Context<'_>) {
@ -506,6 +506,6 @@ impl JobQueue for Jobs {
fn enqueue_future_job(&self, future: FutureJob, _: &mut Context<'_>) { fn enqueue_future_job(&self, future: FutureJob, _: &mut Context<'_>) {
let job = pollster::block_on(future); 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). // 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))?; items.get_iterator(context, Some(IteratorHint::Sync), Some(using_iterator))?;
// d. Let k be 0. // d. Let k be 0.
@ -571,19 +571,16 @@ impl Array {
// x. Set k to k + 1. // x. Set k to k + 1.
for k in 0..9_007_199_254_740_991_u64 { for k in 0..9_007_199_254_740_991_u64 {
// iii. Let next be ? IteratorStep(iteratorRecord). // iii. Let next be ? IteratorStep(iteratorRecord).
let next = iterator_record.step(context)?; if iterator_record.step(context)? {
// iv. If next is false, then
let Some(next) = next else {
// 1. Perform ? Set(A, "length", 𝔽(k), true). // 1. Perform ? Set(A, "length", 𝔽(k), true).
a.set(utf16!("length"), k, true, context)?; a.set(utf16!("length"), k, true, context)?;
// 2. Return A. // 2. Return A.
return Ok(a.into()); return Ok(a.into());
}; }
// iv. If next is false, then
// v. Let nextValue be ? IteratorValue(next). // 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 // vi. If mapping is true, then
let mapped_value = if let Some(mapfn) = mapping { 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, realm::Realm,
symbol::JsSymbol, symbol::JsSymbol,
value::JsValue, value::JsValue,
vm::GeneratorResumeKind, vm::{CompletionRecord, GeneratorResumeKind},
Context, JsArgs, JsError, JsResult, Context, JsArgs, JsError, JsResult,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
@ -46,7 +46,7 @@ pub(crate) enum AsyncGeneratorState {
#[derive(Debug, Clone, Finalize, Trace)] #[derive(Debug, Clone, Finalize, Trace)]
pub(crate) struct AsyncGeneratorRequest { pub(crate) struct AsyncGeneratorRequest {
/// The `[[Completion]]` slot. /// The `[[Completion]]` slot.
pub(crate) completion: (JsResult<JsValue>, bool), pub(crate) completion: CompletionRecord,
/// The `[[Capability]]` slot. /// The `[[Capability]]` slot.
capability: PromiseCapability, capability: PromiseCapability,
@ -164,7 +164,7 @@ impl AsyncGenerator {
} }
// 7. Let completion be NormalCompletion(value). // 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). // 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
generator.enqueue(completion.clone(), promise_capability.clone()); generator.enqueue(completion.clone(), promise_capability.clone());
@ -232,7 +232,8 @@ impl AsyncGenerator {
if_abrupt_reject_promise!(generator, promise_capability, context); if_abrupt_reject_promise!(generator, promise_capability, context);
// 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. // 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). // 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
generator.enqueue(completion.clone(), promise_capability.clone()); generator.enqueue(completion.clone(), promise_capability.clone());
@ -246,14 +247,8 @@ impl AsyncGenerator {
generator.state = AsyncGeneratorState::AwaitingReturn; generator.state = AsyncGeneratorState::AwaitingReturn;
// b. Perform ! AsyncGeneratorAwaitReturn(generator). // b. Perform ! AsyncGeneratorAwaitReturn(generator).
let next = generator
.queue
.front()
.cloned()
.expect("queue cannot be empty here");
drop(generator_obj_mut); drop(generator_obj_mut);
let (completion, _) = &next.completion; Self::await_return(generator_object.clone(), return_value, context);
Self::await_return(generator_object.clone(), completion.clone(), context);
} }
// 9. Else if state is suspendedYield, then // 9. Else if state is suspendedYield, then
else if state == AsyncGeneratorState::SuspendedYield { else if state == AsyncGeneratorState::SuspendedYield {
@ -346,10 +341,8 @@ impl AsyncGenerator {
} }
// 8. Let completion be ThrowCompletion(exception). // 8. Let completion be ThrowCompletion(exception).
let completion = ( let completion =
Err(JsError::from_opaque(args.get_or_undefined(0).clone())), CompletionRecord::Throw(JsError::from_opaque(args.get_or_undefined(0).clone()));
false,
);
// 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). // 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
generator.enqueue(completion.clone(), promise_capability.clone()); generator.enqueue(completion.clone(), promise_capability.clone());
@ -384,7 +377,7 @@ impl AsyncGenerator {
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorenqueue /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorenqueue
pub(crate) fn enqueue( pub(crate) fn enqueue(
&mut self, &mut self,
completion: (JsResult<JsValue>, bool), completion: CompletionRecord,
promise_capability: PromiseCapability, promise_capability: PromiseCapability,
) { ) {
// 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }. // 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }.
@ -469,7 +462,7 @@ impl AsyncGenerator {
generator: &JsObject, generator: &JsObject,
state: AsyncGeneratorState, state: AsyncGeneratorState,
mut generator_context: GeneratorContext, mut generator_context: GeneratorContext,
completion: (JsResult<JsValue>, bool), completion: CompletionRecord,
context: &mut Context<'_>, context: &mut Context<'_>,
) { ) {
// 1. Assert: generator.[[AsyncGeneratorState]] is either suspendedStart or suspendedYield. // 1. Assert: generator.[[AsyncGeneratorState]] is either suspendedStart or suspendedYield.
@ -491,15 +484,9 @@ impl AsyncGenerator {
.state = AsyncGeneratorState::Executing; .state = AsyncGeneratorState::Executing;
let (value, resume_kind) = match completion { let (value, resume_kind) = match completion {
(Ok(value), r#return) => ( CompletionRecord::Normal(val) => (val, GeneratorResumeKind::Normal),
value, CompletionRecord::Return(val) => (val, GeneratorResumeKind::Return),
if r#return { CompletionRecord::Throw(err) => (err.to_opaque(context), GeneratorResumeKind::Throw),
GeneratorResumeKind::Return
} else {
GeneratorResumeKind::Normal
},
),
(Err(value), _) => (value.to_opaque(context), GeneratorResumeKind::Throw),
}; };
// 6. Push genContext onto the execution context stack; genContext is now the running execution context. // 6. Push genContext onto the execution context stack; genContext is now the running execution context.
@ -527,19 +514,12 @@ impl AsyncGenerator {
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn
pub(crate) fn await_return( pub(crate) fn await_return(generator: JsObject, value: JsValue, context: &mut Context<'_>) {
generator: JsObject,
completion: JsResult<JsValue>,
context: &mut Context<'_>,
) {
// 1. Let queue be generator.[[AsyncGeneratorQueue]]. // 1. Let queue be generator.[[AsyncGeneratorQueue]].
// 2. Assert: queue is not empty. // 2. Assert: queue is not empty.
// 3. Let next be the first element of queue. // 3. Let next be the first element of queue.
// 4. Let completion be Completion(next.[[Completion]]). // 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. // Note: The spec is currently broken here.
// See: https://github.com/tc39/ecma262/pull/2683 // See: https://github.com/tc39/ecma262/pull/2683
@ -681,29 +661,24 @@ impl AsyncGenerator {
let next = queue.front().expect("must have entry"); let next = queue.front().expect("must have entry");
// b. Let completion be Completion(next.[[Completion]]). // b. Let completion be Completion(next.[[Completion]]).
match &next.completion { match next.completion.clone() {
// c. If completion.[[Type]] is return, then // c. If completion.[[Type]] is return, then
(completion, true) => { CompletionRecord::Return(val) => {
// i. Set generator.[[AsyncGeneratorState]] to awaiting-return. // i. Set generator.[[AsyncGeneratorState]] to awaiting-return.
gen.state = AsyncGeneratorState::AwaitingReturn; gen.state = AsyncGeneratorState::AwaitingReturn;
drop(generator_borrow_mut);
// ii. Perform ! AsyncGeneratorAwaitReturn(generator). // ii. Perform ! AsyncGeneratorAwaitReturn(generator).
let completion = completion.clone(); Self::await_return(generator.clone(), val, context);
drop(generator_borrow_mut);
Self::await_return(generator.clone(), completion, context);
// iii. Set done to true. // iii. Set done to true.
break; break;
} }
// d. Else, // d. Else,
(completion, false) => { completion => {
// i. If completion.[[Type]] is normal, then // i. If completion.[[Type]] is normal, then
let completion = if completion.is_ok() {
// 1. Set completion to NormalCompletion(undefined). // 1. Set completion to NormalCompletion(undefined).
Ok(JsValue::undefined()) let completion = completion.consume().map(|_| JsValue::undefined());
} else {
completion.clone()
};
// ii. Perform AsyncGeneratorCompleteStep(generator, completion, true). // ii. Perform AsyncGeneratorCompleteStep(generator, completion, true).
let next = queue.pop_front().expect("must have entry"); 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). // 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. // 3. Let list be a new empty List.
let mut list = Vec::new(); let mut list = Vec::new();
@ -478,9 +478,9 @@ fn string_list_from_iterable(
// 5. Repeat, while next is not false, // 5. Repeat, while next is not false,
// a. Set next to ? IteratorStep(iteratorRecord). // a. Set next to ? IteratorStep(iteratorRecord).
// b. If next is not false, then // 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). // i. Let nextValue be ? IteratorValue(next).
let item = item.value(context)?;
// ii. If Type(nextValue) is not String, then // ii. If Type(nextValue) is not String, then
let Some(s) = item.as_string().cloned() else { let Some(s) = item.as_string().cloned() else {
// 1. Let error be ThrowCompletion(a newly created TypeError object). // 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}, object::{FunctionObjectBuilder, JsObject, ObjectData},
realm::Realm, realm::Realm,
string::utf16, string::utf16,
Context, JsArgs, JsNativeError, JsResult, JsValue, Context, JsArgs, JsResult, JsValue,
}; };
use boa_gc::{Finalize, Trace}; use boa_gc::{Finalize, Trace};
use boa_profiler::Profiler; use boa_profiler::Profiler;
@ -84,7 +84,7 @@ impl AsyncFromSyncIterator {
// 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: asyncIterator, [[NextMethod]]: nextMethod, [[Done]]: false }. // 4. Let iteratorRecord be the Iterator Record { [[Iterator]]: asyncIterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
// 5. Return iteratorRecord. // 5. Return iteratorRecord.
IteratorRecord::new(async_iterator, next_method, false) IteratorRecord::new(async_iterator, next_method)
} }
/// `%AsyncFromSyncIteratorPrototype%.next ( [ value ] )` /// `%AsyncFromSyncIteratorPrototype%.next ( [ value ] )`
@ -117,7 +117,15 @@ impl AsyncFromSyncIterator {
// a. Let result be Completion(IteratorNext(syncIteratorRecord, value)). // a. Let result be Completion(IteratorNext(syncIteratorRecord, value)).
// 6. Else, // 6. Else,
// a. Let result be Completion(IteratorNext(syncIteratorRecord)). // 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). // 7. IfAbruptRejectPromise(result, promiseCapability).
if_abrupt_reject_promise!(result, promise_capability, context); 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 // 11. If Type(result) is not Object, then
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
promise_capability let result = result.and_then(IteratorResult::from_value);
.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");
// b. Return promiseCapability.[[Promise]]. // 10. IfAbruptRejectPromise(result, promiseCapability).
return Ok(promise_capability.promise().clone().into()); if_abrupt_reject_promise!(result, promise_capability, context);
};
// 12. Return AsyncFromSyncIteratorContinuation(result, promiseCapability). // 12. Return AsyncFromSyncIteratorContinuation(result, promiseCapability).
Self::continuation( Self::continuation(&result, &promise_capability, context)
&IteratorResult {
object: result.clone(),
},
&promise_capability,
context,
)
} }
/// `%AsyncFromSyncIteratorPrototype%.throw ( [ value ] )` /// `%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 // 11. If Type(result) is not Object, then
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »). // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « a newly created TypeError object »).
promise_capability let result = result.and_then(IteratorResult::from_value);
.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");
// b. Return promiseCapability.[[Promise]]. // 10. IfAbruptRejectPromise(result, promiseCapability).
return Ok(promise_capability.promise().clone().into()); if_abrupt_reject_promise!(result, promise_capability, context);
};
// 12. Return AsyncFromSyncIteratorContinuation(result, promiseCapability). // 12. Return AsyncFromSyncIteratorContinuation(result, promiseCapability).
Self::continuation( Self::continuation(&result, &promise_capability, context)
&IteratorResult {
object: result.clone(),
},
&promise_capability,
context,
)
} }
/// `AsyncFromSyncIteratorContinuation ( result, promiseCapability )` /// `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 }. // 6. Let iteratorRecord be the Record { [[Iterator]]: iterator, [[NextMethod]]: nextMethod, [[Done]]: false }.
// 7. Return iteratorRecord. // 7. Return iteratorRecord.
Ok(IteratorRecord::new( Ok(IteratorRecord::new(iterator_obj.clone(), next_method))
iterator_obj.clone(),
next_method,
false,
))
} }
} }
/// The result of the iteration process. /// The result of the iteration process.
#[derive(Debug)] #[derive(Debug, Clone, Trace, Finalize)]
pub struct IteratorResult { pub struct IteratorResult {
object: JsObject, object: JsObject,
} }
impl IteratorResult { impl IteratorResult {
/// Create a new `IteratorResult`. /// Gets a new `IteratorResult` from a value. Returns `Err` if
pub(crate) fn new(object: JsObject) -> Self { /// the value is not a [`JsObject`]
Self { object } 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 )` /// `IteratorComplete ( iterResult )`
@ -362,16 +370,22 @@ pub struct IteratorRecord {
/// ///
/// Whether the iterator has been closed. /// Whether the iterator has been closed.
done: bool, done: bool,
/// The result of the last call to `next`.
last_result: IteratorResult,
} }
impl IteratorRecord { impl IteratorRecord {
/// Creates a new `IteratorRecord` with the given iterator object, next method and `done` flag. /// Creates a new `IteratorRecord` with the given iterator object, next method and `done` flag.
#[inline] #[inline]
pub fn new(iterator: JsObject, next_method: JsValue, done: bool) -> Self { pub fn new(iterator: JsObject, next_method: JsValue) -> Self {
Self { Self {
iterator, iterator,
next_method, next_method,
done, done: false,
last_result: IteratorResult {
object: JsObject::with_null_proto(),
},
} }
} }
@ -380,19 +394,63 @@ impl IteratorRecord {
&self.iterator &self.iterator
} }
/// Get the `[[NextMethod]]` field of the `IteratorRecord`. /// Gets the `[[NextMethod]]` field of the `IteratorRecord`.
pub(crate) const fn next_method(&self) -> &JsValue { pub(crate) const fn next_method(&self) -> &JsValue {
&self.next_method &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`. /// Get the `[[Done]]` field of the `IteratorRecord`.
pub(crate) const fn done(&self) -> bool { pub(crate) const fn done(&self) -> bool {
self.done self.done
} }
/// Sets the `[[Done]]` field of the `IteratorRecord`. /// Updates the current result value of this iterator record.
pub(crate) fn set_done(&mut self, done: bool) { pub(crate) fn update_result(
self.done = done; &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 ] )` /// `IteratorNext ( iteratorRecord [ , value ] )`
@ -405,63 +463,43 @@ impl IteratorRecord {
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-iteratornext /// [spec]: https://tc39.es/ecma262/#sec-iteratornext
pub(crate) fn next( pub(crate) fn step_with(
&self, &mut self,
value: Option<&JsValue>, value: Option<&JsValue>,
context: &mut Context<'_>, context: &mut Context<'_>,
) -> JsResult<IteratorResult> { ) -> JsResult<bool> {
let _timer = Profiler::global().start_event("IteratorRecord::next", "iterator"); let _timer = Profiler::global().start_event("IteratorRecord::step_with", "iterator");
self.set_done_on_err(|iter| {
// 1. If value is not present, then // 1. If value is not present, then
// a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]). // a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
// 2. Else, // 2. Else,
// a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « value »). // a. Let result be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]], « value »).
let result = self.next_method.call( let result = iter.next_method.call(
&self.iterator.clone().into(), &iter.iterator.clone().into(),
value.map_or(&[], std::slice::from_ref), value.map_or(&[], std::slice::from_ref),
context, context,
)?; )?;
// 3. If Type(result) is not Object, throw a TypeError exception. iter.update_result(result, context)?;
// 4. Return result. // 4. Return result.
result Ok(iter.done)
.as_object()
.map(|o| IteratorResult { object: o.clone() })
.ok_or_else(|| {
JsNativeError::typ()
.with_message("next value should be an object")
.into()
}) })
} }
/// `IteratorStep ( iteratorRecord )` /// `IteratorStep ( iteratorRecord )`
/// ///
/// The abstract operation `IteratorStep` takes argument `iteratorRecord` (an `Iterator` /// Updates the `IteratorRecord` and returns `true` if the next result record returned
/// Record) and returns either a normal completion containing either an `Object` or `false`, or /// `done: true`, otherwise returns `false`. This differs slightly from the spec, but also
/// a throw completion. It requests the next value from `iteratorRecord.[[Iterator]]` by /// simplifies some logic around iterators.
/// 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.
/// ///
/// More information: /// More information:
/// - [ECMA reference][spec] /// - [ECMA reference][spec]
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-iteratorstep /// [spec]: https://tc39.es/ecma262/#sec-iteratorstep
pub(crate) fn step(&self, context: &mut Context<'_>) -> JsResult<Option<IteratorResult>> { pub(crate) fn step(&mut self, context: &mut Context<'_>) -> JsResult<bool> {
let _timer = Profiler::global().start_event("IteratorRecord::step", "iterator"); self.step_with(None, context)
// 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))
} }
/// `IteratorClose ( iteratorRecord, completion )` /// `IteratorClose ( iteratorRecord, completion )`
@ -548,7 +586,7 @@ pub(crate) fn iterable_to_list(
// a. Let iteratorRecord be ? GetIterator(items, sync, method). // a. Let iteratorRecord be ? GetIterator(items, sync, method).
// 2. Else, // 2. Else,
// a. Let iteratorRecord be ? GetIterator(items, sync). // 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. // 3. Let values be a new empty List.
let mut values = Vec::new(); let mut values = Vec::new();
@ -559,9 +597,8 @@ pub(crate) fn iterable_to_list(
// b. If next is not false, then // b. If next is not false, then
// i. Let nextValue be ? IteratorValue(next). // i. Let nextValue be ? IteratorValue(next).
// ii. Append nextValue to the end of the List values. // ii. Append nextValue to the end of the List values.
while let Some(next) = iterator_record.step(context)? { while !iterator_record.step(context)? {
let next_value = next.value(context)?; values.push(iterator_record.value(context)?);
values.push(next_value);
} }
// 6. Return values. // 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). // 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, // 3. Repeat,
loop { loop {
// a. Let next be ? IteratorStep(iteratorRecord). // a. Let next be ? IteratorStep(iteratorRecord).
let next = iterator_record.step(context)?;
// b. If next is false, return target. // b. If next is false, return target.
// c. Let nextItem be ? IteratorValue(next). // c. Let nextItem be ? IteratorValue(next).
let Some(next_item) = next else { if iterator_record.step(context)? {
return Ok(target.clone().into()); 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 { let Some(next_item) = next_item.as_object() else {
// d. If Type(nextItem) is not Object, then // d. If Type(nextItem) is not Object, then

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

@ -542,21 +542,13 @@ impl Promise {
// 4. Repeat, // 4. Repeat,
loop { loop {
// a. Let next be Completion(IteratorStep(iteratorRecord)). // 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. // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// c. ReturnIfAbrupt(next). // c. ReturnIfAbrupt(next).
return Err(e); let done = iterator_record.step(context)?;
}
// d. If next is false, then // d. If next is false, then
Ok(None) => {
// i. Set iteratorRecord.[[Done]] to true. // i. Set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true); if done {
// ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. // ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
remaining_elements_count.set(remaining_elements_count.get() - 1); remaining_elements_count.set(remaining_elements_count.get() - 1);
@ -579,22 +571,11 @@ impl Promise {
// iv. Return resultCapability.[[Promise]]. // iv. Return resultCapability.[[Promise]].
return Ok(result_capability.promise.clone()); 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 { // e. Let nextValue be Completion(IteratorValue(next)).
Err(e) => {
// f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. // f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// g. ReturnIfAbrupt(nextValue). // g. ReturnIfAbrupt(nextValue).
return Err(e); let next_value = iterator_record.value(context)?;
}
Ok(next_value) => next_value,
}
}
};
// h. Append undefined to values. // h. Append undefined to values.
values.borrow_mut().push(JsValue::Undefined); values.borrow_mut().push(JsValue::Undefined);
@ -785,21 +766,13 @@ impl Promise {
// 4. Repeat, // 4. Repeat,
loop { loop {
// a. Let next be Completion(IteratorStep(iteratorRecord)). // 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. // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// c. ReturnIfAbrupt(next). // c. ReturnIfAbrupt(next).
return Err(e); let done = iterator_record.step(context)?;
}
// d. If next is false, then // d. If next is false, then
Ok(None) => { if done {
// i. Set iteratorRecord.[[Done]] to true. // i. Set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. // ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
remaining_elements_count.set(remaining_elements_count.get() - 1); remaining_elements_count.set(remaining_elements_count.get() - 1);
@ -822,22 +795,11 @@ impl Promise {
// iv. Return resultCapability.[[Promise]]. // iv. Return resultCapability.[[Promise]].
return Ok(result_capability.promise.clone()); 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 { // e. Let nextValue be Completion(IteratorValue(next)).
Err(e) => {
// f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. // f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// g. ReturnIfAbrupt(nextValue). // g. ReturnIfAbrupt(nextValue).
return Err(e); let next_value = iterator_record.value(context)?;
}
Ok(next_value) => next_value,
}
}
};
// h. Append undefined to values. // h. Append undefined to values.
values.borrow_mut().push(JsValue::undefined()); values.borrow_mut().push(JsValue::undefined());
@ -1131,20 +1093,13 @@ impl Promise {
// 4. Repeat, // 4. Repeat,
loop { loop {
// a. Let next be Completion(IteratorStep(iteratorRecord)). // 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. // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// c. ReturnIfAbrupt(next). // c. ReturnIfAbrupt(next).
return Err(e); let done = iterator_record.step(context)?;
}
// d. If next is false, then // d. If next is false, then
Ok(None) => { if done {
// i. Set iteratorRecord.[[Done]] to true. // i. Set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1. // ii. Set remainingElementsCount.[[Value]] to remainingElementsCount.[[Value]] - 1.
remaining_elements_count.set(remaining_elements_count.get() - 1); remaining_elements_count.set(remaining_elements_count.get() - 1);
@ -1170,22 +1125,11 @@ impl Promise {
// iv. Return resultCapability.[[Promise]]. // iv. Return resultCapability.[[Promise]].
return Ok(result_capability.promise.clone()); 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 { // e. Let nextValue be Completion(IteratorValue(next)).
Err(e) => {
// f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true. // f. If nextValue is an abrupt completion, set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// g. ReturnIfAbrupt(nextValue). // g. ReturnIfAbrupt(nextValue).
return Err(e); let next_value = iterator_record.value(context)?;
}
Ok(next_value) => next_value,
}
}
};
// h. Append undefined to errors. // h. Append undefined to errors.
errors.borrow_mut().push(JsValue::undefined()); errors.borrow_mut().push(JsValue::undefined());
@ -1375,35 +1319,21 @@ impl Promise {
// 1. Repeat, // 1. Repeat,
loop { loop {
// a. Let next be Completion(IteratorStep(iteratorRecord)). // 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. // b. If next is an abrupt completion, set iteratorRecord.[[Done]] to true.
if next.is_err() {
iterator_record.set_done(true);
}
// c. ReturnIfAbrupt(next). // 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 // d. If next is false, then
// i. Set iteratorRecord.[[Done]] to true. // i. Set iteratorRecord.[[Done]] to true.
iterator_record.set_done(true);
// ii. Return resultCapability.[[Promise]]. // ii. Return resultCapability.[[Promise]].
return Ok(result_capability.promise.clone()); return Ok(result_capability.promise.clone());
}; }
// e. Let nextValue be Completion(IteratorValue(next)). // 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. // 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). // g. ReturnIfAbrupt(nextValue).
let next_value = next_value?; let next_value = iterator_record.value(context)?;
// h. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »). // h. Let nextPromise be ? Call(promiseResolve, constructor, « nextValue »).
let next_promise = promise_resolve.call(&constructor, &[next_value], context)?; 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). // 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, // 8. Repeat,
// a. Let next be ? IteratorStep(iteratorRecord). // a. Let next be ? IteratorStep(iteratorRecord).
@ -153,12 +153,12 @@ impl BuiltInConstructor for Set {
// c. Let nextValue be ? IteratorValue(next). // c. Let nextValue be ? IteratorValue(next).
// d. Let status be Completion(Call(adder, set, « nextValue »)). // d. Let status be Completion(Call(adder, set, « nextValue »)).
// e. IfAbruptCloseIterator(status, iteratorRecord). // e. IfAbruptCloseIterator(status, iteratorRecord).
while let Some(next) = iterator_record.step(context)? { while !iterator_record.step(context)? {
let next = iterator_record.value(context)?;
// c // c
let next_value = next.value(context)?;
// d, e // 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); 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"))?; .ok_or_else(|| JsNativeError::typ().with_message("WeakSet: 'add' is not a function"))?;
// 7. Let iteratorRecord be ? GetIterator(iterable). // 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, // 8. Repeat,
// a. Let next be ? IteratorStep(iteratorRecord). // 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). // 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 »)). // d. Let status be Completion(Call(adder, set, « nextValue »)).
// e. IfAbruptCloseIterator(status, iteratorRecord). // 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); 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) => { Pattern::Array(pattern) => {
self.emit_opcode(Opcode::ValueNotNullOrUndefined); self.emit_opcode(Opcode::ValueNotNullOrUndefined);
self.emit_opcode(Opcode::GetIterator); self.emit_opcode(Opcode::GetIterator);
self.emit_opcode(Opcode::IteratorClosePush);
match pattern.bindings().split_last() { for element in pattern.bindings() {
None => self.emit_opcode(Opcode::PushFalse), self.compile_array_pattern_element(element, def);
Some((last, rest)) => {
for element in rest {
self.compile_array_pattern_element(element, def, false);
}
self.compile_array_pattern_element(last, def, true);
}
} }
self.emit_opcode(Opcode::IteratorClosePop);
self.iterator_close(false); self.iterator_close(false);
} }
} }
} }
fn compile_array_pattern_element( fn compile_array_pattern_element(&mut self, element: &ArrayPatternElement, def: BindingOpcode) {
&mut self,
element: &ArrayPatternElement,
def: BindingOpcode,
with_done: bool,
) {
use ArrayPatternElement::{ use ArrayPatternElement::{
Elision, Pattern, PatternRest, PropertyAccess, PropertyAccessRest, SingleName, Elision, Pattern, PatternRest, PropertyAccess, PropertyAccessRest, SingleName,
SingleNameRest, SingleNameRest,
}; };
let unwrapping = if with_done {
Opcode::IteratorUnwrapNext
} else {
Opcode::IteratorUnwrapValue
};
match element { match element {
// ArrayBindingPattern : [ Elision ] // ArrayBindingPattern : [ Elision ]
Elision => { Elision => {
self.emit_opcode(Opcode::IteratorNextSetDone); self.emit_opcode(Opcode::IteratorNext);
if with_done {
self.emit_opcode(Opcode::IteratorUnwrapNext);
}
self.emit_opcode(Opcode::Pop);
} }
// SingleNameBinding : BindingIdentifier Initializer[opt] // SingleNameBinding : BindingIdentifier Initializer[opt]
SingleName { SingleName {
ident, ident,
default_init, default_init,
} => { } => {
self.emit_opcode(Opcode::IteratorNextSetDone); self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(unwrapping); self.emit_opcode(Opcode::IteratorValue);
if let Some(init) = default_init { if let Some(init) = default_init {
let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
self.compile_expr(init, true); self.compile_expr(init, true);
@ -232,23 +212,9 @@ impl ByteCompiler<'_, '_> {
self.emit_binding(def, *ident); self.emit_binding(def, *ident);
} }
PropertyAccess { access } => { PropertyAccess { access } => {
self.access_set(Access::Property { access }, false, |compiler, level| { self.access_set(Access::Property { access }, false, |compiler, _level| {
if level != 0 { compiler.emit_opcode(Opcode::IteratorNext);
compiler.emit_opcode(Opcode::RotateLeft); compiler.emit_opcode(Opcode::IteratorValue);
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);
}
}); });
} }
// BindingElement : BindingPattern Initializer[opt] // BindingElement : BindingPattern Initializer[opt]
@ -256,8 +222,8 @@ impl ByteCompiler<'_, '_> {
pattern, pattern,
default_init, default_init,
} => { } => {
self.emit_opcode(Opcode::IteratorNextSetDone); self.emit_opcode(Opcode::IteratorNext);
self.emit_opcode(unwrapping); self.emit_opcode(Opcode::IteratorValue);
if let Some(init) = default_init { if let Some(init) = default_init {
let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); let skip = self.emit_opcode_with_operand(Opcode::JumpIfNotUndefined);
@ -271,39 +237,16 @@ impl ByteCompiler<'_, '_> {
SingleNameRest { ident } => { SingleNameRest { ident } => {
self.emit_opcode(Opcode::IteratorToArray); self.emit_opcode(Opcode::IteratorToArray);
self.emit_binding(def, *ident); self.emit_binding(def, *ident);
if with_done {
self.emit_opcode(Opcode::PushTrue);
}
} }
PropertyAccessRest { access } => { PropertyAccessRest { access } => {
self.access_set(Access::Property { access }, false, |compiler, level| { 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::IteratorToArray); 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 // BindingRestElement : ... BindingPattern
PatternRest { pattern } => { PatternRest { pattern } => {
self.emit_opcode(Opcode::IteratorToArray); self.emit_opcode(Opcode::IteratorToArray);
self.compile_declaration_pattern(pattern, def); 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, 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. // b. Let fo be InstantiateFunctionObject of f with arguments env and privateEnv.
let function = if generator { let function = if generator {
create_generator_function_object(code, r#async, None, self.context) 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 // c. If varEnv is a Global Environment Record, then
if var_environment_is_global { 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. // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv.
let function = if generator { let function = if generator {
create_generator_function_object(code, r#async, None, self.context) create_generator_function_object(code, r#async, None, self.context)
@ -988,7 +994,9 @@ impl ByteCompiler<'_, '_> {
} }
if generator { if generator {
self.emit_opcode(Opcode::PushUndefined); 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 // 27. If hasParameterExpressions is false, then

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

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

39
boa_engine/src/bytecompiler/jump_control.rs

@ -27,7 +27,7 @@ pub(crate) struct JumpControlInfo {
bitflags! { bitflags! {
/// A bitflag that contains the type flags and relevant booleans for `JumpControlInfo`. /// A bitflag that contains the type flags and relevant booleans for `JumpControlInfo`.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct JumpControlInfoFlags: u8 { pub(crate) struct JumpControlInfoFlags: u16 {
const LOOP = 0b0000_0001; const LOOP = 0b0000_0001;
const SWITCH = 0b0000_0010; const SWITCH = 0b0000_0010;
const TRY_BLOCK = 0b0000_0100; const TRY_BLOCK = 0b0000_0100;
@ -35,7 +35,8 @@ bitflags! {
const IN_CATCH = 0b0001_0000; const IN_CATCH = 0b0001_0000;
const IN_FINALLY = 0b0010_0000; const IN_FINALLY = 0b0010_0000;
const HAS_FINALLY = 0b0100_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 self
} }
pub(crate) fn with_for_of_in_loop(mut self, value: bool) -> Self { pub(crate) fn with_iterator_loop(mut self, value: bool) -> Self {
self.flags.set(JumpControlInfoFlags::FOR_OF_IN_LOOP, value); 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 self
} }
} }
@ -139,8 +146,12 @@ impl JumpControlInfo {
self.flags.contains(JumpControlInfoFlags::HAS_FINALLY) self.flags.contains(JumpControlInfoFlags::HAS_FINALLY)
} }
pub(crate) const fn for_of_in_loop(&self) -> bool { pub(crate) const fn iterator_loop(&self) -> bool {
self.flags.contains(JumpControlInfoFlags::FOR_OF_IN_LOOP) 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_loop_flag(true)
.with_label(label) .with_label(label)
.with_start_address(start_address) .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); 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) 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 { fn jump_if_null_or_undefined(&mut self) -> Label {
self.emit_opcode_with_operand(Opcode::JumpIfNullOrUndefined) self.emit_opcode_with_operand(Opcode::JumpIfNullOrUndefined)
} }
@ -519,6 +515,28 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
(Label { index }, Label { index: index + 4 }) (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) { pub(crate) fn patch_jump_with_target(&mut self, label: Label, target: u32) {
const U32_SIZE: usize = std::mem::size_of::<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. // 1. Handle if node has a label.
if let Some(node_label) = node.label() { if let Some(node_label) = node.label() {
let items = self.jump_info.iter().rev().filter(|info| info.is_loop()); 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 { for info in items {
if info.label() == Some(node_label) { if info.label() == Some(node_label) {
break; break;
} }
if info.for_of_in_loop() { if info.iterator_loop() {
emit_for_of_in_exit += 1; iterator_closes.push(info.for_await_of_loop());
} }
} }
for _ in 0..emit_for_of_in_exit { for r#async in iterator_closes {
self.emit_opcode(Opcode::Pop); self.iterator_close(r#async);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
} }
let (cont_label, set_label) = self.emit_opcode_with_two_operands(Opcode::Continue); 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() { } else if let Some(node_label) = node.label() {
let items = self.jump_info.iter().rev().filter(|info| info.is_loop()); 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 { for info in items {
if info.label() == Some(node_label) { if info.label() == Some(node_label) {
break; break;
} }
if info.for_of_in_loop() { if info.iterator_loop() {
emit_for_of_in_exit += 1; iterator_closes.push(info.for_await_of_loop());
} }
} }
for _ in 0..emit_for_of_in_exit { for r#async in iterator_closes {
self.emit_opcode(Opcode::Pop); self.iterator_close(r#async);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
} }
let (cont_label, set_label) = self.emit_opcode_with_two_operands(Opcode::Continue); 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(); let early_exit = self.jump_if_null_or_undefined();
self.emit_opcode(Opcode::CreateForInIterator); 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(); let start_address = self.next_opcode_location();
self.push_loop_control_info_for_of_in_loop(label, start_address); self.push_loop_control_info_for_of_in_loop(label, start_address);
self.emit_opcode(Opcode::LoopContinue); self.emit_opcode(Opcode::LoopContinue);
self.patch_jump_with_target(loop_start, start_address); self.patch_jump_with_target(loop_start, start_address);
self.emit_opcode(Opcode::Pop); // pop the `done` value.
self.emit_opcode(Opcode::IteratorNext); 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() { let iteration_environment = if initializer_bound_names.is_empty() {
None None
@ -245,11 +247,8 @@ impl ByteCompiler<'_, '_> {
self.patch_jump(exit_label); self.patch_jump(exit_label);
self.pop_loop_control_info(); self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd); self.emit_opcode(Opcode::LoopEnd);
self.emit_opcode(Opcode::RotateRight);
self.emit_u8(4); self.iterator_close(false);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
self.emit_opcode(Opcode::Pop);
let skip_early_exit = self.jump(); let skip_early_exit = self.jump();
self.patch_jump(early_exit); self.patch_jump(early_exit);
@ -293,21 +292,27 @@ impl ByteCompiler<'_, '_> {
} else { } else {
self.emit_opcode(Opcode::GetIterator); 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(); 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.push_loop_control_info_for_of_in_loop(label, start_address);
}
self.emit_opcode(Opcode::LoopContinue); self.emit_opcode(Opcode::LoopContinue);
self.patch_jump_with_target(loop_start, start_address); self.patch_jump_with_target(loop_start, start_address);
self.emit_opcode(Opcode::Pop); // pop the `done` value.
self.emit_opcode(Opcode::IteratorNext); self.emit_opcode(Opcode::IteratorNext);
if for_of_loop.r#await() { if for_of_loop.r#await() {
self.emit_opcode(Opcode::IteratorResult);
self.emit_opcode(Opcode::Await); self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::IteratorFinishAsyncNext);
self.emit_opcode(Opcode::GeneratorNext); 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() { let iteration_environment = if initializer_bound_names.is_empty() {
None None
@ -388,9 +393,9 @@ impl ByteCompiler<'_, '_> {
self.patch_jump(loop_exit); self.patch_jump(loop_exit);
self.pop_loop_control_info(); self.pop_loop_control_info();
self.emit_opcode(Opcode::LoopEnd); self.emit_opcode(Opcode::LoopEnd);
self.emit_opcode(Opcode::RotateRight);
self.emit_u8(4);
self.iterator_close(for_of_loop.r#await()); self.iterator_close(for_of_loop.r#await());
if !use_expr { if !use_expr {
self.emit_opcode(Opcode::Pop); self.emit_opcode(Opcode::Pop);
} }

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

@ -51,6 +51,10 @@ impl ByteCompiler<'_, '_> {
Statement::Return(ret) => { Statement::Return(ret) => {
if let Some(expr) = ret.target() { if let Some(expr) = ret.target() {
self.compile_expr(expr, true); self.compile_expr(expr, true);
if self.in_async_generator {
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext);
}
} else { } else {
self.emit(Opcode::PushUndefined, &[]); 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 crate::{js_string, vm::Opcode};
use super::{ByteCompiler, Literal}; use super::{ByteCompiler, Literal};
@ -10,36 +8,21 @@ impl ByteCompiler<'_, '_> {
/// This is equivalent to the [`IteratorClose`][iter] and [`AsyncIteratorClose`][async] /// This is equivalent to the [`IteratorClose`][iter] and [`AsyncIteratorClose`][async]
/// operations. /// operations.
/// ///
/// Stack: /// Iterator Stack:
/// - iterator, `next_method`, done **=>** \<empty\> /// - iterator **=>** \<empty\>
/// ///
/// [iter]: https://tc39.es/ecma262/#sec-iteratorclose /// [iter]: https://tc39.es/ecma262/#sec-iteratorclose
/// [async]: https://tc39.es/ecma262/#sec-asynciteratorclose /// [async]: https://tc39.es/ecma262/#sec-asynciteratorclose
pub(super) fn iterator_close(&mut self, async_: bool) { pub(super) fn iterator_close(&mut self, async_: bool) {
// Need to remove `next_method` to manipulate the iterator self.emit_opcode(Opcode::IteratorDone);
self.emit_opcode(Opcode::Swap);
self.emit_opcode(Opcode::Pop);
let skip_iter_pop = self.jump_if_false();
// `iterator` is done, we can skip calling `return`. let skip_return = self.jump_if_true();
// `iterator` is still in the stack, so pop it to cleanup.
self.emit_opcode(Opcode::Pop);
let skip_return = self.jump();
// iterator didn't finish iterating. // iterator didn't finish iterating.
self.patch_jump(skip_iter_pop); self.emit_opcode(Opcode::IteratorReturn);
let index = self.get_or_insert_name(Sym::RETURN.into());
self.emit(Opcode::GetMethod, &[index]);
let skip_jump = self.jump_if_not_undefined();
// `iterator` didn't have a `return` method, so we can early exit. // `iterator` didn't have a `return` method, so we can early exit.
// `iterator` is still in the stack, so pop it to cleanup. let early_exit = self.jump_if_false();
self.emit_opcode(Opcode::Pop);
let early_exit = self.jump();
self.patch_jump(skip_jump);
self.emit(Opcode::Call, &[0]);
if async_ { if async_ {
self.emit_opcode(Opcode::Await); self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext); self.emit_opcode(Opcode::GeneratorNext);
@ -53,7 +36,71 @@ impl ByteCompiler<'_, '_> {
self.emit(Opcode::ThrowNewTypeError, &[error_msg]); self.emit(Opcode::ThrowNewTypeError, &[error_msg]);
self.patch_jump(skip_return); self.patch_jump(skip_return);
self.emit_opcode(Opcode::IteratorPop);
self.patch_jump(skip_throw); self.patch_jump(skip_throw);
self.patch_jump(early_exit); 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 { impl JobQueue for SimpleJobQueue {
fn enqueue_promise_job(&self, job: NativeJob, _: &mut Context<'_>) { 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); 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 control_flow;
mod env; mod env;
mod function; mod function;
mod iterators;
mod operators; mod operators;
mod promise; mod promise;
mod spread; 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 { pub(crate) enum EnvEntryKind {
Global, Global,
Loop { 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, iteration_count: u64,
// This is the latest return value of the loop. /// The latest return value of the loop.
value: JsValue, value: JsValue,
/// The index of the currently active iterator.
iterator: Option<u32>,
}, },
Try, Try,
Catch, Catch,
@ -71,12 +74,24 @@ impl EnvStackEntry {
self 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`. /// Returns calling `EnvStackEntry` with `kind` field of `Loop`.
/// And the loop iteration set to zero. /// And the loop iteration set to zero.
pub(crate) fn with_loop_flag(mut self, iteration_count: u64) -> Self { pub(crate) fn with_loop_flag(mut self, iteration_count: u64) -> Self {
self.kind = EnvEntryKind::Loop { self.kind = EnvEntryKind::Loop {
iteration_count, iteration_count,
value: JsValue::undefined(), value: JsValue::undefined(),
iterator: None,
}; };
self self
} }
@ -150,6 +165,14 @@ impl EnvStackEntry {
None 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 /// Returns true if an `EnvStackEntry` is a try block
pub(crate) fn is_try_env(&self) -> bool { pub(crate) fn is_try_env(&self) -> bool {
self.kind == EnvEntryKind::Try self.kind == EnvEntryKind::Try

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

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

55
boa_engine/src/vm/code_block.rs

@ -317,10 +317,7 @@ impl CodeBlock {
| Opcode::Call | Opcode::Call
| Opcode::New | Opcode::New
| Opcode::SuperCall | Opcode::SuperCall
| Opcode::IteratorUnwrapNextOrJump | Opcode::ConcatToString => {
| Opcode::ConcatToString
| Opcode::GeneratorAsyncResumeYield
| Opcode::GeneratorNextDelegate => {
let result = self.read::<u32>(*pc).to_string(); let result = self.read::<u32>(*pc).to_string();
*pc += size_of::<u32>(); *pc += size_of::<u32>();
result result
@ -334,9 +331,10 @@ impl CodeBlock {
| Opcode::Break | Opcode::Break
| Opcode::Continue | Opcode::Continue
| Opcode::LoopStart | Opcode::LoopStart
| Opcode::IteratorLoopStart
| Opcode::TryStart | Opcode::TryStart
| Opcode::AsyncGeneratorNext | Opcode::GeneratorDelegateNext
| Opcode::GeneratorAsyncDelegateNext => { | Opcode::GeneratorDelegateResume => {
let operand1 = self.read::<u32>(*pc); let operand1 = self.read::<u32>(*pc);
*pc += size_of::<u32>(); *pc += size_of::<u32>();
let operand2 = self.read::<u32>(*pc); let operand2 = self.read::<u32>(*pc);
@ -350,15 +348,6 @@ impl CodeBlock {
*pc += size_of::<u64>(); *pc += size_of::<u64>();
format!("{operand1}, {operand2}") 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::GetArrowFunction
| Opcode::GetAsyncArrowFunction | Opcode::GetAsyncArrowFunction
| Opcode::GetFunction | Opcode::GetFunction
@ -373,6 +362,7 @@ impl CodeBlock {
} }
Opcode::GetGenerator | Opcode::GetGeneratorAsync => { Opcode::GetGenerator | Opcode::GetGeneratorAsync => {
let operand = self.read::<u32>(*pc); let operand = self.read::<u32>(*pc);
*pc += size_of::<u32>();
format!( format!(
"{operand:04}: '{}' (length: {})", "{operand:04}: '{}' (length: {})",
interner.resolve_expect(self.functions[operand as usize].name), interner.resolve_expect(self.functions[operand as usize].name),
@ -439,6 +429,20 @@ impl CodeBlock {
*pc += size_of::<u32>() * (count as usize + 1); *pc += size_of::<u32>() * (count as usize + 1);
String::new() 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::Pop
| Opcode::PopIfThrown | Opcode::PopIfThrown
| Opcode::Dup | Opcode::Dup
@ -522,12 +526,14 @@ impl CodeBlock {
| Opcode::GetAsyncIterator | Opcode::GetAsyncIterator
| Opcode::GeneratorResumeReturn | Opcode::GeneratorResumeReturn
| Opcode::IteratorNext | Opcode::IteratorNext
| Opcode::IteratorNextSetDone | Opcode::IteratorFinishAsyncNext
| Opcode::IteratorUnwrapNext | Opcode::IteratorValue
| Opcode::IteratorUnwrapValue | Opcode::IteratorResult
| Opcode::IteratorDone
| Opcode::IteratorToArray | Opcode::IteratorToArray
| Opcode::IteratorClosePush | Opcode::IteratorPop
| Opcode::IteratorClosePop | Opcode::IteratorReturn
| Opcode::IteratorStackEmpty
| Opcode::RequireObjectCoercible | Opcode::RequireObjectCoercible
| Opcode::ValueNotNullOrUndefined | Opcode::ValueNotNullOrUndefined
| Opcode::RestParameterInit | Opcode::RestParameterInit
@ -538,8 +544,10 @@ impl CodeBlock {
| Opcode::PushNewArray | Opcode::PushNewArray
| Opcode::PopOnReturnAdd | Opcode::PopOnReturnAdd
| Opcode::PopOnReturnSub | Opcode::PopOnReturnSub
| Opcode::Yield | Opcode::GeneratorYield
| Opcode::AsyncGeneratorYield
| Opcode::GeneratorNext | Opcode::GeneratorNext
| Opcode::GeneratorSetReturn
| Opcode::PushClassField | Opcode::PushClassField
| Opcode::SuperCallDerived | Opcode::SuperCallDerived
| Opcode::Await | Opcode::Await
@ -609,10 +617,7 @@ impl CodeBlock {
| Opcode::Reserved50 | Opcode::Reserved50
| Opcode::Reserved51 | Opcode::Reserved51
| Opcode::Reserved52 | Opcode::Reserved52
| Opcode::Reserved53 | Opcode::Reserved53 => unreachable!("Reserved opcodes are unrechable"),
| Opcode::Reserved54
| Opcode::Reserved55
| Opcode::Reserved56 => 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. //! An implementation of a `CompletionRecord` for Boa's VM.
use boa_gc::{custom_trace, Finalize, Trace};
use crate::{JsError, JsResult, JsValue}; use crate::{JsError, JsResult, JsValue};
/// An implementation of the ECMAScript's `CompletionRecord` [specification] for /// An implementation of the ECMAScript's `CompletionRecord` [specification] for
/// Boa's VM output Completion and Result. /// Boa's VM output Completion and Result.
/// ///
/// [specification]: https://tc39.es/ecma262/#sec-completion-record-specification-type /// [specification]: https://tc39.es/ecma262/#sec-completion-record-specification-type
#[derive(Debug, Clone)] #[derive(Debug, Clone, Finalize)]
pub(crate) enum CompletionRecord { pub(crate) enum CompletionRecord {
Normal(JsValue), Normal(JsValue),
Return(JsValue), Return(JsValue),
Throw(JsError), 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 ---- // ---- `CompletionRecord` methods ----
impl CompletionRecord { impl CompletionRecord {
pub(crate) const fn is_throw_completion(&self) -> bool { 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}; use crate::vm::flowgraph::{Color, Edge, EdgeStyle, EdgeType, Node, NodeShape};
/// This represents the direction of flow in the flowgraph. /// This represents the direction of flow in the flowgraph.
@ -95,7 +100,10 @@ impl SubGraph {
/// Format into the graphviz format. /// Format into the graphviz format.
fn graphviz_format(&self, result: &mut String, prefix: &str) { 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("\t\tstyle = filled;\n");
result.push_str(&format!( result.push_str(&format!(
"\t\tlabel = \"{}\";\n", "\t\tlabel = \"{}\";\n",
@ -107,13 +115,11 @@ impl SubGraph {
)); ));
result.push_str(&format!( result.push_str(&format!(
"\t\t{prefix}_{}_start [label=\"Start\",shape=Mdiamond,style=filled,color=green]\n", "\t\t{prefix}_{label}_start [label=\"Start\",shape=Mdiamond,style=filled,color=green]\n"
self.label
)); ));
if !self.nodes.is_empty() { if !self.nodes.is_empty() {
result.push_str(&format!( result.push_str(&format!(
"\t\t{prefix}_{}_start -> {prefix}_{}_i_0\n", "\t\t{prefix}_{label}_start -> {prefix}_{label}_i_0\n"
self.label, self.label
)); ));
} }
@ -126,7 +132,7 @@ impl SubGraph {
let color = format!(",style=filled,color=\"{}\"", node.color); let color = format!(",style=filled,color=\"{}\"", node.color);
result.push_str(&format!( result.push_str(&format!(
"\t\t{prefix}_{}_i_{}[label=\"{:04}: {}\"{shape}{color}];\n", "\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!( result.push_str(&format!(
"\t\t{prefix}_{}_i_{} -> {prefix}_{}_i_{} [label=\"{}\", len=f{style}{color}];\n", "\t\t{prefix}_{}_i_{} -> {prefix}_{}_i_{} [label=\"{}\", len=f{style}{color}];\n",
self.label, label,
edge.from, edge.from,
self.label, label,
edge.to, edge.to,
edge.label.as_deref().unwrap_or("") edge.label.as_deref().unwrap_or("")
)); ));
@ -158,6 +164,9 @@ impl SubGraph {
/// Format into the mermaid format. /// Format into the mermaid format.
fn mermaid_format(&self, result: &mut String, prefix: &str) { 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 { let rankdir = match self.direction {
Direction::TopToBottom => "TB", Direction::TopToBottom => "TB",
Direction::BottomToTop => "BT", Direction::BottomToTop => "BT",
@ -166,7 +175,7 @@ impl SubGraph {
}; };
result.push_str(&format!( result.push_str(&format!(
" subgraph {prefix}_{}[\"{}\"]\n", " subgraph {prefix}_{}[\"{}\"]\n",
self.label, label,
if self.label.is_empty() { if self.label.is_empty() {
"Anonymous Function" "Anonymous Function"
} else { } else {
@ -175,15 +184,11 @@ impl SubGraph {
)); ));
result.push_str(&format!(" direction {rankdir}\n")); result.push_str(&format!(" direction {rankdir}\n"));
result.push_str(&format!(" {prefix}_{}_start{{Start}}\n", self.label)); result.push_str(&format!(" {prefix}_{label}_start{{Start}}\n"));
result.push_str(&format!( result.push_str(&format!(" style {prefix}_{label}_start fill:green\n"));
" style {prefix}_{}_start fill:green\n",
self.label
));
if !self.nodes.is_empty() { if !self.nodes.is_empty() {
result.push_str(&format!( result.push_str(&format!(
" {prefix}_{}_start --> {prefix}_{}_i_0\n", " {prefix}_{label}_start --> {prefix}_{label}_i_0\n"
self.label, self.label
)); ));
} }
@ -194,12 +199,12 @@ impl SubGraph {
}; };
result.push_str(&format!( result.push_str(&format!(
" {prefix}_{}_i_{}{shape_begin}\"{:04}: {}\"{shape_end}\n", " {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() { if !node.color.is_none() {
result.push_str(&format!( result.push_str(&format!(
" style {prefix}_{}_i_{} fill:{}\n", " 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!( result.push_str(&format!(
" {prefix}_{}_i_{} {style}| {}| {prefix}_{}_i_{}\n", " {prefix}_{}_i_{} {style}| {}| {prefix}_{}_i_{}\n",
self.label, label,
edge.from, edge.from,
edge.label.as_deref().unwrap_or(""), edge.label.as_deref().unwrap_or(""),
self.label, label,
edge.to, 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_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, pc, None, Color::None, EdgeStyle::Line);
} }
Opcode::RotateLeft | Opcode::RotateRight => { Opcode::RotateLeft | Opcode::RotateRight | Opcode::CreateIteratorResult => {
pc += size_of::<u8>(); pc += size_of::<u8>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); 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, 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_node(previous_pc, NodeShape::None, label.into(), Color::Red);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
} }
Opcode::LoopStart => { Opcode::LoopStart | Opcode::IteratorLoopStart => {
let start_address = self.read::<u32>(pc); pc += size_of::<u32>() * 2;
pc += size_of::<u32>();
let end_address = self.read::<u32>(pc);
pc += size_of::<u32>();
let label = format!("{opcode_str} {start_address}, {end_address}");
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::Red);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); 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_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, address, None, Color::None, EdgeStyle::Line); graph.add_edge(previous_pc, address, None, Color::None, EdgeStyle::Line);
} }
Opcode::IteratorUnwrapNextOrJump Opcode::GeneratorDelegateNext => {
| Opcode::GeneratorAsyncResumeYield let throw_method_undefined = self.read::<u32>(pc) as usize;
| Opcode::GeneratorNextDelegate => {
let address = self.read::<u32>(pc) as usize;
pc += size_of::<u32>(); pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); let return_method_undefined = self.read::<u32>(pc) as usize;
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); pc += size_of::<u32>();
graph.add_edge( graph.add_node(
previous_pc, previous_pc,
address, NodeShape::Diamond,
Some("DONE".into()), opcode_str.into(),
Color::None, Color::None,
EdgeStyle::Line,
); );
} graph.add_edge(previous_pc, pc, None, 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( graph.add_edge(
previous_pc, previous_pc,
throw_method_undefined, throw_method_undefined,
Some("throw method undefined".into()), Some("`throw` undefined".into()),
Color::None, Color::Red,
EdgeStyle::Line, EdgeStyle::Line,
); );
graph.add_edge( graph.add_edge(
previous_pc, previous_pc,
return_method_undefined, return_method_undefined,
Some("return method undefined".into()), Some("`return` undefined".into()),
Color::None, Color::Blue,
EdgeStyle::Line, EdgeStyle::Line,
); );
} }
Opcode::GeneratorAsyncDelegateResume => { Opcode::GeneratorDelegateResume => {
self.read::<u32>(pc); let return_gen = self.read::<u32>(pc) as usize;
self.read::<u32>(pc); pc += size_of::<u32>();
let exit = self.read::<u32>(pc) as usize; let exit = self.read::<u32>(pc) as usize;
pc += size_of::<u32>(); 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( graph.add_edge(
previous_pc, previous_pc,
exit, exit,
Some("DONE".into()), Some("done".into()),
Color::None, Color::Blue,
EdgeStyle::Line, EdgeStyle::Line,
); );
} }
@ -281,31 +283,45 @@ impl CodeBlock {
| Opcode::Call | Opcode::Call
| Opcode::New | Opcode::New
| Opcode::SuperCall | Opcode::SuperCall
| Opcode::ConcatToString => { | Opcode::ConcatToString
| Opcode::FinallyStart => {
pc += size_of::<u32>(); pc += size_of::<u32>();
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); 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, pc, None, Color::None, EdgeStyle::Line);
} }
Opcode::AsyncGeneratorNext => { Opcode::GeneratorJumpOnResumeKind => {
let skip_yield = self.read::<u32>(pc); let normal = self.read::<u32>(pc);
pc += size_of::<u32>();
let throw = self.read::<u32>(pc);
pc += size_of::<u32>(); pc += size_of::<u32>();
let skip_yield_await = self.read::<u32>(pc); let r#return = self.read::<u32>(pc);
pc += size_of::<u32>(); 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( graph.add_edge(
previous_pc, previous_pc,
skip_yield as usize, normal as usize,
Some("return value pending".into()), None,
Color::None, Color::None,
EdgeStyle::Line, EdgeStyle::Line,
); );
graph.add_edge( graph.add_edge(
previous_pc, previous_pc,
skip_yield_await as usize, throw as usize,
Some("yield value pending".into()), Some("throw".into()),
Color::None, Color::Red,
EdgeStyle::Line,
);
graph.add_edge(
previous_pc,
r#return as usize,
Some("return".into()),
Color::Yellow,
EdgeStyle::Line, EdgeStyle::Line,
); );
} }
@ -465,7 +481,22 @@ impl CodeBlock {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); 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, 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); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
if let Some((_try_pc, next, _finally)) = try_entries.last() { if let Some((_try_pc, next, _finally)) = try_entries.last() {
graph.add_edge( graph.add_edge(
@ -475,6 +506,8 @@ impl CodeBlock {
Color::None, Color::None,
EdgeStyle::Line, EdgeStyle::Line,
); );
} else {
returns.push(previous_pc);
} }
} }
Opcode::PushPrivateEnvironment => { Opcode::PushPrivateEnvironment => {
@ -550,7 +583,6 @@ impl CodeBlock {
| Opcode::ToBoolean | Opcode::ToBoolean
| Opcode::CatchEnd | Opcode::CatchEnd
| Opcode::CatchEnd2 | Opcode::CatchEnd2
| Opcode::FinallyStart
| Opcode::FinallyEnd | Opcode::FinallyEnd
| Opcode::This | Opcode::This
| Opcode::Super | Opcode::Super
@ -562,12 +594,14 @@ impl CodeBlock {
| Opcode::GetIterator | Opcode::GetIterator
| Opcode::GetAsyncIterator | Opcode::GetAsyncIterator
| Opcode::IteratorNext | Opcode::IteratorNext
| Opcode::IteratorNextSetDone | Opcode::IteratorFinishAsyncNext
| Opcode::IteratorUnwrapNext | Opcode::IteratorValue
| Opcode::IteratorUnwrapValue | Opcode::IteratorResult
| Opcode::IteratorDone
| Opcode::IteratorToArray | Opcode::IteratorToArray
| Opcode::IteratorClosePush | Opcode::IteratorPop
| Opcode::IteratorClosePop | Opcode::IteratorReturn
| Opcode::IteratorStackEmpty
| Opcode::RequireObjectCoercible | Opcode::RequireObjectCoercible
| Opcode::ValueNotNullOrUndefined | Opcode::ValueNotNullOrUndefined
| Opcode::RestParameterInit | Opcode::RestParameterInit
@ -578,7 +612,8 @@ impl CodeBlock {
| Opcode::PushNewArray | Opcode::PushNewArray
| Opcode::PopOnReturnAdd | Opcode::PopOnReturnAdd
| Opcode::PopOnReturnSub | Opcode::PopOnReturnSub
| Opcode::Yield | Opcode::GeneratorYield
| Opcode::AsyncGeneratorYield
| Opcode::GeneratorNext | Opcode::GeneratorNext
| Opcode::PushClassField | Opcode::PushClassField
| Opcode::SuperCallDerived | Opcode::SuperCallDerived
@ -596,6 +631,7 @@ impl CodeBlock {
| Opcode::PushObjectEnvironment | Opcode::PushObjectEnvironment
| Opcode::PopPrivateEnvironment | Opcode::PopPrivateEnvironment
| Opcode::ImportCall | Opcode::ImportCall
| Opcode::GeneratorSetReturn
| Opcode::Nop => { | Opcode::Nop => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); 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, pc, None, Color::None, EdgeStyle::Line);
@ -674,10 +710,7 @@ impl CodeBlock {
| Opcode::Reserved50 | Opcode::Reserved50
| Opcode::Reserved51 | Opcode::Reserved51
| Opcode::Reserved52 | Opcode::Reserved52
| Opcode::Reserved53 | Opcode::Reserved53 => unreachable!("Reserved opcodes are unrechable"),
| Opcode::Reserved54
| Opcode::Reserved55
| Opcode::Reserved56 => unreachable!("Reserved opcodes are unrechable"),
} }
} }

6
boa_engine/src/vm/mod.rs

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

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

@ -1,9 +1,11 @@
use crate::{ use crate::{
js_string, vm::{
vm::{call_frame::AbruptCompletionRecord, opcode::Operation, CompletionType}, call_frame::{AbruptCompletionRecord, EnvStackEntry},
Context, JsError, JsNativeError, JsResult, opcode::Operation,
CompletionType,
},
Context, JsError, JsNativeError, JsResult, JsValue,
}; };
use thin_vec::ThinVec;
/// `Throw` implements the Opcode Operation for `Opcode::Throw` /// `Throw` implements the Opcode Operation for `Opcode::Throw`
/// ///
@ -23,30 +25,34 @@ impl Operation for Throw {
JsError::from_opaque(context.vm.pop()) 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 // 1. Find the viable catch and finally blocks
let current_address = context.vm.frame().pc; let current_address = context.vm.frame().pc;
let viable_catch_candidates = context let mut envs = context.vm.frame().env_stack.iter();
.vm
.frame() if let Some(idx) =
.env_stack envs.rposition(|env| env.is_try_env() && env.start_address() < env.exit_address())
{
let active_iterator = context.vm.frame().env_stack[..idx]
.iter() .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 = context.vm.frame().env_stack[idx].start_address();
let catch_target = candidate.start_address();
let mut env_to_pop = 0; let mut env_to_pop = 0;
let mut target_address = u32::MAX; let mut target_address = u32::MAX;
@ -126,6 +132,29 @@ impl Operation for Throw {
context.vm.frame_mut().env_stack.pop(); 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); let env_truncation_len = context.vm.environments.len().saturating_sub(env_to_pop);
context.vm.environments.truncate(env_truncation_len); context.vm.environments.truncate(env_truncation_len);
@ -143,6 +172,14 @@ impl Operation for Throw {
return Ok(CompletionType::Normal); 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); context.vm.err = Some(error);
Ok(CompletionType::Throw) Ok(CompletionType::Throw)
} }

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

@ -1,18 +1,14 @@
pub(crate) mod yield_stm; pub(crate) mod yield_stm;
use crate::{ use crate::{
builtins::{
async_generator::{AsyncGenerator, AsyncGeneratorState},
iterable::IteratorRecord,
},
error::JsNativeError, error::JsNativeError,
string::utf16, string::utf16,
vm::{ vm::{
call_frame::{AbruptCompletionRecord, GeneratorResumeKind}, call_frame::GeneratorResumeKind,
opcode::{control_flow::Return, Operation}, opcode::{control_flow::Return, Operation},
CompletionType, CompletionType,
}, },
Context, JsError, JsResult, JsValue, Context, JsError, JsResult,
}; };
pub(crate) use yield_stm::*; pub(crate) use yield_stm::*;
@ -32,116 +28,51 @@ impl Operation for GeneratorNext {
match context.vm.frame().generator_resume_kind { match context.vm.frame().generator_resume_kind {
GeneratorResumeKind::Normal => Ok(CompletionType::Normal), GeneratorResumeKind::Normal => Ok(CompletionType::Normal),
GeneratorResumeKind::Throw => Err(JsError::from_opaque(context.vm.pop())), GeneratorResumeKind::Throw => Err(JsError::from_opaque(context.vm.pop())),
GeneratorResumeKind::Return => { GeneratorResumeKind::Return => Return::execute(context),
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)
}
} }
} }
} }
/// `AsyncGeneratorNext` implements the Opcode Operation for `Opcode::AsyncGeneratorNext` /// `GeneratorJumpOnResumeKind` implements the Opcode Operation for
/// `Opcode::GeneratorJumpOnResumeKind`
/// ///
/// Operation: /// Operation:
/// - Resumes the current generator function. /// - Jumps to the specified instruction if the current resume kind is `Return`.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct AsyncGeneratorNext; pub(crate) struct GeneratorJumpOnResumeKind;
impl Operation for AsyncGeneratorNext { impl Operation for GeneratorJumpOnResumeKind {
const NAME: &'static str = "AsyncGeneratorNext"; const NAME: &'static str = "GeneratorJumpOnResumeKind";
const INSTRUCTION: &'static str = "INST - AsyncGeneratorNext"; const INSTRUCTION: &'static str = "INST - GeneratorJumpOnResumeKind";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let skip_yield = context.vm.read::<u32>(); let normal = context.vm.read::<u32>();
let skip_yield_await = context.vm.read::<u32>(); let throw = context.vm.read::<u32>();
let r#return = context.vm.read::<u32>();
if context.vm.frame().generator_resume_kind == GeneratorResumeKind::Throw { match context.vm.frame().generator_resume_kind {
return Err(JsError::from_opaque(context.vm.pop())); 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,
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;
} }
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }
/// `GeneratorAsyncResumeYield` implements the Opcode Operation for `Opcode::GeneratorAsyncResumeYield` /// `GeneratorSetReturn` implements the Opcode Operation for `Opcode::GeneratorSetReturn`
/// ///
/// Operation: /// Operation:
/// - Resumes the current async generator function after a yield. /// - Sets the current generator resume kind to `Return`.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct GeneratorAsyncResumeYield; pub(crate) struct GeneratorSetReturn;
impl Operation for GeneratorAsyncResumeYield { impl Operation for GeneratorSetReturn {
const NAME: &'static str = "GeneratorAsyncResumeYield"; const NAME: &'static str = "GeneratorSetReturn";
const INSTRUCTION: &'static str = "INST - GeneratorAsyncResumeYield"; const INSTRUCTION: &'static str = "INST - GeneratorSetReturn";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let normal_completion = context.vm.read::<u32>(); context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Return;
match context.vm.frame().generator_resume_kind {
GeneratorResumeKind::Normal => {
context.vm.frame_mut().pc = normal_completion;
Ok(CompletionType::Normal) 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` /// `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: /// Operation:
/// - Delegates the current generator function another generator. /// - Delegates the current generator function to another iterator.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct GeneratorNextDelegate; pub(crate) struct GeneratorDelegateNext;
impl Operation for GeneratorNextDelegate { impl Operation for GeneratorDelegateNext {
const NAME: &'static str = "GeneratorNextDelegate"; const NAME: &'static str = "GeneratorDelegateNext";
const INSTRUCTION: &'static str = "INST - GeneratorNextDelegate"; const INSTRUCTION: &'static str = "INST - GeneratorDelegateNext";
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";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let throw_method_undefined = context.vm.read::<u32>(); let throw_method_undefined = context.vm.read::<u32>();
let return_method_undefined = context.vm.read::<u32>(); let return_method_undefined = context.vm.read::<u32>();
let received = context.vm.pop(); 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 => { GeneratorResumeKind::Normal => {
let result_value = let result = iterator_record.next_method().call(
next_method.call(&iterator.clone().into(), &[received], context)?; &iterator_record.iterator().clone().into(),
context.vm.push(iterator.clone()); &[received],
context.vm.push(next_method.clone()); context,
)?;
context.vm.push(false); context.vm.push(false);
context.vm.push(result_value); context.vm.push(result);
Ok(CompletionType::Normal)
} }
GeneratorResumeKind::Throw => { 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 { if let Some(throw) = throw {
let result = throw.call(&iterator.clone().into(), &[received], context)?; let result = throw.call(
context.vm.push(iterator.clone()); &iterator_record.iterator().clone().into(),
context.vm.push(next_method.clone()); &[received],
context,
)?;
context.vm.push(false); context.vm.push(false);
context.vm.push(result); context.vm.push(result);
} else { } else {
@ -299,111 +146,80 @@ impl Operation for GeneratorAsyncDelegateNext {
.with_message("iterator does not have a throw method") .with_message("iterator does not have a throw method")
.to_opaque(context); .to_opaque(context);
context.vm.push(error); 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; context.vm.frame_mut().pc = throw_method_undefined;
} }
Ok(CompletionType::Normal)
} }
GeneratorResumeKind::Return => { 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 { if let Some(r#return) = r#return {
let result = r#return.call(&iterator.clone().into(), &[received], context)?; let result = r#return.call(
context.vm.push(iterator.clone()); &iterator_record.iterator().clone().into(),
context.vm.push(next_method.clone()); &[received],
context,
)?;
context.vm.push(true); context.vm.push(true);
context.vm.push(result); context.vm.push(result);
return Ok(CompletionType::Normal); } else {
}
context.vm.push(iterator.clone());
context.vm.push(next_method.clone());
context.vm.push(received); context.vm.push(received);
context.vm.frame_mut().pc = return_method_undefined; 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: /// 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)] #[derive(Debug, Clone, Copy)]
pub(crate) struct GeneratorAsyncDelegateResume; pub(crate) struct GeneratorDelegateResume;
impl Operation for GeneratorAsyncDelegateResume { impl Operation for GeneratorDelegateResume {
const NAME: &'static str = "GeneratorAsyncDelegateResume"; const NAME: &'static str = "GeneratorDelegateResume";
const INSTRUCTION: &'static str = "INST - GeneratorAsyncDelegateResume"; const INSTRUCTION: &'static str = "INST - GeneratorDelegateResume";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let skip_yield = context.vm.read::<u32>(); let return_gen = context.vm.read::<u32>();
let normal_completion = context.vm.read::<u32>();
let exit = 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 { 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(); iterator.update_result(result, context)?;
let is_return = context.vm.pop().to_boolean();
let result = received.as_object().ok_or_else(|| { if iterator.done() {
JsNativeError::typ().with_message("generator next method returned non-object") let value = iterator.value(context)?;
})?;
let done = result.get(utf16!("done"), context)?.to_boolean();
let value = result.get(utf16!("value"), context)?;
if done {
context.vm.push(value); context.vm.push(value);
if is_return { context.vm.frame_mut().pc = if is_return { return_gen } else { exit };
return Return::execute(context);
}
context.vm.frame_mut().pc = exit;
return Ok(CompletionType::Normal); return Ok(CompletionType::Normal);
} }
let completion = Ok(value); context.vm.frame_mut().iterators.push(iterator);
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;
}
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }

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

@ -1,25 +1,87 @@
use crate::{ use crate::{
builtins::iterable::create_iter_result_object, builtins::async_generator::{AsyncGenerator, AsyncGeneratorState},
vm::{opcode::Operation, CompletionType}, vm::{opcode::Operation, CompletionRecord, CompletionType, GeneratorResumeKind},
Context, JsResult, Context, JsResult, JsValue,
}; };
/// `Yield` implements the Opcode Operation for `Opcode::Yield` /// `GeneratorYield` implements the Opcode Operation for `Opcode::GeneratorYield`
/// ///
/// Operation: /// Operation:
/// - Yield from the current execution. /// - Yield from the current generator execution.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(crate) struct Yield; pub(crate) struct GeneratorYield;
impl Operation for Yield { impl Operation for GeneratorYield {
const NAME: &'static str = "Yield"; const NAME: &'static str = "GeneratorYield";
const INSTRUCTION: &'static str = "INST - Yield"; const INSTRUCTION: &'static str = "INST - GeneratorYield";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { 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; context.vm.frame_mut().r#yield = true;
Ok(CompletionType::Return) 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::{ use crate::{
builtins::object::for_in_iterator::ForInIterator, builtins::{iterable::IteratorRecord, object::for_in_iterator::ForInIterator},
vm::{opcode::Operation, CompletionType}, vm::{opcode::Operation, CompletionType},
Context, JsResult, JsValue, Context, JsResult, JsValue,
}; };
@ -24,8 +24,12 @@ impl Operation for CreateForInIterator {
.get("next", context) .get("next", context)
.expect("ForInIterator must have a `next` method"); .expect("ForInIterator must have a `next` method");
context.vm.push(iterator); context
context.vm.push(next_method); .vm
.frame_mut()
.iterators
.push(IteratorRecord::new(iterator, next_method));
Ok(CompletionType::Normal) 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> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let object = context.vm.pop(); let object = context.vm.pop();
let iterator = object.get_iterator(context, None, None)?; let iterator = object.get_iterator(context, None, None)?;
context.vm.push(iterator.iterator().clone()); context.vm.frame_mut().iterators.push(iterator);
context.vm.push(iterator.next_method().clone());
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }
@ -38,8 +37,7 @@ impl Operation for GetAsyncIterator {
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let object = context.vm.pop(); let object = context.vm.pop();
let iterator = object.get_iterator(context, Some(IteratorHint::Async), None)?; let iterator = object.get_iterator(context, Some(IteratorHint::Async), None)?;
context.vm.push(iterator.iterator().clone()); context.vm.frame_mut().iterators.push(iterator);
context.vm.push(iterator.next_method().clone());
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }

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

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

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

@ -4,6 +4,29 @@ use crate::{
Context, JsResult, 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` /// `LoopStart` implements the Opcode Operation for `Opcode::LoopStart`
/// ///
/// Operation: /// Operation:

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

@ -1379,7 +1379,9 @@ generate_impl! {
/// Push loop start marker. /// Push loop start marker.
/// ///
/// Operands: Exit Address: `u32` /// Operands:
/// - start: `u32`
/// - exit: `u32`
/// ///
/// Stack: **=>** /// Stack: **=>**
LoopStart, LoopStart,
@ -1421,82 +1423,122 @@ generate_impl! {
/// Creates the ForInIterator of an object. /// Creates the ForInIterator of an object.
/// ///
/// Stack: object **=>** iterator, next_method /// Stack: object **=>**
///
/// Iterator Stack: `iterator`
CreateForInIterator, CreateForInIterator,
/// Push iterator loop start marker.
///
/// Operands:
/// - start: `u32`
/// - exit: `u32`
///
/// Stack: **=>**
IteratorLoopStart,
/// Gets the iterator of an object. /// Gets the iterator of an object.
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: object **=>** iterator, next_method /// Stack: object **=>**
///
/// Iterator Stack: `iterator`
GetIterator, GetIterator,
/// Gets the async iterator of an object. /// Gets the async iterator of an object.
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: object **=>** iterator, next_method /// Stack: object **=>**
///
/// Iterator Stack: `iterator`
GetAsyncIterator, 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: /// Operands:
/// ///
/// Stack: iterator, next_method **=>** iterator, next_method, next_value /// Iterator Stack: `iterator` **=>** `iterator`
IteratorNext, IteratorNext,
/// Calls the `next` method of `iterator`, puts its return value on the stack /// Returns `true` if the current iterator is done, or `false` otherwise
/// and sets the `[[Done]]` value of the iterator on the call frame.
/// ///
/// Operands: /// Stack: **=>** done
/// ///
/// Stack: iterator, next_method **=>** iterator, next_method, next_value /// Iterator Stack: `iterator` **=>** `iterator`
IteratorNextSetDone, 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 /// Stack: `next_result` **=>**
IteratorUnwrapNext, ///
/// 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 /// Iterator Stack: `iterator` **=>** `iterator`
IteratorUnwrapValue, IteratorValue,
/// Gets the `value` and `done` properties of an iterator result, or jump to `address` if /// - Gets the last iteration result of the current iterator record.
/// `done` is true.
/// ///
/// Operands: address: `u32` /// Stack: **=>** `result`
/// ///
/// Stack: next_result **=>** done, next_value ( if done != true ) /// Iterator Stack: `iterator` **=>** `iterator`
IteratorUnwrapNextOrJump, IteratorResult,
/// Consume the iterator and construct and array with all the values. /// Consume the iterator and construct and array with all the values.
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: iterator, next_method **=>** iterator, next_method, array /// Stack: **=>** array
///
/// Iterator Stack: `iterator` **=>** `iterator`
IteratorToArray, 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 /// Iterator Stack:
IteratorClosePush, /// - **=>**
IteratorStackEmpty,
/// Pop an iterator from the call frame close iterator stack. /// Creates a new iterator result object.
/// ///
/// Operands: /// Operands:
/// - done: bool (codified as u8 with `0` -> `false` and `!0` -> `true`)
/// ///
/// Stack: /// 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. /// Concat multiple stack objects into a string.
/// ///
/// Operands: value_count: `u32` /// Operands: value_count: `u32`
/// ///
/// Stack: value_1,...value_n **=>** string /// Stack: `value_1`,...`value_n` **=>** `string`
ConcatToString, ConcatToString,
/// Call RequireObjectCoercible on the stack value. /// Call RequireObjectCoercible on the stack value.
@ -1541,12 +1583,12 @@ generate_impl! {
/// Stack: **=>** /// Stack: **=>**
PopOnReturnSub, PopOnReturnSub,
/// Yield from the current execution. /// Yields from the current generator execution.
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: value **=>** /// Stack: value **=>** received
Yield, GeneratorYield,
/// Resumes the current generator function. /// Resumes the current generator function.
/// ///
@ -1562,46 +1604,49 @@ generate_impl! {
/// Stack: **=>** /// Stack: **=>**
GeneratorResumeReturn, 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>` /// Stack: value **=>** received
AsyncGeneratorNext, 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: **=>** /// Stack:
GeneratorAsyncResumeYield, 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 /// Stack:
GeneratorNextDelegate, GeneratorSetReturn,
/// Delegates the current async generator function to another iterator. /// Delegates the current async generator function to another iterator.
/// ///
/// Operands: throw_method_undefined: `u32`, return_method_undefined: `u32` /// Operands: throw_method_undefined: `u32`, return_method_undefined: `u32`
/// ///
/// Stack: iterator, next_method, received **=>** iterator, next_method, is_return, result /// Stack: received **=>** result
GeneratorAsyncDelegateNext, GeneratorDelegateNext,
/// Resume the async generator with yield delegate logic after it awaits a value. /// 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 /// Stack: is_return, received **=>** value
GeneratorAsyncDelegateResume, GeneratorDelegateResume,
/// Stops the current async function and schedules it to resume later. /// Stops the current async function and schedules it to resume later.
/// ///
/// Operands: /// Operands:
/// ///
/// Stack: promise **=>** /// Stack: promise **=>** received
Await, Await,
/// Push the current new target to the stack. /// Push the current new target to the stack.
@ -1766,12 +1811,6 @@ generate_impl! {
Reserved52 => Reserved, Reserved52 => Reserved,
/// Reserved [`Opcode`]. /// Reserved [`Opcode`].
Reserved53 => Reserved, 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::{ use crate::{
builtins::{iterable::IteratorRecord, Array}, builtins::Array,
object::ObjectData, object::ObjectData,
string::utf16, string::utf16,
vm::{opcode::Operation, CompletionType}, vm::{opcode::Operation, CompletionType},
@ -90,15 +90,17 @@ impl Operation for PushIteratorToArray {
const INSTRUCTION: &'static str = "INST - PushIteratorToArray"; const INSTRUCTION: &'static str = "INST - PushIteratorToArray";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> { fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let next_method = context.vm.pop(); let mut iterator = context
let iterator = context.vm.pop(); .vm
let iterator = iterator.as_object().expect("iterator was not an object"); .frame_mut()
.iterators
.pop()
.expect("iterator stack should have at least an iterator");
let array = context.vm.pop(); let array = context.vm.pop();
let iterator = IteratorRecord::new(iterator.clone(), next_method, false); while !iterator.step(context)? {
while let Some(next) = iterator.step(context)? { let next = iterator.value(context)?;
let next_value = next.value(context)?; Array::push(&array, &[next], context)?;
Array::push(&array, &[next_value], context)?;
} }
context.vm.push(array); context.vm.push(array);

Loading…
Cancel
Save