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}