Skip to main content

nova_vm/ecmascript/builtins/control_abstraction_objects/
generator_objects.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5use crate::{
6    ecmascript::{
7        Agent, ExceptionType, ExecutionContext, InternalMethods, InternalSlots, JsError, JsResult,
8        OrdinaryObject, ProtoIntrinsics, Value, create_iter_result_object, object_handle,
9    },
10    engine::{
11        Bindable, Executable, ExecutionResult, GcScope, Scopable, SuspendedVm, bindable_handle,
12    },
13    heap::{
14        ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap,
15        HeapMarkAndSweep, HeapSweepWeakReference, WorkQueues, arena_vec_access,
16    },
17};
18
19/// ## [27.5 Generator Objects](https://tc39.es/ecma262/#sec-generator-objects)
20///
21/// A Generator is created by calling a generator function and conforms to both
22/// the iterator interface and the iterable interface.
23///
24/// Generator instances directly inherit properties from the initial value of
25/// the **"prototype"** property of the generator function that created the
26/// instance. Generator instances indirectly inherit properties from
27/// %GeneratorPrototype%.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
29#[repr(transparent)]
30pub struct Generator<'a>(BaseIndex<'a, GeneratorHeapData<'static>>);
31object_handle!(Generator);
32arena_vec_access!(Generator, 'a, GeneratorHeapData, generators);
33
34impl Generator<'_> {
35    /// ### [27.5.3.3 GeneratorResume ( generator, value, generatorBrand )](https://tc39.es/ecma262/#sec-generatorresume)
36    pub(crate) fn resume<'a>(
37        self,
38        agent: &mut Agent,
39        value: Value,
40        mut gc: GcScope<'a, '_>,
41    ) -> JsResult<'a, Value<'a>> {
42        let value = value.bind(gc.nogc());
43        let generator = self.bind(gc.nogc());
44        // 1. Let state be ? GeneratorValidate(generator, generatorBrand).
45        match generator.get(agent).generator_state.as_ref().unwrap() {
46            GeneratorState::Executing => {
47                return Err(agent.throw_exception_with_static_message(
48                    ExceptionType::TypeError,
49                    "The generator is currently running",
50                    gc.into_nogc(),
51                ));
52            }
53            GeneratorState::Completed => {
54                // 2. If state is completed, return CreateIterResultObject(undefined, true).
55                return create_iter_result_object(agent, Value::Undefined, true, gc.into_nogc())
56                    .map(|o| o.into());
57            }
58            GeneratorState::SuspendedStart(_) | GeneratorState::SuspendedYield(_) => {
59                // 3. Assert: state is either suspended-start or suspended-yield.
60            }
61        };
62
63        // 7. Set generator.[[GeneratorState]] to executing.
64        let SuspendedGeneratorState {
65            vm,
66            executable,
67            execution_context,
68        } = match generator
69            .get_mut(agent)
70            .generator_state
71            .replace(GeneratorState::Executing)
72        {
73            Some(GeneratorState::SuspendedYield(state))
74            | Some(GeneratorState::SuspendedStart(state)) => state,
75            _ => unreachable!(),
76        };
77        let executable = executable.scope(agent, gc.nogc());
78
79        // 4. Let genContext be generator.[[GeneratorContext]].
80        // 5. Let methodContext be the running execution context.
81        // 6. Suspend methodContext.
82        // 8. Push genContext onto the execution context stack; genContext is now the running
83        // execution context.
84        agent.push_execution_context(execution_context);
85
86        let saved = generator.scope(agent, gc.nogc());
87
88        // 9. Resume the suspended evaluation of genContext using NormalCompletion(value) as the
89        // result of the operation that suspended it. Let result be the value returned by the
90        // resumed computation.
91        let execution_result = vm.resume(agent, executable.clone(), value.unbind(), gc.reborrow());
92
93        let execution_result = execution_result.unbind();
94        let gc = gc.into_nogc();
95        let generator = saved.get(agent).bind(gc);
96        let execution_result = execution_result.bind(gc);
97
98        // GeneratorStart: 4.f. Remove acGenContext from the execution context stack and restore the
99        // execution context that is at the top of the execution context stack as the running
100        // execution context.
101        // GeneratorYield 6 is the same.
102        let execution_context = agent.pop_execution_context().unwrap();
103
104        // 10. Assert: When we return here, genContext has already been removed
105        // from the execution context stack and methodContext is the currently
106        // running execution context.
107        // 11. Return ? result.
108        match execution_result {
109            ExecutionResult::Return(result_value) => {
110                // GeneratorStart step 4:
111                // g. Set acGenerator.[[GeneratorState]] to completed.
112                // h. NOTE: Once a generator enters the completed state it never leaves it and its
113                // associated execution context is never resumed. Any execution state associated
114                // with acGenerator can be discarded at this point.
115                generator.get_mut(agent).generator_state = Some(GeneratorState::Completed);
116                // i. If result is a normal completion, then
117                //    i. Let resultValue be undefined.
118                // j. Else if result is a return completion, then
119                //    i. Let resultValue be result.[[Value]].
120                // l. Return CreateIterResultObject(resultValue, true).
121                create_iter_result_object(agent, result_value, true, gc.into_nogc())
122                    .map(|o| o.into())
123            }
124            ExecutionResult::Throw(err) => {
125                // GeneratorStart step 4:
126                // g. Set acGenerator.[[GeneratorState]] to completed.
127                // h. NOTE: Once a generator enters the completed state it never leaves it and its
128                // associated execution context is never resumed. Any execution state associated
129                // with acGenerator can be discarded at this point.
130                generator.get_mut(agent).generator_state = Some(GeneratorState::Completed);
131                // k. i. Assert: result is a throw completion.
132                //    ii. Return ? result.
133                Err(err.unbind())
134            }
135            ExecutionResult::Yield { vm, yielded_value } => {
136                // Yield:
137                // 3. Otherwise, return ? GeneratorYield(CreateIterResultObject(value, false)).
138                // GeneratorYield:
139                // 3. Let generator be the value of the Generator component of genContext.
140                // 5. Set generator.[[GeneratorState]] to suspended-yield.
141                generator.get_mut(agent).generator_state =
142                    Some(GeneratorState::SuspendedYield(SuspendedGeneratorState {
143                        vm,
144                        executable: executable.get(agent),
145                        execution_context,
146                    }));
147                // 8. Resume callerContext passing NormalCompletion(iterNextObj). ...
148                // NOTE: `callerContext` here is the `GeneratorResume` execution context.
149                Ok(yielded_value)
150            }
151            ExecutionResult::Await { .. } => unreachable!(),
152        }
153    }
154
155    /// ### [27.5.3.4 GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand )](https://tc39.es/ecma262/#sec-generatorresumeabrupt)
156    /// NOTE: This method only accepts throw completions.
157    pub(crate) fn resume_throw<'a>(
158        self,
159        agent: &mut Agent,
160        value: Value,
161        mut gc: GcScope<'a, '_>,
162    ) -> JsResult<'a, Value<'a>> {
163        let value = value.bind(gc.nogc());
164        let generator = self.bind(gc.nogc());
165        // 1. Let state be ? GeneratorValidate(generator, generatorBrand).
166        match generator.get(agent).generator_state.as_ref().unwrap() {
167            GeneratorState::SuspendedStart(_) => {
168                // 2. If state is suspended-start, then
169                // a. Set generator.[[GeneratorState]] to completed.
170                // b. NOTE: Once a generator enters the completed state it never leaves it and its
171                // associated execution context is never resumed. Any execution state associated
172                // with generator can be discarded at this point.
173                generator.get_mut(agent).generator_state = Some(GeneratorState::Completed);
174                // c. Set state to completed.
175
176                // 3. If state is completed, then
177                // b. Return ? abruptCompletion.
178                return Err(JsError::new(value.unbind()));
179            }
180            GeneratorState::SuspendedYield(_) => {
181                // 4. Assert: state is suspended-yield.
182            }
183            GeneratorState::Executing => {
184                return Err(agent.throw_exception_with_static_message(
185                    ExceptionType::TypeError,
186                    "The generator is currently running",
187                    gc.into_nogc(),
188                ));
189            }
190            GeneratorState::Completed => {
191                // 3. If state is completed, then
192                //    b. Return ? abruptCompletion.
193                return Err(JsError::new(value.unbind()));
194            }
195        };
196
197        // 8. Set generator.[[GeneratorState]] to executing.
198        let Some(GeneratorState::SuspendedYield(SuspendedGeneratorState {
199            vm,
200            executable,
201            execution_context,
202        })) = generator
203            .get_mut(agent)
204            .generator_state
205            .replace(GeneratorState::Executing)
206        else {
207            unreachable!()
208        };
209        let generator = generator.scope(agent, gc.nogc());
210        let executable = executable.scope(agent, gc.nogc());
211
212        // 5. Let genContext be generator.[[GeneratorContext]].
213        // 6. Let methodContext be the running execution context.
214        // 7. Suspend methodContext.
215        // 9. Push genContext onto the execution context stack; genContext is now the running
216        // execution context.
217        agent.push_execution_context(execution_context);
218
219        // 10. Resume the suspended evaluation of genContext using NormalCompletion(value) as the
220        // result of the operation that suspended it. Let result be the value returned by the
221        // resumed computation.
222        let execution_result = vm
223            .resume_throw(agent, executable.clone(), value.unbind(), gc.reborrow())
224            .unbind();
225        let gc = gc.into_nogc();
226        let execution_result = execution_result.bind(gc);
227        // SAFETY: shared but not stored by resume.
228        let executable = unsafe { executable.take(agent).bind(gc) };
229        // SAFETY: not shared.
230        let generator = unsafe { generator.take(agent).bind(gc) };
231
232        // GeneratorStart: 4.f. Remove acGenContext from the execution context stack and restore the
233        // execution context that is at the top of the execution context stack as the running
234        // execution context.
235        // GeneratorYield 6 is the same.
236        let execution_context = agent.pop_execution_context().unwrap();
237
238        // 11. Assert: When we return here, genContext has already been removed
239        // from the execution context stack and methodContext is the currently
240        // running execution context.
241        // 12. Return ? result.
242        match execution_result {
243            ExecutionResult::Return(result) => {
244                generator.get_mut(agent).generator_state = Some(GeneratorState::Completed);
245                create_iter_result_object(agent, result.unbind(), true, gc.into_nogc())
246                    .map(|o| o.into())
247            }
248            ExecutionResult::Throw(err) => {
249                generator.get_mut(agent).generator_state = Some(GeneratorState::Completed);
250                Err(err)
251            }
252            ExecutionResult::Yield { vm, yielded_value } => {
253                generator.get_mut(agent).generator_state =
254                    Some(GeneratorState::SuspendedYield(SuspendedGeneratorState {
255                        vm,
256                        executable: executable.unbind(),
257                        execution_context,
258                    }));
259                Ok(yielded_value.unbind())
260            }
261            ExecutionResult::Await { .. } => unreachable!(),
262        }
263    }
264
265    /// ### [27.5.3.4 GeneratorResumeAbrupt ( generator, abruptCompletion, generatorBrand )](https://tc39.es/ecma262/#sec-generatorresumeabrupt)
266    /// NOTE: This method only accepts return completions.
267    pub(crate) fn resume_return<'a>(
268        self,
269        agent: &mut Agent,
270        abrupt_completion: Value,
271        mut gc: GcScope<'a, '_>,
272    ) -> JsResult<'a, Value<'a>> {
273        let abrupt_completion = abrupt_completion.bind(gc.nogc());
274        let generator = self.bind(gc.nogc());
275        // 1. Let state be ? GeneratorValidate(generator, generatorBrand).
276        match generator.get(agent).generator_state.as_ref().unwrap() {
277            GeneratorState::SuspendedStart(_) => {
278                // 2. If state is suspended-start, then
279                // a. Set generator.[[GeneratorState]] to completed.
280                // b. NOTE: Once a generator enters the completed state it never leaves it and its
281                // associated execution context is never resumed. Any execution state associated
282                // with generator can be discarded at this point.
283                generator.get_mut(agent).generator_state = Some(GeneratorState::Completed);
284                // c. Set state to completed.
285
286                // 3. If abruptCompletion is a return completion, then
287                // i. Return CreateIteratorResultObject(abruptCompletion.[[Value]], true).
288                return create_iter_result_object(
289                    agent,
290                    abrupt_completion.unbind(),
291                    true,
292                    gc.into_nogc(),
293                )
294                .map(|o| o.into());
295            }
296            GeneratorState::SuspendedYield(_) => {
297                // 4. Assert: state is suspended-yield.
298            }
299            GeneratorState::Executing => {
300                return Err(agent.throw_exception_with_static_message(
301                    ExceptionType::TypeError,
302                    "The generator is currently running",
303                    gc.into_nogc(),
304                ));
305            }
306            GeneratorState::Completed => {
307                // 3. If abruptCompletion is a return completion, then
308                // i. Return CreateIteratorResultObject(abruptCompletion.[[Value]], true).
309                return create_iter_result_object(
310                    agent,
311                    abrupt_completion.unbind(),
312                    true,
313                    gc.into_nogc(),
314                )
315                .map(|o| o.into());
316            }
317        };
318
319        // 4. Assert: state is suspended-yield.
320        // 8. Set generator.[[GeneratorState]] to executing.
321        let Some(GeneratorState::SuspendedYield(SuspendedGeneratorState {
322            vm,
323            executable,
324            execution_context,
325        })) = generator
326            .get_mut(agent)
327            .generator_state
328            .replace(GeneratorState::Executing)
329        else {
330            unreachable!()
331        };
332        let generator = generator.scope(agent, gc.nogc());
333        let executable = executable.scope(agent, gc.nogc());
334
335        // 5. Let genContext be generator.[[GeneratorContext]].
336        // 6. Let methodContext be the running execution context.
337        // 7. Suspend methodContext.
338        // 9. Push genContext onto the execution context stack; genContext is now the running
339        // execution context.
340        agent.push_execution_context(execution_context);
341
342        // 10. Resume the suspended evaluation of genContext using
343        //     abruptCompletion as the result of the operation that suspended
344        //     it. Let result be the Completion Record returned by the resumed
345        //     computation.
346        let execution_result = vm
347            .resume_return(
348                agent,
349                executable.clone(),
350                abrupt_completion.unbind(),
351                gc.reborrow(),
352            )
353            .unbind();
354        let gc = gc.into_nogc();
355        let execution_result = execution_result.bind(gc);
356        // SAFETY: shared but not stored by resume.
357        let executable = unsafe { executable.take(agent).bind(gc) };
358        // SAFETY: not shared.
359        let generator = unsafe { generator.take(agent).bind(gc) };
360
361        // GeneratorStart: 4.f. Remove acGenContext from the execution context stack and restore the
362        // execution context that is at the top of the execution context stack as the running
363        // execution context.
364        // GeneratorYield 6 is the same.
365        let execution_context = agent.pop_execution_context().unwrap();
366
367        // 11. Assert: When we return here, genContext has already been removed
368        // from the execution context stack and methodContext is the currently
369        // running execution context.
370        // 12. Return ? result.
371        match execution_result {
372            ExecutionResult::Return(result) => {
373                generator.get_mut(agent).generator_state = Some(GeneratorState::Completed);
374                create_iter_result_object(agent, result, true, gc.into_nogc()).map(|o| o.into())
375            }
376            ExecutionResult::Throw(err) => {
377                generator.get_mut(agent).generator_state = Some(GeneratorState::Completed);
378                Err(err)
379            }
380            ExecutionResult::Yield { vm, yielded_value } => {
381                generator.get_mut(agent).generator_state =
382                    Some(GeneratorState::SuspendedYield(SuspendedGeneratorState {
383                        vm,
384                        executable: executable.unbind(),
385                        execution_context,
386                    }));
387                Ok(yielded_value)
388            }
389            ExecutionResult::Await { .. } => unreachable!(),
390        }
391    }
392}
393
394impl<'a> InternalSlots<'a> for Generator<'a> {
395    const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::Generator;
396
397    #[inline(always)]
398    fn get_backing_object(self, agent: &Agent) -> Option<OrdinaryObject<'static>> {
399        self.get(agent).object_index.unbind()
400    }
401
402    fn set_backing_object(self, agent: &mut Agent, backing_object: OrdinaryObject<'static>) {
403        assert!(
404            self.get_mut(agent)
405                .object_index
406                .replace(backing_object)
407                .is_none()
408        );
409    }
410}
411
412impl<'a> InternalMethods<'a> for Generator<'a> {}
413
414impl<'a> CreateHeapData<GeneratorHeapData<'a>, Generator<'a>> for Heap {
415    fn create(&mut self, data: GeneratorHeapData<'a>) -> Generator<'a> {
416        self.generators.push(data.unbind());
417        self.alloc_counter += core::mem::size_of::<GeneratorHeapData<'static>>();
418        Generator(BaseIndex::last(&self.generators))
419    }
420}
421
422impl HeapMarkAndSweep for Generator<'static> {
423    fn mark_values(&self, queues: &mut WorkQueues) {
424        queues.generators.push(*self);
425    }
426
427    fn sweep_values(&mut self, compactions: &CompactionLists) {
428        compactions.generators.shift_index(&mut self.0)
429    }
430}
431
432impl HeapSweepWeakReference for Generator<'static> {
433    fn sweep_weak_reference(self, compactions: &CompactionLists) -> Option<Self> {
434        compactions.generators.shift_weak_index(self.0).map(Self)
435    }
436}
437
438#[derive(Debug, Default)]
439pub(crate) struct GeneratorHeapData<'a> {
440    pub(crate) object_index: Option<OrdinaryObject<'a>>,
441    pub(crate) generator_state: Option<GeneratorState>,
442}
443
444#[derive(Debug)]
445pub(crate) struct SuspendedGeneratorState {
446    pub(crate) vm: SuspendedVm,
447    pub(crate) executable: Executable<'static>,
448    pub(crate) execution_context: ExecutionContext,
449}
450
451#[derive(Debug)]
452pub(crate) enum GeneratorState {
453    SuspendedStart(SuspendedGeneratorState),
454    SuspendedYield(SuspendedGeneratorState),
455    Executing,
456    Completed,
457}
458
459impl HeapMarkAndSweep for SuspendedGeneratorState {
460    fn mark_values(&self, queues: &mut WorkQueues) {
461        let Self {
462            vm,
463            executable,
464            execution_context,
465        } = self;
466        vm.mark_values(queues);
467        executable.mark_values(queues);
468        execution_context.mark_values(queues);
469    }
470
471    fn sweep_values(&mut self, compactions: &CompactionLists) {
472        let Self {
473            vm,
474            executable,
475            execution_context,
476        } = self;
477        vm.sweep_values(compactions);
478        executable.sweep_values(compactions);
479        execution_context.sweep_values(compactions);
480    }
481}
482
483bindable_handle!(GeneratorHeapData);
484
485impl HeapMarkAndSweep for GeneratorHeapData<'static> {
486    fn mark_values(&self, queues: &mut WorkQueues) {
487        let Self {
488            object_index,
489            generator_state,
490        } = self;
491        object_index.mark_values(queues);
492        generator_state.mark_values(queues);
493    }
494
495    fn sweep_values(&mut self, compactions: &CompactionLists) {
496        let Self {
497            object_index,
498            generator_state,
499        } = self;
500        object_index.sweep_values(compactions);
501        generator_state.sweep_values(compactions);
502    }
503}
504
505impl HeapMarkAndSweep for GeneratorState {
506    fn mark_values(&self, queues: &mut WorkQueues) {
507        match self {
508            GeneratorState::SuspendedStart(s) | GeneratorState::SuspendedYield(s) => {
509                s.mark_values(queues)
510            }
511            GeneratorState::Executing | GeneratorState::Completed => {}
512        }
513    }
514
515    fn sweep_values(&mut self, compactions: &CompactionLists) {
516        match self {
517            GeneratorState::SuspendedStart(s) | GeneratorState::SuspendedYield(s) => {
518                s.sweep_values(compactions)
519            }
520            GeneratorState::Executing | GeneratorState::Completed => {}
521        }
522    }
523}