Skip to main content

boa_engine/vm/opcode/generator/
mod.rs

1pub(crate) mod yield_stm;
2
3use super::VaryingOperand;
4use crate::{
5    Context, JsError, JsObject, JsResult,
6    builtins::{
7        async_generator::{AsyncGenerator, AsyncGeneratorState},
8        generator::{GeneratorContext, GeneratorState},
9    },
10    js_string,
11    object::PROTOTYPE,
12    vm::{
13        CompletionRecord,
14        call_frame::GeneratorResumeKind,
15        opcode::{Operation, ReThrow},
16    },
17};
18use std::{collections::VecDeque, ops::ControlFlow};
19
20pub(crate) use yield_stm::*;
21
22/// `Generator` implements the Opcode Operation for `Opcode::Generator`
23///
24/// Operation:
25///  - Creates the generator object and yields.
26#[derive(Debug, Clone, Copy)]
27pub(crate) struct Generator;
28
29impl Generator {
30    #[inline(always)]
31    pub(super) fn operation(
32        r#async: VaryingOperand,
33        context: &mut Context,
34    ) -> ControlFlow<CompletionRecord> {
35        let r#async = u32::from(r#async) != 0;
36
37        let active_function = context.vm.stack.get_function(context.vm.frame());
38        let this_function_object =
39            active_function.expect("active function should be set to the generator");
40
41        let proto = this_function_object
42            .get(PROTOTYPE, context)
43            .expect("generator must have a prototype property")
44            .as_object()
45            .unwrap_or_else(|| {
46                if r#async {
47                    context.intrinsics().objects().async_generator()
48                } else {
49                    context.intrinsics().objects().generator()
50                }
51            });
52
53        let generator = if r#async {
54            JsObject::from_proto_and_data_with_shared_shape(
55                context.root_shape(),
56                proto,
57                AsyncGenerator {
58                    state: AsyncGeneratorState::SuspendedStart,
59                    context: None,
60                    queue: VecDeque::new(),
61                },
62            )
63        } else {
64            JsObject::from_proto_and_data_with_shared_shape(
65                context.root_shape(),
66                proto,
67                crate::builtins::generator::Generator {
68                    state: GeneratorState::Completed,
69                },
70            )
71        };
72
73        if r#async {
74            let generator_context =
75                GeneratorContext::from_current(context, Some(generator.clone()));
76
77            let mut r#gen = generator
78                .downcast_mut::<AsyncGenerator>()
79                .expect("must be object here");
80
81            r#gen.context = Some(generator_context);
82        } else {
83            let generator_context = GeneratorContext::from_current(context, None);
84
85            let mut r#gen = generator
86                .downcast_mut::<crate::builtins::generator::Generator>()
87                .expect("must be object here");
88
89            r#gen.state = GeneratorState::SuspendedStart {
90                context: generator_context,
91            };
92        }
93
94        context.vm.set_return_value(generator.into());
95        context.handle_yield()
96    }
97}
98
99impl Operation for Generator {
100    const NAME: &'static str = "Generator";
101    const INSTRUCTION: &'static str = "INST - Generator";
102    const COST: u8 = 8;
103}
104
105/// `AsyncGeneratorClose` implements the Opcode Operation for `Opcode::AsyncGeneratorClose`
106///
107/// Operation:
108///  - Close an async generator function.
109#[derive(Debug, Clone, Copy)]
110pub(crate) struct AsyncGeneratorClose;
111
112impl AsyncGeneratorClose {
113    #[inline(always)]
114    pub(super) fn operation((): (), context: &mut Context) {
115        // Step 3.e-g in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart)
116        let generator = context
117            .vm
118            .stack
119            .async_generator_object(&context.vm.frame)
120            .expect("There should be a object")
121            .downcast::<AsyncGenerator>()
122            .expect("must be async generator");
123
124        let mut r#gen = generator.borrow_mut();
125
126        // e. Assert: If we return here, the async generator either threw an exception or performed either an implicit or explicit return.
127        // f. Remove acGenContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
128
129        // g. Set acGenerator.[[AsyncGeneratorState]] to draining-queue.
130        r#gen.data_mut().state = AsyncGeneratorState::DrainingQueue;
131
132        // h. If result is a normal completion, set result to NormalCompletion(undefined).
133        // i. If result is a return completion, set result to NormalCompletion(result.[[Value]]).
134        let return_value = context.vm.take_return_value();
135
136        let result = context
137            .vm
138            .pending_exception
139            .take()
140            .map_or(Ok(return_value), Err);
141
142        drop(r#gen);
143
144        // j. Perform AsyncGeneratorCompleteStep(acGenerator, result, true).
145        AsyncGenerator::complete_step(&generator, result, true, None, context);
146        // k. Perform AsyncGeneratorDrainQueue(acGenerator).
147        AsyncGenerator::drain_queue(&generator, context);
148
149        // l. Return undefined.
150    }
151}
152
153impl Operation for AsyncGeneratorClose {
154    const NAME: &'static str = "AsyncGeneratorClose";
155    const INSTRUCTION: &'static str = "INST - AsyncGeneratorClose";
156    const COST: u8 = 8;
157}
158
159/// `GeneratorNext` implements the Opcode Operation for `Opcode::GeneratorNext`
160///
161/// Operation:
162///  - Resumes the current generator function.
163#[derive(Debug, Clone, Copy)]
164pub(crate) struct GeneratorNext;
165
166impl GeneratorNext {
167    #[inline(always)]
168    pub(super) fn operation(
169        (resume_kind, value): (VaryingOperand, VaryingOperand),
170        context: &mut Context,
171    ) -> ControlFlow<CompletionRecord> {
172        let resume_kind = context
173            .vm
174            .get_register(resume_kind.into())
175            .to_generator_resume_kind();
176        match resume_kind {
177            GeneratorResumeKind::Normal => ControlFlow::Continue(()),
178            GeneratorResumeKind::Throw => context.handle_error(JsError::from_opaque(
179                context.vm.get_register(value.into()).clone(),
180            )),
181            GeneratorResumeKind::Return => {
182                assert!(context.vm.pending_exception.is_none());
183                let value = context.vm.get_register(value.into());
184                context.vm.set_return_value(value.clone());
185                ReThrow::operation((), context)
186            }
187        }
188    }
189}
190
191impl Operation for GeneratorNext {
192    const NAME: &'static str = "GeneratorNext";
193    const INSTRUCTION: &'static str = "INST - GeneratorNext";
194    const COST: u8 = 1;
195}
196
197/// `JumpIfNotResumeKind` implements the Opcode Operation for `Opcode::JumpIfNotResumeKind`
198///
199/// Operation:
200///  - Jumps to the specified address if the resume kind is not equal.
201#[derive(Debug, Clone, Copy)]
202pub(crate) struct JumpIfNotResumeKind;
203
204impl JumpIfNotResumeKind {
205    #[inline(always)]
206    pub(super) fn operation(
207        (exit, expected, value): (u32, VaryingOperand, VaryingOperand),
208        context: &mut Context,
209    ) {
210        let resume_kind = context
211            .vm
212            .get_register(value.into())
213            .to_generator_resume_kind();
214        if resume_kind as u8 != u32::from(expected) as u8 {
215            context.vm.frame_mut().pc = exit;
216        }
217    }
218}
219
220impl Operation for JumpIfNotResumeKind {
221    const NAME: &'static str = "JumpIfNotResumeKind";
222    const INSTRUCTION: &'static str = "INST - JumpIfNotResumeKind";
223    const COST: u8 = 1;
224}
225
226/// `GeneratorDelegateNext` implements the Opcode Operation for `Opcode::GeneratorDelegateNext`
227///
228/// Operation:
229///  - Delegates the current generator function to another iterator.
230#[derive(Debug, Clone, Copy)]
231pub(crate) struct GeneratorDelegateNext;
232
233impl GeneratorDelegateNext {
234    #[inline(always)]
235    pub(super) fn operation(
236        (throw_method_undefined, return_method_undefined, value, resume_kind, is_return): (
237            u32,
238            u32,
239            VaryingOperand,
240            VaryingOperand,
241            VaryingOperand,
242        ),
243        context: &mut Context,
244    ) -> JsResult<()> {
245        let resume_kind = context
246            .vm
247            .get_register(resume_kind.into())
248            .to_generator_resume_kind();
249        let received = context.vm.get_register(value.into()).clone();
250
251        // Preemptively popping removes the iterator from the iterator stack if any operation
252        // throws, which avoids calling cleanup operations on the poisoned iterator.
253        let iterator_record = context
254            .vm
255            .frame_mut()
256            .iterators
257            .pop()
258            .expect("iterator stack should have at least an iterator");
259
260        match resume_kind {
261            GeneratorResumeKind::Normal => {
262                let result = iterator_record.next_method().call(
263                    &iterator_record.iterator().clone().into(),
264                    std::slice::from_ref(&received),
265                    context,
266                )?;
267                context.vm.set_register(is_return.into(), false.into());
268                context.vm.set_register(value.into(), result);
269            }
270            GeneratorResumeKind::Throw => {
271                let throw = iterator_record
272                    .iterator()
273                    .get_method(js_string!("throw"), context)?;
274                if let Some(throw) = throw {
275                    let result = throw.call(
276                        &iterator_record.iterator().clone().into(),
277                        std::slice::from_ref(&received),
278                        context,
279                    )?;
280                    context.vm.set_register(is_return.into(), false.into());
281                    context.vm.set_register(value.into(), result);
282                } else {
283                    context.vm.frame_mut().pc = throw_method_undefined;
284                }
285            }
286            GeneratorResumeKind::Return => {
287                let r#return = iterator_record
288                    .iterator()
289                    .get_method(js_string!("return"), context)?;
290                if let Some(r#return) = r#return {
291                    let result = r#return.call(
292                        &iterator_record.iterator().clone().into(),
293                        std::slice::from_ref(&received),
294                        context,
295                    )?;
296                    context.vm.set_register(is_return.into(), true.into());
297                    context.vm.set_register(value.into(), result);
298                } else {
299                    context.vm.frame_mut().pc = return_method_undefined;
300
301                    // The current iterator didn't have a cleanup `return` method, so we can
302                    // skip pushing it to the iterator stack for cleanup.
303                    return Ok(());
304                }
305            }
306        }
307
308        context.vm.frame_mut().iterators.push(iterator_record);
309
310        Ok(())
311    }
312}
313
314impl Operation for GeneratorDelegateNext {
315    const NAME: &'static str = "GeneratorDelegateNext";
316    const INSTRUCTION: &'static str = "INST - GeneratorDelegateNext";
317    const COST: u8 = 18;
318}
319
320/// `GeneratorDelegateResume` implements the Opcode Operation for `Opcode::GeneratorDelegateResume`
321///
322/// Operation:
323///  - Resume the generator with yield delegate logic after it awaits a value.
324#[derive(Debug, Clone, Copy)]
325pub(crate) struct GeneratorDelegateResume;
326
327impl GeneratorDelegateResume {
328    #[inline(always)]
329    pub(super) fn operation(
330        (return_gen, exit, value, resume_kind, is_return): (
331            u32,
332            u32,
333            VaryingOperand,
334            VaryingOperand,
335            VaryingOperand,
336        ),
337        context: &mut Context,
338    ) -> JsResult<()> {
339        let resume_kind = context
340            .vm
341            .get_register(resume_kind.into())
342            .to_generator_resume_kind();
343        let result = context.vm.get_register(value.into()).clone();
344        let is_return = context.vm.get_register(is_return.into()).to_boolean();
345
346        let mut iterator = context
347            .vm
348            .frame_mut()
349            .iterators
350            .pop()
351            .expect("iterator stack should have at least an iterator");
352
353        if resume_kind == GeneratorResumeKind::Throw {
354            return Err(JsError::from_opaque(result.clone()));
355        }
356
357        iterator.update_result(result.clone(), context)?;
358
359        if iterator.done() {
360            let result = iterator.value(context)?;
361            context.vm.set_register(value.into(), result);
362            context.vm.frame_mut().pc = if is_return { return_gen } else { exit };
363            return Ok(());
364        }
365
366        context.vm.frame_mut().iterators.push(iterator);
367
368        Ok(())
369    }
370}
371
372impl Operation for GeneratorDelegateResume {
373    const NAME: &'static str = "GeneratorDelegateResume";
374    const INSTRUCTION: &'static str = "INST - GeneratorDelegateResume";
375    const COST: u8 = 7;
376}