boa_engine/vm/opcode/generator/
mod.rs1pub(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#[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#[derive(Debug, Clone, Copy)]
110pub(crate) struct AsyncGeneratorClose;
111
112impl AsyncGeneratorClose {
113 #[inline(always)]
114 pub(super) fn operation((): (), context: &mut Context) {
115 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 r#gen.data_mut().state = AsyncGeneratorState::DrainingQueue;
131
132 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 AsyncGenerator::complete_step(&generator, result, true, None, context);
146 AsyncGenerator::drain_queue(&generator, context);
148
149 }
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#[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#[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#[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 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 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#[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}