Skip to content

Commit 4f33a15

Browse files
committed
Move async generator object to the stack
1 parent a7aea59 commit 4f33a15

File tree

8 files changed

+107
-37
lines changed

8 files changed

+107
-37
lines changed

core/engine/src/builtins/generator/mod.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use crate::{
2020
string::common::StaticJsStrings,
2121
symbol::JsSymbol,
2222
value::JsValue,
23-
vm::{CallFrame, CompletionRecord, GeneratorResumeKind},
23+
vm::{CallFrame, CallFrameFlags, CompletionRecord, GeneratorResumeKind},
2424
Context, JsArgs, JsData, JsError, JsResult, JsString,
2525
};
2626
use boa_gc::{custom_trace, Finalize, Trace};
@@ -74,6 +74,10 @@ impl GeneratorContext {
7474

7575
frame.fp = CallFrame::FUNCTION_PROLOGUE + frame.argument_count;
7676

77+
// NOTE: Since we get a pre-built call frame with stack, and we reuse them.
78+
// So we don't need to push the locals in subsuquent calls.
79+
frame.flags |= CallFrameFlags::LOCALS_ALREADY_PUSHED;
80+
7781
Self {
7882
call_frame: Some(frame),
7983
stack,
@@ -107,6 +111,13 @@ impl GeneratorContext {
107111
assert!(self.call_frame.is_some());
108112
result
109113
}
114+
115+
/// Returns the async generator object, if the function that this [`GeneratorContext`] is from an async generator, [`None`] otherwise.
116+
pub(crate) fn async_generator_object(&self) -> Option<JsObject> {
117+
self.call_frame
118+
.as_ref()
119+
.and_then(|frame| frame.async_generator_object(&self.stack))
120+
}
110121
}
111122

112123
/// The internal representation of a `Generator` object.

core/engine/src/bytecompiler/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,8 @@ pub struct ByteCompiler<'ctx> {
255255
/// The number of arguments expected.
256256
pub(crate) length: u32,
257257

258+
pub(crate) locals_count: u32,
259+
258260
/// \[\[ThisMode\]\]
259261
pub(crate) this_mode: ThisMode,
260262

@@ -327,6 +329,7 @@ impl<'ctx> ByteCompiler<'ctx> {
327329
params: FormalParameterList::default(),
328330
current_open_environments_count: 0,
329331

332+
locals_count: 0,
330333
current_stack_value_count: 0,
331334
code_block_flags,
332335
handlers: ThinVec::default(),
@@ -1520,9 +1523,17 @@ impl<'ctx> ByteCompiler<'ctx> {
15201523
}
15211524
self.r#return(false);
15221525

1526+
if self.is_async_generator() {
1527+
self.locals_count += 1;
1528+
}
1529+
for handler in &mut self.handlers {
1530+
handler.stack_count += self.locals_count;
1531+
}
1532+
15231533
CodeBlock {
15241534
name: self.function_name,
15251535
length: self.length,
1536+
locals_count: self.locals_count,
15261537
this_mode: self.this_mode,
15271538
params: self.params,
15281539
bytecode: self.bytecode.into_boxed_slice(),

core/engine/src/vm/call_frame/mod.rs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ bitflags::bitflags! {
2525

2626
/// Was this [`CallFrame`] created from the `__construct__()` internal object method?
2727
const CONSTRUCT = 0b0000_0010;
28+
29+
/// Does this [`CallFrame`] need to push local variables on [`Vm::push_frame()`].
30+
const LOCALS_ALREADY_PUSHED = 0b0000_0100;
2831
}
2932
}
3033

@@ -40,10 +43,6 @@ pub struct CallFrame {
4043
pub(crate) argument_count: u32,
4144
pub(crate) promise_capability: Option<PromiseCapability>,
4245

43-
// When an async generator is resumed, the generator object is needed
44-
// to fulfill the steps 4.e-j in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart).
45-
pub(crate) async_generator: Option<JsObject>,
46-
4746
// Iterators and their `[[Done]]` flags that must be closed when an abrupt completion is thrown.
4847
pub(crate) iterators: ThinVec<IteratorRecord>,
4948

@@ -123,6 +122,7 @@ impl CallFrame {
123122
pub(crate) const FUNCTION_PROLOGUE: u32 = 2;
124123
pub(crate) const THIS_POSITION: u32 = 2;
125124
pub(crate) const FUNCTION_POSITION: u32 = 1;
125+
pub(crate) const ASYNC_GENERATOR_OBJECT_LOCAL_INDEX: u32 = 0;
126126

127127
/// Creates a new `CallFrame` with the provided `CodeBlock`.
128128
pub(crate) fn new(
@@ -138,7 +138,6 @@ impl CallFrame {
138138
env_fp: 0,
139139
argument_count: 0,
140140
promise_capability: None,
141-
async_generator: None,
142141
iterators: ThinVec::new(),
143142
binding_stack: Vec::new(),
144143
loop_iteration_count: 0,
@@ -201,6 +200,28 @@ impl CallFrame {
201200
vm.stack.truncate(fp as usize);
202201
}
203202

203+
/// Returns the async generator object, if the function that this [`GeneratorContext`] is from an async generator, [`None`] otherwise.
204+
pub(crate) fn async_generator_object(&self, stack: &[JsValue]) -> Option<JsObject> {
205+
if !self.code_block().is_async_generator() {
206+
return None;
207+
}
208+
209+
self.local(Self::ASYNC_GENERATOR_OBJECT_LOCAL_INDEX, stack)
210+
.as_object()
211+
.cloned()
212+
}
213+
214+
/// Returns the local at the given index.
215+
///
216+
/// # Panics
217+
///
218+
/// If the index is out of bounds.
219+
pub(crate) fn local<'stack>(&self, index: u32, stack: &'stack [JsValue]) -> &'stack JsValue {
220+
debug_assert!(index < self.code_block().locals_count);
221+
let at = self.fp + index;
222+
&stack[at as usize]
223+
}
224+
204225
/// Does this have the [`CallFrameFlags::EXIT_EARLY`] flag.
205226
pub(crate) fn exit_early(&self) -> bool {
206227
self.flags.contains(CallFrameFlags::EXIT_EARLY)
@@ -213,6 +234,10 @@ impl CallFrame {
213234
pub(crate) fn construct(&self) -> bool {
214235
self.flags.contains(CallFrameFlags::CONSTRUCT)
215236
}
237+
/// Does this [`CallFrame`] need to push local variables on [`Vm::push_frame()`].
238+
pub(crate) fn locals_already_pushed(&self) -> bool {
239+
self.flags.contains(CallFrameFlags::LOCALS_ALREADY_PUSHED)
240+
}
216241
}
217242

218243
/// ---- `CallFrame` stack methods ----

core/engine/src/vm/code_block.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,8 @@ pub struct CodeBlock {
177177
/// The number of arguments expected.
178178
pub(crate) length: u32,
179179

180+
pub(crate) locals_count: u32,
181+
180182
/// \[\[ThisMode\]\]
181183
pub(crate) this_mode: ThisMode,
182184

@@ -216,6 +218,7 @@ impl CodeBlock {
216218
name,
217219
flags: Cell::new(flags),
218220
length,
221+
locals_count: 0,
219222
this_mode: ThisMode::Global,
220223
params: FormalParameterList::default(),
221224
handlers: ThinVec::default(),
@@ -286,6 +289,13 @@ impl CodeBlock {
286289
self.flags.get().contains(CodeBlockFlags::IS_GENERATOR)
287290
}
288291

292+
/// Returns true if this function a async generator function.
293+
pub(crate) fn is_async_generator(&self) -> bool {
294+
self.flags
295+
.get()
296+
.contains(CodeBlockFlags::IS_ASYNC | CodeBlockFlags::IS_GENERATOR)
297+
}
298+
289299
/// Returns true if this function an async function.
290300
pub(crate) fn is_ordinary(&self) -> bool {
291301
!self.is_async() && !self.is_generator()

core/engine/src/vm/mod.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,17 @@ impl Vm {
162162
std::mem::swap(&mut self.environments, &mut frame.environments);
163163
std::mem::swap(&mut self.realm, &mut frame.realm);
164164

165+
// NOTE: We need to check if we already pushed the locals,
166+
// since generator-like functions push the same call
167+
// frame with pre-built stack.
168+
if !frame.locals_already_pushed() {
169+
let locals_count = frame.code_block().locals_count;
170+
self.stack.resize_with(
171+
current_stack_length + locals_count as usize,
172+
JsValue::undefined,
173+
);
174+
}
175+
165176
self.frames.push(frame);
166177
}
167178

core/engine/src/vm/opcode/await/mod.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,16 @@ impl Operation for Await {
4949
// d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it.
5050
let mut gen = captures.borrow_mut().take().expect("should only run once");
5151

52+
// NOTE: We need to get the object before resuming, since it could clear the stack.
53+
let async_generator = gen.async_generator_object();
54+
5255
gen.resume(
5356
Some(args.get_or_undefined(0).clone()),
5457
GeneratorResumeKind::Normal,
5558
context,
5659
);
5760

58-
if let Some(async_generator) = gen
59-
.call_frame
60-
.as_ref()
61-
.and_then(|f| f.async_generator.clone())
62-
{
61+
if let Some(async_generator) = async_generator {
6362
async_generator
6463
.downcast_mut::<AsyncGenerator>()
6564
.expect("must be async generator")
@@ -92,17 +91,16 @@ impl Operation for Await {
9291

9392
let mut gen = captures.borrow_mut().take().expect("should only run once");
9493

94+
// NOTE: We need to get the object before resuming, since it could clear the stack.
95+
let async_generator = gen.async_generator_object();
96+
9597
gen.resume(
9698
Some(args.get_or_undefined(0).clone()),
9799
GeneratorResumeKind::Throw,
98100
context,
99101
);
100102

101-
if let Some(async_generator) = gen
102-
.call_frame
103-
.as_ref()
104-
.and_then(|f| f.async_generator.clone())
105-
{
103+
if let Some(async_generator) = async_generator {
106104
async_generator
107105
.downcast_mut::<AsyncGenerator>()
108106
.expect("must be async generator")

core/engine/src/vm/opcode/generator/mod.rs

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ impl Operation for Generator {
4141
let this_function_object =
4242
active_function.expect("active function should be set to the generator");
4343

44-
let frame = GeneratorContext::from_current(context);
44+
let mut frame = GeneratorContext::from_current(context);
4545

4646
let proto = this_function_object
4747
.get(PROTOTYPE, context)
@@ -64,7 +64,7 @@ impl Operation for Generator {
6464
proto,
6565
AsyncGenerator {
6666
state: AsyncGeneratorState::SuspendedStart,
67-
context: Some(frame),
67+
context: None,
6868
queue: VecDeque::new(),
6969
},
7070
)
@@ -73,23 +73,29 @@ impl Operation for Generator {
7373
context.root_shape(),
7474
proto,
7575
crate::builtins::generator::Generator {
76-
state: GeneratorState::SuspendedStart { context: frame },
76+
state: GeneratorState::Completed,
7777
},
7878
)
7979
};
8080

8181
if r#async {
82-
let gen_clone = generator.clone();
82+
let fp = frame
83+
.call_frame
84+
.as_ref()
85+
.map_or(0, |frame| frame.fp as usize);
86+
frame.stack[fp] = generator.clone().into();
87+
8388
let mut gen = generator
8489
.downcast_mut::<AsyncGenerator>()
8590
.expect("must be object here");
86-
let gen_context = gen.context.as_mut().expect("must exist");
87-
// TODO: try to move this to the context itself.
88-
gen_context
89-
.call_frame
90-
.as_mut()
91-
.expect("should have a call frame initialized")
92-
.async_generator = Some(gen_clone);
91+
92+
gen.context = Some(frame);
93+
} else {
94+
let mut gen = generator
95+
.downcast_mut::<crate::builtins::generator::Generator>()
96+
.expect("must be object here");
97+
98+
gen.state = GeneratorState::SuspendedStart { context: frame };
9399
}
94100

95101
context.vm.set_return_value(generator.into());
@@ -111,14 +117,13 @@ impl Operation for AsyncGeneratorClose {
111117

112118
fn execute(context: &mut Context) -> JsResult<CompletionType> {
113119
// Step 3.e-g in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart)
114-
let generator_object = context
120+
let async_generator_object = context
115121
.vm
116122
.frame()
117-
.async_generator
118-
.clone()
123+
.async_generator_object(&context.vm.stack)
119124
.expect("There should be a object");
120125

121-
let mut gen = generator_object
126+
let mut gen = async_generator_object
122127
.downcast_mut::<AsyncGenerator>()
123128
.expect("must be async generator");
124129

@@ -136,7 +141,7 @@ impl Operation for AsyncGeneratorClose {
136141
} else {
137142
AsyncGenerator::complete_step(&next, Ok(return_value), true, None, context);
138143
}
139-
AsyncGenerator::drain_queue(&generator_object, context);
144+
AsyncGenerator::drain_queue(&async_generator_object, context);
140145

141146
Ok(CompletionType::Normal)
142147
}

core/engine/src/vm/opcode/generator/yield_stm.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,13 @@ impl Operation for AsyncGeneratorYield {
3838
fn execute(context: &mut Context) -> JsResult<CompletionType> {
3939
let value = context.vm.pop();
4040

41-
let async_gen = context
41+
let async_generator_object = context
4242
.vm
4343
.frame()
44-
.async_generator
45-
.clone()
44+
.async_generator_object(&context.vm.stack)
4645
.expect("`AsyncGeneratorYield` must only be called inside async generators");
4746
let completion = Ok(value);
48-
let next = async_gen
47+
let next = async_generator_object
4948
.downcast_mut::<AsyncGenerator>()
5049
.expect("must be async generator object")
5150
.queue
@@ -55,7 +54,7 @@ impl Operation for AsyncGeneratorYield {
5554
// TODO: 7. Let previousContext be the second to top element of the execution context stack.
5655
AsyncGenerator::complete_step(&next, completion, false, None, context);
5756

58-
let mut generator_object_mut = async_gen.borrow_mut();
57+
let mut generator_object_mut = async_generator_object.borrow_mut();
5958
let gen = generator_object_mut
6059
.downcast_mut::<AsyncGenerator>()
6160
.expect("must be async generator object");

0 commit comments

Comments
 (0)