Skip to content

Commit 7f722a7

Browse files
committed
perf(vm): Reduce suspended Vm memory footprint
1 parent 50414fa commit 7f722a7

File tree

5 files changed

+120
-42
lines changed

5 files changed

+120
-42
lines changed

nova_vm/src/ecmascript/builtins/control_abstraction_objects/async_function_objects/await_reaction.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@ use crate::{
1818
promise_prototype::inner_promise_then,
1919
},
2020
promise::Promise,
21+
ECMAScriptFunction,
2122
},
2223
execution::{Agent, ExecutionContext},
2324
types::Value,
2425
},
25-
engine::{Executable, ExecutionResult, Vm},
26+
engine::{ExecutionResult, SuspendedVm},
2627
heap::{CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, WorkQueues},
2728
};
2829

@@ -66,10 +67,12 @@ impl AwaitReactionIdentifier {
6667
// 3. d. Resume the suspended evaluation of asyncContext using NormalCompletion(v) as the result of the operation that suspended it.
6768
// 5. d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it.
6869
let vm = agent[self].vm.take().unwrap();
69-
let executable = agent[self].executable.take().unwrap();
70+
let async_function = agent[self].async_function.unwrap();
71+
// SAFETY: We keep the async function alive.
72+
let executable = unsafe { agent[async_function].compiled_bytecode.unwrap().as_ref() };
7073
let execution_result = match reaction_type {
71-
PromiseReactionType::Fulfill => vm.resume(agent, &executable, value),
72-
PromiseReactionType::Reject => vm.resume_throw(agent, &executable, value),
74+
PromiseReactionType::Fulfill => vm.resume(agent, executable, value),
75+
PromiseReactionType::Reject => vm.resume_throw(agent, executable, value),
7376
};
7477

7578
match execution_result {
@@ -97,7 +100,6 @@ impl AwaitReactionIdentifier {
97100
// [27.7.5.3 Await ( value )](https://tc39.es/ecma262/#await)
98101
// 8. Remove asyncContext 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.
99102
agent[self].vm = Some(vm);
100-
agent[self].executable = Some(executable);
101103
agent[self].execution_context = Some(agent.execution_context_stack.pop().unwrap());
102104

103105
// `handler` corresponds to the `fulfilledClosure` and `rejectedClosure` functions,
@@ -162,8 +164,8 @@ impl HeapMarkAndSweep for AwaitReactionIdentifier {
162164

163165
#[derive(Debug)]
164166
pub(crate) struct AwaitReaction {
165-
pub(crate) vm: Option<Vm>,
166-
pub(crate) executable: Option<Executable>,
167+
pub(crate) vm: Option<SuspendedVm>,
168+
pub(crate) async_function: Option<ECMAScriptFunction>,
167169
pub(crate) execution_context: Option<ExecutionContext>,
168170
pub(crate) return_promise_capability: PromiseCapability,
169171
}
@@ -178,13 +180,13 @@ impl CreateHeapData<AwaitReaction, AwaitReactionIdentifier> for Heap {
178180
impl HeapMarkAndSweep for AwaitReaction {
179181
fn mark_values(&self, queues: &mut WorkQueues) {
180182
self.vm.mark_values(queues);
181-
self.executable.mark_values(queues);
183+
self.async_function.mark_values(queues);
182184
self.return_promise_capability.mark_values(queues);
183185
}
184186

185187
fn sweep_values(&mut self, compactions: &CompactionLists) {
186188
self.vm.sweep_values(compactions);
187-
self.executable.sweep_values(compactions);
189+
self.async_function.sweep_values(compactions);
188190
self.return_promise_capability.sweep_values(compactions);
189191
}
190192
}

nova_vm/src/ecmascript/builtins/control_abstraction_objects/generator_objects.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::{
1515
InternalMethods, InternalSlots, IntoObject, IntoValue, Object, OrdinaryObject, Value,
1616
},
1717
},
18-
engine::{Executable, ExecutionResult, Vm},
18+
engine::{Executable, ExecutionResult, SuspendedVm, Vm},
1919
heap::{
2020
indexes::{BaseIndex, GeneratorIndex},
2121
CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, WorkQueues,
@@ -337,7 +337,7 @@ pub struct GeneratorHeapData {
337337

338338
#[derive(Debug)]
339339
pub(crate) enum VmOrArguments {
340-
Vm(Vm),
340+
Vm(SuspendedVm),
341341
Arguments(Box<[Value]>),
342342
}
343343

nova_vm/src/ecmascript/syntax_directed_operations/function_definitions.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,18 @@ pub(crate) fn evaluate_async_function_body(
300300
//} else {
301301
// 4. Else,
302302
// a. Perform AsyncFunctionStart(promiseCapability, FunctionBody).
303-
let data = CompileFunctionBodyData::new(agent, function_object);
304-
let exe = Executable::compile_function_body(agent, data);
303+
let exe = if let Some(exe) = agent[function_object].compiled_bytecode {
304+
// SAFETY: exe is a non-null pointer pointing to an initialized
305+
// Executable that cannot have a live mutable reference to it.
306+
unsafe { exe.as_ref() }
307+
} else {
308+
let data = CompileFunctionBodyData::new(agent, function_object);
309+
let exe = Box::new(Executable::compile_function_body(agent, data));
310+
let exe = NonNull::from(Box::leak(exe));
311+
agent[function_object].compiled_bytecode = Some(exe);
312+
// SAFETY: Same as above, only more.
313+
unsafe { exe.as_ref() }
314+
};
305315

306316
// AsyncFunctionStart will run the function until it returns, throws or gets suspended with
307317
// an await.
@@ -329,7 +339,7 @@ pub(crate) fn evaluate_async_function_body(
329339
// cloning it would mess up the execution context stack.
330340
let handler = PromiseReactionHandler::Await(agent.heap.create(AwaitReaction {
331341
vm: Some(vm),
332-
executable: Some(exe),
342+
async_function: Some(function_object),
333343
execution_context: Some(agent.running_execution_context().clone()),
334344
return_promise_capability: promise_capability,
335345
}));

nova_vm/src/engine/bytecode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ pub(crate) use executable::{
1212
NamedEvaluationParameter, SendableRef,
1313
};
1414
pub(crate) use instructions::{Instruction, InstructionIter};
15-
pub(crate) use vm::{instanceof_operator, ExecutionResult, Vm};
15+
pub(crate) use vm::{instanceof_operator, ExecutionResult, SuspendedVm, Vm};

nova_vm/src/engine/bytecode/vm.rs

Lines changed: 93 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,14 @@ unsafe impl Sync for EmptyParametersList {}
6464
pub(crate) enum ExecutionResult {
6565
Return(Value),
6666
Throw(JsError),
67-
Await { vm: Vm, awaited_value: Value },
68-
Yield { vm: Vm, yielded_value: Value },
67+
Await {
68+
vm: SuspendedVm,
69+
awaited_value: Value,
70+
},
71+
Yield {
72+
vm: SuspendedVm,
73+
yielded_value: Value,
74+
},
6975
}
7076
impl ExecutionResult {
7177
pub(crate) fn into_js_result(self) -> JsResult<Value> {
@@ -113,6 +119,55 @@ pub(crate) struct Vm {
113119
reference: Option<Reference>,
114120
}
115121

122+
#[derive(Debug)]
123+
pub(crate) struct SuspendedVm {
124+
ip: usize,
125+
/// Note: Stack is non-empty only if the code awaits inside a call
126+
/// expression. This is reasonably rare that we can expect the stack to
127+
/// usually be empty. In this case this Box is an empty dangling pointer
128+
/// and no heap data clone is required.
129+
stack: Box<[Value]>,
130+
/// Note: Reference stack is non-empty only if the code awaits inside a
131+
/// call expression. This means that usually no heap data clone is
132+
/// required.
133+
reference_stack: Box<[Reference]>,
134+
/// Note: Iterator stack is non-empty only if the code awaits inside a
135+
/// for-in or for-of loop. This means that often no heap data clone is
136+
/// required.
137+
iterator_stack: Box<[VmIterator]>,
138+
/// Note: Exception jump stack is non-empty only if the code awaits inside
139+
/// a try block. This means that often no heap data clone is required.
140+
exception_jump_target_stack: Box<[ExceptionJumpTarget]>,
141+
}
142+
143+
impl SuspendedVm {
144+
pub(crate) fn resume(
145+
self,
146+
agent: &mut Agent,
147+
executable: &Executable,
148+
value: Value,
149+
) -> ExecutionResult {
150+
let vm = Vm::from_suspended(self);
151+
vm.resume(agent, executable, value)
152+
}
153+
154+
pub(crate) fn resume_throw(
155+
self,
156+
agent: &mut Agent,
157+
executable: &Executable,
158+
err: Value,
159+
) -> ExecutionResult {
160+
// Optimisation: Avoid unsuspending the Vm if we're just going to throw
161+
// out of it immediately.
162+
if self.exception_jump_target_stack.is_empty() {
163+
let err = JsError::new(err);
164+
return ExecutionResult::Throw(err);
165+
}
166+
let vm = Vm::from_suspended(self);
167+
vm.resume_throw(agent, executable, err)
168+
}
169+
}
170+
116171
impl Vm {
117172
fn new() -> Self {
118173
Self {
@@ -127,6 +182,29 @@ impl Vm {
127182
}
128183
}
129184

185+
fn suspend(self) -> SuspendedVm {
186+
SuspendedVm {
187+
ip: self.ip,
188+
stack: self.stack.into_boxed_slice(),
189+
reference_stack: self.reference_stack.into_boxed_slice(),
190+
iterator_stack: self.iterator_stack.into_boxed_slice(),
191+
exception_jump_target_stack: self.exception_jump_target_stack.into_boxed_slice(),
192+
}
193+
}
194+
195+
fn from_suspended(suspended: SuspendedVm) -> Self {
196+
Self {
197+
ip: suspended.ip,
198+
stack: suspended.stack.into_vec(),
199+
reference_stack: suspended.reference_stack.into_vec(),
200+
iterator_stack: suspended.iterator_stack.into_vec(),
201+
exception_jump_target_stack: suspended.exception_jump_target_stack.into_vec(),
202+
result: None,
203+
exception: None,
204+
reference: None,
205+
}
206+
}
207+
130208
fn fetch_identifier(&self, exe: &Executable, index: usize) -> String {
131209
String::try_from(exe.constants[index])
132210
.expect("Invalid identifier index: Value was not a String")
@@ -183,7 +261,7 @@ impl Vm {
183261
vm.inner_execute(agent, executable)
184262
}
185263

186-
pub(crate) fn resume(
264+
pub fn resume(
187265
mut self,
188266
agent: &mut Agent,
189267
executable: &Executable,
@@ -193,7 +271,7 @@ impl Vm {
193271
self.inner_execute(agent, executable)
194272
}
195273

196-
pub(crate) fn resume_throw(
274+
pub fn resume_throw(
197275
mut self,
198276
agent: &mut Agent,
199277
executable: &Executable,
@@ -217,14 +295,14 @@ impl Vm {
217295
Ok(ContinuationKind::Yield) => {
218296
let yielded_value = self.result.take().unwrap();
219297
return ExecutionResult::Yield {
220-
vm: self,
298+
vm: self.suspend(),
221299
yielded_value,
222300
};
223301
}
224302
Ok(ContinuationKind::Await) => {
225303
let awaited_value = self.result.take().unwrap();
226304
return ExecutionResult::Await {
227-
vm: self,
305+
vm: self.suspend(),
228306
awaited_value,
229307
};
230308
}
@@ -2205,30 +2283,18 @@ impl HeapMarkAndSweep for ExceptionJumpTarget {
22052283
}
22062284
}
22072285

2208-
impl HeapMarkAndSweep for Vm {
2286+
impl HeapMarkAndSweep for SuspendedVm {
22092287
fn mark_values(&self, queues: &mut WorkQueues) {
2210-
self.stack.as_slice().mark_values(queues);
2211-
self.reference_stack.as_slice().mark_values(queues);
2212-
self.iterator_stack.as_slice().mark_values(queues);
2213-
self.exception_jump_target_stack
2214-
.as_slice()
2215-
.mark_values(queues);
2216-
self.result.mark_values(queues);
2217-
self.exception.mark_values(queues);
2218-
self.reference.mark_values(queues);
2288+
self.stack.mark_values(queues);
2289+
self.reference_stack.mark_values(queues);
2290+
self.iterator_stack.mark_values(queues);
2291+
self.exception_jump_target_stack.mark_values(queues);
22192292
}
22202293

22212294
fn sweep_values(&mut self, compactions: &CompactionLists) {
2222-
self.stack.as_mut_slice().sweep_values(compactions);
2223-
self.reference_stack
2224-
.as_mut_slice()
2225-
.sweep_values(compactions);
2226-
self.iterator_stack.as_mut_slice().sweep_values(compactions);
2227-
self.exception_jump_target_stack
2228-
.as_mut_slice()
2229-
.sweep_values(compactions);
2230-
self.result.sweep_values(compactions);
2231-
self.exception.sweep_values(compactions);
2232-
self.reference.sweep_values(compactions);
2295+
self.stack.sweep_values(compactions);
2296+
self.reference_stack.sweep_values(compactions);
2297+
self.iterator_stack.sweep_values(compactions);
2298+
self.exception_jump_target_stack.sweep_values(compactions);
22332299
}
22342300
}

0 commit comments

Comments
 (0)