diff --git a/boa_ast/src/function/parameters.rs b/boa_ast/src/function/parameters.rs index a7a5ae28ab1..c48b5d37b8b 100644 --- a/boa_ast/src/function/parameters.rs +++ b/boa_ast/src/function/parameters.rs @@ -176,7 +176,7 @@ impl<'a> arbitrary::Arbitrary<'a> for FormalParameterList { bitflags! { /// Flags for a [`FormalParameterList`]. - #[derive(Debug, Copy, Clone, PartialEq)] + #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct FormalParameterListFlags: u8 { /// Has only identifier parameters with no initialization expressions. diff --git a/boa_ast/src/position.rs b/boa_ast/src/position.rs index 216693e70f4..76634c3c4e8 100644 --- a/boa_ast/src/position.rs +++ b/boa_ast/src/position.rs @@ -204,9 +204,9 @@ mod tests { let a = Position::new(10, 30); let b = Position::new(10, 50); - let _ = Span::new(a, b); - let _ = Span::new(a, a); - let _ = Span::from(a); + Span::new(a, b); + Span::new(a, a); + Span::from(a); } /// Checks that the `PartialEq` implementation of `Span` is consistent. diff --git a/boa_engine/src/bytecompiler/expression/mod.rs b/boa_engine/src/bytecompiler/expression/mod.rs index 4c285e71878..40681b44085 100644 --- a/boa_engine/src/bytecompiler/expression/mod.rs +++ b/boa_engine/src/bytecompiler/expression/mod.rs @@ -4,7 +4,7 @@ mod object_literal; mod unary; mod update; -use super::{Access, Callable, NodeKind}; +use super::{Access, Callable, Label, NodeKind}; use crate::{ bytecompiler::{ByteCompiler, Literal}, vm::Opcode, @@ -162,12 +162,42 @@ impl ByteCompiler<'_, '_> { self.emit_opcode(Opcode::PushUndefined); } - if r#yield.delegate() { - if self.in_async_generator { - self.emit_opcode(Opcode::GetAsyncIterator); - } else { - self.emit_opcode(Opcode::GetIterator); - } + if r#yield.delegate() && self.in_async_generator { + self.emit_opcode(Opcode::GetAsyncIterator); + self.emit_opcode(Opcode::PushUndefined); + + let start_address = self.next_opcode_location(); + let (throw_method_undefined, return_method_undefined) = + self.emit_opcode_with_two_operands(Opcode::GeneratorAsyncDelegateNext); + self.emit_opcode(Opcode::Await); + + let skip_yield = Label { + index: self.next_opcode_location(), + }; + let exit = Label { + index: self.next_opcode_location() + 8, + }; + self.emit( + Opcode::GeneratorAsyncDelegateResume, + &[Self::DUMMY_ADDRESS, start_address, Self::DUMMY_ADDRESS], + ); + + self.emit_opcode(Opcode::PushUndefined); + self.emit_opcode(Opcode::Yield); + self.emit(Opcode::Jump, &[start_address]); + + self.patch_jump(skip_yield); + self.patch_jump(return_method_undefined); + self.emit_opcode(Opcode::Await); + self.emit_opcode(Opcode::GeneratorResumeReturn); + + self.patch_jump(throw_method_undefined); + self.iterator_close(true); + self.emit_opcode(Opcode::Throw); + + self.patch_jump(exit); + } else if r#yield.delegate() { + self.emit_opcode(Opcode::GetIterator); self.emit_opcode(Opcode::PushUndefined); let start_address = self.next_opcode_location(); let start = self.emit_opcode_with_operand(Opcode::GeneratorNextDelegate); @@ -179,11 +209,14 @@ impl ByteCompiler<'_, '_> { self.emit_opcode_with_two_operands(Opcode::AsyncGeneratorNext); self.emit_opcode(Opcode::PushUndefined); self.emit_opcode(Opcode::Yield); - self.emit_opcode(Opcode::GeneratorNext); + let normal_completion = + self.emit_opcode_with_operand(Opcode::GeneratorAsyncResumeYield); self.patch_jump(skip_yield); self.emit_opcode(Opcode::Await); - self.emit_opcode(Opcode::GeneratorNext); + 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); diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 0556c72edfa..076edb71a83 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -277,12 +277,12 @@ impl CodeBlock { | Opcode::SuperCall | Opcode::IteratorUnwrapNextOrJump | Opcode::ConcatToString + | Opcode::GeneratorAsyncResumeYield | Opcode::GeneratorNextDelegate => { let result = self.read::(*pc).to_string(); *pc += size_of::(); result } - Opcode::PushDeclarativeEnvironment | Opcode::PushFunctionEnvironment | Opcode::CopyDataProperties @@ -291,13 +291,23 @@ impl CodeBlock { | Opcode::LoopContinue | Opcode::LoopStart | Opcode::TryStart - | Opcode::AsyncGeneratorNext => { + | Opcode::AsyncGeneratorNext + | Opcode::GeneratorAsyncDelegateNext => { let operand1 = self.read::(*pc); *pc += size_of::(); let operand2 = self.read::(*pc); *pc += size_of::(); format!("{operand1}, {operand2}") } + Opcode::GeneratorAsyncDelegateResume => { + let operand1 = self.read::(*pc); + *pc += size_of::(); + let operand2 = self.read::(*pc); + *pc += size_of::(); + let operand3 = self.read::(*pc); + *pc += size_of::(); + format!("{operand1}, {operand2}, {operand3}") + } Opcode::GetArrowFunction | Opcode::GetAsyncArrowFunction | Opcode::GetFunction @@ -447,6 +457,7 @@ impl CodeBlock { | Opcode::CreateForInIterator | Opcode::GetIterator | Opcode::GetAsyncIterator + | Opcode::GeneratorResumeReturn | Opcode::IteratorNext | Opcode::IteratorNextSetDone | Opcode::IteratorUnwrapNext diff --git a/boa_engine/src/vm/flowgraph/mod.rs b/boa_engine/src/vm/flowgraph/mod.rs index b14b6a8e8c5..80c9b1dbeca 100644 --- a/boa_engine/src/vm/flowgraph/mod.rs +++ b/boa_engine/src/vm/flowgraph/mod.rs @@ -219,7 +219,9 @@ impl CodeBlock { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); graph.add_edge(previous_pc, address, None, Color::None, EdgeStyle::Line); } - Opcode::IteratorUnwrapNextOrJump | Opcode::GeneratorNextDelegate => { + Opcode::IteratorUnwrapNextOrJump + | Opcode::GeneratorAsyncResumeYield + | Opcode::GeneratorNextDelegate => { let address = self.read::(pc) as usize; pc += size_of::(); graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); @@ -232,6 +234,39 @@ impl CodeBlock { EdgeStyle::Line, ); } + Opcode::GeneratorAsyncDelegateNext => { + 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, + throw_method_undefined, + Some("throw method undefined".into()), + Color::None, + EdgeStyle::Line, + ); + graph.add_edge( + previous_pc, + return_method_undefined, + Some("return method undefined".into()), + Color::None, + EdgeStyle::Line, + ); + } + Opcode::GeneratorAsyncDelegateResume => { + self.read::(pc); + self.read::(pc); + let exit = self.read::(pc) as usize; + pc += size_of::(); + graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); + graph.add_edge( + previous_pc, + exit, + Some("DONE".into()), + Color::None, + EdgeStyle::Line, + ); + } Opcode::CatchStart | Opcode::CallEval | Opcode::Call @@ -544,7 +579,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::Return => { + Opcode::Return | Opcode::GeneratorResumeReturn => { graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None); if let Some((_try_pc, _next, Some(finally))) = try_entries.last() { graph.add_edge( diff --git a/boa_engine/src/vm/opcode/await_stm/mod.rs b/boa_engine/src/vm/opcode/await_stm/mod.rs index 01dcbde7000..6296da9294a 100644 --- a/boa_engine/src/vm/opcode/await_stm/mod.rs +++ b/boa_engine/src/vm/opcode/await_stm/mod.rs @@ -50,6 +50,19 @@ impl Operation for Await { GeneratorResumeKind::Normal, context, ); + + if let Some(async_generator) = gen + .call_frame + .as_ref() + .and_then(|f| f.async_generator.clone()) + { + async_generator + .borrow_mut() + .as_async_generator_mut() + .expect("must be async generator") + .context = Some(gen); + } + // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. // f. Return undefined. Ok(JsValue::undefined()) @@ -82,6 +95,18 @@ impl Operation for Await { context, ); + if let Some(async_generator) = gen + .call_frame + .as_ref() + .and_then(|f| f.async_generator.clone()) + { + async_generator + .borrow_mut() + .as_async_generator_mut() + .expect("must be async generator") + .context = Some(gen); + } + Ok(JsValue::undefined()) }, captures, diff --git a/boa_engine/src/vm/opcode/generator/mod.rs b/boa_engine/src/vm/opcode/generator/mod.rs index 98a9701c32f..f2bea763998 100644 --- a/boa_engine/src/vm/opcode/generator/mod.rs +++ b/boa_engine/src/vm/opcode/generator/mod.rs @@ -1,3 +1,5 @@ +pub(crate) mod yield_stm; + use crate::{ builtins::{ async_generator::{AsyncGenerator, AsyncGeneratorState}, @@ -13,8 +15,6 @@ use crate::{ Context, JsError, JsResult, JsValue, }; -pub(crate) mod yield_stm; - pub(crate) use yield_stm::*; /// `GeneratorNext` implements the Opcode Operation for `Opcode::GeneratorNext` @@ -119,6 +119,50 @@ impl Operation for AsyncGeneratorNext { } } +/// `GeneratorAsyncResumeYield` implements the Opcode Operation for `Opcode::GeneratorAsyncResumeYield` +/// +/// Operation: +/// - Resumes the current async generator function after a yield. +#[derive(Debug, Clone, Copy)] +pub(crate) struct GeneratorAsyncResumeYield; + +impl Operation for GeneratorAsyncResumeYield { + const NAME: &'static str = "GeneratorAsyncResumeYield"; + const INSTRUCTION: &'static str = "INST - GeneratorAsyncResumeYield"; + + 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 as usize; + 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` +/// +/// Operation: +/// - Resumes a generator with a return completion. +#[derive(Debug, Clone, Copy)] +pub(crate) struct GeneratorResumeReturn; + +impl Operation for GeneratorResumeReturn { + const NAME: &'static str = "GeneratorResumeReturn"; + const INSTRUCTION: &'static str = "INST - GeneratorResumeReturn"; + + fn execute(context: &mut Context<'_>) -> JsResult { + if context.vm.frame().generator_resume_kind == GeneratorResumeKind::Throw { + return Err(JsError::from_opaque(context.vm.pop())); + } + Return::execute(context) + } +} + /// `GeneratorNextDelegate` implements the Opcode Operation for `Opcode::GeneratorNextDelegate` /// /// Operation: @@ -144,7 +188,6 @@ impl Operation for GeneratorNextDelegate { let result = result_value.as_object().ok_or_else(|| { JsNativeError::typ().with_message("generator next method returned non-object") })?; - // TODO: This is wrong for async generators, since we need to await the result first. let done = result.get(utf16!("done"), context)?.to_boolean(); if done { context.vm.frame_mut().pc = done_address as usize; @@ -213,3 +256,154 @@ impl Operation for GeneratorNextDelegate { } } } + +/// `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 { + let throw_method_undefined = context.vm.read::(); + let return_method_undefined = context.vm.read::(); + 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)?; + context.vm.push(iterator.clone()); + context.vm.push(next_method.clone()); + context.vm.push(false); + context.vm.push(result_value); + Ok(CompletionType::Normal) + } + GeneratorResumeKind::Throw => { + let throw = iterator.get_method(utf16!("throw"), context)?; + if let Some(throw) = throw { + let result = throw.call(&iterator.clone().into(), &[received], context)?; + context.vm.push(iterator.clone()); + context.vm.push(next_method.clone()); + context.vm.push(false); + context.vm.push(result); + } else { + let error = JsNativeError::typ() + .with_message("iterator does not have a throw method") + .to_opaque(context); + context.vm.push(error); + context.vm.push(iterator.clone()); + context.vm.push(next_method.clone()); + context.vm.push(false); + context.vm.frame_mut().pc = throw_method_undefined as usize; + } + + Ok(CompletionType::Normal) + } + 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)?; + context.vm.push(iterator.clone()); + context.vm.push(next_method.clone()); + context.vm.push(true); + context.vm.push(result); + return Ok(CompletionType::Normal); + } + context.vm.push(iterator.clone()); + context.vm.push(next_method.clone()); + context.vm.push(received); + context.vm.frame_mut().pc = return_method_undefined as usize; + Ok(CompletionType::Normal) + } + } + } +} + +/// `GeneratorAsyncDelegateResume` implements the Opcode Operation for `Opcode::GeneratorAsyncDelegateResume` +/// +/// Operation: +/// - Resume the async generator with yield delegate logic after it awaits a value. +#[derive(Debug, Clone, Copy)] +pub(crate) struct GeneratorAsyncDelegateResume; + +impl Operation for GeneratorAsyncDelegateResume { + const NAME: &'static str = "GeneratorAsyncDelegateResume"; + const INSTRUCTION: &'static str = "INST - GeneratorAsyncDelegateResume"; + + fn execute(context: &mut Context<'_>) -> JsResult { + let skip_yield = context.vm.read::(); + let normal_completion = context.vm.read::(); + let exit = context.vm.read::(); + + if context.vm.frame().generator_resume_kind == GeneratorResumeKind::Throw { + return Err(JsError::from_opaque(context.vm.pop())); + } + + let received = context.vm.pop(); + let is_return = context.vm.pop().to_boolean(); + + let result = received.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("generator next method returned non-object") + })?; + let done = result.get(utf16!("done"), context)?.to_boolean(); + let value = result.get(utf16!("value"), context)?; + if done { + context.vm.push(value); + + if is_return { + return Return::execute(context); + } + + context.vm.frame_mut().pc = exit as usize; + return Ok(CompletionType::Normal); + } + + let completion = Ok(value); + let generator_object = context + .vm + .frame() + .async_generator + .as_ref() + .expect("must be in generator context here") + .clone(); + let next = generator_object + .borrow_mut() + .as_async_generator_mut() + .expect("must be async generator object") + .queue + .pop_front() + .expect("must have item in queue"); + AsyncGenerator::complete_step(&next, completion, false, None, context); + + let mut generator_object_mut = generator_object.borrow_mut(); + let gen = generator_object_mut + .as_async_generator_mut() + .expect("must be async generator object"); + + if let Some(next) = gen.queue.front() { + let (completion, r#return) = &next.completion; + if *r#return { + let value = match completion { + Ok(value) => value.clone(), + Err(e) => e.clone().to_opaque(context), + }; + context.vm.push(value); + context.vm.frame_mut().pc = skip_yield as usize; + } else { + context.vm.push(completion.clone()?); + context.vm.frame_mut().pc = normal_completion as usize; + } + } else { + gen.state = AsyncGeneratorState::SuspendedYield; + } + Ok(CompletionType::Normal) + } +} diff --git a/boa_engine/src/vm/opcode/mod.rs b/boa_engine/src/vm/opcode/mod.rs index ed9e32db99a..fa24b994317 100644 --- a/boa_engine/src/vm/opcode/mod.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -1533,6 +1533,13 @@ generate_impl! { /// Stack: received **=>** GeneratorNext, + /// Resumes a generator with a return completion. + /// + /// Operands: + /// + /// Stack: **=>** + GeneratorResumeReturn, + /// Resumes the current generator function. /// /// Operands: skip_yield: u32, skip_yield_await: u32 @@ -1540,13 +1547,34 @@ generate_impl! { /// Stack: received **=>** `Option` AsyncGeneratorNext, - /// Delegates the current generator function to another generator. + /// Resumes the current async generator function after a yield. + /// + /// Operands: normal_completion: u32 + /// + /// Stack: **=>** + GeneratorAsyncResumeYield, + + /// Delegates the current generator function to another iterator. /// /// Operands: done_address: `u32` /// /// Stack: iterator, next_method, received **=>** iterator, next_method GeneratorNextDelegate, + /// Delegates the current async generator function to another iterator. + /// + /// Operands: throw_method_undefined: `u32`, return_method_undefined: `u32` + /// + /// Stack: iterator, next_method, received **=>** iterator, next_method, is_return, result + GeneratorAsyncDelegateNext, + + /// Resume the async generator with yield delegate logic after it awaits a value. + /// + /// Operands: skip_yield: `u32`, normal_completion: `u32`, exit: `u32` + /// + /// Stack: is_return, received **=>** value + GeneratorAsyncDelegateResume, + /// Stops the current async function and schedules it to resume later. /// /// Operands: diff --git a/boa_parser/src/lexer/cursor.rs b/boa_parser/src/lexer/cursor.rs index 0e63c741f3c..4c510c3a039 100644 --- a/boa_parser/src/lexer/cursor.rs +++ b/boa_parser/src/lexer/cursor.rs @@ -104,7 +104,7 @@ where Ok(match self.peek()? { Some(next) if next == byte => { - let _ = self.next_byte()?; + self.next_byte()?; true } _ => false, diff --git a/boa_parser/src/lexer/identifier.rs b/boa_parser/src/lexer/identifier.rs index 60930c4210a..397643a197a 100644 --- a/boa_parser/src/lexer/identifier.rs +++ b/boa_parser/src/lexer/identifier.rs @@ -128,7 +128,7 @@ impl Identifier { } } Some(ch) if Self::is_identifier_part(ch) => { - let _ = cursor.next_char()?; + cursor.next_char()?; ch }, _ => break, diff --git a/boa_parser/src/lexer/operator.rs b/boa_parser/src/lexer/operator.rs index 821781faecf..473eeb80730 100644 --- a/boa_parser/src/lexer/operator.rs +++ b/boa_parser/src/lexer/operator.rs @@ -121,7 +121,7 @@ impl Tokenizer for Operator { ); match first { Some(b'?') => { - let _ = cursor.next_byte()?.expect("? vanished"); + cursor.next_byte()?.expect("? vanished"); op!( cursor, start_pos, @@ -130,7 +130,7 @@ impl Tokenizer for Operator { ) } Some(b'.') if !matches!(second, Some(second) if second.is_ascii_digit()) => { - let _ = cursor.next_byte()?.expect(". vanished"); + cursor.next_byte()?.expect(". vanished"); Ok(Token::new( TokenKind::Punctuator(Punctuator::Optional), Span::new(start_pos, cursor.pos()), diff --git a/boa_parser/src/lexer/string.rs b/boa_parser/src/lexer/string.rs index 8c7b1c951aa..c9f1844b542 100644 --- a/boa_parser/src/lexer/string.rs +++ b/boa_parser/src/lexer/string.rs @@ -329,14 +329,14 @@ impl StringLiteral { // Grammar: FourToSeven OctalDigit if let Some(byte) = cursor.peek()? { if (b'0'..=b'7').contains(&byte) { - let _ = cursor.next_byte()?; + cursor.next_byte()?; code_point = (code_point * 8) + u32::from(byte - b'0'); if (b'0'..=b'3').contains(&init_byte) { // Grammar: ZeroToThree OctalDigit OctalDigit if let Some(byte) = cursor.peek()? { if (b'0'..=b'7').contains(&byte) { - let _ = cursor.next_byte()?; + cursor.next_byte()?; code_point = (code_point * 8) + u32::from(byte - b'0'); } } diff --git a/boa_profiler/src/lib.rs b/boa_profiler/src/lib.rs index 4e6d89c7ccd..fe4859daf0e 100644 --- a/boa_profiler/src/lib.rs +++ b/boa_profiler/src/lib.rs @@ -125,6 +125,7 @@ impl Profiler { .start_recording_interval_event(kind, id, thread_id) } + #[allow(clippy::significant_drop_tightening)] fn get_or_alloc_string(&self, s: &str) -> StringId { { // Check the cache only with the read lock first.