From 6a869880e2efa9dc31aa8c02ea98d8926b588219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= Date: Fri, 2 Jun 2023 03:43:00 +0000 Subject: [PATCH] Fix remaining async generator tests * Fix remaining async generator tests --- .vscode/launch.json | 4 +- .vscode/tasks.json | 10 ++ boa_cli/src/main.rs | 4 +- .../src/builtins/async_generator/mod.rs | 67 +++----- boa_engine/src/bytecompiler/declarations.rs | 10 +- boa_engine/src/bytecompiler/expression/mod.rs | 27 +-- boa_engine/src/bytecompiler/mod.rs | 22 +++ boa_engine/src/bytecompiler/statement/mod.rs | 4 + boa_engine/src/bytecompiler/utils.rs | 56 +++++- boa_engine/src/job.rs | 4 - boa_engine/src/vm/code_block.rs | 23 ++- boa_engine/src/vm/completion_record.rs | 16 +- boa_engine/src/vm/flowgraph/graph.rs | 45 ++--- boa_engine/src/vm/flowgraph/mod.rs | 108 ++++++++---- boa_engine/src/vm/opcode/generator/mod.rs | 161 +++--------------- .../src/vm/opcode/generator/yield_stm.rs | 86 ++++++++-- .../src/vm/opcode/iteration/iterator.rs | 25 ++- boa_engine/src/vm/opcode/mod.rs | 44 +++-- 18 files changed, 420 insertions(+), 296 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index b0de59888a7..55b75dc5abf 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -14,7 +14,7 @@ "program": "${workspaceFolder}/target/debug/boa", "args": ["${workspaceFolder}/tests/js/test.js", "--debug-object"], "sourceLanguages": ["rust"], - "preLaunchTask": "Cargo Build" + "preLaunchTask": "Cargo Build boa_cli" }, { "type": "lldb", @@ -32,7 +32,7 @@ "tests/js" ], "sourceLanguages": ["rust"], - "preLaunchTask": "Cargo Build" + "preLaunchTask": "Cargo Build boa_cli" } ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ca67f62f35b..1c0b67ad97a 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -13,6 +13,16 @@ "clear": true } }, + { + "type": "process", + "label": "Cargo Build boa_cli", + "command": "cargo", + "args": ["build", "-p", "boa_cli"], + "group": "build", + "presentation": { + "clear": true + } + }, { "type": "process", "label": "Cargo Run", diff --git a/boa_cli/src/main.rs b/boa_cli/src/main.rs index 723d8c44f01..2a4e58612ec 100644 --- a/boa_cli/src/main.rs +++ b/boa_cli/src/main.rs @@ -490,7 +490,7 @@ struct Jobs(RefCell>); impl JobQueue for Jobs { fn enqueue_promise_job(&self, job: NativeJob, _: &mut Context<'_>) { - self.0.borrow_mut().push_front(job); + self.0.borrow_mut().push_back(job); } fn run_jobs(&self, context: &mut Context<'_>) { @@ -509,6 +509,6 @@ impl JobQueue for Jobs { fn enqueue_future_job(&self, future: FutureJob, _: &mut Context<'_>) { let job = pollster::block_on(future); - self.0.borrow_mut().push_front(job); + self.0.borrow_mut().push_back(job); } } diff --git a/boa_engine/src/builtins/async_generator/mod.rs b/boa_engine/src/builtins/async_generator/mod.rs index 872767db957..22cc8f532a6 100644 --- a/boa_engine/src/builtins/async_generator/mod.rs +++ b/boa_engine/src/builtins/async_generator/mod.rs @@ -18,7 +18,7 @@ use crate::{ realm::Realm, symbol::JsSymbol, value::JsValue, - vm::GeneratorResumeKind, + vm::{CompletionRecord, GeneratorResumeKind}, Context, JsArgs, JsError, JsResult, }; use boa_gc::{Finalize, Trace}; @@ -46,7 +46,7 @@ pub(crate) enum AsyncGeneratorState { #[derive(Debug, Clone, Finalize, Trace)] pub(crate) struct AsyncGeneratorRequest { /// The `[[Completion]]` slot. - pub(crate) completion: (JsResult, bool), + pub(crate) completion: CompletionRecord, /// The `[[Capability]]` slot. capability: PromiseCapability, @@ -164,7 +164,7 @@ impl AsyncGenerator { } // 7. Let completion be NormalCompletion(value). - let completion = (Ok(args.get_or_undefined(0).clone()), false); + let completion = CompletionRecord::Normal(args.get_or_undefined(0).clone()); // 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). generator.enqueue(completion.clone(), promise_capability.clone()); @@ -232,7 +232,8 @@ impl AsyncGenerator { if_abrupt_reject_promise!(generator, promise_capability, context); // 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. - let completion = (Ok(args.get_or_undefined(0).clone()), true); + let return_value = args.get_or_undefined(0).clone(); + let completion = CompletionRecord::Return(return_value.clone()); // 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). generator.enqueue(completion.clone(), promise_capability.clone()); @@ -246,14 +247,8 @@ impl AsyncGenerator { generator.state = AsyncGeneratorState::AwaitingReturn; // b. Perform ! AsyncGeneratorAwaitReturn(generator). - let next = generator - .queue - .front() - .cloned() - .expect("queue cannot be empty here"); drop(generator_obj_mut); - let (completion, _) = &next.completion; - Self::await_return(generator_object.clone(), completion.clone(), context); + Self::await_return(generator_object.clone(), return_value, context); } // 9. Else if state is suspendedYield, then else if state == AsyncGeneratorState::SuspendedYield { @@ -346,10 +341,8 @@ impl AsyncGenerator { } // 8. Let completion be ThrowCompletion(exception). - let completion = ( - Err(JsError::from_opaque(args.get_or_undefined(0).clone())), - false, - ); + let completion = + CompletionRecord::Throw(JsError::from_opaque(args.get_or_undefined(0).clone())); // 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). generator.enqueue(completion.clone(), promise_capability.clone()); @@ -384,7 +377,7 @@ impl AsyncGenerator { /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorenqueue pub(crate) fn enqueue( &mut self, - completion: (JsResult, bool), + completion: CompletionRecord, promise_capability: PromiseCapability, ) { // 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }. @@ -469,7 +462,7 @@ impl AsyncGenerator { generator: &JsObject, state: AsyncGeneratorState, mut generator_context: GeneratorContext, - completion: (JsResult, bool), + completion: CompletionRecord, context: &mut Context<'_>, ) { // 1. Assert: generator.[[AsyncGeneratorState]] is either suspendedStart or suspendedYield. @@ -491,15 +484,9 @@ impl AsyncGenerator { .state = AsyncGeneratorState::Executing; let (value, resume_kind) = match completion { - (Ok(value), r#return) => ( - value, - if r#return { - GeneratorResumeKind::Return - } else { - GeneratorResumeKind::Normal - }, - ), - (Err(value), _) => (value.to_opaque(context), GeneratorResumeKind::Throw), + CompletionRecord::Normal(val) => (val, GeneratorResumeKind::Normal), + CompletionRecord::Return(val) => (val, GeneratorResumeKind::Return), + CompletionRecord::Throw(err) => (err.to_opaque(context), GeneratorResumeKind::Throw), }; // 6. Push genContext onto the execution context stack; genContext is now the running execution context. @@ -527,19 +514,12 @@ impl AsyncGenerator { /// - [ECMAScript reference][spec] /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn - pub(crate) fn await_return( - generator: JsObject, - completion: JsResult, - context: &mut Context<'_>, - ) { + pub(crate) fn await_return(generator: JsObject, value: JsValue, context: &mut Context<'_>) { // 1. Let queue be generator.[[AsyncGeneratorQueue]]. // 2. Assert: queue is not empty. // 3. Let next be the first element of queue. // 4. Let completion be Completion(next.[[Completion]]). - // 5. Assert: completion.[[Type]] is return. - let value = completion.expect("completion must be a return completion"); - // Note: The spec is currently broken here. // See: https://github.com/tc39/ecma262/pull/2683 @@ -681,29 +661,24 @@ impl AsyncGenerator { let next = queue.front().expect("must have entry"); // b. Let completion be Completion(next.[[Completion]]). - match &next.completion { + match next.completion.clone() { // c. If completion.[[Type]] is return, then - (completion, true) => { + CompletionRecord::Return(val) => { // i. Set generator.[[AsyncGeneratorState]] to awaiting-return. gen.state = AsyncGeneratorState::AwaitingReturn; + drop(generator_borrow_mut); // ii. Perform ! AsyncGeneratorAwaitReturn(generator). - let completion = completion.clone(); - drop(generator_borrow_mut); - Self::await_return(generator.clone(), completion, context); + Self::await_return(generator.clone(), val, context); // iii. Set done to true. break; } // d. Else, - (completion, false) => { + completion => { // i. If completion.[[Type]] is normal, then - let completion = if completion.is_ok() { - // 1. Set completion to NormalCompletion(undefined). - Ok(JsValue::undefined()) - } else { - completion.clone() - }; + // 1. Set completion to NormalCompletion(undefined). + let completion = completion.consume().map(|_| JsValue::undefined()); // ii. Perform AsyncGeneratorCompleteStep(generator, completion, true). let next = queue.pop_front().expect("must have entry"); diff --git a/boa_engine/src/bytecompiler/declarations.rs b/boa_engine/src/bytecompiler/declarations.rs index 570ed815685..0e10a4318d3 100644 --- a/boa_engine/src/bytecompiler/declarations.rs +++ b/boa_engine/src/bytecompiler/declarations.rs @@ -272,6 +272,9 @@ impl ByteCompiler<'_, '_> { self.context, ); + // Ensures global functions are printed when generating the global flowgraph. + self.functions.push(code.clone()); + // b. Let fo be InstantiateFunctionObject of f with arguments env and privateEnv. let function = if generator { create_generator_function_object(code, r#async, None, self.context) @@ -686,6 +689,9 @@ impl ByteCompiler<'_, '_> { // c. If varEnv is a Global Environment Record, then if var_environment_is_global { + // Ensures global functions are printed when generating the global flowgraph. + self.functions.push(code.clone()); + // b. Let fo be InstantiateFunctionObject of f with arguments lexEnv and privateEnv. let function = if generator { create_generator_function_object(code, r#async, None, self.context) @@ -988,7 +994,9 @@ impl ByteCompiler<'_, '_> { } if generator { self.emit_opcode(Opcode::PushUndefined); - self.emit_opcode(Opcode::Yield); + // Don't need to use `AsyncGeneratorYield` since + // we just want to stop the execution of the generator. + self.emit_opcode(Opcode::GeneratorYield); } // 27. If hasParameterExpressions is false, then diff --git a/boa_engine/src/bytecompiler/expression/mod.rs b/boa_engine/src/bytecompiler/expression/mod.rs index cdec9af016c..9ed2e4987d0 100644 --- a/boa_engine/src/bytecompiler/expression/mod.rs +++ b/boa_engine/src/bytecompiler/expression/mod.rs @@ -179,6 +179,13 @@ impl ByteCompiler<'_, '_> { let (return_gen, exit) = self.emit_opcode_with_two_operands(Opcode::GeneratorDelegateResume); + if self.in_async_generator { + self.emit_opcode(Opcode::IteratorValue); + self.async_generator_yield(); + } else { + self.emit_opcode(Opcode::IteratorResult); + self.emit_opcode(Opcode::GeneratorYield); + } self.emit(Opcode::Jump, &[start_address]); self.patch_jump(return_gen); @@ -186,7 +193,7 @@ impl ByteCompiler<'_, '_> { if self.in_async_generator { self.emit_opcode(Opcode::Await); } - self.close_active_iterators(true); + self.close_active_iterators(); self.emit_opcode(Opcode::GeneratorResumeReturn); self.patch_jump(throw_method_undefined); @@ -194,24 +201,8 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(Opcode::Throw); self.patch_jump(exit); - } 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.close_active_iterators(true); - self.emit_opcode(Opcode::GeneratorResumeReturn); - - self.patch_jump(skip_yield_await); - self.patch_jump(normal_completion); } else { - self.emit_opcode(Opcode::Yield); - self.emit_opcode(Opcode::GeneratorNext); + self.r#yield(); } if !use_expr { diff --git a/boa_engine/src/bytecompiler/mod.rs b/boa_engine/src/bytecompiler/mod.rs index 4eca83a4b93..b2d5a3a3c81 100644 --- a/boa_engine/src/bytecompiler/mod.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -515,6 +515,28 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> { (Label { index }, Label { index: index + 4 }) } + /// Emit an opcode with three dummy operands. + /// Return the `Label`s of the three operands. + pub(crate) fn emit_opcode_with_three_operands( + &mut self, + opcode: Opcode, + ) -> (Label, Label, Label) { + let index = self.next_opcode_location(); + self.emit( + opcode, + &[ + Self::DUMMY_ADDRESS, + Self::DUMMY_ADDRESS, + Self::DUMMY_ADDRESS, + ], + ); + ( + Label { index }, + Label { index: index + 4 }, + Label { index: index + 8 }, + ) + } + pub(crate) fn patch_jump_with_target(&mut self, label: Label, target: u32) { const U32_SIZE: usize = std::mem::size_of::(); diff --git a/boa_engine/src/bytecompiler/statement/mod.rs b/boa_engine/src/bytecompiler/statement/mod.rs index 43472fd86d4..54b54c2e098 100644 --- a/boa_engine/src/bytecompiler/statement/mod.rs +++ b/boa_engine/src/bytecompiler/statement/mod.rs @@ -51,6 +51,10 @@ impl ByteCompiler<'_, '_> { Statement::Return(ret) => { if let Some(expr) = ret.target() { self.compile_expr(expr, true); + if self.in_async_generator { + self.emit_opcode(Opcode::Await); + self.emit_opcode(Opcode::GeneratorNext); + } } else { self.emit(Opcode::PushUndefined, &[]); } diff --git a/boa_engine/src/bytecompiler/utils.rs b/boa_engine/src/bytecompiler/utils.rs index b9a2d1a9cea..e366c46880b 100644 --- a/boa_engine/src/bytecompiler/utils.rs +++ b/boa_engine/src/bytecompiler/utils.rs @@ -43,12 +43,64 @@ impl ByteCompiler<'_, '_> { } /// Closes all active iterators in the current [`CallFrame`][crate::vm::CallFrame]. - pub(super) fn close_active_iterators(&mut self, async_: bool) { + 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(async_); + 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); + } } diff --git a/boa_engine/src/job.rs b/boa_engine/src/job.rs index 7e9a2a1bf64..0b828815acd 100644 --- a/boa_engine/src/job.rs +++ b/boa_engine/src/job.rs @@ -276,10 +276,6 @@ impl SimpleJobQueue { impl JobQueue for SimpleJobQueue { fn enqueue_promise_job(&self, job: NativeJob, _: &mut Context<'_>) { - // If realm is not null ... - // TODO - // Let scriptOrModule be ... - // TODO self.0.borrow_mut().push_back(job); } diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index fbdfca46a5d..e1ddd69f966 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -317,8 +317,7 @@ impl CodeBlock { | Opcode::Call | Opcode::New | Opcode::SuperCall - | Opcode::ConcatToString - | Opcode::GeneratorAsyncResumeYield => { + | Opcode::ConcatToString => { let result = self.read::(*pc).to_string(); *pc += size_of::(); result @@ -334,7 +333,6 @@ impl CodeBlock { | Opcode::LoopStart | Opcode::IteratorLoopStart | Opcode::TryStart - | Opcode::AsyncGeneratorNext | Opcode::GeneratorDelegateNext | Opcode::GeneratorDelegateResume => { let operand1 = self.read::(*pc); @@ -364,6 +362,7 @@ impl CodeBlock { } Opcode::GetGenerator | Opcode::GetGeneratorAsync => { let operand = self.read::(*pc); + *pc += size_of::(); format!( "{operand:04}: '{}' (length: {})", interner.resolve_expect(self.functions[operand as usize].name), @@ -430,6 +429,20 @@ impl CodeBlock { *pc += size_of::() * (count as usize + 1); String::new() } + Opcode::GeneratorJumpOnResumeKind => { + let normal = self.read::(*pc); + *pc += size_of::(); + let throw = self.read::(*pc); + *pc += size_of::(); + let r#return = self.read::(*pc); + *pc += size_of::(); + format!("n: {normal}, t: {throw}, r: {return}") + } + Opcode::CreateIteratorResult => { + let done = self.read::(*pc) != 0; + *pc += size_of::(); + format!("done: {done}") + } Opcode::Pop | Opcode::PopIfThrown | Opcode::Dup @@ -531,8 +544,10 @@ impl CodeBlock { | Opcode::PushNewArray | Opcode::PopOnReturnAdd | Opcode::PopOnReturnSub - | Opcode::Yield + | Opcode::GeneratorYield + | Opcode::AsyncGeneratorYield | Opcode::GeneratorNext + | Opcode::GeneratorSetReturn | Opcode::PushClassField | Opcode::SuperCallDerived | Opcode::Await diff --git a/boa_engine/src/vm/completion_record.rs b/boa_engine/src/vm/completion_record.rs index 0c307c6f33d..a484a841504 100644 --- a/boa_engine/src/vm/completion_record.rs +++ b/boa_engine/src/vm/completion_record.rs @@ -1,18 +1,32 @@ //! An implementation of a `CompletionRecord` for Boa's VM. +use boa_gc::{custom_trace, Finalize, Trace}; + use crate::{JsError, JsResult, JsValue}; /// An implementation of the ECMAScript's `CompletionRecord` [specification] for /// Boa's VM output Completion and Result. /// /// [specification]: https://tc39.es/ecma262/#sec-completion-record-specification-type -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Finalize)] pub(crate) enum CompletionRecord { Normal(JsValue), Return(JsValue), Throw(JsError), } +// SAFETY: this matches all possible variants and traces +// their inner contents, which makes this safe. +unsafe impl Trace for CompletionRecord { + custom_trace!(this, { + match this { + Self::Normal(v) => mark(v), + Self::Return(r) => mark(r), + Self::Throw(th) => mark(th), + } + }); +} + // ---- `CompletionRecord` methods ---- impl CompletionRecord { pub(crate) const fn is_throw_completion(&self) -> bool { diff --git a/boa_engine/src/vm/flowgraph/graph.rs b/boa_engine/src/vm/flowgraph/graph.rs index 13d0ca646cd..e82eba9fcf7 100644 --- a/boa_engine/src/vm/flowgraph/graph.rs +++ b/boa_engine/src/vm/flowgraph/graph.rs @@ -1,3 +1,8 @@ +use std::{ + collections::hash_map::RandomState, + hash::{BuildHasher, Hash, Hasher}, +}; + use crate::vm::flowgraph::{Color, Edge, EdgeStyle, EdgeType, Node, NodeShape}; /// This represents the direction of flow in the flowgraph. @@ -95,7 +100,10 @@ impl SubGraph { /// Format into the graphviz format. fn graphviz_format(&self, result: &mut String, prefix: &str) { - result.push_str(&format!("\tsubgraph cluster_{prefix}_{} {{\n", self.label)); + let mut hasher = RandomState::new().build_hasher(); + self.label.hash(&mut hasher); + let label = format!("{}", hasher.finish()); + result.push_str(&format!("\tsubgraph cluster_{prefix}_{label} {{\n")); result.push_str("\t\tstyle = filled;\n"); result.push_str(&format!( "\t\tlabel = \"{}\";\n", @@ -107,13 +115,11 @@ impl SubGraph { )); result.push_str(&format!( - "\t\t{prefix}_{}_start [label=\"Start\",shape=Mdiamond,style=filled,color=green]\n", - self.label + "\t\t{prefix}_{label}_start [label=\"Start\",shape=Mdiamond,style=filled,color=green]\n" )); if !self.nodes.is_empty() { result.push_str(&format!( - "\t\t{prefix}_{}_start -> {prefix}_{}_i_0\n", - self.label, self.label + "\t\t{prefix}_{label}_start -> {prefix}_{label}_i_0\n" )); } @@ -126,7 +132,7 @@ impl SubGraph { let color = format!(",style=filled,color=\"{}\"", node.color); result.push_str(&format!( "\t\t{prefix}_{}_i_{}[label=\"{:04}: {}\"{shape}{color}];\n", - self.label, node.location, node.location, node.label + label, node.location, node.location, node.label )); } @@ -142,9 +148,9 @@ impl SubGraph { }; result.push_str(&format!( "\t\t{prefix}_{}_i_{} -> {prefix}_{}_i_{} [label=\"{}\", len=f{style}{color}];\n", - self.label, + label, edge.from, - self.label, + label, edge.to, edge.label.as_deref().unwrap_or("") )); @@ -158,6 +164,9 @@ impl SubGraph { /// Format into the mermaid format. fn mermaid_format(&self, result: &mut String, prefix: &str) { + let mut hasher = RandomState::new().build_hasher(); + self.label.hash(&mut hasher); + let label = format!("{}", hasher.finish()); let rankdir = match self.direction { Direction::TopToBottom => "TB", Direction::BottomToTop => "BT", @@ -166,7 +175,7 @@ impl SubGraph { }; result.push_str(&format!( " subgraph {prefix}_{}[\"{}\"]\n", - self.label, + label, if self.label.is_empty() { "Anonymous Function" } else { @@ -175,15 +184,11 @@ impl SubGraph { )); result.push_str(&format!(" direction {rankdir}\n")); - result.push_str(&format!(" {prefix}_{}_start{{Start}}\n", self.label)); - result.push_str(&format!( - " style {prefix}_{}_start fill:green\n", - self.label - )); + result.push_str(&format!(" {prefix}_{label}_start{{Start}}\n")); + result.push_str(&format!(" style {prefix}_{label}_start fill:green\n")); if !self.nodes.is_empty() { result.push_str(&format!( - " {prefix}_{}_start --> {prefix}_{}_i_0\n", - self.label, self.label + " {prefix}_{label}_start --> {prefix}_{label}_i_0\n" )); } @@ -194,12 +199,12 @@ impl SubGraph { }; result.push_str(&format!( " {prefix}_{}_i_{}{shape_begin}\"{:04}: {}\"{shape_end}\n", - self.label, node.location, node.location, node.label + label, node.location, node.location, node.label )); if !node.color.is_none() { result.push_str(&format!( " style {prefix}_{}_i_{} fill:{}\n", - self.label, node.location, node.color + label, node.location, node.color )); } } @@ -213,10 +218,10 @@ impl SubGraph { }; result.push_str(&format!( " {prefix}_{}_i_{} {style}| {}| {prefix}_{}_i_{}\n", - self.label, + label, edge.from, edge.label.as_deref().unwrap_or(""), - self.label, + label, edge.to, )); diff --git a/boa_engine/src/vm/flowgraph/mod.rs b/boa_engine/src/vm/flowgraph/mod.rs index 056406d15cd..16fe6f6f18e 100644 --- a/boa_engine/src/vm/flowgraph/mod.rs +++ b/boa_engine/src/vm/flowgraph/mod.rs @@ -59,7 +59,7 @@ impl CodeBlock { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } - Opcode::RotateLeft | Opcode::RotateRight => { + Opcode::RotateLeft | Opcode::RotateRight | Opcode::CreateIteratorResult => { pc += size_of::(); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); @@ -228,55 +228,57 @@ impl CodeBlock { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, address, None, Color::None, EdgeStyle::Line); } - Opcode::GeneratorAsyncResumeYield => { - let address = self.read::(pc) as usize; + Opcode::GeneratorDelegateNext => { + let throw_method_undefined = self.read::(pc) as usize; pc += size_of::(); - graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); - graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); - graph.add_edge( + let return_method_undefined = self.read::(pc) as usize; + pc += size_of::(); + graph.add_node( previous_pc, - address, - Some("DONE".into()), + NodeShape::Diamond, + opcode_str.into(), Color::None, - EdgeStyle::Line, ); - } - Opcode::GeneratorDelegateNext => { - let throw_method_undefined = self.read::(pc) as usize; - let return_method_undefined = self.read::(pc) as usize; - graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); + graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); graph.add_edge( previous_pc, throw_method_undefined, - Some("throw method undefined".into()), - Color::None, + Some("`throw` undefined".into()), + Color::Red, EdgeStyle::Line, ); graph.add_edge( previous_pc, return_method_undefined, - Some("return method undefined".into()), - Color::None, + Some("`return` undefined".into()), + Color::Blue, EdgeStyle::Line, ); } Opcode::GeneratorDelegateResume => { let return_gen = self.read::(pc) as usize; + pc += size_of::(); let exit = self.read::(pc) as usize; pc += size_of::(); - 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::None, + Some("return".into()), + Color::Yellow, EdgeStyle::Line, ); graph.add_edge( previous_pc, exit, - Some("DONE".into()), - Color::None, + Some("done".into()), + Color::Blue, EdgeStyle::Line, ); } @@ -285,31 +287,45 @@ impl CodeBlock { | Opcode::Call | Opcode::New | Opcode::SuperCall - | Opcode::ConcatToString => { + | Opcode::ConcatToString + | Opcode::FinallyStart => { pc += size_of::(); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } - Opcode::AsyncGeneratorNext => { - let skip_yield = self.read::(pc); + Opcode::GeneratorJumpOnResumeKind => { + let normal = self.read::(pc); pc += size_of::(); - let skip_yield_await = self.read::(pc); + let throw = self.read::(pc); pc += size_of::(); - graph.add_node(previous_pc, NodeShape::None, opcode_str.into(), Color::None); + let r#return = self.read::(pc); + pc += size_of::(); + graph.add_node( + previous_pc, + NodeShape::Diamond, + opcode_str.into(), + Color::None, + ); - graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); graph.add_edge( previous_pc, - skip_yield as usize, - Some("return value pending".into()), + normal as usize, + None, Color::None, EdgeStyle::Line, ); graph.add_edge( previous_pc, - skip_yield_await as usize, - Some("yield value pending".into()), - Color::None, + throw as usize, + Some("throw".into()), + Color::Red, + EdgeStyle::Line, + ); + graph.add_edge( + previous_pc, + r#return as usize, + Some("return".into()), + Color::Yellow, EdgeStyle::Line, ); } @@ -469,7 +485,8 @@ impl CodeBlock { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); } - Opcode::Throw | Opcode::ThrowNewTypeError => { + Opcode::ThrowNewTypeError => { + pc += size_of::(); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); if let Some((_try_pc, next, _finally)) = try_entries.last() { graph.add_edge( @@ -479,6 +496,22 @@ impl CodeBlock { Color::None, EdgeStyle::Line, ); + } else { + returns.push(previous_pc); + } + } + Opcode::Throw => { + graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); + if let Some((_try_pc, next, _finally)) = try_entries.last() { + graph.add_edge( + previous_pc, + *next as usize, + Some("CAUGHT".into()), + Color::None, + EdgeStyle::Line, + ); + } else { + returns.push(previous_pc); } } Opcode::PushPrivateEnvironment => { @@ -554,7 +587,6 @@ impl CodeBlock { | Opcode::ToBoolean | Opcode::CatchEnd | Opcode::CatchEnd2 - | Opcode::FinallyStart | Opcode::FinallyEnd | Opcode::This | Opcode::Super @@ -584,7 +616,8 @@ impl CodeBlock { | Opcode::PushNewArray | Opcode::PopOnReturnAdd | Opcode::PopOnReturnSub - | Opcode::Yield + | Opcode::GeneratorYield + | Opcode::AsyncGeneratorYield | Opcode::GeneratorNext | Opcode::PushClassField | Opcode::SuperCallDerived @@ -602,6 +635,7 @@ impl CodeBlock { | Opcode::PushObjectEnvironment | Opcode::PopPrivateEnvironment | Opcode::ImportCall + | Opcode::GeneratorSetReturn | Opcode::Nop => { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line); diff --git a/boa_engine/src/vm/opcode/generator/mod.rs b/boa_engine/src/vm/opcode/generator/mod.rs index cc38a1d9d2d..d7818946d13 100644 --- a/boa_engine/src/vm/opcode/generator/mod.rs +++ b/boa_engine/src/vm/opcode/generator/mod.rs @@ -1,15 +1,14 @@ pub(crate) mod yield_stm; use crate::{ - builtins::async_generator::{AsyncGenerator, AsyncGeneratorState}, error::JsNativeError, string::utf16, vm::{ - call_frame::{AbruptCompletionRecord, GeneratorResumeKind}, + call_frame::GeneratorResumeKind, opcode::{control_flow::Return, Operation}, CompletionType, }, - Context, JsError, JsResult, JsValue, + Context, JsError, JsResult, }; pub(crate) use yield_stm::*; @@ -29,115 +28,50 @@ impl Operation for GeneratorNext { match context.vm.frame().generator_resume_kind { GeneratorResumeKind::Normal => Ok(CompletionType::Normal), GeneratorResumeKind::Throw => Err(JsError::from_opaque(context.vm.pop())), - GeneratorResumeKind::Return => { - let finally_entries = context - .vm - .frame() - .env_stack - .iter() - .filter(|entry| entry.is_finally_env()); - if let Some(next_finally) = finally_entries.rev().next() { - if context.vm.frame().pc < next_finally.start_address() { - context.vm.frame_mut().pc = next_finally.start_address(); - let return_record = AbruptCompletionRecord::new_return(); - context.vm.frame_mut().abrupt_completion = Some(return_record); - return Ok(CompletionType::Normal); - } - } - - let return_record = AbruptCompletionRecord::new_return(); - context.vm.frame_mut().abrupt_completion = Some(return_record); - Ok(CompletionType::Return) - } + GeneratorResumeKind::Return => Return::execute(context), } } } -/// `AsyncGeneratorNext` implements the Opcode Operation for `Opcode::AsyncGeneratorNext` +/// `GeneratorJumpOnResumeKind` implements the Opcode Operation for +/// `Opcode::GeneratorJumpOnResumeKind` /// /// Operation: -/// - Resumes the current generator function. +/// - Jumps to the specified instruction if the current resume kind is `Return`. #[derive(Debug, Clone, Copy)] -pub(crate) struct AsyncGeneratorNext; +pub(crate) struct GeneratorJumpOnResumeKind; -impl Operation for AsyncGeneratorNext { - const NAME: &'static str = "AsyncGeneratorNext"; - const INSTRUCTION: &'static str = "INST - AsyncGeneratorNext"; +impl Operation for GeneratorJumpOnResumeKind { + const NAME: &'static str = "GeneratorJumpOnResumeKind"; + const INSTRUCTION: &'static str = "INST - GeneratorJumpOnResumeKind"; fn execute(context: &mut Context<'_>) -> JsResult { - let skip_yield = context.vm.read::(); - let skip_yield_await = context.vm.read::(); - - if context.vm.frame().generator_resume_kind == GeneratorResumeKind::Throw { - return Err(JsError::from_opaque(context.vm.pop())); - } - - let value = context.vm.pop(); - - let completion = Ok(value); - let generator_object = context - .vm - .frame() - .async_generator - .as_ref() - .expect("must be in generator context here") - .clone(); - let next = generator_object - .borrow_mut() - .as_async_generator_mut() - .expect("must be async generator object") - .queue - .pop_front() - .expect("must have item in queue"); - AsyncGenerator::complete_step(&next, completion, false, None, context); - - let mut generator_object_mut = generator_object.borrow_mut(); - let gen = generator_object_mut - .as_async_generator_mut() - .expect("must be async generator object"); - - if let Some(next) = gen.queue.front() { - let (completion, r#return) = &next.completion; - if *r#return { - let value = match completion { - Ok(value) => value.clone(), - Err(e) => e.clone().to_opaque(context), - }; - context.vm.push(value); - context.vm.frame_mut().pc = skip_yield; - } else { - context.vm.push(completion.clone()?); - context.vm.frame_mut().pc = skip_yield_await; - } - } else { - gen.state = AsyncGeneratorState::SuspendedYield; + let normal = context.vm.read::(); + let throw = context.vm.read::(); + let r#return = context.vm.read::(); + match context.vm.frame().generator_resume_kind { + GeneratorResumeKind::Normal => context.vm.frame_mut().pc = normal, + GeneratorResumeKind::Throw => context.vm.frame_mut().pc = throw, + GeneratorResumeKind::Return => context.vm.frame_mut().pc = r#return, } Ok(CompletionType::Normal) } } -/// `GeneratorAsyncResumeYield` implements the Opcode Operation for `Opcode::GeneratorAsyncResumeYield` +/// `GeneratorSetReturn` implements the Opcode Operation for `Opcode::GeneratorSetReturn` /// /// Operation: -/// - Resumes the current async generator function after a yield. +/// - Sets the current generator resume kind to `Return`. #[derive(Debug, Clone, Copy)] -pub(crate) struct GeneratorAsyncResumeYield; +pub(crate) struct GeneratorSetReturn; -impl Operation for GeneratorAsyncResumeYield { - const NAME: &'static str = "GeneratorAsyncResumeYield"; - const INSTRUCTION: &'static str = "INST - GeneratorAsyncResumeYield"; +impl Operation for GeneratorSetReturn { + const NAME: &'static str = "GeneratorSetReturn"; + const INSTRUCTION: &'static str = "INST - GeneratorSetReturn"; fn execute(context: &mut Context<'_>) -> JsResult { - let normal_completion = context.vm.read::(); - - match context.vm.frame().generator_resume_kind { - GeneratorResumeKind::Normal => { - context.vm.frame_mut().pc = normal_completion; - Ok(CompletionType::Normal) - } - GeneratorResumeKind::Throw => Err(JsError::from_opaque(context.vm.pop())), - GeneratorResumeKind::Return => Ok(CompletionType::Normal), - } + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Return; + Ok(CompletionType::Normal) } } @@ -273,7 +207,7 @@ impl Operation for GeneratorDelegateResume { return Err(JsError::from_opaque(result)); } - iterator.update_result(result.clone(), context)?; + iterator.update_result(result, context)?; if iterator.done() { let value = iterator.value(context)?; @@ -284,49 +218,8 @@ impl Operation for GeneratorDelegateResume { return Ok(CompletionType::Normal); } - let Some(async_gen) = context.vm.frame().async_generator.clone() else { - context.vm.frame_mut().iterators.push(iterator); - context.vm.push(result); - context.vm.frame_mut().r#yield = true; - return Ok(CompletionType::Return); - }; - - let value = iterator.value(context)?; context.vm.frame_mut().iterators.push(iterator); - 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"); - 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 (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 = return_gen; - } else { - context.vm.push(completion.clone()?); - } - Ok(CompletionType::Normal) - } else { - gen.state = AsyncGeneratorState::SuspendedYield; - context.vm.push(JsValue::undefined()); - context.vm.frame_mut().r#yield = true; - Ok(CompletionType::Return) - } + Ok(CompletionType::Normal) } } diff --git a/boa_engine/src/vm/opcode/generator/yield_stm.rs b/boa_engine/src/vm/opcode/generator/yield_stm.rs index e70b71a5565..8f545de58da 100644 --- a/boa_engine/src/vm/opcode/generator/yield_stm.rs +++ b/boa_engine/src/vm/opcode/generator/yield_stm.rs @@ -1,25 +1,87 @@ use crate::{ - builtins::iterable::create_iter_result_object, - vm::{opcode::Operation, CompletionType}, - Context, JsResult, + builtins::async_generator::{AsyncGenerator, AsyncGeneratorState}, + vm::{opcode::Operation, CompletionRecord, CompletionType, GeneratorResumeKind}, + Context, JsResult, JsValue, }; -/// `Yield` implements the Opcode Operation for `Opcode::Yield` +/// `GeneratorYield` implements the Opcode Operation for `Opcode::GeneratorYield` /// /// Operation: -/// - Yield from the current execution. +/// - Yield from the current generator execution. #[derive(Debug, Clone, Copy)] -pub(crate) struct Yield; +pub(crate) struct GeneratorYield; -impl Operation for Yield { - const NAME: &'static str = "Yield"; - const INSTRUCTION: &'static str = "INST - Yield"; +impl Operation for GeneratorYield { + const NAME: &'static str = "GeneratorYield"; + const INSTRUCTION: &'static str = "INST - GeneratorYield"; fn execute(context: &mut Context<'_>) -> JsResult { - let value = context.vm.pop(); - let value = create_iter_result_object(value, false, context); - context.vm.push(value); context.vm.frame_mut().r#yield = true; Ok(CompletionType::Return) } } + +/// `AsyncGeneratorYield` implements the Opcode Operation for `Opcode::AsyncGeneratorYield` +/// +/// Operation: +/// - Yield from the current async generator execution. +#[derive(Debug, Clone, Copy)] +pub(crate) struct AsyncGeneratorYield; + +impl Operation for AsyncGeneratorYield { + const NAME: &'static str = "AsyncGeneratorYield"; + const INSTRUCTION: &'static str = "INST - AsyncGeneratorYield"; + + fn execute(context: &mut Context<'_>) -> JsResult { + 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) + } + } +} diff --git a/boa_engine/src/vm/opcode/iteration/iterator.rs b/boa_engine/src/vm/opcode/iteration/iterator.rs index 49e45df043f..1a7b06deb70 100644 --- a/boa_engine/src/vm/opcode/iteration/iterator.rs +++ b/boa_engine/src/vm/opcode/iteration/iterator.rs @@ -1,7 +1,7 @@ use std::matches; use crate::{ - builtins::Array, + builtins::{iterable::create_iter_result_object, Array}, js_string, vm::{opcode::Operation, CompletionType, GeneratorResumeKind}, Context, JsResult, @@ -275,3 +275,26 @@ impl Operation for IteratorStackEmpty { 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 { + let value = context.vm.pop(); + let done = context.vm.read::() != 0; + + let result = create_iter_result_object(value, done, context); + + context.vm.push(result); + + Ok(CompletionType::Normal) + } +} diff --git a/boa_engine/src/vm/opcode/mod.rs b/boa_engine/src/vm/opcode/mod.rs index 97f49bbf0fc..cdf900c35e0 100644 --- a/boa_engine/src/vm/opcode/mod.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -1510,6 +1510,16 @@ generate_impl! { /// - **=>** IteratorStackEmpty, + /// Creates a new iterator result object. + /// + /// Operands: + /// - done: bool (codified as u8 with `0` -> `false` and `!0` -> `true`) + /// + /// Stack: + /// - value **=>** + /// + CreateIteratorResult, + /// Calls `return` on the current iterator and returns the result. /// /// Stack: **=>** return_val (if return is a method), is_return_method @@ -1566,12 +1576,12 @@ generate_impl! { /// Stack: **=>** PopOnReturnSub, - /// Yield from the current execution. + /// Yields from the current generator execution. /// /// Operands: /// - /// Stack: value **=>** - Yield, + /// Stack: value **=>** received + GeneratorYield, /// Resumes the current generator function. /// @@ -1587,19 +1597,29 @@ generate_impl! { /// Stack: **=>** GeneratorResumeReturn, - /// Resumes the current generator function. + /// Yields from the current async generator execution. /// - /// Operands: skip_yield: u32, skip_yield_await: u32 + /// Operands: /// - /// Stack: received **=>** `Option` - AsyncGeneratorNext, + /// Stack: value **=>** received + AsyncGeneratorYield, - /// Resumes the current async generator function after a yield. + /// Jumps to the specified instruction for each resume kind. /// - /// Operands: normal_completion: u32 + /// Operands: + /// - normal: u32, + /// - throw: u32, + /// - return: u32, /// - /// Stack: **=>** - GeneratorAsyncResumeYield, + /// Stack: + GeneratorJumpOnResumeKind, + + /// Sets the current generator resume kind to `Return`. + /// + /// Operands: + /// + /// Stack: + GeneratorSetReturn, /// Delegates the current async generator function to another iterator. /// @@ -1619,7 +1639,7 @@ generate_impl! { /// /// Operands: /// - /// Stack: promise **=>** + /// Stack: promise **=>** received Await, /// Push the current new target to the stack.